@ziffer-io/verify 0.2.0 → 0.2.1
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/README.md +8 -0
- package/dist/chain.d.ts +142 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +430 -0
- package/dist/chain.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -50,6 +50,14 @@ Every refusal is a thrown `Refusal` whose `clause` names the rule that fired; th
|
|
|
50
50
|
clause, what it means and what to do is at https://ziffer.io/docs/refusals. Narrow with
|
|
51
51
|
`instanceof Refusal`, record the clause, and do not retry it.
|
|
52
52
|
|
|
53
|
+
## Checking an audit chain download
|
|
54
|
+
|
|
55
|
+
The console's *Download the audit chain for this window* file checks here too:
|
|
56
|
+
`verifyChain(fileBytes, parseAnchorKey(anchorKeyBytes))` recomputes every record's hash, checks
|
|
57
|
+
each anchor's signature under the audit anchor key you hold (never the copy inside the file), and
|
|
58
|
+
reports which records no anchor covers yet. A break throws `ChainRefusal`, whose `refusal` and
|
|
59
|
+
`seq` name what broke and where. Integrating the SDK, section 6, has the whole of it.
|
|
60
|
+
|
|
53
61
|
## Documentation
|
|
54
62
|
|
|
55
63
|
- Quickstart: https://ziffer.io/docs/quickstart
|
package/dist/chain.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The audit chain export verifier (ACP-428) -- the TypeScript reader of the
|
|
3
|
+
* file the console's "Download the audit chain for this window" link gives.
|
|
4
|
+
*
|
|
5
|
+
* The link said the file was verifiable offline and nothing a customer held
|
|
6
|
+
* verified it. The Python SDK's `verify_chain` and this function are that
|
|
7
|
+
* verifier, and they are held to ONE verdict per file by the shared corpus in
|
|
8
|
+
* `fixtures/chain-export/` -- the same refusal name at the same record, the
|
|
9
|
+
* same recomputed last hash, the same report of what the anchors cover. The
|
|
10
|
+
* check order below is the Python reader's, step for step, because on a file
|
|
11
|
+
* with two defects the order decides which one is named.
|
|
12
|
+
*
|
|
13
|
+
* # What is recomputed, and what is only reported
|
|
14
|
+
*
|
|
15
|
+
* Every `chain_hash` is RECOMPUTED -- `sha256:` over the canonical bytes of the
|
|
16
|
+
* record (the first record of the chain) or of `{prev, record}` (every later
|
|
17
|
+
* one), the audit service's own link -- and the file's value is compared,
|
|
18
|
+
* never used. Every `previous_hash` after the first row is compared against
|
|
19
|
+
* the hash just recomputed. The first row's `previous_hash`, in a file that
|
|
20
|
+
* starts after the beginning of the chain, is the one link this file cannot
|
|
21
|
+
* recompute; it is reported as `startsAfter`, and it is checked only when an
|
|
22
|
+
* anchor names exactly that position.
|
|
23
|
+
*
|
|
24
|
+
* # The anchor key never comes from the file
|
|
25
|
+
*
|
|
26
|
+
* Every anchor entry carries an `anchor_identity` beside it. Reading the key
|
|
27
|
+
* from there would accept any file whose author signed their own anchors and
|
|
28
|
+
* wrote their own key next to them -- the party under verification supplying
|
|
29
|
+
* the value it is verified with. So the key is a parameter, from a file the
|
|
30
|
+
* customer holds, and with no key every anchor is reported `unchecked`: never
|
|
31
|
+
* passed, and never dropped from the report.
|
|
32
|
+
*
|
|
33
|
+
* # Numbers: the file's TEXT is read, not only its parsed values
|
|
34
|
+
*
|
|
35
|
+
* `JSON.parse` reads `1.0` as the integer 1. The record the writer hashed said
|
|
36
|
+
* `1.0`, so hashing `1` would compare bytes nobody wrote and name the wrong
|
|
37
|
+
* defect. The parser's reviver sees each number's source text (Node 22 and
|
|
38
|
+
* later), and a number written with a fraction or an exponent becomes a value
|
|
39
|
+
* the canonical encoder refuses -- `RecordNotCanonical` inside a record,
|
|
40
|
+
* `ExportMalformed` in the file's own fields, exactly where the Python reader
|
|
41
|
+
* says so. On a runtime whose parser does not hand the reviver the source
|
|
42
|
+
* text, that one case would be named `ChainHashMismatch` instead; the file is
|
|
43
|
+
* refused either way.
|
|
44
|
+
*/
|
|
45
|
+
/** The one suite an audit anchor is signed under here, and the only one an anchor key may name. */
|
|
46
|
+
export declare const ANCHOR_SUITE = "hybrid-ed25519-mldsa65";
|
|
47
|
+
/** A refusal of an export, by name, at the first record or anchor that broke. */
|
|
48
|
+
export declare class ChainRefusal extends Error {
|
|
49
|
+
/** The refusal name, identical to the Python reader's `ChainRefused.name`. */
|
|
50
|
+
readonly refusal: string;
|
|
51
|
+
readonly seq: number | null;
|
|
52
|
+
readonly anchor: number | null;
|
|
53
|
+
constructor(refusal: string, detail: string, where?: {
|
|
54
|
+
seq?: number;
|
|
55
|
+
anchor?: number;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** An anchor key file that cannot be used, by name. A defect in the caller's own file, not a verdict. */
|
|
59
|
+
export declare class AnchorKeyRefusal extends Error {
|
|
60
|
+
readonly refusal: string;
|
|
61
|
+
constructor(refusal: string, detail: string);
|
|
62
|
+
}
|
|
63
|
+
/** The audit anchor identity's public halves, from the file the customer holds. */
|
|
64
|
+
export interface AnchorKey {
|
|
65
|
+
readonly alg: typeof ANCHOR_SUITE;
|
|
66
|
+
readonly classical: Uint8Array;
|
|
67
|
+
readonly pq: Uint8Array;
|
|
68
|
+
readonly fingerprint: string;
|
|
69
|
+
}
|
|
70
|
+
export interface AnchorResult {
|
|
71
|
+
readonly index: number;
|
|
72
|
+
readonly status: 'verified' | 'unchecked';
|
|
73
|
+
readonly placement: 'in_file' | 'window_floor' | 'before_file' | 'after_file' | 'no_head';
|
|
74
|
+
readonly headSeq: number | null;
|
|
75
|
+
readonly periodEnd: number;
|
|
76
|
+
}
|
|
77
|
+
export interface ChainVerified {
|
|
78
|
+
readonly tenant: string;
|
|
79
|
+
readonly firstSeq: number;
|
|
80
|
+
readonly lastSeq: number;
|
|
81
|
+
readonly records: number;
|
|
82
|
+
/** RECOMPUTED, never the file's. */
|
|
83
|
+
readonly lastChainHash: string;
|
|
84
|
+
readonly startsAfter: string | null;
|
|
85
|
+
readonly anchorsChecked: boolean;
|
|
86
|
+
readonly anchors: readonly AnchorResult[];
|
|
87
|
+
readonly anchoredThrough: number | null;
|
|
88
|
+
readonly unanchoredFrom: number | null;
|
|
89
|
+
readonly unanchoredCount: number;
|
|
90
|
+
}
|
|
91
|
+
/** The verdict as the shared corpus writes it -- snake_case, the Python reader's keys. */
|
|
92
|
+
export type ChainVerdict = {
|
|
93
|
+
readonly verdict: 'verified';
|
|
94
|
+
readonly tenant: string;
|
|
95
|
+
readonly first_seq: number;
|
|
96
|
+
readonly last_seq: number;
|
|
97
|
+
readonly records: number;
|
|
98
|
+
readonly last_chain_hash: string;
|
|
99
|
+
readonly starts_after: string | null;
|
|
100
|
+
readonly anchors_checked: boolean;
|
|
101
|
+
readonly anchors: readonly {
|
|
102
|
+
readonly index: number;
|
|
103
|
+
readonly status: string;
|
|
104
|
+
readonly placement: string;
|
|
105
|
+
readonly head_seq: number | null;
|
|
106
|
+
readonly period_end: number;
|
|
107
|
+
}[];
|
|
108
|
+
readonly anchored_through: number | null;
|
|
109
|
+
readonly unanchored_from: number | null;
|
|
110
|
+
readonly unanchored_count: number;
|
|
111
|
+
} | {
|
|
112
|
+
readonly verdict: 'refused';
|
|
113
|
+
readonly refusal: string;
|
|
114
|
+
readonly seq: number | null;
|
|
115
|
+
readonly anchor: number | null;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Read the audit anchor identity's public document -- `{alg, classical, pq,
|
|
119
|
+
* fingerprint}`, the halves in base64 -- checking every redundant field: the
|
|
120
|
+
* fingerprint is recomputed over both halves and compared, and a small-order
|
|
121
|
+
* Ed25519 half is refused, because under one a single signature verifies every
|
|
122
|
+
* message.
|
|
123
|
+
*/
|
|
124
|
+
export declare function parseAnchorKey(file: string | Uint8Array): AnchorKey;
|
|
125
|
+
/**
|
|
126
|
+
* Verify an audit chain export, from the file's bytes or text.
|
|
127
|
+
*
|
|
128
|
+
* Throws {@link ChainRefusal} at the first break: `ExportMalformed`,
|
|
129
|
+
* `ExportEmpty`, `ChainSeqGap`, `RecordOutOfPlace`, `ChainLinkBroken`,
|
|
130
|
+
* `RecordNotCanonical`, `ChainHashMismatch`, `AnchorMalformed`,
|
|
131
|
+
* `AnchorSuiteNotEnrolled`, `AnchorSignatureInvalid`, `AnchorHeadMismatch`.
|
|
132
|
+
*
|
|
133
|
+
* NOT checked, on purpose, as in the Python reader: whether each record's
|
|
134
|
+
* fields are the set its kind of event carries, the transparency-log entry and
|
|
135
|
+
* timestamp stored beside each anchor, and whether the file holds EVERY record
|
|
136
|
+
* of its window -- a file cut short at the end verifies, and `unanchoredFrom`
|
|
137
|
+
* says how much of it nothing yet commits to.
|
|
138
|
+
*/
|
|
139
|
+
export declare function verifyChain(file: string | Uint8Array, anchorKey?: AnchorKey): ChainVerified;
|
|
140
|
+
/** {@link verifyChain}'s answer, or its refusal, as one corpus-shaped object. */
|
|
141
|
+
export declare function chainVerdict(file: string | Uint8Array, anchorKey?: AnchorKey): ChainVerdict;
|
|
142
|
+
//# sourceMappingURL=chain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AASH,mGAAmG;AACnG,eAAO,MAAM,YAAY,2BAA2B,CAAC;AAKrD,iFAAiF;AACjF,qBAAa,YAAa,SAAQ,KAAK;IACrC,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEnB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;CAO3F;AAED,yGAAyG;AACzG,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK5C;AAED,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,YAAY,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,cAAc,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,CAAC;IAC1F,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,0FAA0F;AAC1F,MAAM,MAAM,YAAY,GACpB;IACE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS;QACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;KAC7B,EAAE,CAAC;IACJ,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC,GACD;IACE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAyEN;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAyCnE;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,aAAa,CAyK3F;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,YAAY,CA8B3F"}
|
package/dist/chain.js
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The audit chain export verifier (ACP-428) -- the TypeScript reader of the
|
|
3
|
+
* file the console's "Download the audit chain for this window" link gives.
|
|
4
|
+
*
|
|
5
|
+
* The link said the file was verifiable offline and nothing a customer held
|
|
6
|
+
* verified it. The Python SDK's `verify_chain` and this function are that
|
|
7
|
+
* verifier, and they are held to ONE verdict per file by the shared corpus in
|
|
8
|
+
* `fixtures/chain-export/` -- the same refusal name at the same record, the
|
|
9
|
+
* same recomputed last hash, the same report of what the anchors cover. The
|
|
10
|
+
* check order below is the Python reader's, step for step, because on a file
|
|
11
|
+
* with two defects the order decides which one is named.
|
|
12
|
+
*
|
|
13
|
+
* # What is recomputed, and what is only reported
|
|
14
|
+
*
|
|
15
|
+
* Every `chain_hash` is RECOMPUTED -- `sha256:` over the canonical bytes of the
|
|
16
|
+
* record (the first record of the chain) or of `{prev, record}` (every later
|
|
17
|
+
* one), the audit service's own link -- and the file's value is compared,
|
|
18
|
+
* never used. Every `previous_hash` after the first row is compared against
|
|
19
|
+
* the hash just recomputed. The first row's `previous_hash`, in a file that
|
|
20
|
+
* starts after the beginning of the chain, is the one link this file cannot
|
|
21
|
+
* recompute; it is reported as `startsAfter`, and it is checked only when an
|
|
22
|
+
* anchor names exactly that position.
|
|
23
|
+
*
|
|
24
|
+
* # The anchor key never comes from the file
|
|
25
|
+
*
|
|
26
|
+
* Every anchor entry carries an `anchor_identity` beside it. Reading the key
|
|
27
|
+
* from there would accept any file whose author signed their own anchors and
|
|
28
|
+
* wrote their own key next to them -- the party under verification supplying
|
|
29
|
+
* the value it is verified with. So the key is a parameter, from a file the
|
|
30
|
+
* customer holds, and with no key every anchor is reported `unchecked`: never
|
|
31
|
+
* passed, and never dropped from the report.
|
|
32
|
+
*
|
|
33
|
+
* # Numbers: the file's TEXT is read, not only its parsed values
|
|
34
|
+
*
|
|
35
|
+
* `JSON.parse` reads `1.0` as the integer 1. The record the writer hashed said
|
|
36
|
+
* `1.0`, so hashing `1` would compare bytes nobody wrote and name the wrong
|
|
37
|
+
* defect. The parser's reviver sees each number's source text (Node 22 and
|
|
38
|
+
* later), and a number written with a fraction or an exponent becomes a value
|
|
39
|
+
* the canonical encoder refuses -- `RecordNotCanonical` inside a record,
|
|
40
|
+
* `ExportMalformed` in the file's own fields, exactly where the Python reader
|
|
41
|
+
* says so. On a runtime whose parser does not hand the reviver the source
|
|
42
|
+
* text, that one case would be named `ChainHashMismatch` instead; the file is
|
|
43
|
+
* refused either way.
|
|
44
|
+
*/
|
|
45
|
+
import { createHash } from 'node:crypto';
|
|
46
|
+
import { canon } from './canon.js';
|
|
47
|
+
import { ed25519IsSmallOrder } from './ed25519.js';
|
|
48
|
+
import { MLDSA65_PK_LEN, verifyUnderSuite } from './sig.js';
|
|
49
|
+
import { isRecord } from './wire.js';
|
|
50
|
+
/** The one suite an audit anchor is signed under here, and the only one an anchor key may name. */
|
|
51
|
+
export const ANCHOR_SUITE = 'hybrid-ed25519-mldsa65';
|
|
52
|
+
const ANCHOR_VERSION = 1;
|
|
53
|
+
const HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
54
|
+
const ED25519_PK_BYTES = 32;
|
|
55
|
+
/** A refusal of an export, by name, at the first record or anchor that broke. */
|
|
56
|
+
export class ChainRefusal extends Error {
|
|
57
|
+
/** The refusal name, identical to the Python reader's `ChainRefused.name`. */
|
|
58
|
+
refusal;
|
|
59
|
+
seq;
|
|
60
|
+
anchor;
|
|
61
|
+
constructor(refusal, detail, where = {}) {
|
|
62
|
+
super(`${refusal}: ${detail}`);
|
|
63
|
+
this.name = 'ChainRefusal';
|
|
64
|
+
this.refusal = refusal;
|
|
65
|
+
this.seq = where.seq ?? null;
|
|
66
|
+
this.anchor = where.anchor ?? null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** An anchor key file that cannot be used, by name. A defect in the caller's own file, not a verdict. */
|
|
70
|
+
export class AnchorKeyRefusal extends Error {
|
|
71
|
+
refusal;
|
|
72
|
+
constructor(refusal, detail) {
|
|
73
|
+
super(`${refusal}: ${detail}`);
|
|
74
|
+
this.name = 'AnchorKeyRefusal';
|
|
75
|
+
this.refusal = refusal;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// A number the source spelled with a fraction or an exponent. A symbol, so the
|
|
79
|
+
// canonical encoder refuses it wherever it lands and no field check mistakes it
|
|
80
|
+
// for an integer.
|
|
81
|
+
const NOT_AN_INTEGER = Symbol('a number written with a fraction or an exponent');
|
|
82
|
+
function reviver(_key, value, context) {
|
|
83
|
+
if (typeof value === 'number' && typeof context?.source === 'string' && /[.eE]/.test(context.source)) {
|
|
84
|
+
return NOT_AN_INTEGER;
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
function isInt(v) {
|
|
89
|
+
return typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
90
|
+
}
|
|
91
|
+
// Half of a UTF-16 surrogate pair on its own: the Python reader cannot encode
|
|
92
|
+
// one at all, and JSON.stringify would escape it into bytes nobody wrote.
|
|
93
|
+
const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
94
|
+
function canonicalValue(v) {
|
|
95
|
+
if (v === null || typeof v === 'boolean')
|
|
96
|
+
return true;
|
|
97
|
+
if (typeof v === 'number')
|
|
98
|
+
return Number.isSafeInteger(v);
|
|
99
|
+
if (typeof v === 'string')
|
|
100
|
+
return !LONE_SURROGATE.test(v);
|
|
101
|
+
if (Array.isArray(v))
|
|
102
|
+
return v.every(canonicalValue);
|
|
103
|
+
if (isRecord(v))
|
|
104
|
+
return Object.entries(v).every(([k, x]) => !LONE_SURROGATE.test(k) && canonicalValue(x));
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function sha256Hex(bytes) {
|
|
108
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
109
|
+
}
|
|
110
|
+
/** The audit service's link: `sha256:` over the canonical bytes. */
|
|
111
|
+
function link(value) {
|
|
112
|
+
return `sha256:${sha256Hex(canon(value))}`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* RFC 4648 section 4 base64 with padding, in its one spelling: a value that
|
|
116
|
+
* does not re-encode to itself is refused, so two spellings of one signature
|
|
117
|
+
* are not two accepted signatures.
|
|
118
|
+
*/
|
|
119
|
+
function b64Strict(value) {
|
|
120
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
121
|
+
return null;
|
|
122
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0)
|
|
123
|
+
return null;
|
|
124
|
+
const raw = Uint8Array.from(Buffer.from(value, 'base64'));
|
|
125
|
+
return Buffer.from(raw).toString('base64') === value ? raw : null;
|
|
126
|
+
}
|
|
127
|
+
function parseText(file) {
|
|
128
|
+
let text;
|
|
129
|
+
if (typeof file === 'string') {
|
|
130
|
+
text = file;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
try {
|
|
134
|
+
// ignoreBOM keeps a byte-order mark in the text, where JSON.parse refuses
|
|
135
|
+
// it -- as the Python reader's parser does.
|
|
136
|
+
text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(file);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
throw new ChainRefusal('ExportMalformed', 'the file is not UTF-8');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(text, reviver);
|
|
144
|
+
return parsed;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
throw new ChainRefusal('ExportMalformed', 'the file is not JSON');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Read the audit anchor identity's public document -- `{alg, classical, pq,
|
|
152
|
+
* fingerprint}`, the halves in base64 -- checking every redundant field: the
|
|
153
|
+
* fingerprint is recomputed over both halves and compared, and a small-order
|
|
154
|
+
* Ed25519 half is refused, because under one a single signature verifies every
|
|
155
|
+
* message.
|
|
156
|
+
*/
|
|
157
|
+
export function parseAnchorKey(file) {
|
|
158
|
+
let doc;
|
|
159
|
+
try {
|
|
160
|
+
doc = JSON.parse(typeof file === 'string' ? file : new TextDecoder('utf-8', { fatal: true }).decode(file));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
throw new AnchorKeyRefusal('AnchorKeyMalformed', 'the anchor key file is not JSON');
|
|
164
|
+
}
|
|
165
|
+
if (!isRecord(doc)) {
|
|
166
|
+
throw new AnchorKeyRefusal('AnchorKeyMalformed', 'the anchor key document is not a JSON object');
|
|
167
|
+
}
|
|
168
|
+
if ('ed25519_pk_hex' in doc || 'identity' in doc) {
|
|
169
|
+
throw new AnchorKeyRefusal('AnchorKeyMalformed', 'this is a receipt trust anchor (the document `ziffer pubkey` writes), not the audit anchor identity');
|
|
170
|
+
}
|
|
171
|
+
if (doc['alg'] !== ANCHOR_SUITE) {
|
|
172
|
+
throw new AnchorKeyRefusal('AnchorKeyUnsupported', `audit anchors are verified under ${ANCHOR_SUITE} only`);
|
|
173
|
+
}
|
|
174
|
+
const classical = b64Strict(doc['classical']);
|
|
175
|
+
const pq = b64Strict(doc['pq']);
|
|
176
|
+
if (classical === null || classical.length !== ED25519_PK_BYTES || pq === null || pq.length !== MLDSA65_PK_LEN) {
|
|
177
|
+
throw new AnchorKeyRefusal('AnchorKeyMalformed', '`classical` must be 32 bytes and `pq` 1952 bytes, each in padded base64');
|
|
178
|
+
}
|
|
179
|
+
if (ed25519IsSmallOrder(classical)) {
|
|
180
|
+
throw new AnchorKeyRefusal('AnchorKeyMalformed', 'the Ed25519 half is a small-order point');
|
|
181
|
+
}
|
|
182
|
+
const both = new Uint8Array(classical.length + pq.length);
|
|
183
|
+
both.set(classical, 0);
|
|
184
|
+
both.set(pq, classical.length);
|
|
185
|
+
const fingerprint = `sha256:${sha256Hex(both)}`;
|
|
186
|
+
if (doc['fingerprint'] !== fingerprint) {
|
|
187
|
+
throw new AnchorKeyRefusal('AnchorKeyMalformed', 'the fingerprint is not the fingerprint of the document’s own keys; it was edited after it was written');
|
|
188
|
+
}
|
|
189
|
+
return { alg: ANCHOR_SUITE, classical, pq, fingerprint };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Both halves of the hybrid signature over the bytes the audit service signed:
|
|
193
|
+
* the canonical encoding of the anchor's `alg`, `heads`, `period_end` and `v`.
|
|
194
|
+
* Conjunctive, over exactly {classical, pq}.
|
|
195
|
+
*/
|
|
196
|
+
function anchorSignatureOk(key, signed) {
|
|
197
|
+
const sig = signed['sig'];
|
|
198
|
+
if (!isRecord(sig))
|
|
199
|
+
return false;
|
|
200
|
+
const names = Object.keys(sig).sort();
|
|
201
|
+
if (names.length !== 2 || names[0] !== 'classical' || names[1] !== 'pq')
|
|
202
|
+
return false;
|
|
203
|
+
const classical = b64Strict(sig['classical']);
|
|
204
|
+
const pq = b64Strict(sig['pq']);
|
|
205
|
+
if (classical === null || pq === null)
|
|
206
|
+
return false;
|
|
207
|
+
const body = canon({
|
|
208
|
+
alg: signed['alg'],
|
|
209
|
+
heads: signed['heads'],
|
|
210
|
+
period_end: signed['period_end'],
|
|
211
|
+
v: signed['v'],
|
|
212
|
+
});
|
|
213
|
+
const parts = [
|
|
214
|
+
{ primitive: 'classical', bytes: classical },
|
|
215
|
+
{ primitive: 'pq', bytes: pq },
|
|
216
|
+
];
|
|
217
|
+
return verifyUnderSuite(ANCHOR_SUITE, key, body, parts) === null;
|
|
218
|
+
}
|
|
219
|
+
function isHead(h) {
|
|
220
|
+
return (Array.isArray(h) && h.length === 3 && typeof h[0] === 'string' && isInt(h[1]) && typeof h[2] === 'string');
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Verify an audit chain export, from the file's bytes or text.
|
|
224
|
+
*
|
|
225
|
+
* Throws {@link ChainRefusal} at the first break: `ExportMalformed`,
|
|
226
|
+
* `ExportEmpty`, `ChainSeqGap`, `RecordOutOfPlace`, `ChainLinkBroken`,
|
|
227
|
+
* `RecordNotCanonical`, `ChainHashMismatch`, `AnchorMalformed`,
|
|
228
|
+
* `AnchorSuiteNotEnrolled`, `AnchorSignatureInvalid`, `AnchorHeadMismatch`.
|
|
229
|
+
*
|
|
230
|
+
* NOT checked, on purpose, as in the Python reader: whether each record's
|
|
231
|
+
* fields are the set its kind of event carries, the transparency-log entry and
|
|
232
|
+
* timestamp stored beside each anchor, and whether the file holds EVERY record
|
|
233
|
+
* of its window -- a file cut short at the end verifies, and `unanchoredFrom`
|
|
234
|
+
* says how much of it nothing yet commits to.
|
|
235
|
+
*/
|
|
236
|
+
export function verifyChain(file, anchorKey) {
|
|
237
|
+
const doc = parseText(file);
|
|
238
|
+
if (!isRecord(doc))
|
|
239
|
+
throw new ChainRefusal('ExportMalformed', 'the file is not a JSON object');
|
|
240
|
+
const tenant = doc['tenant'];
|
|
241
|
+
const chain = doc['chain'];
|
|
242
|
+
const anchors = doc['anchors'];
|
|
243
|
+
if (typeof tenant !== 'string' || !Array.isArray(chain) || !Array.isArray(anchors)) {
|
|
244
|
+
throw new ChainRefusal('ExportMalformed', 'the file carries no `tenant`, `chain` and `anchors`');
|
|
245
|
+
}
|
|
246
|
+
if (chain.length === 0)
|
|
247
|
+
throw new ChainRefusal('ExportEmpty', 'the file holds no records');
|
|
248
|
+
// ---- the chain: every link recomputed, in order -------------------------
|
|
249
|
+
const recomputed = [];
|
|
250
|
+
let firstSeq = 0;
|
|
251
|
+
let startsAfter = null;
|
|
252
|
+
for (let i = 0; i < chain.length; i += 1) {
|
|
253
|
+
const row = chain[i];
|
|
254
|
+
if (!isRecord(row) ||
|
|
255
|
+
!isInt(row['seq']) ||
|
|
256
|
+
typeof row['chain_hash'] !== 'string' ||
|
|
257
|
+
!('previous_hash' in row) ||
|
|
258
|
+
!(row['previous_hash'] === null || typeof row['previous_hash'] === 'string') ||
|
|
259
|
+
!isRecord(row['record'])) {
|
|
260
|
+
throw new ChainRefusal('ExportMalformed', `row ${i} is not {seq, previous_hash, chain_hash, record}`);
|
|
261
|
+
}
|
|
262
|
+
const seq = row['seq'];
|
|
263
|
+
const record = row['record'];
|
|
264
|
+
const previous = row['previous_hash'];
|
|
265
|
+
if (i === 0) {
|
|
266
|
+
firstSeq = seq;
|
|
267
|
+
}
|
|
268
|
+
else if (seq !== firstSeq + i) {
|
|
269
|
+
throw new ChainRefusal('ChainSeqGap', `follows record ${firstSeq + i - 1}; a chain has no gaps`, { seq });
|
|
270
|
+
}
|
|
271
|
+
const recordSeq = record['seq'];
|
|
272
|
+
if (record['tenant_id'] !== tenant || (seq > 0 && !(isInt(recordSeq) && recordSeq === seq))) {
|
|
273
|
+
throw new ChainRefusal('RecordOutOfPlace', 'the record names another tenant or another position', { seq });
|
|
274
|
+
}
|
|
275
|
+
let prev;
|
|
276
|
+
if (i === 0) {
|
|
277
|
+
if (seq === 0 && previous !== null) {
|
|
278
|
+
throw new ChainRefusal('ChainLinkBroken', 'the first record of a chain has no predecessor', { seq });
|
|
279
|
+
}
|
|
280
|
+
if (seq > 0) {
|
|
281
|
+
if (typeof previous !== 'string' || !HASH_PATTERN.test(previous)) {
|
|
282
|
+
throw new ChainRefusal('ChainLinkBroken', 'the file starts after the chain’s beginning and does not say which hash it continues from', { seq });
|
|
283
|
+
}
|
|
284
|
+
startsAfter = previous;
|
|
285
|
+
}
|
|
286
|
+
prev = typeof previous === 'string' ? previous : null;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
const before = recomputed[i - 1] ?? null;
|
|
290
|
+
if (previous !== before) {
|
|
291
|
+
throw new ChainRefusal('ChainLinkBroken', 'does not continue from the record before it', { seq });
|
|
292
|
+
}
|
|
293
|
+
prev = before;
|
|
294
|
+
}
|
|
295
|
+
if (!canonicalValue(record)) {
|
|
296
|
+
throw new ChainRefusal('RecordNotCanonical', 'the record holds a fraction or an integer too large to encode one way', { seq });
|
|
297
|
+
}
|
|
298
|
+
const hash = seq === 0 ? link(record) : link({ prev, record });
|
|
299
|
+
if (hash !== row['chain_hash']) {
|
|
300
|
+
throw new ChainRefusal('ChainHashMismatch', `the record hashes to ${hash}`, { seq });
|
|
301
|
+
}
|
|
302
|
+
recomputed.push(hash);
|
|
303
|
+
}
|
|
304
|
+
const lastSeq = firstSeq + chain.length - 1;
|
|
305
|
+
// ---- the anchors: each signature under YOUR key, each head against the chain
|
|
306
|
+
const results = [];
|
|
307
|
+
let anchoredThrough = null;
|
|
308
|
+
for (let j = 0; j < anchors.length; j += 1) {
|
|
309
|
+
const entry = anchors[j];
|
|
310
|
+
const signed = isRecord(entry) ? entry['anchor'] : undefined;
|
|
311
|
+
if (!isRecord(signed) ||
|
|
312
|
+
typeof signed['alg'] !== 'string' ||
|
|
313
|
+
!isInt(signed['v']) ||
|
|
314
|
+
signed['v'] !== ANCHOR_VERSION ||
|
|
315
|
+
!isInt(signed['period_end']) ||
|
|
316
|
+
!Array.isArray(signed['heads']) ||
|
|
317
|
+
!signed['heads'].every(isHead) ||
|
|
318
|
+
!('sig' in signed)) {
|
|
319
|
+
throw new ChainRefusal('AnchorMalformed', 'not {alg, heads, period_end, sig, v} with three-part heads', {
|
|
320
|
+
anchor: j,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
const periodEnd = signed['period_end'];
|
|
324
|
+
const heads = signed['heads'];
|
|
325
|
+
const mine = heads.filter(isHead).filter((h) => h[0] === tenant);
|
|
326
|
+
if (mine.length > 1) {
|
|
327
|
+
throw new ChainRefusal('AnchorMalformed', `names this tenant ${mine.length} times`, { anchor: j });
|
|
328
|
+
}
|
|
329
|
+
let status = 'unchecked';
|
|
330
|
+
if (anchorKey !== undefined) {
|
|
331
|
+
if (signed['alg'] !== anchorKey.alg) {
|
|
332
|
+
throw new ChainRefusal('AnchorSuiteNotEnrolled', 'signed under a suite your anchor key is not', {
|
|
333
|
+
anchor: j,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (!anchorSignatureOk(anchorKey, signed)) {
|
|
337
|
+
throw new ChainRefusal('AnchorSignatureInvalid', 'the anchor’s signature does not verify under your anchor key -- both halves must', { anchor: j });
|
|
338
|
+
}
|
|
339
|
+
status = 'verified';
|
|
340
|
+
}
|
|
341
|
+
let placement = 'no_head';
|
|
342
|
+
let headSeq = null;
|
|
343
|
+
const head = mine[0];
|
|
344
|
+
if (head !== undefined) {
|
|
345
|
+
const [, hSeq, hHash] = head;
|
|
346
|
+
headSeq = hSeq;
|
|
347
|
+
let expected = null;
|
|
348
|
+
if (firstSeq <= hSeq && hSeq <= lastSeq) {
|
|
349
|
+
placement = 'in_file';
|
|
350
|
+
expected = recomputed[hSeq - firstSeq] ?? null;
|
|
351
|
+
}
|
|
352
|
+
else if (hSeq === firstSeq - 1 && startsAfter !== null) {
|
|
353
|
+
placement = 'window_floor';
|
|
354
|
+
expected = startsAfter;
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
placement = hSeq < firstSeq ? 'before_file' : 'after_file';
|
|
358
|
+
}
|
|
359
|
+
// Compared with or without a key, as in the Python reader: a file whose
|
|
360
|
+
// anchors disagree with its own recomputed chain is refused either way.
|
|
361
|
+
if (expected !== null && hHash !== expected) {
|
|
362
|
+
throw new ChainRefusal('AnchorHeadMismatch', `the chain recomputes to ${expected} at this position`, {
|
|
363
|
+
seq: hSeq,
|
|
364
|
+
anchor: j,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (status === 'verified' && (placement === 'in_file' || placement === 'window_floor')) {
|
|
368
|
+
anchoredThrough = anchoredThrough === null ? hSeq : Math.max(anchoredThrough, hSeq);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
results.push({ index: j, status, placement, headSeq, periodEnd });
|
|
372
|
+
}
|
|
373
|
+
let unanchoredFrom;
|
|
374
|
+
if (anchoredThrough === null)
|
|
375
|
+
unanchoredFrom = firstSeq;
|
|
376
|
+
else if (anchoredThrough < lastSeq)
|
|
377
|
+
unanchoredFrom = anchoredThrough + 1;
|
|
378
|
+
else
|
|
379
|
+
unanchoredFrom = null;
|
|
380
|
+
const lastChainHash = recomputed[recomputed.length - 1];
|
|
381
|
+
if (lastChainHash === undefined)
|
|
382
|
+
throw new ChainRefusal('ExportEmpty', 'the file holds no records');
|
|
383
|
+
return {
|
|
384
|
+
tenant,
|
|
385
|
+
firstSeq,
|
|
386
|
+
lastSeq,
|
|
387
|
+
records: chain.length,
|
|
388
|
+
lastChainHash,
|
|
389
|
+
startsAfter,
|
|
390
|
+
anchorsChecked: anchorKey !== undefined,
|
|
391
|
+
anchors: results,
|
|
392
|
+
anchoredThrough,
|
|
393
|
+
unanchoredFrom,
|
|
394
|
+
unanchoredCount: unanchoredFrom === null ? 0 : lastSeq - unanchoredFrom + 1,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** {@link verifyChain}'s answer, or its refusal, as one corpus-shaped object. */
|
|
398
|
+
export function chainVerdict(file, anchorKey) {
|
|
399
|
+
let v;
|
|
400
|
+
try {
|
|
401
|
+
v = verifyChain(file, anchorKey);
|
|
402
|
+
}
|
|
403
|
+
catch (e) {
|
|
404
|
+
if (e instanceof ChainRefusal) {
|
|
405
|
+
return { verdict: 'refused', refusal: e.refusal, seq: e.seq, anchor: e.anchor };
|
|
406
|
+
}
|
|
407
|
+
throw e;
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
verdict: 'verified',
|
|
411
|
+
tenant: v.tenant,
|
|
412
|
+
first_seq: v.firstSeq,
|
|
413
|
+
last_seq: v.lastSeq,
|
|
414
|
+
records: v.records,
|
|
415
|
+
last_chain_hash: v.lastChainHash,
|
|
416
|
+
starts_after: v.startsAfter,
|
|
417
|
+
anchors_checked: v.anchorsChecked,
|
|
418
|
+
anchors: v.anchors.map((a) => ({
|
|
419
|
+
index: a.index,
|
|
420
|
+
status: a.status,
|
|
421
|
+
placement: a.placement,
|
|
422
|
+
head_seq: a.headSeq,
|
|
423
|
+
period_end: a.periodEnd,
|
|
424
|
+
})),
|
|
425
|
+
anchored_through: v.anchoredThrough,
|
|
426
|
+
unanchored_from: v.unanchoredFrom,
|
|
427
|
+
unanchored_count: v.unanchoredCount,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
//# sourceMappingURL=chain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain.js","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAsB,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,mGAAmG;AACnG,MAAM,CAAC,MAAM,YAAY,GAAG,wBAAwB,CAAC;AACrD,MAAM,cAAc,GAAG,CAAC,CAAC;AACzB,MAAM,YAAY,GAAG,uBAAuB,CAAC;AAC7C,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,iFAAiF;AACjF,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,8EAA8E;IACrE,OAAO,CAAS;IAChB,GAAG,CAAgB;IACnB,MAAM,CAAgB;IAE/B,YAAY,OAAe,EAAE,MAAc,EAAE,QAA2C,EAAE;QACxF,KAAK,CAAC,GAAG,OAAO,KAAK,MAAM,EAAE,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC;IACrC,CAAC;CACF;AAED,yGAAyG;AACzG,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,OAAO,CAAS;IAEzB,YAAY,OAAe,EAAE,MAAc;QACzC,KAAK,CAAC,GAAG,OAAO,KAAK,MAAM,EAAE,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AA8DD,+EAA+E;AAC/E,gFAAgF;AAChF,kBAAkB;AAClB,MAAM,cAAc,GAAG,MAAM,CAAC,iDAAiD,CAAC,CAAC;AAEjF,SAAS,OAAO,CAAgB,IAAY,EAAE,KAAc,EAAE,OAA6B;IACzF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,OAAO,EAAE,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACrG,OAAO,cAAc,CAAC;IACxB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,KAAK,CAAC,CAAU;IACvB,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpE,CAAC;AAED,8EAA8E;AAC9E,0EAA0E;AAC1E,MAAM,cAAc,GAAG,wEAAwE,CAAC;AAEhG,SAAS,cAAc,CAAC,CAAU;IAChC,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1G,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,SAAS,CAAC,KAAiB;IAClC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,oEAAoE;AACpE,SAAS,IAAI,CAAC,KAAc;IAC1B,OAAO,UAAU,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACjE,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACjF,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1D,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACpE,CAAC;AAED,SAAS,SAAS,CAAC,IAAyB;IAC1C,IAAI,IAAY,CAAC;IACjB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,GAAG,IAAI,CAAC;IACd,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,0EAA0E;YAC1E,4CAA4C;YAC5C,IAAI,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjF,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,sBAAsB,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAyB;IACtD,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7G,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,gBAAgB,CAAC,oBAAoB,EAAE,iCAAiC,CAAC,CAAC;IACtF,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,gBAAgB,CAAC,oBAAoB,EAAE,8CAA8C,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,gBAAgB,IAAI,GAAG,IAAI,UAAU,IAAI,GAAG,EAAE,CAAC;QACjD,MAAM,IAAI,gBAAgB,CACxB,oBAAoB,EACpB,qGAAqG,CACtG,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,YAAY,EAAE,CAAC;QAChC,MAAM,IAAI,gBAAgB,CAAC,sBAAsB,EAAE,oCAAoC,YAAY,OAAO,CAAC,CAAC;IAC9G,CAAC;IACD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,MAAM,KAAK,gBAAgB,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;QAC/G,MAAM,IAAI,gBAAgB,CACxB,oBAAoB,EACpB,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IACD,IAAI,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,gBAAgB,CAAC,oBAAoB,EAAE,yCAAyC,CAAC,CAAC;IAC9F,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IAC1D,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IACvB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,WAAW,GAAG,UAAU,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;IAChD,IAAI,GAAG,CAAC,aAAa,CAAC,KAAK,WAAW,EAAE,CAAC;QACvC,MAAM,IAAI,gBAAgB,CACxB,oBAAoB,EACpB,uGAAuG,CACxG,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,GAAc,EAAE,MAA+B;IACxE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,WAAW,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACtF,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,IAAI,SAAS,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACpD,MAAM,IAAI,GAAG,KAAK,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC;QAChC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC;KACf,CAAC,CAAC;IACH,MAAM,KAAK,GAAoB;QAC7B,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE;QAC5C,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;KAC/B,CAAC;IACF,OAAO,gBAAgB,CAAC,YAAY,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC;AACnE,CAAC;AAED,SAAS,MAAM,CAAC,CAAU;IACxB,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAC1G,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,IAAyB,EAAE,SAAqB;IAC1E,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,+BAA+B,CAAC,CAAC;IAC/F,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,qDAAqD,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,YAAY,CAAC,aAAa,EAAE,2BAA2B,CAAC,CAAC;IAE3F,4EAA4E;IAC5E,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,WAAW,GAAkB,IAAI,CAAC;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,GAAG,GAAY,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9B,IACE,CAAC,QAAQ,CAAC,GAAG,CAAC;YACd,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAClB,OAAO,GAAG,CAAC,YAAY,CAAC,KAAK,QAAQ;YACrC,CAAC,CAAC,eAAe,IAAI,GAAG,CAAC;YACzB,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,eAAe,CAAC,KAAK,QAAQ,CAAC;YAC5E,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EACxB,CAAC;YACD,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,kDAAkD,CAAC,CAAC;QACxG,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACvB,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACZ,QAAQ,GAAG,GAAG,CAAC;QACjB,CAAC;aAAM,IAAI,GAAG,KAAK,QAAQ,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,YAAY,CAAC,aAAa,EAAE,kBAAkB,QAAQ,GAAG,CAAC,GAAG,CAAC,uBAAuB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5G,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,MAAM,CAAC,WAAW,CAAC,KAAK,MAAM,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,SAAS,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;YAC5F,MAAM,IAAI,YAAY,CAAC,kBAAkB,EAAE,qDAAqD,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7G,CAAC;QACD,IAAI,IAAmB,CAAC;QACxB,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACZ,IAAI,GAAG,KAAK,CAAC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACnC,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,gDAAgD,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YACvG,CAAC;YACD,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;gBACZ,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACjE,MAAM,IAAI,YAAY,CACpB,iBAAiB,EACjB,2FAA2F,EAC3F,EAAE,GAAG,EAAE,CACR,CAAC;gBACJ,CAAC;gBACD,WAAW,GAAG,QAAQ,CAAC;YACzB,CAAC;YACD,IAAI,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;YACzC,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;gBACxB,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,6CAA6C,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YACpG,CAAC;YACD,IAAI,GAAG,MAAM,CAAC;QAChB,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,YAAY,CACpB,oBAAoB,EACpB,uEAAuE,EACvE,EAAE,GAAG,EAAE,CACR,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/D,IAAI,IAAI,KAAK,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,YAAY,CAAC,mBAAmB,EAAE,wBAAwB,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAE5C,+EAA+E;IAC/E,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,IAAI,eAAe,GAAkB,IAAI,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAY,OAAO,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7D,IACE,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjB,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ;YACjC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACnB,MAAM,CAAC,GAAG,CAAC,KAAK,cAAc;YAC9B,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YAC5B,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC/B,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YAC9B,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,EAClB,CAAC;YACD,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,4DAA4D,EAAE;gBACtG,MAAM,EAAE,CAAC;aACV,CAAC,CAAC;QACL,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;QACvC,MAAM,KAAK,GAAc,MAAM,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC;QACjE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,YAAY,CAAC,iBAAiB,EAAE,qBAAqB,IAAI,CAAC,MAAM,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACrG,CAAC;QACD,IAAI,MAAM,GAA2B,WAAW,CAAC;QACjD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,GAAG,EAAE,CAAC;gBACpC,MAAM,IAAI,YAAY,CAAC,wBAAwB,EAAE,6CAA6C,EAAE;oBAC9F,MAAM,EAAE,CAAC;iBACV,CAAC,CAAC;YACL,CAAC;YACD,IAAI,CAAC,iBAAiB,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,YAAY,CACpB,wBAAwB,EACxB,kFAAkF,EAClF,EAAE,MAAM,EAAE,CAAC,EAAE,CACd,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,UAAU,CAAC;QACtB,CAAC;QACD,IAAI,SAAS,GAA8B,SAAS,CAAC;QACrD,IAAI,OAAO,GAAkB,IAAI,CAAC;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC;YAC7B,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,QAAQ,GAAkB,IAAI,CAAC;YACnC,IAAI,QAAQ,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,EAAE,CAAC;gBACxC,SAAS,GAAG,SAAS,CAAC;gBACtB,QAAQ,GAAG,UAAU,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,CAAC;YACjD,CAAC;iBAAM,IAAI,IAAI,KAAK,QAAQ,GAAG,CAAC,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;gBACzD,SAAS,GAAG,cAAc,CAAC;gBAC3B,QAAQ,GAAG,WAAW,CAAC;YACzB,CAAC;iBAAM,CAAC;gBACN,SAAS,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC;YAC7D,CAAC;YACD,wEAAwE;YACxE,wEAAwE;YACxE,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC5C,MAAM,IAAI,YAAY,CAAC,oBAAoB,EAAE,2BAA2B,QAAQ,mBAAmB,EAAE;oBACnG,GAAG,EAAE,IAAI;oBACT,MAAM,EAAE,CAAC;iBACV,CAAC,CAAC;YACL,CAAC;YACD,IAAI,MAAM,KAAK,UAAU,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,cAAc,CAAC,EAAE,CAAC;gBACvF,eAAe,GAAG,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;YACtF,CAAC;QACH,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,cAA6B,CAAC;IAClC,IAAI,eAAe,KAAK,IAAI;QAAE,cAAc,GAAG,QAAQ,CAAC;SACnD,IAAI,eAAe,GAAG,OAAO;QAAE,cAAc,GAAG,eAAe,GAAG,CAAC,CAAC;;QACpE,cAAc,GAAG,IAAI,CAAC;IAC3B,MAAM,aAAa,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxD,IAAI,aAAa,KAAK,SAAS;QAAE,MAAM,IAAI,YAAY,CAAC,aAAa,EAAE,2BAA2B,CAAC,CAAC;IACpG,OAAO;QACL,MAAM;QACN,QAAQ;QACR,OAAO;QACP,OAAO,EAAE,KAAK,CAAC,MAAM;QACrB,aAAa;QACb,WAAW;QACX,cAAc,EAAE,SAAS,KAAK,SAAS;QACvC,OAAO,EAAE,OAAO;QAChB,eAAe;QACf,cAAc;QACd,eAAe,EAAE,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,cAAc,GAAG,CAAC;KAC5E,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,IAAyB,EAAE,SAAqB;IAC3E,IAAI,CAAgB,CAAC;IACrB,IAAI,CAAC;QACH,CAAC,GAAG,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,YAAY,EAAE,CAAC;YAC9B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;QAClF,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;IACD,OAAO;QACL,OAAO,EAAE,UAAU;QACnB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,SAAS,EAAE,CAAC,CAAC,QAAQ;QACrB,QAAQ,EAAE,CAAC,CAAC,OAAO;QACnB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,eAAe,EAAE,CAAC,CAAC,aAAa;QAChC,YAAY,EAAE,CAAC,CAAC,WAAW;QAC3B,eAAe,EAAE,CAAC,CAAC,cAAc;QACjC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,QAAQ,EAAE,CAAC,CAAC,OAAO;YACnB,UAAU,EAAE,CAAC,CAAC,SAAS;SACxB,CAAC,CAAC;QACH,gBAAgB,EAAE,CAAC,CAAC,eAAe;QACnC,eAAe,EAAE,CAAC,CAAC,cAAc;QACjC,gBAAgB,EAAE,CAAC,CAAC,eAAe;KACpC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @ziffer-io/verify -- the third receipt-verifier implementation (ACP-197 §5).
|
|
3
3
|
*
|
|
4
|
-
* `verifyReceipt` is the API
|
|
4
|
+
* `verifyReceipt` is the API for a receipt and `verifyChain` for an exported
|
|
5
|
+
* audit chain (ACP-428); everything else is exported so the pieces are
|
|
5
6
|
* individually testable and so `@ziffer-io/client` can re-export ONE verify (the
|
|
6
7
|
* client adds no verification logic of its own -- one home per rule).
|
|
7
8
|
*/
|
|
8
9
|
export { canon, compareCodePoints, type Json } from './canon.js';
|
|
10
|
+
export { ANCHOR_SUITE, AnchorKeyRefusal, ChainRefusal, chainVerdict, parseAnchorKey, verifyChain, type AnchorKey, type AnchorResult, type ChainVerdict, type ChainVerified, } from './chain.js';
|
|
9
11
|
export { CborError, cborAsBytes, cborEncode, cborGetInt, cborGetText, cborIsMap, cborKeys, decodeCanonical, type Cbor, type CborMap, } from './cbor.js';
|
|
10
12
|
export { CLAUSE_ASSURANCE, CLAUSE_ATTESTATION_SUITE, CLAUSE_ATTESTER_SIG, CLAUSE_ATT_NONCE_SIZE, CLAUSE_BINDING, CLAUSE_CARDINALITY, CLAUSE_CONSENT, CLAUSE_DERIVED_ID, CLAUSE_DIGEST_DUP, CLAUSE_DIGEST_ORDER, CLAUSE_MALFORMED, CLAUSE_MEMBERSHIP, CLAUSE_NO_ATTESTATIONS, CLAUSE_NO_OBJECT, CLAUSE_OBJECT_SCHEMA, CLAUSE_OPERATOR_DISAGREE, CLAUSE_OPERATOR_SELF, CLAUSE_POLICY_BASIS, CLAUSE_QUORUM, CLAUSE_WEBAUTHN_ALG, CLAUSE_WEBAUTHN_COUNTER, } from './clauses.js';
|
|
11
13
|
export { AT1_FIELDS, attestationId, entryDigest, verifyQuorum, type AttesterCredential, type DecisionBasis, type HybridAttester, type QuorumOutcome, type QuorumPolicy, type WebauthnAttester, } from './quorum.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,WAAW,EACX,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,SAAS,EACT,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,SAAS,EACT,QAAQ,EACR,eAAe,EACf,KAAK,IAAI,EACT,KAAK,OAAO,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,UAAU,EACV,aAAa,EACb,WAAW,EACX,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,aAAa,EACb,2BAA2B,EAC3B,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,uBAAuB,EACvB,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EACL,UAAU,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,KAAK,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,wBAAwB,EACxB,eAAe,EACf,cAAc,EACd,QAAQ,EACR,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @ziffer-io/verify -- the third receipt-verifier implementation (ACP-197 §5).
|
|
3
3
|
*
|
|
4
|
-
* `verifyReceipt` is the API
|
|
4
|
+
* `verifyReceipt` is the API for a receipt and `verifyChain` for an exported
|
|
5
|
+
* audit chain (ACP-428); everything else is exported so the pieces are
|
|
5
6
|
* individually testable and so `@ziffer-io/client` can re-export ONE verify (the
|
|
6
7
|
* client adds no verification logic of its own -- one home per rule).
|
|
7
8
|
*/
|
|
8
9
|
export { canon, compareCodePoints } from './canon.js';
|
|
10
|
+
export { ANCHOR_SUITE, AnchorKeyRefusal, ChainRefusal, chainVerdict, parseAnchorKey, verifyChain, } from './chain.js';
|
|
9
11
|
export { CborError, cborAsBytes, cborEncode, cborGetInt, cborGetText, cborIsMap, cborKeys, decodeCanonical, } from './cbor.js';
|
|
10
12
|
export { CLAUSE_ASSURANCE, CLAUSE_ATTESTATION_SUITE, CLAUSE_ATTESTER_SIG, CLAUSE_ATT_NONCE_SIZE, CLAUSE_BINDING, CLAUSE_CARDINALITY, CLAUSE_CONSENT, CLAUSE_DERIVED_ID, CLAUSE_DIGEST_DUP, CLAUSE_DIGEST_ORDER, CLAUSE_MALFORMED, CLAUSE_MEMBERSHIP, CLAUSE_NO_ATTESTATIONS, CLAUSE_NO_OBJECT, CLAUSE_OBJECT_SCHEMA, CLAUSE_OPERATOR_DISAGREE, CLAUSE_OPERATOR_SELF, CLAUSE_POLICY_BASIS, CLAUSE_QUORUM, CLAUSE_WEBAUTHN_ALG, CLAUSE_WEBAUTHN_COUNTER, } from './clauses.js';
|
|
11
13
|
export { AT1_FIELDS, attestationId, entryDigest, verifyQuorum, } from './quorum.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAa,MAAM,YAAY,CAAC;AACjE,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,WAAW,GAKZ,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,SAAS,EACT,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,SAAS,EACT,QAAQ,EACR,eAAe,GAGhB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,UAAU,EACV,aAAa,EACb,WAAW,EACX,YAAY,GAOb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,aAAa,EACb,2BAA2B,EAC3B,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,eAAe,EACf,cAAc,EACd,iBAAiB,GAGlB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,uBAAuB,EACvB,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EACL,UAAU,EACV,cAAc,EACd,eAAe,EACf,YAAY,GAKb,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,wBAAwB,EACxB,eAAe,EACf,cAAc,EACd,QAAQ,EACR,aAAa,GAId,MAAM,aAAa,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziffer-io/verify",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Verify a ZIFFER decision receipt in your own process, against a key you configured.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ziffer",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"ziffer": {
|
|
44
|
-
"enginePin": "
|
|
44
|
+
"enginePin": "0ce0567bec3ee9d8c2b58d8c26c1ec83c4782f72"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@noble/curves": "2.3.0",
|