xoonya 1.0.0

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.
package/proof_sdk.js ADDED
@@ -0,0 +1,745 @@
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+
5
+ const MAGIC_HEX = "50524654";
6
+ const VERSION_V1_HEX = "01";
7
+ const VERSION_V2_HEX = "02";
8
+ const TYPE_REGISTER = "01";
9
+ const VALID_TYPES = new Set(["01", "02", "03"]);
10
+ const ZERO_HASH32 = "00".repeat(32);
11
+ const ZERO_CONTEXT12 = "00".repeat(12);
12
+ const PROOF_ENVELOPE_VERSION = "proof-envelope-v1";
13
+ const ECONOMIC_RECEIPT_PROFILE_VERSION = "economic-receipt-profile-v1";
14
+ const REGISTRY_PROFILE_VERSION = "registry-profile-v1";
15
+ const CANONICAL_EVENT_BUNDLE_VERSION = "canonical-event-bundle/v1";
16
+ const CANONICAL_RECEIPT_VERSION = "receipt/v1";
17
+ const PROOF_BUNDLE_VERSION = "proof-bundle/v1";
18
+ const REGISTRY_EVENT_TO_TYPE = {
19
+ register: "01",
20
+ update: "02",
21
+ revoke: "03"
22
+ };
23
+
24
+ function sha256Hex(buf) {
25
+ return crypto.createHash("sha256").update(buf).digest("hex");
26
+ }
27
+
28
+ function hash160Hex(buf) {
29
+ const sha = crypto.createHash("sha256").update(buf).digest();
30
+ return crypto.createHash("ripemd160").update(sha).digest("hex");
31
+ }
32
+
33
+ function assertHex(hex, expectedLen, label) {
34
+ const normalized = String(hex || "").trim().toLowerCase();
35
+ if (!/^[a-f0-9]+$/.test(normalized) || normalized.length !== expectedLen) {
36
+ throw new Error(`${label} must be ${expectedLen / 2} byte hex`);
37
+ }
38
+ return normalized;
39
+ }
40
+
41
+ function canonicalize(value) {
42
+ if (Array.isArray(value)) {
43
+ return `[${value.map((item) => canonicalize(item)).join(",")}]`;
44
+ }
45
+ if (value && typeof value === "object") {
46
+ const keys = Object.keys(value).sort();
47
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`).join(",")}}`;
48
+ }
49
+ return JSON.stringify(value);
50
+ }
51
+
52
+ function canonicalJsonSha256Hex(value) {
53
+ return sha256Hex(Buffer.from(canonicalize(value), "utf8"));
54
+ }
55
+
56
+ function normalizeString(value) {
57
+ return String(value == null ? "" : value).trim();
58
+ }
59
+
60
+ function normalizeStringArray(values, label) {
61
+ if (values == null) return undefined;
62
+ if (!Array.isArray(values)) {
63
+ throw new Error(`${label} must be an array`);
64
+ }
65
+ const normalized = values
66
+ .map((value) => normalizeString(value))
67
+ .filter(Boolean);
68
+ return normalized.length ? normalized : undefined;
69
+ }
70
+
71
+ function normalizePlainObject(value, label) {
72
+ if (value == null) return undefined;
73
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
74
+ throw new Error(`${label} must be an object`);
75
+ }
76
+ return value;
77
+ }
78
+
79
+ function canonicalizeEvent(event) {
80
+ if (!event || typeof event !== "object" || Array.isArray(event)) {
81
+ throw new Error("event must be an object");
82
+ }
83
+ const normalized = {
84
+ version: normalizeString(event.version || CANONICAL_EVENT_BUNDLE_VERSION),
85
+ domain_id: normalizeString(event.domain_id),
86
+ sequence: normalizeString(event.sequence),
87
+ actor_id: normalizeString(event.actor_id),
88
+ adapter_source: normalizeString(event.adapter_source),
89
+ event_type: normalizeString(event.event_type),
90
+ event_hash: normalizeString(event.event_hash).toLowerCase()
91
+ };
92
+ Object.entries(normalized).forEach(([key, value]) => {
93
+ if (!value) {
94
+ throw new Error(`event.${key} required`);
95
+ }
96
+ });
97
+ if (normalized.version !== CANONICAL_EVENT_BUNDLE_VERSION) {
98
+ throw new Error(`event.version must be ${CANONICAL_EVENT_BUNDLE_VERSION}`);
99
+ }
100
+ assertHex(normalized.event_hash, 64, "event.event_hash");
101
+
102
+ const timestampNs = normalizeString(event.timestamp_ns);
103
+ if (timestampNs) {
104
+ normalized.timestamp_ns = timestampNs;
105
+ }
106
+
107
+ const parentRefs = normalizeStringArray(event.parent_refs, "event.parent_refs");
108
+ if (parentRefs) {
109
+ normalized.parent_refs = parentRefs;
110
+ }
111
+
112
+ const capabilityRefs = normalizeStringArray(event.capability_refs, "event.capability_refs");
113
+ if (capabilityRefs) {
114
+ normalized.capability_refs = capabilityRefs;
115
+ }
116
+
117
+ const attestationRef = normalizeString(event.attestation_ref);
118
+ if (attestationRef) {
119
+ normalized.attestation_ref = attestationRef;
120
+ }
121
+
122
+ const semanticSlots = normalizePlainObject(event.semantic_slots, "event.semantic_slots");
123
+ if (semanticSlots) {
124
+ normalized.semantic_slots = semanticSlots;
125
+ }
126
+
127
+ return normalized;
128
+ }
129
+
130
+ function hashEvent(event) {
131
+ return canonicalJsonSha256Hex(canonicalizeEvent(event));
132
+ }
133
+
134
+ function buildCanonicalReceiptSigningMaterial(receipt) {
135
+ return {
136
+ agent_identity: receipt.agent_identity,
137
+ domain_id: receipt.domain_id,
138
+ event_commitment: receipt.event_commitment,
139
+ receipt_id: receipt.receipt_id,
140
+ sequence: receipt.sequence,
141
+ version: receipt.version
142
+ };
143
+ }
144
+
145
+ function emitReceipt(event, options = {}) {
146
+ const canonicalEvent = canonicalizeEvent(event);
147
+ const eventCommitment = hashEvent(canonicalEvent);
148
+ const agentIdentity = normalizeString(options.agent_identity || options.agentIdentity || canonicalEvent.actor_id);
149
+ if (!agentIdentity) {
150
+ throw new Error("agent_identity required");
151
+ }
152
+
153
+ const legacyIdentityMaterialHex = options.legacy_identity_material_hex == null
154
+ ? null
155
+ : String(options.legacy_identity_material_hex).trim().toLowerCase();
156
+ let compatibility = null;
157
+ if (legacyIdentityMaterialHex) {
158
+ const legacyReceipt = createReceipt(legacyIdentityMaterialHex, Buffer.from(canonicalize(canonicalEvent), "utf8"));
159
+ compatibility = {
160
+ legacy_receipt: {
161
+ agent_id: legacyReceipt.agent_id,
162
+ opreturn_hex: legacyReceipt.opreturn_hex,
163
+ payload_hash: legacyReceipt.payload_hash,
164
+ spec_version: legacyReceipt.spec_version
165
+ }
166
+ };
167
+ }
168
+
169
+ const receiptId = sha256Hex(Buffer.from(
170
+ canonicalize({
171
+ agent_identity: agentIdentity,
172
+ domain_id: canonicalEvent.domain_id,
173
+ event_commitment: eventCommitment,
174
+ sequence: canonicalEvent.sequence,
175
+ version: CANONICAL_RECEIPT_VERSION
176
+ }),
177
+ "utf8"
178
+ ));
179
+
180
+ const unsignedReceipt = {
181
+ version: CANONICAL_RECEIPT_VERSION,
182
+ receipt_id: receiptId,
183
+ agent_identity: agentIdentity,
184
+ domain_id: canonicalEvent.domain_id,
185
+ sequence: canonicalEvent.sequence,
186
+ event_commitment: eventCommitment,
187
+ algorithm: normalizeString(options.algorithm || (compatibility ? "legacy-opreturn-v1" : "none"))
188
+ };
189
+
190
+ if (compatibility) {
191
+ unsignedReceipt.compatibility = compatibility;
192
+ }
193
+
194
+ let signature = options.signature == null ? "" : normalizeString(options.signature);
195
+ if (!signature && typeof options.signer === "function") {
196
+ signature = normalizeString(options.signer(buildCanonicalReceiptSigningMaterial(unsignedReceipt), canonicalEvent));
197
+ }
198
+ if (!signature && compatibility) {
199
+ signature = compatibility.legacy_receipt.opreturn_hex;
200
+ }
201
+ if (!signature) {
202
+ throw new Error("signature, signer, or legacy_identity_material_hex required");
203
+ }
204
+
205
+ return {
206
+ ...unsignedReceipt,
207
+ signature
208
+ };
209
+ }
210
+
211
+ function buildProofBundle(input) {
212
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
213
+ throw new Error("input must be an object");
214
+ }
215
+ const ceb = canonicalizeEvent(input.ceb || input.event);
216
+ const receipt = input.receipt || emitReceipt(ceb, input.receipt_options || {});
217
+ return {
218
+ version: PROOF_BUNDLE_VERSION,
219
+ ceb,
220
+ receipt,
221
+ anchors: Array.isArray(input.anchors) ? input.anchors : undefined,
222
+ attestations: Array.isArray(input.attestations) ? input.attestations : undefined,
223
+ evidence_refs: Array.isArray(input.evidence_refs) ? input.evidence_refs : undefined,
224
+ batch_refs: Array.isArray(input.batch_refs) ? input.batch_refs : undefined,
225
+ metadata: input.metadata && typeof input.metadata === "object" && !Array.isArray(input.metadata) ? input.metadata : undefined
226
+ };
227
+ }
228
+
229
+ function verifyProofBundle(bundle) {
230
+ try {
231
+ if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
232
+ return { valid: false, reason: "bundle_not_object" };
233
+ }
234
+ if (normalizeString(bundle.version) !== PROOF_BUNDLE_VERSION) {
235
+ return { valid: false, reason: "bad_bundle_version" };
236
+ }
237
+ const ceb = canonicalizeEvent(bundle.ceb);
238
+ const receipt = bundle.receipt;
239
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
240
+ return { valid: false, reason: "receipt_not_object" };
241
+ }
242
+ if (normalizeString(receipt.version) !== CANONICAL_RECEIPT_VERSION) {
243
+ return { valid: false, reason: "bad_receipt_version" };
244
+ }
245
+ if (normalizeString(receipt.domain_id) !== ceb.domain_id) {
246
+ return { valid: false, reason: "receipt_domain_mismatch" };
247
+ }
248
+ if (normalizeString(receipt.sequence) !== ceb.sequence) {
249
+ return { valid: false, reason: "receipt_sequence_mismatch" };
250
+ }
251
+ const expectedCommitment = hashEvent(ceb);
252
+ if (normalizeString(receipt.event_commitment).toLowerCase() !== expectedCommitment) {
253
+ return { valid: false, reason: "event_commitment_mismatch" };
254
+ }
255
+ const expectedReceiptId = sha256Hex(Buffer.from(
256
+ canonicalize({
257
+ agent_identity: normalizeString(receipt.agent_identity),
258
+ domain_id: ceb.domain_id,
259
+ event_commitment: expectedCommitment,
260
+ sequence: ceb.sequence,
261
+ version: CANONICAL_RECEIPT_VERSION
262
+ }),
263
+ "utf8"
264
+ ));
265
+ if (normalizeString(receipt.receipt_id).toLowerCase() !== expectedReceiptId) {
266
+ return { valid: false, reason: "receipt_id_mismatch" };
267
+ }
268
+ if (!normalizeString(receipt.agent_identity)) {
269
+ return { valid: false, reason: "agent_identity_missing" };
270
+ }
271
+ if (!normalizeString(receipt.signature)) {
272
+ return { valid: false, reason: "signature_missing" };
273
+ }
274
+
275
+ const legacyReceipt = receipt.compatibility && receipt.compatibility.legacy_receipt;
276
+ if (legacyReceipt) {
277
+ const legacyVerification = verifyReceipt(
278
+ legacyReceipt.opreturn_hex,
279
+ Buffer.from(canonicalize(ceb), "utf8")
280
+ );
281
+ if (!legacyVerification.valid) {
282
+ return { valid: false, reason: `legacy_receipt_${legacyVerification.reason}` };
283
+ }
284
+ if (normalizeString(legacyReceipt.agent_id).toLowerCase() !== normalizeString(legacyVerification.agent_id).toLowerCase()) {
285
+ return { valid: false, reason: "legacy_agent_id_mismatch" };
286
+ }
287
+ if (normalizeString(receipt.signature).toLowerCase() !== normalizeString(legacyReceipt.opreturn_hex).toLowerCase()) {
288
+ return { valid: false, reason: "signature_legacy_mismatch" };
289
+ }
290
+ }
291
+
292
+ return {
293
+ valid: true,
294
+ reason: null,
295
+ version: PROOF_BUNDLE_VERSION,
296
+ event_commitment: expectedCommitment,
297
+ receipt_id: expectedReceiptId
298
+ };
299
+ } catch (error) {
300
+ return { valid: false, reason: error.message };
301
+ }
302
+ }
303
+
304
+ function parseReceipt(opreturnHex) {
305
+ const hex = String(opreturnHex || "").trim().toLowerCase();
306
+ if (!/^[a-f0-9]+$/.test(hex)) {
307
+ throw new Error("opreturnHex must be hex");
308
+ }
309
+ if (hex.length !== 116 && hex.length !== 140) {
310
+ throw new Error("opreturnHex must be a supported v1 or v2 receipt");
311
+ }
312
+ const magic = hex.slice(0, 8);
313
+ const version = hex.slice(8, 10);
314
+ const recordType = hex.slice(10, 12);
315
+ const agentId = hex.slice(12, 52);
316
+ const payloadHash = hex.slice(52, 116);
317
+ if (magic !== MAGIC_HEX) {
318
+ throw new Error("opreturnHex has bad receipt magic");
319
+ }
320
+ if (!VALID_TYPES.has(recordType)) {
321
+ throw new Error("opreturnHex has unsupported record type");
322
+ }
323
+ if (version === VERSION_V1_HEX && hex.length === 116) {
324
+ return {
325
+ spec_version: "v1",
326
+ record_type_hex: recordType,
327
+ agent_id: agentId,
328
+ payload_hash_sha256: payloadHash,
329
+ receipt_hash_sha256: sha256Hex(Buffer.from(hex, "hex")),
330
+ opreturn_hex: hex
331
+ };
332
+ }
333
+ if (version === VERSION_V2_HEX && hex.length === 140) {
334
+ return {
335
+ spec_version: "v2",
336
+ record_type_hex: recordType,
337
+ agent_id: agentId,
338
+ payload_hash_sha256: payloadHash,
339
+ context_hash12: hex.slice(116, 140),
340
+ receipt_hash_sha256: sha256Hex(Buffer.from(hex, "hex")),
341
+ opreturn_hex: hex
342
+ };
343
+ }
344
+ throw new Error("opreturnHex/version length mismatch");
345
+ }
346
+
347
+ function normalizeProofEntry(proof, index) {
348
+ if (!proof || typeof proof !== "object" || Array.isArray(proof)) {
349
+ throw new Error(`proofs[${index}] must be an object`);
350
+ }
351
+ const normalized = {
352
+ proof_id: String(proof.proof_id || "").trim(),
353
+ proof_type: String(proof.proof_type || "").trim(),
354
+ role: String(proof.role || "").trim(),
355
+ media_type: String(proof.media_type || "").trim(),
356
+ proof_hash_sha256: assertHex(proof.proof_hash_sha256, 64, `proofs[${index}].proof_hash_sha256`),
357
+ locator: String(proof.locator || "").trim()
358
+ };
359
+ if (!normalized.proof_id) throw new Error(`proofs[${index}].proof_id required`);
360
+ if (!normalized.proof_type) throw new Error(`proofs[${index}].proof_type required`);
361
+ if (!normalized.role) throw new Error(`proofs[${index}].role required`);
362
+ if (!normalized.media_type) throw new Error(`proofs[${index}].media_type required`);
363
+ if (!normalized.locator) throw new Error(`proofs[${index}].locator required`);
364
+ return normalized;
365
+ }
366
+
367
+ function makeProofEnvelopeBindingMaterial(receiptRef, proofs) {
368
+ return {
369
+ envelope_version: PROOF_ENVELOPE_VERSION,
370
+ payload_hash_sha256: receiptRef.payload_hash_sha256,
371
+ proofs: proofs.map((proof) => ({
372
+ locator: proof.locator,
373
+ media_type: proof.media_type,
374
+ proof_hash_sha256: proof.proof_hash_sha256,
375
+ proof_id: proof.proof_id,
376
+ proof_type: proof.proof_type,
377
+ role: proof.role
378
+ })),
379
+ receipt_hash_sha256: receiptRef.receipt_hash_sha256
380
+ };
381
+ }
382
+
383
+ function makeIdentityMaterial(typePrefixHex, rawBytesHex) {
384
+ const typeHex = assertHex(typePrefixHex, 2, "typePrefixHex");
385
+ const rawHex = String(rawBytesHex || "").trim().toLowerCase();
386
+ if (!/^[a-f0-9]+$/.test(rawHex) || rawHex.length === 0) {
387
+ throw new Error("rawBytesHex must be hex");
388
+ }
389
+ return typeHex + rawHex;
390
+ }
391
+
392
+ function createIdentity(identityMaterialHex) {
393
+ const materialHex = String(identityMaterialHex || "").trim().toLowerCase();
394
+ if (!materialHex) {
395
+ throw new Error("identityMaterialHex required (type_prefix || raw_bytes)");
396
+ }
397
+ const material = Buffer.from(materialHex, "hex");
398
+ const agentId = hash160Hex(material);
399
+ return { identity_material_hex: materialHex, agent_id: agentId };
400
+ }
401
+
402
+ function createReceipt(identityMaterialHex, payloadBytes, typeHex = TYPE_REGISTER) {
403
+ const materialHex = String(identityMaterialHex || "").trim().toLowerCase();
404
+ if (!materialHex) {
405
+ throw new Error("identityMaterialHex required (type_prefix || raw_bytes)");
406
+ }
407
+ const type = assertHex(typeHex, 2, "typeHex");
408
+ if (!VALID_TYPES.has(type)) {
409
+ throw new Error("typeHex must be 01, 02, or 03");
410
+ }
411
+ const agentId = hash160Hex(Buffer.from(materialHex, "hex"));
412
+ const payloadHash = payloadBytes && payloadBytes.length
413
+ ? sha256Hex(Buffer.from(payloadBytes))
414
+ : ZERO_HASH32;
415
+ const opreturnHex = MAGIC_HEX + VERSION_V1_HEX + type + agentId + payloadHash;
416
+ return {
417
+ opreturn_hex: opreturnHex,
418
+ agent_id: agentId,
419
+ payload_hash: payloadHash,
420
+ spec_version: "v1"
421
+ };
422
+ }
423
+
424
+ function createReceiptV2(identityMaterialHex, payloadBytes, contextHash12Hex, typeHex = TYPE_REGISTER) {
425
+ const materialHex = String(identityMaterialHex || "").trim().toLowerCase();
426
+ if (!materialHex) {
427
+ throw new Error("identityMaterialHex required (type_prefix || raw_bytes)");
428
+ }
429
+ const type = assertHex(typeHex, 2, "typeHex");
430
+ if (!VALID_TYPES.has(type)) {
431
+ throw new Error("typeHex must be 01, 02, or 03");
432
+ }
433
+ const context = assertHex(contextHash12Hex, 24, "contextHash12Hex");
434
+ if (context === ZERO_CONTEXT12) {
435
+ throw new Error("contextHash12Hex cannot be all-zero");
436
+ }
437
+ const agentId = hash160Hex(Buffer.from(materialHex, "hex"));
438
+ const payloadHash = payloadBytes && payloadBytes.length
439
+ ? sha256Hex(Buffer.from(payloadBytes))
440
+ : ZERO_HASH32;
441
+ const opreturnHex = MAGIC_HEX + VERSION_V2_HEX + type + agentId + payloadHash + context;
442
+ return {
443
+ opreturn_hex: opreturnHex,
444
+ agent_id: agentId,
445
+ payload_hash: payloadHash,
446
+ context_hash12: context,
447
+ spec_version: "v2"
448
+ };
449
+ }
450
+
451
+ function verifyReceipt(opreturnHex, payloadBytes) {
452
+ const hex = String(opreturnHex || "").trim().toLowerCase();
453
+ if (!/^[a-f0-9]+$/.test(hex)) {
454
+ return { valid: false, reason: "non_hex", spec_version: "v1", compliance_hash: null };
455
+ }
456
+ if (hex.length !== 116) {
457
+ return { valid: false, reason: "invalid_length", spec_version: "v1", compliance_hash: null };
458
+ }
459
+ const magic = hex.slice(0, 8);
460
+ const version = hex.slice(8, 10);
461
+ const type = hex.slice(10, 12);
462
+ const agentId = hex.slice(12, 52);
463
+ const payloadHash = hex.slice(52, 116);
464
+ if (magic !== MAGIC_HEX) return { valid: false, reason: "bad_magic", spec_version: "v1", compliance_hash: null };
465
+ if (version !== VERSION_V1_HEX) return { valid: false, reason: "bad_version", spec_version: "v1", compliance_hash: null };
466
+ if (!VALID_TYPES.has(type)) return { valid: false, reason: "bad_type", spec_version: "v1", compliance_hash: null };
467
+ const expectedPayload = payloadBytes && payloadBytes.length
468
+ ? sha256Hex(Buffer.from(payloadBytes))
469
+ : ZERO_HASH32;
470
+ if (expectedPayload !== payloadHash) {
471
+ return { valid: false, reason: "payload_mismatch", spec_version: "v1", compliance_hash: null };
472
+ }
473
+ return {
474
+ valid: true,
475
+ reason: null,
476
+ spec_version: "v1",
477
+ compliance_hash: sha256Hex(Buffer.from(hex, "hex")),
478
+ agent_id: agentId
479
+ };
480
+ }
481
+
482
+ function verifyReceiptV2(opreturnHex, payloadBytes) {
483
+ const hex = String(opreturnHex || "").trim().toLowerCase();
484
+ if (!/^[a-f0-9]+$/.test(hex)) {
485
+ return { valid: false, reason: "non_hex", spec_version: "v2", compliance_hash: null };
486
+ }
487
+ if (hex.length !== 140) {
488
+ return { valid: false, reason: "invalid_length", spec_version: "v2", compliance_hash: null };
489
+ }
490
+ const magic = hex.slice(0, 8);
491
+ const version = hex.slice(8, 10);
492
+ const type = hex.slice(10, 12);
493
+ const agentId = hex.slice(12, 52);
494
+ const payloadHash = hex.slice(52, 116);
495
+ const contextHash12 = hex.slice(116, 140);
496
+ if (magic !== MAGIC_HEX) return { valid: false, reason: "bad_magic", spec_version: "v2", compliance_hash: null };
497
+ if (version !== VERSION_V2_HEX) return { valid: false, reason: "bad_version", spec_version: "v2", compliance_hash: null };
498
+ if (!VALID_TYPES.has(type)) return { valid: false, reason: "bad_type", spec_version: "v2", compliance_hash: null };
499
+ if (contextHash12 === ZERO_CONTEXT12) return { valid: false, reason: "context_zero_forbidden", spec_version: "v2", compliance_hash: null };
500
+ const expectedPayload = payloadBytes && payloadBytes.length
501
+ ? sha256Hex(Buffer.from(payloadBytes))
502
+ : ZERO_HASH32;
503
+ if (expectedPayload !== payloadHash) {
504
+ return { valid: false, reason: "payload_mismatch", spec_version: "v2", compliance_hash: null };
505
+ }
506
+ return {
507
+ valid: true,
508
+ reason: null,
509
+ spec_version: "v2",
510
+ compliance_hash: sha256Hex(Buffer.from(hex, "hex")),
511
+ agent_id: agentId,
512
+ context_hash12: contextHash12
513
+ };
514
+ }
515
+
516
+ function createProofEnvelopeV1(opreturnHex, proofs, metadata = {}) {
517
+ const receiptRef = parseReceipt(opreturnHex);
518
+ const normalizedProofs = Array.isArray(proofs) ? proofs.map(normalizeProofEntry) : [];
519
+ if (normalizedProofs.length === 0) throw new Error("proofs must be a non-empty array");
520
+ const proofIds = new Set();
521
+ normalizedProofs.forEach((proof) => {
522
+ if (proofIds.has(proof.proof_id)) throw new Error(`duplicate proof_id: ${proof.proof_id}`);
523
+ proofIds.add(proof.proof_id);
524
+ });
525
+ return {
526
+ envelope_version: PROOF_ENVELOPE_VERSION,
527
+ receipt_ref: {
528
+ opreturn_hex: receiptRef.opreturn_hex,
529
+ spec_version: receiptRef.spec_version,
530
+ receipt_hash_sha256: receiptRef.receipt_hash_sha256,
531
+ payload_hash_sha256: receiptRef.payload_hash_sha256
532
+ },
533
+ proofs: normalizedProofs,
534
+ binding_hash_sha256: canonicalJsonSha256Hex(makeProofEnvelopeBindingMaterial(receiptRef, normalizedProofs)),
535
+ metadata: metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : {}
536
+ };
537
+ }
538
+
539
+ function verifyProofEnvelopeV1(envelope) {
540
+ try {
541
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
542
+ return { valid: false, reason: "envelope_not_object" };
543
+ }
544
+ if (envelope.envelope_version !== PROOF_ENVELOPE_VERSION) {
545
+ return { valid: false, reason: "bad_envelope_version" };
546
+ }
547
+ const receiptRef = parseReceipt(envelope.receipt_ref && envelope.receipt_ref.opreturn_hex);
548
+ if (String((envelope.receipt_ref && envelope.receipt_ref.spec_version) || "") !== receiptRef.spec_version) {
549
+ return { valid: false, reason: "receipt_spec_version_mismatch" };
550
+ }
551
+ if (String((envelope.receipt_ref && envelope.receipt_ref.receipt_hash_sha256) || "").toLowerCase() !== receiptRef.receipt_hash_sha256) {
552
+ return { valid: false, reason: "receipt_hash_mismatch" };
553
+ }
554
+ if (String((envelope.receipt_ref && envelope.receipt_ref.payload_hash_sha256) || "").toLowerCase() !== receiptRef.payload_hash_sha256) {
555
+ return { valid: false, reason: "payload_hash_mismatch" };
556
+ }
557
+ const proofs = Array.isArray(envelope.proofs) ? envelope.proofs.map(normalizeProofEntry) : [];
558
+ if (proofs.length === 0) return { valid: false, reason: "proofs_empty" };
559
+ const proofIds = new Set();
560
+ for (const proof of proofs) {
561
+ if (proofIds.has(proof.proof_id)) return { valid: false, reason: "proof_duplicate_id" };
562
+ proofIds.add(proof.proof_id);
563
+ }
564
+ const expectedBindingHash = canonicalJsonSha256Hex(makeProofEnvelopeBindingMaterial(receiptRef, proofs));
565
+ if (String(envelope.binding_hash_sha256 || "").toLowerCase() !== expectedBindingHash) {
566
+ return { valid: false, reason: "binding_hash_mismatch" };
567
+ }
568
+ return {
569
+ valid: true,
570
+ reason: null,
571
+ envelope_version: PROOF_ENVELOPE_VERSION,
572
+ binding_hash_sha256: expectedBindingHash
573
+ };
574
+ } catch (error) {
575
+ return { valid: false, reason: error.message };
576
+ }
577
+ }
578
+
579
+ function createEconomicReceiptProfileV1(opreturnHex, payload, proofBinding) {
580
+ const receiptRef = parseReceipt(opreturnHex);
581
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("payload must be an object");
582
+ const normalizedPayload = {
583
+ agent_id: assertHex(payload.agent_id, 40, "payload.agent_id"),
584
+ counterparty_id: String(payload.counterparty_id || "").trim(),
585
+ event_class: String(payload.event_class || "").trim(),
586
+ event_id: String(payload.event_id || "").trim(),
587
+ meter_profile_id: String(payload.meter_profile_id || "").trim(),
588
+ quantity: String(payload.quantity || "").trim(),
589
+ unit: String(payload.unit || "").trim(),
590
+ usage_hash_sha256: assertHex(payload.usage_hash_sha256, 64, "payload.usage_hash_sha256"),
591
+ window_end_unix_ns: String(payload.window_end_unix_ns || "").trim(),
592
+ window_start_unix_ns: String(payload.window_start_unix_ns || "").trim()
593
+ };
594
+ const start = BigInt(normalizedPayload.window_start_unix_ns);
595
+ const end = BigInt(normalizedPayload.window_end_unix_ns);
596
+ if (end < start) throw new Error("window_order_invalid");
597
+ const payloadHash = canonicalJsonSha256Hex(normalizedPayload);
598
+ if (payloadHash !== receiptRef.payload_hash_sha256) throw new Error("payload_hash_mismatch");
599
+ if (!proofBinding || typeof proofBinding !== "object" || Array.isArray(proofBinding)) throw new Error("proofBinding must be an object");
600
+ const bindingHash = assertHex(proofBinding.binding_hash_sha256, 64, "proofBinding.binding_hash_sha256");
601
+ const envelopeVersion = String(proofBinding.envelope_version || "").trim();
602
+ if (envelopeVersion !== PROOF_ENVELOPE_VERSION) throw new Error("bad_proof_binding_envelope_version");
603
+ return {
604
+ profile_version: ECONOMIC_RECEIPT_PROFILE_VERSION,
605
+ receipt_ref: {
606
+ opreturn_hex: receiptRef.opreturn_hex,
607
+ spec_version: receiptRef.spec_version,
608
+ receipt_hash_sha256: receiptRef.receipt_hash_sha256,
609
+ payload_hash_sha256: receiptRef.payload_hash_sha256
610
+ },
611
+ payload: normalizedPayload,
612
+ payload_hash_sha256: payloadHash,
613
+ proof_binding: {
614
+ binding_hash_sha256: bindingHash,
615
+ envelope_version: envelopeVersion
616
+ }
617
+ };
618
+ }
619
+
620
+ function verifyEconomicReceiptProfileV1(profile) {
621
+ try {
622
+ if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
623
+ return { valid: false, reason: "profile_not_object" };
624
+ }
625
+ if (profile.profile_version !== ECONOMIC_RECEIPT_PROFILE_VERSION) {
626
+ return { valid: false, reason: "bad_profile_version" };
627
+ }
628
+ const rebuilt = createEconomicReceiptProfileV1(profile.receipt_ref && profile.receipt_ref.opreturn_hex, profile.payload, profile.proof_binding);
629
+ if (String(profile.payload_hash_sha256 || "").toLowerCase() !== rebuilt.payload_hash_sha256) {
630
+ return { valid: false, reason: "payload_hash_mismatch" };
631
+ }
632
+ return {
633
+ valid: true,
634
+ reason: null,
635
+ profile_version: ECONOMIC_RECEIPT_PROFILE_VERSION,
636
+ payload_hash_sha256: rebuilt.payload_hash_sha256
637
+ };
638
+ } catch (error) {
639
+ return { valid: false, reason: error.message };
640
+ }
641
+ }
642
+
643
+ function createRegistryProfileV1(opreturnHex, eventType, lifecycle = {}, payloadProfileHint = null) {
644
+ const receiptRef = parseReceipt(opreturnHex);
645
+ const normalizedEventType = String(eventType || "").trim();
646
+ if (!REGISTRY_EVENT_TO_TYPE[normalizedEventType]) throw new Error("event_type_invalid");
647
+ if (receiptRef.record_type_hex !== REGISTRY_EVENT_TO_TYPE[normalizedEventType]) throw new Error("event_type_mapping_invalid");
648
+ const sequence = String(lifecycle.sequence || "").trim();
649
+ if (!/^[1-9][0-9]*$/.test(sequence)) throw new Error("sequence_invalid");
650
+ const normalizedLifecycle = { sequence };
651
+ const previous = lifecycle.previous_receipt_hash_sha256 == null ? "" : String(lifecycle.previous_receipt_hash_sha256).trim().toLowerCase();
652
+ if (normalizedEventType === "register") {
653
+ if (previous) throw new Error("register_previous_forbidden");
654
+ } else {
655
+ normalizedLifecycle.previous_receipt_hash_sha256 = assertHex(previous, 64, "lifecycle.previous_receipt_hash_sha256");
656
+ }
657
+ const rotationFrom = lifecycle.rotation_from_agent_id == null ? "" : String(lifecycle.rotation_from_agent_id).trim().toLowerCase();
658
+ if (rotationFrom) {
659
+ if (normalizedEventType !== "update") throw new Error("rotation_from_only_update");
660
+ normalizedLifecycle.rotation_from_agent_id = assertHex(rotationFrom, 40, "lifecycle.rotation_from_agent_id");
661
+ }
662
+ if (lifecycle.rotation_proof_binding != null) {
663
+ if (normalizedEventType !== "update") throw new Error("rotation_binding_only_update");
664
+ const binding = lifecycle.rotation_proof_binding;
665
+ if (!binding || typeof binding !== "object" || Array.isArray(binding)) throw new Error("rotation_binding_invalid");
666
+ normalizedLifecycle.rotation_proof_binding = {
667
+ binding_hash_sha256: assertHex(binding.binding_hash_sha256, 64, "lifecycle.rotation_proof_binding.binding_hash_sha256"),
668
+ envelope_version: String(binding.envelope_version || "").trim()
669
+ };
670
+ if (normalizedLifecycle.rotation_proof_binding.envelope_version !== PROOF_ENVELOPE_VERSION) {
671
+ throw new Error("bad_rotation_proof_binding_envelope_version");
672
+ }
673
+ }
674
+ return {
675
+ profile_version: REGISTRY_PROFILE_VERSION,
676
+ event_type: normalizedEventType,
677
+ receipt_ref: {
678
+ opreturn_hex: receiptRef.opreturn_hex,
679
+ spec_version: receiptRef.spec_version,
680
+ receipt_hash_sha256: receiptRef.receipt_hash_sha256,
681
+ agent_id: receiptRef.agent_id,
682
+ record_type_hex: receiptRef.record_type_hex,
683
+ payload_hash_sha256: receiptRef.payload_hash_sha256
684
+ },
685
+ lifecycle: normalizedLifecycle,
686
+ payload_profile_hint: payloadProfileHint == null ? undefined : String(payloadProfileHint)
687
+ };
688
+ }
689
+
690
+ function verifyRegistryProfileV1(profile) {
691
+ try {
692
+ if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
693
+ return { valid: false, reason: "profile_not_object" };
694
+ }
695
+ if (profile.profile_version !== REGISTRY_PROFILE_VERSION) {
696
+ return { valid: false, reason: "bad_profile_version" };
697
+ }
698
+ const rebuilt = createRegistryProfileV1(
699
+ profile.receipt_ref && profile.receipt_ref.opreturn_hex,
700
+ profile.event_type,
701
+ profile.lifecycle,
702
+ profile.payload_profile_hint
703
+ );
704
+ if (String((profile.receipt_ref && profile.receipt_ref.receipt_hash_sha256) || "").toLowerCase() !== rebuilt.receipt_ref.receipt_hash_sha256) {
705
+ return { valid: false, reason: "receipt_hash_mismatch" };
706
+ }
707
+ if (String((profile.receipt_ref && profile.receipt_ref.agent_id) || "").toLowerCase() !== rebuilt.receipt_ref.agent_id) {
708
+ return { valid: false, reason: "agent_id_mismatch" };
709
+ }
710
+ if (String((profile.receipt_ref && profile.receipt_ref.record_type_hex) || "").toLowerCase() !== rebuilt.receipt_ref.record_type_hex) {
711
+ return { valid: false, reason: "record_type_mismatch" };
712
+ }
713
+ if (String((profile.receipt_ref && profile.receipt_ref.payload_hash_sha256) || "").toLowerCase() !== rebuilt.receipt_ref.payload_hash_sha256) {
714
+ return { valid: false, reason: "payload_hash_mismatch" };
715
+ }
716
+ return {
717
+ valid: true,
718
+ reason: null,
719
+ profile_version: REGISTRY_PROFILE_VERSION
720
+ };
721
+ } catch (error) {
722
+ return { valid: false, reason: error.message };
723
+ }
724
+ }
725
+
726
+ module.exports = {
727
+ canonicalizeEvent,
728
+ hashEvent,
729
+ emitReceipt,
730
+ buildProofBundle,
731
+ verifyProofBundle,
732
+ parseReceipt,
733
+ makeIdentityMaterial,
734
+ createIdentity,
735
+ createReceipt,
736
+ verifyReceipt,
737
+ createReceiptV2,
738
+ verifyReceiptV2,
739
+ createProofEnvelopeV1,
740
+ verifyProofEnvelopeV1,
741
+ createEconomicReceiptProfileV1,
742
+ verifyEconomicReceiptProfileV1,
743
+ createRegistryProfileV1,
744
+ verifyRegistryProfileV1
745
+ };