@forestrie/receipt-verify 3.0.1 → 3.1.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/decode-trust-root-cbor.d.ts +8 -1
- package/dist/decode-trust-root-cbor.d.ts.map +1 -1
- package/dist/decode-trust-root-cbor.js +10 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/known-log-key.d.ts +15 -0
- package/dist/known-log-key.d.ts.map +1 -0
- package/dist/known-log-key.js +20 -0
- package/dist/ks256-attestation.d.ts +50 -0
- package/dist/ks256-attestation.d.ts.map +1 -0
- package/dist/ks256-attestation.js +104 -0
- package/dist/resolve-delegated-verify-key.d.ts +18 -9
- package/dist/resolve-delegated-verify-key.d.ts.map +1 -1
- package/dist/resolve-delegated-verify-key.js +26 -9
- package/dist/verify-grant-receipt-offline.d.ts +20 -6
- package/dist/verify-grant-receipt-offline.d.ts.map +1 -1
- package/dist/verify-grant-receipt-offline.js +60 -11
- package/dist/verify-ks256-delegation.d.ts +56 -0
- package/dist/verify-ks256-delegation.d.ts.map +1 -0
- package/dist/verify-ks256-delegation.js +95 -0
- package/package.json +5 -3
- package/src/decode-trust-root-cbor.ts +10 -1
- package/src/index.ts +23 -0
- package/src/known-log-key.ts +28 -0
- package/src/ks256-attestation.ts +144 -0
- package/src/resolve-delegated-verify-key.ts +37 -11
- package/src/verify-grant-receipt-offline.ts +92 -19
- package/src/verify-ks256-delegation.ts +120 -0
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { ParsedVerifyKey } from "@forestrie/encoding";
|
|
2
|
-
import { type RootVerifyKey } from "./root-verify-key.js";
|
|
2
|
+
import { type ParsedKs256RootKey, type RootVerifyKey } from "./root-verify-key.js";
|
|
3
3
|
export declare function importEs256PublicKeyFromGrantDataXy64(xy: Uint8Array): Promise<CryptoKey>;
|
|
4
|
+
/**
|
|
5
|
+
* Wrap a 20-byte KS256 root address as a {@link ParsedKs256RootKey}.
|
|
6
|
+
*
|
|
7
|
+
* @throws When `address` is not 20 bytes: a key of any other length would
|
|
8
|
+
* fail {@link isParsedKs256RootKey} and be mistaken for an ES256 key.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ks256RootFromAddress(address: Uint8Array): ParsedKs256RootKey;
|
|
4
11
|
export declare function decodeTrustRootCbor(decoded: unknown): Promise<RootVerifyKey>;
|
|
5
12
|
export declare function es256ReceiptVerifyKeys(keys: RootVerifyKey[]): ParsedVerifyKey[];
|
|
6
13
|
//# sourceMappingURL=decode-trust-root-cbor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decode-trust-root-cbor.d.ts","sourceRoot":"","sources":["../src/decode-trust-root-cbor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,
|
|
1
|
+
{"version":3,"file":"decode-trust-root-cbor.d.ts","sourceRoot":"","sources":["../src/decode-trust-root-cbor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,wBAAsB,qCAAqC,CACzD,EAAE,EAAE,UAAU,GACb,OAAO,CAAC,SAAS,CAAC,CAgBpB;AAoBD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG,kBAAkB,CAK5E;AAgBD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,aAAa,CAAC,CAwCxB;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,aAAa,EAAE,GACpB,eAAe,EAAE,CAEnB"}
|
|
@@ -28,7 +28,16 @@ function parseAlgInt(raw) {
|
|
|
28
28
|
}
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Wrap a 20-byte KS256 root address as a {@link ParsedKs256RootKey}.
|
|
33
|
+
*
|
|
34
|
+
* @throws When `address` is not 20 bytes: a key of any other length would
|
|
35
|
+
* fail {@link isParsedKs256RootKey} and be mistaken for an ES256 key.
|
|
36
|
+
*/
|
|
37
|
+
export function ks256RootFromAddress(address) {
|
|
38
|
+
if (address.length !== 20) {
|
|
39
|
+
throw new Error("KS256 root address must be 20 bytes");
|
|
40
|
+
}
|
|
32
41
|
return { kind: "KS256", alg: COSE_ALG_KS256, address };
|
|
33
42
|
}
|
|
34
43
|
function recordToTrustRootFields(decoded) {
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,21 @@ export { checkDelegationConstraints, idtimestampToUnixMs, idtimestampToUnixSecon
|
|
|
48
48
|
export type { DelegatedResolution, DelegationConstraints, } from "./resolve-delegated-verify-key.js";
|
|
49
49
|
/** Import a raw 64-byte x||y P-256 public key as an ES256 verify key. */
|
|
50
50
|
export { importEs256PublicKeyFromGrantDataXy64 } from "./decode-trust-root-cbor.js";
|
|
51
|
+
/**
|
|
52
|
+
* KS256 roots in offline verification (plan-2610-03 D4–D6, protocol ADR-0045
|
|
53
|
+
* amendment 2026-10-18). A KS256 root comes only from genesis, a caller-known
|
|
54
|
+
* log key ({@link knownLogKeyFromBytes}: asserts, does not prove) or, for a
|
|
55
|
+
* contract root, a caller-supplied ERC-1271 attestation; never from the
|
|
56
|
+
* delegation coordinator. EOA roots verify by ecrecover; anything else needs
|
|
57
|
+
* an attestation or fails with `ks256_root_not_recovered`.
|
|
58
|
+
*/
|
|
59
|
+
export { knownLogKeyFromBytes } from "./known-log-key.js";
|
|
60
|
+
export { ks256RootFromAddress } from "./decode-trust-root-cbor.js";
|
|
61
|
+
export type { ParsedKs256RootKey, RootVerifyKey } from "./root-verify-key.js";
|
|
62
|
+
export { ERC1271_MAGIC_VALUE, decodeKs256Attestation, encodeKs256Attestation, type Ks256Attestation, } from "./ks256-attestation.js";
|
|
63
|
+
export { extractKs256DelegationDigest, verifyKs256DelegationCert, } from "./verify-ks256-delegation.js";
|
|
64
|
+
export type { Ks256DelegationEvidence } from "./verify-ks256-delegation.js";
|
|
65
|
+
export type { Ks256CertificateDigest } from "@forestrie/delegation-cose";
|
|
51
66
|
/**
|
|
52
67
|
* Passkey session-key endorsement v2 (ADR-0064 as amended by ADR-0065):
|
|
53
68
|
* build, assemble, and verify the artifact chaining a passkey log root to the
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,18 @@ export { verifyGrantReceiptOfflineWithKeys, verifyReceiptOfflineWithKeys, } from
|
|
|
37
37
|
export { checkDelegationConstraints, idtimestampToUnixMs, idtimestampToUnixSeconds, resolveDelegatedVerifyKey, } from "./resolve-delegated-verify-key.js";
|
|
38
38
|
/** Import a raw 64-byte x||y P-256 public key as an ES256 verify key. */
|
|
39
39
|
export { importEs256PublicKeyFromGrantDataXy64 } from "./decode-trust-root-cbor.js";
|
|
40
|
+
/**
|
|
41
|
+
* KS256 roots in offline verification (plan-2610-03 D4–D6, protocol ADR-0045
|
|
42
|
+
* amendment 2026-10-18). A KS256 root comes only from genesis, a caller-known
|
|
43
|
+
* log key ({@link knownLogKeyFromBytes}: asserts, does not prove) or, for a
|
|
44
|
+
* contract root, a caller-supplied ERC-1271 attestation; never from the
|
|
45
|
+
* delegation coordinator. EOA roots verify by ecrecover; anything else needs
|
|
46
|
+
* an attestation or fails with `ks256_root_not_recovered`.
|
|
47
|
+
*/
|
|
48
|
+
export { knownLogKeyFromBytes } from "./known-log-key.js";
|
|
49
|
+
export { ks256RootFromAddress } from "./decode-trust-root-cbor.js";
|
|
50
|
+
export { ERC1271_MAGIC_VALUE, decodeKs256Attestation, encodeKs256Attestation, } from "./ks256-attestation.js";
|
|
51
|
+
export { extractKs256DelegationDigest, verifyKs256DelegationCert, } from "./verify-ks256-delegation.js";
|
|
40
52
|
/**
|
|
41
53
|
* Passkey session-key endorsement v2 (ADR-0064 as amended by ADR-0065):
|
|
42
54
|
* build, assemble, and verify the artifact chaining a passkey log root to the
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-known log key bytes → trust key (FOR-297 "known log key"; plan-2610-03
|
|
3
|
+
* D6). The bytes alone decide the type, as univocity's
|
|
4
|
+
* `GrantDataInvalidKeyLength` and arbor's `grantDataIdentity` do: 64 bytes is
|
|
5
|
+
* an ES256 x||y public key, 20 bytes a KS256 address, anything else is
|
|
6
|
+
* refused. Either way the caller ASSERTS, and does not prove, that the key
|
|
7
|
+
* owns the log — see `verifyReceiptOfflineWithKeys`.
|
|
8
|
+
*/
|
|
9
|
+
import type { RootVerifyKey } from "./root-verify-key.js";
|
|
10
|
+
/**
|
|
11
|
+
* @param bytes - 64-byte ES256 x||y or 20-byte KS256 address.
|
|
12
|
+
* @throws When the length is neither.
|
|
13
|
+
*/
|
|
14
|
+
export declare function knownLogKeyFromBytes(bytes: Uint8Array): Promise<RootVerifyKey>;
|
|
15
|
+
//# sourceMappingURL=known-log-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"known-log-key.d.ts","sourceRoot":"","sources":["../src/known-log-key.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,aAAa,CAAC,CAMxB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-known log key bytes → trust key (FOR-297 "known log key"; plan-2610-03
|
|
3
|
+
* D6). The bytes alone decide the type, as univocity's
|
|
4
|
+
* `GrantDataInvalidKeyLength` and arbor's `grantDataIdentity` do: 64 bytes is
|
|
5
|
+
* an ES256 x||y public key, 20 bytes a KS256 address, anything else is
|
|
6
|
+
* refused. Either way the caller ASSERTS, and does not prove, that the key
|
|
7
|
+
* owns the log — see `verifyReceiptOfflineWithKeys`.
|
|
8
|
+
*/
|
|
9
|
+
import { importEs256PublicKeyFromGrantDataXy64, ks256RootFromAddress, } from "./decode-trust-root-cbor.js";
|
|
10
|
+
/**
|
|
11
|
+
* @param bytes - 64-byte ES256 x||y or 20-byte KS256 address.
|
|
12
|
+
* @throws When the length is neither.
|
|
13
|
+
*/
|
|
14
|
+
export async function knownLogKeyFromBytes(bytes) {
|
|
15
|
+
if (bytes.length === 64)
|
|
16
|
+
return importEs256PublicKeyFromGrantDataXy64(bytes);
|
|
17
|
+
if (bytes.length === 20)
|
|
18
|
+
return ks256RootFromAddress(bytes);
|
|
19
|
+
throw new Error(`known log key must be 64 bytes (ES256 x||y) or 20 bytes (KS256 address), got ${bytes.length}`);
|
|
20
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERC-1271 attestation (plan-2610-03 D4, protocol ADR-0045 amendment
|
|
3
|
+
* 2026-10-18): a caller-supplied record that a contract KS256 root (a Safe)
|
|
4
|
+
* answered `isValidSignature(digest, signature)` with the ERC-1271 magic value
|
|
5
|
+
* at a given block. It lets a contract-rooted delegation certificate verify
|
|
6
|
+
* offline with the narrower guarantee "valid under the contract's signers at
|
|
7
|
+
* the attested block".
|
|
8
|
+
*
|
|
9
|
+
* Same trust model as the known-accumulator snapshot (known-accumulator.ts):
|
|
10
|
+
* the record is the fetching party's chain read, made explicit and auditable.
|
|
11
|
+
* It binds `(chainId, address, digest, signature hash, block)`, so anyone with
|
|
12
|
+
* RPC can re-run exactly that call at that block (the signature itself is in
|
|
13
|
+
* the receipt's certificate) and confirm or disprove it. This package never makes
|
|
14
|
+
* that call; forestrie-cli and mcp-resolve fetch it (slice 04).
|
|
15
|
+
*
|
|
16
|
+
* The verifier accepts an attestation only for the exact root address and
|
|
17
|
+
* certificate digest, and only when `chainId` equals the forest's genesis
|
|
18
|
+
* chain: the same Safe address on another chain can have other owners (D6).
|
|
19
|
+
*/
|
|
20
|
+
/** ERC-1271 `isValidSignature` success value, `bytes4(0x1626ba7e)`. */
|
|
21
|
+
export declare const ERC1271_MAGIC_VALUE: Uint8Array<ArrayBuffer>;
|
|
22
|
+
/** A block-bound ERC-1271 `isValidSignature` result for one digest. */
|
|
23
|
+
export type Ks256Attestation = {
|
|
24
|
+
version: number;
|
|
25
|
+
/** Chain the call was made on; must equal the forest's genesis chain. */
|
|
26
|
+
chainId: bigint;
|
|
27
|
+
/** Contract root address (20 bytes) the call was made to. */
|
|
28
|
+
address: Uint8Array;
|
|
29
|
+
/** keccak256(Sig_structure) of the delegation certificate (32 bytes). */
|
|
30
|
+
digest: Uint8Array;
|
|
31
|
+
/**
|
|
32
|
+
* keccak256 of the signature bytes `isValidSignature` was called with
|
|
33
|
+
* (32 bytes); must match the certificate's signature.
|
|
34
|
+
*/
|
|
35
|
+
signatureHash: Uint8Array;
|
|
36
|
+
/** Block the call was made at. */
|
|
37
|
+
blockNumber: bigint;
|
|
38
|
+
/** Hash of that block (32 bytes): the falsifiability handle. */
|
|
39
|
+
blockHash: Uint8Array;
|
|
40
|
+
/** The call's 4-byte return; only {@link ERC1271_MAGIC_VALUE} attests. */
|
|
41
|
+
magic: Uint8Array;
|
|
42
|
+
};
|
|
43
|
+
/** Encode an attestation as canonical CBOR (RFC 8949 §4.2, hard policy). */
|
|
44
|
+
export declare function encodeKs256Attestation(attestation: Ks256Attestation): Uint8Array;
|
|
45
|
+
/**
|
|
46
|
+
* Strict decode and shape validation of an attestation artifact: canonical
|
|
47
|
+
* CBOR, known labels only, version 1, exact types and lengths.
|
|
48
|
+
*/
|
|
49
|
+
export declare function decodeKs256Attestation(bytes: Uint8Array): Ks256Attestation;
|
|
50
|
+
//# sourceMappingURL=ks256-attestation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ks256-attestation.d.ts","sourceRoot":"","sources":["../src/ks256-attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,yBAA2C,CAAC;AAyB5E,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,OAAO,EAAE,UAAU,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,UAAU,CAAC;IACnB;;;OAGG;IACH,aAAa,EAAE,UAAU,CAAC;IAC1B,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,SAAS,EAAE,UAAU,CAAC;IACtB,0EAA0E;IAC1E,KAAK,EAAE,UAAU,CAAC;CACnB,CAAC;AAEF,4EAA4E;AAC5E,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,gBAAgB,GAC5B,UAAU,CAaZ;AAgBD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,gBAAgB,CAkC1E"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERC-1271 attestation (plan-2610-03 D4, protocol ADR-0045 amendment
|
|
3
|
+
* 2026-10-18): a caller-supplied record that a contract KS256 root (a Safe)
|
|
4
|
+
* answered `isValidSignature(digest, signature)` with the ERC-1271 magic value
|
|
5
|
+
* at a given block. It lets a contract-rooted delegation certificate verify
|
|
6
|
+
* offline with the narrower guarantee "valid under the contract's signers at
|
|
7
|
+
* the attested block".
|
|
8
|
+
*
|
|
9
|
+
* Same trust model as the known-accumulator snapshot (known-accumulator.ts):
|
|
10
|
+
* the record is the fetching party's chain read, made explicit and auditable.
|
|
11
|
+
* It binds `(chainId, address, digest, signature hash, block)`, so anyone with
|
|
12
|
+
* RPC can re-run exactly that call at that block (the signature itself is in
|
|
13
|
+
* the receipt's certificate) and confirm or disprove it. This package never makes
|
|
14
|
+
* that call; forestrie-cli and mcp-resolve fetch it (slice 04).
|
|
15
|
+
*
|
|
16
|
+
* The verifier accepts an attestation only for the exact root address and
|
|
17
|
+
* certificate digest, and only when `chainId` equals the forest's genesis
|
|
18
|
+
* chain: the same Safe address on another chain can have other owners (D6).
|
|
19
|
+
*/
|
|
20
|
+
import { decodeCborDeterministic, encodeCborDeterministic, } from "@forestrie/encoding";
|
|
21
|
+
/** ERC-1271 `isValidSignature` success value, `bytes4(0x1626ba7e)`. */
|
|
22
|
+
export const ERC1271_MAGIC_VALUE = new Uint8Array([0x16, 0x26, 0xba, 0x7e]);
|
|
23
|
+
/** CBOR map labels for the attestation artifact (strict RFC 8949 §4.2). */
|
|
24
|
+
const LABEL_VERSION = 1;
|
|
25
|
+
const LABEL_CHAIN_ID = 2;
|
|
26
|
+
const LABEL_ADDRESS = 3;
|
|
27
|
+
const LABEL_DIGEST = 4;
|
|
28
|
+
const LABEL_BLOCK_NUMBER = 5;
|
|
29
|
+
const LABEL_BLOCK_HASH = 6;
|
|
30
|
+
const LABEL_MAGIC = 7;
|
|
31
|
+
const LABEL_SIGNATURE_HASH = 8;
|
|
32
|
+
const KNOWN_LABELS = new Set([
|
|
33
|
+
LABEL_VERSION,
|
|
34
|
+
LABEL_CHAIN_ID,
|
|
35
|
+
LABEL_ADDRESS,
|
|
36
|
+
LABEL_DIGEST,
|
|
37
|
+
LABEL_BLOCK_NUMBER,
|
|
38
|
+
LABEL_BLOCK_HASH,
|
|
39
|
+
LABEL_MAGIC,
|
|
40
|
+
LABEL_SIGNATURE_HASH,
|
|
41
|
+
]);
|
|
42
|
+
const ATTESTATION_VERSION = 1;
|
|
43
|
+
/** Encode an attestation as canonical CBOR (RFC 8949 §4.2, hard policy). */
|
|
44
|
+
export function encodeKs256Attestation(attestation) {
|
|
45
|
+
return encodeCborDeterministic(new Map([
|
|
46
|
+
[LABEL_VERSION, attestation.version],
|
|
47
|
+
[LABEL_CHAIN_ID, attestation.chainId],
|
|
48
|
+
[LABEL_ADDRESS, attestation.address],
|
|
49
|
+
[LABEL_DIGEST, attestation.digest],
|
|
50
|
+
[LABEL_BLOCK_NUMBER, attestation.blockNumber],
|
|
51
|
+
[LABEL_BLOCK_HASH, attestation.blockHash],
|
|
52
|
+
[LABEL_MAGIC, attestation.magic],
|
|
53
|
+
[LABEL_SIGNATURE_HASH, attestation.signatureHash],
|
|
54
|
+
]));
|
|
55
|
+
}
|
|
56
|
+
function asBigint(v, what) {
|
|
57
|
+
if (typeof v === "bigint")
|
|
58
|
+
return v;
|
|
59
|
+
if (typeof v === "number" && Number.isSafeInteger(v) && v >= 0)
|
|
60
|
+
return BigInt(v);
|
|
61
|
+
throw new Error(`ks256 attestation: ${what} must be an unsigned integer`);
|
|
62
|
+
}
|
|
63
|
+
function asBytes(v, length, what) {
|
|
64
|
+
if (!(v instanceof Uint8Array) || v.length !== length) {
|
|
65
|
+
throw new Error(`ks256 attestation: ${what} must be ${length} bytes`);
|
|
66
|
+
}
|
|
67
|
+
return v;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Strict decode and shape validation of an attestation artifact: canonical
|
|
71
|
+
* CBOR, known labels only, version 1, exact types and lengths.
|
|
72
|
+
*/
|
|
73
|
+
export function decodeKs256Attestation(bytes) {
|
|
74
|
+
let decoded;
|
|
75
|
+
try {
|
|
76
|
+
decoded = decodeCborDeterministic(bytes);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
+
throw new Error(`ks256 attestation is not canonical CBOR: ${message}`);
|
|
81
|
+
}
|
|
82
|
+
if (!(decoded instanceof Map)) {
|
|
83
|
+
throw new Error("ks256 attestation must be a CBOR map");
|
|
84
|
+
}
|
|
85
|
+
for (const label of decoded.keys()) {
|
|
86
|
+
if (!KNOWN_LABELS.has(label)) {
|
|
87
|
+
throw new Error(`ks256 attestation: unknown label ${String(label)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const version = Number(asBigint(decoded.get(LABEL_VERSION), "version"));
|
|
91
|
+
if (version !== ATTESTATION_VERSION) {
|
|
92
|
+
throw new Error(`ks256 attestation version ${version} not supported`);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
version,
|
|
96
|
+
chainId: asBigint(decoded.get(LABEL_CHAIN_ID), "chainId"),
|
|
97
|
+
address: asBytes(decoded.get(LABEL_ADDRESS), 20, "address"),
|
|
98
|
+
digest: asBytes(decoded.get(LABEL_DIGEST), 32, "digest"),
|
|
99
|
+
blockNumber: asBigint(decoded.get(LABEL_BLOCK_NUMBER), "blockNumber"),
|
|
100
|
+
blockHash: asBytes(decoded.get(LABEL_BLOCK_HASH), 32, "blockHash"),
|
|
101
|
+
magic: asBytes(decoded.get(LABEL_MAGIC), 4, "magic"),
|
|
102
|
+
signatureHash: asBytes(decoded.get(LABEL_SIGNATURE_HASH), 32, "signatureHash"),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
* trust root and returns the delegated public key as an additional verify
|
|
7
7
|
* candidate — the offline port of canopy-api's `resolveReceiptVerifyKey`.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Sealer delegate keys are always secp256r1. A P-256 root's certificate is
|
|
10
|
+
* checked here; a passkey (WebAuthn) root signs the certificate with alg
|
|
11
|
+
* -65800 and the ADR-0063 unprotected-header envelope, which
|
|
12
|
+
* `verifyCoseSign1WithParsedKey` verifies against the same P-256 root keys
|
|
13
|
+
* (plan-2608-13 1.4). A KS256 root's certificate is checked by
|
|
14
|
+
* {@link verifyKs256DelegationCert}: ecrecover for an EOA, or a caller-supplied
|
|
15
|
+
* ERC-1271 attestation for a contract (plan-2610-03 D4).
|
|
15
16
|
*
|
|
16
17
|
* Trust anchors (FOR-297 trust ladder): `rootKeys` need not be genesis-derived
|
|
17
18
|
* — a caller-known log OWNER key works identically (see
|
|
@@ -34,6 +35,8 @@
|
|
|
34
35
|
* `issuedAt` lower bound are NOT enforced (see that function). This replaces the
|
|
35
36
|
* earlier "does not yet enforce the window" gap.
|
|
36
37
|
*/
|
|
38
|
+
import { type RootVerifyKey } from "./root-verify-key.js";
|
|
39
|
+
import { type Ks256DelegationEvidence } from "./verify-ks256-delegation.js";
|
|
37
40
|
export declare function idtimestampToUnixSeconds(idtimestamp: bigint): number;
|
|
38
41
|
/**
|
|
39
42
|
* Snowflake idtimestamp → Unix milliseconds: the time component an
|
|
@@ -58,7 +61,8 @@ export type DelegationConstraints = {
|
|
|
58
61
|
* `constraints` carries the cert's coverage/validity window when the payload
|
|
59
62
|
* declares it (null for legacy certs that predate labels 3/4/8/9).
|
|
60
63
|
* broken: a cert is present but did not verify under the root, or its delegated
|
|
61
|
-
* key could not be parsed — the delegation chain is invalid.
|
|
64
|
+
* key could not be parsed — the delegation chain is invalid. `reason` is set
|
|
65
|
+
* when a KS256 root gives a more specific one than `delegation_invalid`.
|
|
62
66
|
*/
|
|
63
67
|
export type DelegatedResolution = {
|
|
64
68
|
kind: "root-only";
|
|
@@ -68,6 +72,7 @@ export type DelegatedResolution = {
|
|
|
68
72
|
constraints: DelegationConstraints | null;
|
|
69
73
|
} | {
|
|
70
74
|
kind: "broken";
|
|
75
|
+
reason?: string;
|
|
71
76
|
};
|
|
72
77
|
/**
|
|
73
78
|
* Enforce a resolved cert's constraints against the verified leaf (FOR-420).
|
|
@@ -107,7 +112,11 @@ export declare function checkDelegationConstraints(constraints: DelegationConstr
|
|
|
107
112
|
* Resolve the delegated verify key from a receipt's delegation certificate.
|
|
108
113
|
*
|
|
109
114
|
* @param receiptCbor - the COSE_Sign1 receipt bytes.
|
|
110
|
-
* @param rootKeys -
|
|
115
|
+
* @param rootKeys - trust roots: ES256 keys and KS256 addresses, from genesis
|
|
116
|
+
* or caller-known log keys. A KS256 root never comes from the coordinator
|
|
117
|
+
* (its public roots are last-write-wins, FOR-601/FOR-603).
|
|
118
|
+
* @param ks256Evidence - optional ERC-1271 attestation for a KS256 contract
|
|
119
|
+
* root, with the forest's genesis chain id it must match.
|
|
111
120
|
*/
|
|
112
|
-
export declare function resolveDelegatedVerifyKey(receiptCbor: Uint8Array, rootKeys:
|
|
121
|
+
export declare function resolveDelegatedVerifyKey(receiptCbor: Uint8Array, rootKeys: RootVerifyKey[], ks256Evidence?: Ks256DelegationEvidence): Promise<DelegatedResolution>;
|
|
113
122
|
//# sourceMappingURL=resolve-delegated-verify-key.d.ts.map
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAQH,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,8BAA8B,CAAC;AA8BtC,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,SAAS,CAAC;IACxB,WAAW,EAAE,qBAAqB,GAAG,IAAI,CAAC;CAC3C,GACD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAsGxC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,qBAAqB,EAClC,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,GACtB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAS9C;AAED;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,UAAU,EACvB,QAAQ,EAAE,aAAa,EAAE,EACzB,aAAa,GAAE,uBAA4B,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CA8C9B"}
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
* trust root and returns the delegated public key as an additional verify
|
|
7
7
|
* candidate — the offline port of canopy-api's `resolveReceiptVerifyKey`.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Sealer delegate keys are always secp256r1. A P-256 root's certificate is
|
|
10
|
+
* checked here; a passkey (WebAuthn) root signs the certificate with alg
|
|
11
|
+
* -65800 and the ADR-0063 unprotected-header envelope, which
|
|
12
|
+
* `verifyCoseSign1WithParsedKey` verifies against the same P-256 root keys
|
|
13
|
+
* (plan-2608-13 1.4). A KS256 root's certificate is checked by
|
|
14
|
+
* {@link verifyKs256DelegationCert}: ecrecover for an EOA, or a caller-supplied
|
|
15
|
+
* ERC-1271 attestation for a contract (plan-2610-03 D4).
|
|
15
16
|
*
|
|
16
17
|
* Trust anchors (FOR-297 trust ladder): `rootKeys` need not be genesis-derived
|
|
17
18
|
* — a caller-known log OWNER key works identically (see
|
|
@@ -35,6 +36,8 @@
|
|
|
35
36
|
* earlier "does not yet enforce the window" gap.
|
|
36
37
|
*/
|
|
37
38
|
import { coseUnprotectedToMap, decodeCborDeterministic, decodeCoseSign1, verifyCoseSign1WithParsedKey, } from "@forestrie/encoding";
|
|
39
|
+
import { isParsedKs256RootKey } from "./root-verify-key.js";
|
|
40
|
+
import { verifyKs256DelegationCert, } from "./verify-ks256-delegation.js";
|
|
38
41
|
/** Unprotected header label carrying the delegation certificate. */
|
|
39
42
|
const DELEGATION_CERT_LABEL = 1000;
|
|
40
43
|
/** COSE_Key labels (RFC 9052) and EC2/P-256 constants. */
|
|
@@ -207,9 +210,13 @@ export function checkDelegationConstraints(constraints, leafMmrIndex, leafIdtime
|
|
|
207
210
|
* Resolve the delegated verify key from a receipt's delegation certificate.
|
|
208
211
|
*
|
|
209
212
|
* @param receiptCbor - the COSE_Sign1 receipt bytes.
|
|
210
|
-
* @param rootKeys -
|
|
213
|
+
* @param rootKeys - trust roots: ES256 keys and KS256 addresses, from genesis
|
|
214
|
+
* or caller-known log keys. A KS256 root never comes from the coordinator
|
|
215
|
+
* (its public roots are last-write-wins, FOR-601/FOR-603).
|
|
216
|
+
* @param ks256Evidence - optional ERC-1271 attestation for a KS256 contract
|
|
217
|
+
* root, with the forest's genesis chain id it must match.
|
|
211
218
|
*/
|
|
212
|
-
export async function resolveDelegatedVerifyKey(receiptCbor, rootKeys) {
|
|
219
|
+
export async function resolveDelegatedVerifyKey(receiptCbor, rootKeys, ks256Evidence = {}) {
|
|
213
220
|
const decoded = decodeCoseSign1(receiptCbor);
|
|
214
221
|
if (!decoded)
|
|
215
222
|
return { kind: "root-only" };
|
|
@@ -217,7 +224,17 @@ export async function resolveDelegatedVerifyKey(receiptCbor, rootKeys) {
|
|
|
217
224
|
if (!certBytes)
|
|
218
225
|
return { kind: "root-only" };
|
|
219
226
|
let verified = false;
|
|
227
|
+
let ks256Reason;
|
|
220
228
|
for (const rootKey of rootKeys) {
|
|
229
|
+
if (isParsedKs256RootKey(rootKey)) {
|
|
230
|
+
const check = await verifyKs256DelegationCert(certBytes, rootKey.address, ks256Evidence);
|
|
231
|
+
if (check.ok) {
|
|
232
|
+
verified = true;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
ks256Reason ??= check.reason;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
221
238
|
if (await verifyCoseSign1WithParsedKey(certBytes, rootKey, {
|
|
222
239
|
logPrefix: "delegation-cert-offline",
|
|
223
240
|
})) {
|
|
@@ -226,7 +243,7 @@ export async function resolveDelegatedVerifyKey(receiptCbor, rootKeys) {
|
|
|
226
243
|
}
|
|
227
244
|
}
|
|
228
245
|
if (!verified)
|
|
229
|
-
return { kind: "broken" };
|
|
246
|
+
return { kind: "broken", reason: ks256Reason };
|
|
230
247
|
const certDecoded = decodeCoseSign1(certBytes);
|
|
231
248
|
if (!certDecoded)
|
|
232
249
|
return { kind: "broken" };
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import type { Grant } from "@forestrie/encoding";
|
|
2
|
+
import type { Ks256Attestation } from "./ks256-attestation.js";
|
|
3
|
+
import { type RootVerifyKey } from "./root-verify-key.js";
|
|
2
4
|
import type { ReceiptVerifyResult } from "./receipt-verify-result.js";
|
|
3
5
|
export type VerifyGrantReceiptOfflineInput = {
|
|
4
6
|
genesisCbor: Uint8Array;
|
|
5
7
|
receiptCbor: Uint8Array;
|
|
6
8
|
grant: Grant;
|
|
7
9
|
idtimestampBe8: Uint8Array;
|
|
10
|
+
/** See {@link VerifyReceiptOfflineInput.ks256Attestation}. */
|
|
11
|
+
ks256Attestation?: Ks256Attestation;
|
|
8
12
|
};
|
|
9
13
|
/**
|
|
10
14
|
* Offline grant receipt verify (layers A–C, ADR-0045). Pure over bytes; no network.
|
|
11
15
|
*/
|
|
12
16
|
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
|
+
export type VerifyGrantReceiptOfflineWithKeysInput = Omit<VerifyGrantReceiptOfflineInput, "genesisCbor"> & Pick<VerifyReceiptOfflineWithKeysInput, "trustKeys" | "forestGenesisCbor">;
|
|
17
18
|
/**
|
|
18
19
|
* {@link verifyGrantReceiptOffline} under caller-supplied trust keys instead of
|
|
19
20
|
* the genesis trust root — see {@link verifyReceiptOfflineWithKeys} for the
|
|
@@ -31,6 +32,12 @@ export type VerifyReceiptOfflineInput = {
|
|
|
31
32
|
*/
|
|
32
33
|
payload: Uint8Array;
|
|
33
34
|
idtimestampBe8: Uint8Array;
|
|
35
|
+
/**
|
|
36
|
+
* ERC-1271 attestation for a KS256 contract root (plan-2610-03 D4). Used
|
|
37
|
+
* only when ecrecover doesn't match the root, and accepted only when it
|
|
38
|
+
* binds the root address, the certificate digest and the genesis chain id.
|
|
39
|
+
*/
|
|
40
|
+
ks256Attestation?: Ks256Attestation;
|
|
34
41
|
};
|
|
35
42
|
/**
|
|
36
43
|
* Offline verify of a receipt against the EXACT registered payload. The log
|
|
@@ -42,12 +49,19 @@ export type VerifyReceiptOfflineInput = {
|
|
|
42
49
|
export declare function verifyReceiptOffline(input: VerifyReceiptOfflineInput): Promise<ReceiptVerifyResult>;
|
|
43
50
|
export type VerifyReceiptOfflineWithKeysInput = Omit<VerifyReceiptOfflineInput, "genesisCbor"> & {
|
|
44
51
|
/**
|
|
45
|
-
* Caller-known
|
|
52
|
+
* Caller-known trust keys, tried in order: ES256 `CryptoKey`s or KS256
|
|
53
|
+
* 20-byte addresses ({@link knownLogKeyFromBytes}). For a child-log receipt
|
|
46
54
|
* this is the log OWNER's key (the delegation-cert issuer), NOT the sealer
|
|
47
55
|
* key — the label-1000 cert is verified under these keys and the delegated
|
|
48
56
|
* sealer key is extracted from it, so the anchor survives sealer rotation.
|
|
49
57
|
*/
|
|
50
|
-
trustKeys:
|
|
58
|
+
trustKeys: RootVerifyKey[];
|
|
59
|
+
/**
|
|
60
|
+
* The forest's genesis, read only for the chain id a `ks256Attestation`
|
|
61
|
+
* must match (D6); it supplies no trust keys. Without it an attestation is
|
|
62
|
+
* refused with `ks256_attestation_chain_unbound`.
|
|
63
|
+
*/
|
|
64
|
+
forestGenesisCbor?: Uint8Array;
|
|
51
65
|
};
|
|
52
66
|
/**
|
|
53
67
|
* {@link verifyReceiptOffline} under caller-supplied trust keys instead of the
|
|
@@ -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;
|
|
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;AAIjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAOhF,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;IAC3B,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC,CAAC;AA4EF;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAe9B;AAED,MAAM,MAAM,sCAAsC,GAAG,IAAI,CACvD,8BAA8B,EAC9B,aAAa,CACd,GACC,IAAI,CAAC,iCAAiC,EAAE,WAAW,GAAG,mBAAmB,CAAC,CAAC;AAE7E;;;;GAIG;AACH,wBAAsB,iCAAiC,CACrD,KAAK,EAAE,sCAAsC,GAC5C,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;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAc9B;AAED,MAAM,MAAM,iCAAiC,GAAG,IAAI,CAClD,yBAAyB,EACzB,aAAa,CACd,GAAG;IACF;;;;;;OAMG;IACH,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,UAAU,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,mBAAmB,CAAC,CAc9B"}
|
|
@@ -2,7 +2,8 @@ import { verifyCoseSign1WithParsedKey } from "@forestrie/encoding";
|
|
|
2
2
|
import { calculateRoot, verifyInclusion, } from "@forestrie/merklelog";
|
|
3
3
|
import { grantCommitmentHashFromGrant } from "./grant-commitment.js";
|
|
4
4
|
import { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
|
|
5
|
-
import {
|
|
5
|
+
import { decodeChainBindingFromGenesis } from "./decode-chain-binding-from-genesis.js";
|
|
6
|
+
import { isParsedKs256RootKey } from "./root-verify-key.js";
|
|
6
7
|
import { checkDelegationConstraints, resolveDelegatedVerifyKey, } from "./resolve-delegated-verify-key.js";
|
|
7
8
|
import { univocityLeafHash } from "./leaf-commitment.js";
|
|
8
9
|
import { parseReceipt } from "./parse-receipt.js";
|
|
@@ -72,6 +73,7 @@ export async function verifyGrantReceiptOffline(input) {
|
|
|
72
73
|
receiptCbor: input.receiptCbor,
|
|
73
74
|
idtimestampBe8: input.idtimestampBe8,
|
|
74
75
|
inner,
|
|
76
|
+
ks256Attestation: input.ks256Attestation,
|
|
75
77
|
});
|
|
76
78
|
}
|
|
77
79
|
/**
|
|
@@ -92,6 +94,7 @@ export async function verifyGrantReceiptOfflineWithKeys(input) {
|
|
|
92
94
|
idtimestampBe8: input.idtimestampBe8,
|
|
93
95
|
inner,
|
|
94
96
|
trustKeys: input.trustKeys,
|
|
97
|
+
ks256Evidence: withKeysKs256Evidence(input),
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
/**
|
|
@@ -108,6 +111,7 @@ export async function verifyReceiptOffline(input) {
|
|
|
108
111
|
receiptCbor: input.receiptCbor,
|
|
109
112
|
idtimestampBe8: input.idtimestampBe8,
|
|
110
113
|
inner,
|
|
114
|
+
ks256Attestation: input.ks256Attestation,
|
|
111
115
|
});
|
|
112
116
|
}
|
|
113
117
|
/**
|
|
@@ -134,8 +138,35 @@ export async function verifyReceiptOfflineWithKeys(input) {
|
|
|
134
138
|
idtimestampBe8: input.idtimestampBe8,
|
|
135
139
|
inner,
|
|
136
140
|
trustKeys: input.trustKeys,
|
|
141
|
+
ks256Evidence: withKeysKs256Evidence(input),
|
|
137
142
|
});
|
|
138
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* KS256 chain evidence for the caller-known-key entries: the attestation, and
|
|
146
|
+
* the chain id from the forest genesis the caller supplied, if any.
|
|
147
|
+
*/
|
|
148
|
+
function withKeysKs256Evidence(input) {
|
|
149
|
+
if (!input.ks256Attestation)
|
|
150
|
+
return {};
|
|
151
|
+
return {
|
|
152
|
+
attestation: input.ks256Attestation,
|
|
153
|
+
forestChainId: input.forestGenesisCbor
|
|
154
|
+
? genesisChainIdOrUndefined(input.forestGenesisCbor)
|
|
155
|
+
: undefined,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The forest's chain id from its genesis chain binding, or `undefined` when
|
|
160
|
+
* the genesis carries none (an attestation then can't be bound, D6).
|
|
161
|
+
*/
|
|
162
|
+
function genesisChainIdOrUndefined(genesisCbor) {
|
|
163
|
+
try {
|
|
164
|
+
return BigInt(decodeChainBindingFromGenesis(genesisCbor).chainId);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
139
170
|
/**
|
|
140
171
|
* Shared offline-verify core: reconstruct the leaf as
|
|
141
172
|
* `univocityLeafHash(idtimestamp, inner)`, resolve the (possibly delegated,
|
|
@@ -151,21 +182,25 @@ async function verifyReceiptOfflineWithLeafInner(input) {
|
|
|
151
182
|
catch {
|
|
152
183
|
return { ok: false, stage: "parse", reason: "genesis_invalid" };
|
|
153
184
|
}
|
|
154
|
-
const verifyKeys = es256ReceiptVerifyKeys([trustRoot]);
|
|
155
|
-
if (!verifyKeys.length) {
|
|
156
|
-
return { ok: false, stage: "signature", reason: "no_es256_trust_key" };
|
|
157
|
-
}
|
|
158
185
|
return verifyReceiptOfflineWithLeafInnerKeys({
|
|
159
186
|
receiptCbor: input.receiptCbor,
|
|
160
187
|
idtimestampBe8: input.idtimestampBe8,
|
|
161
188
|
inner: input.inner,
|
|
162
|
-
trustKeys:
|
|
189
|
+
trustKeys: [trustRoot],
|
|
190
|
+
ks256Evidence: input.ks256Attestation
|
|
191
|
+
? {
|
|
192
|
+
attestation: input.ks256Attestation,
|
|
193
|
+
forestChainId: genesisChainIdOrUndefined(input.genesisCbor),
|
|
194
|
+
}
|
|
195
|
+
: {},
|
|
163
196
|
});
|
|
164
197
|
}
|
|
165
198
|
/**
|
|
166
199
|
* Trust-key-parameterised offline-verify core. Identical to the genesis path
|
|
167
200
|
* once the trust keys are in hand: resolve the (possibly delegated, FOR-297)
|
|
168
|
-
* verify key against `trustKeys`, then check signature + inclusion.
|
|
201
|
+
* verify key against `trustKeys`, then check signature + inclusion. Receipts
|
|
202
|
+
* are always ES256, so a KS256 trust key only ever verifies the delegation
|
|
203
|
+
* certificate; only ES256 trust keys are tried on the receipt itself.
|
|
169
204
|
*/
|
|
170
205
|
async function verifyReceiptOfflineWithLeafInnerKeys(input) {
|
|
171
206
|
let parsed;
|
|
@@ -183,13 +218,27 @@ async function verifyReceiptOfflineWithLeafInnerKeys(input) {
|
|
|
183
218
|
// delegated key first. A cert present but not chaining to a trust key is a
|
|
184
219
|
// hard failure — do not silently fall back to the trust key (which cannot
|
|
185
220
|
// verify a delegated-signed receipt anyway).
|
|
186
|
-
const delegation = await resolveDelegatedVerifyKey(input.receiptCbor, input.trustKeys);
|
|
221
|
+
const delegation = await resolveDelegatedVerifyKey(input.receiptCbor, input.trustKeys, input.ks256Evidence);
|
|
187
222
|
if (delegation.kind === "broken") {
|
|
188
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
stage: "signature",
|
|
226
|
+
reason: delegation.reason ?? "delegation_invalid",
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const es256TrustKeys = input.trustKeys.filter((k) => !isParsedKs256RootKey(k));
|
|
230
|
+
if (delegation.kind === "root-only" && !es256TrustKeys.length) {
|
|
231
|
+
// A KS256 root never signs a receipt; without a certificate there is no
|
|
232
|
+
// ES256 key to check it with.
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
stage: "signature",
|
|
236
|
+
reason: "ks256_root_requires_delegation",
|
|
237
|
+
};
|
|
189
238
|
}
|
|
190
239
|
const allVerifyKeys = delegation.kind === "resolved"
|
|
191
|
-
? [delegation.delegatedKey, ...
|
|
192
|
-
:
|
|
240
|
+
? [delegation.delegatedKey, ...es256TrustKeys]
|
|
241
|
+
: es256TrustKeys;
|
|
193
242
|
let idtimestamp;
|
|
194
243
|
try {
|
|
195
244
|
idtimestamp = readIdtimestampBe8(input.idtimestampBe8);
|