@passportsign/core 0.1.0 → 0.2.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/badge.d.ts +5 -0
- package/dist/badge.d.ts.map +1 -1
- package/dist/badge.js +8 -2
- package/dist/badge.js.map +1 -1
- package/dist/bind.d.ts.map +1 -1
- package/dist/bind.js +2 -8
- package/dist/bind.js.map +1 -1
- package/dist/bundle-fs.d.ts +16 -0
- package/dist/bundle-fs.d.ts.map +1 -0
- package/dist/bundle-fs.js +31 -0
- package/dist/bundle-fs.js.map +1 -0
- package/dist/bundle.d.ts +13 -5
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +18 -20
- package/dist/bundle.js.map +1 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +3 -4
- package/dist/canonical.js.map +1 -1
- package/dist/classify.d.ts +68 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +117 -0
- package/dist/classify.js.map +1 -0
- package/dist/dsse-common.d.ts +32 -0
- package/dist/dsse-common.d.ts.map +1 -0
- package/dist/dsse-common.js +26 -0
- package/dist/dsse-common.js.map +1 -0
- package/dist/dsse-web.d.ts +28 -0
- package/dist/dsse-web.d.ts.map +1 -0
- package/dist/dsse-web.js +81 -0
- package/dist/dsse-web.js.map +1 -0
- package/dist/dsse.d.ts +2 -26
- package/dist/dsse.d.ts.map +1 -1
- package/dist/dsse.js +2 -19
- package/dist/dsse.js.map +1 -1
- package/dist/encoding.d.ts +20 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +88 -0
- package/dist/encoding.js.map +1 -0
- package/dist/github.js +2 -2
- package/dist/github.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/log/rekor.d.ts +1 -1
- package/dist/log/rekor.d.ts.map +1 -1
- package/dist/log/rekor.js +7 -10
- package/dist/log/rekor.js.map +1 -1
- package/dist/lookup.d.ts +46 -0
- package/dist/lookup.d.ts.map +1 -0
- package/dist/lookup.js +101 -0
- package/dist/lookup.js.map +1 -0
- package/dist/merkle.js +3 -3
- package/dist/merkle.js.map +1 -1
- package/dist/nonce.js +1 -1
- package/dist/nonce.js.map +1 -1
- package/dist/profile-index.d.ts +64 -0
- package/dist/profile-index.d.ts.map +1 -0
- package/dist/profile-index.js +161 -0
- package/dist/profile-index.js.map +1 -0
- package/dist/revoke.d.ts +30 -0
- package/dist/revoke.d.ts.map +1 -0
- package/dist/revoke.js +42 -0
- package/dist/revoke.js.map +1 -0
- package/dist/sdk-payload.d.ts.map +1 -1
- package/dist/sdk-payload.js +4 -6
- package/dist/sdk-payload.js.map +1 -1
- package/dist/statement.d.ts +41 -0
- package/dist/statement.d.ts.map +1 -1
- package/dist/statement.js +43 -0
- package/dist/statement.js.map +1 -1
- package/dist/submit.d.ts +3 -3
- package/dist/submit.d.ts.map +1 -1
- package/dist/submit.js +3 -14
- package/dist/submit.js.map +1 -1
- package/dist/verifier.d.ts.map +1 -1
- package/dist/verifier.js +4 -14
- package/dist/verifier.js.map +1 -1
- package/dist/web.d.ts +35 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +35 -0
- package/dist/web.js.map +1 -0
- package/package.json +6 -2
- package/src/badge.ts +124 -113
- package/src/bind.ts +128 -137
- package/src/bundle-fs.ts +40 -0
- package/src/bundle.ts +138 -127
- package/src/canonical.ts +33 -33
- package/src/classify.ts +165 -0
- package/src/dsse-common.ts +45 -0
- package/src/dsse-web.ts +97 -0
- package/src/dsse.ts +63 -91
- package/src/encoding.ts +96 -0
- package/src/github.ts +196 -196
- package/src/index.ts +59 -2
- package/src/log/rekor.ts +330 -334
- package/src/lookup.ts +175 -0
- package/src/merkle.ts +187 -187
- package/src/nonce.ts +53 -53
- package/src/profile-index.ts +222 -0
- package/src/revoke.ts +67 -0
- package/src/sdk-payload.ts +60 -62
- package/src/statement.ts +203 -119
- package/src/submit.ts +38 -54
- package/src/verifier.ts +304 -317
- package/src/web.ts +175 -0
package/src/verifier.ts
CHANGED
|
@@ -1,317 +1,304 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bundle verifier — the trust anchor for everyone who is not us.
|
|
3
|
-
*
|
|
4
|
-
* Day 6 scope: structural integrity (statement hash matches Rekor's
|
|
5
|
-
* recorded payloadHash), Merkle inclusion proof against the captured
|
|
6
|
-
* root, and log-root consistency between the captured root and the
|
|
7
|
-
* current witnessed root.
|
|
8
|
-
*
|
|
9
|
-
* Day 7 scope (deferred): SDK proof verification. Requires a
|
|
10
|
-
* bundle-schema extension to carry SDK inputs (proofs array,
|
|
11
|
-
* originalQuery, queryResult). For now `sdk_proof` reports
|
|
12
|
-
* `'pending_day_7'`.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
import { type
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* `'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
payloadHashHex:
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
result.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
result.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
result.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
captured.treeSize
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
result.root_consistency =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
);
|
|
265
|
-
return 'fail';
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
);
|
|
306
|
-
return 'fail';
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return 'pass';
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function computeOverall(r: BundleVerifyResult): 'pass' | 'fail' | 'pending' {
|
|
313
|
-
const all = [r.hash_match, r.inclusion_proof, r.root_consistency, r.sdk_proof];
|
|
314
|
-
if (all.some((s) => s === 'fail')) return 'fail';
|
|
315
|
-
if (all.every((s) => s === 'pass')) return 'pass';
|
|
316
|
-
return 'pending';
|
|
317
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Bundle verifier — the trust anchor for everyone who is not us.
|
|
3
|
+
*
|
|
4
|
+
* Day 6 scope: structural integrity (statement hash matches Rekor's
|
|
5
|
+
* recorded payloadHash), Merkle inclusion proof against the captured
|
|
6
|
+
* root, and log-root consistency between the captured root and the
|
|
7
|
+
* current witnessed root.
|
|
8
|
+
*
|
|
9
|
+
* Day 7 scope (deferred): SDK proof verification. Requires a
|
|
10
|
+
* bundle-schema extension to carry SDK inputs (proofs array,
|
|
11
|
+
* originalQuery, queryResult). For now `sdk_proof` reports
|
|
12
|
+
* `'pending_day_7'`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { type PassportsignBundle, validateBundle } from './bundle.js';
|
|
16
|
+
import { base64ToBytes, bytesToUtf8, hexToBytes, sha256Hex } from './encoding.js';
|
|
17
|
+
import { type RekorClient } from './log/rekor.js';
|
|
18
|
+
import { hashLeaf, verifyConsistency, verifyInclusion } from './merkle.js';
|
|
19
|
+
import { unpackSdkPayload } from './sdk-payload.js';
|
|
20
|
+
|
|
21
|
+
export type CheckResult = 'pass' | 'fail' | 'skipped';
|
|
22
|
+
|
|
23
|
+
export interface BundleVerifyResult {
|
|
24
|
+
/**
|
|
25
|
+
* Statement bytes in the bundle hash to the `payloadHash` Rekor recorded
|
|
26
|
+
* for the entry.
|
|
27
|
+
*/
|
|
28
|
+
hash_match: CheckResult;
|
|
29
|
+
/**
|
|
30
|
+
* The captured inclusion proof verifies the Rekor entry's leaf hash
|
|
31
|
+
* against the captured root.
|
|
32
|
+
*/
|
|
33
|
+
inclusion_proof: CheckResult;
|
|
34
|
+
/**
|
|
35
|
+
* The captured root is a prefix of the current witnessed root (the log
|
|
36
|
+
* has not been rewritten in a way that orphans our entry). Skipped when
|
|
37
|
+
* no rekor client is provided.
|
|
38
|
+
*/
|
|
39
|
+
root_consistency: CheckResult;
|
|
40
|
+
/**
|
|
41
|
+
* SDK proof verification. `'skipped'` when no SDK verifier is injected.
|
|
42
|
+
* `'pass'` when the SDK validates the proofs AND the returned
|
|
43
|
+
* uniqueIdentifier matches the statement's predicate.
|
|
44
|
+
*/
|
|
45
|
+
sdk_proof: CheckResult;
|
|
46
|
+
/**
|
|
47
|
+
* `'pass'` only when every enabled check passes; `'fail'` if any check
|
|
48
|
+
* fails; `'pending'` when one or more checks are `'skipped'`.
|
|
49
|
+
*/
|
|
50
|
+
overall: 'pass' | 'fail' | 'pending';
|
|
51
|
+
errors: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SdkVerifyInput {
|
|
55
|
+
proofs: unknown[];
|
|
56
|
+
originalQuery: unknown;
|
|
57
|
+
queryResult: unknown;
|
|
58
|
+
scope?: string;
|
|
59
|
+
devMode?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SdkVerifyResult {
|
|
63
|
+
verified: boolean;
|
|
64
|
+
uniqueIdentifier: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SdkVerifier {
|
|
68
|
+
verify(input: SdkVerifyInput): Promise<SdkVerifyResult>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface VerifyBundleDeps {
|
|
72
|
+
/** Inject a Rekor client to enable hash_match / inclusion / consistency checks. */
|
|
73
|
+
rekor?: RekorClient;
|
|
74
|
+
/** Inject a zkPassport SDK verifier to enable the sdk_proof check. */
|
|
75
|
+
sdkVerifier?: SdkVerifier;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface ParsedEntryBody {
|
|
79
|
+
payloadHashHex: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseEntryBody(bodyBase64: string): ParsedEntryBody {
|
|
83
|
+
const bytes = bytesToUtf8(base64ToBytes(bodyBase64));
|
|
84
|
+
const body = JSON.parse(bytes) as Record<string, unknown>;
|
|
85
|
+
const spec = body['spec'] as Record<string, unknown> | undefined;
|
|
86
|
+
const content = spec?.['content'] as Record<string, unknown> | undefined;
|
|
87
|
+
const payloadHash = content?.['payloadHash'] as Record<string, unknown> | undefined;
|
|
88
|
+
const value = payloadHash?.['value'];
|
|
89
|
+
if (typeof value !== 'string') {
|
|
90
|
+
throw new Error('Rekor entry body missing spec.content.payloadHash.value');
|
|
91
|
+
}
|
|
92
|
+
return { payloadHashHex: value };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Verify a passportsign bundle. Online checks (hash_match, inclusion_proof,
|
|
97
|
+
* root_consistency) require a {@link RekorClient}; without one they are
|
|
98
|
+
* marked `'skipped'`. SDK proof verification is Day 7 work and currently
|
|
99
|
+
* always returns `'pending_day_7'`.
|
|
100
|
+
*/
|
|
101
|
+
export async function verifyBundle(
|
|
102
|
+
bundle: PassportsignBundle,
|
|
103
|
+
deps: VerifyBundleDeps = {},
|
|
104
|
+
): Promise<BundleVerifyResult> {
|
|
105
|
+
validateBundle(bundle);
|
|
106
|
+
|
|
107
|
+
const result: BundleVerifyResult = {
|
|
108
|
+
hash_match: 'skipped',
|
|
109
|
+
inclusion_proof: 'skipped',
|
|
110
|
+
root_consistency: 'skipped',
|
|
111
|
+
sdk_proof: 'skipped',
|
|
112
|
+
overall: 'pending',
|
|
113
|
+
errors: [],
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// SDK proof verification (independent of rekor) — runs first because
|
|
117
|
+
// it can be done purely from the bundle.
|
|
118
|
+
if (deps.sdkVerifier) {
|
|
119
|
+
result.sdk_proof = await runSdkVerification(bundle, deps.sdkVerifier, result.errors);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!deps.rekor) {
|
|
123
|
+
result.overall = computeOverall(result);
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 1. Fetch the entry from Rekor (any operator's Rekor mirror would do).
|
|
128
|
+
let entry;
|
|
129
|
+
try {
|
|
130
|
+
entry = await deps.rekor.getEntry(bundle.rekor.log_entry_hash);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
result.errors.push(
|
|
133
|
+
`failed to fetch Rekor entry: ${err instanceof Error ? err.message : String(err)}`,
|
|
134
|
+
);
|
|
135
|
+
result.overall = 'fail';
|
|
136
|
+
result.hash_match = 'fail';
|
|
137
|
+
result.inclusion_proof = 'fail';
|
|
138
|
+
result.root_consistency = 'fail';
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. hash_match: bundle.statement bytes' sha256 must equal entry.body's payloadHash.
|
|
143
|
+
const statementBytes = hexToBytes(bundle.statement);
|
|
144
|
+
const expectedPayloadHash = sha256Hex(statementBytes);
|
|
145
|
+
let entryPayloadHash: string;
|
|
146
|
+
try {
|
|
147
|
+
entryPayloadHash = parseEntryBody(entry.body).payloadHashHex;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
result.errors.push(
|
|
150
|
+
`failed to parse Rekor entry body: ${err instanceof Error ? err.message : String(err)}`,
|
|
151
|
+
);
|
|
152
|
+
result.hash_match = 'fail';
|
|
153
|
+
result.inclusion_proof = 'fail';
|
|
154
|
+
result.root_consistency = 'fail';
|
|
155
|
+
result.overall = 'fail';
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
result.hash_match = expectedPayloadHash === entryPayloadHash ? 'pass' : 'fail';
|
|
159
|
+
if (result.hash_match === 'fail') {
|
|
160
|
+
result.errors.push(
|
|
161
|
+
`payloadHash mismatch: bundle says ${expectedPayloadHash}, Rekor entry has ${entryPayloadHash}`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 3. inclusion_proof: leaf hash = sha256(0x00 || decoded-body-bytes); verify against captured root.
|
|
166
|
+
const bodyBytes = base64ToBytes(entry.body);
|
|
167
|
+
const leaf = hashLeaf(bodyBytes);
|
|
168
|
+
const captured = bundle.rekor.inclusion_proof as {
|
|
169
|
+
hashes: string[];
|
|
170
|
+
logIndex: number;
|
|
171
|
+
treeSize: number;
|
|
172
|
+
rootHash: string;
|
|
173
|
+
};
|
|
174
|
+
const proofHashes = captured.hashes.map(hexToBytes);
|
|
175
|
+
const rootBytes = hexToBytes(captured.rootHash);
|
|
176
|
+
result.inclusion_proof = verifyInclusion(
|
|
177
|
+
leaf,
|
|
178
|
+
captured.logIndex,
|
|
179
|
+
captured.treeSize,
|
|
180
|
+
proofHashes,
|
|
181
|
+
rootBytes,
|
|
182
|
+
)
|
|
183
|
+
? 'pass'
|
|
184
|
+
: 'fail';
|
|
185
|
+
if (result.inclusion_proof === 'fail') {
|
|
186
|
+
result.errors.push('inclusion proof does not verify against captured root');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 4. root_consistency: captured root must be a prefix of current witnessed root.
|
|
190
|
+
try {
|
|
191
|
+
const logInfo = await deps.rekor.getLogInfo();
|
|
192
|
+
if (logInfo.treeSize < captured.treeSize) {
|
|
193
|
+
result.errors.push(
|
|
194
|
+
`current tree size ${logInfo.treeSize} is smaller than captured ${captured.treeSize} — log may have been rewound`,
|
|
195
|
+
);
|
|
196
|
+
result.root_consistency = 'fail';
|
|
197
|
+
} else if (logInfo.treeSize === captured.treeSize) {
|
|
198
|
+
// Same tree, just compare roots
|
|
199
|
+
result.root_consistency =
|
|
200
|
+
logInfo.rootHash === captured.rootHash ? 'pass' : 'fail';
|
|
201
|
+
if (result.root_consistency === 'fail') {
|
|
202
|
+
result.errors.push(
|
|
203
|
+
`root mismatch at same treeSize: captured ${captured.rootHash}, current ${logInfo.rootHash}`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
const proof = await deps.rekor.getConsistencyProof(
|
|
208
|
+
captured.treeSize,
|
|
209
|
+
logInfo.treeSize,
|
|
210
|
+
);
|
|
211
|
+
const proofBytes = proof.hashes.map(hexToBytes);
|
|
212
|
+
result.root_consistency = verifyConsistency(
|
|
213
|
+
captured.treeSize,
|
|
214
|
+
logInfo.treeSize,
|
|
215
|
+
rootBytes,
|
|
216
|
+
hexToBytes(logInfo.rootHash),
|
|
217
|
+
proofBytes,
|
|
218
|
+
)
|
|
219
|
+
? 'pass'
|
|
220
|
+
: 'fail';
|
|
221
|
+
if (result.root_consistency === 'fail') {
|
|
222
|
+
result.errors.push(
|
|
223
|
+
'consistency proof does not verify — captured root is not an ancestor of current root',
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch (err) {
|
|
228
|
+
result.errors.push(
|
|
229
|
+
`consistency check error: ${err instanceof Error ? err.message : String(err)}`,
|
|
230
|
+
);
|
|
231
|
+
result.root_consistency = 'fail';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
result.overall = computeOverall(result);
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function runSdkVerification(
|
|
239
|
+
bundle: PassportsignBundle,
|
|
240
|
+
sdkVerifier: SdkVerifier,
|
|
241
|
+
errors: string[],
|
|
242
|
+
): Promise<CheckResult> {
|
|
243
|
+
let payload;
|
|
244
|
+
try {
|
|
245
|
+
payload = unpackSdkPayload(bundle.proof_blob);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
errors.push(
|
|
248
|
+
`failed to unpack SDK payload from bundle.proof_blob: ${
|
|
249
|
+
err instanceof Error ? err.message : String(err)
|
|
250
|
+
}`,
|
|
251
|
+
);
|
|
252
|
+
return 'fail';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let sdkResult: SdkVerifyResult;
|
|
256
|
+
try {
|
|
257
|
+
sdkResult = await sdkVerifier.verify({
|
|
258
|
+
proofs: payload.proofs,
|
|
259
|
+
originalQuery: payload.original_query,
|
|
260
|
+
queryResult: payload.query_result,
|
|
261
|
+
devMode: payload.dev_mode,
|
|
262
|
+
});
|
|
263
|
+
} catch (err) {
|
|
264
|
+
errors.push(`SDK verify threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
265
|
+
return 'fail';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (sdkResult.verified !== true) {
|
|
269
|
+
errors.push(`SDK reported verified=${String(sdkResult.verified)}`);
|
|
270
|
+
return 'fail';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// The returned uniqueIdentifier must match the statement's predicate.
|
|
274
|
+
let statementUniqueId: string | undefined;
|
|
275
|
+
try {
|
|
276
|
+
const statementBytes = bytesToUtf8(hexToBytes(bundle.statement));
|
|
277
|
+
const parsed = JSON.parse(statementBytes) as {
|
|
278
|
+
predicate?: { unique_identifier?: string };
|
|
279
|
+
};
|
|
280
|
+
statementUniqueId = parsed.predicate?.unique_identifier;
|
|
281
|
+
} catch {
|
|
282
|
+
statementUniqueId = undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!statementUniqueId) {
|
|
286
|
+
errors.push('could not extract unique_identifier from bundle.statement');
|
|
287
|
+
return 'fail';
|
|
288
|
+
}
|
|
289
|
+
if (sdkResult.uniqueIdentifier !== statementUniqueId) {
|
|
290
|
+
errors.push(
|
|
291
|
+
`SDK uniqueIdentifier ${String(sdkResult.uniqueIdentifier)} does not match statement.predicate.unique_identifier ${statementUniqueId}`,
|
|
292
|
+
);
|
|
293
|
+
return 'fail';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return 'pass';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function computeOverall(r: BundleVerifyResult): 'pass' | 'fail' | 'pending' {
|
|
300
|
+
const all = [r.hash_match, r.inclusion_proof, r.root_consistency, r.sdk_proof];
|
|
301
|
+
if (all.some((s) => s === 'fail')) return 'fail';
|
|
302
|
+
if (all.every((s) => s === 'pass')) return 'pass';
|
|
303
|
+
return 'pending';
|
|
304
|
+
}
|