Skip to content

koombea/omh-technical-test

Repository files navigation

Remote Control — Agentic Payment Flow Prototype

A proof-of-architecture prototype demonstrating the "Remote Control" agentic commerce model: an AI agent that can discover, negotiate, and execute a payment on behalf of a user — without ever touching the user's payment credentials.


The 4-Step Flow

Step 1: INTENT          Step 2: NEGOTIATION       Step 3: SIGNING          Step 4: EXECUTION
User → Agent            Agent → Merchant           Agent → Device           Agent → Merchant
"Book flight <$150"     via UCP (mock)             (WebSocket push)         (with Signed Proof)
Agent gets INTENT       Stages TX proposal         User reviews + SCA       Proof + DPC used
NOT credentials         Agent never sees DPC       Device signs TX          Confirmation returned

Core security invariant: The agent never holds, sees, or touches the Digital Payment Credential (DPC) or private key — ever. The agent only holds a payment_instrument_id reference.


Quick Start

# Requires Node 20+ and pnpm

# 1. Install dependencies
pnpm install

# 2. Copy and set environment variables
cp .env.example .env
# Edit .env and add your ANTHROPIC_API_KEY

# 3. Start all three services (three terminal tabs recommended)
pnpm --filter merchant-mock start   # port 3001
pnpm --filter agent start           # port 3000 + WebSocket
pnpm --filter wallet-ui dev         # port 5173

Open http://localhost:5173 in your browser.


Demo Script

  1. Open wallet-ui at http://localhost:5173

    • Wallet initializes, generates ECDSA keypair, registers with agent
    • Mock DPC card displayed: Chase ••••4242
  2. In the intent input, type:

    Book me a one-way flight NYC to LA under $150
    

    Select Chase ••••4242 from the payment picker. Click Submit.

  3. Agent parses intent via Claude API → discovers merchant offers → selects cheapest → verifies merchant JWS signature → generates CheckoutMandate → sends signed push to wallet.

  4. Transaction review screen appears showing:

    • Your card section (DPC fields: issuer, masked number, holder)
    • Payment details section (TransactionalData: payee, amount, currency, nonce, expiry)
  5. Click Approve → wallet signs PaymentMandate → agent forwards both mandates to merchant → merchant verifies both independently → confirmation screen.

Alternate flows:

  • Click Reject → state transitions to REJECTED, clean abort, no signed artifacts
  • Click Counter-propose → adjust max price → agent re-negotiates with new terms, old TX invalidated
  • Wait for TTL to elapse → wallet shows "Transaction expired", sign button disabled

Architecture

┌─────────────────────────────────────────────────────────────┐
│                     CLOUD TRUST ZONE                        │
│                                                             │
│  ┌─────────────────────┐    HTTP    ┌─────────────────────┐ │
│  │    Agent Service    │◄──────────►│   Merchant Mock     │ │
│  │    (port 3000)      │            │   (port 3001)       │ │
│  │                     │            │                     │ │
│  │  • Intent parsing   │            │  • UCP endpoints    │ │
│  │  • UCP negotiation  │            │  • JWS signing      │ │
│  │  • CheckoutMandate  │            │  • Mandate verify   │ │
│  │  • Push signing     │            │  • Mock catalog     │ │
│  └──────────┬──────────┘            └─────────────────────┘ │
└─────────────│───────────────────────────────────────────────┘
              │ WebSocket (signed PushEnvelope)
┌─────────────▼───────────────────────────────────────────────┐
│                    DEVICE TRUST ZONE                        │
│                                                             │
│  ┌─────────────────────────────────────────────────────────┐│
│  │                  Wallet UI (port 5173)                  ││
│  │                                                         ││
│  │  • DPC storage (NEVER leaves this zone)                 ││
│  │  • Private key (non-extractable, SubtleCrypto)          ││
│  │  • Push signature verification                          ││
│  │  • Transaction review UI                                ││
│  │  • PaymentMandate generation (device-side signing)      ││
│  └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘

See docs/architecture.md for full detail.


Key Design Decisions

AP2 Dual-Mandate Structure

Two distinct signed artifacts, verified by different parties:

Mandate Signed by Verified by Covers
CheckoutMandate Agent (cloud) Merchant Hash of checkout terms
PaymentMandate Wallet (device) PSP via Merchant Payment authorization

These are intentionally separate — the merchant doesn't need to see payment credentials; the PSP doesn't need to see cart contents.

AP2 Mandate Lock

Once dev.ucp.shopping.ap2_mandate appears in the negotiated capability intersection, the session is security-locked. The agent cannot fall back to a simpler flow. If AP2 cannot be completed, the transaction must abort.

Wallet Public Key Registration

On startup, the wallet generates an ECDSA P-256 keypair and registers its public key with the agent (POST /agent/register-wallet-key). The agent caches this and forwards it to the merchant before any transaction begins. The merchant uses this key to verify PaymentMandate signatures at execute time.


Monorepo Structure

