@forestrie/receipt-verify 0.3.0 → 0.5.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/dist/build-receipt-offline.d.ts +69 -0
- package/dist/build-receipt-offline.d.ts.map +1 -0
- package/dist/build-receipt-offline.js +165 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/parse-receipt.d.ts +4 -2
- package/dist/parse-receipt.d.ts.map +1 -1
- package/dist/parse-receipt.js +3 -3
- package/package.json +3 -3
- package/src/build-receipt-offline.ts +250 -0
- package/src/index.ts +17 -0
- package/src/parse-receipt.ts +4 -4
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-created SCITT receipts from local artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors canopy-api resolve-receipt receipt assembly, but over local massif
|
|
5
|
+
* (`.log`) and checkpoint (`.sth`) bytes instead of R2 objects. The checkpoint
|
|
6
|
+
* signature covers only the accumulator, so the inclusion path is free to
|
|
7
|
+
* rebuild client-side: read massif nodes, build the leaf→peak path, attach it
|
|
8
|
+
* at header 396 to the checkpoint's pre-signed peak receipt. The result is
|
|
9
|
+
* verify-equivalent with an API-issued receipt (FOR-334 AC: both pass
|
|
10
|
+
* verifyGrantReceiptOffline identically; byte-equality is intentionally not
|
|
11
|
+
* claimed — encoder-level variation is known-benign, see plan-2607-15 §2).
|
|
12
|
+
*
|
|
13
|
+
* `computeAccumulatorPeak` supports the chain-anchored variant: build the path
|
|
14
|
+
* at an externally attested tree size (e.g. Univocity `logState` on Base
|
|
15
|
+
* Sepolia) and compare the computed peak with the published accumulator.
|
|
16
|
+
*/
|
|
17
|
+
import { openMassifNodeStore, type MassifNodeStore } from "@forestrie/merklelog";
|
|
18
|
+
import { type CoseSign1 } from "./parse-receipt.js";
|
|
19
|
+
/**
|
|
20
|
+
* Re-exported for compatibility. The MMR proof math and massif node store now
|
|
21
|
+
* live in `@forestrie/merklelog` (plan-2607-15 §4, phase 2); receipt-verify
|
|
22
|
+
* keeps only the COSE-shaped layer below.
|
|
23
|
+
*/
|
|
24
|
+
export { openMassifNodeStore };
|
|
25
|
+
export type { MassifNodeStore };
|
|
26
|
+
export type ParsedCheckpoint = {
|
|
27
|
+
coseSign1: CoseSign1;
|
|
28
|
+
unprotected: Map<number, unknown>;
|
|
29
|
+
/** Pre-signed peak receipts (label -65931), or null when absent. */
|
|
30
|
+
peakReceipts: unknown[] | null;
|
|
31
|
+
/** Sealed tree size (tree-size-2 of the embedded consistency proof). */
|
|
32
|
+
mmrSize: bigint | null;
|
|
33
|
+
delegationCert: Uint8Array | null;
|
|
34
|
+
};
|
|
35
|
+
/** Parse a format-v3 checkpoint (`.sth`) COSE Sign1. */
|
|
36
|
+
export declare function parseCheckpoint(checkpointBytes: Uint8Array): ParsedCheckpoint;
|
|
37
|
+
export type BuildReceiptOfflineInput = {
|
|
38
|
+
massifBytes: Uint8Array;
|
|
39
|
+
checkpointBytes: Uint8Array;
|
|
40
|
+
mmrIndex: bigint;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Assemble a receipt for the entry at `mmrIndex` from a local massif blob and
|
|
44
|
+
* its checkpoint. Pure over bytes; no network, no signing key — the signature
|
|
45
|
+
* is the checkpoint's pre-signed peak receipt. Throws with a specific reason
|
|
46
|
+
* on any failure.
|
|
47
|
+
*/
|
|
48
|
+
export declare function buildReceiptOffline(input: BuildReceiptOfflineInput): Uint8Array;
|
|
49
|
+
export type ComputedAccumulatorPeak = {
|
|
50
|
+
/** Root computed from the massif's leaf value and rebuilt path. */
|
|
51
|
+
peak: Uint8Array;
|
|
52
|
+
/** Position of that peak in the accumulator, left to right. */
|
|
53
|
+
peakIndex: number;
|
|
54
|
+
proof: Uint8Array[];
|
|
55
|
+
leafValue: Uint8Array;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Chain-anchored variant: rebuild the inclusion path for `mmrIndex` at an
|
|
59
|
+
* externally attested tree size and compute the accumulator peak it commits
|
|
60
|
+
* to. Compare `peak` with `accumulator[peakIndex]` published by the Univocity
|
|
61
|
+
* contract (`logState`). Requires the local massif to hold nodes up to
|
|
62
|
+
* `mmrSize`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function computeAccumulatorPeak(opts: {
|
|
65
|
+
massifBytes: Uint8Array;
|
|
66
|
+
mmrIndex: bigint;
|
|
67
|
+
mmrSize: bigint;
|
|
68
|
+
}): Promise<ComputedAccumulatorPeak>;
|
|
69
|
+
//# sourceMappingURL=build-receipt-offline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-receipt-offline.d.ts","sourceRoot":"","sources":["../src/build-receipt-offline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAIL,mBAAmB,EAEnB,KAAK,eAAe,EAErB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAQ5B;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAC/B,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,oEAAoE;IACpE,YAAY,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,wEAAwE;IACxE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,UAAU,GAAG,IAAI,CAAC;CACnC,CAAC;AAEF,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,eAAe,EAAE,UAAU,GAAG,gBAAgB,CAqB7E;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,UAAU,CAAC;IACxB,eAAe,EAAE,UAAU,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,UAAU,CA2EZ;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,mEAAmE;IACnE,IAAI,EAAE,UAAU,CAAC;IACjB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,WAAW,EAAE,UAAU,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAwBnC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-created SCITT receipts from local artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors canopy-api resolve-receipt receipt assembly, but over local massif
|
|
5
|
+
* (`.log`) and checkpoint (`.sth`) bytes instead of R2 objects. The checkpoint
|
|
6
|
+
* signature covers only the accumulator, so the inclusion path is free to
|
|
7
|
+
* rebuild client-side: read massif nodes, build the leaf→peak path, attach it
|
|
8
|
+
* at header 396 to the checkpoint's pre-signed peak receipt. The result is
|
|
9
|
+
* verify-equivalent with an API-issued receipt (FOR-334 AC: both pass
|
|
10
|
+
* verifyGrantReceiptOffline identically; byte-equality is intentionally not
|
|
11
|
+
* claimed — encoder-level variation is known-benign, see plan-2607-15 §2).
|
|
12
|
+
*
|
|
13
|
+
* `computeAccumulatorPeak` supports the chain-anchored variant: build the path
|
|
14
|
+
* at an externally attested tree size (e.g. Univocity `logState` on Base
|
|
15
|
+
* Sepolia) and compare the computed peak with the published accumulator.
|
|
16
|
+
*/
|
|
17
|
+
import { decode as decodeCbor, encode as encodeCbor } from "cbor-x";
|
|
18
|
+
import { calculateRoot, inclusionProof, massifIndexFromMMRIndex, openMassifNodeStore, peakIndexForLeafProof, } from "@forestrie/merklelog";
|
|
19
|
+
import { requireCoseSign1, toHeaderMap, unwrapCoseSign1Tag, } from "./parse-receipt.js";
|
|
20
|
+
import { SubtleHasher } from "./subtle-hasher.js";
|
|
21
|
+
const VDS_COSE_RECEIPT_PROOFS_TAG = 396;
|
|
22
|
+
const SEAL_PEAK_RECEIPTS_LABEL = -65931;
|
|
23
|
+
const DELEGATION_CERT_LABEL = 1000;
|
|
24
|
+
const VDP_CONSISTENCY_PROOF_KEY = -2;
|
|
25
|
+
/**
|
|
26
|
+
* Re-exported for compatibility. The MMR proof math and massif node store now
|
|
27
|
+
* live in `@forestrie/merklelog` (plan-2607-15 §4, phase 2); receipt-verify
|
|
28
|
+
* keeps only the COSE-shaped layer below.
|
|
29
|
+
*/
|
|
30
|
+
export { openMassifNodeStore };
|
|
31
|
+
/** Parse a format-v3 checkpoint (`.sth`) COSE Sign1. */
|
|
32
|
+
export function parseCheckpoint(checkpointBytes) {
|
|
33
|
+
const decoded = decodeCbor(checkpointBytes);
|
|
34
|
+
const coseSign1 = requireCoseSign1(unwrapCoseSign1Tag(decoded));
|
|
35
|
+
const unprotected = toHeaderMap(coseSign1[1]);
|
|
36
|
+
const peakReceiptsRaw = unprotected.get(SEAL_PEAK_RECEIPTS_LABEL);
|
|
37
|
+
const peakReceipts = Array.isArray(peakReceiptsRaw) ? peakReceiptsRaw : null;
|
|
38
|
+
const delegationCertRaw = unprotected.get(DELEGATION_CERT_LABEL);
|
|
39
|
+
const delegationCert = delegationCertRaw instanceof Uint8Array && delegationCertRaw.length > 0
|
|
40
|
+
? delegationCertRaw
|
|
41
|
+
: null;
|
|
42
|
+
return {
|
|
43
|
+
coseSign1,
|
|
44
|
+
unprotected,
|
|
45
|
+
peakReceipts,
|
|
46
|
+
mmrSize: sealedSizeFromCheckpoint(unprotected),
|
|
47
|
+
delegationCert,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Assemble a receipt for the entry at `mmrIndex` from a local massif blob and
|
|
52
|
+
* its checkpoint. Pure over bytes; no network, no signing key — the signature
|
|
53
|
+
* is the checkpoint's pre-signed peak receipt. Throws with a specific reason
|
|
54
|
+
* on any failure.
|
|
55
|
+
*/
|
|
56
|
+
export function buildReceiptOffline(input) {
|
|
57
|
+
const { mmrIndex } = input;
|
|
58
|
+
if (mmrIndex < 0n) {
|
|
59
|
+
throw new Error("mmrIndex must be non-negative");
|
|
60
|
+
}
|
|
61
|
+
const checkpoint = parseCheckpoint(input.checkpointBytes);
|
|
62
|
+
if (!checkpoint.peakReceipts) {
|
|
63
|
+
throw new Error("checkpoint carries no pre-signed peak receipts (label -65931)");
|
|
64
|
+
}
|
|
65
|
+
if (checkpoint.mmrSize === null || checkpoint.mmrSize <= 0n) {
|
|
66
|
+
throw new Error("checkpoint carries no consistency proof (cannot determine sealed size)");
|
|
67
|
+
}
|
|
68
|
+
const mmrSize = checkpoint.mmrSize;
|
|
69
|
+
const mmrLastIndex = mmrSize - 1n;
|
|
70
|
+
if (mmrIndex > mmrLastIndex) {
|
|
71
|
+
throw new Error(`checkpoint does not cover entry: mmrIndex ${mmrIndex.toString(10)} ` +
|
|
72
|
+
`>= sealed size ${mmrSize.toString(10)}`);
|
|
73
|
+
}
|
|
74
|
+
const store = openMassifNodeStore(input.massifBytes);
|
|
75
|
+
const expectedMassifIndex = massifIndexFromMMRIndex(store.massifHeight, mmrIndex);
|
|
76
|
+
if (expectedMassifIndex !== store.massifIndex) {
|
|
77
|
+
throw new Error(`entry at mmrIndex ${mmrIndex.toString(10)} lives in massif ` +
|
|
78
|
+
`${expectedMassifIndex.toString(10)}, but this blob is massif ` +
|
|
79
|
+
`${store.massifIndex.toString(10)}`);
|
|
80
|
+
}
|
|
81
|
+
const proof = inclusionProof(store.get, mmrLastIndex, mmrIndex);
|
|
82
|
+
const peakIdx = peakIndexForLeafProof(mmrSize, proof.length);
|
|
83
|
+
const receiptBytes = checkpoint.peakReceipts[peakIdx];
|
|
84
|
+
if (!(receiptBytes instanceof Uint8Array)) {
|
|
85
|
+
throw new Error(`checkpoint peak receipt slot ${peakIdx} is missing or not bstr`);
|
|
86
|
+
}
|
|
87
|
+
const receiptSign1 = requireCoseSign1(unwrapCoseSign1Tag(decodeCbor(receiptBytes)));
|
|
88
|
+
const receiptUnprotected = toHeaderMap(receiptSign1[1]);
|
|
89
|
+
if (checkpoint.delegationCert) {
|
|
90
|
+
receiptUnprotected.set(DELEGATION_CERT_LABEL, checkpoint.delegationCert);
|
|
91
|
+
}
|
|
92
|
+
const inclusionProofEntry = new Map([
|
|
93
|
+
[1, mmrIndex],
|
|
94
|
+
[2, proof],
|
|
95
|
+
]);
|
|
96
|
+
const verifiableProofs = new Map([
|
|
97
|
+
[-1, [inclusionProofEntry]],
|
|
98
|
+
]);
|
|
99
|
+
receiptUnprotected.set(VDS_COSE_RECEIPT_PROOFS_TAG, verifiableProofs);
|
|
100
|
+
// Peak receipts are signed with detached payload; emit nil so verify uses
|
|
101
|
+
// the peak derived from the inclusion proof.
|
|
102
|
+
const assembled = [
|
|
103
|
+
receiptSign1[0],
|
|
104
|
+
receiptUnprotected,
|
|
105
|
+
null,
|
|
106
|
+
receiptSign1[3],
|
|
107
|
+
];
|
|
108
|
+
return cborBytes(assembled);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Chain-anchored variant: rebuild the inclusion path for `mmrIndex` at an
|
|
112
|
+
* externally attested tree size and compute the accumulator peak it commits
|
|
113
|
+
* to. Compare `peak` with `accumulator[peakIndex]` published by the Univocity
|
|
114
|
+
* contract (`logState`). Requires the local massif to hold nodes up to
|
|
115
|
+
* `mmrSize`.
|
|
116
|
+
*/
|
|
117
|
+
export async function computeAccumulatorPeak(opts) {
|
|
118
|
+
const { mmrIndex, mmrSize } = opts;
|
|
119
|
+
if (mmrSize <= 0n || mmrIndex >= mmrSize) {
|
|
120
|
+
throw new Error(`mmrIndex ${mmrIndex.toString(10)} is not covered by tree size ` +
|
|
121
|
+
`${mmrSize.toString(10)} (entry not anchored yet?)`);
|
|
122
|
+
}
|
|
123
|
+
const store = openMassifNodeStore(opts.massifBytes);
|
|
124
|
+
const leafValue = store.get(mmrIndex);
|
|
125
|
+
const path = inclusionProof(store.get, mmrSize - 1n, mmrIndex);
|
|
126
|
+
const proof = { path, mmrIndex };
|
|
127
|
+
const peak = await calculateRoot(new SubtleHasher(), leafValue, proof, mmrIndex);
|
|
128
|
+
return {
|
|
129
|
+
peak,
|
|
130
|
+
peakIndex: peakIndexForLeafProof(mmrSize, path.length),
|
|
131
|
+
proof: path,
|
|
132
|
+
leafValue,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// --- helpers ---
|
|
136
|
+
function cborBytes(value) {
|
|
137
|
+
const encoded = encodeCbor(value);
|
|
138
|
+
return encoded instanceof Uint8Array
|
|
139
|
+
? encoded
|
|
140
|
+
: new Uint8Array(encoded);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Sealed mmr size from a format-v3 checkpoint: tree-size-2 of the consistency
|
|
144
|
+
* proof (`bstr .cbor [tree-size-1, tree-size-2, paths, right-peaks]`) under
|
|
145
|
+
* the verifiable-proofs unprotected header (label 396, key -2).
|
|
146
|
+
*/
|
|
147
|
+
function sealedSizeFromCheckpoint(unprotected) {
|
|
148
|
+
const vdpRaw = unprotected.get(VDS_COSE_RECEIPT_PROOFS_TAG);
|
|
149
|
+
if (vdpRaw === undefined || vdpRaw === null)
|
|
150
|
+
return null;
|
|
151
|
+
const vdp = toHeaderMap(vdpRaw);
|
|
152
|
+
const proofBstr = vdp.get(VDP_CONSISTENCY_PROOF_KEY);
|
|
153
|
+
if (!(proofBstr instanceof Uint8Array))
|
|
154
|
+
return null;
|
|
155
|
+
const proof = decodeCbor(proofBstr);
|
|
156
|
+
if (!Array.isArray(proof) || proof.length < 2)
|
|
157
|
+
return null;
|
|
158
|
+
const treeSize2 = proof[1];
|
|
159
|
+
if (typeof treeSize2 === "bigint")
|
|
160
|
+
return treeSize2;
|
|
161
|
+
if (typeof treeSize2 === "number" && Number.isSafeInteger(treeSize2)) {
|
|
162
|
+
return BigInt(treeSize2);
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ export type { ReceiptVerifyResult, ReceiptVerifyStage, } from "./receipt-verify-
|
|
|
3
3
|
export type { Grant } from "@forestrie/encoding";
|
|
4
4
|
export type { VerifyGrantReceiptOfflineInput } from "./verify-grant-receipt-offline.js";
|
|
5
5
|
export { parseReceipt } from "./parse-receipt.js";
|
|
6
|
+
export type { BuildReceiptOfflineInput, ComputedAccumulatorPeak, MassifNodeStore, ParsedCheckpoint, } from "./build-receipt-offline.js";
|
|
7
|
+
export { buildReceiptOffline, computeAccumulatorPeak, openMassifNodeStore, parseCheckpoint, } from "./build-receipt-offline.js";
|
|
8
|
+
/**
|
|
9
|
+
* MMR proof math now lives in @forestrie/merklelog (plan-2607-15 §4 hoist);
|
|
10
|
+
* re-exported here to preserve the receipt-verify public surface.
|
|
11
|
+
*/
|
|
12
|
+
export { peakMMRIndexes } from "@forestrie/merklelog";
|
|
6
13
|
export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
|
|
7
14
|
export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
8
15
|
export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,oFAAoF;AACpF,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE3E,0EAA0E;AAC1E,OAAO,EACL,cAAc,EACd,iDAAiD,GAClD,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC7E,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,oFAAoF;AACpF,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,eAAe,EACf,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,GAChB,MAAM,4BAA4B,CAAC;AACpC;;;GAGG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE3E,0EAA0E;AAC1E,OAAO,EACL,cAAc,EACd,iDAAiD,GAClD,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC7E,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export { parseReceipt } from "./parse-receipt.js";
|
|
2
|
+
export { buildReceiptOffline, computeAccumulatorPeak, openMassifNodeStore, parseCheckpoint, } from "./build-receipt-offline.js";
|
|
3
|
+
/**
|
|
4
|
+
* MMR proof math now lives in @forestrie/merklelog (plan-2607-15 §4 hoist);
|
|
5
|
+
* re-exported here to preserve the receipt-verify public surface.
|
|
6
|
+
*/
|
|
7
|
+
export { peakMMRIndexes } from "@forestrie/merklelog";
|
|
2
8
|
export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
|
|
3
9
|
export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
4
10
|
export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
|
package/dist/parse-receipt.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { Proof } from "@forestrie/merklelog";
|
|
2
|
-
type CoseSign1 = [
|
|
2
|
+
export type CoseSign1 = [
|
|
3
3
|
protectedHeader: Uint8Array,
|
|
4
4
|
unprotectedHeader: Map<number, unknown> | Record<string, unknown>,
|
|
5
5
|
payload: Uint8Array | null,
|
|
6
6
|
signature: Uint8Array
|
|
7
7
|
];
|
|
8
|
+
export declare function unwrapCoseSign1Tag(value: unknown): unknown;
|
|
9
|
+
export declare function requireCoseSign1(value: unknown): CoseSign1;
|
|
10
|
+
export declare function toHeaderMap(value: Map<number, unknown> | Record<string, unknown>): Map<number, unknown>;
|
|
8
11
|
export declare function parseReceipt(receiptBytes: Uint8Array): {
|
|
9
12
|
explicitPeak: Uint8Array | null;
|
|
10
13
|
proof: Proof;
|
|
11
14
|
receiptCbor: Uint8Array;
|
|
12
15
|
coseSign1: CoseSign1;
|
|
13
16
|
};
|
|
14
|
-
export {};
|
|
15
17
|
//# sourceMappingURL=parse-receipt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-receipt.d.ts","sourceRoot":"","sources":["../src/parse-receipt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAIlD,
|
|
1
|
+
{"version":3,"file":"parse-receipt.d.ts","sourceRoot":"","sources":["../src/parse-receipt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAIlD,MAAM,MAAM,SAAS,GAAG;IACtB,eAAe,EAAE,UAAU;IAC3B,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,OAAO,EAAE,UAAU,GAAG,IAAI;IAC1B,SAAS,EAAE,UAAU;CACtB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW1D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAiB1D;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACpD,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAUtB;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,UAAU,GAAG;IACtD,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;CACtB,CAwDA"}
|
package/dist/parse-receipt.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decode as decodeCbor } from "cbor-x";
|
|
2
2
|
const VDS_COSE_RECEIPT_PROOFS_TAG = 396;
|
|
3
|
-
function unwrapCoseSign1Tag(value) {
|
|
3
|
+
export function unwrapCoseSign1Tag(value) {
|
|
4
4
|
if (value && typeof value === "object" && !(value instanceof Map)) {
|
|
5
5
|
const tagged = value;
|
|
6
6
|
if (Object.prototype.hasOwnProperty.call(tagged, "value") &&
|
|
@@ -10,7 +10,7 @@ function unwrapCoseSign1Tag(value) {
|
|
|
10
10
|
}
|
|
11
11
|
return value;
|
|
12
12
|
}
|
|
13
|
-
function requireCoseSign1(value) {
|
|
13
|
+
export function requireCoseSign1(value) {
|
|
14
14
|
if (!Array.isArray(value) || value.length !== 4) {
|
|
15
15
|
throw new Error("Receipt is not a COSE Sign1 array");
|
|
16
16
|
}
|
|
@@ -28,7 +28,7 @@ function requireCoseSign1(value) {
|
|
|
28
28
|
sig,
|
|
29
29
|
];
|
|
30
30
|
}
|
|
31
|
-
function toHeaderMap(value) {
|
|
31
|
+
export function toHeaderMap(value) {
|
|
32
32
|
if (value instanceof Map)
|
|
33
33
|
return value;
|
|
34
34
|
const out = new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forestrie/receipt-verify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Offline SCITT grant receipt verification (ADR-0045)",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@noble/hashes": "^1.7.1",
|
|
31
31
|
"cbor-x": "^1.5.9",
|
|
32
|
-
"@forestrie/
|
|
33
|
-
"@forestrie/
|
|
32
|
+
"@forestrie/merklelog": "0.1.0",
|
|
33
|
+
"@forestrie/encoding": "0.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"esbuild": "^0.24.0",
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-created SCITT receipts from local artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors canopy-api resolve-receipt receipt assembly, but over local massif
|
|
5
|
+
* (`.log`) and checkpoint (`.sth`) bytes instead of R2 objects. The checkpoint
|
|
6
|
+
* signature covers only the accumulator, so the inclusion path is free to
|
|
7
|
+
* rebuild client-side: read massif nodes, build the leaf→peak path, attach it
|
|
8
|
+
* at header 396 to the checkpoint's pre-signed peak receipt. The result is
|
|
9
|
+
* verify-equivalent with an API-issued receipt (FOR-334 AC: both pass
|
|
10
|
+
* verifyGrantReceiptOffline identically; byte-equality is intentionally not
|
|
11
|
+
* claimed — encoder-level variation is known-benign, see plan-2607-15 §2).
|
|
12
|
+
*
|
|
13
|
+
* `computeAccumulatorPeak` supports the chain-anchored variant: build the path
|
|
14
|
+
* at an externally attested tree size (e.g. Univocity `logState` on Base
|
|
15
|
+
* Sepolia) and compare the computed peak with the published accumulator.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { decode as decodeCbor, encode as encodeCbor } from "cbor-x";
|
|
19
|
+
import {
|
|
20
|
+
calculateRoot,
|
|
21
|
+
inclusionProof,
|
|
22
|
+
massifIndexFromMMRIndex,
|
|
23
|
+
openMassifNodeStore,
|
|
24
|
+
peakIndexForLeafProof,
|
|
25
|
+
type MassifNodeStore,
|
|
26
|
+
type Proof,
|
|
27
|
+
} from "@forestrie/merklelog";
|
|
28
|
+
import {
|
|
29
|
+
requireCoseSign1,
|
|
30
|
+
toHeaderMap,
|
|
31
|
+
unwrapCoseSign1Tag,
|
|
32
|
+
type CoseSign1,
|
|
33
|
+
} from "./parse-receipt.js";
|
|
34
|
+
import { SubtleHasher } from "./subtle-hasher.js";
|
|
35
|
+
|
|
36
|
+
const VDS_COSE_RECEIPT_PROOFS_TAG = 396;
|
|
37
|
+
const SEAL_PEAK_RECEIPTS_LABEL = -65931;
|
|
38
|
+
const DELEGATION_CERT_LABEL = 1000;
|
|
39
|
+
const VDP_CONSISTENCY_PROOF_KEY = -2;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Re-exported for compatibility. The MMR proof math and massif node store now
|
|
43
|
+
* live in `@forestrie/merklelog` (plan-2607-15 §4, phase 2); receipt-verify
|
|
44
|
+
* keeps only the COSE-shaped layer below.
|
|
45
|
+
*/
|
|
46
|
+
export { openMassifNodeStore };
|
|
47
|
+
export type { MassifNodeStore };
|
|
48
|
+
|
|
49
|
+
export type ParsedCheckpoint = {
|
|
50
|
+
coseSign1: CoseSign1;
|
|
51
|
+
unprotected: Map<number, unknown>;
|
|
52
|
+
/** Pre-signed peak receipts (label -65931), or null when absent. */
|
|
53
|
+
peakReceipts: unknown[] | null;
|
|
54
|
+
/** Sealed tree size (tree-size-2 of the embedded consistency proof). */
|
|
55
|
+
mmrSize: bigint | null;
|
|
56
|
+
delegationCert: Uint8Array | null;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Parse a format-v3 checkpoint (`.sth`) COSE Sign1. */
|
|
60
|
+
export function parseCheckpoint(checkpointBytes: Uint8Array): ParsedCheckpoint {
|
|
61
|
+
const decoded = decodeCbor(checkpointBytes) as unknown;
|
|
62
|
+
const coseSign1 = requireCoseSign1(unwrapCoseSign1Tag(decoded));
|
|
63
|
+
const unprotected = toHeaderMap(coseSign1[1]);
|
|
64
|
+
|
|
65
|
+
const peakReceiptsRaw = unprotected.get(SEAL_PEAK_RECEIPTS_LABEL);
|
|
66
|
+
const peakReceipts = Array.isArray(peakReceiptsRaw) ? peakReceiptsRaw : null;
|
|
67
|
+
|
|
68
|
+
const delegationCertRaw = unprotected.get(DELEGATION_CERT_LABEL);
|
|
69
|
+
const delegationCert =
|
|
70
|
+
delegationCertRaw instanceof Uint8Array && delegationCertRaw.length > 0
|
|
71
|
+
? delegationCertRaw
|
|
72
|
+
: null;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
coseSign1,
|
|
76
|
+
unprotected,
|
|
77
|
+
peakReceipts,
|
|
78
|
+
mmrSize: sealedSizeFromCheckpoint(unprotected),
|
|
79
|
+
delegationCert,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type BuildReceiptOfflineInput = {
|
|
84
|
+
massifBytes: Uint8Array;
|
|
85
|
+
checkpointBytes: Uint8Array;
|
|
86
|
+
mmrIndex: bigint;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Assemble a receipt for the entry at `mmrIndex` from a local massif blob and
|
|
91
|
+
* its checkpoint. Pure over bytes; no network, no signing key — the signature
|
|
92
|
+
* is the checkpoint's pre-signed peak receipt. Throws with a specific reason
|
|
93
|
+
* on any failure.
|
|
94
|
+
*/
|
|
95
|
+
export function buildReceiptOffline(
|
|
96
|
+
input: BuildReceiptOfflineInput,
|
|
97
|
+
): Uint8Array {
|
|
98
|
+
const { mmrIndex } = input;
|
|
99
|
+
if (mmrIndex < 0n) {
|
|
100
|
+
throw new Error("mmrIndex must be non-negative");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const checkpoint = parseCheckpoint(input.checkpointBytes);
|
|
104
|
+
if (!checkpoint.peakReceipts) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
"checkpoint carries no pre-signed peak receipts (label -65931)",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (checkpoint.mmrSize === null || checkpoint.mmrSize <= 0n) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
"checkpoint carries no consistency proof (cannot determine sealed size)",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const mmrSize = checkpoint.mmrSize;
|
|
115
|
+
const mmrLastIndex = mmrSize - 1n;
|
|
116
|
+
if (mmrIndex > mmrLastIndex) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`checkpoint does not cover entry: mmrIndex ${mmrIndex.toString(10)} ` +
|
|
119
|
+
`>= sealed size ${mmrSize.toString(10)}`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const store = openMassifNodeStore(input.massifBytes);
|
|
124
|
+
const expectedMassifIndex = massifIndexFromMMRIndex(
|
|
125
|
+
store.massifHeight,
|
|
126
|
+
mmrIndex,
|
|
127
|
+
);
|
|
128
|
+
if (expectedMassifIndex !== store.massifIndex) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`entry at mmrIndex ${mmrIndex.toString(10)} lives in massif ` +
|
|
131
|
+
`${expectedMassifIndex.toString(10)}, but this blob is massif ` +
|
|
132
|
+
`${store.massifIndex.toString(10)}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const proof = inclusionProof(store.get, mmrLastIndex, mmrIndex);
|
|
137
|
+
const peakIdx = peakIndexForLeafProof(mmrSize, proof.length);
|
|
138
|
+
const receiptBytes = checkpoint.peakReceipts[peakIdx];
|
|
139
|
+
if (!(receiptBytes instanceof Uint8Array)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`checkpoint peak receipt slot ${peakIdx} is missing or not bstr`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const receiptSign1 = requireCoseSign1(
|
|
146
|
+
unwrapCoseSign1Tag(decodeCbor(receiptBytes) as unknown),
|
|
147
|
+
);
|
|
148
|
+
const receiptUnprotected = toHeaderMap(receiptSign1[1]);
|
|
149
|
+
|
|
150
|
+
if (checkpoint.delegationCert) {
|
|
151
|
+
receiptUnprotected.set(DELEGATION_CERT_LABEL, checkpoint.delegationCert);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const inclusionProofEntry = new Map<number, unknown>([
|
|
155
|
+
[1, mmrIndex],
|
|
156
|
+
[2, proof],
|
|
157
|
+
]);
|
|
158
|
+
const verifiableProofs = new Map<number, unknown>([
|
|
159
|
+
[-1, [inclusionProofEntry]],
|
|
160
|
+
]);
|
|
161
|
+
receiptUnprotected.set(VDS_COSE_RECEIPT_PROOFS_TAG, verifiableProofs);
|
|
162
|
+
|
|
163
|
+
// Peak receipts are signed with detached payload; emit nil so verify uses
|
|
164
|
+
// the peak derived from the inclusion proof.
|
|
165
|
+
const assembled: CoseSign1 = [
|
|
166
|
+
receiptSign1[0],
|
|
167
|
+
receiptUnprotected,
|
|
168
|
+
null,
|
|
169
|
+
receiptSign1[3],
|
|
170
|
+
];
|
|
171
|
+
return cborBytes(assembled);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type ComputedAccumulatorPeak = {
|
|
175
|
+
/** Root computed from the massif's leaf value and rebuilt path. */
|
|
176
|
+
peak: Uint8Array;
|
|
177
|
+
/** Position of that peak in the accumulator, left to right. */
|
|
178
|
+
peakIndex: number;
|
|
179
|
+
proof: Uint8Array[];
|
|
180
|
+
leafValue: Uint8Array;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Chain-anchored variant: rebuild the inclusion path for `mmrIndex` at an
|
|
185
|
+
* externally attested tree size and compute the accumulator peak it commits
|
|
186
|
+
* to. Compare `peak` with `accumulator[peakIndex]` published by the Univocity
|
|
187
|
+
* contract (`logState`). Requires the local massif to hold nodes up to
|
|
188
|
+
* `mmrSize`.
|
|
189
|
+
*/
|
|
190
|
+
export async function computeAccumulatorPeak(opts: {
|
|
191
|
+
massifBytes: Uint8Array;
|
|
192
|
+
mmrIndex: bigint;
|
|
193
|
+
mmrSize: bigint;
|
|
194
|
+
}): Promise<ComputedAccumulatorPeak> {
|
|
195
|
+
const { mmrIndex, mmrSize } = opts;
|
|
196
|
+
if (mmrSize <= 0n || mmrIndex >= mmrSize) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`mmrIndex ${mmrIndex.toString(10)} is not covered by tree size ` +
|
|
199
|
+
`${mmrSize.toString(10)} (entry not anchored yet?)`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const store = openMassifNodeStore(opts.massifBytes);
|
|
203
|
+
const leafValue = store.get(mmrIndex);
|
|
204
|
+
const path = inclusionProof(store.get, mmrSize - 1n, mmrIndex);
|
|
205
|
+
const proof: Proof = { path, mmrIndex };
|
|
206
|
+
const peak = await calculateRoot(
|
|
207
|
+
new SubtleHasher(),
|
|
208
|
+
leafValue,
|
|
209
|
+
proof,
|
|
210
|
+
mmrIndex,
|
|
211
|
+
);
|
|
212
|
+
return {
|
|
213
|
+
peak,
|
|
214
|
+
peakIndex: peakIndexForLeafProof(mmrSize, path.length),
|
|
215
|
+
proof: path,
|
|
216
|
+
leafValue,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --- helpers ---
|
|
221
|
+
|
|
222
|
+
function cborBytes(value: unknown): Uint8Array {
|
|
223
|
+
const encoded = encodeCbor(value);
|
|
224
|
+
return encoded instanceof Uint8Array
|
|
225
|
+
? encoded
|
|
226
|
+
: new Uint8Array(encoded as ArrayLike<number>);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Sealed mmr size from a format-v3 checkpoint: tree-size-2 of the consistency
|
|
231
|
+
* proof (`bstr .cbor [tree-size-1, tree-size-2, paths, right-peaks]`) under
|
|
232
|
+
* the verifiable-proofs unprotected header (label 396, key -2).
|
|
233
|
+
*/
|
|
234
|
+
function sealedSizeFromCheckpoint(
|
|
235
|
+
unprotected: Map<number, unknown>,
|
|
236
|
+
): bigint | null {
|
|
237
|
+
const vdpRaw = unprotected.get(VDS_COSE_RECEIPT_PROOFS_TAG);
|
|
238
|
+
if (vdpRaw === undefined || vdpRaw === null) return null;
|
|
239
|
+
const vdp = toHeaderMap(vdpRaw as Map<number, unknown>);
|
|
240
|
+
const proofBstr = vdp.get(VDP_CONSISTENCY_PROOF_KEY);
|
|
241
|
+
if (!(proofBstr instanceof Uint8Array)) return null;
|
|
242
|
+
const proof = decodeCbor(proofBstr) as unknown;
|
|
243
|
+
if (!Array.isArray(proof) || proof.length < 2) return null;
|
|
244
|
+
const treeSize2 = proof[1];
|
|
245
|
+
if (typeof treeSize2 === "bigint") return treeSize2;
|
|
246
|
+
if (typeof treeSize2 === "number" && Number.isSafeInteger(treeSize2)) {
|
|
247
|
+
return BigInt(treeSize2);
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,23 @@ export type {
|
|
|
6
6
|
export type { Grant } from "@forestrie/encoding";
|
|
7
7
|
export type { VerifyGrantReceiptOfflineInput } from "./verify-grant-receipt-offline.js";
|
|
8
8
|
export { parseReceipt } from "./parse-receipt.js";
|
|
9
|
+
export type {
|
|
10
|
+
BuildReceiptOfflineInput,
|
|
11
|
+
ComputedAccumulatorPeak,
|
|
12
|
+
MassifNodeStore,
|
|
13
|
+
ParsedCheckpoint,
|
|
14
|
+
} from "./build-receipt-offline.js";
|
|
15
|
+
export {
|
|
16
|
+
buildReceiptOffline,
|
|
17
|
+
computeAccumulatorPeak,
|
|
18
|
+
openMassifNodeStore,
|
|
19
|
+
parseCheckpoint,
|
|
20
|
+
} from "./build-receipt-offline.js";
|
|
21
|
+
/**
|
|
22
|
+
* MMR proof math now lives in @forestrie/merklelog (plan-2607-15 §4 hoist);
|
|
23
|
+
* re-exported here to preserve the receipt-verify public surface.
|
|
24
|
+
*/
|
|
25
|
+
export { peakMMRIndexes } from "@forestrie/merklelog";
|
|
9
26
|
export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
|
|
10
27
|
export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
11
28
|
export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
|
package/src/parse-receipt.ts
CHANGED
|
@@ -3,14 +3,14 @@ import type { Proof } from "@forestrie/merklelog";
|
|
|
3
3
|
|
|
4
4
|
const VDS_COSE_RECEIPT_PROOFS_TAG = 396;
|
|
5
5
|
|
|
6
|
-
type CoseSign1 = [
|
|
6
|
+
export type CoseSign1 = [
|
|
7
7
|
protectedHeader: Uint8Array,
|
|
8
8
|
unprotectedHeader: Map<number, unknown> | Record<string, unknown>,
|
|
9
9
|
payload: Uint8Array | null,
|
|
10
10
|
signature: Uint8Array,
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
-
function unwrapCoseSign1Tag(value: unknown): unknown {
|
|
13
|
+
export function unwrapCoseSign1Tag(value: unknown): unknown {
|
|
14
14
|
if (value && typeof value === "object" && !(value instanceof Map)) {
|
|
15
15
|
const tagged = value as { tag?: number; value?: unknown };
|
|
16
16
|
if (
|
|
@@ -23,7 +23,7 @@ function unwrapCoseSign1Tag(value: unknown): unknown {
|
|
|
23
23
|
return value;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function requireCoseSign1(value: unknown): CoseSign1 {
|
|
26
|
+
export function requireCoseSign1(value: unknown): CoseSign1 {
|
|
27
27
|
if (!Array.isArray(value) || value.length !== 4) {
|
|
28
28
|
throw new Error("Receipt is not a COSE Sign1 array");
|
|
29
29
|
}
|
|
@@ -42,7 +42,7 @@ function requireCoseSign1(value: unknown): CoseSign1 {
|
|
|
42
42
|
] as CoseSign1;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function toHeaderMap(
|
|
45
|
+
export function toHeaderMap(
|
|
46
46
|
value: Map<number, unknown> | Record<string, unknown>,
|
|
47
47
|
): Map<number, unknown> {
|
|
48
48
|
if (value instanceof Map) return value as Map<number, unknown>;
|