@intyga/verify 0.0.0-bootstrap.0 → 1.0.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 +162 -0
- package/LICENSE +201 -0
- package/README.md +296 -2
- package/dist/approval-policy.d.ts +37 -0
- package/dist/approval-policy.js +149 -0
- package/dist/index.d.ts +803 -0
- package/dist/index.js +2236 -0
- package/dist/ledger-anchor.d.ts +195 -0
- package/dist/ledger-anchor.js +314 -0
- package/dist/ledger-bundle.d.ts +235 -0
- package/dist/ledger-bundle.js +419 -0
- package/dist/ledger-chain.d.ts +58 -0
- package/dist/ledger-chain.js +121 -0
- package/dist/ledger-evidence.d.ts +193 -0
- package/dist/ledger-evidence.js +613 -0
- package/dist/ledger-leaf.d.ts +25 -0
- package/dist/ledger-leaf.js +44 -0
- package/dist/ledger-merkle.d.ts +46 -0
- package/dist/ledger-merkle.js +183 -0
- package/dist/ledger-proof.d.ts +40 -0
- package/dist/ledger-proof.js +29 -0
- package/dist/ledger-rekor.d.ts +49 -0
- package/dist/ledger-rekor.js +187 -0
- package/dist/ledger-rfc3161.d.ts +25 -0
- package/dist/ledger-rfc3161.js +345 -0
- package/dist/ledger-signature.d.ts +16 -0
- package/dist/ledger-signature.js +61 -0
- package/package.json +57 -4
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export declare const CHAIN_TAG = 4;
|
|
2
|
+
/** The genesis predecessor. No real chain hash can collide: those are always 64 hex characters. */
|
|
3
|
+
export declare const GENESIS_PREV_CHAIN_HASH = "";
|
|
4
|
+
/** One line of a published `roots.jsonl` (v2), as far as chain verification is concerned. */
|
|
5
|
+
export interface RootsChainEntry {
|
|
6
|
+
seqStart: string;
|
|
7
|
+
seqEnd: string;
|
|
8
|
+
entryCount: number;
|
|
9
|
+
root: string;
|
|
10
|
+
anchoredAt: string;
|
|
11
|
+
prevChainHash?: string;
|
|
12
|
+
chainHash?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ChainInput {
|
|
15
|
+
prevChainHash: string;
|
|
16
|
+
root: string;
|
|
17
|
+
seqStart: string;
|
|
18
|
+
seqEnd: string;
|
|
19
|
+
entryCount: number;
|
|
20
|
+
anchoredAt: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The canonical chain preimage: RFC 8785 JCS of a 6-element array of STRINGS. Every element is
|
|
24
|
+
* stringified — including counts — so JCS reduces to plain `JSON.stringify` and no verifier has to
|
|
25
|
+
* implement RFC 8785 number canonicalization. `anchorPreimage` makes the same trade.
|
|
26
|
+
*/
|
|
27
|
+
export declare function chainPreimage(c: ChainInput): string;
|
|
28
|
+
/** The chain hash: `SHA-256(0x04 || UTF8(chainPreimage))`, hex. */
|
|
29
|
+
export declare function chainHash(c: ChainInput): string;
|
|
30
|
+
export interface ChainVerification {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
/** Entries whose chain hash recomputed AND linked to their predecessor. */
|
|
33
|
+
verifiedCount: number;
|
|
34
|
+
/** Index of the first entry that failed, or -1 when everything verified. */
|
|
35
|
+
brokenAt: number;
|
|
36
|
+
/**
|
|
37
|
+
* True when the entries carry no chain fields at all — a pre-chain (v1) roots file. Reported
|
|
38
|
+
* separately from a broken chain because "this file is older than the chain" and "someone edited
|
|
39
|
+
* this file" are completely different findings and must never be conflated in an alert.
|
|
40
|
+
*/
|
|
41
|
+
unchained: boolean;
|
|
42
|
+
reason?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify a published roots sequence: recompute every chain hash, check each entry links to its
|
|
46
|
+
* predecessor, and check the covered seq ranges advance without overlapping.
|
|
47
|
+
*
|
|
48
|
+
* On global seq: `AuditLog.seq` is a Postgres `autoincrement()` sequence, and Postgres sequences are
|
|
49
|
+
* NON-transactional — a rolled-back audit insert burns its value permanently. Global seq therefore
|
|
50
|
+
* has legitimate holes in healthy operation, which is exactly why DEWP uses the transactionally
|
|
51
|
+
* allocated per-tenant `tenantSeq` for gapless completeness and calls global `seq` "ordering only".
|
|
52
|
+
* So this checks `seqStart(N+1) > seqEnd(N)` (monotonic, non-overlapping) and NOT `== seqEnd(N) + 1`:
|
|
53
|
+
* the strict form would fire on ordinary rollbacks and train operators to ignore the alarm. Deletion
|
|
54
|
+
* of an entry is caught by the chain link, which is the mechanism that actually detects it.
|
|
55
|
+
*
|
|
56
|
+
* `entries` MUST be in published order (ascending `seqEnd`), which is the order they appear in the file.
|
|
57
|
+
*/
|
|
58
|
+
export declare function verifyRootsChain(entries: RootsChainEntry[]): ChainVerification;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
// Checkpoint continuity chain (DEWP §5.4) — the verification half. Zero deps beyond node:crypto.
|
|
3
|
+
//
|
|
4
|
+
// An inclusion proof says "this event is under daily root R". It says nothing about whether the
|
|
5
|
+
// SEQUENCE of daily roots was rewritten — a published roots file with one day silently replaced or
|
|
6
|
+
// removed is internally consistent and verifies fine. The chain closes that hole: each checkpoint
|
|
7
|
+
// commits to its predecessor under the 0x04 domain tag, so the sequence can be extended but not
|
|
8
|
+
// edited or reordered without breaking every link after the edit.
|
|
9
|
+
//
|
|
10
|
+
// SCOPE — read this before quoting it as a consistency proof. This is NOT an RFC 6962 consistency
|
|
11
|
+
// proof. It proves the root SEQUENCE is unrewritten. It does not prove the leaves under an earlier
|
|
12
|
+
// root are unchanged; that comes from each root being independently anchored to an external log
|
|
13
|
+
// (Rekor / RFC 3161) that the producer cannot edit. Chain + external anchors together are what make
|
|
14
|
+
// history-rewriting detectable — neither alone is sufficient.
|
|
15
|
+
//
|
|
16
|
+
// MUST stay byte-identical to the producer in @intyga/db (chain.ts).
|
|
17
|
+
export const CHAIN_TAG = 0x04;
|
|
18
|
+
/** The genesis predecessor. No real chain hash can collide: those are always 64 hex characters. */
|
|
19
|
+
export const GENESIS_PREV_CHAIN_HASH = "";
|
|
20
|
+
/**
|
|
21
|
+
* The canonical chain preimage: RFC 8785 JCS of a 6-element array of STRINGS. Every element is
|
|
22
|
+
* stringified — including counts — so JCS reduces to plain `JSON.stringify` and no verifier has to
|
|
23
|
+
* implement RFC 8785 number canonicalization. `anchorPreimage` makes the same trade.
|
|
24
|
+
*/
|
|
25
|
+
export function chainPreimage(c) {
|
|
26
|
+
return JSON.stringify([c.prevChainHash, c.root, c.seqStart, c.seqEnd, String(c.entryCount), c.anchoredAt]);
|
|
27
|
+
}
|
|
28
|
+
/** The chain hash: `SHA-256(0x04 || UTF8(chainPreimage))`, hex. */
|
|
29
|
+
export function chainHash(c) {
|
|
30
|
+
return crypto
|
|
31
|
+
.createHash("sha256")
|
|
32
|
+
.update(Buffer.from([CHAIN_TAG]))
|
|
33
|
+
.update(Buffer.from(chainPreimage(c), "utf8"))
|
|
34
|
+
.digest("hex");
|
|
35
|
+
}
|
|
36
|
+
function fail(brokenAt, reason, verifiedCount) {
|
|
37
|
+
return { ok: false, verifiedCount, brokenAt, unchained: false, reason };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Verify a published roots sequence: recompute every chain hash, check each entry links to its
|
|
41
|
+
* predecessor, and check the covered seq ranges advance without overlapping.
|
|
42
|
+
*
|
|
43
|
+
* On global seq: `AuditLog.seq` is a Postgres `autoincrement()` sequence, and Postgres sequences are
|
|
44
|
+
* NON-transactional — a rolled-back audit insert burns its value permanently. Global seq therefore
|
|
45
|
+
* has legitimate holes in healthy operation, which is exactly why DEWP uses the transactionally
|
|
46
|
+
* allocated per-tenant `tenantSeq` for gapless completeness and calls global `seq` "ordering only".
|
|
47
|
+
* So this checks `seqStart(N+1) > seqEnd(N)` (monotonic, non-overlapping) and NOT `== seqEnd(N) + 1`:
|
|
48
|
+
* the strict form would fire on ordinary rollbacks and train operators to ignore the alarm. Deletion
|
|
49
|
+
* of an entry is caught by the chain link, which is the mechanism that actually detects it.
|
|
50
|
+
*
|
|
51
|
+
* `entries` MUST be in published order (ascending `seqEnd`), which is the order they appear in the file.
|
|
52
|
+
*/
|
|
53
|
+
export function verifyRootsChain(entries) {
|
|
54
|
+
if (entries.length === 0) {
|
|
55
|
+
return { ok: true, verifiedCount: 0, brokenAt: -1, unchained: false };
|
|
56
|
+
}
|
|
57
|
+
const chained = entries.filter((e) => e.chainHash !== undefined);
|
|
58
|
+
if (chained.length === 0) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
verifiedCount: 0,
|
|
62
|
+
brokenAt: -1,
|
|
63
|
+
unchained: true,
|
|
64
|
+
reason: "roots file carries no chain hashes (pre-DEWP-5.4 v1 file); continuity cannot be checked",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (chained.length !== entries.length) {
|
|
68
|
+
// A partially chained file means someone spliced old and new lines together. Refuse to guess
|
|
69
|
+
// which half is authentic.
|
|
70
|
+
return fail(entries.findIndex((e) => e.chainHash === undefined), "roots file mixes chained and unchained entries", 0);
|
|
71
|
+
}
|
|
72
|
+
let verifiedCount = 0;
|
|
73
|
+
let prev;
|
|
74
|
+
let prevEnd;
|
|
75
|
+
for (let i = 0; i < entries.length; i++) {
|
|
76
|
+
const e = entries[i];
|
|
77
|
+
if (e === undefined)
|
|
78
|
+
return fail(i, "roots entry missing", verifiedCount);
|
|
79
|
+
const expectedPrev = prev === undefined ? GENESIS_PREV_CHAIN_HASH : (prev.chainHash ?? "");
|
|
80
|
+
const declaredPrev = e.prevChainHash ?? GENESIS_PREV_CHAIN_HASH;
|
|
81
|
+
if (declaredPrev !== expectedPrev) {
|
|
82
|
+
return fail(i, `chain link broken at seqEnd=${e.seqEnd}: prevChainHash ${declaredPrev || "(genesis)"} does not match predecessor ${expectedPrev || "(genesis)"}`, verifiedCount);
|
|
83
|
+
}
|
|
84
|
+
const recomputed = chainHash({
|
|
85
|
+
prevChainHash: declaredPrev,
|
|
86
|
+
root: e.root,
|
|
87
|
+
seqStart: e.seqStart,
|
|
88
|
+
seqEnd: e.seqEnd,
|
|
89
|
+
entryCount: e.entryCount,
|
|
90
|
+
anchoredAt: e.anchoredAt,
|
|
91
|
+
});
|
|
92
|
+
if (recomputed !== e.chainHash) {
|
|
93
|
+
return fail(i, `chain hash mismatch at seqEnd=${e.seqEnd}: recomputed ${recomputed}, file says ${e.chainHash}`, verifiedCount);
|
|
94
|
+
}
|
|
95
|
+
// The entry's OWN range is checked unconditionally. A non-integer or self-inverted range is
|
|
96
|
+
// malformed wherever it sits, and gating this on having a predecessor let the FIRST entry
|
|
97
|
+
// through unvalidated — so a single-entry roots file (a new tenant, or the first day after a
|
|
98
|
+
// truncation) was never range-checked at all. Only the overlap check is relational, because
|
|
99
|
+
// only it needs a predecessor. Go and Rust always did it this way; TS, Java and Python did not,
|
|
100
|
+
// which meant the same roots.jsonl verified clean in three ports and was refused by two.
|
|
101
|
+
let thisStart;
|
|
102
|
+
let thisEnd;
|
|
103
|
+
try {
|
|
104
|
+
thisStart = BigInt(e.seqStart);
|
|
105
|
+
thisEnd = BigInt(e.seqEnd);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return fail(i, `non-integer seq range at index ${i}`, verifiedCount);
|
|
109
|
+
}
|
|
110
|
+
if (thisEnd < thisStart) {
|
|
111
|
+
return fail(i, `seq range inverted: seqStart=${thisStart} > seqEnd=${thisEnd}`, verifiedCount);
|
|
112
|
+
}
|
|
113
|
+
if (prevEnd !== undefined && thisStart <= prevEnd) {
|
|
114
|
+
return fail(i, `seq ranges overlap or regress: entry starts at ${thisStart} but predecessor ended at ${prevEnd}`, verifiedCount);
|
|
115
|
+
}
|
|
116
|
+
verifiedCount++;
|
|
117
|
+
prev = e;
|
|
118
|
+
prevEnd = thisEnd;
|
|
119
|
+
}
|
|
120
|
+
return { ok: true, verifiedCount, brokenAt: -1, unchained: false };
|
|
121
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { type AnchorKeyResolver, type AnchorPolicy, type ExternalAnchorKeys, type SignedAnchor } from "./ledger-anchor.js";
|
|
2
|
+
import { type AlgorithmRegistry, type TrustedCheckpoint } from "./ledger-bundle.js";
|
|
3
|
+
import { type AuditSignaturePolicy, type AuditSignatureCheck } from "./ledger-signature.js";
|
|
4
|
+
import { type AuditLeaf } from "./ledger-leaf.js";
|
|
5
|
+
import { type InclusionProof } from "./ledger-proof.js";
|
|
6
|
+
export declare const EVIDENCE_BUNDLE_KIND = "dewp.audit.evidence-bundle";
|
|
7
|
+
/**
|
|
8
|
+
* DEWP §6.3/§16 redaction record. `COMMITMENT_ONLY` means content was lawfully purged under a
|
|
9
|
+
* retention or erasure policy while the Merkle commitment was preserved — recording *that* it was
|
|
10
|
+
* purged and *why* is what lets an auditor tell lawful redaction from tampering.
|
|
11
|
+
*/
|
|
12
|
+
export interface RedactionRecord {
|
|
13
|
+
mode: "NONE" | "COMMITMENT_ONLY";
|
|
14
|
+
removedFields?: string[];
|
|
15
|
+
redactedAt?: string;
|
|
16
|
+
reason?: string;
|
|
17
|
+
commitment?: {
|
|
18
|
+
leaf: string;
|
|
19
|
+
tenantSeq?: string | null;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface EvidenceEntry {
|
|
23
|
+
event: {
|
|
24
|
+
seq: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
type: string;
|
|
27
|
+
outcome: string;
|
|
28
|
+
/** DEWP §6.3 redaction record. Preferred over the legacy `redacted` boolean below. */
|
|
29
|
+
redaction?: RedactionRecord;
|
|
30
|
+
/** Legacy boolean form, still read so bundles exported before `redaction` keep verifying. */
|
|
31
|
+
redacted?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Per-tenant monotonic counter, present on redacted entries too (DEWP §16).
|
|
34
|
+
*
|
|
35
|
+
* DISPLAY COPY. It is a sibling of `canonical` and is covered by nothing — not the leaf, not the
|
|
36
|
+
* root, not any anchor. The gapless check reads `canonical.tenantSeq` whenever a preimage is
|
|
37
|
+
* present, and falls back to the redaction record or this copy only for an entry with no preimage
|
|
38
|
+
* (a COMMITMENT_ONLY redaction), saying so in the verdict notes (DEWP §7.2).
|
|
39
|
+
*/
|
|
40
|
+
tenantSeq?: string | null;
|
|
41
|
+
signerDid: string | null;
|
|
42
|
+
sigAlg: string | null;
|
|
43
|
+
/** Full canonical preimage — present only for unredacted entries. */
|
|
44
|
+
canonical?: AuditLeaf;
|
|
45
|
+
};
|
|
46
|
+
proof: InclusionProof;
|
|
47
|
+
}
|
|
48
|
+
export interface EvidenceBundle {
|
|
49
|
+
/** DEWP §6.3 envelope. Absent on bundles exported before the envelope was added. */
|
|
50
|
+
protocol?: string;
|
|
51
|
+
kind: typeof EVIDENCE_BUNDLE_KIND;
|
|
52
|
+
version: string | number;
|
|
53
|
+
/** Canonical-preimage Application Profile (§4.5). */
|
|
54
|
+
profile?: string;
|
|
55
|
+
algorithmRegistry?: AlgorithmRegistry;
|
|
56
|
+
exportedAt: string;
|
|
57
|
+
/**
|
|
58
|
+
* The tenant the bundle is for. Every content-verified entry must carry this `tenantId` (or none),
|
|
59
|
+
* so `tenantSeq` contiguity is judged over ONE tenant's counter. Absent or null, any entry that
|
|
60
|
+
* names a tenant is refused (DEWP §7.2).
|
|
61
|
+
*/
|
|
62
|
+
tenant?: {
|
|
63
|
+
id: string | null;
|
|
64
|
+
name: string | null;
|
|
65
|
+
} | null;
|
|
66
|
+
range: {
|
|
67
|
+
from: string;
|
|
68
|
+
to: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The contiguous committed tenantSeq range this bundle CLAIMS to cover (§6.3). Checking the entries
|
|
72
|
+
* against it is what turns "these entries happen to be contiguous" into "nothing was dropped from
|
|
73
|
+
* the range that was asked for" — without it, a producer could silently narrow the export.
|
|
74
|
+
*/
|
|
75
|
+
tenantSequenceCommitment?: {
|
|
76
|
+
tenantId: string;
|
|
77
|
+
firstTenantSeq: string;
|
|
78
|
+
lastTenantSeq: string;
|
|
79
|
+
};
|
|
80
|
+
entries: EvidenceEntry[];
|
|
81
|
+
checkpoints: {
|
|
82
|
+
id: string;
|
|
83
|
+
root: string;
|
|
84
|
+
anchorRef: string | null;
|
|
85
|
+
anchoredAt: string | null;
|
|
86
|
+
seqStart: string;
|
|
87
|
+
seqEnd: string;
|
|
88
|
+
/**
|
|
89
|
+
* The §5.4 chain fields. With them the verifier recomputes `chainHash`, and every anchor must bind
|
|
90
|
+
* that chain hash — which commits to the checkpoint's range, commit time and every earlier root.
|
|
91
|
+
* Absent on exports made before anchors bound position.
|
|
92
|
+
*/
|
|
93
|
+
entryCount?: number;
|
|
94
|
+
prevChainHash?: string | null;
|
|
95
|
+
chainHash?: string | null;
|
|
96
|
+
/**
|
|
97
|
+
* The §5.2 signed anchors over this checkpoint's root — the set the §5.3 quorum rule is
|
|
98
|
+
* evaluated over (§6.3). Used as quorum candidates when the caller supplies none of its own;
|
|
99
|
+
* they still verify only under keys the CALLER trusts, so a bundle cannot vouch for itself.
|
|
100
|
+
*/
|
|
101
|
+
anchors?: SignedAnchor[];
|
|
102
|
+
/** Producer claim of §5.3 quorum (distinct INDEPENDENT issuers). Display only — never trusted. */
|
|
103
|
+
externallyAnchored?: boolean;
|
|
104
|
+
/** The quorum size that claim was evaluated against (§6.3). Absent on older exports. */
|
|
105
|
+
externallyAnchoredRequired?: number;
|
|
106
|
+
}[];
|
|
107
|
+
}
|
|
108
|
+
export interface EvidenceVerification {
|
|
109
|
+
ok: boolean;
|
|
110
|
+
total: number;
|
|
111
|
+
contentVerified: number;
|
|
112
|
+
commitmentOnly: number;
|
|
113
|
+
failed: {
|
|
114
|
+
seq: string;
|
|
115
|
+
reason: string;
|
|
116
|
+
}[];
|
|
117
|
+
/**
|
|
118
|
+
* Distinct daily roots the entries chain up to. `anchorVerified` is true only when a quorum of
|
|
119
|
+
* trusted issuers signed that root (requires anchors + policy + resolver in the options); it is
|
|
120
|
+
* null when no policy was supplied, i.e. nobody checked.
|
|
121
|
+
*/
|
|
122
|
+
roots: {
|
|
123
|
+
root: string;
|
|
124
|
+
anchorRef: string | null;
|
|
125
|
+
anchorVerified: boolean | null;
|
|
126
|
+
verifiedIssuers: string[];
|
|
127
|
+
/** Authenticated external witness time per issuer, Unix seconds (see AnchorQuorumResult). */
|
|
128
|
+
witnessTimes: Record<string, number>;
|
|
129
|
+
}[];
|
|
130
|
+
/**
|
|
131
|
+
* Per-event mathematical signature checks, with explicit status and caller-key trust.
|
|
132
|
+
* WebAuthn uses committed metadata.webauthn plus caller-selected keys, origin and RP ID.
|
|
133
|
+
* No authorization/quorum is inferred; use verifyApprovalReceipt for the full approval policy.
|
|
134
|
+
* By default an invalid signature is reported without treating the ledger inclusion as invalid.
|
|
135
|
+
* requireSignatures additionally fails the bundle unless EVERY entry has a trusted signature.
|
|
136
|
+
*/
|
|
137
|
+
signatures: {
|
|
138
|
+
checks?: Array<AuditSignatureCheck & {
|
|
139
|
+
seq: string;
|
|
140
|
+
}>;
|
|
141
|
+
verified: number;
|
|
142
|
+
invalid: {
|
|
143
|
+
seq: string;
|
|
144
|
+
}[];
|
|
145
|
+
notCheckable: number;
|
|
146
|
+
};
|
|
147
|
+
notes: string[];
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Caller-fetched anchors, either flat or attributed to the checkpoint each one vouches for.
|
|
151
|
+
*
|
|
152
|
+
* The keyed form maps a checkpoint's `id` OR its `root` (as the bundle states it) to the anchors YOU
|
|
153
|
+
* fetched for that checkpoint. Attribution is what makes a DIVERGENCE verdict meaningful on a bundle
|
|
154
|
+
* spanning more than one day. An anchor's signed seq range now names its checkpoint too, and one
|
|
155
|
+
* naming a different range than the checkpoint it is keyed to is discarded as divergence evidence;
|
|
156
|
+
* attribution still decides which checkpoint a caller vouches it was fetched for.
|
|
157
|
+
*/
|
|
158
|
+
export type EvidenceAnchorSet = SignedAnchor[] | Record<string, SignedAnchor[]>;
|
|
159
|
+
export interface EvidenceVerifyOptions {
|
|
160
|
+
signaturePolicy?: AuditSignaturePolicy;
|
|
161
|
+
requireSignatures?: boolean;
|
|
162
|
+
/**
|
|
163
|
+
* Daily roots to verify against (root hex strings). When supplied, every entry must chain to one of
|
|
164
|
+
* them. They are only as independent as their source: roots recorded earlier or taken from the
|
|
165
|
+
* published roots file are; roots copied out of this bundle are not. (An external anchor cannot
|
|
166
|
+
* supply one — Rekor stores a hash of the anchor digest and a TSA the digest, not the root.)
|
|
167
|
+
*/
|
|
168
|
+
trustedRoots?: string[];
|
|
169
|
+
/**
|
|
170
|
+
* Checkpoint records YOU hold — normally the chain-verified lines of the published roots file
|
|
171
|
+
* (DEWP §5.4.1). Their roots count as trusted roots. For a bundle checkpoint over one of these roots,
|
|
172
|
+
* every field both carry must agree, or the bundle fails; and anchors are held to YOUR record's
|
|
173
|
+
* range, chain hash and claimed time rather than to the bundle's, so a producer cannot re-date a
|
|
174
|
+
* checkpoint to make a late witness look prompt (§5.3). A record's `entryCount` also bounds the
|
|
175
|
+
* proofs' leaf counts.
|
|
176
|
+
*/
|
|
177
|
+
trustedCheckpoints?: TrustedCheckpoint[];
|
|
178
|
+
/**
|
|
179
|
+
* Signed anchors for the roots below, fetched by YOU from each issuer (DEWP §5.3). When omitted,
|
|
180
|
+
* quorum falls back to the anchors carried in the bundle's own `checkpoints[].anchors` — still
|
|
181
|
+
* checked under YOUR keys via `resolveAnchorKey`, but see the divergence carve-out at the quorum
|
|
182
|
+
* loop: only caller-fetched anchors can establish divergence.
|
|
183
|
+
*
|
|
184
|
+
* Prefer the `EvidenceAnchorSet` keyed form on a multi-checkpoint bundle (a date-range export
|
|
185
|
+
* routinely is one): a flat list cannot say which checkpoint each anchor was fetched for.
|
|
186
|
+
*/
|
|
187
|
+
anchors?: EvidenceAnchorSet;
|
|
188
|
+
anchorPolicy?: AnchorPolicy;
|
|
189
|
+
resolveAnchorKey?: AnchorKeyResolver;
|
|
190
|
+
/** Pinned external-log keys (Rekor). Unpinned ⇒ that anchor is unverifiable ⇒ it does not count. */
|
|
191
|
+
externalKeys?: ExternalAnchorKeys;
|
|
192
|
+
}
|
|
193
|
+
export declare function verifyEvidenceBundle(bundle: EvidenceBundle, opts?: EvidenceVerifyOptions): EvidenceVerification;
|