@ziffer-io/verify 0.1.1 → 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/cbor.d.ts +87 -0
- package/dist/cbor.d.ts.map +1 -0
- package/dist/cbor.js +326 -0
- package/dist/cbor.js.map +1 -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/clauses.d.ts +70 -0
- package/dist/clauses.d.ts.map +1 -1
- package/dist/clauses.js +77 -0
- package/dist/clauses.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/quorum.d.ts +245 -0
- package/dist/quorum.d.ts.map +1 -0
- package/dist/quorum.js +566 -0
- package/dist/quorum.js.map +1 -0
- package/dist/sig.d.ts +57 -0
- package/dist/sig.d.ts.map +1 -0
- package/dist/sig.js +127 -0
- package/dist/sig.js.map +1 -0
- package/dist/testkeys.d.ts +33 -0
- package/dist/testkeys.d.ts.map +1 -1
- package/dist/testkeys.js +100 -0
- package/dist/testkeys.js.map +1 -1
- package/dist/verify.d.ts +44 -20
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +55 -146
- package/dist/verify.js.map +1 -1
- package/dist/webauthn.d.ts +148 -0
- package/dist/webauthn.d.ts.map +1 -0
- package/dist/webauthn.js +414 -0
- package/dist/webauthn.js.map +1 -0
- package/dist/wire.d.ts +44 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +90 -0
- package/dist/wire.js.map +1 -0
- package/package.json +2 -2
package/dist/verify.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* | 5 | temporal position (9.3-5) and the L-14 window ceiling | yes |
|
|
20
20
|
* | 6 | nonce WE-4 type and L-17 size | yes; the CL-2 single-use CLAIM is ledger state |
|
|
21
21
|
* | 7 | TR-8 / RV-3 recomputation | NO -- needs the signed policy |
|
|
22
|
-
* | 7b | the AT-* quorum, AB-1..AB-4 |
|
|
22
|
+
* | 7b | the AT-* quorum, AB-1..AB-4 | YES, when the anchor carries a `quorum` policy |
|
|
23
23
|
* | 8 | tenant scoping | NO -- needs the bundle's tenant |
|
|
24
24
|
* | 9-10 | capability recheck, delivery identity | NO -- absent in the engine too |
|
|
25
25
|
*
|
|
@@ -28,6 +28,24 @@
|
|
|
28
28
|
* here means "the stateless set found nothing", never "the receipt may be
|
|
29
29
|
* consumed".
|
|
30
30
|
*
|
|
31
|
+
* # Step 7b, and the one thing it still cannot answer (ACP-406 gap 1)
|
|
32
|
+
*
|
|
33
|
+
* Step 7b runs here when {@link TrustAnchor.quorum} is supplied from the
|
|
34
|
+
* caller's own signed bundle -- see `quorum.ts` for the full checklist and its
|
|
35
|
+
* R/B/T table. Two consequences belong in this header rather than only there:
|
|
36
|
+
*
|
|
37
|
+
* - A receipt that CARRIES attestations while the anchor carries no quorum
|
|
38
|
+
* policy is **refused** (`AB-1`), not passed. A verifier that cannot
|
|
39
|
+
* recompute an entry digest has no binding at all, and passing would be the
|
|
40
|
+
* permissive default this package exists to refuse.
|
|
41
|
+
* - **This function still cannot tell that a quorum was REQUIRED.** That is
|
|
42
|
+
* step 7's recomputed floor-only risk, which needs the bundle's grading rules
|
|
43
|
+
* and is not here. A receipt carrying no attestations passes 7b vacuously and
|
|
44
|
+
* {@link Verified.quorum} is `null`, which the caller can read; taking the
|
|
45
|
+
* answer from the receipt's own `risk_level_floor_only` instead would be X1,
|
|
46
|
+
* the defect where a compromised issuer asserting `LOW` suppressed
|
|
47
|
+
* attestation entirely.
|
|
48
|
+
*
|
|
31
49
|
* # R / B / T classification (suite 12) for the inputs this module reads
|
|
32
50
|
*
|
|
33
51
|
* | input | class | why |
|
|
@@ -39,13 +57,19 @@
|
|
|
39
57
|
* | `nowUnixSeconds` | T | the caller's clock. Nothing here can check it; a stale or forward value silently voids the expiry and skew checks (the engine's residual 3, verbatim). Only L-14 survives such a caller, because it compares the receipt's two instants against each other |
|
|
40
58
|
*/
|
|
41
59
|
import { createHash } from 'node:crypto';
|
|
42
|
-
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
|
|
43
60
|
import { canon } from './canon.js';
|
|
44
|
-
import { CLAUSE_BODY_SIZE, CLAUSE_CANONICAL, CLAUSE_DECISION, CLAUSE_PROPOSAL_BINDING, CLAUSE_RECEIPT_NONCE_SIZE, CLAUSE_SIGNATURE, CLAUSE_SUITE_FLOOR, CLAUSE_TEMPORAL, CLAUSE_UNKNOWN_SUITE, CLAUSE_VALIDITY_WINDOW, CLAUSE_VERSION, CLAUSE_WIRE_TYPE, } from './clauses.js';
|
|
45
|
-
import { verifyEd25519Strict } from './ed25519.js';
|
|
61
|
+
import { CLAUSE_BODY_SIZE, CLAUSE_MEMBERSHIP, CLAUSE_CANONICAL, CLAUSE_DECISION, CLAUSE_PROPOSAL_BINDING, CLAUSE_RECEIPT_NONCE_SIZE, CLAUSE_SIGNATURE, CLAUSE_SUITE_FLOOR, CLAUSE_TEMPORAL, CLAUSE_UNKNOWN_SUITE, CLAUSE_VALIDITY_WINDOW, CLAUSE_VERSION, CLAUSE_WIRE_TYPE, } from './clauses.js';
|
|
46
62
|
import { parseInstant } from './instant.js';
|
|
63
|
+
import { verifyQuorum, } from './quorum.js';
|
|
47
64
|
import { Refusal } from './refusal.js';
|
|
48
|
-
import {
|
|
65
|
+
import { hex, parseSig, verifyUnderSuite } from './sig.js';
|
|
66
|
+
import { parseSuite, satisfiesFloor } from './suite.js';
|
|
67
|
+
import { isRecord, isWe4B64, NONCE128_BYTES, NONCE128_LEN, strField } from './wire.js';
|
|
68
|
+
// WE-4 and the nonce sizes moved to `wire.ts` so `quorum.ts` can apply the one
|
|
69
|
+
// definition to `att_nonce`; re-exported here because they were this module's
|
|
70
|
+
// published surface first and a move is not a removal.
|
|
71
|
+
export { isWe4B64, NONCE128_BYTES, NONCE128_LEN } from './wire.js';
|
|
72
|
+
export { MLDSA65_PK_LEN, MLDSA65_SIG_LEN } from './sig.js';
|
|
49
73
|
/** AB-0 (§8.6b): version 3, the digest-bound form, and NO other. A verifier
|
|
50
74
|
* accepting both 2 and 3 lets an attacker present the weaker one -- the CR-4
|
|
51
75
|
* downgrade shape with the format negotiated by the party under verification. */
|
|
@@ -60,127 +84,6 @@ export const RECEIPT_MAX_SIGNED_BYTES = 4096;
|
|
|
60
84
|
export const CLOCK_SKEW_SECS = 5;
|
|
61
85
|
/** L-14's ceiling on a receipt's validity window, seconds. */
|
|
62
86
|
export const MAX_VALIDITY_WINDOW_SECS = 120;
|
|
63
|
-
/** FIPS 204 ML-DSA-65 public key length. */
|
|
64
|
-
export const MLDSA65_PK_LEN = 1952;
|
|
65
|
-
/** FIPS 204 ML-DSA-65 signature length. */
|
|
66
|
-
export const MLDSA65_SIG_LEN = 3309;
|
|
67
|
-
/** The one wire nonce size, 128-bit (`Nonce128`, cited by AT-1 and L-17). */
|
|
68
|
-
export const NONCE128_BYTES = 16;
|
|
69
|
-
/** `"b64:"` plus base64 of 16 bytes: 4 + 24. Counted, never decoded -- a rule
|
|
70
|
-
* whose behaviour on bad input is "throw" is not a refusal. */
|
|
71
|
-
export const NONCE128_LEN = 4 + Math.ceil(NONCE128_BYTES / 3) * 4;
|
|
72
|
-
function isRecord(v) {
|
|
73
|
-
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
74
|
-
}
|
|
75
|
-
function strField(v, key) {
|
|
76
|
-
const raw = v[key];
|
|
77
|
-
return typeof raw === 'string' ? raw : null;
|
|
78
|
-
}
|
|
79
|
-
function hex(bytes) {
|
|
80
|
-
let out = '';
|
|
81
|
-
for (const b of bytes)
|
|
82
|
-
out += b.toString(16).padStart(2, '0');
|
|
83
|
-
return out;
|
|
84
|
-
}
|
|
85
|
-
/** Decode a hex signature leg, refusing exactly as decide.rs's `unhex`. */
|
|
86
|
-
function unhex(s) {
|
|
87
|
-
if (s.length % 2 !== 0) {
|
|
88
|
-
throw new Refusal(CLAUSE_SIGNATURE, 'signature hex has odd length');
|
|
89
|
-
}
|
|
90
|
-
const out = new Uint8Array(s.length / 2);
|
|
91
|
-
for (let i = 0; i < s.length; i += 2) {
|
|
92
|
-
const byte = Number.parseInt(s.slice(i, i + 2), 16);
|
|
93
|
-
// parseInt would tolerate "0x", whitespace and a lone valid first digit;
|
|
94
|
-
// requiring both chars to be hex keeps this as strict as Rust's
|
|
95
|
-
// from_str_radix over a 2-char slice.
|
|
96
|
-
if (Number.isNaN(byte) || !/^[0-9a-fA-F]{2}$/.test(s.slice(i, i + 2))) {
|
|
97
|
-
throw new Refusal(CLAUSE_SIGNATURE, 'signature is not hex');
|
|
98
|
-
}
|
|
99
|
-
out[i / 2] = byte;
|
|
100
|
-
}
|
|
101
|
-
return out;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* WE-4: is this the ASCII string `b64:` followed by RFC 4648 §4 base64, WITH
|
|
105
|
-
* padding? Ported from `quorum.rs::is_we4_b64` -- the body is whole
|
|
106
|
-
* four-character groups, the alphabet is §4's (`+` and `/`, never §5's `-` and
|
|
107
|
-
* `_`), and `=` appears only as one or two characters at the very end.
|
|
108
|
-
* Rejected, never normalised: normalising would make this verifier accept two
|
|
109
|
-
* spellings of one nonce, which is how one nonce comes to hold two ledger
|
|
110
|
-
* slots (ACP-87).
|
|
111
|
-
*/
|
|
112
|
-
export function isWe4B64(s) {
|
|
113
|
-
if (!s.startsWith('b64:'))
|
|
114
|
-
return false;
|
|
115
|
-
const body = s.slice(4);
|
|
116
|
-
if (body.length % 4 !== 0)
|
|
117
|
-
return false;
|
|
118
|
-
let pad = 0;
|
|
119
|
-
for (let i = body.length - 1; i >= 0 && body[i] === '='; i -= 1)
|
|
120
|
-
pad += 1;
|
|
121
|
-
if (pad > 2)
|
|
122
|
-
return false;
|
|
123
|
-
for (let i = 0; i < body.length - pad; i += 1) {
|
|
124
|
-
const c = body.charCodeAt(i);
|
|
125
|
-
const alnum = (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a);
|
|
126
|
-
if (!alnum && c !== 0x2b /* + */ && c !== 0x2f /* / */)
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Parse a wire `sig` object into the parts the CR-3 combiner verifies --
|
|
133
|
-
* `decide.rs::parse_sig`, including its rule that the key set must be EXACTLY
|
|
134
|
-
* the suite's. Three attacks live in the difference: a scalar `sig` (format
|
|
135
|
-
* confusion is a downgrade in disguise), a missing primitive (the stripped
|
|
136
|
-
* leg), and an extra undeclared primitive (an accepted code path the attacker
|
|
137
|
-
* chose). An unknown primitive name is refused rather than dropped -- a
|
|
138
|
-
* dropped part would let a suite be satisfied by the parts that remain.
|
|
139
|
-
*/
|
|
140
|
-
function parseSig(sig, alg) {
|
|
141
|
-
// CR-1 first: an unparseable suite has no primitive set at all, so asking
|
|
142
|
-
// which primitives it requires would be a category error.
|
|
143
|
-
const suite = parseSuite(alg);
|
|
144
|
-
if (suite === null) {
|
|
145
|
-
throw new Refusal(CLAUSE_UNKNOWN_SUITE, 'unknown signature suite');
|
|
146
|
-
}
|
|
147
|
-
if (!isRecord(sig)) {
|
|
148
|
-
throw new Refusal(CLAUSE_SIGNATURE, 'receipt signature is not a per-primitive object');
|
|
149
|
-
}
|
|
150
|
-
const required = suitePrimitives(suite);
|
|
151
|
-
const keys = Object.keys(sig);
|
|
152
|
-
if (keys.length !== required.length || !required.every((p) => keys.includes(p))) {
|
|
153
|
-
throw new Refusal(CLAUSE_SIGNATURE, 'signature primitives are not exactly those the declared suite requires');
|
|
154
|
-
}
|
|
155
|
-
const parts = [];
|
|
156
|
-
for (const primitive of required) {
|
|
157
|
-
const value = sig[primitive];
|
|
158
|
-
if (typeof value !== 'string') {
|
|
159
|
-
throw new Refusal(CLAUSE_SIGNATURE, 'signature value is not a hex string');
|
|
160
|
-
}
|
|
161
|
-
parts.push({ primitive, bytes: unhex(value) });
|
|
162
|
-
}
|
|
163
|
-
return parts;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Verify one ML-DSA-65 signature. Never throws; length checks first, exactly
|
|
167
|
-
* as `primitives.rs` -- a malformed input is `invalid`, never `unsupported`:
|
|
168
|
-
* one is a statement about the signature, the other about this build, and
|
|
169
|
-
* reporting one as the other sends the investigation to the wrong place.
|
|
170
|
-
*/
|
|
171
|
-
function verifyMlDsa65(publicKey, message, signature) {
|
|
172
|
-
if (publicKey.length !== MLDSA65_PK_LEN || signature.length !== MLDSA65_SIG_LEN) {
|
|
173
|
-
return 'invalid';
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
// Empty FIPS 204 context, matching both engine sides (MLDSA_CTX = &[]):
|
|
177
|
-
// a context the signer did not use is a different message.
|
|
178
|
-
return ml_dsa65.verify(signature, message, publicKey) ? 'valid' : 'invalid';
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
return 'invalid';
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
87
|
/**
|
|
185
88
|
* The §9.3 gate steps 1-2 with CR-1/CR-4/CR-3 -- `receipt.rs::verify_receipt`.
|
|
186
89
|
*
|
|
@@ -198,23 +101,7 @@ function verifyReceiptGate(alg, floor, identity, signedBytes, parts, decision) {
|
|
|
198
101
|
if (!satisfiesFloor(suite, floor)) {
|
|
199
102
|
throw new Refusal(CLAUSE_SUITE_FLOOR, 'signature suite does not contain every primitive of the bundle floor');
|
|
200
103
|
}
|
|
201
|
-
const
|
|
202
|
-
switch (part.primitive) {
|
|
203
|
-
case 'classical':
|
|
204
|
-
return [
|
|
205
|
-
part.primitive,
|
|
206
|
-
verifyEd25519Strict(identity.classical, signedBytes, part.bytes) ? 'valid' : 'invalid',
|
|
207
|
-
];
|
|
208
|
-
case 'pq':
|
|
209
|
-
return [part.primitive, verifyMlDsa65(identity.pq, signedBytes, part.bytes)];
|
|
210
|
-
case 'pq-slh':
|
|
211
|
-
// Declared, not implemented. Never a pass, and never silently dropped
|
|
212
|
-
// from the set either -- dropping it would let a suite naming it be
|
|
213
|
-
// satisfied by the primitives that remain.
|
|
214
|
-
return [part.primitive, 'unsupported'];
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
const err = verifyHybrid(suite, verdicts);
|
|
104
|
+
const err = verifyUnderSuite(suite, identity, signedBytes, parts);
|
|
218
105
|
if (err !== null) {
|
|
219
106
|
throw new Refusal(CLAUSE_SIGNATURE, err);
|
|
220
107
|
}
|
|
@@ -331,8 +218,30 @@ export function verifyReceipt(receiptJson, proposalBytes, identity, opts) {
|
|
|
331
218
|
if (nonce.length !== NONCE128_LEN) {
|
|
332
219
|
throw new Refusal(CLAUSE_RECEIPT_NONCE_SIZE, `receipt nonce is not ${NONCE128_BYTES * 8}-bit`);
|
|
333
220
|
}
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
221
|
+
// ----------------------------------------------------------- step 7b
|
|
222
|
+
// The AT-* quorum and §8.6b's AB-*, LAST, because every value it is checked
|
|
223
|
+
// against was established above: `proposalHash` at step 3 and `iat` at step
|
|
224
|
+
// 5. Steps 7 (grading) and 8 (tenant) are still absent -- see the module
|
|
225
|
+
// doc's table -- so `floor_only_risk` is the one 7b(iii) comparison this
|
|
226
|
+
// package cannot make, and `quorum.ts` says so rather than approximating it.
|
|
227
|
+
const entries = receiptJson['attestations'];
|
|
228
|
+
const digests = receiptJson['attestation_digests'];
|
|
229
|
+
const carriesQuorum = (Array.isArray(entries) && entries.length > 0) ||
|
|
230
|
+
(Array.isArray(digests) && digests.length > 0) ||
|
|
231
|
+
(entries !== undefined && !Array.isArray(entries)) ||
|
|
232
|
+
(digests !== undefined && !Array.isArray(digests));
|
|
233
|
+
if (identity.quorum === undefined) {
|
|
234
|
+
if (carriesQuorum) {
|
|
235
|
+
// FAIL CLOSED. A verifier with no registry cannot recompute an entry
|
|
236
|
+
// digest, so it has no binding to the attestations at all; passing here
|
|
237
|
+
// would be exactly the permissive-by-omission default RES-8 keeps
|
|
238
|
+
// producing. AB-1 is the clause because it is the obligation that cannot
|
|
239
|
+
// be discharged.
|
|
240
|
+
throw new Refusal(CLAUSE_MEMBERSHIP, 'the receipt carries attestations and this trust anchor names no attester registry: step 7b cannot be discharged');
|
|
241
|
+
}
|
|
242
|
+
return { proposalHash, receiptExpiresAt: exp, quorum: null };
|
|
243
|
+
}
|
|
244
|
+
const quorum = verifyQuorum(identity.quorum, { proposalHash, receiptIssuedAt: iat, floor }, entries, digests);
|
|
245
|
+
return { proposalHash, receiptExpiresAt: exp, quorum };
|
|
337
246
|
}
|
|
338
247
|
//# sourceMappingURL=verify.js.map
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,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,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,YAAY,GAGb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,gBAAgB,EAAsB,MAAM,UAAU,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEvF,+EAA+E;AAC/E,8EAA8E;AAC9E,uDAAuD;AACvD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3D;;iFAEiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAEjC;gFACgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAsB,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;AAEzE;6EAC6E;AAC7E,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAE7C,iFAAiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAEjC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAgE5C;;;;;;;;GAQG;AACH,SAAS,iBAAiB,CACxB,GAAW,EACX,KAAY,EACZ,QAAqB,EACrB,WAAuB,EACvB,KAA+B,EAC/B,QAAgB;IAEhB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,OAAO,CAAC,oBAAoB,EAAE,yBAAyB,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,OAAO,CACf,kBAAkB,EAClB,sEAAsE,CACvE,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IAClE,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,MAAM,IAAI,OAAO,CAAC,eAAe,EAAE,uBAAuB,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAC3B,WAAoB,EACpB,aAAyB,EACzB,QAAqB,EACrB,IAAoB;IAEpB,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,wEAAwE;QACxE,uEAAuE;QACvE,uEAAuE;QACvE,MAAM,IAAI,OAAO,CAAC,oBAAoB,EAAE,2CAA2C,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,EAAE,cAAc,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAEtD,qEAAqE;IACrE,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,yDAAyD;IACzD,MAAM,OAAO,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,IAAI,OAAO,KAAK,eAAe,EAAE,CAAC;QAChC,MAAM,IAAI,OAAO,CACf,cAAc,EACd,mBAAmB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,eAAe,EAAE,CACvE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3B,uEAAuE;QACvE,kEAAkE;QAClE,MAAM,IAAI,OAAO,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,CAAC;IAClE,CAAC;IAED,uEAAuE;IACvE,4EAA4E;IAC5E,uEAAuE;IACvE,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAEhC,wEAAwE;IACxE,+BAA+B;IAC/B,IAAI,WAAW,CAAC,MAAM,GAAG,wBAAwB,EAAE,CAAC;QAClD,MAAM,IAAI,OAAO,CACf,gBAAgB,EAChB,kBAAkB,WAAW,CAAC,MAAM,oBAAoB,wBAAwB,qBAAqB,CACtG,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACzC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,OAAO,CAAC,oBAAoB,EAAE,2BAA2B,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;IAChD,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IAErG,sEAAsE;IACtE,wEAAwE;IACxE,4EAA4E;IAC5E,uEAAuE;IACvE,uCAAuC;IACvC,IAAI,aAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,OAAO,CAAC,gBAAgB,EAAE,mCAAmC,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,YAAY,GAAG,UAAU,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;IACjG,IAAI,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC,KAAK,YAAY,EAAE,CAAC;QAC5D,MAAM,IAAI,OAAO,CAAC,uBAAuB,EAAE,oCAAoC,CAAC,CAAC;IACnF,CAAC;IAED,yEAAyE;IACzE,wEAAwE;IACxE,8EAA8E;IAE9E,sEAAsE;IACtE,yEAAyE;IACzE,sCAAsC;IACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IACnD,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACvC,MAAM,IAAI,OAAO,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjC,MAAM,IAAI,OAAO,CAAC,eAAe,EAAE,+CAA+C,CAAC,CAAC;IACtF,CAAC;IACD,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;QACd,MAAM,IAAI,OAAO,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,GAAG,GAAG,GAAG,GAAG,eAAe,EAAE,CAAC;QAChC,MAAM,IAAI,OAAO,CAAC,eAAe,EAAE,kCAAkC,CAAC,CAAC;IACzE,CAAC;IACD,0EAA0E;IAC1E,sEAAsE;IACtE,IAAI,GAAG,GAAG,GAAG,GAAG,wBAAwB,EAAE,CAAC;QACzC,MAAM,IAAI,OAAO,CACf,sBAAsB,EACtB,mBAAmB,GAAG,GAAG,GAAG,aAAa,wBAAwB,GAAG,CACrE,CAAC;IACJ,CAAC;IAED,sEAAsE;IACtE,2EAA2E;IAC3E,oEAAoE;IACpE,wEAAwE;IACxE,yEAAyE;IACzE,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;IACnD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,OAAO,CACf,gBAAgB,EAChB,iBAAiB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,mDAAmD,CACzG,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QAClC,MAAM,IAAI,OAAO,CACf,yBAAyB,EACzB,wBAAwB,cAAc,GAAG,CAAC,MAAM,CACjD,CAAC;IACJ,CAAC;IAED,sEAAsE;IACtE,4EAA4E;IAC5E,4EAA4E;IAC5E,yEAAyE;IACzE,yEAAyE;IACzE,6EAA6E;IAC7E,MAAM,OAAO,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,WAAW,CAAC,qBAAqB,CAAC,CAAC;IACnD,MAAM,aAAa,GACjB,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9C,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9C,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAClC,IAAI,aAAa,EAAE,CAAC;YAClB,qEAAqE;YACrE,wEAAwE;YACxE,kEAAkE;YAClE,yEAAyE;YACzE,iBAAiB;YACjB,MAAM,IAAI,OAAO,CACf,iBAAiB,EACjB,iHAAiH,CAClH,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC/D,CAAC;IACD,MAAM,MAAM,GAAG,YAAY,CACzB,QAAQ,CAAC,MAAM,EACf,EAAE,YAAY,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,EAAE,EAC7C,OAAO,EACP,OAAO,CACR,CAAC;IAEF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §8.6c HM-1..HM-7 -- a human attester's WebAuthn assertion, verified in the
|
|
3
|
+
* customer's own process.
|
|
4
|
+
*
|
|
5
|
+
* The third writing of `crates/acp-crypto/src/webauthn.rs` and
|
|
6
|
+
* `reference/src/acp_crypto.py`, and the mirrored corpus
|
|
7
|
+
* `fixtures/refused_webauthn.json` is what holds the three to one rule: 18
|
|
8
|
+
* vectors, two ACCEPT and one refusal per HM-4 step, replayed by
|
|
9
|
+
* `webauthn.test.ts` against this file.
|
|
10
|
+
*
|
|
11
|
+
* # What a human leg is NOT (HM-6, CR-8)
|
|
12
|
+
*
|
|
13
|
+
* **CLASSICAL, and this function must never be read as though it were more.**
|
|
14
|
+
* No passkey, platform authenticator or security key in existence produces a
|
|
15
|
+
* post-quantum signature, so a `webauthn` entry is never compared against the
|
|
16
|
+
* bundle's `min_suite` (CR-8) -- the absence of a floor check here IS the
|
|
17
|
+
* clause, not an omission. The post-quantum binding of a human's decision is
|
|
18
|
+
* the receipt AB-1 digests the whole entry into and the §11 anchor over it.
|
|
19
|
+
*
|
|
20
|
+
* # The order is normative and the NAMES are the product (HM-4)
|
|
21
|
+
*
|
|
22
|
+
* HM-4 states six checks in order and says no step is skipped for a smaller one
|
|
23
|
+
* that already failed. What the implementations must agree on is not "refused"
|
|
24
|
+
* but WHICH step refused: `WebauthnOrigin` is a phishing finding and
|
|
25
|
+
* `WebauthnChallenge` is a replay, and one name for both would make them one
|
|
26
|
+
* event in an audit record. That is why this throws a named
|
|
27
|
+
* {@link WebauthnRefusal} rather than returning a boolean.
|
|
28
|
+
*
|
|
29
|
+
* Step (f), the signature counter, is NOT here: comparing it needs the stored
|
|
30
|
+
* value for the credential, which is Consumption Ledger state. What this
|
|
31
|
+
* returns is the counter the assertion carried; `quorum.ts` compares it against
|
|
32
|
+
* what the caller stored, and discloses that this package holds no ledger.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* HM-1 / CR-8: the two names an entry's `alg` may take.
|
|
36
|
+
*
|
|
37
|
+
* ATTESTATION-ENTRY SUITES ONLY. `suite.ts`'s table is the machine one and
|
|
38
|
+
* these are deliberately absent from it, because CR-8 forbids either as a
|
|
39
|
+
* bundle, receipt or door suite -- a name present in both tables is a name
|
|
40
|
+
* whose meaning depends on where it was read.
|
|
41
|
+
*/
|
|
42
|
+
export declare const WEBAUTHN_ALGS: readonly string[];
|
|
43
|
+
/** The one primitive name a human entry's `sig` map carries (HM-3). */
|
|
44
|
+
export declare const WEBAUTHN_PRIMITIVE = "webauthn";
|
|
45
|
+
/**
|
|
46
|
+
* HM-3's three fields.
|
|
47
|
+
*
|
|
48
|
+
* **This is not their canonical ORDER**, and the difference looks like one.
|
|
49
|
+
* RFC 8949 §4.2.1 sorts map keys by their ENCODED bytes and a text key's head
|
|
50
|
+
* carries its length, so the wire order is by length first: `signature` (9),
|
|
51
|
+
* `client_data_json` (16), `authenticator_data` (18). The decoder gets that
|
|
52
|
+
* right by construction; a reader who assumed this array's order was the wire
|
|
53
|
+
* order would write a decoder that refuses every real assertion.
|
|
54
|
+
*/
|
|
55
|
+
export declare const ASSERTION_FIELDS: readonly string[];
|
|
56
|
+
/** The registry entry, the COSE_Key or the assertion is not the declared shape. */
|
|
57
|
+
export declare const MALFORMED = "Malformed";
|
|
58
|
+
/** PB-9 / HM-5, at enrolment: an off-curve ES256 key or a small-order Ed25519 one. */
|
|
59
|
+
export declare const REGISTRY_KEY_WEAK = "RegistryKeyWeak";
|
|
60
|
+
/** HM-4 (a). */
|
|
61
|
+
export declare const WEBAUTHN_TYPE = "WebauthnType";
|
|
62
|
+
/** HM-4 (b). */
|
|
63
|
+
export declare const WEBAUTHN_CHALLENGE = "WebauthnChallenge";
|
|
64
|
+
/** HM-4 (c). */
|
|
65
|
+
export declare const WEBAUTHN_ORIGIN = "WebauthnOrigin";
|
|
66
|
+
/** HM-4 (d). */
|
|
67
|
+
export declare const WEBAUTHN_AUTHENTICATOR_DATA = "WebauthnAuthenticatorData";
|
|
68
|
+
/** HM-4 (e). */
|
|
69
|
+
export declare const WEBAUTHN_SIGNATURE = "WebauthnSignature";
|
|
70
|
+
/**
|
|
71
|
+
* One HM-4 step refused, named.
|
|
72
|
+
*
|
|
73
|
+
* `name` is the STEP and is what the cross-implementation comparison is on; the
|
|
74
|
+
* clause is HM-4 for all of them, and six failures under one clause id would be
|
|
75
|
+
* one name for six objects. Deliberately NOT a {@link import('./refusal.js').Refusal}:
|
|
76
|
+
* `quorum.ts` maps these to §9.3's vocabulary at its own boundary, and a type
|
|
77
|
+
* that could travel straight out would let an unmapped name reach a caller.
|
|
78
|
+
*/
|
|
79
|
+
export declare class WebauthnRefusal extends Error {
|
|
80
|
+
readonly refusalName: string;
|
|
81
|
+
readonly detail: string;
|
|
82
|
+
constructor(refusalName: string, detail: string);
|
|
83
|
+
}
|
|
84
|
+
/** A credential's public key, decoded and already known to be a usable point. */
|
|
85
|
+
export type CredentialKey = {
|
|
86
|
+
readonly kind: 'es256';
|
|
87
|
+
readonly sec1: Uint8Array;
|
|
88
|
+
} | {
|
|
89
|
+
readonly kind: 'ed25519';
|
|
90
|
+
readonly raw: Uint8Array;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* HM-1 / HM-5: decode a credential's COSE_Key.
|
|
94
|
+
*
|
|
95
|
+
* The decode is CANONICAL AND VALIDATING (AT-8a) because the registry entry is
|
|
96
|
+
* signed policy: a key that decodes from two different byte strings is a
|
|
97
|
+
* credential with two names.
|
|
98
|
+
*
|
|
99
|
+
* WEAKNESS IS REFUSED HERE, at enrolment, not left to the verifier: an ES256
|
|
100
|
+
* point off the curve and an Ed25519 point of small order are both
|
|
101
|
+
* `RegistryKeyWeak` (HM-5, PB-9). The curve-membership question is asked of
|
|
102
|
+
* `p256`'s own construction -- the same one the verifier will use -- rather than
|
|
103
|
+
* of a second copy of the curve equation, which would be a second definition of
|
|
104
|
+
* the curve. {@link ed25519IsSmallOrder} is the predicate `verifyEd25519Strict`
|
|
105
|
+
* already applies: one rule, two callers (ACP-109).
|
|
106
|
+
*/
|
|
107
|
+
export declare function coseKeyParse(alg: string, raw: Uint8Array): CredentialKey;
|
|
108
|
+
/** HM-3's three fields, decoded. */
|
|
109
|
+
export interface Assertion {
|
|
110
|
+
readonly authenticatorData: Uint8Array;
|
|
111
|
+
readonly clientDataJson: Uint8Array;
|
|
112
|
+
readonly signature: Uint8Array;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* HM-3: the `webauthn` primitive's value, decoded under a FIXED SHAPE.
|
|
116
|
+
*
|
|
117
|
+
* A general CBOR decoder is the wrong tool and is deliberately not offered.
|
|
118
|
+
* What arrives here is attacker-supplied on the transport path, so the decoder
|
|
119
|
+
* must accept exactly one shape -- three text keys, each a byte string -- and
|
|
120
|
+
* refuse everything else as `Malformed`. A tag, a float, a nested map, an extra
|
|
121
|
+
* key and a missing key are all the same answer, because a decoder that reports
|
|
122
|
+
* them differently is a parser an attacker can interrogate.
|
|
123
|
+
*/
|
|
124
|
+
export declare function decodeAssertion(raw: Uint8Array): Assertion;
|
|
125
|
+
/**
|
|
126
|
+
* HM-2: the challenge is base64url WITHOUT padding of the message's bytes.
|
|
127
|
+
*
|
|
128
|
+
* ONE definition, so "the challenge encoding" cannot mean two things. Note what
|
|
129
|
+
* the verifier does with it: it RECOMPUTES this from an id it derived itself and
|
|
130
|
+
* compares (RES-8). It never decodes the challenge the assertion carries and
|
|
131
|
+
* treats the result as an identifier -- a transmitted id is a name for a
|
|
132
|
+
* binding, not evidence of one.
|
|
133
|
+
*/
|
|
134
|
+
export declare function webauthnChallenge(messageIdBytes: Uint8Array): string;
|
|
135
|
+
/**
|
|
136
|
+
* HM-4 (a)-(e), IN ORDER, returning the assertion's 32-bit signature counter.
|
|
137
|
+
*
|
|
138
|
+
* `messageIdBytes` is the UTF-8 bytes of a **recomputed** id -- the attestation
|
|
139
|
+
* id at §9.3 step 7b(v). Never an id read off the message being verified
|
|
140
|
+
* (HM-2, RES-8). `rpId` is the entry's SIGNED relying-party id: signed policy,
|
|
141
|
+
* never a value the assertion supplies, which is the whole of HM-4 (c).
|
|
142
|
+
*
|
|
143
|
+
* NO STEP IS SKIPPED FOR AN EARLIER ONE, and the order is normative: an
|
|
144
|
+
* assertion wrong in two ways must produce the same refusal name in every
|
|
145
|
+
* implementation, or the corpus is comparing luck.
|
|
146
|
+
*/
|
|
147
|
+
export declare function verifyWebauthn(alg: string, coseKey: Uint8Array, messageIdBytes: Uint8Array, rpId: string, assertion: Uint8Array): number;
|
|
148
|
+
//# sourceMappingURL=webauthn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webauthn.d.ts","sourceRoot":"","sources":["../src/webauthn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AASH;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAA2C,CAAC;AAEvF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAE7C;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAI7C,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,SAAS,cAAc,CAAC;AACrC,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AACnD,gBAAgB;AAChB,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAC5C,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,sBAAsB,CAAC;AACtD,gBAAgB;AAChB,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAChD,gBAAgB;AAChB,eAAO,MAAM,2BAA2B,8BAA8B,CAAC;AACvE,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,sBAAsB,CAAC;AAEtD;;;;;;;;GAQG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAMhD;AAmBD,iFAAiF;AACjF,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;CAAE,CAAC;AAsC3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,aAAa,CA8DxE;AAED,oCAAoC;AACpC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,iBAAiB,EAAE,UAAU,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAuB1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,UAAU,GAAG,MAAM,CAcpE;AAkBD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,UAAU,EACnB,cAAc,EAAE,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,UAAU,GACpB,MAAM,CA+IR"}
|