@entpoind/verify 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/CHANGELOG.md +24 -0
- package/README.md +127 -1
- package/dist/canonical-json.js +10 -9
- package/dist/ed25519ph.d.ts +25 -0
- package/dist/ed25519ph.js +171 -0
- package/dist/index.d.ts +92 -1
- package/dist/index.js +131 -2
- package/dist/merkle.d.ts +6 -0
- package/dist/merkle.js +18 -4
- package/dist/witness.d.ts +175 -0
- package/dist/witness.js +523 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
* Deno, Bun, and Cloudflare Workers.
|
|
16
16
|
*/
|
|
17
17
|
import { canonicalJcs } from "./canonical-json.js";
|
|
18
|
-
import { verifyMerkleInclusion } from "./merkle.js";
|
|
18
|
+
import { merkleLeafHash, sha256Hex, verifyMerkleInclusion, verifyMerkleInclusionFromHash } from "./merkle.js";
|
|
19
|
+
import { checkLedgerWitness } from "./witness.js";
|
|
19
20
|
export { canonicalJcs, NonCanonicalizableError } from "./canonical-json.js";
|
|
20
|
-
export { merkleLeafHash, verifyMerkleInclusion } from "./merkle.js";
|
|
21
|
+
export { merkleLeafHash, verifyMerkleInclusion, verifyMerkleInclusionFromHash } from "./merkle.js";
|
|
22
|
+
export { verifyEd25519ph } from "./ed25519ph.js";
|
|
23
|
+
export { DEFAULT_REKOR_URL, WITNESS_KIND, } from "./witness.js";
|
|
21
24
|
/**
|
|
22
25
|
* Where the production key set is published. The same document is mirrored on
|
|
23
26
|
* the Tools marketing domain; either origin serves it CORS-open.
|
|
@@ -207,6 +210,132 @@ export async function verifyBundle(input, publicKeys) {
|
|
|
207
210
|
export async function verifyReceiptOnline(input, keysUrl = DEFAULT_KEYS_URL) {
|
|
208
211
|
return verifyReceipt(input, await fetchToolsKeys(keysUrl));
|
|
209
212
|
}
|
|
213
|
+
const LEDGER_ROOT_KIND = "entpoind.tools.receipt-ledger.root";
|
|
214
|
+
const HEX64 = /^[0-9a-f]{64}$/;
|
|
215
|
+
function isLedgerProof(x) {
|
|
216
|
+
if (!x || typeof x !== "object")
|
|
217
|
+
return false;
|
|
218
|
+
const p = x;
|
|
219
|
+
const str = (k) => typeof p[k] === "string" && p[k].length > 0;
|
|
220
|
+
if (p.kind !== "entpoind.tools.receipt-ledger.proof" || p.v !== 1)
|
|
221
|
+
return false;
|
|
222
|
+
for (const k of ["receiptId", "batchId", "rootSignature", "signatureKeyId", "region", "retentionClass", "bucketHour", "retainUntil"])
|
|
223
|
+
if (!str(k))
|
|
224
|
+
return false;
|
|
225
|
+
if (typeof p.root !== "string" || !HEX64.test(p.root) || typeof p.leafHash !== "string" || !HEX64.test(p.leafHash))
|
|
226
|
+
return false;
|
|
227
|
+
if (!Number.isInteger(p.leafIndex) || !Number.isInteger(p.treeSize) || p.treeSize < 1 || p.leafIndex < 0 || p.leafIndex >= p.treeSize)
|
|
228
|
+
return false;
|
|
229
|
+
if (!Array.isArray(p.proof))
|
|
230
|
+
return false;
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The positions (`left`/`right` of the sibling) a proof for leaf `index` in a tree of `size` leaves must have, from the
|
|
235
|
+
* leaf up. An odd node at a level is carried up and contributes no step. Checking the served path against this binds the
|
|
236
|
+
* claimed leaf index and tree size to the proof: a path for a different place cannot be passed off as this one's.
|
|
237
|
+
*/
|
|
238
|
+
function expectedPositions(index, size) {
|
|
239
|
+
const out = [];
|
|
240
|
+
let idx = index;
|
|
241
|
+
let n = size;
|
|
242
|
+
while (n > 1) {
|
|
243
|
+
const sibling = idx % 2 === 0 ? idx + 1 : idx - 1;
|
|
244
|
+
if (sibling < n)
|
|
245
|
+
out.push(idx % 2 === 0 ? "right" : "left");
|
|
246
|
+
idx = Math.floor(idx / 2);
|
|
247
|
+
n = Math.ceil(n / 2);
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
/** The bytes the region's receipt key signed over a batch root. */
|
|
252
|
+
export function ledgerRootMessage(p) {
|
|
253
|
+
return canonicalJcs({
|
|
254
|
+
kind: LEDGER_ROOT_KIND,
|
|
255
|
+
v: 1,
|
|
256
|
+
region: p.region,
|
|
257
|
+
batchId: p.batchId,
|
|
258
|
+
retentionClass: p.retentionClass,
|
|
259
|
+
bucketHour: p.bucketHour,
|
|
260
|
+
treeSize: p.treeSize,
|
|
261
|
+
root: p.root,
|
|
262
|
+
retainUntil: p.retainUntil,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Verify a receipt-ledger proof offline. Pure public-key crypto: no account, no network, and no S3 access.
|
|
267
|
+
*
|
|
268
|
+
* 1. (with `receipt`) the envelope the ledger locked is RECOMPUTED from your saved receipt (its canonical form,
|
|
269
|
+
* hashed, with its signature and key id) and must be the leaf the proof starts from. Without a receipt the leaf is
|
|
270
|
+
* the envelope the proof carries, or just the leaf hash; `receiptBound` says which.
|
|
271
|
+
* 2. the sibling path has exactly the shape its leaf index and tree size require, and folds, leaf to root;
|
|
272
|
+
* 3. the root carries a valid signature by the region's receipt key (the key set that verifies your receipts), over a
|
|
273
|
+
* message that also names the batch, the tree size and the date the lock lapses.
|
|
274
|
+
*
|
|
275
|
+
* It proves the copy was FILED, under that root, by that key. That the object is still locked at S3 is S3's guarantee,
|
|
276
|
+
* checked there (GET object-retention on `s3.key` / `s3.versionId`) by someone holding read access to the bucket.
|
|
277
|
+
*
|
|
278
|
+
* Once the root verifies, the proof's `witness` (the root's entry in the public Rekor log) is checked too and reported in
|
|
279
|
+
* `witness` (see witness.ts): offline, that its hashes are the root message's, that it is by the root's key and its
|
|
280
|
+
* Ed25519ph signature verifies, and that the copy of the log's answer is that entry. With `witnessLog` (opt-in, the only
|
|
281
|
+
* network call here) the entry is also fetched from the log and checked there, with the log's own signatures.
|
|
282
|
+
*/
|
|
283
|
+
export async function verifyLedgerProof(input, publicKeys, options = {}) {
|
|
284
|
+
const proof = typeof input === "string" ? safeParse(input) : input;
|
|
285
|
+
if (!isLedgerProof(proof))
|
|
286
|
+
return { ok: false, reason: "malformed_proof" };
|
|
287
|
+
// 1. the leaf
|
|
288
|
+
let receiptBound = false;
|
|
289
|
+
let leaf = null;
|
|
290
|
+
let receipt = typeof options.receipt === "string" ? safeParse(options.receipt) : options.receipt;
|
|
291
|
+
if (options.receipt !== undefined) {
|
|
292
|
+
if (!isReceiptEnvelope(receipt) && receipt && typeof receipt === "object")
|
|
293
|
+
receipt = receipt.receipt;
|
|
294
|
+
if (!isReceiptEnvelope(receipt))
|
|
295
|
+
return { ok: false, reason: "malformed_envelope" };
|
|
296
|
+
if (receipt.receiptId !== proof.receiptId)
|
|
297
|
+
return { ok: false, reason: "not_this_receipt" };
|
|
298
|
+
}
|
|
299
|
+
if (isReceiptEnvelope(receipt) && receipt.redacted !== true) {
|
|
300
|
+
const envelope = {
|
|
301
|
+
v: 1,
|
|
302
|
+
receipt_id: receipt.receiptId,
|
|
303
|
+
kind: receipt.type,
|
|
304
|
+
receipt_hash: await sha256Hex(canonicalReceiptString(receipt)),
|
|
305
|
+
signature: receipt.signature,
|
|
306
|
+
signature_key_id: receipt.signatureKeyId,
|
|
307
|
+
issued_at: receipt.issuedAt,
|
|
308
|
+
region: proof.region,
|
|
309
|
+
retain_until: proof.retainUntil,
|
|
310
|
+
};
|
|
311
|
+
leaf = canonicalJcs(envelope);
|
|
312
|
+
receiptBound = true;
|
|
313
|
+
}
|
|
314
|
+
else if (proof.envelope) {
|
|
315
|
+
leaf = canonicalJcs(proof.envelope);
|
|
316
|
+
}
|
|
317
|
+
if (leaf !== null && (await merkleLeafHash(leaf)) !== proof.leafHash)
|
|
318
|
+
return { ok: false, reason: "receipt_not_in_batch" };
|
|
319
|
+
// 2. the path
|
|
320
|
+
const want = expectedPositions(proof.leafIndex, proof.treeSize);
|
|
321
|
+
if (proof.proof.length !== want.length || proof.proof.some((s, i) => !s || s.position !== want[i]))
|
|
322
|
+
return { ok: false, reason: "proof_shape_mismatch" };
|
|
323
|
+
if (!(await verifyMerkleInclusionFromHash(proof.leafHash, proof.proof, proof.root)))
|
|
324
|
+
return { ok: false, reason: "not_in_root" };
|
|
325
|
+
// 3. the signed root
|
|
326
|
+
const key = findKey(publicKeys, proof.signatureKeyId);
|
|
327
|
+
if (!key)
|
|
328
|
+
return { ok: false, reason: "unknown_key" };
|
|
329
|
+
const check = await checkSignature(key, ledgerRootMessage(proof), proof.rootSignature);
|
|
330
|
+
if (check === "unsupported")
|
|
331
|
+
return { ok: false, reason: "unsupported_runtime" };
|
|
332
|
+
if (check === "invalid")
|
|
333
|
+
return { ok: false, reason: "bad_root_signature" };
|
|
334
|
+
// 4. the public witness: reported, never a verdict on the proof
|
|
335
|
+
const witnessLog = options.witnessLog === true ? {} : options.witnessLog || undefined;
|
|
336
|
+
const witness = await checkLedgerWitness(proof.witness, { rootMessage: ledgerRootMessage(proof), signatureKeyId: proof.signatureKeyId, publicKeyX: key.x }, witnessLog);
|
|
337
|
+
return { ok: true, receiptBound, batchId: proof.batchId, leafIndex: proof.leafIndex, treeSize: proof.treeSize, root: proof.root, signatureKeyId: proof.signatureKeyId, retainUntil: proof.retainUntil, witness };
|
|
338
|
+
}
|
|
210
339
|
// Only a well-formed Ed25519 public key is ever used, whatever the caller's key list holds.
|
|
211
340
|
function findKey(keys, kid) {
|
|
212
341
|
return keys.find((k) => !!k && k.kid === kid && k.kty === "OKP" && k.crv === "Ed25519" && typeof k.x === "string");
|
package/dist/merkle.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* node hash = SHA-256( 0x01 ‖ leftBytes ‖ rightBytes )
|
|
7
7
|
* odd nodes are carried up unchanged (never duplicated).
|
|
8
8
|
*/
|
|
9
|
+
export declare function sha256Hex(input: string | Uint8Array): Promise<string>;
|
|
9
10
|
export declare function merkleLeafHash(leaf: string): Promise<string>;
|
|
10
11
|
export type MerkleProofStep = {
|
|
11
12
|
sibling: string;
|
|
@@ -16,3 +17,8 @@ export type MerkleProofStep = {
|
|
|
16
17
|
* folds along `proof` into `root`. Never throws on a malformed proof.
|
|
17
18
|
*/
|
|
18
19
|
export declare function verifyMerkleInclusion(leaf: string, proof: ReadonlyArray<MerkleProofStep>, root: string): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* The same fold, starting from a leaf HASH (hex) rather than the leaf string — for a proof whose leaf string is not
|
|
22
|
+
* available (a ledger proof for a redacted receipt). Never throws on a malformed proof.
|
|
23
|
+
*/
|
|
24
|
+
export declare function verifyMerkleInclusionFromHash(leafHash: string, proof: ReadonlyArray<MerkleProofStep>, root: string): Promise<boolean>;
|
package/dist/merkle.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* odd nodes are carried up unchanged (never duplicated).
|
|
8
8
|
*/
|
|
9
9
|
const ENC = new TextEncoder();
|
|
10
|
-
async function sha256Hex(input) {
|
|
10
|
+
export async function sha256Hex(input) {
|
|
11
11
|
const bytes = typeof input === "string" ? ENC.encode(input) : input;
|
|
12
12
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
13
13
|
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
@@ -40,12 +40,26 @@ async function merkleNodeHash(leftHex, rightHex) {
|
|
|
40
40
|
*/
|
|
41
41
|
export async function verifyMerkleInclusion(leaf, proof, root) {
|
|
42
42
|
try {
|
|
43
|
-
if (typeof leaf !== "string"
|
|
43
|
+
if (typeof leaf !== "string")
|
|
44
|
+
return false;
|
|
45
|
+
return await verifyMerkleInclusionFromHash(await merkleLeafHash(leaf), proof, root);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The same fold, starting from a leaf HASH (hex) rather than the leaf string — for a proof whose leaf string is not
|
|
53
|
+
* available (a ledger proof for a redacted receipt). Never throws on a malformed proof.
|
|
54
|
+
*/
|
|
55
|
+
export async function verifyMerkleInclusionFromHash(leafHash, proof, root) {
|
|
56
|
+
try {
|
|
57
|
+
if (typeof leafHash !== "string" || typeof root !== "string" || !Array.isArray(proof))
|
|
44
58
|
return false;
|
|
45
59
|
const want = root.toLowerCase();
|
|
46
|
-
if (!SHA256_HEX.test(want))
|
|
60
|
+
if (!SHA256_HEX.test(want) || !SHA256_HEX.test(leafHash.toLowerCase()))
|
|
47
61
|
return false;
|
|
48
|
-
let h =
|
|
62
|
+
let h = leafHash.toLowerCase();
|
|
49
63
|
for (const step of proof) {
|
|
50
64
|
if (!step || (step.position !== "left" && step.position !== "right"))
|
|
51
65
|
return false;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @entpoind/verify — the receipt ledger's public witness: a batch root's entry in the Sigstore Rekor log.
|
|
3
|
+
*
|
|
4
|
+
* What the ledger logs (runtime/shared/rekor-witness.ts, by the hourly cron job):
|
|
5
|
+
*
|
|
6
|
+
* rootMessage the canonical JSON the batch's `rootSignature` is over (ledgerRootMessage)
|
|
7
|
+
* A SHA-512(UTF-8 rootMessage) `witness.artifactSha512`
|
|
8
|
+
* data.hash SHA-512(A) `witness.rekorSha512`, the hash in the `hashedrekord` entry
|
|
9
|
+
* signature Ed25519ph by the region's receipt key, the SAME key (`kid`) as the root's, with A as the message,
|
|
10
|
+
* so its prehash is SHA-512(A) = data.hash
|
|
11
|
+
*
|
|
12
|
+
* Offline (always): the witness's hashes are recomputed from the root message, the witness is by the root's key, its
|
|
13
|
+
* Ed25519ph signature verifies over the recomputed hash with that key, and the copy of the log's answer the proof carries
|
|
14
|
+
* (`body`, `entryUuid`, `inclusionProof`) is that entry and hangs together. That copy came from Entpoind, so it shows the
|
|
15
|
+
* witness is consistent, not that the log holds it.
|
|
16
|
+
*
|
|
17
|
+
* Online (opt-in): the entry is fetched from the log itself and checked again, with the log's own inclusion proof,
|
|
18
|
+
* signed checkpoint and signed entry timestamp verified against the log's public key (ECDSA P-256, as rekor.sigstore.dev
|
|
19
|
+
* uses). The key is pinned by the caller, or fetched from the log, which is then trust on first use; the result says which.
|
|
20
|
+
*
|
|
21
|
+
* Nothing here can fail the receipt or the ledger proof: a witness that is absent, off, pending, inconsistent or
|
|
22
|
+
* unreachable is reported as exactly that, next to a ledger proof that verified on its own. `off` means the receipt's
|
|
23
|
+
* workspace had turned the public witness off before the receipt was batched: the batch's root was never submitted, by
|
|
24
|
+
* the workspace's choice. It is not a failure.
|
|
25
|
+
*/
|
|
26
|
+
export declare const WITNESS_KIND = "rekor.hashedrekord.v0.0.1";
|
|
27
|
+
export declare const DEFAULT_REKOR_URL = "https://rekor.sigstore.dev";
|
|
28
|
+
export declare const WITNESS_LOG_TIMEOUT_MS = 10000;
|
|
29
|
+
/** `witness` in the ledger proof, as the Tools Worker serves it. */
|
|
30
|
+
export type LedgerWitness =
|
|
31
|
+
/** The workspace had turned the public witness off when the batch was built; its root is never submitted. */
|
|
32
|
+
{
|
|
33
|
+
status: "off";
|
|
34
|
+
} | {
|
|
35
|
+
status: "pending";
|
|
36
|
+
since: string;
|
|
37
|
+
attempts: number;
|
|
38
|
+
lastError: string | null;
|
|
39
|
+
nextAttemptAt: string | null;
|
|
40
|
+
} | {
|
|
41
|
+
status: "logged";
|
|
42
|
+
kind: string;
|
|
43
|
+
log: string;
|
|
44
|
+
logIndex: number;
|
|
45
|
+
entryUuid: string;
|
|
46
|
+
integratedTime: number;
|
|
47
|
+
integratedAt: string;
|
|
48
|
+
searchUrl: string;
|
|
49
|
+
entryUrl: string;
|
|
50
|
+
artifactSha512: string;
|
|
51
|
+
rekorSha512: string;
|
|
52
|
+
signature: string;
|
|
53
|
+
signatureKeyId: string;
|
|
54
|
+
logId: string;
|
|
55
|
+
body: string;
|
|
56
|
+
inclusionProof: {
|
|
57
|
+
logIndex: number;
|
|
58
|
+
rootHash: string;
|
|
59
|
+
treeSize: number;
|
|
60
|
+
hashes: string[];
|
|
61
|
+
checkpoint: string;
|
|
62
|
+
} | null;
|
|
63
|
+
signedEntryTimestamp: string;
|
|
64
|
+
};
|
|
65
|
+
/** Why a logged witness is not consistent with the ledger proof. Each is exact: several can be reported at once. */
|
|
66
|
+
export type WitnessProblem =
|
|
67
|
+
/** A field the check needs is missing or of the wrong type. */
|
|
68
|
+
"malformed_witness" | "unknown_witness_kind"
|
|
69
|
+
/** `artifactSha512` is not SHA-512 of the root message. */
|
|
70
|
+
| "artifact_hash_mismatch"
|
|
71
|
+
/** `rekorSha512` is not SHA-512 of the artifact: not the hash the root message gives. */
|
|
72
|
+
| "rekor_hash_mismatch"
|
|
73
|
+
/** `signatureKeyId` is not the key that signed the root. */
|
|
74
|
+
| "not_the_root_key"
|
|
75
|
+
/** The Ed25519ph signature does not verify over the recomputed hash with the root's key. */
|
|
76
|
+
| "bad_witness_signature"
|
|
77
|
+
/** The log entry (`body`) is not a decodable `hashedrekord`. */
|
|
78
|
+
| "entry_body_malformed"
|
|
79
|
+
/** The entry's `data.hash` is not `{ sha512, <the recomputed hash> }`. */
|
|
80
|
+
| "entry_hash_mismatch"
|
|
81
|
+
/** The entry's public key is not the root's key. */
|
|
82
|
+
| "entry_key_mismatch"
|
|
83
|
+
/** The entry's signature is not the witness signature. */
|
|
84
|
+
| "entry_signature_mismatch"
|
|
85
|
+
/** `entryUuid` does not end in the RFC 6962 leaf hash of the entry. */
|
|
86
|
+
| "entry_uuid_mismatch"
|
|
87
|
+
/** The copied inclusion proof does not fold the entry to the root hash it names. */
|
|
88
|
+
| "inclusion_proof_invalid"
|
|
89
|
+
/** `integratedAt` is not the instant `integratedTime` names. */
|
|
90
|
+
| "integrated_at_mismatch";
|
|
91
|
+
/** What the log itself said, when it was asked (`witnessLog`). */
|
|
92
|
+
export type WitnessLogProblem =
|
|
93
|
+
/** The log has no entry with that UUID (404). */
|
|
94
|
+
"entry_not_in_log" | "bad_log_response" | "entry_uuid_mismatch" | "entry_body_malformed" | "entry_hash_mismatch" | "entry_key_mismatch" | "bad_witness_signature"
|
|
95
|
+
/** The log's `logIndex` / `integratedTime` are not the ones the proof states. */
|
|
96
|
+
| "log_index_mismatch" | "integrated_time_mismatch"
|
|
97
|
+
/** The log's public key could not be fetched, is not ECDSA P-256, or does not hash to the entry's `logID`. */
|
|
98
|
+
| "log_key_unavailable" | "log_key_unsupported" | "log_key_not_log_id" | "bad_signed_entry_timestamp" | "no_inclusion_proof" | "inclusion_proof_invalid" | "checkpoint_malformed"
|
|
99
|
+
/** The checkpoint names another tree size or root hash than the inclusion proof. */
|
|
100
|
+
| "checkpoint_mismatch" | "bad_checkpoint_signature";
|
|
101
|
+
export type WitnessPart = "verified" | "failed" | "not_checked";
|
|
102
|
+
export type WitnessLogCheck =
|
|
103
|
+
/** The log was not asked, or did not answer: nothing about the witness was judged. */
|
|
104
|
+
{
|
|
105
|
+
checked: false;
|
|
106
|
+
logUrl: string;
|
|
107
|
+
reason: "log_url_not_https" | "no_entry_uuid" | "log_unreachable" | "log_error";
|
|
108
|
+
status?: number;
|
|
109
|
+
} | {
|
|
110
|
+
checked: true;
|
|
111
|
+
logUrl: string;
|
|
112
|
+
/** True only when every part below is `verified` and there is no problem. */
|
|
113
|
+
ok: boolean;
|
|
114
|
+
problems: WitnessLogProblem[];
|
|
115
|
+
/** `pinned`: the caller's `logPublicKey`. `fetched`: from the log's own /api/v1/log/publicKey, trust on first use. */
|
|
116
|
+
logKey: "pinned" | "fetched" | "unavailable";
|
|
117
|
+
/** The log's time for the entry (Unix seconds), as the log answered. */
|
|
118
|
+
integratedTime: number | null;
|
|
119
|
+
inclusionProof: WitnessPart;
|
|
120
|
+
checkpoint: WitnessPart;
|
|
121
|
+
signedEntryTimestamp: WitnessPart;
|
|
122
|
+
};
|
|
123
|
+
export type WitnessReport =
|
|
124
|
+
/** The proof carries no witness: the batch has not been submitted yet, or the witness is not switched on. */
|
|
125
|
+
{
|
|
126
|
+
status: "absent";
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Not witnessed, by the workspace's choice: its owner or admin had turned the public witness off before this receipt was
|
|
130
|
+
* batched, so the batch's root was never submitted to the log. Not a failure, and nothing to retry.
|
|
131
|
+
*/
|
|
132
|
+
| {
|
|
133
|
+
status: "off";
|
|
134
|
+
}
|
|
135
|
+
/** Tried, not in the log yet. The batch itself is complete. */
|
|
136
|
+
| {
|
|
137
|
+
status: "pending";
|
|
138
|
+
since: string | null;
|
|
139
|
+
attempts: number | null;
|
|
140
|
+
lastError: string | null;
|
|
141
|
+
nextAttemptAt: string | null;
|
|
142
|
+
}
|
|
143
|
+
/** A `witness` whose status is none of the three the API defines (`off`, `pending`, `logged`). */
|
|
144
|
+
| {
|
|
145
|
+
status: "malformed";
|
|
146
|
+
} | {
|
|
147
|
+
status: "logged";
|
|
148
|
+
/** The witness is the root's: its hashes, key and signature, and the copy of the log's answer, all check out. */
|
|
149
|
+
consistent: boolean;
|
|
150
|
+
problems: WitnessProblem[];
|
|
151
|
+
log: string | null;
|
|
152
|
+
logIndex: number | null;
|
|
153
|
+
entryUuid: string | null;
|
|
154
|
+
integratedAt: string | null;
|
|
155
|
+
searchUrl: string | null;
|
|
156
|
+
/** Present only when the log was asked (`witnessLog`). */
|
|
157
|
+
logCheck?: WitnessLogCheck;
|
|
158
|
+
};
|
|
159
|
+
export type WitnessLogOptions = {
|
|
160
|
+
/** Overrides the proof's `witness.log`. Must be https. Default: the proof's, else https://rekor.sigstore.dev. */
|
|
161
|
+
logUrl?: string;
|
|
162
|
+
/** The log's public key (SubjectPublicKeyInfo PEM) to verify its signatures with. Without it, the key is fetched from the log. */
|
|
163
|
+
logPublicKey?: string;
|
|
164
|
+
fetch?: typeof fetch;
|
|
165
|
+
timeoutMs?: number;
|
|
166
|
+
};
|
|
167
|
+
export type WitnessContext = {
|
|
168
|
+
/** The message the root signature is over, rebuilt from the proof. */
|
|
169
|
+
rootMessage: string;
|
|
170
|
+
/** The root's key id, and its key from the published set (already used to verify the root). */
|
|
171
|
+
signatureKeyId: string;
|
|
172
|
+
publicKeyX: string;
|
|
173
|
+
};
|
|
174
|
+
/** Never throws: anything the witness holds that cannot be checked is a problem, reported. */
|
|
175
|
+
export declare function checkLedgerWitness(witness: unknown, ctx: WitnessContext, log?: WitnessLogOptions): Promise<WitnessReport>;
|