@goodmeta/agent-verifier 0.2.0 → 0.6.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/README.md +67 -26
- package/dist/ap2/chain.d.ts +43 -0
- package/dist/ap2/chain.d.ts.map +1 -0
- package/dist/ap2/chain.js +100 -0
- package/dist/ap2/chain.js.map +1 -0
- package/dist/ap2/chains.d.ts +68 -0
- package/dist/ap2/chains.d.ts.map +1 -0
- package/dist/ap2/chains.js +119 -0
- package/dist/ap2/chains.js.map +1 -0
- package/dist/ap2/constraints.d.ts +47 -0
- package/dist/ap2/constraints.d.ts.map +1 -0
- package/dist/ap2/constraints.js +231 -0
- package/dist/ap2/constraints.js.map +1 -0
- package/dist/ap2/hash.d.ts +23 -0
- package/dist/ap2/hash.d.ts.map +1 -0
- package/dist/ap2/hash.js +54 -0
- package/dist/ap2/hash.js.map +1 -0
- package/dist/ap2/index.d.ts +28 -0
- package/dist/ap2/index.d.ts.map +1 -0
- package/dist/ap2/index.js +35 -0
- package/dist/ap2/index.js.map +1 -0
- package/dist/ap2/jwk.d.ts +24 -0
- package/dist/ap2/jwk.d.ts.map +1 -0
- package/dist/ap2/jwk.js +29 -0
- package/dist/ap2/jwk.js.map +1 -0
- package/dist/ap2/kb-sd-jwt.d.ts +44 -0
- package/dist/ap2/kb-sd-jwt.d.ts.map +1 -0
- package/dist/ap2/kb-sd-jwt.js +97 -0
- package/dist/ap2/kb-sd-jwt.js.map +1 -0
- package/dist/ap2/keys.d.ts +41 -0
- package/dist/ap2/keys.d.ts.map +1 -0
- package/dist/ap2/keys.js +114 -0
- package/dist/ap2/keys.js.map +1 -0
- package/dist/ap2/max-flow.d.ts +26 -0
- package/dist/ap2/max-flow.d.ts.map +1 -0
- package/dist/ap2/max-flow.js +173 -0
- package/dist/ap2/max-flow.js.map +1 -0
- package/dist/ap2/parse.d.ts +35 -0
- package/dist/ap2/parse.d.ts.map +1 -0
- package/dist/ap2/parse.js +78 -0
- package/dist/ap2/parse.js.map +1 -0
- package/dist/ap2/sd-jwt.d.ts +83 -0
- package/dist/ap2/sd-jwt.d.ts.map +1 -0
- package/dist/ap2/sd-jwt.js +306 -0
- package/dist/ap2/sd-jwt.js.map +1 -0
- package/dist/ap2/types.d.ts +270 -0
- package/dist/ap2/types.d.ts.map +1 -0
- package/dist/ap2/types.js +141 -0
- package/dist/ap2/types.js.map +1 -0
- package/dist/client.d.ts +152 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +171 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/policy.d.ts +45 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +88 -0
- package/dist/policy.js.map +1 -0
- package/dist/receipt-jwt.d.ts +30 -0
- package/dist/receipt-jwt.d.ts.map +1 -0
- package/dist/receipt-jwt.js +39 -0
- package/dist/receipt-jwt.js.map +1 -0
- package/dist/schema.d.ts +41 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +50 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +24 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +69 -0
- package/dist/verify.js.map +1 -0
- package/package.json +14 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @goodmeta/agent-verifier
|
|
2
2
|
|
|
3
|
-
Can this agent spend $X on Y right now? One function call.
|
|
3
|
+
Implements the [Budget Authority Protocol](https://github.com/goodmeta/agent-payments-landscape/blob/main/specs/budget-authority-protocol.md). Can this agent spend $X on Y right now? One function call.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import { checkPolicy } from "@goodmeta/agent-verifier"
|
|
@@ -19,7 +19,7 @@ Or with the hosted verifier (cross-agent budget tracking):
|
|
|
19
19
|
```ts
|
|
20
20
|
import { VerifierClient } from "@goodmeta/agent-verifier"
|
|
21
21
|
|
|
22
|
-
const verifier = new VerifierClient({ apiKey: "
|
|
22
|
+
const verifier = new VerifierClient({ apiKey: "gm_test_..." })
|
|
23
23
|
const { approved, verificationId } = await verifier.verify(mandate, {
|
|
24
24
|
amount: "3000",
|
|
25
25
|
idempotencyKey: "tx-1",
|
|
@@ -88,26 +88,52 @@ checkPolicy(policy, {
|
|
|
88
88
|
// → { approved: false, reason: "CODE_NOT_ALLOWED", detail: 'Code "storage" not in allowed list' }
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
### AP2 mandate verification
|
|
91
|
+
### Real AP2 mandate verification (dSD-JWT)
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
[AP2](https://ap2-protocol.org/) mandates (Google, 60+ partners) are **not** plain signed JSON — they are **dSD-JWT delegation chains**: an issuer-signed SD-JWT root plus N KB-SD-JWT hops, each hop signed by the previous hop's `cnf.jwk`, hash-chained via `sd_hash`/`issuer_jwt_hash`, ES256/P-256. The `ap2` namespace verifies the whole chain and evaluates the mandate's constraints. It is **ported byte-exact from AP2's reference SDK** (pinned commit, 65 golden vectors) and is **stricter than AP2 on every trust decision** — see [`AP2-AUDIT.md`](./AP2-AUDIT.md).
|
|
94
94
|
|
|
95
95
|
```ts
|
|
96
|
-
import {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
import { ap2 } from "@goodmeta/agent-verifier"
|
|
97
|
+
|
|
98
|
+
// 1. Verify the delegation chain. Pass the root issuer key (or an x5c/kid
|
|
99
|
+
// provider) and the expected audience + nonce — both REQUIRED (fail-closed).
|
|
100
|
+
const tokens = ap2.splitChain(compactChain)
|
|
101
|
+
const payloads = await ap2.verifyChain(tokens, rootIssuerJwk, {
|
|
102
|
+
expectedAud: "merchant",
|
|
103
|
+
expectedNonce: "nonce-1",
|
|
104
|
+
})
|
|
105
|
+
// signatures, cnf hop-chaining, sd_hash binding, typ rules, aud/nonce all
|
|
106
|
+
// verified — throws on any failure. payloads = [open, ...intermediate, closed]
|
|
107
|
+
|
|
108
|
+
// 2. Type the [open, closed] pair and evaluate constraints (budget, amount
|
|
109
|
+
// range, allowed payees/instruments, line-items max-flow, …).
|
|
110
|
+
const chain = ap2.parsePaymentChain(payloads)
|
|
111
|
+
const violations = ap2.verifyPaymentChain(chain, {
|
|
112
|
+
mandateContext: { total_amount: 0, total_uses: 0 },
|
|
113
|
+
})
|
|
114
|
+
if (violations.length) throw new Error(violations.join("; ")) // [] = authorized
|
|
100
115
|
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
|
|
116
|
+
// 3. Stable receipt reference (hash of the final SD-JWT in the chain).
|
|
117
|
+
const reference = ap2.receiptReference(compactChain)
|
|
118
|
+
```
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
})
|
|
110
|
-
|
|
120
|
+
For certificate-anchored roots, build a fail-closed `x5c`/`kid` provider (mandatory trusted roots, validity window, `CA:TRUE`, leaf P-256, anchor-to-root):
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const provider = ap2.x5cOrKidProvider({ trustedRoots /* X509Certificate[] */ })
|
|
124
|
+
const payloads = await ap2.verifyChain(tokens, provider, { expectedAud, expectedNonce })
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Receipts (ES256 JWS)
|
|
128
|
+
|
|
129
|
+
A *Mandate Receipt* in AP2 is a Verifier-signed JWT (`iss`/`result`/`reference`). `signReceipt`/`verifyReceipt` are a plain compact ES256 JWS over the whole payload — the receipt mechanism. (They also back the simpler legacy `IntentMandate` self-hosted flow; see `examples/single-merchant.ts`.)
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { signReceipt, verifyReceipt } from "@goodmeta/agent-verifier"
|
|
133
|
+
|
|
134
|
+
const token = await signReceipt(receipt, verifierPrivateJwk)
|
|
135
|
+
const { valid, payload, error } = await verifyReceipt(token, verifierPublicJwk)
|
|
136
|
+
if (!valid) throw new Error(error)
|
|
111
137
|
```
|
|
112
138
|
|
|
113
139
|
### Hosted verifier
|
|
@@ -118,7 +144,7 @@ When one agent's budget spans multiple services — Mistral AND Groq AND CoreWea
|
|
|
118
144
|
import { VerifierClient } from "@goodmeta/agent-verifier"
|
|
119
145
|
|
|
120
146
|
const verifier = new VerifierClient({
|
|
121
|
-
apiKey: "
|
|
147
|
+
apiKey: "gm_test_...",
|
|
122
148
|
baseUrl: "https://verifier.goodmeta.co",
|
|
123
149
|
})
|
|
124
150
|
|
|
@@ -149,16 +175,26 @@ if (approved) {
|
|
|
149
175
|
| --- | --- |
|
|
150
176
|
| `checkPolicy(policy, request, currentSpend?)` | Check a spending request against policy constraints |
|
|
151
177
|
|
|
152
|
-
### AP2
|
|
178
|
+
### AP2 dSD-JWT verifier (`ap2.*`)
|
|
153
179
|
|
|
154
180
|
| Function | Description |
|
|
155
181
|
| --- | --- |
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
182
|
+
| `ap2.splitChain(compact)` | Split a `~~`-joined dSD-JWT chain into parsed tokens |
|
|
183
|
+
| `ap2.verifyChain(tokens, keyOrProvider, opts)` | Verify the full delegation chain (signatures, cnf hop-chaining, binding, typ, terminal aud/nonce); returns per-hop payloads, throws on failure |
|
|
184
|
+
| `ap2.x5cOrKidProvider(opts)` | Build a fail-closed root-key provider from `x5c` certs or a `kid` lookup |
|
|
185
|
+
| `ap2.parsePaymentChain` / `ap2.verifyPaymentChain` | Type the `[open, closed]` payment pair; evaluate constraints + linkage (`[]` = ok) |
|
|
186
|
+
| `ap2.parseCheckoutChain` / `ap2.verifyCheckoutChain` | Same for checkout; self-computes `checkout_hash` from the embedded `checkout_jwt` |
|
|
187
|
+
| `ap2.checkPaymentConstraints` / `ap2.checkCheckoutConstraints` | Evaluate the 8 payment / 2 checkout constraints directly |
|
|
188
|
+
| `ap2.receiptReference(compact)` | Stable receipt reference (`sd_hash` of the final SD-JWT) |
|
|
189
|
+
| `ap2.*Schema` / types | zod schemas + types for the snake_case AP2 mandates |
|
|
190
|
+
|
|
191
|
+
### Receipts (ES256 JWS)
|
|
192
|
+
|
|
193
|
+
| Function | Description |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| `signReceipt(payload, privateJwk)` | Sign a payload as a compact ES256 JWS (AP2 receipt format) |
|
|
196
|
+
| `verifyReceipt(token, publicJwk)` | Verify the JWS signature; returns the trusted payload |
|
|
197
|
+
| `checkConstraints(mandate, tx)` | (Legacy) check budget/merchant/category/temporal constraints on an `IntentMandate` |
|
|
162
198
|
|
|
163
199
|
### Hosted verifier client
|
|
164
200
|
|
|
@@ -166,9 +202,14 @@ if (approved) {
|
|
|
166
202
|
| --- | --- |
|
|
167
203
|
| `verifier.verify(mandate, tx)` | Verify with full mandate object + place budget hold |
|
|
168
204
|
| `verifier.verifyById(mandateId, tx)` | Verify by ID (agent passes ID, verifier has mandate on file) |
|
|
169
|
-
| `verifier.
|
|
205
|
+
| `verifier.createBudget(opts)` | Create a budget envelope — no signature; the API key is the trust anchor |
|
|
206
|
+
| `verifier.settle(id, result)` | Confirm payment (debit budget) or release the hold |
|
|
207
|
+
| `verifier.release(id)` | Return a pre-commit hold to the budget (before settlement) |
|
|
208
|
+
| `verifier.refund(id, cents, key)` | Reverse a settled payment, full or partial (idempotent) |
|
|
170
209
|
| `verifier.getMandateState(id)` | Query budget, tx count, and history |
|
|
171
210
|
|
|
211
|
+
Calls apply a timeout and throw a typed `VerifierError` (with HTTP `status` + `body`) on transport, auth, or server failures; a denied verification is returned with `approved: false`, not thrown.
|
|
212
|
+
|
|
172
213
|
## Related
|
|
173
214
|
|
|
174
215
|
- [AP2](https://ap2-protocol.org/) — Agent payment authorization by Google (60+ partners)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dSD-JWT delegation-chain walk.
|
|
3
|
+
*
|
|
4
|
+
* Port of AP2's `sdjwt/chain.py::verify_chain` (+ `_check_time_claims`):
|
|
5
|
+
* verify the root SD-JWT, then each KB-SD-JWT hop under the previous hop's
|
|
6
|
+
* `cnf.jwk`, returning one effective payload per hop (`[open, …, closed]`).
|
|
7
|
+
*
|
|
8
|
+
* Hardenings over AP2's reference (PLAN §5):
|
|
9
|
+
* H4 — `expectedAud`+`expectedNonce` are REQUIRED for any chain that has KB
|
|
10
|
+
* hops (AP2 leaves them optional); fail closed if absent.
|
|
11
|
+
* H5 — DoS caps enforced BEFORE any crypto (chain depth, per-token bytes,
|
|
12
|
+
* disclosures per token).
|
|
13
|
+
* The signature/binding/typ/cnf checks live in the per-primitive modules
|
|
14
|
+
* (`sd-jwt.ts`, `kb-sd-jwt.ts`), as in AP2.
|
|
15
|
+
*/
|
|
16
|
+
import type { ParsedToken } from "./parse.js";
|
|
17
|
+
import { type VerificationKey } from "./sd-jwt.js";
|
|
18
|
+
type Json = Record<string, unknown>;
|
|
19
|
+
/** DoS caps (H5) — frozen so a consumer of the public barrel can't weaken them
|
|
20
|
+
* process-wide (`as const` is compile-time only). */
|
|
21
|
+
export declare const CHAIN_CAPS: Readonly<{
|
|
22
|
+
maxDepth: 8;
|
|
23
|
+
maxTokenBytes: number;
|
|
24
|
+
maxDisclosuresPerToken: 64;
|
|
25
|
+
}>;
|
|
26
|
+
/** Resolves the root token's verification key (x5c/kid trust → P4). */
|
|
27
|
+
export type RootKeyProvider = (root: ParsedToken) => VerificationKey;
|
|
28
|
+
export interface VerifyChainOptions {
|
|
29
|
+
expectedAud?: string;
|
|
30
|
+
expectedNonce?: string;
|
|
31
|
+
clockSkewSeconds?: number;
|
|
32
|
+
/** Unix seconds; defaults to the current time. Override for reproducible tests. */
|
|
33
|
+
currentTime?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Verify a dSD-JWT delegation chain and return per-hop effective payloads.
|
|
37
|
+
*
|
|
38
|
+
* @param tokens parsed chain segments (root first), e.g. from `splitChain`.
|
|
39
|
+
* @param rootKey the root issuer key, or a provider `(root) => key` (P4 x5c/kid).
|
|
40
|
+
*/
|
|
41
|
+
export declare function verifyChain(tokens: ParsedToken[], rootKey: VerificationKey | RootKeyProvider, opts?: VerifyChainOptions): Promise<Json[]>;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=chain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../../src/ap2/chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAA6D,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9G,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpC;qDACqD;AACrD,eAAO,MAAM,UAAU;;;;EAIrB,CAAC;AAIH,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,eAAe,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgCD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EAAE,EACrB,OAAO,EAAE,eAAe,GAAG,eAAe,EAC1C,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,IAAI,EAAE,CAAC,CAuCjB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dSD-JWT delegation-chain walk.
|
|
3
|
+
*
|
|
4
|
+
* Port of AP2's `sdjwt/chain.py::verify_chain` (+ `_check_time_claims`):
|
|
5
|
+
* verify the root SD-JWT, then each KB-SD-JWT hop under the previous hop's
|
|
6
|
+
* `cnf.jwk`, returning one effective payload per hop (`[open, …, closed]`).
|
|
7
|
+
*
|
|
8
|
+
* Hardenings over AP2's reference (PLAN §5):
|
|
9
|
+
* H4 — `expectedAud`+`expectedNonce` are REQUIRED for any chain that has KB
|
|
10
|
+
* hops (AP2 leaves them optional); fail closed if absent.
|
|
11
|
+
* H5 — DoS caps enforced BEFORE any crypto (chain depth, per-token bytes,
|
|
12
|
+
* disclosures per token).
|
|
13
|
+
* The signature/binding/typ/cnf checks live in the per-primitive modules
|
|
14
|
+
* (`sd-jwt.ts`, `kb-sd-jwt.ts`), as in AP2.
|
|
15
|
+
*/
|
|
16
|
+
import { verifyRootSdJwt, resolveDelegateItems } from "./sd-jwt.js";
|
|
17
|
+
import { verifyKbHop } from "./kb-sd-jwt.js";
|
|
18
|
+
/** DoS caps (H5) — frozen so a consumer of the public barrel can't weaken them
|
|
19
|
+
* process-wide (`as const` is compile-time only). */
|
|
20
|
+
export const CHAIN_CAPS = Object.freeze({
|
|
21
|
+
maxDepth: 8,
|
|
22
|
+
maxTokenBytes: 64 * 1024,
|
|
23
|
+
maxDisclosuresPerToken: 64,
|
|
24
|
+
});
|
|
25
|
+
const DEFAULT_CLOCK_SKEW_SECONDS = 300;
|
|
26
|
+
function enforceCaps(tokens) {
|
|
27
|
+
if (tokens.length > CHAIN_CAPS.maxDepth) {
|
|
28
|
+
throw new Error(`Chain too deep: ${tokens.length} tokens > cap ${CHAIN_CAPS.maxDepth}`);
|
|
29
|
+
}
|
|
30
|
+
tokens.forEach((t, i) => {
|
|
31
|
+
if (t.canonical.length > CHAIN_CAPS.maxTokenBytes) {
|
|
32
|
+
throw new Error(`Token ${i} exceeds ${CHAIN_CAPS.maxTokenBytes} bytes`);
|
|
33
|
+
}
|
|
34
|
+
if (t.disclosures.length > CHAIN_CAPS.maxDisclosuresPerToken) {
|
|
35
|
+
throw new Error(`Token ${i} has ${t.disclosures.length} disclosures > cap ${CHAIN_CAPS.maxDisclosuresPerToken}`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** Port of `_check_time_claims`: reject expired tokens and future-dated `iat`. */
|
|
40
|
+
function checkTimeClaims(payloads, tokenIndex, now, skew) {
|
|
41
|
+
for (const p of payloads) {
|
|
42
|
+
const exp = p["exp"];
|
|
43
|
+
if (exp !== undefined && exp !== null) {
|
|
44
|
+
if (typeof exp !== "number")
|
|
45
|
+
throw new Error(`Token ${tokenIndex} has invalid 'exp' claim type`);
|
|
46
|
+
if (now > exp + skew)
|
|
47
|
+
throw new Error(`Token ${tokenIndex} expired at ${exp}`);
|
|
48
|
+
}
|
|
49
|
+
const iat = p["iat"];
|
|
50
|
+
if (iat !== undefined && iat !== null) {
|
|
51
|
+
if (typeof iat !== "number")
|
|
52
|
+
throw new Error(`Token ${tokenIndex} has invalid 'iat' claim type`);
|
|
53
|
+
if (iat > now + skew)
|
|
54
|
+
throw new Error(`Token ${tokenIndex} iat is in the future: ${iat}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Verify a dSD-JWT delegation chain and return per-hop effective payloads.
|
|
60
|
+
*
|
|
61
|
+
* @param tokens parsed chain segments (root first), e.g. from `splitChain`.
|
|
62
|
+
* @param rootKey the root issuer key, or a provider `(root) => key` (P4 x5c/kid).
|
|
63
|
+
*/
|
|
64
|
+
export async function verifyChain(tokens, rootKey, opts = {}) {
|
|
65
|
+
if (tokens.length === 0)
|
|
66
|
+
throw new Error("Tokens list cannot be empty");
|
|
67
|
+
enforceCaps(tokens); // H5 — before any crypto
|
|
68
|
+
// H4 — aud+nonce mandatory for any KB-bearing chain (fail closed).
|
|
69
|
+
if (tokens.length > 1 && (!opts.expectedAud || !opts.expectedNonce)) {
|
|
70
|
+
throw new Error("expectedAud and expectedNonce are required to verify a chain with KB-SD-JWT hops");
|
|
71
|
+
}
|
|
72
|
+
const skew = opts.clockSkewSeconds ?? DEFAULT_CLOCK_SKEW_SECONDS;
|
|
73
|
+
const now = opts.currentTime ?? Math.floor(Date.now() / 1000);
|
|
74
|
+
const payloads = [];
|
|
75
|
+
// Root (index 0).
|
|
76
|
+
const rootKeyResolved = typeof rootKey === "function" ? rootKey(tokens[0]) : rootKey;
|
|
77
|
+
let prev = await verifyRootSdJwt(tokens[0], rootKeyResolved);
|
|
78
|
+
checkTimeClaims([prev.verifiedPayload], 0, now, skew);
|
|
79
|
+
checkTimeClaims(prev.delegateItems, 0, now, skew);
|
|
80
|
+
payloads.push(...(prev.delegateItems.length ? prev.delegateItems : [prev.verifiedPayload]));
|
|
81
|
+
// KB-SD-JWT hops (index 1..n-1) verify under the previous hop's cnf.jwk.
|
|
82
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
83
|
+
const isLast = i === tokens.length - 1;
|
|
84
|
+
const payload = await verifyKbHop(tokens[i], prev, {
|
|
85
|
+
expectedAud: isLast ? opts.expectedAud : undefined,
|
|
86
|
+
expectedNonce: isLast ? opts.expectedNonce : undefined,
|
|
87
|
+
requireTerminal: isLast, // H4 anti-truncation: last hop must be terminal-typ
|
|
88
|
+
});
|
|
89
|
+
const delegateItems = resolveDelegateItems(payload["delegate_payload"], tokens[i]);
|
|
90
|
+
if (!isLast && delegateItems.length > 1) {
|
|
91
|
+
throw new Error(`Token ${i}: delegate_payload has ${delegateItems.length} disclosed items, expected exactly 1`);
|
|
92
|
+
}
|
|
93
|
+
checkTimeClaims([payload], i, now, skew);
|
|
94
|
+
checkTimeClaims(delegateItems, i, now, skew);
|
|
95
|
+
payloads.push(...(delegateItems.length ? delegateItems : [payload]));
|
|
96
|
+
prev = { token: tokens[i], verifiedPayload: payload, delegateItems };
|
|
97
|
+
}
|
|
98
|
+
return payloads;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=chain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain.js","sourceRoot":"","sources":["../../src/ap2/chain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed mandate-chain wrappers + linkage + receipt reference.
|
|
3
|
+
*
|
|
4
|
+
* Ports AP2's `payment_mandate_chain.py` / `checkout_mandate_chain.py`: the
|
|
5
|
+
* per-hop effective payloads from `verifyChain` are parsed into a typed
|
|
6
|
+
* `[open, closed]` pair, then constraints are evaluated.
|
|
7
|
+
*
|
|
8
|
+
* Hardening H6 (stricter than AP2): AP2 takes the `transaction_id` /
|
|
9
|
+
* `checkout_hash` linkage as caller-supplied. We SELF-COMPUTE the
|
|
10
|
+
* `checkout_hash` from the embedded `checkout_jwt` and require it to match the
|
|
11
|
+
* mandate's own `checkout_hash` (mandatory) before evaluating constraints — a
|
|
12
|
+
* caller can't assert a linkage the bytes don't support.
|
|
13
|
+
*/
|
|
14
|
+
import { type OpenPaymentMandate, type PaymentMandate, type OpenCheckoutMandate, type CheckoutMandate, type Checkout, type MandateContext } from "./types.js";
|
|
15
|
+
export interface PaymentChain {
|
|
16
|
+
open: OpenPaymentMandate;
|
|
17
|
+
closed: PaymentMandate;
|
|
18
|
+
}
|
|
19
|
+
/** Parse exactly two verified payloads into a typed payment chain. */
|
|
20
|
+
export declare function parsePaymentChain(payloads: unknown[]): PaymentChain;
|
|
21
|
+
export interface VerifyPaymentChainOptions {
|
|
22
|
+
/** The verified open-checkout-mandate hash to bind to (H6: pass the value
|
|
23
|
+
* self-computed by `verifyCheckoutChain`, not a caller assertion). */
|
|
24
|
+
expectedTransactionId?: string;
|
|
25
|
+
openCheckoutHash?: string;
|
|
26
|
+
mandateContext?: MandateContext;
|
|
27
|
+
}
|
|
28
|
+
/** Verify a payment chain's constraints + linkage. Returns [] when satisfied. */
|
|
29
|
+
export declare function verifyPaymentChain(chain: PaymentChain, opts?: VerifyPaymentChainOptions): string[];
|
|
30
|
+
export interface CheckoutChain {
|
|
31
|
+
open: OpenCheckoutMandate;
|
|
32
|
+
closed: CheckoutMandate;
|
|
33
|
+
}
|
|
34
|
+
export declare function parseCheckoutChain(payloads: unknown[]): CheckoutChain;
|
|
35
|
+
/** Decode the `Checkout` object from a `checkout_jwt` payload (port of
|
|
36
|
+
* `extract_parsed_checkout_object`). The merchant signature is out of AP2's
|
|
37
|
+
* scope (CHK-07); only the payload is decoded + schema-validated. */
|
|
38
|
+
export declare function extractCheckout(checkoutJwt: string): Checkout;
|
|
39
|
+
export interface VerifyCheckoutChainOptions {
|
|
40
|
+
/** `_sd_alg` of the closed mandate's SD-JWT (defaults to sha-256). */
|
|
41
|
+
sdAlg?: string;
|
|
42
|
+
expectedCheckoutHash?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify a checkout chain: self-compute `checkout_hash` from the embedded
|
|
46
|
+
* `checkout_jwt` and require it to match the mandate (H6), then evaluate the
|
|
47
|
+
* open mandate's constraints against the decoded checkout. Returns [].
|
|
48
|
+
*/
|
|
49
|
+
export declare function verifyCheckoutChain(chain: CheckoutChain, opts?: VerifyCheckoutChainOptions): string[];
|
|
50
|
+
/** The closed-mandate leaf JWT of a chain (port of `get_closed_mandate_jwt`):
|
|
51
|
+
* the bare issuer JWT (no disclosures) of the final `~~`-separated segment.
|
|
52
|
+
*
|
|
53
|
+
* NOTE: this is for embedding/identifying the closed mandate. It is NOT the
|
|
54
|
+
* receipt-reference input — use `receiptReference()` for that. (AP2's SDK
|
|
55
|
+
* `get_closed_mandate_jwt` docstring suggests `sha256` of this string as the
|
|
56
|
+
* reference, but the AP2 spec (AUTH-17) defines the reference as `sd_hash` over
|
|
57
|
+
* the final SD-JWT *including disclosures*; we follow the spec. The two differ.)
|
|
58
|
+
*/
|
|
59
|
+
export declare function getClosedMandateJwt(presentationToken: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Stable receipt `reference` for a chain (AUTH-17 / SPEC-6): the base64url hash
|
|
62
|
+
* of the FINAL SD-JWT in the chain, computed "in the same manner as `sd_hash`"
|
|
63
|
+
* — i.e. `sd_hash` over the last segment's canonical SD-JWT form (issuer JWT +
|
|
64
|
+
* disclosures). This is the spec-authoritative reference; it is deliberately
|
|
65
|
+
* NOT `sha256(getClosedMandateJwt(chain))` (see that function's note).
|
|
66
|
+
*/
|
|
67
|
+
export declare function receiptReference(presentationToken: string): string;
|
|
68
|
+
//# sourceMappingURL=chains.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../../src/ap2/chains.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAML,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAOpB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAQnE;AAED,MAAM,WAAW,yBAAyB;IACxC;0EACsE;IACtE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,GAAE,yBAA8B,GAAG,MAAM,EAAE,CAWtG;AAID,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,aAAa,CAQrE;AAED;;qEAEqE;AACrE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAY7D;AAED,MAAM,WAAW,0BAA0B;IACzC,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,GAAE,0BAA+B,GAAG,MAAM,EAAE,CA0BzG;AAID;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAIlE"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed mandate-chain wrappers + linkage + receipt reference.
|
|
3
|
+
*
|
|
4
|
+
* Ports AP2's `payment_mandate_chain.py` / `checkout_mandate_chain.py`: the
|
|
5
|
+
* per-hop effective payloads from `verifyChain` are parsed into a typed
|
|
6
|
+
* `[open, closed]` pair, then constraints are evaluated.
|
|
7
|
+
*
|
|
8
|
+
* Hardening H6 (stricter than AP2): AP2 takes the `transaction_id` /
|
|
9
|
+
* `checkout_hash` linkage as caller-supplied. We SELF-COMPUTE the
|
|
10
|
+
* `checkout_hash` from the embedded `checkout_jwt` and require it to match the
|
|
11
|
+
* mandate's own `checkout_hash` (mandatory) before evaluating constraints — a
|
|
12
|
+
* caller can't assert a linkage the bytes don't support.
|
|
13
|
+
*/
|
|
14
|
+
import { Buffer } from "node:buffer";
|
|
15
|
+
import { computeValueHash, computeSdHash } from "./hash.js";
|
|
16
|
+
import { canonicalChainSegment, parseToken } from "./parse.js";
|
|
17
|
+
import { OpenPaymentMandateSchema, PaymentMandateSchema, OpenCheckoutMandateSchema, CheckoutMandateSchema, CheckoutSchema, } from "./types.js";
|
|
18
|
+
import { checkPaymentConstraints, checkCheckoutConstraints } from "./constraints.js";
|
|
19
|
+
const JWT_COMPACT_PARTS = 3;
|
|
20
|
+
/** Parse exactly two verified payloads into a typed payment chain. */
|
|
21
|
+
export function parsePaymentChain(payloads) {
|
|
22
|
+
if (payloads.length !== 2) {
|
|
23
|
+
throw new Error(`Payment mandate chain requires exactly 2 payloads, got ${payloads.length}`);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
open: OpenPaymentMandateSchema.parse(payloads[0]),
|
|
27
|
+
closed: PaymentMandateSchema.parse(payloads[1]),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Verify a payment chain's constraints + linkage. Returns [] when satisfied. */
|
|
31
|
+
export function verifyPaymentChain(chain, opts = {}) {
|
|
32
|
+
const violations = checkPaymentConstraints(chain.open, chain.closed, {
|
|
33
|
+
openCheckoutHash: opts.openCheckoutHash,
|
|
34
|
+
mandateContext: opts.mandateContext,
|
|
35
|
+
});
|
|
36
|
+
if (opts.expectedTransactionId !== undefined && opts.expectedTransactionId !== chain.closed.transaction_id) {
|
|
37
|
+
violations.push(`Payment transaction_id mismatch: expected ${opts.expectedTransactionId}, got ${chain.closed.transaction_id}`);
|
|
38
|
+
}
|
|
39
|
+
return violations;
|
|
40
|
+
}
|
|
41
|
+
export function parseCheckoutChain(payloads) {
|
|
42
|
+
if (payloads.length !== 2) {
|
|
43
|
+
throw new Error(`Checkout mandate chain requires exactly 2 payloads, got ${payloads.length}`);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
open: OpenCheckoutMandateSchema.parse(payloads[0]),
|
|
47
|
+
closed: CheckoutMandateSchema.parse(payloads[1]),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Decode the `Checkout` object from a `checkout_jwt` payload (port of
|
|
51
|
+
* `extract_parsed_checkout_object`). The merchant signature is out of AP2's
|
|
52
|
+
* scope (CHK-07); only the payload is decoded + schema-validated. */
|
|
53
|
+
export function extractCheckout(checkoutJwt) {
|
|
54
|
+
const parts = checkoutJwt.split(".");
|
|
55
|
+
if (parts.length !== JWT_COMPACT_PARTS) {
|
|
56
|
+
throw new Error("Malformed checkout_jwt: expected header.payload.signature");
|
|
57
|
+
}
|
|
58
|
+
let json;
|
|
59
|
+
try {
|
|
60
|
+
json = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
throw new Error(`Invalid checkout_jwt payload: ${e.message}`);
|
|
64
|
+
}
|
|
65
|
+
return CheckoutSchema.parse(json);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Verify a checkout chain: self-compute `checkout_hash` from the embedded
|
|
69
|
+
* `checkout_jwt` and require it to match the mandate (H6), then evaluate the
|
|
70
|
+
* open mandate's constraints against the decoded checkout. Returns [].
|
|
71
|
+
*/
|
|
72
|
+
export function verifyCheckoutChain(chain, opts = {}) {
|
|
73
|
+
const violations = [];
|
|
74
|
+
// H6 — self-computed linkage is mandatory.
|
|
75
|
+
const computed = computeValueHash(chain.closed.checkout_jwt, opts.sdAlg);
|
|
76
|
+
if (computed !== chain.closed.checkout_hash) {
|
|
77
|
+
violations.push(`Self-computed checkout_hash mismatch: hash(checkout_jwt)=${computed}, mandate checkout_hash=${chain.closed.checkout_hash}`);
|
|
78
|
+
return violations; // do not evaluate constraints against an unbound checkout
|
|
79
|
+
}
|
|
80
|
+
let checkout;
|
|
81
|
+
try {
|
|
82
|
+
checkout = extractCheckout(chain.closed.checkout_jwt);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
return [e.message];
|
|
86
|
+
}
|
|
87
|
+
violations.push(...checkCheckoutConstraints(chain.open, checkout));
|
|
88
|
+
if (opts.expectedCheckoutHash !== undefined && opts.expectedCheckoutHash !== chain.closed.checkout_hash) {
|
|
89
|
+
violations.push(`Checkout checkout_hash mismatch: expected ${opts.expectedCheckoutHash}, got ${chain.closed.checkout_hash}`);
|
|
90
|
+
}
|
|
91
|
+
return violations;
|
|
92
|
+
}
|
|
93
|
+
// ── Receipt reference ────────────────────────────────────────────────────────
|
|
94
|
+
/** The closed-mandate leaf JWT of a chain (port of `get_closed_mandate_jwt`):
|
|
95
|
+
* the bare issuer JWT (no disclosures) of the final `~~`-separated segment.
|
|
96
|
+
*
|
|
97
|
+
* NOTE: this is for embedding/identifying the closed mandate. It is NOT the
|
|
98
|
+
* receipt-reference input — use `receiptReference()` for that. (AP2's SDK
|
|
99
|
+
* `get_closed_mandate_jwt` docstring suggests `sha256` of this string as the
|
|
100
|
+
* reference, but the AP2 spec (AUTH-17) defines the reference as `sd_hash` over
|
|
101
|
+
* the final SD-JWT *including disclosures*; we follow the spec. The two differ.)
|
|
102
|
+
*/
|
|
103
|
+
export function getClosedMandateJwt(presentationToken) {
|
|
104
|
+
const lastSegment = presentationToken.split("~~").at(-1);
|
|
105
|
+
return lastSegment.split("~", 1)[0];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Stable receipt `reference` for a chain (AUTH-17 / SPEC-6): the base64url hash
|
|
109
|
+
* of the FINAL SD-JWT in the chain, computed "in the same manner as `sd_hash`"
|
|
110
|
+
* — i.e. `sd_hash` over the last segment's canonical SD-JWT form (issuer JWT +
|
|
111
|
+
* disclosures). This is the spec-authoritative reference; it is deliberately
|
|
112
|
+
* NOT `sha256(getClosedMandateJwt(chain))` (see that function's note).
|
|
113
|
+
*/
|
|
114
|
+
export function receiptReference(presentationToken) {
|
|
115
|
+
const segments = presentationToken.split("~~");
|
|
116
|
+
const last = parseToken(canonicalChainSegment(segments.at(-1), segments.length - 1, segments.length));
|
|
117
|
+
return computeSdHash(last);
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=chains.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chains.js","sourceRoot":"","sources":["../../src/ap2/chains.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment-constraint evaluation — line-for-line port of AP2's
|
|
3
|
+
* `sdk/constraints.py` (commit e1ea56d). Each evaluator returns a list of
|
|
4
|
+
* violation messages ([] = satisfied). Violation strings match AP2's f-strings
|
|
5
|
+
* byte-for-byte where the inputs are reproducible (amounts, ids, names, dates);
|
|
6
|
+
* the few AP2 messages that embed a Python object `repr` (pisp / pre-set
|
|
7
|
+
* amount+instrument) are noted and use a readable JSON form instead — only the
|
|
8
|
+
* violation COUNT is asserted against AP2 for those.
|
|
9
|
+
*
|
|
10
|
+
* Closed-world rule (AUTH-15 / SPEC-39): an UNKNOWN constraint type MUST be
|
|
11
|
+
* treated as failing evaluation.
|
|
12
|
+
*/
|
|
13
|
+
import { type Checkout, type Merchant, type MandateContext, type OpenCheckoutMandate, type OpenPaymentMandate, type PaymentMandate } from "./types.js";
|
|
14
|
+
interface EvalContext {
|
|
15
|
+
openCheckoutHash?: string;
|
|
16
|
+
mandateContext?: MandateContext;
|
|
17
|
+
/**
|
|
18
|
+
* Constraint types the caller requires to have been enforced on this mandate,
|
|
19
|
+
* e.g. `["payment.budget"]`.
|
|
20
|
+
*
|
|
21
|
+
* AP2 builds one evaluator per constraint PRESENT in the open mandate and never
|
|
22
|
+
* asserts which constraints ought to have been there, so a constraint withheld
|
|
23
|
+
* through selective disclosure produces no evaluator and no violation. An empty
|
|
24
|
+
* result therefore cannot distinguish "every constraint was evaluated and
|
|
25
|
+
* satisfied" from "nothing was evaluated". Declaring the set here makes the
|
|
26
|
+
* difference observable: a required constraint that is absent is reported
|
|
27
|
+
* rather than silently skipped.
|
|
28
|
+
*
|
|
29
|
+
* Opt-in and additive. Omit it and evaluation is byte-identical to AP2.
|
|
30
|
+
*/
|
|
31
|
+
requiredConstraints?: string[];
|
|
32
|
+
}
|
|
33
|
+
/** Port of `merchant_matches`: id-first, else name+website (both non-empty). */
|
|
34
|
+
export declare function merchantMatches(candidate: Merchant, target: Merchant): boolean;
|
|
35
|
+
/** Port of `check_preset_payment_claims`: open mandate pre-set fields must be
|
|
36
|
+
* present unchanged in the closed mandate (SPEC-38 / step 2). */
|
|
37
|
+
export declare function checkPresetPaymentClaims(open: OpenPaymentMandate, closed: PaymentMandate): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Verify a closed payment satisfies an open mandate's pre-set claims and
|
|
40
|
+
* constraints. Port of `check_payment_constraints`. Returns [] when satisfied.
|
|
41
|
+
*/
|
|
42
|
+
export declare function checkPaymentConstraints(open: OpenPaymentMandate, closed: PaymentMandate, ctx?: EvalContext): string[];
|
|
43
|
+
/** Verify a checkout satisfies an open checkout mandate's constraints. Port of
|
|
44
|
+
* `check_checkout_constraints`. Returns [] when satisfied. */
|
|
45
|
+
export declare function checkCheckoutConstraints(open: OpenCheckoutMandate, checkout: Checkout): string[];
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=constraints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constraints.d.ts","sourceRoot":"","sources":["../../src/ap2/constraints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAIL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAKpB,UAAU,WAAW;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;;;;;;;;;;;OAaG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAQ9E;AAkID;iEACiE;AACjE,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAenG;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,GAAE,WAAgB,GAAG,MAAM,EAAE,CA8BzH;AA+BD;8DAC8D;AAC9D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,mBAAmB,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAMhG"}
|