@originals/sdk 1.8.0 → 1.8.2

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.
Files changed (145) hide show
  1. package/dist/utils/hash.js +1 -0
  2. package/package.json +6 -5
  3. package/src/adapters/FeeOracleMock.ts +9 -0
  4. package/src/adapters/index.ts +5 -0
  5. package/src/adapters/providers/OrdHttpProvider.ts +126 -0
  6. package/src/adapters/providers/OrdMockProvider.ts +101 -0
  7. package/src/adapters/types.ts +66 -0
  8. package/src/bitcoin/BitcoinManager.ts +329 -0
  9. package/src/bitcoin/BroadcastClient.ts +54 -0
  10. package/src/bitcoin/OrdinalsClient.ts +120 -0
  11. package/src/bitcoin/PSBTBuilder.ts +106 -0
  12. package/src/bitcoin/fee-calculation.ts +38 -0
  13. package/src/bitcoin/providers/OrdNodeProvider.ts +92 -0
  14. package/src/bitcoin/providers/OrdinalsProvider.ts +56 -0
  15. package/src/bitcoin/providers/types.ts +59 -0
  16. package/src/bitcoin/transactions/commit.ts +465 -0
  17. package/src/bitcoin/transactions/index.ts +13 -0
  18. package/src/bitcoin/transfer.ts +43 -0
  19. package/src/bitcoin/utxo-selection.ts +322 -0
  20. package/src/bitcoin/utxo.ts +113 -0
  21. package/src/cel/ExternalReferenceManager.ts +87 -0
  22. package/src/cel/OriginalsCel.ts +460 -0
  23. package/src/cel/algorithms/createEventLog.ts +68 -0
  24. package/src/cel/algorithms/deactivateEventLog.ts +109 -0
  25. package/src/cel/algorithms/index.ts +11 -0
  26. package/src/cel/algorithms/updateEventLog.ts +99 -0
  27. package/src/cel/algorithms/verifyEventLog.ts +306 -0
  28. package/src/cel/algorithms/witnessEvent.ts +87 -0
  29. package/src/cel/cli/create.ts +330 -0
  30. package/src/cel/cli/index.ts +383 -0
  31. package/src/cel/cli/inspect.ts +549 -0
  32. package/src/cel/cli/migrate.ts +473 -0
  33. package/src/cel/cli/verify.ts +249 -0
  34. package/src/cel/hash.ts +71 -0
  35. package/src/cel/index.ts +16 -0
  36. package/src/cel/layers/BtcoCelManager.ts +408 -0
  37. package/src/cel/layers/PeerCelManager.ts +371 -0
  38. package/src/cel/layers/WebVHCelManager.ts +361 -0
  39. package/src/cel/layers/index.ts +27 -0
  40. package/src/cel/serialization/cbor.ts +189 -0
  41. package/src/cel/serialization/index.ts +10 -0
  42. package/src/cel/serialization/json.ts +209 -0
  43. package/src/cel/types.ts +160 -0
  44. package/src/cel/witnesses/BitcoinWitness.ts +184 -0
  45. package/src/cel/witnesses/HttpWitness.ts +241 -0
  46. package/src/cel/witnesses/WitnessService.ts +51 -0
  47. package/src/cel/witnesses/index.ts +11 -0
  48. package/src/contexts/credentials-v1.json +237 -0
  49. package/src/contexts/credentials-v2-examples.json +5 -0
  50. package/src/contexts/credentials-v2.json +340 -0
  51. package/src/contexts/credentials.json +237 -0
  52. package/src/contexts/data-integrity-v2.json +81 -0
  53. package/src/contexts/dids.json +58 -0
  54. package/src/contexts/ed255192020.json +93 -0
  55. package/src/contexts/ordinals-plus.json +23 -0
  56. package/src/contexts/originals.json +22 -0
  57. package/src/core/OriginalsSDK.ts +420 -0
  58. package/src/crypto/Multikey.ts +194 -0
  59. package/src/crypto/Signer.ts +262 -0
  60. package/src/crypto/noble-init.ts +138 -0
  61. package/src/did/BtcoDidResolver.ts +231 -0
  62. package/src/did/DIDManager.ts +705 -0
  63. package/src/did/Ed25519Verifier.ts +68 -0
  64. package/src/did/KeyManager.ts +239 -0
  65. package/src/did/WebVHManager.ts +499 -0
  66. package/src/did/createBtcoDidDocument.ts +60 -0
  67. package/src/did/providers/OrdinalsClientProviderAdapter.ts +68 -0
  68. package/src/events/EventEmitter.ts +222 -0
  69. package/src/events/index.ts +19 -0
  70. package/src/events/types.ts +331 -0
  71. package/src/examples/basic-usage.ts +78 -0
  72. package/src/examples/create-module-original.ts +435 -0
  73. package/src/examples/full-lifecycle-flow.ts +514 -0
  74. package/src/examples/run.ts +60 -0
  75. package/src/index.ts +204 -0
  76. package/src/kinds/KindRegistry.ts +320 -0
  77. package/src/kinds/index.ts +74 -0
  78. package/src/kinds/types.ts +470 -0
  79. package/src/kinds/validators/AgentValidator.ts +257 -0
  80. package/src/kinds/validators/AppValidator.ts +211 -0
  81. package/src/kinds/validators/DatasetValidator.ts +242 -0
  82. package/src/kinds/validators/DocumentValidator.ts +311 -0
  83. package/src/kinds/validators/MediaValidator.ts +269 -0
  84. package/src/kinds/validators/ModuleValidator.ts +225 -0
  85. package/src/kinds/validators/base.ts +276 -0
  86. package/src/kinds/validators/index.ts +12 -0
  87. package/src/lifecycle/BatchOperations.ts +381 -0
  88. package/src/lifecycle/LifecycleManager.ts +2156 -0
  89. package/src/lifecycle/OriginalsAsset.ts +524 -0
  90. package/src/lifecycle/ProvenanceQuery.ts +280 -0
  91. package/src/lifecycle/ResourceVersioning.ts +163 -0
  92. package/src/migration/MigrationManager.ts +587 -0
  93. package/src/migration/audit/AuditLogger.ts +176 -0
  94. package/src/migration/checkpoint/CheckpointManager.ts +112 -0
  95. package/src/migration/checkpoint/CheckpointStorage.ts +101 -0
  96. package/src/migration/index.ts +33 -0
  97. package/src/migration/operations/BaseMigration.ts +126 -0
  98. package/src/migration/operations/PeerToBtcoMigration.ts +105 -0
  99. package/src/migration/operations/PeerToWebvhMigration.ts +62 -0
  100. package/src/migration/operations/WebvhToBtcoMigration.ts +105 -0
  101. package/src/migration/rollback/RollbackManager.ts +170 -0
  102. package/src/migration/state/StateMachine.ts +92 -0
  103. package/src/migration/state/StateTracker.ts +156 -0
  104. package/src/migration/types.ts +356 -0
  105. package/src/migration/validation/BitcoinValidator.ts +107 -0
  106. package/src/migration/validation/CredentialValidator.ts +62 -0
  107. package/src/migration/validation/DIDCompatibilityValidator.ts +151 -0
  108. package/src/migration/validation/LifecycleValidator.ts +64 -0
  109. package/src/migration/validation/StorageValidator.ts +79 -0
  110. package/src/migration/validation/ValidationPipeline.ts +213 -0
  111. package/src/resources/ResourceManager.ts +655 -0
  112. package/src/resources/index.ts +21 -0
  113. package/src/resources/types.ts +202 -0
  114. package/src/storage/LocalStorageAdapter.ts +64 -0
  115. package/src/storage/MemoryStorageAdapter.ts +29 -0
  116. package/src/storage/StorageAdapter.ts +25 -0
  117. package/src/storage/index.ts +3 -0
  118. package/src/types/bitcoin.ts +98 -0
  119. package/src/types/common.ts +92 -0
  120. package/src/types/credentials.ts +89 -0
  121. package/src/types/did.ts +31 -0
  122. package/src/types/external-shims.d.ts +53 -0
  123. package/src/types/index.ts +7 -0
  124. package/src/types/network.ts +178 -0
  125. package/src/utils/EventLogger.ts +298 -0
  126. package/src/utils/Logger.ts +324 -0
  127. package/src/utils/MetricsCollector.ts +358 -0
  128. package/src/utils/bitcoin-address.ts +132 -0
  129. package/src/utils/cbor.ts +31 -0
  130. package/src/utils/encoding.ts +135 -0
  131. package/src/utils/hash.ts +12 -0
  132. package/src/utils/retry.ts +46 -0
  133. package/src/utils/satoshi-validation.ts +196 -0
  134. package/src/utils/serialization.ts +102 -0
  135. package/src/utils/telemetry.ts +44 -0
  136. package/src/utils/validation.ts +123 -0
  137. package/src/vc/CredentialManager.ts +955 -0
  138. package/src/vc/Issuer.ts +105 -0
  139. package/src/vc/Verifier.ts +54 -0
  140. package/src/vc/cryptosuites/bbs.ts +253 -0
  141. package/src/vc/cryptosuites/bbsSimple.ts +21 -0
  142. package/src/vc/cryptosuites/eddsa.ts +99 -0
  143. package/src/vc/documentLoader.ts +81 -0
  144. package/src/vc/proofs/data-integrity.ts +33 -0
  145. package/src/vc/utils/jsonld.ts +18 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * updateEventLog Algorithm
