The stub is gone

For six weeks, our /v1/mandates/verify endpoint returned 200 OK for any signature payload. The stub existed so we could ship the envelope schema, the SDK, and the rail integrations in parallel — a reasonable staging decision, but one that left a critical gap: the verifier accepted malformed signatures, empty strings, and outright garbage equally well.

Today that stub is gone. The endpoint now calls mldsa65.verify() from @noble/post-quantum on every request. If the ML-DSA-65 signature does not verify against the public key and the JCS-canonicalised envelope body, the API returns 400. No exceptions.

What changed

Three concrete things landed in production today:

  • Signature verification. Every POST to /v1/mandates/verify now calls mldsa65.verify(publicKey, signature, message) where message is the SHA-256 fingerprint of the JCS-canonicalised envelope body. Verification is strict: the 3,309-byte ML-DSA-65 signature must match exactly.
  • Public key length check. The ML-DSA-65 public key is 1,952 bytes. Any submission with a key of the wrong length is rejected before the crypto call, which prevents a class of length-confusion attacks.
  • SHA-256(JCS) fingerprint protocol. The signing and verification flow now canonicalises the JSON envelope body with RFC 8785 JSON Canonicalization Scheme before hashing. This eliminates key ordering and whitespace ambiguities from the verification input. Any implementation that skips JCS will produce a wrong fingerprint and fail verification, even with a correct key pair.

The ML-DSA-65 parameter set (NIST FIPS 204, security category 3, analogous to AES-192) produces 3,309-byte signatures and 1,952-byte public keys. These figures are normative in the AP2-PQ profile.

Parameter Value
AlgorithmML-DSA-65 (CRYSTALS-Dilithium, round 3)
StandardNIST FIPS 204 (final, 2024-08-13)
Security category3 (AES-192 equivalent)
Public key size1,952 bytes
Signature size3,309 bytes
Message digestSHA-256(JCS-canonicalised envelope body)
Implementation@noble/post-quantum (audited)

Why this matters

In agent-payment infrastructure, a stubbed crypto endpoint is not infrastructure — it is a marketing claim. An attacker (or a buggy agent) can replay any envelope, forge spend authorisations, or escalate a capped $5 envelope into an uncapped one simply by sending a different signature value. The stub invites exactly that class of attack.

The risk is not theoretical. The ClawHavoc post-mortem (Vellum / Betterclaw, April 2026) documented 138 exploited CVEs and more than 1,400 malicious skills distributed via clawhub.ai. Many of those skills bypassed payment authorisation precisely because downstream verifiers did not actually verify. A skill that presents a signed payment envelope to a stubbed verifier gains all the appearance of cryptographic legitimacy with none of the protection.

Most "post-quantum aware" skills on clawhub.ai today call a verifier that does not verify. The signature bytes are present; the check is absent. That is the gap this deployment closes — for our endpoint.

The broader point: cryptographic correctness is not something you add after product-market fit. For payment infrastructure, it is the product. An envelope signed with ML-DSA-65 and verified by a real implementation gives a merchant, a payment rail, or an auditor a cryptographic guarantee that the authorisation came from the key that issued it, was not tampered with in transit, and cannot be repudiated. A stub gives none of those properties.

Conformance: six published test vectors

Deploying a verifier is necessary but not sufficient. Interoperability requires that other implementations — payment rails, merchant SDKs, compliance tools — can independently verify AP2-PQ envelopes and arrive at the same result. We have published six canonical test vectors at pqsafe.xyz/spec/ap2-pq-test-vectors-v1.json:

  • Five positive cases (valid envelope, correct signature, must return 200)
  • One negative case: TC-N1, which exposes a silent-accept bug in pqcrypto 0.4.0 — a Python library that incorrectly accepts a tampered envelope that should fail

To run the full conformance suite against any verifier implementation:

# Run conformance suite against your own verifier npx @pqsafe/conformance --impl ./your-verifier.js # Or against the live production endpoint npx @pqsafe/conformance --endpoint https://api.pqsafe.xyz/v1/mandates/verify # Verbose output with per-vector pass/fail npx @pqsafe/conformance --endpoint https://api.pqsafe.xyz/v1/mandates/verify --verbose

The conformance package sources its vectors directly from the canonical JSON at pqsafe.xyz, so any future additions to the test vector file are picked up automatically on the next run.

Try it: verify a signed envelope

The endpoint accepts a JSON body with envelope, signature (base64url), and publicKey (base64url). A minimal example using @pqsafe/agent-pay:

import { createEnvelope, signEnvelope } from '@pqsafe/agent-pay'; // 1. Create and sign a spend envelope const { envelope, keyPair } = await createEnvelope({ agentId: 'agent-001', maxAmount: { value: '5.00', currency: 'USD' }, allowedRecipients: ['perplexity.ai'], validUntil: '2026-05-05T12:00:00Z', }); const { signature } = await signEnvelope(envelope, keyPair.privateKey); // 2. Verify against the live API const res = await fetch('https://api.pqsafe.xyz/v1/mandates/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ envelope, signature, // base64url-encoded, 3,309-byte ML-DSA-65 signature publicKey: keyPair.publicKey, // base64url-encoded, 1,952-byte public key }), }); const result = await res.json(); console.log(result); // { valid: true, algorithm: "ML-DSA-65", fips: "FIPS-204", envelopeId: "..." }

The browser-based verifier at pqsafe.xyz/verify/ also now runs real ML-DSA-65 verification client-side via the same @noble/post-quantum library — no server round-trip required.

What is next

  • api.pqsafe.xyz custom domain. NS migration is pending — currently the verifier endpoint resolves via the Vercel default domain. Custom domain propagation expected within the week.
  • Multi-region observability. Latency and error-rate dashboards for the verifier endpoint, broken down by region. The 3,309-byte signature verification adds roughly 2–4 ms per call; we want that number observable.
  • AP2-PQ v1 spec submission to FIDO Agentic Auth TWG. The FIDO Alliance Agentic Authentication Technical Working Group was formed on 2026-04-28. We are preparing a formal submission of the AP2-PQ profile — the post-quantum extension to FIDO AP2 for agent-initiated payments. The profile is already published at pqsafe.xyz/spec/ap2-pq-v1/.
  • @pqsafe/conformance v0.1.0 release announcement. The conformance package is published on npm but has not had a formal launch post yet. That comes next.

References

  1. clawhub.ai/skills/pqsafe-pay — pqsafe-pay OpenClaw skill listing
  2. pqsafe.xyz/spec/ap2-pq-v1/ — AP2-PQ profile canonical specification
  3. pqsafe.xyz/spec/ap2-pq-test-vectors-v1.json — Conformance test vectors (machine-readable JSON)
  4. npmjs.com/package/@pqsafe/conformance — Conformance test runner
  5. NIST FIPS 204 — Module-Lattice-Based Digital Signature Standard (final, 2024-08-13). doi.org/10.6028/NIST.FIPS.204
  6. FIDO Alliance — Agentic Authentication Technical Working Group formed 2026-04-28. fidoalliance.org
  7. Vellum / Betterclaw — ClawHavoc post-mortem: 138 CVEs, 1,400+ malicious skills. betterclaw.dev/clawhavoc