๐Ÿฆž
PQSafe · Quickstart

Verify your first envelope in 60 seconds.

One command. One paste. One signed mandate. No signup, no API key, no email.

1

Install the SDK

0:00 — 0:20

Node / TypeScript
# core SDK
npm i @pqsafe/agent-pay

# conformance test vectors (optional)
npm i @pqsafe/conformance
Python
# core SDK
pip install pqsafe

# conformance test vectors (optional)
pip install pqsafe-conformance
2

Verify a test envelope

0:20 — 0:40

verify.ts
import { verify } from '@pqsafe/agent-pay';

const vec = await fetch(
  'https://pqsafe.xyz/spec/ap2-pq-test-vectors-v1.json'
).then(r => r.json());

const result = await verify(vec.tcs[0].envelope, {
  ecdsa_pub_hex: vec.ecdsa_public_key_compressed_hex,
  mldsa_pub_b64u: vec.mldsa_public_key_base64url,
});

console.log(result.valid ? 'OK' : 'REJECTED');
// โ†’ OK
verify.py
from pqsafe import verify
import urllib.request, json

vec = json.load(urllib.request.urlopen(
  'https://pqsafe.xyz/spec/ap2-pq-test-vectors-v1.json'
))

result = verify(
  vec['tcs'][0]['envelope'],
  ecdsa_pub_hex=vec['ecdsa_public_key_compressed_hex'],
  mldsa_pub_b64u=vec['mldsa_public_key_base64url'],
)

print('OK' if result.valid else 'REJECTED')
# โ†’ OK
3

Reject a tampered one

0:40 — 0:60

tamper.ts
const evil = structuredClone(vec.tcs[0].envelope);
evil.amount = '9999.00';  // mutate

const r = await verify(evil, {
  ecdsa_pub_hex: vec.ecdsa_public_key_compressed_hex,
  mldsa_pub_b64u: vec.mldsa_public_key_base64url,
});

console.log(r.valid ? 'OK' : 'REJECTED');
// โ†’ REJECTED
tamper.py
import copy
evil = copy.deepcopy(vec['tcs'][0]['envelope'])
evil['amount'] = '9999.00'  # mutate

r = verify(
  evil,
  ecdsa_pub_hex=vec['ecdsa_public_key_compressed_hex'],
  mldsa_pub_b64u=vec['mldsa_public_key_base64url'],
)

print('OK' if r.valid else 'REJECTED')
# โ†’ REJECTED

Drop into your agent framework

One import. The pqsafePay tool gates every payment call on a verified envelope. Snippets below are minimal — consult each plugin's README for production wiring.

pip install langchain-pqsafe
from langchain.agents import AgentExecutor
from langchain_pqsafe import AgentPayTool

agent = AgentExecutor(
    tools=[AgentPayTool(spend_cap='100.00', currency='USD')],
    ...
)
# every payment the agent attempts is gated by a signed envelope
What just happened

The SDK fetched the canonical test vectors, ran RFC 8785 JCS canonicalization on the mandate (without the signature field), SHA-256'd the canonical bytes to get a 32-byte fingerprint, and verified two signatures over that fingerprint: ECDSA P-256 (legacy compatibility) and ML-DSA-65 (FIPS 204 post-quantum). Both must pass.

When you mutated amount, the canonical bytes changed, the fingerprint changed, and both signatures stopped matching. There is no way to flip a field and re-use the original signature — you would need the issuer's private keys.

This is what makes the SpendEnvelope a cryptographic permission slip rather than a session token. The amount, the recipient, the currency, and the nonce are bound to the signature. Replay protection comes from the nonce registry (on-chain + edge cache). Revocation comes from a separate signed revoke list. Audit comes from the canonical envelope retention.

Why two signatures? ECDSA P-256 gives you universal verifier compatibility today. ML-DSA-65 gives you forward security through the post-quantum transition. The cost is ~3KB of signature bytes and ~0.03 ms of native verify time on top of ECDSA — see /bench.

Try the Tamper Explorer โ†’

8 one-click mutations on a real envelope. See exactly which signature fails when which field changes.

Issue one in your browser โ†’

Generate an ephemeral keypair and sign a SpendEnvelope client-side. No setup.

Compare to JWT / AP2 / Stripe Issuing โ†’

Capability matrix, where each one wins, when post-quantum matters.

Read the AP2-PQ spec โ†’

Wire-level details of the canonical envelope shape and dual-signature scheme.