@forestrie/receipt-verify 0.6.1 → 0.6.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.
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/resolve-delegated-verify-key.d.ts +10 -0
- package/dist/resolve-delegated-verify-key.d.ts.map +1 -1
- package/dist/resolve-delegated-verify-key.js +10 -0
- package/dist/verify-grant-receipt-offline.d.ts +37 -0
- package/dist/verify-grant-receipt-offline.d.ts.map +1 -1
- package/dist/verify-grant-receipt-offline.js +76 -14
- package/package.json +1 -1
- package/src/index.ts +17 -0
- package/src/resolve-delegated-verify-key.ts +10 -0
- package/src/verify-grant-receipt-offline.ts +114 -14
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,17 @@ export { peakMMRIndexes } from "@forestrie/merklelog";
|
|
|
14
14
|
export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
|
|
15
15
|
export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
16
16
|
export { verifyReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
17
|
+
/**
|
|
18
|
+
* Caller-supplied trust anchors (FOR-297 "known log key"): verify offline
|
|
19
|
+
* under keys the caller trusts out of band instead of the genesis trust root.
|
|
20
|
+
* See the trust-ladder notes on verifyReceiptOfflineWithKeys.
|
|
21
|
+
*/
|
|
22
|
+
export { verifyGrantReceiptOfflineWithKeys, verifyReceiptOfflineWithKeys, } from "./verify-grant-receipt-offline.js";
|
|
23
|
+
export type { VerifyGrantReceiptOfflineWithKeysInput, VerifyReceiptOfflineWithKeysInput, } from "./verify-grant-receipt-offline.js";
|
|
24
|
+
export { resolveDelegatedVerifyKey } from "./resolve-delegated-verify-key.js";
|
|
25
|
+
export type { DelegatedResolution } from "./resolve-delegated-verify-key.js";
|
|
26
|
+
/** Import a raw 64-byte x||y P-256 public key as an ES256 verify key. */
|
|
27
|
+
export { importEs256PublicKeyFromGrantDataXy64 } from "./decode-trust-root-cbor.js";
|
|
17
28
|
export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
|
|
18
29
|
export { decodeGrantPayload, decodeGrantResponse } from "./grant-codec.js";
|
|
19
30
|
/** Deterministic receipt construction (plan-2607-12 Phase 2, FOR-353). */
|
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,YAAY,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACnF,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,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,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;AAEpD,6EAA6E;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACrE,oEAAoE;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,8EAA8E;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,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,YAAY,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACnF,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,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE;;;;GAIG;AACH,OAAO,EACL,iCAAiC,EACjC,4BAA4B,GAC7B,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACV,sCAAsC,EACtC,iCAAiC,GAClC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,YAAY,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,yEAAyE;AACzE,OAAO,EAAE,qCAAqC,EAAE,MAAM,6BAA6B,CAAC;AACpF,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;AAEpD,6EAA6E;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACrE,oEAAoE;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,8EAA8E;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,15 @@ export { peakMMRIndexes } from "@forestrie/merklelog";
|
|
|
8
8
|
export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
|
|
9
9
|
export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
10
10
|
export { verifyReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
11
|
+
/**
|
|
12
|
+
* Caller-supplied trust anchors (FOR-297 "known log key"): verify offline
|
|
13
|
+
* under keys the caller trusts out of band instead of the genesis trust root.
|
|
14
|
+
* See the trust-ladder notes on verifyReceiptOfflineWithKeys.
|
|
15
|
+
*/
|
|
16
|
+
export { verifyGrantReceiptOfflineWithKeys, verifyReceiptOfflineWithKeys, } from "./verify-grant-receipt-offline.js";
|
|
17
|
+
export { resolveDelegatedVerifyKey } from "./resolve-delegated-verify-key.js";
|
|
18
|
+
/** Import a raw 64-byte x||y P-256 public key as an ES256 verify key. */
|
|
19
|
+
export { importEs256PublicKeyFromGrantDataXy64 } from "./decode-trust-root-cbor.js";
|
|
11
20
|
export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
|
|
12
21
|
export { decodeGrantPayload, decodeGrantResponse } from "./grant-codec.js";
|
|
13
22
|
/** Deterministic receipt construction (plan-2607-12 Phase 2, FOR-353). */
|
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
* sealer delegate keys are always secp256r1. KS256-rooted delegation is a
|
|
11
11
|
* server-only concern.
|
|
12
12
|
*
|
|
13
|
+
* Trust anchors (FOR-297 trust ladder): `rootKeys` need not be genesis-derived
|
|
14
|
+
* — a caller-known log OWNER key works identically (see
|
|
15
|
+
* `verifyReceiptOfflineWithKeys`), because the certificate issuer for a child
|
|
16
|
+
* log IS its owner. What differs is why you trust the keys: genesis-derived
|
|
17
|
+
* keys prove the key↔log binding from the bootstrap; a caller-known key merely
|
|
18
|
+
* asserts it (provenance of the key is the only defence, and there is no grant
|
|
19
|
+
* lifecycle visibility or split-view protection). The grant-chain walk
|
|
20
|
+
* (approach A, open) will derive per-log owner keys from genesis + public
|
|
21
|
+
* tiles, closing that gap without key distribution.
|
|
22
|
+
*
|
|
13
23
|
* Note (parity with the server port source): this establishes that the root
|
|
14
24
|
* authorized the delegated key, but does NOT yet enforce the certificate's MMR
|
|
15
25
|
* window or expiry-at-issuance against the leaf — that hardening is shared with
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-delegated-verify-key.d.ts","sourceRoot":"","sources":["../src/resolve-delegated-verify-key.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"resolve-delegated-verify-key.d.ts","sourceRoot":"","sources":["../src/resolve-delegated-verify-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAuBH;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,SAAS,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAqEvB;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,UAAU,EACvB,QAAQ,EAAE,SAAS,EAAE,GACpB,OAAO,CAAC,mBAAmB,CAAC,CA4B9B"}
|
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
* sealer delegate keys are always secp256r1. KS256-rooted delegation is a
|
|
11
11
|
* server-only concern.
|
|
12
12
|
*
|
|
13
|
+
* Trust anchors (FOR-297 trust ladder): `rootKeys` need not be genesis-derived
|
|
14
|
+
* — a caller-known log OWNER key works identically (see
|
|
15
|
+
* `verifyReceiptOfflineWithKeys`), because the certificate issuer for a child
|
|
16
|
+
* log IS its owner. What differs is why you trust the keys: genesis-derived
|
|
17
|
+
* keys prove the key↔log binding from the bootstrap; a caller-known key merely
|
|
18
|
+
* asserts it (provenance of the key is the only defence, and there is no grant
|
|
19
|
+
* lifecycle visibility or split-view protection). The grant-chain walk
|
|
20
|
+
* (approach A, open) will derive per-log owner keys from genesis + public
|
|
21
|
+
* tiles, closing that gap without key distribution.
|
|
22
|
+
*
|
|
13
23
|
* Note (parity with the server port source): this establishes that the root
|
|
14
24
|
* authorized the delegated key, but does NOT yet enforce the certificate's MMR
|
|
15
25
|
* window or expiry-at-issuance against the leaf — that hardening is shared with
|
|
@@ -10,6 +10,16 @@ export type VerifyGrantReceiptOfflineInput = {
|
|
|
10
10
|
* Offline grant receipt verify (layers A–C, ADR-0045). Pure over bytes; no network.
|
|
11
11
|
*/
|
|
12
12
|
export declare function verifyGrantReceiptOffline(input: VerifyGrantReceiptOfflineInput): Promise<ReceiptVerifyResult>;
|
|
13
|
+
export type VerifyGrantReceiptOfflineWithKeysInput = Omit<VerifyGrantReceiptOfflineInput, "genesisCbor"> & {
|
|
14
|
+
/** Caller-known ES256 trust keys — see {@link VerifyReceiptOfflineWithKeysInput}. */
|
|
15
|
+
trustKeys: CryptoKey[];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* {@link verifyGrantReceiptOffline} under caller-supplied trust keys instead of
|
|
19
|
+
* the genesis trust root — see {@link verifyReceiptOfflineWithKeys} for the
|
|
20
|
+
* trust model this buys (and what it does not).
|
|
21
|
+
*/
|
|
22
|
+
export declare function verifyGrantReceiptOfflineWithKeys(input: VerifyGrantReceiptOfflineWithKeysInput): Promise<ReceiptVerifyResult>;
|
|
13
23
|
export type VerifyReceiptOfflineInput = {
|
|
14
24
|
genesisCbor: Uint8Array;
|
|
15
25
|
receiptCbor: Uint8Array;
|
|
@@ -30,4 +40,31 @@ export type VerifyReceiptOfflineInput = {
|
|
|
30
40
|
* resolution, inclusion, and signature are all standard.
|
|
31
41
|
*/
|
|
32
42
|
export declare function verifyReceiptOffline(input: VerifyReceiptOfflineInput): Promise<ReceiptVerifyResult>;
|
|
43
|
+
export type VerifyReceiptOfflineWithKeysInput = Omit<VerifyReceiptOfflineInput, "genesisCbor"> & {
|
|
44
|
+
/**
|
|
45
|
+
* Caller-known ES256 trust keys, tried in order. For a child-log receipt
|
|
46
|
+
* this is the log OWNER's key (the delegation-cert issuer), NOT the sealer
|
|
47
|
+
* key — the label-1000 cert is verified under these keys and the delegated
|
|
48
|
+
* sealer key is extracted from it, so the anchor survives sealer rotation.
|
|
49
|
+
*/
|
|
50
|
+
trustKeys: CryptoKey[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* {@link verifyReceiptOffline} under caller-supplied trust keys instead of the
|
|
54
|
+
* genesis trust root (FOR-297 "known log key"). Standard SCITT relying-party
|
|
55
|
+
* posture: the caller obtained the log owner's key out of band and trusts it.
|
|
56
|
+
*
|
|
57
|
+
* Trust ladder — what this rung gives up relative to its neighbours:
|
|
58
|
+
* - Known log key (this entry): fully offline, but the "key K owns log L"
|
|
59
|
+
* binding is ASSERTED by the caller's key provenance, not proven; no grant
|
|
60
|
+
* lifecycle/expiry visibility; no split-view protection.
|
|
61
|
+
* - Grant-chain walk (approach A, open): derives the binding from
|
|
62
|
+
* `genesis.cbor` + public tiles and adds lifecycle visibility.
|
|
63
|
+
* - Chain-anchored (live or cached accumulator): adds split-view protection —
|
|
64
|
+
* the contract-enforced on-chain accumulator is the strongest anchor.
|
|
65
|
+
*
|
|
66
|
+
* Do NOT fetch the trust key from the log operator's API or tile store — that
|
|
67
|
+
* silently reintroduces the operator trust this entry exists to remove.
|
|
68
|
+
*/
|
|
69
|
+
export declare function verifyReceiptOfflineWithKeys(input: VerifyReceiptOfflineWithKeysInput): Promise<ReceiptVerifyResult>;
|
|
33
70
|
//# sourceMappingURL=verify-grant-receipt-offline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-grant-receipt-offline.d.ts","sourceRoot":"","sources":["../src/verify-grant-receipt-offline.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAOjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,MAAM,MAAM,8BAA8B,GAAG;IAC3C,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,EAAE,UAAU,CAAC;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,UAAU,CAAC;CAC5B,CAAC;AA4EF;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAc9B;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,EAAE,UAAU,CAAC;IACxB;;;;;OAKG;IACH,OAAO,EAAE,UAAU,CAAC;IACpB,cAAc,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAa9B"}
|
|
1
|
+
{"version":3,"file":"verify-grant-receipt-offline.d.ts","sourceRoot":"","sources":["../src/verify-grant-receipt-offline.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAOjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,MAAM,MAAM,8BAA8B,GAAG;IAC3C,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,EAAE,UAAU,CAAC;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,UAAU,CAAC;CAC5B,CAAC;AA4EF;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAc9B;AAED,MAAM,MAAM,sCAAsC,GAAG,IAAI,CACvD,8BAA8B,EAC9B,aAAa,CACd,GAAG;IACF,qFAAqF;IACrF,SAAS,EAAE,SAAS,EAAE,CAAC;CACxB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,iCAAiC,CACrD,KAAK,EAAE,sCAAsC,GAC5C,OAAO,CAAC,mBAAmB,CAAC,CAa9B;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,EAAE,UAAU,CAAC;IACxB;;;;;OAKG;IACH,OAAO,EAAE,UAAU,CAAC;IACpB,cAAc,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAa9B;AAED,MAAM,MAAM,iCAAiC,GAAG,IAAI,CAClD,yBAAyB,EACzB,aAAa,CACd,GAAG;IACF;;;;;OAKG;IACH,SAAS,EAAE,SAAS,EAAE,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,mBAAmB,CAAC,CAa9B"}
|
|
@@ -74,6 +74,26 @@ export async function verifyGrantReceiptOffline(input) {
|
|
|
74
74
|
inner,
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* {@link verifyGrantReceiptOffline} under caller-supplied trust keys instead of
|
|
79
|
+
* the genesis trust root — see {@link verifyReceiptOfflineWithKeys} for the
|
|
80
|
+
* trust model this buys (and what it does not).
|
|
81
|
+
*/
|
|
82
|
+
export async function verifyGrantReceiptOfflineWithKeys(input) {
|
|
83
|
+
let inner;
|
|
84
|
+
try {
|
|
85
|
+
inner = await grantCommitmentHashFromGrant(input.grant);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return { ok: false, stage: "binding", reason: "grant_invalid" };
|
|
89
|
+
}
|
|
90
|
+
return verifyReceiptOfflineWithLeafInnerKeys({
|
|
91
|
+
receiptCbor: input.receiptCbor,
|
|
92
|
+
idtimestampBe8: input.idtimestampBe8,
|
|
93
|
+
inner,
|
|
94
|
+
trustKeys: input.trustKeys,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
77
97
|
/**
|
|
78
98
|
* Offline verify of a receipt against the EXACT registered payload. The log
|
|
79
99
|
* leaf commits `SHA-256(idtimestamp ‖ SHA-256(payload))`; this is the standard,
|
|
@@ -90,6 +110,32 @@ export async function verifyReceiptOffline(input) {
|
|
|
90
110
|
inner,
|
|
91
111
|
});
|
|
92
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* {@link verifyReceiptOffline} under caller-supplied trust keys instead of the
|
|
115
|
+
* genesis trust root (FOR-297 "known log key"). Standard SCITT relying-party
|
|
116
|
+
* posture: the caller obtained the log owner's key out of band and trusts it.
|
|
117
|
+
*
|
|
118
|
+
* Trust ladder — what this rung gives up relative to its neighbours:
|
|
119
|
+
* - Known log key (this entry): fully offline, but the "key K owns log L"
|
|
120
|
+
* binding is ASSERTED by the caller's key provenance, not proven; no grant
|
|
121
|
+
* lifecycle/expiry visibility; no split-view protection.
|
|
122
|
+
* - Grant-chain walk (approach A, open): derives the binding from
|
|
123
|
+
* `genesis.cbor` + public tiles and adds lifecycle visibility.
|
|
124
|
+
* - Chain-anchored (live or cached accumulator): adds split-view protection —
|
|
125
|
+
* the contract-enforced on-chain accumulator is the strongest anchor.
|
|
126
|
+
*
|
|
127
|
+
* Do NOT fetch the trust key from the log operator's API or tile store — that
|
|
128
|
+
* silently reintroduces the operator trust this entry exists to remove.
|
|
129
|
+
*/
|
|
130
|
+
export async function verifyReceiptOfflineWithKeys(input) {
|
|
131
|
+
const inner = new Uint8Array(await crypto.subtle.digest("SHA-256", input.payload));
|
|
132
|
+
return verifyReceiptOfflineWithLeafInnerKeys({
|
|
133
|
+
receiptCbor: input.receiptCbor,
|
|
134
|
+
idtimestampBe8: input.idtimestampBe8,
|
|
135
|
+
inner,
|
|
136
|
+
trustKeys: input.trustKeys,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
93
139
|
/**
|
|
94
140
|
* Shared offline-verify core: reconstruct the leaf as
|
|
95
141
|
* `univocityLeafHash(idtimestamp, inner)`, resolve the (possibly delegated,
|
|
@@ -98,13 +144,6 @@ export async function verifyReceiptOffline(input) {
|
|
|
98
144
|
* commitment for a grant leaf, or `SHA-256(statement)` for a statement leaf.
|
|
99
145
|
*/
|
|
100
146
|
async function verifyReceiptOfflineWithLeafInner(input) {
|
|
101
|
-
let parsed;
|
|
102
|
-
try {
|
|
103
|
-
parsed = parseReceipt(input.receiptCbor);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return { ok: false, stage: "parse", reason: "receipt_malformed" };
|
|
107
|
-
}
|
|
108
147
|
let trustRoot;
|
|
109
148
|
try {
|
|
110
149
|
trustRoot = await decodeTrustRootFromGenesis(input.genesisCbor);
|
|
@@ -116,18 +155,41 @@ async function verifyReceiptOfflineWithLeafInner(input) {
|
|
|
116
155
|
if (!verifyKeys.length) {
|
|
117
156
|
return { ok: false, stage: "signature", reason: "no_es256_trust_key" };
|
|
118
157
|
}
|
|
158
|
+
return verifyReceiptOfflineWithLeafInnerKeys({
|
|
159
|
+
receiptCbor: input.receiptCbor,
|
|
160
|
+
idtimestampBe8: input.idtimestampBe8,
|
|
161
|
+
inner: input.inner,
|
|
162
|
+
trustKeys: verifyKeys,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Trust-key-parameterised offline-verify core. Identical to the genesis path
|
|
167
|
+
* once the trust keys are in hand: resolve the (possibly delegated, FOR-297)
|
|
168
|
+
* verify key against `trustKeys`, then check signature + inclusion.
|
|
169
|
+
*/
|
|
170
|
+
async function verifyReceiptOfflineWithLeafInnerKeys(input) {
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = parseReceipt(input.receiptCbor);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return { ok: false, stage: "parse", reason: "receipt_malformed" };
|
|
177
|
+
}
|
|
178
|
+
if (!input.trustKeys.length) {
|
|
179
|
+
return { ok: false, stage: "signature", reason: "no_es256_trust_key" };
|
|
180
|
+
}
|
|
119
181
|
// FOR-297: when the receipt was signed by a DELEGATED key, verify the
|
|
120
|
-
// label-1000 delegation certificate under the
|
|
121
|
-
// first. A cert present but not chaining to
|
|
122
|
-
// not silently fall back to the
|
|
123
|
-
// delegated-signed receipt anyway).
|
|
124
|
-
const delegation = await resolveDelegatedVerifyKey(input.receiptCbor,
|
|
182
|
+
// label-1000 delegation certificate under the trust keys and try the
|
|
183
|
+
// delegated key first. A cert present but not chaining to a trust key is a
|
|
184
|
+
// hard failure — do not silently fall back to the trust key (which cannot
|
|
185
|
+
// verify a delegated-signed receipt anyway).
|
|
186
|
+
const delegation = await resolveDelegatedVerifyKey(input.receiptCbor, input.trustKeys);
|
|
125
187
|
if (delegation.kind === "broken") {
|
|
126
188
|
return { ok: false, stage: "signature", reason: "delegation_invalid" };
|
|
127
189
|
}
|
|
128
190
|
const allVerifyKeys = delegation.kind === "resolved"
|
|
129
|
-
? [delegation.delegatedKey, ...
|
|
130
|
-
:
|
|
191
|
+
? [delegation.delegatedKey, ...input.trustKeys]
|
|
192
|
+
: input.trustKeys;
|
|
131
193
|
let idtimestamp;
|
|
132
194
|
try {
|
|
133
195
|
idtimestamp = readIdtimestampBe8(input.idtimestampBe8);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -27,6 +27,23 @@ export { peakMMRIndexes } from "@forestrie/merklelog";
|
|
|
27
27
|
export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
|
|
28
28
|
export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
29
29
|
export { verifyReceiptOffline } from "./verify-grant-receipt-offline.js";
|
|
30
|
+
/**
|
|
31
|
+
* Caller-supplied trust anchors (FOR-297 "known log key"): verify offline
|
|
32
|
+
* under keys the caller trusts out of band instead of the genesis trust root.
|
|
33
|
+
* See the trust-ladder notes on verifyReceiptOfflineWithKeys.
|
|
34
|
+
*/
|
|
35
|
+
export {
|
|
36
|
+
verifyGrantReceiptOfflineWithKeys,
|
|
37
|
+
verifyReceiptOfflineWithKeys,
|
|
38
|
+
} from "./verify-grant-receipt-offline.js";
|
|
39
|
+
export type {
|
|
40
|
+
VerifyGrantReceiptOfflineWithKeysInput,
|
|
41
|
+
VerifyReceiptOfflineWithKeysInput,
|
|
42
|
+
} from "./verify-grant-receipt-offline.js";
|
|
43
|
+
export { resolveDelegatedVerifyKey } from "./resolve-delegated-verify-key.js";
|
|
44
|
+
export type { DelegatedResolution } from "./resolve-delegated-verify-key.js";
|
|
45
|
+
/** Import a raw 64-byte x||y P-256 public key as an ES256 verify key. */
|
|
46
|
+
export { importEs256PublicKeyFromGrantDataXy64 } from "./decode-trust-root-cbor.js";
|
|
30
47
|
export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
|
|
31
48
|
export { decodeGrantPayload, decodeGrantResponse } from "./grant-codec.js";
|
|
32
49
|
|
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
* sealer delegate keys are always secp256r1. KS256-rooted delegation is a
|
|
11
11
|
* server-only concern.
|
|
12
12
|
*
|
|
13
|
+
* Trust anchors (FOR-297 trust ladder): `rootKeys` need not be genesis-derived
|
|
14
|
+
* — a caller-known log OWNER key works identically (see
|
|
15
|
+
* `verifyReceiptOfflineWithKeys`), because the certificate issuer for a child
|
|
16
|
+
* log IS its owner. What differs is why you trust the keys: genesis-derived
|
|
17
|
+
* keys prove the key↔log binding from the bootstrap; a caller-known key merely
|
|
18
|
+
* asserts it (provenance of the key is the only defence, and there is no grant
|
|
19
|
+
* lifecycle visibility or split-view protection). The grant-chain walk
|
|
20
|
+
* (approach A, open) will derive per-log owner keys from genesis + public
|
|
21
|
+
* tiles, closing that gap without key distribution.
|
|
22
|
+
*
|
|
13
23
|
* Note (parity with the server port source): this establishes that the root
|
|
14
24
|
* authorized the delegated key, but does NOT yet enforce the certificate's MMR
|
|
15
25
|
* window or expiry-at-issuance against the leaf — that hardening is shared with
|
|
@@ -116,6 +116,36 @@ export async function verifyGrantReceiptOffline(
|
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
export type VerifyGrantReceiptOfflineWithKeysInput = Omit<
|
|
120
|
+
VerifyGrantReceiptOfflineInput,
|
|
121
|
+
"genesisCbor"
|
|
122
|
+
> & {
|
|
123
|
+
/** Caller-known ES256 trust keys — see {@link VerifyReceiptOfflineWithKeysInput}. */
|
|
124
|
+
trustKeys: CryptoKey[];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* {@link verifyGrantReceiptOffline} under caller-supplied trust keys instead of
|
|
129
|
+
* the genesis trust root — see {@link verifyReceiptOfflineWithKeys} for the
|
|
130
|
+
* trust model this buys (and what it does not).
|
|
131
|
+
*/
|
|
132
|
+
export async function verifyGrantReceiptOfflineWithKeys(
|
|
133
|
+
input: VerifyGrantReceiptOfflineWithKeysInput,
|
|
134
|
+
): Promise<ReceiptVerifyResult> {
|
|
135
|
+
let inner: Uint8Array;
|
|
136
|
+
try {
|
|
137
|
+
inner = await grantCommitmentHashFromGrant(input.grant);
|
|
138
|
+
} catch {
|
|
139
|
+
return { ok: false, stage: "binding", reason: "grant_invalid" };
|
|
140
|
+
}
|
|
141
|
+
return verifyReceiptOfflineWithLeafInnerKeys({
|
|
142
|
+
receiptCbor: input.receiptCbor,
|
|
143
|
+
idtimestampBe8: input.idtimestampBe8,
|
|
144
|
+
inner,
|
|
145
|
+
trustKeys: input.trustKeys,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
119
149
|
export type VerifyReceiptOfflineInput = {
|
|
120
150
|
genesisCbor: Uint8Array;
|
|
121
151
|
receiptCbor: Uint8Array;
|
|
@@ -153,6 +183,53 @@ export async function verifyReceiptOffline(
|
|
|
153
183
|
});
|
|
154
184
|
}
|
|
155
185
|
|
|
186
|
+
export type VerifyReceiptOfflineWithKeysInput = Omit<
|
|
187
|
+
VerifyReceiptOfflineInput,
|
|
188
|
+
"genesisCbor"
|
|
189
|
+
> & {
|
|
190
|
+
/**
|
|
191
|
+
* Caller-known ES256 trust keys, tried in order. For a child-log receipt
|
|
192
|
+
* this is the log OWNER's key (the delegation-cert issuer), NOT the sealer
|
|
193
|
+
* key — the label-1000 cert is verified under these keys and the delegated
|
|
194
|
+
* sealer key is extracted from it, so the anchor survives sealer rotation.
|
|
195
|
+
*/
|
|
196
|
+
trustKeys: CryptoKey[];
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* {@link verifyReceiptOffline} under caller-supplied trust keys instead of the
|
|
201
|
+
* genesis trust root (FOR-297 "known log key"). Standard SCITT relying-party
|
|
202
|
+
* posture: the caller obtained the log owner's key out of band and trusts it.
|
|
203
|
+
*
|
|
204
|
+
* Trust ladder — what this rung gives up relative to its neighbours:
|
|
205
|
+
* - Known log key (this entry): fully offline, but the "key K owns log L"
|
|
206
|
+
* binding is ASSERTED by the caller's key provenance, not proven; no grant
|
|
207
|
+
* lifecycle/expiry visibility; no split-view protection.
|
|
208
|
+
* - Grant-chain walk (approach A, open): derives the binding from
|
|
209
|
+
* `genesis.cbor` + public tiles and adds lifecycle visibility.
|
|
210
|
+
* - Chain-anchored (live or cached accumulator): adds split-view protection —
|
|
211
|
+
* the contract-enforced on-chain accumulator is the strongest anchor.
|
|
212
|
+
*
|
|
213
|
+
* Do NOT fetch the trust key from the log operator's API or tile store — that
|
|
214
|
+
* silently reintroduces the operator trust this entry exists to remove.
|
|
215
|
+
*/
|
|
216
|
+
export async function verifyReceiptOfflineWithKeys(
|
|
217
|
+
input: VerifyReceiptOfflineWithKeysInput,
|
|
218
|
+
): Promise<ReceiptVerifyResult> {
|
|
219
|
+
const inner = new Uint8Array(
|
|
220
|
+
await crypto.subtle.digest(
|
|
221
|
+
"SHA-256",
|
|
222
|
+
input.payload as unknown as BufferSource,
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
return verifyReceiptOfflineWithLeafInnerKeys({
|
|
226
|
+
receiptCbor: input.receiptCbor,
|
|
227
|
+
idtimestampBe8: input.idtimestampBe8,
|
|
228
|
+
inner,
|
|
229
|
+
trustKeys: input.trustKeys,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
156
233
|
/**
|
|
157
234
|
* Shared offline-verify core: reconstruct the leaf as
|
|
158
235
|
* `univocityLeafHash(idtimestamp, inner)`, resolve the (possibly delegated,
|
|
@@ -166,13 +243,6 @@ async function verifyReceiptOfflineWithLeafInner(input: {
|
|
|
166
243
|
idtimestampBe8: Uint8Array;
|
|
167
244
|
inner: Uint8Array;
|
|
168
245
|
}): Promise<ReceiptVerifyResult> {
|
|
169
|
-
let parsed: ReturnType<typeof parseReceipt>;
|
|
170
|
-
try {
|
|
171
|
-
parsed = parseReceipt(input.receiptCbor);
|
|
172
|
-
} catch {
|
|
173
|
-
return { ok: false, stage: "parse", reason: "receipt_malformed" };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
246
|
let trustRoot;
|
|
177
247
|
try {
|
|
178
248
|
trustRoot = await decodeTrustRootFromGenesis(input.genesisCbor);
|
|
@@ -185,22 +255,52 @@ async function verifyReceiptOfflineWithLeafInner(input: {
|
|
|
185
255
|
return { ok: false, stage: "signature", reason: "no_es256_trust_key" };
|
|
186
256
|
}
|
|
187
257
|
|
|
258
|
+
return verifyReceiptOfflineWithLeafInnerKeys({
|
|
259
|
+
receiptCbor: input.receiptCbor,
|
|
260
|
+
idtimestampBe8: input.idtimestampBe8,
|
|
261
|
+
inner: input.inner,
|
|
262
|
+
trustKeys: verifyKeys,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Trust-key-parameterised offline-verify core. Identical to the genesis path
|
|
268
|
+
* once the trust keys are in hand: resolve the (possibly delegated, FOR-297)
|
|
269
|
+
* verify key against `trustKeys`, then check signature + inclusion.
|
|
270
|
+
*/
|
|
271
|
+
async function verifyReceiptOfflineWithLeafInnerKeys(input: {
|
|
272
|
+
receiptCbor: Uint8Array;
|
|
273
|
+
idtimestampBe8: Uint8Array;
|
|
274
|
+
inner: Uint8Array;
|
|
275
|
+
trustKeys: CryptoKey[];
|
|
276
|
+
}): Promise<ReceiptVerifyResult> {
|
|
277
|
+
let parsed: ReturnType<typeof parseReceipt>;
|
|
278
|
+
try {
|
|
279
|
+
parsed = parseReceipt(input.receiptCbor);
|
|
280
|
+
} catch {
|
|
281
|
+
return { ok: false, stage: "parse", reason: "receipt_malformed" };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!input.trustKeys.length) {
|
|
285
|
+
return { ok: false, stage: "signature", reason: "no_es256_trust_key" };
|
|
286
|
+
}
|
|
287
|
+
|
|
188
288
|
// FOR-297: when the receipt was signed by a DELEGATED key, verify the
|
|
189
|
-
// label-1000 delegation certificate under the
|
|
190
|
-
// first. A cert present but not chaining to
|
|
191
|
-
// not silently fall back to the
|
|
192
|
-
// delegated-signed receipt anyway).
|
|
289
|
+
// label-1000 delegation certificate under the trust keys and try the
|
|
290
|
+
// delegated key first. A cert present but not chaining to a trust key is a
|
|
291
|
+
// hard failure — do not silently fall back to the trust key (which cannot
|
|
292
|
+
// verify a delegated-signed receipt anyway).
|
|
193
293
|
const delegation = await resolveDelegatedVerifyKey(
|
|
194
294
|
input.receiptCbor,
|
|
195
|
-
|
|
295
|
+
input.trustKeys,
|
|
196
296
|
);
|
|
197
297
|
if (delegation.kind === "broken") {
|
|
198
298
|
return { ok: false, stage: "signature", reason: "delegation_invalid" };
|
|
199
299
|
}
|
|
200
300
|
const allVerifyKeys =
|
|
201
301
|
delegation.kind === "resolved"
|
|
202
|
-
? [delegation.delegatedKey, ...
|
|
203
|
-
:
|
|
302
|
+
? [delegation.delegatedKey, ...input.trustKeys]
|
|
303
|
+
: input.trustKeys;
|
|
204
304
|
|
|
205
305
|
let idtimestamp: bigint;
|
|
206
306
|
try {
|