packages/
├── shared/         # TypeScript types only — no runtime code
│   └── src/types/
│       ├── intent.ts        # IntentManifest
│       ├── transaction.ts   # TransactionalData
│       ├── mandates.ts      # CheckoutMandate, PaymentMandate, SignedProof
│       ├── push.ts          # PushEnvelope
│       ├── state.ts         # AgentTransactionState + VALID_TRANSITIONS
│       └── ucp.ts           # UCP protocol types
│
├── agent/          # Express + socket.io, port 3000
│   └── src/
│       ├── agent-server.ts      # All routes + WebSocket
│       ├── crypto.ts            # ECDSA via Node crypto.subtle
│       ├── intent-parser.ts     # Claude API + regex fallback
│       ├── ucp-negotiator.ts    # UCP discover/checkout/counter-propose
│       ├── jws-verify.ts        # Merchant JWS signature verification
│       └── transaction-store.ts # State machine + AP2 lock
│
├── merchant-mock/  # Express, port 3001
│   └── src/
│       ├── server.ts            # UCP endpoints + key management
│       ├── catalog.ts           # Mock flight offers
│       └── crypto.ts            # ECDSA + JWS generation
│
└── wallet-ui/      # Vite + React 18, port 5173
    └── src/
        ├── App.tsx                      # Main app + WebSocket + flow logic
        ├── types/dpc.ts                 # DigitalPaymentCredential (ONLY here)
        ├── lib/
        │   ├── crypto.ts               # SubtleCrypto (browser-native)
        │   ├── dpc-store.ts            # Mock DPC store
        │   ├── push-verifier.ts        # Verify agent push signatures
        │   ├── signed-proof.ts         # Generate SignedProof
        │   └── websocket.ts            # Socket.io client
        └── components/
            ├── IntentInput.tsx          # NL input + payment picker
            ├── DPCCard.tsx              # Card display (DPC fields only)
            ├── TransactionReview.tsx    # Review + approve/reject/counter-propose
            └── Confirmation.tsx        # Result screen

Important: DigitalPaymentCredential is defined only in wallet-ui/src/types/dpc.ts. It is never imported by agent or merchant-mock. This is enforced at the TypeScript type level.


Running Tests

# Run all tests across all packages
pnpm test

# Run tests for a specific package
pnpm --filter agent test
pnpm --filter merchant-mock test
pnpm --filter wallet-ui test

357 tests across 17 test suites — all passing.

Test coverage includes:

  • Cryptographic operations (ECDSA keygen, sign, verify, key mismatch)
  • Intent parsing (Claude API + fallback, field validation, DPC field security)
  • JWS signing and verification (valid, tampered, wrong key, JWKS rotation)
  • UCP negotiation (offer selection, merchant signature, expiry, counter-proposal)
  • Mandate generation (CheckoutMandate shape, canonical payloads, signatures)
  • State machine (all 16 states, all valid transitions, invalid transition rejection)
  • AP2 lock invariant (immutability, dual-mandate enforcement)
  • Nonce invalidation (permanent blocking, counter-proposal safety)
  • Push verification (signature attacks, tamper detection, field validation)
  • SignedProof generation (wallet signing, canonical format, verification)

Environment Variables

ANTHROPIC_API_KEY=sk-ant-...    # Required for intent parsing (falls back to regex if absent)
AGENT_PORT=3000                 # Optional, defaults to 3000
MERCHANT_PORT=3001              # Optional, defaults to 3001
MERCHANT_URL=http://localhost:3001  # Optional, used by agent to reach merchant

What's Mocked vs Real

Component Status Notes
UCP protocol Mocked REST shape only — real UCP is Google's spec
Merchant catalog Mocked 3 hardcoded flight offers
Biometric SCA Mocked Button click simulates approval
Push notifications Mocked WebSocket instead of APNs/FCM
OpenID4VCI provisioning Mocked DPC pre-loaded in store
SD-JWT-VC encoding Mocked Plain JSON mandates
ECDSA signing REAL SubtleCrypto P-256, non-extractable keys
Mandate separation REAL Distinct CheckoutMandate + PaymentMandate objects
Merchant JWS verification REAL Agent verifies before presenting to user
Agent push signing REAL Wallet verifies before rendering UI
DPC/TransactionalData separation REAL Enforced in types + UI layout
Transaction TTL REAL Timestamp checks on agent + wallet
AP2 capability lock REAL State machine enforces, no fallback
Nonce replay protection REAL Checked by agent before execute
Counter-proposal invalidation REAL Old nonce permanently blocked

Security Model

See docs/security-model.md for the full attack surface analysis.

Core guarantee:

Agent knows:  payment_instrument_id (reference), user intent, staged TX, CheckoutMandate
Agent never:  private key, full PAN, DPC object, biometric data, PaymentMandate signing key

Innovation Highlights

  1. Agent Capability Declaration (ACD) — Agent presents a signed manifest to the merchant declaring: (a) acting on behalf of a verified user, (b) routing signing to a bound device, (c) cannot self-authorize, (d) AP2 support level.

  2. IntentManifest formalization — Natural language is immediately structured into a typed IntentManifest before any UCP interaction. The preferred_payment_instrument_id field handles payment method selection formally, preventing agent assumptions.

  3. Counter-Proposal with nonce invalidation — Re-negotiation permanently invalidates the prior staged transaction and nonce. Neither party can use the old signed proof after re-negotiation.

  4. AP2 Dual-Mandate Proof Structure — Explicit CheckoutMandate + PaymentMandate separation as specified in the AP2 spec (not just "a signed proof").

See docs/innovation-narrative.md for full detail.


Docs

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors