@passportsign/core 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 (84) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +36 -0
  3. package/dist/badge.d.ts +37 -0
  4. package/dist/badge.d.ts.map +1 -0
  5. package/dist/badge.js +94 -0
  6. package/dist/badge.js.map +1 -0
  7. package/dist/bind.d.ts +61 -0
  8. package/dist/bind.d.ts.map +1 -0
  9. package/dist/bind.js +79 -0
  10. package/dist/bind.js.map +1 -0
  11. package/dist/bundle.d.ts +47 -0
  12. package/dist/bundle.d.ts.map +1 -0
  13. package/dist/bundle.js +95 -0
  14. package/dist/bundle.js.map +1 -0
  15. package/dist/canonical.d.ts +19 -0
  16. package/dist/canonical.d.ts.map +1 -0
  17. package/dist/canonical.js +30 -0
  18. package/dist/canonical.js.map +1 -0
  19. package/dist/dsse.d.ts +55 -0
  20. package/dist/dsse.d.ts.map +1 -0
  21. package/dist/dsse.js +64 -0
  22. package/dist/dsse.js.map +1 -0
  23. package/dist/errors.d.ts +17 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +33 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/github.d.ts +28 -0
  28. package/dist/github.d.ts.map +1 -0
  29. package/dist/github.js +113 -0
  30. package/dist/github.js.map +1 -0
  31. package/dist/index.d.ts +22 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +27 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/log/rekor.d.ts +91 -0
  36. package/dist/log/rekor.d.ts.map +1 -0
  37. package/dist/log/rekor.js +218 -0
  38. package/dist/log/rekor.js.map +1 -0
  39. package/dist/merkle.d.ts +37 -0
  40. package/dist/merkle.d.ts.map +1 -0
  41. package/dist/merkle.js +160 -0
  42. package/dist/merkle.js.map +1 -0
  43. package/dist/nonce.d.ts +24 -0
  44. package/dist/nonce.d.ts.map +1 -0
  45. package/dist/nonce.js +50 -0
  46. package/dist/nonce.js.map +1 -0
  47. package/dist/sdk-payload.d.ts +33 -0
  48. package/dist/sdk-payload.d.ts.map +1 -0
  49. package/dist/sdk-payload.js +36 -0
  50. package/dist/sdk-payload.js.map +1 -0
  51. package/dist/statement.d.ts +67 -0
  52. package/dist/statement.d.ts.map +1 -0
  53. package/dist/statement.js +67 -0
  54. package/dist/statement.js.map +1 -0
  55. package/dist/storage/sqlite.d.ts +45 -0
  56. package/dist/storage/sqlite.d.ts.map +1 -0
  57. package/dist/storage/sqlite.js +132 -0
  58. package/dist/storage/sqlite.js.map +1 -0
  59. package/dist/submit.d.ts +26 -0
  60. package/dist/submit.d.ts.map +1 -0
  61. package/dist/submit.js +35 -0
  62. package/dist/submit.js.map +1 -0
  63. package/dist/verifier.d.ts +74 -0
  64. package/dist/verifier.d.ts.map +1 -0
  65. package/dist/verifier.js +197 -0
  66. package/dist/verifier.js.map +1 -0
  67. package/package.json +60 -0
  68. package/src/badge.ts +113 -0
  69. package/src/bind.ts +137 -0
  70. package/src/bundle.ts +127 -0
  71. package/src/canonical.ts +33 -0
  72. package/src/dsse.ts +91 -0
  73. package/src/errors.ts +37 -0
  74. package/src/github.ts +196 -0
  75. package/src/index.ts +121 -0
  76. package/src/log/rekor.ts +334 -0
  77. package/src/merkle.ts +187 -0
  78. package/src/nonce.ts +53 -0
  79. package/src/sdk-payload.ts +62 -0
  80. package/src/statement.ts +119 -0
  81. package/src/storage/sqlite.ts +185 -0
  82. package/src/submit.ts +54 -0
  83. package/src/truestamp-canonify.d.ts +7 -0
  84. package/src/verifier.ts +317 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Rekor client for in-toto v0.0.2 entries.
