@atlasauth/pca-ap2 0.2.0 → 0.4.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 +36 -60
- package/dist/chain.d.ts +217 -0
- package/dist/chain.js +366 -0
- package/dist/chain.js.map +1 -0
- package/dist/constraints.d.ts +61 -0
- package/dist/constraints.js +271 -0
- package/dist/constraints.js.map +1 -0
- package/dist/index.d.ts +14 -316
- package/dist/index.js +27 -332
- package/dist/index.js.map +1 -1
- package/dist/mandates.d.ts +140 -0
- package/dist/mandates.js +279 -0
- package/dist/mandates.js.map +1 -0
- package/dist/pca.d.ts +147 -0
- package/dist/pca.js +313 -0
- package/dist/pca.js.map +1 -0
- package/dist/sdjwt.d.ts +129 -0
- package/dist/sdjwt.js +394 -0
- package/dist/sdjwt.js.map +1 -0
- package/dist/settlement.d.ts +47 -0
- package/dist/settlement.js +91 -0
- package/dist/settlement.js.map +1 -0
- package/package.json +5 -4
package/dist/pca.js
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PcaBindingError = exports.PCA_BINDING_VERSION = void 0;
|
|
4
|
+
exports.pcaOpenClaim = pcaOpenClaim;
|
|
5
|
+
exports.paymentConstraintsFromTerms = paymentConstraintsFromTerms;
|
|
6
|
+
exports.openPaymentMandateFromPca = openPaymentMandateFromPca;
|
|
7
|
+
exports.openCheckoutMandateFromPca = openCheckoutMandateFromPca;
|
|
8
|
+
exports.pcaBindingNonce = pcaBindingNonce;
|
|
9
|
+
exports.riskRatio = riskRatio;
|
|
10
|
+
exports.buildPcaPaymentBinding = buildPcaPaymentBinding;
|
|
11
|
+
exports.verifyPcaBinding = verifyPcaBinding;
|
|
12
|
+
/**
|
|
13
|
+
* Binding AP2 mandates to a PCA grant and to the PCA proof of each action.
|
|
14
|
+
*
|
|
15
|
+
* AP2 decides what a mandate may say; PCA decides whether the agent may act. The two are tied
|
|
16
|
+
* together by a namespaced `x_pca` claim that lives INSIDE the mandate object (so it is covered by
|
|
17
|
+
* the issuer's / agent's signature and by the next hop's `sd_hash`). The official schemas do not set
|
|
18
|
+
* `additionalProperties: false` and the reference SDK ignores unknown mandate claims, so the claim
|
|
19
|
+
* does not affect schema validity or reference verification (both are tested).
|
|
20
|
+
*
|
|
21
|
+
* open mandate `x_pca = { v, grant, terms }` the PCA grant (principal-signed, Ed25519)
|
|
22
|
+
* and the terms it was minted from
|
|
23
|
+
* closed payment `x_pca = { v, charge, pcactn }` the PCActn (agent-signed, Ed25519) that
|
|
24
|
+
* authorises exactly this charge
|
|
25
|
+
*
|
|
26
|
+
* The PCActn's signed `nonce` commits to the AP2 side: the transaction id, payee, amount, instrument,
|
|
27
|
+
* the open mandate's `sd_hash` and the thumbprint of the agent's AP2 key. Swapping any of those, or
|
|
28
|
+
* replaying the PCActn under another mandate or another AP2 agent key, breaks the binding.
|
|
29
|
+
*/
|
|
30
|
+
const pca_1 = require("@atlasauth/pca");
|
|
31
|
+
const pca_payments_1 = require("@atlasauth/pca-payments");
|
|
32
|
+
const sdjwt_1 = require("./sdjwt");
|
|
33
|
+
const mandates_1 = require("./mandates");
|
|
34
|
+
const chain_1 = require("./chain");
|
|
35
|
+
exports.PCA_BINDING_VERSION = 1;
|
|
36
|
+
const toJson = (v) => JSON.parse(JSON.stringify(v));
|
|
37
|
+
// ---------------------------------------------------------------------------------------------
|
|
38
|
+
// PCA terms -> AP2 open mandates
|
|
39
|
+
// ---------------------------------------------------------------------------------------------
|
|
40
|
+
/** The `x_pca` claim of an open mandate: the principal-signed grant and the terms it was minted from. */
|
|
41
|
+
function pcaOpenClaim(mandate) {
|
|
42
|
+
return { v: exports.PCA_BINDING_VERSION, grant: toJson(mandate.grant), terms: toJson(mandate.terms) };
|
|
43
|
+
}
|
|
44
|
+
function openExpiry(terms, o) {
|
|
45
|
+
const periodEnd = terms.periodEnd !== undefined ? Math.floor(terms.periodEnd / 1000) : undefined;
|
|
46
|
+
const exp = o.exp ?? periodEnd;
|
|
47
|
+
if (exp === undefined)
|
|
48
|
+
throw new sdjwt_1.Ap2Error('exp is required when the PCA terms have no period end');
|
|
49
|
+
if (periodEnd !== undefined && exp > periodEnd)
|
|
50
|
+
throw new sdjwt_1.Ap2Error('exp must not exceed the PCA period end');
|
|
51
|
+
return exp;
|
|
52
|
+
}
|
|
53
|
+
function merchantsFor(terms, merchants) {
|
|
54
|
+
return terms.merchants.map((id) => {
|
|
55
|
+
const m = merchants.find((x) => x.id === id);
|
|
56
|
+
if (!m)
|
|
57
|
+
throw new sdjwt_1.Ap2Error(`no merchant descriptor supplied for PCA merchant ${id}`);
|
|
58
|
+
return m;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The AP2 open payment constraints a set of PCA terms denotes. `budget.max` is in MAJOR units (the
|
|
63
|
+
* AP2 SDK multiplies it by 100); `amount_range` is in minor units. The budget is refused for any
|
|
64
|
+
* currency whose exponent is not 2 and for any cap whose x100 product is not exact in floating point,
|
|
65
|
+
* so the SDK and this package read the same number.
|
|
66
|
+
*/
|
|
67
|
+
function paymentConstraintsFromTerms(terms, merchants, checkoutReference) {
|
|
68
|
+
const cur = terms.currency;
|
|
69
|
+
const perTx = (0, mandates_1.toMinorUnits)(terms.perTransactionCap, cur);
|
|
70
|
+
const cumulativeMinor = (0, mandates_1.toMinorUnits)(terms.cumulativeCap, cur);
|
|
71
|
+
if ((0, mandates_1.currencyExponent)(cur) !== 2)
|
|
72
|
+
throw new sdjwt_1.Ap2Error(`budget in ${cur} is unsupported: the AP2 SDK converts budget.max with a fixed x100`);
|
|
73
|
+
if (Math.trunc(terms.cumulativeCap * 100) !== cumulativeMinor)
|
|
74
|
+
throw new sdjwt_1.Ap2Error('cumulativeCap x100 is not exact in floating point; choose a representable cap');
|
|
75
|
+
return [
|
|
76
|
+
{ type: 'payment.amount_range', currency: cur, min: 0, max: perTx },
|
|
77
|
+
{ type: 'payment.allowed_payees', allowed: merchantsFor(terms, merchants) },
|
|
78
|
+
{ type: 'payment.reference', conditional_transaction_id: checkoutReference },
|
|
79
|
+
{ type: 'payment.budget', currency: cur, max: terms.cumulativeCap },
|
|
80
|
+
{ type: 'payment.agent_recurrence', frequency: 'ON_DEMAND', ...(terms.maxTransactionsPerPeriod !== undefined ? { max_occurrences: terms.maxTransactionsPerPeriod } : {}) },
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
/** Issue the open payment mandate for a PCA mandate (grant + terms), chained to `checkoutReference`. */
|
|
84
|
+
function openPaymentMandateFromPca(mandate, o) {
|
|
85
|
+
return (0, chain_1.issueOpenPaymentMandate)({
|
|
86
|
+
issuer: o.issuer,
|
|
87
|
+
agentJwk: o.agentJwk,
|
|
88
|
+
iat: o.iat,
|
|
89
|
+
exp: openExpiry(mandate.terms, o),
|
|
90
|
+
pca: pcaOpenClaim(mandate),
|
|
91
|
+
constraints: paymentConstraintsFromTerms(mandate.terms, o.merchants, o.checkoutReference),
|
|
92
|
+
...(o.salt ? { salt: o.salt } : {}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Issue the open checkout mandate for a PCA mandate: the merchant allowlist and the acceptable line items. */
|
|
96
|
+
function openCheckoutMandateFromPca(mandate, o) {
|
|
97
|
+
return (0, chain_1.issueOpenCheckoutMandate)({
|
|
98
|
+
issuer: o.issuer,
|
|
99
|
+
agentJwk: o.agentJwk,
|
|
100
|
+
iat: o.iat,
|
|
101
|
+
exp: openExpiry(mandate.terms, o),
|
|
102
|
+
pca: pcaOpenClaim(mandate),
|
|
103
|
+
constraints: [
|
|
104
|
+
{ type: 'checkout.line_items', items: o.lineItems },
|
|
105
|
+
{ type: 'checkout.allowed_merchants', allowed: merchantsFor(mandate.terms, o.merchants) },
|
|
106
|
+
],
|
|
107
|
+
...(o.salt ? { salt: o.salt } : {}),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** The value the PCActn's signed `nonce` must equal for these AP2 facts. */
|
|
111
|
+
function pcaBindingNonce(f) {
|
|
112
|
+
return `ap2:${(0, pca_1.hashCanonical)({ v: exports.PCA_BINDING_VERSION, ...f })}`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* amount / cap, clamped to [0, 1] and rounded UP to 12 decimals. PCA's canonical encoding refuses
|
|
116
|
+
* numbers with more than 15 significant digits (so 100/300 cannot be signed as-is); rounding up can
|
|
117
|
+
* only raise the risk the threshold policy sees, never lower it.
|
|
118
|
+
*/
|
|
119
|
+
function riskRatio(amount, cap) {
|
|
120
|
+
const r = cap > 0 ? amount / cap : 1;
|
|
121
|
+
return Math.min(1, Math.max(0, Math.ceil(r * 1e12 - 1e-6) / 1e12));
|
|
122
|
+
}
|
|
123
|
+
/** Build the `x_pca` claim of a closed payment mandate: a PCActn authorising exactly this charge. */
|
|
124
|
+
function buildPcaPaymentBinding(i) {
|
|
125
|
+
const major = (0, mandates_1.fromMinorUnits)(i.amount.amount, i.amount.currency);
|
|
126
|
+
const charge = { merchant: i.payee.id, amount: major, currency: i.amount.currency, ...(i.category !== undefined ? { category: i.category } : {}) };
|
|
127
|
+
const facts = {
|
|
128
|
+
transactionId: i.transactionId,
|
|
129
|
+
payeeId: i.payee.id,
|
|
130
|
+
amount: i.amount.amount,
|
|
131
|
+
currency: i.amount.currency,
|
|
132
|
+
instrumentId: i.instrumentId,
|
|
133
|
+
openHash: (0, sdjwt_1.sdHashOf)((0, sdjwt_1.parseSdJwtHop)(i.open)),
|
|
134
|
+
agentJkt: (0, sdjwt_1.jwkThumbprint)(i.agentJwk),
|
|
135
|
+
};
|
|
136
|
+
const risk = riskRatio(major, i.mandate.terms.perTransactionCap);
|
|
137
|
+
const nodeId = `ap2:${i.transactionId}`;
|
|
138
|
+
const node = (0, pca_payments_1.chargePlanNode)(i.mandate, charge, nodeId);
|
|
139
|
+
const built = (0, pca_1.buildPCActn)({
|
|
140
|
+
grant: i.mandate.grant,
|
|
141
|
+
chain: [i.mandate.grant],
|
|
142
|
+
plan: [node],
|
|
143
|
+
nodeId,
|
|
144
|
+
params: (0, pca_payments_1.chargeParams)(i.mandate, charge),
|
|
145
|
+
counter: i.counter,
|
|
146
|
+
signerSecret: i.agentSecret,
|
|
147
|
+
aud: i.audience,
|
|
148
|
+
nonce: pcaBindingNonce(facts),
|
|
149
|
+
riskClaim: { r: risk, inputs: { ...(0, pca_payments_1.chargeParams)(i.mandate, charge), blastRadius: risk } },
|
|
150
|
+
...(i.now !== undefined ? { now: i.now } : {}),
|
|
151
|
+
});
|
|
152
|
+
const pcactn = i.cosign ? { ...built, threshold: i.cosign(built) } : built;
|
|
153
|
+
return {
|
|
154
|
+
v: exports.PCA_BINDING_VERSION,
|
|
155
|
+
charge: { amount: major, currency: i.amount.currency, ...(i.category !== undefined ? { category: i.category } : {}) },
|
|
156
|
+
pcactn: toJson(pcactn),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Thrown when `verifyPcaBinding` lacks the server-held state the policy decision needs. */
|
|
160
|
+
class PcaBindingError extends Error {
|
|
161
|
+
code;
|
|
162
|
+
constructor(code, message) {
|
|
163
|
+
super(message);
|
|
164
|
+
this.code = code;
|
|
165
|
+
this.name = 'PcaBindingError';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
exports.PcaBindingError = PcaBindingError;
|
|
169
|
+
const isRec = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
170
|
+
/** Check the open mandate's constraints never exceed the PCA terms (the AP2 side only narrows the grant). */
|
|
171
|
+
function constraintsWithinTerms(open, terms) {
|
|
172
|
+
const cur = terms.currency;
|
|
173
|
+
const perTx = (0, mandates_1.toMinorUnits)(terms.perTransactionCap, cur);
|
|
174
|
+
const range = open.constraints.find((c) => c.type === 'payment.amount_range');
|
|
175
|
+
if (!range || range.type !== 'payment.amount_range')
|
|
176
|
+
return 'open mandate has no amount_range to compare with the PCA per-transaction cap';
|
|
177
|
+
if (range.currency !== cur)
|
|
178
|
+
return 'amount_range currency differs from the PCA terms';
|
|
179
|
+
if (range.max > perTx)
|
|
180
|
+
return `amount_range.max ${range.max} exceeds the PCA per-transaction cap ${perTx}`;
|
|
181
|
+
const payees = open.constraints.find((c) => c.type === 'payment.allowed_payees');
|
|
182
|
+
if (payees && payees.type === 'payment.allowed_payees' && payees.allowed.some((m) => !terms.merchants.includes(m.id)))
|
|
183
|
+
return 'allowed_payees names a merchant outside the PCA allowlist';
|
|
184
|
+
if (!payees)
|
|
185
|
+
return 'open mandate has no allowed_payees; the PCA allowlist would not be enforced on the AP2 side';
|
|
186
|
+
const budget = open.constraints.find((c) => c.type === 'payment.budget');
|
|
187
|
+
if (!budget || budget.type !== 'payment.budget')
|
|
188
|
+
return 'open mandate has no budget to compare with the PCA cumulative cap';
|
|
189
|
+
if (budget.currency !== cur || budget.max > terms.cumulativeCap)
|
|
190
|
+
return 'budget exceeds the PCA cumulative cap';
|
|
191
|
+
const rec = open.constraints.find((c) => c.type === 'payment.agent_recurrence');
|
|
192
|
+
if (terms.maxTransactionsPerPeriod !== undefined && (!rec || rec.type !== 'payment.agent_recurrence' || rec.max_occurrences === undefined || rec.max_occurrences > terms.maxTransactionsPerPeriod)) {
|
|
193
|
+
return 'max_occurrences is missing or exceeds the PCA transaction limit';
|
|
194
|
+
}
|
|
195
|
+
if (terms.periodEnd !== undefined && (open.exp === undefined || open.exp > Math.floor(terms.periodEnd / 1000)))
|
|
196
|
+
return 'open mandate outlives the PCA period';
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Verify the PCA side of a verified AP2 payment chain: the grant in the open mandate (signed by the
|
|
201
|
+
* expected principal, consistent with the terms it claims and with the open constraints), and the
|
|
202
|
+
* closed mandate's PCActn (valid under that grant, for this audience, committing to exactly this
|
|
203
|
+
* AP2 charge). Run it after `verifyPaymentChain`, which establishes the AP2 signatures.
|
|
204
|
+
*/
|
|
205
|
+
async function verifyPcaBinding(i) {
|
|
206
|
+
// The decision (cumulative budget, charge-count rate limit, caveats) needs the verifier's OWN mandate state and history.
|
|
207
|
+
if (!i || !isRec(i.mandate))
|
|
208
|
+
throw new PcaBindingError('mandate_required', 'verifyPcaBinding needs the server-held PCA mandate (its live budget); the policy decision is never skipped');
|
|
209
|
+
const maxTx = i.mandate.terms?.maxTransactionsPerPeriod;
|
|
210
|
+
if (maxTx !== undefined && !(Array.isArray(i.recentChargeTimes) && i.recentChargeTimes.every((t) => typeof t === 'number' && Number.isFinite(t)))) {
|
|
211
|
+
throw new PcaBindingError('charge_history_required', 'the mandate limits the charge count: pass recentChargeTimes from your own records');
|
|
212
|
+
}
|
|
213
|
+
const fail = (reason) => ({ ok: false, reason });
|
|
214
|
+
try {
|
|
215
|
+
const oc = i.open.x_pca;
|
|
216
|
+
if (!isRec(oc) || oc.v !== exports.PCA_BINDING_VERSION || !isRec(oc.grant) || !isRec(oc.terms))
|
|
217
|
+
return fail('open mandate carries no x_pca grant');
|
|
218
|
+
const grant = oc.grant;
|
|
219
|
+
const terms = oc.terms;
|
|
220
|
+
const chain = (0, pca_1.verifyChain)([grant], i.expectedPrincipal, { pqPins: i.pqPins });
|
|
221
|
+
if (!chain.ok)
|
|
222
|
+
return fail(`PCA grant: ${chain.reason ?? 'invalid'}`);
|
|
223
|
+
const env = (0, pca_1.readEnvelope)(grant);
|
|
224
|
+
if (!env)
|
|
225
|
+
return fail('PCA grant has no envelope');
|
|
226
|
+
if ((0, pca_1.hashCanonical)(env.predicates) !== (0, pca_1.hashCanonical)((0, pca_payments_1.mandatePredicates)(terms)) || (0, pca_1.hashCanonical)(env.caveats) !== (0, pca_1.hashCanonical)((0, pca_payments_1.mandateCaveats)(terms)))
|
|
227
|
+
return fail('x_pca terms do not match the grant envelope');
|
|
228
|
+
if (env.risk_policy.kappa !== terms.perTransactionCap || env.risk_policy.bMax !== terms.cumulativeCap)
|
|
229
|
+
return fail('x_pca terms do not match the grant risk policy');
|
|
230
|
+
const within = constraintsWithinTerms(i.open, terms);
|
|
231
|
+
if (within)
|
|
232
|
+
return fail(within);
|
|
233
|
+
const cc = i.closed.x_pca;
|
|
234
|
+
if (!isRec(cc) || cc.v !== exports.PCA_BINDING_VERSION || !isRec(cc.pcactn) || !isRec(cc.charge))
|
|
235
|
+
return fail('closed mandate carries no x_pca PCActn');
|
|
236
|
+
const pcactn = cc.pcactn;
|
|
237
|
+
const charge = cc.charge;
|
|
238
|
+
const { payment_amount: amt, payee } = i.closed;
|
|
239
|
+
if (charge.currency !== amt.currency || charge.amount !== (0, mandates_1.fromMinorUnits)(amt.amount, amt.currency))
|
|
240
|
+
return fail('x_pca charge differs from the mandate payment_amount');
|
|
241
|
+
if (terms.categories.length > 0 && (typeof charge.category !== 'string' || !terms.categories.includes(charge.category)))
|
|
242
|
+
return fail('x_pca charge lacks an allowed category');
|
|
243
|
+
// R2-09: remember the `t` the threshold hook REPORTS it enforced (as the core verifier does), so a hook built for a smaller t cannot satisfy a charge that needs more.
|
|
244
|
+
let enforcedT;
|
|
245
|
+
const thresholdHook = i.hooks?.threshold;
|
|
246
|
+
const hooks = i.hooks
|
|
247
|
+
? {
|
|
248
|
+
...i.hooks,
|
|
249
|
+
...(thresholdHook
|
|
250
|
+
? {
|
|
251
|
+
threshold: async (ctx) => {
|
|
252
|
+
const r = await thresholdHook(ctx);
|
|
253
|
+
if (r.enforced && r.ok && typeof r.t === 'number')
|
|
254
|
+
enforcedT = r.t;
|
|
255
|
+
return r;
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
: {}),
|
|
259
|
+
}
|
|
260
|
+
: undefined;
|
|
261
|
+
const core = await (0, pca_1.verifyPCActnCore)(pcactn, { grant, audience: i.audience, ...(i.now !== undefined ? { nowEpoch: i.now } : {}), ...(hooks ? { hooks } : {}), ...(i.enforce ? { enforce: i.enforce } : {}) });
|
|
262
|
+
if (!core.allow)
|
|
263
|
+
return fail(`PCActn: ${core.reason ?? 'rejected'}`);
|
|
264
|
+
if (pcactn.action.verb !== 'charge' || pcactn.action.resource !== `merchant:${payee.id}`)
|
|
265
|
+
return fail('PCActn action is not a charge at this payee');
|
|
266
|
+
const params = { amount: charge.amount, currency: charge.currency, ...(typeof charge.category === 'string' ? { category: charge.category } : {}) };
|
|
267
|
+
if (pcactn.action.params_digest !== (0, pca_1.paramsDigest)(params))
|
|
268
|
+
return fail('PCActn params do not commit to the x_pca charge');
|
|
269
|
+
const agentJwk = i.open.cnf.jwk;
|
|
270
|
+
const expectedNonce = pcaBindingNonce({
|
|
271
|
+
transactionId: i.closed.transaction_id,
|
|
272
|
+
payeeId: payee.id,
|
|
273
|
+
amount: amt.amount,
|
|
274
|
+
currency: amt.currency,
|
|
275
|
+
instrumentId: i.closed.payment_instrument.id,
|
|
276
|
+
openHash: i.openHash,
|
|
277
|
+
agentJkt: (0, sdjwt_1.jwkThumbprint)(agentJwk),
|
|
278
|
+
});
|
|
279
|
+
if (pcactn.nonce !== expectedNonce)
|
|
280
|
+
return fail('PCActn nonce does not bind this AP2 transaction, open mandate and agent key');
|
|
281
|
+
if (amt.amount > (0, mandates_1.toMinorUnits)(terms.perTransactionCap, terms.currency))
|
|
282
|
+
return fail('payment exceeds the PCA per-transaction cap');
|
|
283
|
+
// The agent may not understate the risk of its own charge, and a charge whose risk needs a human
|
|
284
|
+
// co-sign (t > 1) is only accepted when the verifier supplies a threshold verifier to check it.
|
|
285
|
+
const recomputed = riskRatio((0, mandates_1.fromMinorUnits)(amt.amount, amt.currency), terms.perTransactionCap);
|
|
286
|
+
const claimed = pcactn.risk_claim.r;
|
|
287
|
+
if (!(typeof claimed === 'number' && claimed >= recomputed - 1e-9))
|
|
288
|
+
return fail('PCActn understates the risk of this charge');
|
|
289
|
+
const need = (0, pca_1.requiredThreshold)(claimed, env.risk_policy);
|
|
290
|
+
if (need.t > 1 && !i.hooks?.threshold)
|
|
291
|
+
return fail(`this charge needs a threshold of ${need.t} (human co-sign); supply hooks.threshold to verify it`);
|
|
292
|
+
if (need.t > 1 && !(typeof enforcedT === 'number' && enforcedT >= need.t)) {
|
|
293
|
+
return fail(`this charge needs a threshold of ${need.t} but the threshold hook enforced t=${enforcedT === undefined ? 'unreported' : String(enforcedT)}; build it with createThresholdVerifier({ t: ${need.t} })`);
|
|
294
|
+
}
|
|
295
|
+
if (amt.currency !== terms.currency || !terms.merchants.includes(payee.id))
|
|
296
|
+
return fail('payment currency or payee is outside the PCA terms');
|
|
297
|
+
{
|
|
298
|
+
if ((0, pca_1.hashCanonical)(i.mandate.grant) !== (0, pca_1.hashCanonical)(grant))
|
|
299
|
+
return fail('the supplied PCA mandate is not the one in the open mandate');
|
|
300
|
+
// `grant` was verified above under the caller-pinned principal (verifyChain + pqPins); the decision is bound to it
|
|
301
|
+
const decision = (0, pca_payments_1.authorizeCharge)(i.mandate, { merchant: payee.id, amount: (0, mandates_1.fromMinorUnits)(amt.amount, amt.currency), currency: amt.currency, ...(typeof charge.category === 'string' ? { category: charge.category } : {}), recentChargeTimes: i.recentChargeTimes ?? [], ...(i.now !== undefined ? { now: i.now } : {}) }, grant);
|
|
302
|
+
if (decision.outcome === 'deny')
|
|
303
|
+
return fail(`PCA denies this charge: ${decision.reasons.join('; ')}`);
|
|
304
|
+
if (decision.outcome === 'step_up' && !(typeof enforcedT === 'number' && enforcedT >= 2))
|
|
305
|
+
return fail('PCA requires a human co-sign for this charge and no verified threshold (t >= 2) was enforced');
|
|
306
|
+
}
|
|
307
|
+
return { ok: true, pcactn, grant, terms };
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
return fail(e instanceof Error ? e.message : String(e));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=pca.js.map
|
package/dist/pca.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pca.js","sourceRoot":"","sources":["../src/pca.ts"],"names":[],"mappings":"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"}
|
package/dist/sdjwt.d.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SD-JWT primitives for AP2 mandates (RFC 9901): strict compact parsing, disclosure processing, the
|
|
3
|
+
* `sd_hash` / `issuer_jwt_hash` binding digests, and ES256 (P-256) signing and verification.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here implements a cryptographic primitive: SHA-2 and ECDSA come from `node:crypto`. The
|
|
6
|
+
* code is the SD-JWT *format* (what is hashed, in which order, which shapes are accepted) and it
|
|
7
|
+
* fails closed on every ambiguity (duplicate digests, unreferenced disclosures, malformed base64url,
|
|
8
|
+
* duplicate JSON keys, unknown `_sd_alg`, non-ES256 algorithms).
|
|
9
|
+
*/
|
|
10
|
+
import { type KeyObject } from 'node:crypto';
|
|
11
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
12
|
+
[k: string]: Json;
|
|
13
|
+
};
|
|
14
|
+
export type JsonObject = {
|
|
15
|
+
[k: string]: Json;
|
|
16
|
+
};
|
|
17
|
+
/** Every failure raised while parsing or verifying carries a human-readable reason. */
|
|
18
|
+
export declare class Ap2Error extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
export declare function b64uEncode(data: Uint8Array | string): string;
|
|
22
|
+
/** Strict base64url decode: canonical alphabet, no padding, and the encoding must round-trip. */
|
|
23
|
+
export declare function b64uDecode(s: string, what: string): Buffer;
|
|
24
|
+
export declare function isObject(v: unknown): v is JsonObject;
|
|
25
|
+
/**
|
|
26
|
+
* JSON parse that rejects duplicate keys. Numbers are parsed as written (`1000.0` is accepted: the
|
|
27
|
+
* official Python SDK serialises float fields that way).
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseJson(text: string, what: string): unknown;
|
|
30
|
+
/** The `_sd_alg` values the AP2 reference SDK accepts. */
|
|
31
|
+
export type SdAlg = 'sha-256' | 'sha-384' | 'sha-512';
|
|
32
|
+
export declare function isSdAlg(v: unknown): v is SdAlg;
|
|
33
|
+
/** base64url(hash(ascii(value))): the digest used for disclosures, `sd_hash` and `checkout_hash`. */
|
|
34
|
+
export declare function sdDigest(value: string, alg?: SdAlg): string;
|
|
35
|
+
/** An EC P-256 public key in JWK form (the shape of `types/jwk.json`). */
|
|
36
|
+
export interface P256Jwk {
|
|
37
|
+
kty: 'EC';
|
|
38
|
+
crv: 'P-256';
|
|
39
|
+
x: string;
|
|
40
|
+
y: string;
|
|
41
|
+
alg?: 'ES256';
|
|
42
|
+
use?: 'sig' | 'enc';
|
|
43
|
+
kid?: string;
|
|
44
|
+
[k: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
export interface Ap2KeyPair {
|
|
47
|
+
/** Public JWK (never contains `d`). */
|
|
48
|
+
publicJwk: P256Jwk;
|
|
49
|
+
privateKey: KeyObject;
|
|
50
|
+
}
|
|
51
|
+
/** Validate a public JWK against `types/jwk.json` and prove the point is on the curve. */
|
|
52
|
+
export declare function assertP256Jwk(jwk: unknown, what: string): P256Jwk;
|
|
53
|
+
export declare function generateAp2KeyPair(kid?: string): Ap2KeyPair;
|
|
54
|
+
/** Rebuild a key pair from a private JWK (`d` present), e.g. for fixtures. */
|
|
55
|
+
export declare function ap2KeyPairFromPrivateJwk(jwk: Record<string, unknown>): Ap2KeyPair;
|
|
56
|
+
/** RFC 7638 JWK thumbprint (SHA-256) of an EC P-256 key. */
|
|
57
|
+
export declare function jwkThumbprint(jwk: P256Jwk): string;
|
|
58
|
+
export interface JwtHeader {
|
|
59
|
+
alg: string;
|
|
60
|
+
typ?: string;
|
|
61
|
+
kid?: string;
|
|
62
|
+
x5c?: string[];
|
|
63
|
+
[k: string]: unknown;
|
|
64
|
+
}
|
|
65
|
+
/** One SD-JWT: an issuer-signed JWT plus its disclosures (no trailing KB-JWT; AP2 binds with chain hops). */
|
|
66
|
+
export interface SdJwtHop {
|
|
67
|
+
/** Canonical serialisation `<jwt>~<d1>~...~<dn>~`. */
|
|
68
|
+
raw: string;
|
|
69
|
+
jwt: string;
|
|
70
|
+
header: JwtHeader;
|
|
71
|
+
/** The signed payload exactly as issued (digest placeholders still in place). */
|
|
72
|
+
payload: JsonObject;
|
|
73
|
+
disclosures: string[];
|
|
74
|
+
sdAlg: SdAlg;
|
|
75
|
+
signingInput: string;
|
|
76
|
+
signature: Buffer;
|
|
77
|
+
}
|
|
78
|
+
/** The hop's `~`-joined serialisation: what `sd_hash` is computed over. */
|
|
79
|
+
export declare function hopSdJwt(hop: Pick<SdJwtHop, 'jwt' | 'disclosures'>): string;
|
|
80
|
+
/** Parse one SD-JWT (`jwt~d1~...~dn~`). The serialisation must end with `~`. */
|
|
81
|
+
export declare function parseSdJwtHop(token: string): SdJwtHop;
|
|
82
|
+
/** `sd_hash` of a hop: the digest of `<jwt>~<d1>~...~` (the on-wire form of the preceding hop). */
|
|
83
|
+
export declare function sdHashOf(hop: SdJwtHop, alg?: SdAlg): string;
|
|
84
|
+
/** `issuer_jwt_hash` of a hop: the digest of the issuer-signed JWT alone. */
|
|
85
|
+
export declare function issuerJwtHashOf(hop: SdJwtHop, alg?: SdAlg): string;
|
|
86
|
+
/** Verify the issuer JWT's ES256 signature. Anything other than `alg: ES256` is rejected. */
|
|
87
|
+
export declare function verifyHopSignature(hop: SdJwtHop, key: P256Jwk): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Resolve every selectively-disclosable claim in `payload` using `disclosures`. Returns the processed
|
|
90
|
+
* claims (`_sd`, `...` markers and `_sd_alg` removed). Throws if a disclosure is duplicated, is not
|
|
91
|
+
* referenced from the payload, collides with an existing claim, or is used in the wrong position.
|
|
92
|
+
*/
|
|
93
|
+
export declare function processDisclosures(payload: JsonObject, disclosures: string[], alg: SdAlg): JsonObject;
|
|
94
|
+
export declare function newSalt(): string;
|
|
95
|
+
export interface DisclosureBuilder {
|
|
96
|
+
/** Array-element disclosure for `value`; returns the `{ "...": digest }` placeholder. */
|
|
97
|
+
element(value: Json): {
|
|
98
|
+
'...': string;
|
|
99
|
+
};
|
|
100
|
+
/** Property disclosure; returns the digest to put into the parent's `_sd`. */
|
|
101
|
+
property(name: string, value: Json): string;
|
|
102
|
+
readonly disclosures: string[];
|
|
103
|
+
}
|
|
104
|
+
export declare function disclosureBuilder(alg?: SdAlg, salt?: () => string): DisclosureBuilder;
|
|
105
|
+
/** Sign `payload` as an ES256 JWT and append `disclosures`: `<jwt>~<d1>~...~<dn>~`. */
|
|
106
|
+
export declare function signSdJwt(args: {
|
|
107
|
+
header: {
|
|
108
|
+
typ: string;
|
|
109
|
+
kid?: string;
|
|
110
|
+
x5c?: string[];
|
|
111
|
+
};
|
|
112
|
+
payload: JsonObject;
|
|
113
|
+
disclosures: string[];
|
|
114
|
+
privateKey: KeyObject;
|
|
115
|
+
}): string;
|
|
116
|
+
/** Sign an arbitrary compact JWT (used for merchant-signed `checkout_jwt`). */
|
|
117
|
+
export declare function signJwtEs256(args: {
|
|
118
|
+
header: Record<string, Json>;
|
|
119
|
+
payload: JsonObject;
|
|
120
|
+
privateKey: KeyObject;
|
|
121
|
+
}): string;
|
|
122
|
+
export interface ParsedJwt {
|
|
123
|
+
header: JwtHeader;
|
|
124
|
+
payload: JsonObject;
|
|
125
|
+
signingInput: string;
|
|
126
|
+
signature: Buffer;
|
|
127
|
+
}
|
|
128
|
+
export declare function parseJwt(jwt: string, what: string): ParsedJwt;
|
|
129
|
+
export declare function verifyJwtEs256(parsed: ParsedJwt, key: P256Jwk): boolean;
|