@ruvector/edge-net 0.5.0 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,753 @@
1
+ /**
2
+ * @ruvector/edge-net Model Integrity System
3
+ *
4
+ * Content-addressed integrity with:
5
+ * - Canonical JSON signing
6
+ * - Threshold signatures with trust roots
7
+ * - Merkle chunk verification for streaming
8
+ * - Transparency log integration
9
+ *
10
+ * Design principle: Manifest is truth, everything else is replaceable.
11
+ *
12
+ * @module @ruvector/edge-net/models/integrity
13
+ */
14
+
15
+ import { createHash } from 'crypto';
16
+
17
+ // ============================================================================
18
+ // CANONICAL JSON
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Canonical JSON encoding for deterministic signing.
23
+ * - Keys sorted lexicographically
24
+ * - No whitespace
25
+ * - Unicode escaped consistently
26
+ * - Numbers without trailing zeros
27
+ */
28
+ export function canonicalize(obj) {
29
+ if (obj === null || obj === undefined) {
30
+ return 'null';
31
+ }
32
+
33
+ if (typeof obj === 'boolean') {
34
+ return obj ? 'true' : 'false';
35
+ }
36
+
37
+ if (typeof obj === 'number') {
38
+ if (!Number.isFinite(obj)) {
39
+ throw new Error('Cannot canonicalize Infinity or NaN');
40
+ }
41
+ // Use JSON for consistent number formatting
42
+ return JSON.stringify(obj);
43
+ }
44
+
45
+ if (typeof obj === 'string') {
46
+ // Escape unicode consistently
47
+ return JSON.stringify(obj).replace(/[\u007f-\uffff]/g, (c) => {
48
+ return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
49
+ });
50
+ }
51
+
52
+ if (Array.isArray(obj)) {
53
+ const elements = obj.map(canonicalize);
54
+ return '[' + elements.join(',') + ']';
55
+ }
56
+
57
+ if (typeof obj === 'object') {
58
+ const keys = Object.keys(obj).sort();
59
+ const pairs = keys
60
+ .filter(k => obj[k] !== undefined)
61
+ .map(k => canonicalize(k) + ':' + canonicalize(obj[k]));
62
+ return '{' + pairs.join(',') + '}';
63
+ }
64
+
65
+ throw new Error(`Cannot canonicalize type: ${typeof obj}`);
66
+ }
67
+
68
+ /**
69
+ * Hash canonical JSON bytes
70
+ */
71
+ export function hashCanonical(obj, algorithm = 'sha256') {
72
+ const canonical = canonicalize(obj);
73
+ const hash = createHash(algorithm);
74
+ hash.update(canonical, 'utf8');
75
+ return hash.digest('hex');
76
+ }
77
+
78
+ // ============================================================================
79
+ // TRUST ROOT
80
+ // ============================================================================
81
+
82
+ /**
83
+ * Built-in root keys shipped with SDK.
84
+ * These are the only keys trusted by default.
85
+ */
86
+ export const BUILTIN_ROOT_KEYS = Object.freeze({
87
+ 'ruvector-root-2024': {
88
+ keyId: 'ruvector-root-2024',
89
+ algorithm: 'ed25519',
90
+ publicKey: 'MCowBQYDK2VwAyEAaGVsbG8td29ybGQta2V5LXBsYWNlaG9sZGVy', // Placeholder
91
+ validFrom: '2024-01-01T00:00:00Z',
92
+ validUntil: '2030-01-01T00:00:00Z',
93
+ capabilities: ['sign-manifest', 'sign-adapter', 'delegate'],
94
+ },
95
+ 'ruvector-models-2024': {
96
+ keyId: 'ruvector-models-2024',
97
+ algorithm: 'ed25519',
98
+ publicKey: 'MCowBQYDK2VwAyEAbW9kZWxzLWtleS1wbGFjZWhvbGRlcg==', // Placeholder
99
+ validFrom: '2024-01-01T00:00:00Z',
100
+ validUntil: '2026-01-01T00:00:00Z',
101
+ capabilities: ['sign-manifest'],
102
+ delegatedBy: 'ruvector-root-2024',
103
+ },
104
+ });
105
+
106
+ /**
107
+ * Trust root configuration
108
+ */
109
+ export class TrustRoot {
110
+ constructor(options = {}) {
111
+ // Start with built-in keys
112
+ this.trustedKeys = new Map();
113
+ for (const [id, key] of Object.entries(BUILTIN_ROOT_KEYS)) {
114
+ this.trustedKeys.set(id, key);
115
+ }
116
+
117
+ // Add enterprise keys if configured
118
+ if (options.enterpriseKeys) {
119
+ for (const key of options.enterpriseKeys) {
120
+ this.addEnterpriseKey(key);
121
+ }
122
+ }
123
+
124
+ // Revocation list
125
+ this.revokedKeys = new Set(options.revokedKeys || []);
126
+
127
+ // Minimum signatures required for official releases
128
+ this.minimumSignaturesRequired = options.minimumSignaturesRequired || 1;
129
+
130
+ // Threshold for high-security operations (e.g., new root key)
131
+ this.thresholdSignaturesRequired = options.thresholdSignaturesRequired || 2;
132
+ }
133
+
134
+ /**
135
+ * Add an enterprise root key (for private deployments)
136
+ */
137
+ addEnterpriseKey(key) {
138
+ if (!key.keyId || !key.publicKey) {
139
+ throw new Error('Enterprise key must have keyId and publicKey');
140
+ }
141
+
142
+ // Verify delegation chain if not self-signed
143
+ if (key.delegatedBy && key.delegationSignature) {
144
+ const delegator = this.trustedKeys.get(key.delegatedBy);
145
+ if (!delegator) {
146
+ throw new Error(`Unknown delegator: ${key.delegatedBy}`);
147
+ }
148
+ if (!delegator.capabilities.includes('delegate')) {
149
+ throw new Error(`Key ${key.delegatedBy} cannot delegate`);
150
+ }
151
+ // In production, verify delegationSignature here
152
+ }
153
+
154
+ this.trustedKeys.set(key.keyId, {
155
+ ...key,
156
+ isEnterprise: true,
157
+ });
158
+ }
159
+
160
+ /**
161
+ * Revoke a key
162
+ */
163
+ revokeKey(keyId, reason) {
164
+ this.revokedKeys.add(keyId);
165
+ console.warn(`[TrustRoot] Key revoked: ${keyId} - ${reason}`);
166
+ }
167
+
168
+ /**
169
+ * Check if a key is trusted for a capability
170
+ */
171
+ isKeyTrusted(keyId, capability = 'sign-manifest') {
172
+ if (this.revokedKeys.has(keyId)) {
173
+ return false;
174
+ }
175
+
176
+ const key = this.trustedKeys.get(keyId);
177
+ if (!key) {
178
+ return false;
179
+ }
180
+
181
+ // Check validity period
182
+ const now = new Date();
183
+ if (key.validFrom && new Date(key.validFrom) > now) {
184
+ return false;
185
+ }
186
+ if (key.validUntil && new Date(key.validUntil) < now) {
187
+ return false;
188
+ }
189
+
190
+ // Check capability
191
+ if (!key.capabilities.includes(capability)) {
192
+ return false;
193
+ }
194
+
195
+ return true;
196
+ }
197
+
198
+ /**
199
+ * Get public key for verification
200
+ */
201
+ getPublicKey(keyId) {
202
+ const key = this.trustedKeys.get(keyId);
203
+ if (!key || this.revokedKeys.has(keyId)) {
204
+ return null;
205
+ }
206
+ return key.publicKey;
207
+ }
208
+
209
+ /**
210
+ * Verify signature set meets threshold
211
+ */
212
+ verifySignatureThreshold(signatures, requiredCount = null) {
213
+ const required = requiredCount || this.minimumSignaturesRequired;
214
+ let validCount = 0;
215
+ const validSigners = [];
216
+
217
+ for (const sig of signatures) {
218
+ if (this.isKeyTrusted(sig.keyId, 'sign-manifest')) {
219
+ // In production, verify actual signature here
220
+ validCount++;
221
+ validSigners.push(sig.keyId);
222
+ }
223
+ }
224
+
225
+ return {
226
+ valid: validCount >= required,
227
+ validCount,
228
+ required,
229
+ validSigners,
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Export current trust configuration
235
+ */
236
+ export() {
237
+ return {
238
+ trustedKeys: Object.fromEntries(this.trustedKeys),
239
+ revokedKeys: Array.from(this.revokedKeys),
240
+ minimumSignaturesRequired: this.minimumSignaturesRequired,
241
+ thresholdSignaturesRequired: this.thresholdSignaturesRequired,
242
+ };
243
+ }
244
+ }
245
+
246
+ // ============================================================================
247
+ // MERKLE CHUNK VERIFICATION
248
+ // ============================================================================
249
+
250
+ /**
251
+ * Compute Merkle tree from chunk hashes
252
+ */
253
+ export function computeMerkleRoot(chunkHashes) {
254
+ if (chunkHashes.length === 0) {
255
+ return hashCanonical({ empty: true });
256
+ }
257
+
258
+ if (chunkHashes.length === 1) {
259
+ return chunkHashes[0];
260
+ }
261
+
262
+ // Build tree bottom-up
263
+ let level = [...chunkHashes];
264
+
265
+ while (level.length > 1) {
266
+ const nextLevel = [];
267
+ for (let i = 0; i < level.length; i += 2) {
268
+ const left = level[i];
269
+ const right = level[i + 1] || left; // Duplicate last if odd
270
+ const combined = createHash('sha256')
271
+ .update(left, 'hex')
272
+ .update(right, 'hex')
273
+ .digest('hex');
274
+ nextLevel.push(combined);
275
+ }
276
+ level = nextLevel;
277
+ }
278
+
279
+ return level[0];
280
+ }
281
+
282
+ /**
283
+ * Generate Merkle proof for a chunk
284
+ */
285
+ export function generateMerkleProof(chunkHashes, chunkIndex) {
286
+ const proof = [];
287
+ let level = [...chunkHashes];
288
+ let index = chunkIndex;
289
+
290
+ while (level.length > 1) {
291
+ const isRight = index % 2 === 1;
292
+ const siblingIndex = isRight ? index - 1 : index + 1;
293
+
294
+ if (siblingIndex < level.length) {
295
+ proof.push({
296
+ hash: level[siblingIndex],
297
+ position: isRight ? 'left' : 'right',
298
+ });
299
+ } else {
300
+ // Odd number, sibling is self
301
+ proof.push({
302
+ hash: level[index],
303
+ position: 'right',
304
+ });
305
+ }
306
+
307
+ // Move up
308
+ const nextLevel = [];
309
+ for (let i = 0; i < level.length; i += 2) {
310
+ const left = level[i];
311
+ const right = level[i + 1] || left;
312
+ nextLevel.push(
313
+ createHash('sha256')
314
+ .update(left, 'hex')
315
+ .update(right, 'hex')
316
+ .digest('hex')
317
+ );
318
+ }
319
+ level = nextLevel;
320
+ index = Math.floor(index / 2);
321
+ }
322
+
323
+ return proof;
324
+ }
325
+
326
+ /**
327
+ * Verify a chunk against Merkle root
328
+ */
329
+ export function verifyMerkleProof(chunkHash, chunkIndex, proof, merkleRoot) {
330
+ let computed = chunkHash;
331
+
332
+ for (const step of proof) {
333
+ const left = step.position === 'left' ? step.hash : computed;
334
+ const right = step.position === 'right' ? step.hash : computed;
335
+ computed = createHash('sha256')
336
+ .update(left, 'hex')
337
+ .update(right, 'hex')
338
+ .digest('hex');
339
+ }
340
+
341
+ return computed === merkleRoot;
342
+ }
343
+
344
+ /**
345
+ * Chunk a buffer and compute hashes
346
+ */
347
+ export function chunkAndHash(buffer, chunkSize = 256 * 1024) {
348
+ const chunks = [];
349
+ const hashes = [];
350
+
351
+ for (let offset = 0; offset < buffer.length; offset += chunkSize) {
352
+ const chunk = buffer.slice(offset, offset + chunkSize);
353
+ chunks.push(chunk);
354
+ hashes.push(
355
+ createHash('sha256').update(chunk).digest('hex')
356
+ );
357
+ }
358
+
359
+ return {
360
+ chunks,
361
+ chunkHashes: hashes,
362
+ chunkSize,
363
+ chunkCount: chunks.length,
364
+ totalSize: buffer.length,
365
+ merkleRoot: computeMerkleRoot(hashes),
366
+ };
367
+ }
368
+
369
+ // ============================================================================
370
+ // MANIFEST INTEGRITY
371
+ // ============================================================================
372
+
373
+ /**
374
+ * Integrity block for manifests
375
+ */
376
+ export function createIntegrityBlock(manifest, chunkInfo) {
377
+ // Create the signed payload (everything except signatures)
378
+ const signedPayload = {
379
+ model: manifest.model,
380
+ version: manifest.version,
381
+ artifacts: manifest.artifacts,
382
+ provenance: manifest.provenance,
383
+ capabilities: manifest.capabilities,
384
+ timestamp: new Date().toISOString(),
385
+ };
386
+
387
+ const signedPayloadHash = hashCanonical(signedPayload);
388
+
389
+ return {
390
+ manifestHash: hashCanonical(manifest),
391
+ signedPayloadHash,
392
+ merkleRoot: chunkInfo.merkleRoot,
393
+ chunking: {
394
+ chunkSize: chunkInfo.chunkSize,
395
+ chunkCount: chunkInfo.chunkCount,
396
+ chunkHashes: chunkInfo.chunkHashes,
397
+ },
398
+ signatures: [], // To be filled by signing process
399
+ };
400
+ }
401
+
402
+ /**
403
+ * Provenance block for manifests
404
+ */
405
+ export function createProvenanceBlock(options = {}) {
406
+ return {
407
+ builtBy: {
408
+ tool: options.tool || '@ruvector/model-optimizer',
409
+ version: options.toolVersion || '1.0.0',
410
+ commit: options.commit || 'unknown',
411
+ },
412
+ optimizationRecipeHash: options.recipeHash || null,
413
+ calibrationDatasetHash: options.calibrationHash || null,
414
+ parentLineage: options.parentLineage || null,
415
+ buildTimestamp: new Date().toISOString(),
416
+ environment: {
417
+ platform: process.platform,
418
+ arch: process.arch,
419
+ nodeVersion: process.version,
420
+ },
421
+ };
422
+ }
423
+
424
+ /**
425
+ * Full manifest with integrity
426
+ */
427
+ export function createSecureManifest(model, artifacts, options = {}) {
428
+ const manifest = {
429
+ schemaVersion: '2.0.0',
430
+ model: {
431
+ id: model.id,
432
+ name: model.name,
433
+ version: model.version,
434
+ type: model.type, // 'embedding' | 'generation'
435
+ tier: model.tier, // 'micro' | 'small' | 'large'
436
+ capabilities: model.capabilities || [],
437
+ memoryRequirement: model.memoryRequirement,
438
+ },
439
+ artifacts: artifacts.map(a => ({
440
+ path: a.path,
441
+ size: a.size,
442
+ sha256: a.sha256,
443
+ format: a.format,
444
+ quantization: a.quantization,
445
+ })),
446
+ distribution: {
447
+ gcs: options.gcsUrl,
448
+ ipfs: options.ipfsCid,
449
+ fallbackUrls: options.fallbackUrls || [],
450
+ },
451
+ provenance: createProvenanceBlock(options.provenance || {}),
452
+ capabilities: model.capabilities || [],
453
+ };
454
+
455
+ // Add integrity block if chunk info provided
456
+ if (options.chunkInfo) {
457
+ manifest.integrity = createIntegrityBlock(manifest, options.chunkInfo);
458
+ }
459
+
460
+ // Add trust metadata
461
+ manifest.trust = {
462
+ trustedKeySetId: options.trustedKeySetId || 'ruvector-default-2024',
463
+ minimumSignaturesRequired: options.minimumSignaturesRequired || 1,
464
+ };
465
+
466
+ return manifest;
467
+ }
468
+
469
+ // ============================================================================
470
+ // MANIFEST VERIFICATION
471
+ // ============================================================================
472
+
473
+ /**
474
+ * Verify a manifest's integrity
475
+ */
476
+ export class ManifestVerifier {
477
+ constructor(trustRoot = null) {
478
+ this.trustRoot = trustRoot || new TrustRoot();
479
+ }
480
+
481
+ /**
482
+ * Full verification of a manifest
483
+ */
484
+ verify(manifest) {
485
+ const errors = [];
486
+ const warnings = [];
487
+
488
+ // 1. Schema version check
489
+ if (!manifest.schemaVersion || manifest.schemaVersion < '2.0.0') {
490
+ warnings.push('Manifest uses old schema version');
491
+ }
492
+
493
+ // 2. Verify integrity block
494
+ if (manifest.integrity) {
495
+ // Check manifest hash
496
+ const computed = hashCanonical(manifest);
497
+ // Note: manifestHash is computed before adding integrity, so we skip this
498
+
499
+ // Check signed payload hash
500
+ const signedPayload = {
501
+ model: manifest.model,
502
+ version: manifest.version,
503
+ artifacts: manifest.artifacts,
504
+ provenance: manifest.provenance,
505
+ capabilities: manifest.capabilities,
506
+ timestamp: manifest.integrity.timestamp,
507
+ };
508
+ const computedPayloadHash = hashCanonical(signedPayload);
509
+
510
+ // 3. Verify signatures meet threshold
511
+ if (manifest.integrity.signatures?.length > 0) {
512
+ const sigResult = this.trustRoot.verifySignatureThreshold(
513
+ manifest.integrity.signatures,
514
+ manifest.trust?.minimumSignaturesRequired
515
+ );
516
+
517
+ if (!sigResult.valid) {
518
+ errors.push(`Insufficient valid signatures: ${sigResult.validCount}/${sigResult.required}`);
519
+ }
520
+ } else {
521
+ warnings.push('No signatures present');
522
+ }
523
+
524
+ // 4. Verify Merkle root matches chunk hashes
525
+ if (manifest.integrity.chunking) {
526
+ const computedRoot = computeMerkleRoot(manifest.integrity.chunking.chunkHashes);
527
+ if (computedRoot !== manifest.integrity.merkleRoot) {
528
+ errors.push('Merkle root mismatch');
529
+ }
530
+ }
531
+ } else {
532
+ warnings.push('No integrity block present');
533
+ }
534
+
535
+ // 5. Check provenance
536
+ if (!manifest.provenance) {
537
+ warnings.push('No provenance information');
538
+ }
539
+
540
+ // 6. Check required fields
541
+ if (!manifest.model?.id) errors.push('Missing model.id');
542
+ if (!manifest.model?.version) errors.push('Missing model.version');
543
+ if (!manifest.artifacts?.length) errors.push('No artifacts defined');
544
+
545
+ return {
546
+ valid: errors.length === 0,
547
+ errors,
548
+ warnings,
549
+ trust: manifest.trust,
550
+ provenance: manifest.provenance,
551
+ };
552
+ }
553
+
554
+ /**
555
+ * Verify a single chunk during streaming download
556
+ */
557
+ verifyChunk(chunkData, chunkIndex, manifest) {
558
+ if (!manifest.integrity?.chunking) {
559
+ return { valid: false, error: 'No chunking info in manifest' };
560
+ }
561
+
562
+ const expectedHash = manifest.integrity.chunking.chunkHashes[chunkIndex];
563
+ if (!expectedHash) {
564
+ return { valid: false, error: `No hash for chunk ${chunkIndex}` };
565
+ }
566
+
567
+ const actualHash = createHash('sha256').update(chunkData).digest('hex');
568
+
569
+ if (actualHash !== expectedHash) {
570
+ return {
571
+ valid: false,
572
+ error: `Chunk ${chunkIndex} hash mismatch`,
573
+ expected: expectedHash,
574
+ actual: actualHash,
575
+ };
576
+ }
577
+
578
+ return { valid: true, chunkIndex, hash: actualHash };
579
+ }
580
+ }
581
+
582
+ // ============================================================================
583
+ // TRANSPARENCY LOG
584
+ // ============================================================================
585
+
586
+ /**
587
+ * Entry in the transparency log
588
+ */
589
+ export function createLogEntry(manifest, publisherKeyId) {
590
+ return {
591
+ manifestHash: hashCanonical(manifest),
592
+ modelId: manifest.model.id,
593
+ version: manifest.model.version,
594
+ publisherKeyId,
595
+ timestamp: new Date().toISOString(),
596
+ signedPayloadHash: manifest.integrity?.signedPayloadHash,
597
+ };
598
+ }
599
+
600
+ /**
601
+ * Simple append-only transparency log
602
+ * In production, this would be backed by a Merkle tree or blockchain
603
+ */
604
+ export class TransparencyLog {
605
+ constructor(options = {}) {
606
+ this.entries = [];
607
+ this.indexByModel = new Map();
608
+ this.indexByHash = new Map();
609
+ this.logRoot = null;
610
+ }
611
+
612
+ /**
613
+ * Append an entry to the log
614
+ */
615
+ append(entry) {
616
+ const index = this.entries.length;
617
+
618
+ // Compute log entry hash including previous
619
+ const logEntryHash = hashCanonical({
620
+ ...entry,
621
+ index,
622
+ previousHash: this.logRoot,
623
+ });
624
+
625
+ const fullEntry = {
626
+ ...entry,
627
+ index,
628
+ previousHash: this.logRoot,
629
+ logEntryHash,
630
+ };
631
+
632
+ this.entries.push(fullEntry);
633
+ this.logRoot = logEntryHash;
634
+
635
+ // Update indexes
636
+ if (!this.indexByModel.has(entry.modelId)) {
637
+ this.indexByModel.set(entry.modelId, []);
638
+ }
639
+ this.indexByModel.get(entry.modelId).push(index);
640
+ this.indexByHash.set(entry.manifestHash, index);
641
+
642
+ return fullEntry;
643
+ }
644
+
645
+ /**
646
+ * Generate inclusion proof
647
+ */
648
+ getInclusionProof(manifestHash) {
649
+ const index = this.indexByHash.get(manifestHash);
650
+ if (index === undefined) {
651
+ return null;
652
+ }
653
+
654
+ const entry = this.entries[index];
655
+ const proof = [];
656
+
657
+ // Simple chain proof (in production, use Merkle tree)
658
+ for (let i = index; i < this.entries.length; i++) {
659
+ proof.push({
660
+ index: i,
661
+ logEntryHash: this.entries[i].logEntryHash,
662
+ });
663
+ }
664
+
665
+ return {
666
+ entry,
667
+ proof,
668
+ currentRoot: this.logRoot,
669
+ logLength: this.entries.length,
670
+ };
671
+ }
672
+
673
+ /**
674
+ * Verify inclusion proof
675
+ */
676
+ verifyInclusionProof(proof) {
677
+ if (!proof || !proof.entry || !proof.proof.length) {
678
+ return false;
679
+ }
680
+
681
+ // Verify chain
682
+ let expectedHash = proof.entry.logEntryHash;
683
+ for (let i = 1; i < proof.proof.length; i++) {
684
+ const entry = proof.proof[i];
685
+ // Verify chain continuity
686
+ if (i < proof.proof.length - 1) {
687
+ // Each entry should reference the previous
688
+ }
689
+ }
690
+
691
+ return proof.proof[proof.proof.length - 1].logEntryHash === proof.currentRoot;
692
+ }
693
+
694
+ /**
695
+ * Get history for a model
696
+ */
697
+ getModelHistory(modelId) {
698
+ const indices = this.indexByModel.get(modelId) || [];
699
+ return indices.map(i => this.entries[i]);
700
+ }
701
+
702
+ /**
703
+ * Export log for persistence
704
+ */
705
+ export() {
706
+ return {
707
+ entries: this.entries,
708
+ logRoot: this.logRoot,
709
+ };
710
+ }
711
+
712
+ /**
713
+ * Import log
714
+ */
715
+ import(data) {
716
+ this.entries = data.entries || [];
717
+ this.logRoot = data.logRoot;
718
+
719
+ // Rebuild indexes
720
+ this.indexByModel.clear();
721
+ this.indexByHash.clear();
722
+
723
+ for (let i = 0; i < this.entries.length; i++) {
724
+ const entry = this.entries[i];
725
+ if (!this.indexByModel.has(entry.modelId)) {
726
+ this.indexByModel.set(entry.modelId, []);
727
+ }
728
+ this.indexByModel.get(entry.modelId).push(i);
729
+ this.indexByHash.set(entry.manifestHash, i);
730
+ }
731
+ }
732
+ }
733
+
734
+ // ============================================================================
735
+ // EXPORTS
736
+ // ============================================================================
737
+
738
+ export default {
739
+ canonicalize,
740
+ hashCanonical,
741
+ TrustRoot,
742
+ BUILTIN_ROOT_KEYS,
743
+ computeMerkleRoot,
744
+ generateMerkleProof,
745
+ verifyMerkleProof,
746
+ chunkAndHash,
747
+ createIntegrityBlock,
748
+ createProvenanceBlock,
749
+ createSecureManifest,
750
+ ManifestVerifier,
751
+ createLogEntry,
752
+ TransparencyLog,
753
+ };