3
+ *
4
+ * Behavior pinned from the Day 5 smoke test against
5
+ * `rekor.sigstore.dev` (see docs/v0-acceptance.md Day 5 evidence).
6
+ * The most non-obvious bits live in `buildIntotoEntryBody` below.
7
+ *
8
+ * All HTTP failures surface as
9
+ * `PassportsignError('log_submission_failed', …)` to match spec §4.
10
+ */
11
+ import { type DsseEnvelope } from '../dsse.js';
12
+ export interface InclusionProof {
13
+ checkpoint: string;
14
+ hashes: string[];
15
+ logIndex: number;
16
+ rootHash: string;
17
+ treeSize: number;
18
+ }
19
+ export interface RekorEntryResponse {
20
+ uuid: string;
21
+ logIndex: number;
22
+ integratedTime: number;
23
+ logID: string;
24
+ /** base64-encoded canonicalised entry body the server stored. */
25
+ body: string;
26
+ /** Optional server-stored attestation (base64). */
27
+ attestation?: {
28
+ data?: string;
29
+ } | undefined;
30
+ verification: {
31
+ inclusionProof: InclusionProof;
32
+ /** Rekor's signed timestamp over the entry (base64). */
33
+ signedEntryTimestamp: string;
34
+ };
35
+ }
36
+ export interface RekorLogInfo {
37
+ /** Hex-encoded current root hash of the active tree. */
38
+ rootHash: string;
39
+ /** Number of entries currently in the active tree. */
40
+ treeSize: number;
41
+ /** Signed tree head (Rekor's signature over the current root + size). */
42
+ signedTreeHead: string;
43
+ /** Active tree ID (string per Rekor's API). */
44
+ treeID: string;
45
+ }
46
+ export interface RekorConsistencyProof {
47
+ /** Hex hashes proving treeSize=first is a prefix of treeSize=last. */
48
+ hashes: string[];
49
+ /** Hex root hash at the new size (informational; we verify against our own captured one). */
50
+ rootHash: string;
51
+ }
52
+ export interface RekorClient {
53
+ submitIntoto(envelope: DsseEnvelope): Promise<RekorEntryResponse>;
54
+ getEntry(uuid: string): Promise<RekorEntryResponse>;
55
+ getLogInfo(): Promise<RekorLogInfo>;
56
+ getConsistencyProof(firstSize: number, lastSize: number): Promise<RekorConsistencyProof>;
57
+ }
58
+ export interface PublicSigstoreRekorClientOptions {
59
+ baseUrl?: string;
60
+ fetch?: typeof fetch;
61
+ }
62
+ export declare const DEFAULT_REKOR_BASE_URL = "https://rekor.sigstore.dev";
63
+ export declare class PublicSigstoreRekorClient implements RekorClient {
64
+ private readonly baseUrl;
65
+ private readonly fetchImpl;
66
+ constructor(opts?: PublicSigstoreRekorClientOptions);
67
+ submitIntoto(envelope: DsseEnvelope): Promise<RekorEntryResponse>;
68
+ getEntry(uuid: string): Promise<RekorEntryResponse>;
69
+ getLogInfo(): Promise<RekorLogInfo>;
70
+ getConsistencyProof(firstSize: number, lastSize: number): Promise<RekorConsistencyProof>;
71
+ private postEntry;
72
+ }
73
+ /**
74
+ * Build the Rekor in-toto v0.0.2 entry submission body from a
75
+ * DSSE envelope. Encoding quirks pinned during the Day 5 smoke test:
76
+ *
77
+ * - `payload` and `sig` are **double-base64** at the API boundary
78
+ * (Rekor's go-openapi `strfmt.Base64` re-encodes the already-base64
79
+ * DSSE strings).
80
+ * - `publicKey` is **single-base64** over the PEM bytes (raw PEM text).
81
+ * - `keyid` is **omitted entirely** if empty — sending `""` causes the
82
+ * server's canonicalised entry to differ from the client's and the
83
+ * submission fails with "error generating canonicalized entry".
84
+ * - `hash` and `payloadHash` are **required despite the readOnly
85
+ * schema markers**. The server compares them to its own computation
86
+ * and rejects the submission on mismatch.
87
+ *
88
+ * @internal exported for direct testing.
89
+ */
90
+ export declare function buildIntotoEntryBody(envelope: DsseEnvelope): unknown;
91
+ //# sourceMappingURL=rekor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rekor.d.ts","sourceRoot":"","sources":["../../src/log/rekor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,WAAW,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAC5C,YAAY,EAAE;QACZ,cAAc,EAAE,cAAc,CAAC;QAC/B,wDAAwD;QACxD,oBAAoB,EAAE,MAAM,CAAC;KAC9B,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,sEAAsE;IACtE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,6FAA6F;IAC7F,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACpD,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IACpC,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC1F;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,eAAO,MAAM,sBAAsB,+BAA+B,CAAC;AAEnE,qBAAa,yBAA0B,YAAW,WAAW;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,IAAI,GAAE,gCAAqC;IAKjD,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAKjE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAsBnD,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC;IAoCnC,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;YA2ChF,SAAS;CAyBxB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAwDpE"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Rekor client for in-toto v0.0.2 entries.
3
+ *
4
+ * Behavior pinned from the Day 5 smoke test against
5
+ * `rekor.sigstore.dev` (see docs/v0-acceptance.md Day 5 evidence).
6
+ * The most non-obvious bits live in `buildIntotoEntryBody` below.
7
+ *
8
+ * All HTTP failures surface as
9
+ * `PassportsignError('log_submission_failed', …)` to match spec §4.
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ import { canonicalize } from '../canonical.js';
13
+ import {} from '../dsse.js';
14
+ import { PassportsignError } from '../errors.js';
15
+ export const DEFAULT_REKOR_BASE_URL = 'https://rekor.sigstore.dev';
16
+ export class PublicSigstoreRekorClient {
17
+ baseUrl;
18
+ fetchImpl;
19
+ constructor(opts = {}) {
20
+ this.baseUrl = opts.baseUrl ?? DEFAULT_REKOR_BASE_URL;
21
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
22
+ }
23
+ async submitIntoto(envelope) {
24
+ const body = buildIntotoEntryBody(envelope);
25
+ return this.postEntry(body);
26
+ }
27
+ async getEntry(uuid) {
28
+ let response;
29
+ try {
30
+ response = await this.fetchImpl(`${this.baseUrl}/api/v1/log/entries/${uuid}`);
31
+ }
32
+ catch (err) {
33
+ throw new PassportsignError('log_submission_failed', `Rekor get-entry request failed: ${err instanceof Error ? err.message : String(err)}`, err);
34
+ }
35
+ if (!response.ok) {
36
+ let errBody = '';
37
+ try {
38
+ errBody = await response.text();
39
+ }
40
+ catch { /* ignore */ }
41
+ throw new PassportsignError('log_submission_failed', `Rekor get-entry returned ${response.status}: ${errBody}`);
42
+ }
43
+ return parseEntryResponse(await response.json().catch(() => null));
44
+ }
45
+ async getLogInfo() {
46
+ let response;
47
+ try {
48
+ response = await this.fetchImpl(`${this.baseUrl}/api/v1/log`);
49
+ }
50
+ catch (err) {
51
+ throw new PassportsignError('log_submission_failed', `Rekor log-info request failed: ${err instanceof Error ? err.message : String(err)}`, err);
52
+ }
53
+ if (!response.ok) {
54
+ throw new PassportsignError('log_submission_failed', `Rekor log-info returned ${response.status}`);
55
+ }
56
+ const body = (await response.json().catch(() => null));
57
+ if (!body || typeof body !== 'object') {
58
+ throw new PassportsignError('log_submission_failed', 'Rekor log-info returned non-object');
59
+ }
60
+ const rootHash = body['rootHash'];
61
+ const treeSize = body['treeSize'];
62
+ const signedTreeHead = body['signedTreeHead'];
63
+ const treeID = body['treeID'];
64
+ if (typeof rootHash !== 'string' ||
65
+ typeof treeSize !== 'number' ||
66
+ typeof signedTreeHead !== 'string' ||
67
+ typeof treeID !== 'string') {
68
+ throw new PassportsignError('log_submission_failed', 'Rekor log-info missing required fields');
69
+ }
70
+ return { rootHash, treeSize, signedTreeHead, treeID };
71
+ }
72
+ async getConsistencyProof(firstSize, lastSize) {
73
+ let response;
74
+ try {
75
+ response = await this.fetchImpl(`${this.baseUrl}/api/v1/log/proof?firstSize=${firstSize}&lastSize=${lastSize}`);
76
+ }
77
+ catch (err) {
78
+ throw new PassportsignError('log_submission_failed', `Rekor consistency-proof request failed: ${err instanceof Error ? err.message : String(err)}`, err);
79
+ }
80
+ if (!response.ok) {
81
+ throw new PassportsignError('log_submission_failed', `Rekor consistency-proof returned ${response.status}`);
82
+ }
83
+ const body = (await response.json().catch(() => null));
84
+ if (!body || typeof body !== 'object') {
85
+ throw new PassportsignError('log_submission_failed', 'Rekor consistency-proof returned non-object');
86
+ }
87
+ const hashes = body['hashes'];
88
+ const rootHash = body['rootHash'];
89
+ if (!Array.isArray(hashes) || !hashes.every((h) => typeof h === 'string')) {
90
+ throw new PassportsignError('log_submission_failed', 'Rekor consistency-proof has no hashes array');
91
+ }
92
+ if (typeof rootHash !== 'string') {
93
+ throw new PassportsignError('log_submission_failed', 'Rekor consistency-proof has no rootHash');
94
+ }
95
+ return { hashes: hashes, rootHash };
96
+ }
97
+ async postEntry(body) {
98
+ let response;
99
+ try {
100
+ response = await this.fetchImpl(`${this.baseUrl}/api/v1/log/entries`, {
101
+ method: 'POST',
102
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
103
+ body: JSON.stringify(body),
104
+ });
105
+ }
106
+ catch (err) {
107
+ throw new PassportsignError('log_submission_failed', `Rekor submit request failed: ${err instanceof Error ? err.message : String(err)}`, err);
108
+ }
109
+ if (!response.ok) {
110
+ let errBody = '';
111
+ try {
112
+ errBody = await response.text();
113
+ }
114
+ catch { /* ignore */ }
115
+ throw new PassportsignError('log_submission_failed', `Rekor submit returned ${response.status}: ${errBody}`);
116
+ }
117
+ return parseEntryResponse(await response.json().catch(() => null));
118
+ }
119
+ }
120
+ /**
121
+ * Build the Rekor in-toto v0.0.2 entry submission body from a
122
+ * DSSE envelope. Encoding quirks pinned during the Day 5 smoke test:
123
+ *
124
+ * - `payload` and `sig` are **double-base64** at the API boundary
125
+ * (Rekor's go-openapi `strfmt.Base64` re-encodes the already-base64
126
+ * DSSE strings).
127
+ * - `publicKey` is **single-base64** over the PEM bytes (raw PEM text).
128
+ * - `keyid` is **omitted entirely** if empty — sending `""` causes the
129
+ * server's canonicalised entry to differ from the client's and the
130
+ * submission fails with "error generating canonicalized entry".
131
+ * - `hash` and `payloadHash` are **required despite the readOnly
132
+ * schema markers**. The server compares them to its own computation
133
+ * and rejects the submission on mismatch.
134
+ *
135
+ * @internal exported for direct testing.
136
+ */
137
+ export function buildIntotoEntryBody(envelope) {
138
+ if (envelope.signatures.length === 0) {
139
+ throw new PassportsignError('log_submission_failed', 'envelope must have at least one signature');
140
+ }
141
+ const sig0 = envelope.signatures[0];
142
+ // payloadHash = sha256 of raw payload bytes.
143
+ const payloadBytes = new Uint8Array(Buffer.from(envelope.payload, 'base64'));
144
+ const payloadHashHex = createHash('sha256').update(payloadBytes).digest('hex');
145
+ // envelopeHash = sha256 of canonical JSON of {payloadType, payload-base64,
146
+ // signatures:[{sig-base64, publicKey: PEM-string [, keyid]}]} — note
147
+ // publicKey is the raw PEM string for this hash (not base64).
148
+ const sigForHash = {
149
+ sig: sig0.sig,
150
+ publicKey: sig0.publicKey,
151
+ };
152
+ if (sig0.keyid && sig0.keyid.length > 0) {
153
+ sigForHash['keyid'] = sig0.keyid;
154
+ }
155
+ const envelopeForHash = {
156
+ payloadType: envelope.payloadType,
157
+ payload: envelope.payload,
158
+ signatures: [sigForHash],
159
+ };
160
+ const envelopeHashHex = createHash('sha256')
161
+ .update(canonicalize(envelopeForHash))
162
+ .digest('hex');
163
+ // Build the actual submission body.
164
+ const sigItem = {
165
+ sig: Buffer.from(sig0.sig).toString('base64'),
166
+ publicKey: Buffer.from(sig0.publicKey).toString('base64'),
167
+ };
168
+ if (sig0.keyid && sig0.keyid.length > 0) {
169
+ sigItem['keyid'] = sig0.keyid;
170
+ }
171
+ return {
172
+ apiVersion: '0.0.2',
173
+ kind: 'intoto',
174
+ spec: {
175
+ content: {
176
+ envelope: {
177
+ payloadType: envelope.payloadType,
178
+ payload: Buffer.from(envelope.payload).toString('base64'),
179
+ signatures: [sigItem],
180
+ },
181
+ hash: { algorithm: 'sha256', value: envelopeHashHex },
182
+ payloadHash: { algorithm: 'sha256', value: payloadHashHex },
183
+ },
184
+ },
185
+ };
186
+ }
187
+ function parseEntryResponse(raw) {
188
+ if (typeof raw !== 'object' || raw === null) {
189
+ throw new PassportsignError('log_submission_failed', 'malformed Rekor response (not a JSON object)');
190
+ }
191
+ const entries = Object.entries(raw);
192
+ if (entries.length !== 1) {
193
+ throw new PassportsignError('log_submission_failed', `expected exactly one UUID in Rekor response, got ${entries.length}`);
194
+ }
195
+ const [uuid, entryRaw] = entries[0];
196
+ const entry = entryRaw;
197
+ const verification = entry['verification'];
198
+ if (!verification) {
199
+ throw new PassportsignError('log_submission_failed', 'Rekor response missing verification block');
200
+ }
201
+ const inclusionProof = verification['inclusionProof'];
202
+ const signedEntryTimestamp = verification['signedEntryTimestamp'];
203
+ if (!inclusionProof || typeof signedEntryTimestamp !== 'string') {
204
+ throw new PassportsignError('log_submission_failed', 'Rekor response missing inclusionProof or signedEntryTimestamp');
205
+ }
206
+ return {
207
+ uuid,
208
+ logIndex: entry['logIndex'],
209
+ integratedTime: entry['integratedTime'],
210
+ logID: entry['logID'],
211
+ body: entry['body'],
212
+ ...(entry['attestation']
213
+ ? { attestation: entry['attestation'] }
214
+ : {}),
215
+ verification: { inclusionProof, signedEntryTimestamp },
216
+ };
217
+ }
218
+ //# sourceMappingURL=rekor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rekor.js","sourceRoot":"","sources":["../../src/log/rekor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * RFC 6962 Merkle tree primitives — the math underneath Rekor's
3
+ * inclusion and consistency proofs.
4
+ *
5
+ * Algorithm ported from
6
+ * https://github.com/google/certificate-transparency-go/blob/master/merkle/log_verifier.go
7
+ * (the canonical reference implementation Sigstore tracks).
8
+ *
9
+ * Leaf hash: sha256(0x00 || leaf-bytes)
10
+ * Inner hash: sha256(0x01 || left-hash || right-hash)
11
+ *
12
+ * A proof of length n+m has n "inner" hashes (siblings along the path
13
+ * from leaf up to the highest common ancestor with the rightmost
14
+ * leaf) followed by m "border" hashes (always left-leaning siblings
15
+ * above that ancestor). The split is determined by bit-decomposition
16
+ * of leafIndex against treeSize - 1.
17
+ */
18
+ export declare function hashLeaf(data: Uint8Array): Uint8Array;
19
+ export declare function hashPair(left: Uint8Array, right: Uint8Array): Uint8Array;
20
+ /**
21
+ * Verify an RFC 6962 inclusion proof: prove that a leaf with hash
22
+ * `leafHash` at position `leafIndex` is included in a tree of size
23
+ * `treeSize` with root `rootHash`, using the supplied path of
24
+ * sibling hashes.
25
+ */
26
+ export declare function verifyInclusion(leafHash: Uint8Array, leafIndex: number, treeSize: number, proof: Uint8Array[], rootHash: Uint8Array): boolean;
27
+ /**
28
+ * Verify an RFC 6962 consistency proof: prove that the tree of size
29
+ * `firstSize` with root `firstRoot` is a prefix of the tree of size
30
+ * `secondSize` with root `secondRoot`. Used to detect log rewrites —
31
+ * if our captured root is no longer an ancestor of the current root,
32
+ * the log has been tampered with.
33
+ *
34
+ * Algorithm port of certificate-transparency-go's `VerifyConsistencyProof`.
35
+ */
36
+ export declare function verifyConsistency(firstSize: number, secondSize: number, firstRoot: Uint8Array, secondRoot: Uint8Array, proof: Uint8Array[]): boolean;
37
+ //# sourceMappingURL=merkle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merkle.d.ts","sourceRoot":"","sources":["../src/merkle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAKrD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAMxE;AA6ED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,UAAU,EACpB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,UAAU,GACnB,OAAO,CAST;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,UAAU,EACrB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,UAAU,EAAE,GAClB,OAAO,CAsCT"}
package/dist/merkle.js ADDED
@@ -0,0 +1,160 @@
1
+ /**
2
+ * RFC 6962 Merkle tree primitives — the math underneath Rekor's
3
+ * inclusion and consistency proofs.
4
+ *
5
+ * Algorithm ported from
6
+ * https://github.com/google/certificate-transparency-go/blob/master/merkle/log_verifier.go
7
+ * (the canonical reference implementation Sigstore tracks).
8
+ *
9
+ * Leaf hash: sha256(0x00 || leaf-bytes)
10
+ * Inner hash: sha256(0x01 || left-hash || right-hash)
11
+ *
12
+ * A proof of length n+m has n "inner" hashes (siblings along the path
13
+ * from leaf up to the highest common ancestor with the rightmost
14
+ * leaf) followed by m "border" hashes (always left-leaning siblings
15
+ * above that ancestor). The split is determined by bit-decomposition
16
+ * of leafIndex against treeSize - 1.
17
+ */
18
+ import { createHash } from 'node:crypto';
19
+ export function hashLeaf(data) {
20
+ const buf = new Uint8Array(1 + data.length);
21
+ buf[0] = 0x00;
22
+ buf.set(data, 1);
23
+ return new Uint8Array(createHash('sha256').update(buf).digest());
24
+ }
25
+ export function hashPair(left, right) {
26
+ const buf = new Uint8Array(1 + left.length + right.length);
27
+ buf[0] = 0x01;
28
+ buf.set(left, 1);
29
+ buf.set(right, 1 + left.length);
30
+ return new Uint8Array(createHash('sha256').update(buf).digest());
31
+ }
32
+ function bytesEqual(a, b) {
33
+ if (a.length !== b.length)
34
+ return false;
35
+ for (let i = 0; i < a.length; i++) {
36
+ if (a[i] !== b[i])
37
+ return false;
38
+ }
39
+ return true;
40
+ }
41
+ function bitLength(n) {
42
+ let len = 0;
43
+ while (n > 0) {
44
+ len++;
45
+ n = Math.floor(n / 2);
46
+ }
47
+ return len;
48
+ }
49
+ function popcount(n) {
50
+ let count = 0;
51
+ while (n > 0) {
52
+ count += n & 1;
53
+ n = Math.floor(n / 2);
54
+ }
55
+ return count;
56
+ }
57
+ function trailingZeros(n) {
58
+ if (n === 0)
59
+ return 64;
60
+ let count = 0;
61
+ while ((n & 1) === 0) {
62
+ count++;
63
+ n = Math.floor(n / 2);
64
+ }
65
+ return count;
66
+ }
67
+ function decompInclProof(leafIndex, treeSize) {
68
+ const inner = bitLength(leafIndex ^ (treeSize - 1));
69
+ const border = popcount(Math.floor(leafIndex / Math.pow(2, inner)));
70
+ return { inner, border };
71
+ }
72
+ function chainInner(seed, proof, leafIndex) {
73
+ let res = seed;
74
+ for (let i = 0; i < proof.length; i++) {
75
+ const bit = (Math.floor(leafIndex / Math.pow(2, i))) & 1;
76
+ res = bit === 0 ? hashPair(res, proof[i]) : hashPair(proof[i], res);
77
+ }
78
+ return res;
79
+ }
80
+ function chainInnerRight(seed, proof, leafIndex) {
81
+ let res = seed;
82
+ for (let i = 0; i < proof.length; i++) {
83
+ const bit = (Math.floor(leafIndex / Math.pow(2, i))) & 1;
84
+ if (bit === 1) {
85
+ res = hashPair(proof[i], res);
86
+ }
87
+ }
88
+ return res;
89
+ }
90
+ function chainBorderRight(seed, proof) {
91
+ let res = seed;
92
+ for (const p of proof) {
93
+ res = hashPair(p, res);
94
+ }
95
+ return res;
96
+ }
97
+ /**
98
+ * Verify an RFC 6962 inclusion proof: prove that a leaf with hash
99
+ * `leafHash` at position `leafIndex` is included in a tree of size
100
+ * `treeSize` with root `rootHash`, using the supplied path of
101
+ * sibling hashes.
102
+ */
103
+ export function verifyInclusion(leafHash, leafIndex, treeSize, proof, rootHash) {
104
+ if (leafIndex < 0 || treeSize < 0 || leafIndex >= treeSize)
105
+ return false;
106
+ const { inner, border } = decompInclProof(leafIndex, treeSize);
107
+ if (proof.length !== inner + border)
108
+ return false;
109
+ let res = chainInner(leafHash, proof.slice(0, inner), leafIndex);
110
+ res = chainBorderRight(res, proof.slice(inner));
111
+ return bytesEqual(res, rootHash);
112
+ }
113
+ /**
114
+ * Verify an RFC 6962 consistency proof: prove that the tree of size
115
+ * `firstSize` with root `firstRoot` is a prefix of the tree of size
116
+ * `secondSize` with root `secondRoot`. Used to detect log rewrites —
117
+ * if our captured root is no longer an ancestor of the current root,
118
+ * the log has been tampered with.
119
+ *
120
+ * Algorithm port of certificate-transparency-go's `VerifyConsistencyProof`.
121
+ */
122
+ export function verifyConsistency(firstSize, secondSize, firstRoot, secondRoot, proof) {
123
+ if (firstSize < 0 || secondSize < firstSize)
124
+ return false;
125
+ if (firstSize === secondSize) {
126
+ return proof.length === 0 && bytesEqual(firstRoot, secondRoot);
127
+ }
128
+ if (firstSize === 0) {
129
+ return proof.length === 0;
130
+ }
131
+ let { inner, border } = decompInclProof(firstSize - 1, secondSize);
132
+ const shift = trailingZeros(firstSize);
133
+ inner -= shift;
134
+ let seed;
135
+ let start;
136
+ if ((firstSize & (firstSize - 1)) !== 0) {
137
+ if (proof.length === 0)
138
+ return false;
139
+ seed = proof[0];
140
+ start = 1;
141
+ }
142
+ else {
143
+ seed = firstRoot;
144
+ start = 0;
145
+ }
146
+ if (proof.length !== start + inner + border)
147
+ return false;
148
+ const subProof = proof.slice(start);
149
+ const mask = Math.floor((firstSize - 1) / Math.pow(2, shift));
150
+ let hash1 = chainInnerRight(seed, subProof.slice(0, inner), mask);
151
+ hash1 = chainBorderRight(hash1, subProof.slice(inner));
152
+ if (!bytesEqual(hash1, firstRoot))
153
+ return false;
154
+ let hash2 = chainInner(seed, subProof.slice(0, inner), mask);
155
+ hash2 = chainBorderRight(hash2, subProof.slice(inner));
156
+ if (!bytesEqual(hash2, secondRoot))
157
+ return false;
158
+ return true;
159
+ }
160
+ //# sourceMappingURL=merkle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merkle.js","sourceRoot":"","sources":["../src/merkle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Binding nonce per spec §3 step 1: cryptographically random, ≥128 bits,
3
+ * base32-encoded for gist-friendliness, prefixed with the service
4
+ * namespace ("zkm-") and the username for human readability.
5
+ *
6
+ * Format: `zkm-{username}-{base32}`
7
+ * Entropy: 160 bits (20 bytes → 32 base32 chars).
8
+ */
9
+ export declare const NONCE_BYTES = 20;
10
+ export declare const NONCE_BASE32_LENGTH = 32;
11
+ /**
12
+ * RFC 4648 base32 (lowercase, no padding) of the input bytes.
13
+ *
14
+ * Uses BigInt to avoid 32-bit overflow when accumulating ≥4 bytes.
15
+ */
16
+ export declare function base32Encode(bytes: Uint8Array): string;
17
+ /**
18
+ * Generate a fresh nonce for a binding session.
19
+ *
20
+ * @param username - GitHub username to embed in the nonce (case preserved).
21
+ * @returns `zkm-<username>-<32 base32 chars>` (length-stable for valid usernames).
22
+ */
23
+ export declare function generateNonce(username: string): string;
24
+ //# sourceMappingURL=nonce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nonce.d.ts","sourceRoot":"","sources":["../src/nonce.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAkBtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtD"}
package/dist/nonce.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Binding nonce per spec §3 step 1: cryptographically random, ≥128 bits,
3
+ * base32-encoded for gist-friendliness, prefixed with the service
4
+ * namespace ("zkm-") and the username for human readability.
5
+ *
6
+ * Format: `zkm-{username}-{base32}`
7
+ * Entropy: 160 bits (20 bytes → 32 base32 chars).
8
+ */
9
+ import { randomBytes } from 'node:crypto';
10
+ const BASE32_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567';
11
+ export const NONCE_BYTES = 20;
12
+ export const NONCE_BASE32_LENGTH = 32; // (20 * 8) / 5
13
+ /**
14
+ * RFC 4648 base32 (lowercase, no padding) of the input bytes.
15
+ *
16
+ * Uses BigInt to avoid 32-bit overflow when accumulating ≥4 bytes.
17
+ */
18
+ export function base32Encode(bytes) {
19
+ let bits = 0n;
20
+ let bitCount = 0;
21
+ let result = '';
22
+ for (const byte of bytes) {
23
+ bits = (bits << 8n) | BigInt(byte);
24
+ bitCount += 8;
25
+ while (bitCount >= 5) {
26
+ bitCount -= 5;
27
+ const idx = Number((bits >> BigInt(bitCount)) & 0x1fn);
28
+ result += BASE32_ALPHABET[idx];
29
+ }
30
+ }
31
+ if (bitCount > 0) {
32
+ const idx = Number((bits << BigInt(5 - bitCount)) & 0x1fn);
33
+ result += BASE32_ALPHABET[idx];
34
+ }
35
+ return result;
36
+ }
37
+ /**
38
+ * Generate a fresh nonce for a binding session.
39
+ *
40
+ * @param username - GitHub username to embed in the nonce (case preserved).
41
+ * @returns `zkm-<username>-<32 base32 chars>` (length-stable for valid usernames).
42
+ */
43
+ export function generateNonce(username) {
44
+ if (username.length === 0) {
45
+ throw new TypeError('generateNonce: username must be non-empty');
46
+ }
47
+ const bytes = randomBytes(NONCE_BYTES);
48
+ return `zkm-${username}-${base32Encode(bytes)}`;
49
+ }
50
+ //# sourceMappingURL=nonce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nonce.js","sourceRoot":"","sources":["../src/nonce.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,eAAe,GAAG,kCAAkC,CAAC;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC9B,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC,CAAC,eAAe;AAEtD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,QAAQ,IAAI,CAAC,CAAC;QACd,OAAO,QAAQ,IAAI,CAAC,EAAE,CAAC;YACrB,QAAQ,IAAI,CAAC,CAAC;YACd,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;YACvD,MAAM,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;QAC3D,MAAM,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACvC,OAAO,OAAO,QAAQ,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pack/unpack the zkPassport SDK inputs that a verifier needs to re-run
3
+ * proof verification offline.
4
+ *
5
+ * Rather than expanding the bundle schema, we re-purpose the existing
6
+ * `proof_blob` field: it's the base64 of canonical JCS bytes of an
7
+ * {@link SdkPayload} object. The statement's `proof_blob_sha256` already
8
+ * binds those bytes to the rest of the binding — Day 5's hash check
9
+ * carries through.
10
+ */
11
+ export interface SdkPayload {
12
+ /** The zkPassport SDK version that produced these proofs. */
13
+ sdk_version: string;
14
+ /** Array of ProofResult objects from onProofGenerated callbacks. */
15
+ proofs: unknown[];
16
+ /** The Query object from queryBuilder, in serialised form. */
17
+ original_query: unknown;
18
+ /** The QueryResult from the SDK's onResult callback. */
19
+ query_result: unknown;
20
+ /** Whether the proofs are mock (zkPassport dev mode). */
21
+ dev_mode: boolean;
22
+ }
23
+ export interface PackedSdkPayload {
24
+ /** Canonical bytes (RFC 8785 JCS UTF-8). */
25
+ bytes: Uint8Array;
26
+ /** Base64 of `bytes` — the value that goes into `bundle.proof_blob`. */
27
+ b64: string;
28
+ /** Lowercase-hex SHA-256 of `bytes` — the value that goes into the statement's `proof_blob_sha256`. */
29
+ sha256Hex: string;
30
+ }
31
+ export declare function packSdkPayload(payload: SdkPayload): PackedSdkPayload;
32
+ export declare function unpackSdkPayload(b64: string): SdkPayload;
33
+ //# sourceMappingURL=sdk-payload.d.ts.map