@forestrie/receipt-verify 0.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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cose-key.d.ts +10 -0
  3. package/dist/cose-key.d.ts.map +1 -0
  4. package/dist/cose-key.js +9 -0
  5. package/dist/decode-forestrie-grant-cose.d.ts +9 -0
  6. package/dist/decode-forestrie-grant-cose.d.ts.map +1 -0
  7. package/dist/decode-forestrie-grant-cose.js +62 -0
  8. package/dist/decode-trust-root-cbor.d.ts +6 -0
  9. package/dist/decode-trust-root-cbor.d.ts.map +1 -0
  10. package/dist/decode-trust-root-cbor.js +85 -0
  11. package/dist/decode-trust-root-from-genesis.d.ts +7 -0
  12. package/dist/decode-trust-root-from-genesis.d.ts.map +1 -0
  13. package/dist/decode-trust-root-from-genesis.js +64 -0
  14. package/dist/forest-genesis-labels.d.ts +10 -0
  15. package/dist/forest-genesis-labels.d.ts.map +1 -0
  16. package/dist/forest-genesis-labels.js +9 -0
  17. package/dist/grant-codec.d.ts +8 -0
  18. package/dist/grant-codec.d.ts.map +1 -0
  19. package/dist/grant-codec.js +88 -0
  20. package/dist/grant-commitment.d.ts +3 -0
  21. package/dist/grant-commitment.d.ts.map +1 -0
  22. package/dist/grant-commitment.js +55 -0
  23. package/dist/grant-data.d.ts +7 -0
  24. package/dist/grant-data.d.ts.map +1 -0
  25. package/dist/grant-data.js +7 -0
  26. package/dist/grant.d.ts +12 -0
  27. package/dist/grant.d.ts.map +1 -0
  28. package/dist/grant.js +1 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +5 -0
  32. package/dist/leaf-commitment.d.ts +2 -0
  33. package/dist/leaf-commitment.d.ts.map +1 -0
  34. package/dist/leaf-commitment.js +27 -0
  35. package/dist/parse-receipt.d.ts +15 -0
  36. package/dist/parse-receipt.d.ts.map +1 -0
  37. package/dist/parse-receipt.js +91 -0
  38. package/dist/receipt-verify-result.d.ts +9 -0
  39. package/dist/receipt-verify-result.d.ts.map +1 -0
  40. package/dist/receipt-verify-result.js +1 -0
  41. package/dist/root-verify-key.d.ts +9 -0
  42. package/dist/root-verify-key.d.ts.map +1 -0
  43. package/dist/root-verify-key.js +7 -0
  44. package/dist/subtle-hasher.d.ts +9 -0
  45. package/dist/subtle-hasher.d.ts.map +1 -0
  46. package/dist/subtle-hasher.js +21 -0
  47. package/dist/uuid-bytes.d.ts +4 -0
  48. package/dist/uuid-bytes.d.ts.map +1 -0
  49. package/dist/uuid-bytes.js +21 -0
  50. package/dist/verify-grant-receipt-offline.d.ts +13 -0
  51. package/dist/verify-grant-receipt-offline.d.ts.map +1 -0
  52. package/dist/verify-grant-receipt-offline.js +101 -0
  53. package/package.json +45 -0
  54. package/src/cose-key.ts +9 -0
  55. package/src/decode-forestrie-grant-cose.ts +79 -0
  56. package/src/decode-trust-root-cbor.ts +113 -0
  57. package/src/decode-trust-root-from-genesis.ts +87 -0
  58. package/src/forest-genesis-labels.ts +10 -0
  59. package/src/grant-codec.ts +106 -0
  60. package/src/grant-commitment.ts +64 -0
  61. package/src/grant-data.ts +12 -0
  62. package/src/grant.ts +12 -0
  63. package/src/index.ts +11 -0
  64. package/src/leaf-commitment.ts +35 -0
  65. package/src/parse-receipt.ts +120 -0
  66. package/src/receipt-verify-result.ts +13 -0
  67. package/src/root-verify-key.ts +19 -0
  68. package/src/subtle-hasher.ts +26 -0
  69. package/src/uuid-bytes.ts +20 -0
  70. package/src/verify-grant-receipt-offline.ts +145 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robin Bryce
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ export declare const COSE_ALG_ES256 = -7;
2
+ export declare const COSE_ALG_KS256 = -65799;
3
+ export declare const COSE_KEY_KTY = 1;
4
+ export declare const COSE_KEY_ALG = 3;
5
+ export declare const COSE_EC2_CRV = -1;
6
+ export declare const COSE_EC2_X = -2;
7
+ export declare const COSE_EC2_Y = -3;
8
+ export declare const COSE_KTY_EC2 = 2;
9
+ export declare const COSE_CRV_P256 = 1;
10
+ //# sourceMappingURL=cose-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cose-key.d.ts","sourceRoot":"","sources":["../src/cose-key.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,YAAY,KAAK,CAAC;AAC/B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,aAAa,IAAI,CAAC"}
@@ -0,0 +1,9 @@
1
+ export const COSE_ALG_ES256 = -7;
2
+ export const COSE_ALG_KS256 = -65799;
3
+ export const COSE_KEY_KTY = 1;
4
+ export const COSE_KEY_ALG = 3;
5
+ export const COSE_EC2_CRV = -1;
6
+ export const COSE_EC2_X = -2;
7
+ export const COSE_EC2_Y = -3;
8
+ export const COSE_KTY_EC2 = 2;
9
+ export const COSE_CRV_P256 = 1;
@@ -0,0 +1,9 @@
1
+ import type { Grant } from "./grant.js";
2
+ /**
3
+ * Decode Forestrie-Grant COSE Sign1 (Custodian transparent statement profile).
4
+ */
5
+ export declare function decodeForestrieGrantCose(bytes: Uint8Array): {
6
+ grant: Grant;
7
+ idtimestampBe8: Uint8Array;
8
+ };
9
+ //# sourceMappingURL=decode-forestrie-grant-cose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode-forestrie-grant-cose.d.ts","sourceRoot":"","sources":["../src/decode-forestrie-grant-cose.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAmCxC;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,GAAG;IAC3D,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,UAAU,CAAC;CAC5B,CAmCA"}
@@ -0,0 +1,62 @@
1
+ import { sha256 } from "@noble/hashes/sha256";
2
+ import { decode as decodeCbor } from "cbor-x";
3
+ import { HEADER_FORESTRIE_GRANT_V0, HEADER_IDTIMESTAMP, } from "./forest-genesis-labels.js";
4
+ import { decodeGrantPayload } from "./grant-codec.js";
5
+ const IDTIMESTAMP_BYTES = 8;
6
+ function toHeaderMap(value) {
7
+ if (value instanceof Map)
8
+ return value;
9
+ if (typeof value === "object" &&
10
+ value !== null &&
11
+ !(value instanceof Uint8Array)) {
12
+ const out = new Map();
13
+ for (const [k, v] of Object.entries(value)) {
14
+ const n = Number(k);
15
+ if (Number.isFinite(n))
16
+ out.set(n, v);
17
+ }
18
+ return out;
19
+ }
20
+ return new Map();
21
+ }
22
+ function digestEquals(a, b) {
23
+ if (a.length !== b.length)
24
+ return false;
25
+ let x = 0;
26
+ for (let i = 0; i < a.length; i++)
27
+ x |= a[i] ^ b[i];
28
+ return x === 0;
29
+ }
30
+ /**
31
+ * Decode Forestrie-Grant COSE Sign1 (Custodian transparent statement profile).
32
+ */
33
+ export function decodeForestrieGrantCose(bytes) {
34
+ const raw = decodeCbor(bytes);
35
+ const arr = Array.isArray(raw) ? raw : null;
36
+ if (!arr || arr.length !== 4) {
37
+ throw new Error("Forestrie-Grant must be COSE Sign1 (array of 4)");
38
+ }
39
+ const [, unprotectedRaw, payloadRaw] = arr;
40
+ if (!(payloadRaw instanceof Uint8Array) || payloadRaw.length !== 32) {
41
+ throw new Error("Forestrie-Grant COSE payload must be 32-byte digest");
42
+ }
43
+ const unprotected = toHeaderMap(unprotectedRaw);
44
+ const embedded = unprotected.get(HEADER_FORESTRIE_GRANT_V0);
45
+ if (!(embedded instanceof Uint8Array) || embedded.length === 0) {
46
+ throw new Error("Forestrie-Grant missing unprotected -65538 grant CBOR");
47
+ }
48
+ if (!digestEquals(sha256(embedded), payloadRaw)) {
49
+ throw new Error("Forestrie-Grant digest mismatch");
50
+ }
51
+ const idtimestampVal = unprotected.get(HEADER_IDTIMESTAMP);
52
+ const idtimestampBe8 = idtimestampVal instanceof Uint8Array &&
53
+ idtimestampVal.length >= IDTIMESTAMP_BYTES
54
+ ? idtimestampVal.length === IDTIMESTAMP_BYTES
55
+ ? idtimestampVal
56
+ : idtimestampVal.slice(-IDTIMESTAMP_BYTES)
57
+ : new Uint8Array(IDTIMESTAMP_BYTES);
58
+ return {
59
+ grant: decodeGrantPayload(embedded),
60
+ idtimestampBe8: new Uint8Array(idtimestampBe8),
61
+ };
62
+ }
@@ -0,0 +1,6 @@
1
+ import type { ParsedVerifyKey } from "@forestrie/encoding";
2
+ import { type RootVerifyKey } from "./root-verify-key.js";
3
+ export declare function importEs256PublicKeyFromGrantDataXy64(xy: Uint8Array): Promise<CryptoKey>;
4
+ export declare function decodeTrustRootCbor(decoded: unknown): Promise<RootVerifyKey>;
5
+ export declare function es256ReceiptVerifyKeys(keys: RootVerifyKey[]): ParsedVerifyKey[];
6
+ //# sourceMappingURL=decode-trust-root-cbor.d.ts.map
@@ -0,0 +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,EAGL,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,wBAAsB,qCAAqC,CACzD,EAAE,EAAE,UAAU,GACb,OAAO,CAAC,SAAS,CAAC,CAgBpB;AAsCD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,aAAa,CAAC,CAwCxB;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,aAAa,EAAE,GACpB,eAAe,EAAE,CAEnB"}
@@ -0,0 +1,85 @@
1
+ import { COSE_ALG_ES256, COSE_ALG_KS256 } from "@forestrie/encoding";
2
+ import { isParsedKs256RootKey, } from "./root-verify-key.js";
3
+ export async function importEs256PublicKeyFromGrantDataXy64(xy) {
4
+ if (xy.length !== 64) {
5
+ throw new Error("ES256 grantData must be 64 bytes (x||y) for raw public key import");
6
+ }
7
+ const raw = new Uint8Array(65);
8
+ raw[0] = 0x04;
9
+ raw.set(xy, 1);
10
+ return crypto.subtle.importKey("raw", raw, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
11
+ }
12
+ function asUint8Array(v) {
13
+ if (v instanceof Uint8Array)
14
+ return v;
15
+ return null;
16
+ }
17
+ function parseAlgInt(raw) {
18
+ if (typeof raw === "number" && Number.isInteger(raw))
19
+ return raw;
20
+ if (typeof raw === "bigint") {
21
+ const n = Number(raw);
22
+ return Number.isSafeInteger(n) ? n : null;
23
+ }
24
+ if (typeof raw === "string") {
25
+ const n = Number(raw);
26
+ if (Number.isInteger(n))
27
+ return n;
28
+ }
29
+ return null;
30
+ }
31
+ function ks256RootFromAddress(address) {
32
+ return { kind: "KS256", alg: COSE_ALG_KS256, address };
33
+ }
34
+ function recordToTrustRootFields(decoded) {
35
+ if (decoded instanceof Map) {
36
+ const out = {};
37
+ for (const [k, v] of decoded.entries()) {
38
+ out[String(k)] = v;
39
+ }
40
+ return out;
41
+ }
42
+ if (typeof decoded === "object" && decoded !== null) {
43
+ return decoded;
44
+ }
45
+ throw new Error("trust-root CBOR must be a map");
46
+ }
47
+ export async function decodeTrustRootCbor(decoded) {
48
+ const fields = recordToTrustRootFields(decoded);
49
+ const algRaw = fields.alg;
50
+ const key = asUint8Array(fields.key);
51
+ const algInt = parseAlgInt(algRaw);
52
+ if (algInt === COSE_ALG_KS256) {
53
+ if (!key || key.length !== 20) {
54
+ throw new Error("KS256 trust-root key must be 20 bytes");
55
+ }
56
+ return ks256RootFromAddress(key);
57
+ }
58
+ if (algInt === COSE_ALG_ES256 && key) {
59
+ if (key.length !== 64) {
60
+ throw new Error("ES256 trust-root key must be 64 bytes (x||y)");
61
+ }
62
+ return importEs256PublicKeyFromGrantDataXy64(key);
63
+ }
64
+ if (typeof algRaw === "string" && algRaw.toUpperCase() === "KS256") {
65
+ if (!key || key.length !== 20) {
66
+ throw new Error("KS256 trust-root key must be 20 bytes");
67
+ }
68
+ return ks256RootFromAddress(key);
69
+ }
70
+ if (typeof algRaw === "string" && algRaw.toUpperCase() === "ES256") {
71
+ const x = asUint8Array(fields.x);
72
+ const y = asUint8Array(fields.y);
73
+ if (!x || x.length !== 32 || !y || y.length !== 32) {
74
+ throw new Error("ES256 trust-root x/y must be 32 bytes each");
75
+ }
76
+ const xy = new Uint8Array(64);
77
+ xy.set(x, 0);
78
+ xy.set(y, 32);
79
+ return importEs256PublicKeyFromGrantDataXy64(xy);
80
+ }
81
+ throw new Error(`unsupported trust-root alg ${String(algRaw)}`);
82
+ }
83
+ export function es256ReceiptVerifyKeys(keys) {
84
+ return keys.filter((k) => !isParsedKs256RootKey(k));
85
+ }
@@ -0,0 +1,7 @@
1
+ import type { RootVerifyKey } from "./root-verify-key.js";
2
+ /**
3
+ * Extract receipt verify key from a forest genesis document CBOR blob.
4
+ * Offline path: genesis-only trust anchor (ADR-0045).
5
+ */
6
+ export declare function decodeTrustRootFromGenesis(genesisCbor: Uint8Array): Promise<RootVerifyKey>;
7
+ //# sourceMappingURL=decode-trust-root-from-genesis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode-trust-root-from-genesis.d.ts","sourceRoot":"","sources":["../src/decode-trust-root-from-genesis.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAmB1D;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,UAAU,GACtB,OAAO,CAAC,aAAa,CAAC,CA0CxB"}
@@ -0,0 +1,64 @@
1
+ import { decode as decodeCbor } from "cbor-x";
2
+ import { COSE_ALG_ES256, COSE_CRV_P256, COSE_EC2_CRV, COSE_EC2_X, COSE_EC2_Y, COSE_KEY_ALG, COSE_KEY_KTY, COSE_KTY_EC2, } from "./cose-key.js";
3
+ import { FOREST_GENESIS_LABEL_BOOTSTRAP_KEY, FOREST_GENESIS_LABEL_GENESIS_ALG, FOREST_GENESIS_LABEL_GENESIS_VERSION, FOREST_GENESIS_SCHEMA_V1, FOREST_GENESIS_SCHEMA_V2, } from "./forest-genesis-labels.js";
4
+ import { decodeTrustRootCbor } from "./decode-trust-root-cbor.js";
5
+ function decodeBodyAsIntKeyMap(raw) {
6
+ if (raw instanceof Map)
7
+ return raw;
8
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
9
+ const out = new Map();
10
+ for (const [k, v] of Object.entries(raw)) {
11
+ const n = Number(k);
12
+ if (Number.isFinite(n))
13
+ out.set(n, v);
14
+ }
15
+ return out;
16
+ }
17
+ return null;
18
+ }
19
+ function asGenesisUint8Array(v) {
20
+ return v instanceof Uint8Array ? v : null;
21
+ }
22
+ /**
23
+ * Extract receipt verify key from a forest genesis document CBOR blob.
24
+ * Offline path: genesis-only trust anchor (ADR-0045).
25
+ */
26
+ export async function decodeTrustRootFromGenesis(genesisCbor) {
27
+ let raw;
28
+ try {
29
+ raw = decodeCbor(genesisCbor);
30
+ }
31
+ catch {
32
+ throw new Error("genesis CBOR decode failed");
33
+ }
34
+ const m = decodeBodyAsIntKeyMap(raw);
35
+ if (!m)
36
+ throw new Error("genesis document must be a CBOR map");
37
+ const versionRaw = m.get(FOREST_GENESIS_LABEL_GENESIS_VERSION);
38
+ if (versionRaw === FOREST_GENESIS_SCHEMA_V2) {
39
+ const alg = m.get(FOREST_GENESIS_LABEL_GENESIS_ALG);
40
+ const bootstrapKey = asGenesisUint8Array(m.get(FOREST_GENESIS_LABEL_BOOTSTRAP_KEY));
41
+ if (bootstrapKey === null) {
42
+ throw new Error("v2 genesis missing bootstrapKey");
43
+ }
44
+ return decodeTrustRootCbor({ alg, key: bootstrapKey });
45
+ }
46
+ const kty = m.get(COSE_KEY_KTY);
47
+ const crv = m.get(COSE_EC2_CRV);
48
+ const x = asGenesisUint8Array(m.get(COSE_EC2_X));
49
+ const y = asGenesisUint8Array(m.get(COSE_EC2_Y));
50
+ if (kty === COSE_KTY_EC2 && crv === COSE_CRV_P256 && x && y) {
51
+ const alg = m.get(COSE_KEY_ALG);
52
+ if (alg !== undefined && alg !== COSE_ALG_ES256) {
53
+ throw new Error("genesis EC2 key must be ES256");
54
+ }
55
+ const xy = new Uint8Array(64);
56
+ xy.set(x, 0);
57
+ xy.set(y, 32);
58
+ return decodeTrustRootCbor({ alg: COSE_ALG_ES256, key: xy });
59
+ }
60
+ if (versionRaw === FOREST_GENESIS_SCHEMA_V1 || versionRaw === undefined) {
61
+ throw new Error("unsupported or invalid genesis schema for trust root");
62
+ }
63
+ throw new Error("unsupported genesis document");
64
+ }
@@ -0,0 +1,10 @@
1
+ export declare const FOREST_GENESIS_LABEL_GENESIS_VERSION = -68009;
2
+ export declare const FOREST_GENESIS_LABEL_GENESIS_ALG = -68014;
3
+ export declare const FOREST_GENESIS_LABEL_BOOTSTRAP_KEY = -68015;
4
+ export declare const FOREST_GENESIS_LABEL_UNIVOCITY_ADDR = -68011;
5
+ export declare const FOREST_GENESIS_LABEL_CHAIN_ID = -68013;
6
+ export declare const FOREST_GENESIS_SCHEMA_V2 = 2;
7
+ export declare const FOREST_GENESIS_SCHEMA_V1 = 1;
8
+ export declare const HEADER_IDTIMESTAMP = -65537;
9
+ export declare const HEADER_FORESTRIE_GRANT_V0 = -65538;
10
+ //# sourceMappingURL=forest-genesis-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forest-genesis-labels.d.ts","sourceRoot":"","sources":["../src/forest-genesis-labels.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oCAAoC,SAAS,CAAC;AAC3D,eAAO,MAAM,gCAAgC,SAAS,CAAC;AACvD,eAAO,MAAM,kCAAkC,SAAS,CAAC;AACzD,eAAO,MAAM,mCAAmC,SAAS,CAAC;AAC1D,eAAO,MAAM,6BAA6B,SAAS,CAAC;AACpD,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,kBAAkB,SAAS,CAAC;AACzC,eAAO,MAAM,yBAAyB,SAAS,CAAC"}
@@ -0,0 +1,9 @@
1
+ export const FOREST_GENESIS_LABEL_GENESIS_VERSION = -68009;
2
+ export const FOREST_GENESIS_LABEL_GENESIS_ALG = -68014;
3
+ export const FOREST_GENESIS_LABEL_BOOTSTRAP_KEY = -68015;
4
+ export const FOREST_GENESIS_LABEL_UNIVOCITY_ADDR = -68011;
5
+ export const FOREST_GENESIS_LABEL_CHAIN_ID = -68013;
6
+ export const FOREST_GENESIS_SCHEMA_V2 = 2;
7
+ export const FOREST_GENESIS_SCHEMA_V1 = 1;
8
+ export const HEADER_IDTIMESTAMP = -65537;
9
+ export const HEADER_FORESTRIE_GRANT_V0 = -65538;
@@ -0,0 +1,8 @@
1
+ import type { Grant } from "./grant.js";
2
+ export declare function decodeGrantPayload(bytes: Uint8Array): Grant;
3
+ export declare function decodeGrantResponse(bytes: Uint8Array): {
4
+ grant: Grant;
5
+ idtimestamp: Uint8Array;
6
+ };
7
+ export declare function encodeGrantPayload(grant: Grant): Uint8Array;
8
+ //# sourceMappingURL=grant-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant-codec.d.ts","sourceRoot":"","sources":["../src/grant-codec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAuDxC,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAO3D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG;IACtD,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,UAAU,CAAC;CACzB,CAsBA;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU,CAa3D"}
@@ -0,0 +1,88 @@
1
+ import { decode as decodeCbor, encode as encodeCbor } from "cbor-x";
2
+ import { grantDataToBytes } from "./grant-data.js";
3
+ import { fromPaddedWire32, toPaddedWire32 } from "./uuid-bytes.js";
4
+ const CBOR_KEY_IDTIMESTAMP = 0;
5
+ const CBOR_KEY_LOG_ID = 1;
6
+ const CBOR_KEY_OWNER_LOG_ID = 2;
7
+ const CBOR_KEY_GRANT_FLAGS = 3;
8
+ const CBOR_KEY_MAX_HEIGHT = 4;
9
+ const CBOR_KEY_MIN_GROWTH = 5;
10
+ const CBOR_KEY_GRANT_DATA = 6;
11
+ const WIRE_GRANT_FLAGS_BYTES = 8;
12
+ const IDTIMESTAMP_BYTES = 8;
13
+ function leftPad(b, length) {
14
+ if (b.length >= length) {
15
+ return b.length === length ? b : b.slice(-length);
16
+ }
17
+ const out = new Uint8Array(length);
18
+ out.set(b, length - b.length);
19
+ return out;
20
+ }
21
+ function mapToGrant(m) {
22
+ const get = (k) => m instanceof Map ? m.get(k) : m[k];
23
+ const requireBstr = (v, minLen = 0) => {
24
+ if (!(v instanceof Uint8Array))
25
+ throw new Error("Grant payload: value must be bstr");
26
+ if (v.length < minLen)
27
+ throw new Error("Grant payload: bstr too short");
28
+ return v;
29
+ };
30
+ const requireUint = (v) => {
31
+ if (typeof v === "number" && Number.isInteger(v))
32
+ return v;
33
+ if (typeof v === "bigint")
34
+ return Number(v);
35
+ throw new Error("Grant payload: expected uint");
36
+ };
37
+ return {
38
+ logId: fromPaddedWire32(requireBstr(get(CBOR_KEY_LOG_ID))),
39
+ ownerLogId: fromPaddedWire32(requireBstr(get(CBOR_KEY_OWNER_LOG_ID))),
40
+ grant: leftPad(requireBstr(get(CBOR_KEY_GRANT_FLAGS)), WIRE_GRANT_FLAGS_BYTES),
41
+ maxHeight: requireUint(get(CBOR_KEY_MAX_HEIGHT)),
42
+ minGrowth: requireUint(get(CBOR_KEY_MIN_GROWTH)),
43
+ grantData: get(CBOR_KEY_GRANT_DATA) instanceof Uint8Array
44
+ ? get(CBOR_KEY_GRANT_DATA)
45
+ : new Uint8Array(0),
46
+ };
47
+ }
48
+ export function decodeGrantPayload(bytes) {
49
+ if (!bytes?.length)
50
+ throw new Error("Grant payload is empty");
51
+ const raw = decodeCbor(bytes);
52
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
53
+ throw new Error("Grant payload must be a CBOR map");
54
+ }
55
+ return mapToGrant(raw);
56
+ }
57
+ export function decodeGrantResponse(bytes) {
58
+ const raw = decodeCbor(bytes);
59
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
60
+ throw new Error("Grant response must be a CBOR map");
61
+ }
62
+ const m = raw;
63
+ const get = (k) => m instanceof Map ? m.get(k) : m[k];
64
+ const idtimestampVal = get(CBOR_KEY_IDTIMESTAMP);
65
+ if (!(idtimestampVal instanceof Uint8Array) ||
66
+ idtimestampVal.length < IDTIMESTAMP_BYTES) {
67
+ throw new Error("Grant response: key 0 must be 8-byte bstr");
68
+ }
69
+ const idtimestamp = new Uint8Array(IDTIMESTAMP_BYTES);
70
+ idtimestamp.set(idtimestampVal.length === IDTIMESTAMP_BYTES
71
+ ? idtimestampVal
72
+ : idtimestampVal.slice(-IDTIMESTAMP_BYTES));
73
+ return { grant: mapToGrant(m), idtimestamp };
74
+ }
75
+ export function encodeGrantPayload(grant) {
76
+ const map = new Map([
77
+ [CBOR_KEY_LOG_ID, toPaddedWire32(grant.logId)],
78
+ [CBOR_KEY_OWNER_LOG_ID, toPaddedWire32(grant.ownerLogId)],
79
+ [CBOR_KEY_GRANT_FLAGS, leftPad(grant.grant, WIRE_GRANT_FLAGS_BYTES)],
80
+ [CBOR_KEY_MAX_HEIGHT, grant.maxHeight ?? 0],
81
+ [CBOR_KEY_MIN_GROWTH, grant.minGrowth ?? 0],
82
+ [
83
+ CBOR_KEY_GRANT_DATA,
84
+ grantDataToBytes(grant.grantData ?? new Uint8Array(0)),
85
+ ],
86
+ ]);
87
+ return new Uint8Array(encodeCbor(map));
88
+ }
@@ -0,0 +1,3 @@
1
+ import type { Grant } from "./grant.js";
2
+ export declare function grantCommitmentHashFromGrant(grant: Grant): Promise<Uint8Array>;
3
+ //# sourceMappingURL=grant-commitment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant-commitment.d.ts","sourceRoot":"","sources":["../src/grant-commitment.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAwDxC,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,UAAU,CAAC,CAIrB"}
@@ -0,0 +1,55 @@
1
+ import { grantDataToBytes } from "./grant-data.js";
2
+ import { toPaddedWire32 } from "./uuid-bytes.js";
3
+ const GRANT_FLAGS_32_BYTES = 32;
4
+ const U64_BYTES = 8;
5
+ function u64Be(n) {
6
+ const out = new Uint8Array(U64_BYTES);
7
+ let v = BigInt(n) & 0xffffffffffffffffn;
8
+ for (let i = 7; i >= 0; i--) {
9
+ out[i] = Number(v & 0xffn);
10
+ v >>= 8n;
11
+ }
12
+ return out;
13
+ }
14
+ function grantFlags32(flags) {
15
+ const out = new Uint8Array(GRANT_FLAGS_32_BYTES);
16
+ if (flags.length >= 8) {
17
+ out.set(flags.slice(-8), 24);
18
+ }
19
+ else if (flags.length > 0) {
20
+ out.set(flags, 32 - flags.length);
21
+ }
22
+ return out;
23
+ }
24
+ function grantCommitmentPreimage(grant) {
25
+ const logId = toPaddedWire32(grant.logId);
26
+ const flags32 = grantFlags32(grant.grant);
27
+ const maxHeight = grant.maxHeight ?? 0;
28
+ const minGrowth = grant.minGrowth ?? 0;
29
+ const ownerLogId = toPaddedWire32(grant.ownerLogId);
30
+ const grantData = grantDataToBytes(grant.grantData ?? new Uint8Array(0));
31
+ const total = logId.length +
32
+ flags32.length +
33
+ U64_BYTES * 2 +
34
+ ownerLogId.length +
35
+ grantData.length;
36
+ const out = new Uint8Array(total);
37
+ let off = 0;
38
+ out.set(logId, off);
39
+ off += logId.length;
40
+ out.set(flags32, off);
41
+ off += flags32.length;
42
+ out.set(u64Be(maxHeight), off);
43
+ off += U64_BYTES;
44
+ out.set(u64Be(minGrowth), off);
45
+ off += U64_BYTES;
46
+ out.set(ownerLogId, off);
47
+ off += ownerLogId.length;
48
+ out.set(grantData, off);
49
+ return out;
50
+ }
51
+ export async function grantCommitmentHashFromGrant(grant) {
52
+ const preimage = grantCommitmentPreimage(grant);
53
+ const hash = await crypto.subtle.digest("SHA-256", preimage);
54
+ return new Uint8Array(hash);
55
+ }
@@ -0,0 +1,7 @@
1
+ export interface GrantDataEs256Xy {
2
+ readonly kind: "es256-xy";
3
+ readonly xy: Uint8Array;
4
+ }
5
+ export type GrantData = Uint8Array | GrantDataEs256Xy;
6
+ export declare function grantDataToBytes(data: Uint8Array | GrantData): Uint8Array;
7
+ //# sourceMappingURL=grant-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant-data.d.ts","sourceRoot":"","sources":["../src/grant-data.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAEtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAIzE"}
@@ -0,0 +1,7 @@
1
+ export function grantDataToBytes(data) {
2
+ if (data instanceof Uint8Array)
3
+ return data;
4
+ if (data.kind === "es256-xy")
5
+ return data.xy;
6
+ throw new Error("Unsupported GrantData variant");
7
+ }
@@ -0,0 +1,12 @@
1
+ import type { GrantData } from "./grant-data.js";
2
+ /** On-chain grant content aligned with Univocity `PublishGrant`. */
3
+ export interface Grant {
4
+ logId: Uint8Array;
5
+ grant: Uint8Array;
6
+ request?: bigint;
7
+ maxHeight?: number;
8
+ minGrowth?: number;
9
+ ownerLogId: Uint8Array;
10
+ grantData: Uint8Array | GrantData;
11
+ }
12
+ //# sourceMappingURL=grant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grant.d.ts","sourceRoot":"","sources":["../src/grant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,oEAAoE;AACpE,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,UAAU,GAAG,SAAS,CAAC;CACnC"}
package/dist/grant.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ export type { ReceiptVerifyResult, ReceiptVerifyStage, } from "./receipt-verify-result.js";
2
+ export type { Grant } from "./grant.js";
3
+ export type { VerifyGrantReceiptOfflineInput } from "./verify-grant-receipt-offline.js";
4
+ export { parseReceipt } from "./parse-receipt.js";
5
+ export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
6
+ export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
7
+ export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
8
+ export { decodeGrantPayload, decodeGrantResponse } from "./grant-codec.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,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"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { parseReceipt } from "./parse-receipt.js";
2
+ export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
3
+ export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
4
+ export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
5
+ export { decodeGrantPayload, decodeGrantResponse } from "./grant-codec.js";
@@ -0,0 +1,2 @@
1
+ export declare function univocityLeafHash(idtimestamp: bigint | Uint8Array, grantCommitmentHash: Uint8Array): Promise<Uint8Array>;
2
+ //# sourceMappingURL=leaf-commitment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leaf-commitment.d.ts","sourceRoot":"","sources":["../src/leaf-commitment.ts"],"names":[],"mappings":"AAUA,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,GAAG,UAAU,EAChC,mBAAmB,EAAE,UAAU,GAC9B,OAAO,CAAC,UAAU,CAAC,CAqBrB"}
@@ -0,0 +1,27 @@
1
+ const IDTIMESTAMP_BYTES = 8;
2
+ function writeU64BE(out, offset, value) {
3
+ let v = value & 0xffffffffffffffffn;
4
+ for (let i = 7; i >= 0; i--) {
5
+ out[offset + i] = Number(v & 0xffn);
6
+ v >>= 8n;
7
+ }
8
+ }
9
+ export async function univocityLeafHash(idtimestamp, grantCommitmentHash) {
10
+ const idtimestampBytes = new Uint8Array(IDTIMESTAMP_BYTES);
11
+ if (typeof idtimestamp === "bigint") {
12
+ writeU64BE(idtimestampBytes, 0, idtimestamp);
13
+ }
14
+ else {
15
+ if (idtimestamp.length < IDTIMESTAMP_BYTES) {
16
+ throw new Error("idtimestamp must be at least 8 bytes");
17
+ }
18
+ idtimestampBytes.set(idtimestamp.length > IDTIMESTAMP_BYTES
19
+ ? idtimestamp.slice(-IDTIMESTAMP_BYTES)
20
+ : idtimestamp);
21
+ }
22
+ const preimage = new Uint8Array(idtimestampBytes.length + grantCommitmentHash.length);
23
+ preimage.set(idtimestampBytes);
24
+ preimage.set(grantCommitmentHash, idtimestampBytes.length);
25
+ const hash = await crypto.subtle.digest("SHA-256", preimage);
26
+ return new Uint8Array(hash);
27
+ }
@@ -0,0 +1,15 @@
1
+ import type { Proof } from "@forestrie/merklelog";
2
+ type CoseSign1 = [
3
+ protectedHeader: Uint8Array,
4
+ unprotectedHeader: Map<number, unknown> | Record<string, unknown>,
5
+ payload: Uint8Array | null,
6
+ signature: Uint8Array
7
+ ];
8
+ export declare function parseReceipt(receiptBytes: Uint8Array): {
9
+ explicitPeak: Uint8Array | null;
10
+ proof: Proof;
11
+ receiptCbor: Uint8Array;
12
+ coseSign1: CoseSign1;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=parse-receipt.d.ts.map
@@ -0,0 +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,KAAK,SAAS,GAAG;IACf,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;AAgDF,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"}