Skip to content

Commit 229a2df

Browse files
RBKunnelaclaude
andauthored
feat(ap2): real VC-signature verification - fail-closed pipeline [AK-1] (#101)
New src/ap2-vc.ts: Ap2MandateVc envelope (W3C VC 2.0 shape), pinned proof-suite list (reference-SDK SD-JWT/ES256 + eddsa-jcs-2022), offline-only DID resolution (did:key + operator-pinned did:web cache, no network fetch), operator trust-anchor list failing CLOSED when empty, 8-step ordered verification pipeline with distinct AP2_* error codes, and replay protection keyed (mandateId, cartHash) with an id-reuse-hash-mismatch tamper rule. Ap2Adapter.settleVc() verifies BEFORE translation/signing - an unverifiable envelope never reaches the x402 rail. Legacy settle() receipts are now marked mandateVerification: 'not-performed' (translation-only honesty). Spec pin (T1): google-agentic-commerce/AP2 @ e1ea56db72a6 (SD-JWT, ES256, _sd_alg sha-256, delegate_payload wrapping) - recorded in the module docstring. Python parity gap documented in packages/python/README.md. 89 new tests (adversarial-heavy: forged/wrong-key signature, 3x field tamper, expired, not-yet-valid, untrusted/empty anchors, unknown suite, alg-none downgrade, unresolvable DID, key confusion, replay, id-reuse mismatch, malformed/oversize/null-byte envelopes). Full suite 539 green; coverage ap2-vc.ts 87.7% / ap2.ts 98.5%; lint + type-check clean; quickstart smoke passed against the live host. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 9f58573 commit 229a2df

10 files changed

Lines changed: 2960 additions & 21 deletions

File tree

README.md

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -505,24 +505,46 @@ engine.getQueueStatistics(); // BatchStatis
505505

506506
## AP2 + MPP
507507

508-
paybot is a clean x402 settlement engine, so a Google **AP2** (A2A x402-extension) mandate can settle through it directly. The adapter **translates and settles only — it does not yet verify the AP2 verifiable-credential signature** (see the trust-boundary note below; cryptographic mandate verification is in active development). **MPP** (Stripe/Tempo) is still preview — the SDK ships only a detect-and-route capability seam, not a full client.
508+
paybot is a clean x402 settlement engine, so a Google **AP2** (A2A x402-extension) mandate can settle through it directly. **MPP** (Stripe/Tempo) is still preview — the SDK ships only a detect-and-route capability seam, not a full client.
509509

510510
```typescript
511-
import { Ap2Adapter, detectMppCapability } from 'paybot-sdk';
511+
import { Ap2Adapter, detectMppCapability, InMemoryReplayStore } from 'paybot-sdk';
512512

513513
const ap2 = new Ap2Adapter(handler); // handler: X402Handler
514+
515+
// Verified path (recommended): full AP2 mandate VC envelope.
516+
// Fail-closed 8-step pipeline: envelope shape -> pinned proof suite ->
517+
// offline DID/key resolution -> operator trust anchors -> signature ->
518+
// temporal validity -> replay -> cart/intent linkage.
519+
const receipt = await ap2.settleVc(mandateVc, {
520+
verify: {
521+
trustedIssuers: ['did:key:z6Mk...'], // EMPTY LIST = NOTHING TRUSTED
522+
replayStore: new InMemoryReplayStore(), // per-process; core is authoritative
523+
},
524+
});
525+
receipt.mandateVerification; // 'verified'
526+
527+
// Legacy path: translation-only settlement slice (NO authenticity check).
514528
if (ap2.validateMandate(mandate).valid) {
515-
const receipt = await ap2.settle(mandate); // signs + submits via x402
529+
const r = await ap2.settle(mandate); // signs + submits via x402
530+
r.mandateVerification; // always 'not-performed'
516531
}
517532

518533
detectMppCapability(responseHeaders); // { supported, mode: 'detect-only'|'none', specVersion? }
519534
// createMppSeam().settle(...) throws MPP_NOT_IMPLEMENTED (501) — full MPP deferred until GA
520535
```
521536

522-
> The AP2 adapter is a mandate **envelope** adapter: it settles the payment but does **not**
523-
> yet verify the AP2 verifiable-credential signature — `validateMandate()` checks structure
524-
> and expiry only, not authenticity. Verify the VC out-of-band before calling `settle()`.
525-
> Built-in signature verification is in progress (see Roadmap).
537+
> **What our AP2 support means (honesty note).** `settleVc()` cryptographically
538+
> verifies an AP2 mandate VC envelope before settlement: pinned proof suites
539+
> (the reference SDK's SD-JWT/ES256 shape and W3C `eddsa-jcs-2022`), offline-only
540+
> DID resolution (`did:key` + operator-pinned `did:web` documents — never a
541+
> network fetch), an operator trust-anchor list that fails closed when empty,
542+
> and replay protection keyed `(mandateId, cartHash)`. The legacy
543+
> `settle(mandate)` slice path remains translation-only: its opaque `signature`
544+
> field is **not** verified and its receipts are always marked
545+
> `mandateVerification: 'not-performed'`. PayBot is the consumer side of AP2
546+
> mandates — it does not issue them. SD-JWT key-binding presentations and
547+
> delegation chains are not yet supported and are rejected fail-closed.
526548
527549
## Python SDK
528550

@@ -565,7 +587,7 @@ For AI agent frameworks, use [paybot-mcp](https://github.com/RBKunnela/paybot-mc
565587
|**OpenTelemetry hooks** (opt-in, zero new deps) | T1.5 |
566588
|**Multi-bot pool + spend treasury** | T1.6 |
567589
|**EURC + token registry** (per-token EIP-712 domains) | T2.2 |
568-
|**AP2 mandate envelope adapter** (settlement only — VC signature verification in progress) · 🔭 thin **MPP capability seam** (deferred to GA) | T2.3 |
590+
|**AP2 settlement adapter** · 🔭 thin **MPP capability seam** (deferred to GA) | T2.3 |
569591
|**Python SDK 0.1.0** (real EIP-3009 signing) | T3.2 (partial) |
570592
|**Network expansion** — Optimism, Arbitrum One, Polygon PoS (+ Base, Base Sepolia) | T2.1 |
571593
|**CLI**`paybot` command (register/balance/pay/health/networks/tokens) | T3.4 |
@@ -594,7 +616,7 @@ Prioritized by internal gap severity × external leverage (EU-bank credibility,
594616

595617
**Phase C — Strategic / opportunistic:** full MPP (on GA) · more language ports (Go/Rust) · L402/Lightning shim · more MCP tools.
596618

597-
**Strategic posture:** the moat is *self-hosted, non-custodial, MIT, trust-layer-in-the-SDK* — which custodial/portal-locked rivals (Coinbase Agentic Wallets, Circle, Crossmint, Payman) structurally cannot copy. Being a clean x402 settlement engine makes paybot AP2-pluggable (settlement only — see the AP2 trust-boundary note above) and AgentCore-compatible today — so MPP can wait for GA.
619+
**Strategic posture:** the moat is *self-hosted, non-custodial, MIT, trust-layer-in-the-SDK* — which custodial/portal-locked rivals (Coinbase Agentic Wallets, Circle, Crossmint, Payman) structurally cannot copy. Being a clean x402 settlement engine makes paybot AP2-pluggable and AgentCore-compatible today — so MPP can wait for GA.
598620

599621
## Deployment Options
600622

packages/python/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ asyncio.run(main())
5454
| `src/x402-v2.ts` | `paybot_sdk/x402_v2.py` |`X402Handler` — 402 parse, x402/MPP/dual signing, `upto` scheme + capture validation, PAYMENT-SIGNATURE/RESPONSE headers, submit/verify |
5555
| `src/telemetry.ts` | `paybot_sdk/telemetry.py` | ✅ Opt-in `PayBotTracer`/`PayBotSpan` protocols + `with_span`, no OTel dependency |
5656
| `src/micropayment-engine.ts` | `paybot_sdk/micropayment_engine.py` |`MicropaymentEngine` — queue, thresholds, signed batch, stats |
57-
| `src/ap2.ts` | `paybot_sdk/ap2.py` |`Ap2Adapter` (does NOT verify the AP2 VC signature — documented trust boundary) |
57+
| `src/ap2.ts` | `paybot_sdk/ap2.py` |`Ap2Adapter` legacy slice (translation-only; does NOT verify the AP2 VC signature) |
58+
| `src/ap2-vc.ts` || ⚠️ **Parity gap (AK-1):** AP2 mandate VC verification (`verifyMandate`, `settleVc`, replay store, trust anchors) is **TypeScript-only for now**. Python callers needing verified AP2 settlement must verify out-of-band or settle through paybot core. Port tracked as an AK-1 follow-up. |
5859
| `src/mpp-seam.ts` | `paybot_sdk/mpp_seam.py` |`detect_mpp_capability` + `create_mpp_seam` (`settle` raises `MPP_NOT_IMPLEMENTED` — full MPP deferred to GA) |
5960
| `src/webhook.ts` | `paybot_sdk/webhook.py` | ✅ Full (`verify_webhook_signature`, HMAC-SHA256, replay guard) |
6061
| `src/index.ts` | `paybot_sdk/__init__.py` | ✅ Full exports |

0 commit comments

Comments
 (0)