3
+ *
4
+ * Appends an update event to an existing Cryptographic Event Log.
5
+ * Each update event references the previous event via a hash chain.
6
+ *
7
+ * @see https://w3c-ccg.github.io/cel-spec/
8
+ */
9
+
10
+ import type { EventLog, LogEntry, UpdateOptions, DataIntegrityProof } from '../types';
11
+ import { computeDigestMultibase } from '../hash';
12
+
13
+ /**
14
+ * Serializes a LogEntry to a deterministic byte representation for hashing.
15
+ * Uses JSON with sorted keys for reproducibility.
16
+ *
17
+ * @param entry - The log entry to serialize
18
+ * @returns UTF-8 encoded bytes
19
+ */
20
+ function serializeEntry(entry: LogEntry): Uint8Array {
21
+ // Use JSON with sorted keys for deterministic serialization
22
+ const json = JSON.stringify(entry, Object.keys(entry).sort());
23
+ return new TextEncoder().encode(json);
24
+ }
25
+
26
+ /**
27
+ * Updates an event log by appending a new "update" event.
28
+ *
29
+ * The new event is cryptographically linked to the previous event
30
+ * via a hash of the last event (previousEvent field).
31
+ *
32
+ * @param log - The existing event log to update
33
+ * @param data - The update data (e.g., modified metadata, new resources)
34
+ * @param options - Signing options including signer function and verification method
35
+ * @returns A new EventLog with the update event appended (input is not mutated)
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const updatedLog = await updateEventLog(
40
+ * existingLog,
41
+ * { name: 'Updated Asset Name', version: 2 },
42
+ * {
43
+ * signer: async (data) => createEdDsaProof(data, privateKey),
44
+ * verificationMethod: 'did:key:z6Mk...',
45
+ * proofPurpose: 'assertionMethod'
46
+ * }
47
+ * );
48
+ * ```
49
+ */
50
+ export async function updateEventLog(
51
+ log: EventLog,
52
+ data: unknown,
53
+ options: UpdateOptions
54
+ ): Promise<EventLog> {
55
+ const { signer, verificationMethod, proofPurpose = 'assertionMethod' } = options;
56
+
57
+ // Validate input log
58
+ if (!log.events || log.events.length === 0) {
59
+ throw new Error('Cannot update an empty event log');
60
+ }
61
+
62
+ // Get the last event to compute the hash chain link
63
+ const lastEvent = log.events[log.events.length - 1];
64
+
65
+ // Compute the digestMultibase of the last event
66
+ const previousEvent = computeDigestMultibase(serializeEntry(lastEvent));
67
+
68
+ // Create the event structure without proof first
69
+ const eventBase = {
70
+ type: 'update' as const,
71
+ data,
72
+ previousEvent,
73
+ };
74
+
75
+ // Generate proof using the provided signer
76
+ const proof: DataIntegrityProof = await signer(eventBase);
77
+
78
+ // Validate the proof has required fields
79
+ if (!proof.type || !proof.cryptosuite || !proof.proofValue) {
80
+ throw new Error('Invalid proof: missing required fields (type, cryptosuite, proofValue)');
81
+ }
82
+
83
+ // Construct the complete log entry
84
+ const entry: LogEntry = {
85
+ type: 'update',
86
+ data,
87
+ previousEvent,
88
+ proof: [proof],
89
+ };
90
+
91
+ // Return a new event log (immutable - does not mutate input)
92
+ const eventLog: EventLog = {
93
+ events: [...log.events, entry],
94
+ // Preserve previousLog reference if it exists
95
+ ...(log.previousLog ? { previousLog: log.previousLog } : {}),
96
+ };
97
+
98
+ return eventLog;
99
+ }
@@ -0,0 +1,306 @@
1
+ /**
2
+ * verifyEventLog Algorithm
3
+ *
4
+ * Verifies all proofs and hash chain integrity in a Cryptographic Event Log.
5
+ * Returns detailed per-event verification status.
6
+ *
7
+ * @see https://w3c-ccg.github.io/cel-spec/
8
+ */
9
+
10
+ import type {
11
+ EventLog,
12
+ LogEntry,
13
+ VerifyOptions,
14
+ VerificationResult,
15
+ EventVerification,
16
+ DataIntegrityProof
17
+ } from '../types';
18
+ import { computeDigestMultibase } from '../hash';
19
+
20
+ /**
21
+ * Serializes data to JCS (JSON Canonicalization Scheme) format.
22
+ * Uses JSON with sorted keys for deterministic serialization.
23
+ *
24
+ * @param data - The data to serialize
25
+ * @returns UTF-8 encoded bytes
26
+ */
27
+ function serializeToJcs(data: unknown): Uint8Array {
28
+ // JCS uses JSON with lexicographically sorted keys
29
+ const json = JSON.stringify(data, (_, value) => {
30
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
31
+ return Object.keys(value).sort().reduce((sorted: Record<string, unknown>, key) => {
32
+ sorted[key] = value[key];
33
+ return sorted;
34
+ }, {});
35
+ }
36
+ return value;
37
+ });
38
+ return new TextEncoder().encode(json);
39
+ }
40
+
41
+ /**
42
+ * Serializes a LogEntry to a deterministic byte representation for hashing.
43
+ * Uses JSON with sorted keys for reproducibility.
44
+ * This must match the serialization used in createEventLog/updateEventLog.
45
+ *
46
+ * @param entry - The log entry to serialize
47
+ * @returns UTF-8 encoded bytes
48
+ */
49
+ function serializeEntry(entry: LogEntry): Uint8Array {
50
+ // Use JSON with sorted keys for deterministic serialization
51
+ const json = JSON.stringify(entry, Object.keys(entry).sort());
52
+ return new TextEncoder().encode(json);
53
+ }
54
+
55
+ /**
56
+ * Default proof verifier using eddsa-jcs-2022 cryptosuite.
57
+ *
58
+ * This is a basic implementation that validates proof structure.
59
+ * For full cryptographic verification, a custom verifier should be provided
60
+ * that has access to the public key from the verificationMethod.
61
+ *
62
+ * @param proof - The proof to verify
63
+ * @param data - The data that was signed
64
+ * @returns True if proof structure is valid
65
+ */
66
+ async function defaultVerifier(proof: DataIntegrityProof, data: unknown): Promise<boolean> {
67
+ // Validate proof has required fields
68
+ if (!proof.type || proof.type !== 'DataIntegrityProof') {
69
+ return false;
70
+ }
71
+
72
+ if (!proof.cryptosuite) {
73
+ return false;
74
+ }
75
+
76
+ if (!proof.proofValue || typeof proof.proofValue !== 'string' || proof.proofValue.length === 0) {
77
+ return false;
78
+ }
79
+
80
+ if (!proof.verificationMethod || typeof proof.verificationMethod !== 'string') {
81
+ return false;
82
+ }
83
+
84
+ if (!proof.proofPurpose || typeof proof.proofPurpose !== 'string') {
85
+ return false;
86
+ }
87
+
88
+ // Check for valid cryptosuite
89
+ const validCryptosuites = ['eddsa-jcs-2022', 'eddsa-rdfc-2022'];
90
+ if (!validCryptosuites.includes(proof.cryptosuite)) {
91
+ return false;
92
+ }
93
+
94
+ // Validate proofValue is properly formatted (multibase encoded)
95
+ // Most proofValues start with 'z' (base58btc) or 'u' (base64url)
96
+ if (!proof.proofValue.startsWith('z') && !proof.proofValue.startsWith('u')) {
97
+ return false;
98
+ }
99
+
100
+ return true;
101
+ }
102
+
103
+ /**
104
+ * Verifies the hash chain for a single event.
105
+ *
106
+ * @param event - The current event to verify
107
+ * @param index - The index of the event in the log
108
+ * @param previousEvent - The previous event in the log (undefined for first event)
109
+ * @returns Object with chainValid boolean and any errors
110
+ */
111
+ function verifyChain(
112
+ event: LogEntry,
113
+ index: number,
114
+ previousEvent: LogEntry | undefined
115
+ ): { chainValid: boolean; errors: string[] } {
116
+ const errors: string[] = [];
117
+
118
+ if (index === 0) {
119
+ // First event must NOT have previousEvent
120
+ if (event.previousEvent !== undefined) {
121
+ errors.push(`Event ${index}: First event must not have previousEvent field`);
122
+ return { chainValid: false, errors };
123
+ }
124
+ } else {
125
+ // Subsequent events must have previousEvent that matches hash of prior event
126
+ if (event.previousEvent === undefined) {
127
+ errors.push(`Event ${index}: Missing previousEvent reference`);
128
+ return { chainValid: false, errors };
129
+ }
130
+
131
+ if (!previousEvent) {
132
+ errors.push(`Event ${index}: Cannot verify chain - previous event not provided`);
133
+ return { chainValid: false, errors };
134
+ }
135
+
136
+ // Compute the expected hash of the previous event
137
+ const expectedHash = computeDigestMultibase(serializeEntry(previousEvent));
138
+
139
+ if (event.previousEvent !== expectedHash) {
140
+ errors.push(`Event ${index}: Hash chain broken - previousEvent does not match hash of prior event`);
141
+ return { chainValid: false, errors };
142
+ }
143
+ }
144
+
145
+ return { chainValid: true, errors: [] };
146
+ }
147
+
148
+ /**
149
+ * Verifies a single event's proofs.
150
+ *
151
+ * @param event - The event to verify
152
+ * @param index - The index of the event in the log
153
+ * @param verifier - The proof verification function
154
+ * @param previousEvent - The previous event in the log (undefined for first event)
155
+ * @returns EventVerification result
156
+ */
157
+ async function verifyEvent(
158
+ event: LogEntry,
159
+ index: number,
160
+ verifier: (proof: DataIntegrityProof, data: unknown) => Promise<boolean>,
161
+ previousEvent: LogEntry | undefined
162
+ ): Promise<EventVerification> {
163
+ const errors: string[] = [];
164
+
165
+ // Verify hash chain
166
+ const chainResult = verifyChain(event, index, previousEvent);
167
+ const chainValid = chainResult.chainValid;
168
+ errors.push(...chainResult.errors);
169
+
170
+ // Check that event has proofs
171
+ if (!event.proof || !Array.isArray(event.proof) || event.proof.length === 0) {
172
+ errors.push(`Event ${index}: No proofs found`);
173
+ return {
174
+ index,
175
+ type: event.type,
176
+ proofValid: false,
177
+ chainValid,
178
+ errors,
179
+ };
180
+ }
181
+
182
+ // Verify each proof
183
+ let allProofsValid = true;
184
+ const eventData = {
185
+ type: event.type,
186
+ data: event.data,
187
+ ...(event.previousEvent ? { previousEvent: event.previousEvent } : {}),
188
+ };
189
+
190
+ for (let proofIndex = 0; proofIndex < event.proof.length; proofIndex++) {
191
+ const proof = event.proof[proofIndex];
192
+
193
+ try {
194
+ const isValid = await verifier(proof, eventData);
195
+ if (!isValid) {
196
+ allProofsValid = false;
197
+ errors.push(`Event ${index}, Proof ${proofIndex}: Verification failed`);
198
+ }
199
+ } catch (error) {
200
+ allProofsValid = false;
201
+ const message = error instanceof Error ? error.message : 'Unknown error';
202
+ errors.push(`Event ${index}, Proof ${proofIndex}: ${message}`);
203
+ }
204
+ }
205
+
206
+ return {
207
+ index,
208
+ type: event.type,
209
+ proofValid: allProofsValid,
210
+ chainValid,
211
+ errors,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Verifies all proofs and hash chain integrity in an event log.
217
+ *
218
+ * This algorithm verifies:
219
+ * - Each event has at least one proof
220
+ * - Each proof is structurally valid (type, cryptosuite, proofValue, verificationMethod, proofPurpose)
221
+ * - Proofs use valid cryptosuite (eddsa-jcs-2022 or eddsa-rdfc-2022)
222
+ * - The first event has no previousEvent field
223
+ * - Each subsequent event's previousEvent matches the digestMultibase of the prior event
224
+ *
225
+ * For full cryptographic verification, provide a custom verifier function
226
+ * in the options that can resolve the public key from the verificationMethod.
227
+ *
228
+ * @param log - The event log to verify
229
+ * @param options - Optional verification options including custom verifier
230
+ * @returns VerificationResult with detailed per-event status including chainValid
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * // Basic structural and chain verification
235
+ * const result = await verifyEventLog(eventLog);
236
+ * if (result.verified) {
237
+ * console.log('All proofs are valid and hash chain is intact');
238
+ * }
239
+ *
240
+ * // With custom cryptographic verifier
241
+ * const result = await verifyEventLog(eventLog, {
242
+ * verifier: async (proof, data) => {
243
+ * // Resolve public key and verify signature
244
+ * const publicKey = await resolvePublicKey(proof.verificationMethod);
245
+ * return verifyEdDsaSignature(data, proof.proofValue, publicKey);
246
+ * }
247
+ * });
248
+ * ```
249
+ */
250
+ export async function verifyEventLog(
251
+ log: EventLog,
252
+ options?: VerifyOptions
253
+ ): Promise<VerificationResult> {
254
+ const errors: string[] = [];
255
+ const eventVerifications: EventVerification[] = [];
256
+
257
+ // Use custom verifier if provided, otherwise use default
258
+ const verifier = options?.verifier ?? defaultVerifier;
259
+
260
+ // Validate log structure
261
+ if (!log || !log.events) {
262
+ return {
263
+ verified: false,
264
+ errors: ['Invalid event log: missing events array'],
265
+ events: [],
266
+ };
267
+ }
268
+
269
+ if (!Array.isArray(log.events)) {
270
+ return {
271
+ verified: false,
272
+ errors: ['Invalid event log: events is not an array'],
273
+ events: [],
274
+ };
275
+ }
276
+
277
+ if (log.events.length === 0) {
278
+ return {
279
+ verified: false,
280
+ errors: ['Invalid event log: empty events array'],
281
+ events: [],
282
+ };
283
+ }
284
+
285
+ // Verify each event's proofs and hash chain
286
+ for (let i = 0; i < log.events.length; i++) {
287
+ const event = log.events[i];
288
+ const previousEvent = i > 0 ? log.events[i - 1] : undefined;
289
+ const eventResult = await verifyEvent(event, i, verifier, previousEvent);
290
+ eventVerifications.push(eventResult);
291
+
292
+ if (!eventResult.proofValid || !eventResult.chainValid) {
293
+ errors.push(...eventResult.errors);
294
+ }
295
+ }
296
+
297
+ // Determine overall verification status (both proofs AND chain must be valid)
298
+ const allProofsValid = eventVerifications.every(ev => ev.proofValid);
299
+ const allChainsValid = eventVerifications.every(ev => ev.chainValid);
300
+
301
+ return {
302
+ verified: allProofsValid && allChainsValid,
303
+ errors,
304
+ events: eventVerifications,
305
+ };
306
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * witnessEvent Algorithm
3
+ *
4
+ * Adds a witness proof to an existing log entry. The witness proof is
5
+ * appended to the event's proof array, preserving the original controller proof.
6
+ *
7
+ * @see https://w3c-ccg.github.io/cel-spec/
8
+ */
9
+
10
+ import type { LogEntry, WitnessProof } from '../types';
11
+ import type { WitnessService } from '../witnesses/WitnessService';
12
+ import { computeDigestMultibase } from '../hash';
13
+
14
+ /**
15
+ * Serializes a log entry to bytes for hashing.
16
+ * Uses deterministic JSON serialization with sorted keys.
17
+ */
18
+ function serializeEntry(entry: LogEntry): Uint8Array {
19
+ // Create a canonical representation with sorted keys
20
+ const canonical = JSON.stringify(entry, (key, value) => {
21
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
22
+ return Object.keys(value)
23
+ .sort()
24
+ .reduce((sorted: Record<string, unknown>, k) => {
25
+ sorted[k] = value[k];
26
+ return sorted;
27
+ }, {});
28
+ }
29
+ return value;
30
+ });
31
+ return new TextEncoder().encode(canonical);
32
+ }
33
+
34
+ /**
35
+ * Adds a witness proof to an event by calling a witness service.
36
+ *
37
+ * The witness service attests to the event's digest and returns a proof
38
+ * that is appended to the event's proof array. This does not replace
39
+ * the original controller proof - it adds an additional attestation.
40
+ *
41
+ * @param event - The log entry to witness
42
+ * @param witness - The witness service to use for attestation
43
+ * @returns A new LogEntry with the witness proof appended to the proof array
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const httpWitness = new HttpWitness('https://witness.example.com');
48
+ * const witnessedEvent = await witnessEvent(myEvent, httpWitness);
49
+ * // witnessedEvent.proof now has 2 proofs: original + witness
50
+ * ```
51
+ */
52
+ export async function witnessEvent(
53
+ event: LogEntry,
54
+ witness: WitnessService
55
+ ): Promise<LogEntry> {
56
+ // Validate inputs
57
+ if (!event) {
58
+ throw new Error('Event is required');
59
+ }
60
+ if (!event.proof || event.proof.length === 0) {
61
+ throw new Error('Event must have at least one proof (controller proof)');
62
+ }
63
+ if (!witness) {
64
+ throw new Error('Witness service is required');
65
+ }
66
+
67
+ // Compute digest of the event
68
+ const eventBytes = serializeEntry(event);
69
+ const digestMultibase = computeDigestMultibase(eventBytes);
70
+
71
+ // Get witness proof
72
+ const witnessProof: WitnessProof = await witness.witness(digestMultibase);
73
+
74
+ // Validate witness proof has required fields
75
+ if (!witnessProof.type || !witnessProof.cryptosuite || !witnessProof.proofValue) {
76
+ throw new Error('Invalid witness proof: missing required fields (type, cryptosuite, proofValue)');
77
+ }
78
+ if (!witnessProof.witnessedAt) {
79
+ throw new Error('Invalid witness proof: missing witnessedAt timestamp');
80
+ }
81
+
82
+ // Return new event with witness proof appended (immutable - don't modify input)
83
+ return {
84
+ ...event,
85
+ proof: [...event.proof, witnessProof],
86
+ };
87
+ }