AP2-PQ-EXT-01 · Standards Track (Community Draft) · DRAFT
Post-Quantum Signature Profile for AP2-style Agent-Payment Mandates
Abstract
This document specifies a post-quantum signing profile for AP2-style SD-JWT mandate credentials,
contributed to the FIDO Alliance Payments Technical Working Group. The profile adds two JOSE header
parameters — pq_alg and pq_sig — to the existing JWT header used in
AP2 v0.2 mandate credentials. The primary algorithm identifier is "ML-DSA-65",
corresponding to NIST FIPS 204 (initial public draft qualifier: NIST IR 8547 ipd). A hybrid profile
"ES256+ML-DSA-65" is defined for deployments requiring both classical interoperability
and quantum resistance in a single signing operation. The extension is strictly backward-compatible:
classical verifiers that do not recognize pq_alg continue to verify the
alg: "ES256" signature and ignore the additional header parameters.
| Authors | Raymond Chau, Tris Au Yeung — PQSafe AgentPay |
| Contact | raymond@pqsafe.xyz |
| Created | 2026-04-28 |
| Last revised | 2026-05-01 (Revision 02) |
| Feedback | rfc@pqsafe.xyz · GitHub Issues · Deadline: 2026-05-15 |
| License | Apache-2.0 |
§1 Background — Why Post-Quantum, Why Now
1.1 The Harvest-Now-Forge-Later Threat
"Harvest Now, Decrypt Later" (HNDL) is well understood: nation-state adversaries collect ciphertext today for decryption once a cryptographically-relevant quantum computer (CRQC) arrives. AP2-style mandate credentials face the less-discussed but structurally identical sibling threat: Harvest Now, Forge Later (HNFL).
In HNFL, an adversary collects classically-signed payment authorization records — SD-JWT mandate credentials logged to immutable audit trails — and retrospectively forges them once a CRQC enables reconstruction of ECDSA private keys from their corresponding public keys via Shor's algorithm. For a 15-minute web session token, this risk is negligible. For a payment authorization mandate retained in a financial audit log for seven years, the risk is material: the mandatory retention window overlaps the projected CRQC arrival window for several credible threat timelines.
AP2 mandate credentials have three properties that make them a higher-priority PQ migration target than standard OAuth access tokens or short-lived JWTs:
-
Long retention windows. Financial authorization records must be retained for
5–7 years under HKMA Anti-Money Laundering rules (Cap. 615), IRD requirements (Cap. 112), and
PSD2 Article 69 (5 years for payment service providers in EU jurisdictions). A mandate signed
today under
ES256will still be within its mandatory retention window in 2032–2033. -
Public key discoverability. AP2 mandate signing keys published in DID documents
or
/.well-knownauthorization server metadata can be harvested at scale without intercepting individual mandates. An adversary with a CRQC can reconstruct the private key from the public key and forge any historically-collected mandate. - Dispute resolution utility. The cryptographic integrity of a mandate credential is an evidentiary property in regulatory audits, insurance disputes, and litigation. Forged mandates presented as authoritative audit evidence have direct legal and financial consequences that extend beyond individual transaction amounts.
1.2 Regulatory Drivers
Two active regulatory programs create near-term compliance obligations for institutions deploying agent-payment mandate credentials in regulated jurisdictions.
NIST IR 8547 (2024, initial public draft).
The U.S. National Institute of Standards and Technology proposes deprecation of 112-bit classical
algorithms (including P-256 ECDSA, which underlies ES256) by 2030, and full
disallowance by 2035 for high-priority systems including payment-critical infrastructure. NIST IR 8547
explicitly references FIPS 204 (ML-DSA) as the standardized transition target for signature
schemes. Institutions treating 2030 as a planning horizon and working backward through procurement,
integration, and audit cycles have limited runway. Status: initial public draft — not yet final
NIST policy.
HKMA Quantum Preparedness Index (QPI), announced February 3, 2026. The Hong Kong Monetary Authority has launched a formal quantum readiness assessment framework for regulated entities operating in Hong Kong. The QPI references NIST PQC standards and requires regulated institutions to begin quantum readiness assessments covering payment-critical systems. For institutions deploying agent-payment workflows, mandate-layer PQ signing is within scope of the HKMA's cryptographic inventory requirements. Retention obligations under Cap. 615 are 6 years; under IRD Cap. 112 they extend to 7 years.
PSD2 Article 69. Payment service providers subject to the revised Payment Services Directive in EU jurisdictions are required to retain payment authorization records for a minimum of 5 years. A mandate issued in 2026 remains within its mandatory PSD2 retention window until 2031 — a date that falls within the most aggressive CRQC arrival estimates.
1.3 Algorithm Selection Rationale — Why ML-DSA-65, Not Falcon or SLH-DSA
Three NIST-standardized post-quantum signature algorithms were evaluated:
Falcon-512 produces ~666-byte signatures — substantially smaller than ML-DSA-65's 3,309 bytes. However, Falcon's signing algorithm requires constant-time floating-point arithmetic (discrete Gaussian sampling over lattices using floating-point rejection sampling). Software implementations carry significant timing side-channel risk that is inappropriate for payment signing infrastructure on general-purpose CPUs. Falcon is excluded on safety-of-implementation grounds.
SLH-DSA (NIST FIPS 205) rests on conservative hash-function security assumptions. However, SLH-DSA-128s produces 7,856-byte signatures; larger parameter sets reach 49,856 bytes. These sizes exceed practical limits for per-transaction mandate payloads. SLH-DSA is excluded on wire-size grounds.
ML-DSA-65 (NIST FIPS 204, λ=192 parameter set) provides NIST Category 3 security
(at least 128-bit classical-equivalent) with 3,309-byte signatures, constant-time software
implementations in production-grade libraries, and no floating-point arithmetic requirement.
ML-DSA-65 at 3,309 bytes is the appropriate tradeoff for regulated financial deployments with
mandatory retention requirements. The OID is 2.16.840.1.101.3.4.3.18 (verify against
the NIST CSOR registry
before deployment, as OID assignments may be revised during the early standardization period).
1.4 Structural Alignment with AP2 v0.2 SD-JWT Architecture
AP2 v0.2 uses SD-JWT mandate credentials with "alg": "ES256" in the JWT header.
This profile follows the same structural convention: the signing algorithm is declared in the
JWT header, not the mandate payload body. This extension adds pq_alg and
pq_sig as additional JWT header parameters — additive, not replacing the classical
alg and its corresponding signature. The SD-JWT payload and _sd claim
structure are unchanged.
§2 Specification — JOSE Header Parameter Extension
2.1 Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHOULD", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174).
| Term | Definition |
|---|---|
ML-DSA-65 | Module-Lattice-Based Digital Signature Algorithm, NIST Category 3 parameter set, as specified in NIST FIPS 204 (August 2024, initial public draft qualifier: NIST IR 8547 ipd). Formerly known as Dilithium3. |
pq_alg | JOSE header parameter identifying the post-quantum (or hybrid) algorithm used to produce pq_sig. |
pq_sig | JOSE header parameter containing the post-quantum signature, Base64url-encoded. |
| JCS | JSON Canonicalization Scheme. RFC 8785. Deterministic UTF-8 serialization with keys sorted lexicographically at all nesting levels; array element order preserved. |
| JWS Signing Input | Per RFC 7515 §7.2.1: the ASCII bytes of BASE64URL(JWS Protected Header) || '.' || BASE64URL(JWS Payload). |
| CRQC | Cryptographically Relevant Quantum Computer. A quantum computer with sufficient fault-tolerant qubit count to run Shor's algorithm against P-256 or equivalent elliptic-curve groups. |
| HNFL | Harvest Now, Forge Later. Adversarial strategy of collecting classically-signed records today for retrospective forgery once a CRQC is available. |
2.2 Proposed JOSE Header Parameters
For AP2 v0.2 SD-JWT mandate credentials, this profile proposes two new JOSE header parameters, registered in accordance with RFC 7515 §4.1:
- pq_alg
- Parameter value type: String
- Allowed values:
"ML-DSA-65"|"ES256+ML-DSA-65" - Identifies the post-quantum (or hybrid) algorithm used to produce the
pq_sigvalue. The classicalalgparameter MUST remain present and MUST be a valid JOSE algorithm identifier;pq_algis additive, not a replacement. MUST be ignored by verifiers that do not implement this profile. - pq_sig
- Parameter value type: String (Base64url, RFC 4648 §5, unpadded)
- The post-quantum signature over the JWS Signing Input per RFC 7515 §7.2.1 — the ASCII bytes
of
BASE64URL(JWS Protected Header) || '.' || BASE64URL(JWS Payload). The signing algorithm is identified bypq_alg. Encoded length for"ML-DSA-65": 4,412 characters (3,309 bytes, Base64url-encoded).
2.3 Example JWT Header
{
"alg": "ES256",
"pq_alg": "ML-DSA-65",
"pq_sig": "<Base64url-encoded ML-DSA-65 signature, 4412 chars>",
"_sd_alg": "sha-256"
}
Classical verifiers process alg: "ES256" and ignore pq_alg and
pq_sig. Post-quantum verifiers process both. No changes to the SD-JWT payload
or _sd claim structure are required.
2.4 HashML-DSA Mode (Normative)
pq_sig values generated under pure-mode when pq_alg: "ML-DSA-65"
is declared.
signing_input = ASCII(BASE64URL(header)) || 0x2E || ASCII(BASE64URL(payload))
fingerprint = SHA-256(signing_input) # 32 bytes
pq_sig_bytes = ML-DSA-65.Sign(sk, fingerprint) # 3309 bytes
pq_sig = BASE64URL(pq_sig_bytes) # 4412 chars, unpadded
Implementations using pure-mode ML-DSA-65 (signing the full JWS Signing Input without pre-hashing) will produce non-interoperable signatures. This distinction MUST be explicit in any conforming implementation.
RFC 8785 (JCS) canonicalization applies to the mandate payload prior to SD-JWT encoding. The same canonical byte sequence feeds both the ECDSA and ML-DSA-65 signing operations where a pre-JWT canonicalization step is present.
2.5 Hybrid Profile: "ES256+ML-DSA-65"
When pq_alg: "ES256+ML-DSA-65" is declared:
- The
pq_sigvalue encodes two concatenated signatures:ECDSA_P256_sig (64 bytes, R||S fixed-width) || ML-DSA-65_sig (3309 bytes)= 3,373 bytes total, Base64url-encoded. - The ECDSA P-256 signature (alg:
ES256) covers the same JWS Signing Input as the ML-DSA-65 signature (both in HashML-DSA mode for ML-DSA-65; the ECDSA P-256 component uses SHA-256 over the JWS Signing Input per RFC 7518 §3.4). - Verifiers implementing the hybrid profile MUST verify both component signatures. Failure of either MUST result in verification failure.
- This profile is intended for deployments that require a dual-algorithm audit trail with an additional
ECDSA P-256 signature in
pq_sig, alongside the JOSEalg: "ES256"signature, while ML-DSA-65 implementations mature.
2.6 OID Alignment
| Algorithm | OID | Standard |
|---|---|---|
| ML-DSA-65 | 2.16.840.1.101.3.4.3.18 | NIST FIPS 204 (initial public draft qualifier) |
| ECDSA P-256 (ES256) | 1.2.840.10045.3.1.7 | RFC 5480, RFC 7518 §3.4 |
Implementers MUST verify the ML-DSA-65 OID against the NIST CSOR registry before deployment; OID assignments may be revised during the early standardization period.
2.7 Byte-Level Encoding
| Component | Raw bytes | Base64url chars (unpadded) |
|---|---|---|
| ML-DSA-65 signature | 3,309 | 4,412 |
| ML-DSA-65 public key | 1,952 | ~2,603 |
| ECDSA P-256 signature, R||S (hybrid) | 64 | 86 |
| Hybrid pq_sig total | 3,373 | ~4,498 |
All encoding uses Base64url (RFC 4648 §5, unpadded). Lowercase hex and padded Base64 encodings are not conformant. Implementations MUST NOT accept signatures that decode to any length other than 3,309 bytes (ML-DSA-65 mode) or 3,373 bytes (hybrid mode).
§3 Wire Format — Before and After
3.1 Baseline AP2 Mandate (No PQ Extension)
The following example shows a complete AP2 SD-JWT mandate payload in its baseline form,
with the JWT header using only the standard ES256 algorithm identifier.
// JWT header (baseline)
{
"alg": "ES256",
"_sd_alg": "sha-256"
}
// Mandate payload (illustrative — not all fields shown)
{
"iss": "https://auth.example.com",
"sub": "did:web:user.example.com:alice",
"agent_id": "did:web:agents.example.com:purchasing-bot-v2",
"payee_constraints": [
{ "payee_id": "did:web:stripe.com:merchant:acct_1A2B3C4D" },
{ "payee_id": "did:web:airwallex.com:merchant:aw_1X2Y3Z" }
],
"spend_cap": 500.00,
"currency": "USD",
"valid_from": "2026-05-01T00:00:00Z",
"valid_until": "2026-05-01T23:59:59Z",
"nonce": "a3f8c2e1d4b7906f2c5e8a1b4d7f0e3c"
}
3.2 Same Mandate With PQ Profile Applied (JOSE Header Extension)
The PQ profile is applied entirely at the JWT header level. The mandate payload is unchanged.
Classical verifiers process alg: "ES256" and ignore the two additional header
parameters.
// JWT header with pq_alg + pq_sig added
{
"alg": "ES256",
"pq_alg": "ML-DSA-65",
"pq_sig": "<Base64url-encoded ML-DSA-65 signature, 4412 chars — see §4 for test vectors>",
"_sd_alg": "sha-256"
}
// Mandate payload — identical to baseline, no changes
{
"iss": "https://auth.example.com",
"sub": "did:web:user.example.com:alice",
"agent_id": "did:web:agents.example.com:purchasing-bot-v2",
"payee_constraints": [
{ "payee_id": "did:web:stripe.com:merchant:acct_1A2B3C4D" },
{ "payee_id": "did:web:airwallex.com:merchant:aw_1X2Y3Z" }
],
"spend_cap": 500.00,
"currency": "USD",
"valid_from": "2026-05-01T00:00:00Z",
"valid_until": "2026-05-01T23:59:59Z",
"nonce": "a3f8c2e1d4b7906f2c5e8a1b4d7f0e3c"
}
3.3 Signing Input Construction
The pq_sig value covers the JWS Signing Input per RFC 7515 §7.2.1:
signing_input = BASE64URL(JWS Protected Header) || "." || BASE64URL(JWS Payload)
# HashML-DSA pre-hash (normative per §2.4):
fingerprint = SHA-256(signing_input) # 32 bytes
# Sign the fingerprint, not the raw signing_input:
pq_sig_bytes = ML-DSA-65.Sign(sk, fingerprint) # 3309 bytes
pq_sig = BASE64URL(pq_sig_bytes) # included in JWT header
Key observations:
- The signing input is the Base64url-encoded header+payload string, not the raw mandate JSON body.
- RFC 8785 JCS canonicalization applies to the mandate payload before SD-JWT encoding. The same canonical byte sequence feeds both the ECDSA and ML-DSA-65 signing operations.
- The ECDSA signature covers the JWS Signing Input directly per standard JWT processing. The ML-DSA-65 signature covers the SHA-256 digest of the same JWS Signing Input (HashML-DSA mode).
3.4 Retention-Aware Mandate Example
The primary motivating use case is a mandate issued in 2026 that will still be within its
HKMA Cap. 615 mandatory retention window in 2033. A retention_metadata field
included in the signed bytes ensures any post-issuance modification to the retention timestamp
invalidates the original signature.
// Mandate payload with retention metadata
{
"agent_id": "did:web:agents.example.com:agent-tc4",
"amount": "88000.00",
"currency": "HKD",
"issued_at": "2026-05-01T00:00:00.000Z",
"nonce": "ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d",
"recipient": "did:web:payee.example.com:main",
"retention_metadata": {
"policy": "HKMA-Cap615-7yr",
"retention_until": "2033-05-01T00:00:00.000Z"
}
}
// JCS canonical form (object keys sorted lexicographically at all nesting levels):
// {"agent_id":"did:web:agents.example.com:agent-tc4","amount":"88000.00",
// "currency":"HKD","issued_at":"2026-05-01T00:00:00.000Z",
// "nonce":"ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d",
// "recipient":"did:web:payee.example.com:main",
// "retention_metadata":{"policy":"HKMA-Cap615-7yr",
// "retention_until":"2033-05-01T00:00:00.000Z"}}
500.00 serializes as 500; 1.10 serializes as 1.1;
100.0 serializes as 100. Trailing zeros and unnecessary decimal points
are removed. String-typed amount fields (e.g., "88000.00") are serialized verbatim —
no normalization applies to string values.
§4 Test Vectors
Five deterministic test cases (TC1–TC5) are published at
github.com/PQSafe/ap2-pq-test-vectors
(Apache-2.0). The frozen byte-exact fixture is vectors.json, committed alongside the
generator (generate_vectors.ts).
Key Test Vector Parameters
| Parameter | Value |
|---|---|
| ML-DSA-65 key seed derivation | SHA-256("pqsafe-ap2-tc-mldsa") |
| ECDSA key seed derivation | SHA-256("pqsafe-ap2-tc-ecdsa") |
| Library versions | @noble/post-quantum@0.6.0 (exact, no caret), @noble/curves@1.4.0, canonicalize@2.0.0 |
| TC1 SHA-256 fingerprint | 8617d93f851c2a1fb72f49c90a27e874666fd0c98b4992809d55eec6ef1da539 |
| TC2 SHA-256 fingerprint | f447cb36ee1fe1dbbecbf5961bcb8ae7038a4881a84912bfc89809d998b07e01 |
| Cross-platform CI | Mac, Linux x86-64, Windows (GitHub Actions) |
ml_dsa65.sign(sk, fingerprint) — Algorithm 2, operating on the
32-byte SHA-256 fingerprint as a plain message. If @noble/post-quantum also exports
hashMl_dsa65, that export MUST NOT be used. HashML-DSA.Sign (Algorithm 4, FIPS 204
§5.4) adds an internal pre-hashing step, which would double-hash the already-SHA-256'd fingerprint
and produce non-interoperable signatures. Implementations that mix these two modes will fail
cross-platform verification.
Test Case Summary
| ID | Name | What it validates | Expected outcome |
|---|---|---|---|
| TC1 | Minimal mandate | Baseline: minimum required fields, single payee, correct integer/string serialization | ECDSA ✓, ML-DSA-65 ✓ |
| TC2 | Array ordering | JCS preserves insertion order; deliberately reverse-alphabetical payee array MUST NOT be sorted | ECDSA ✓, ML-DSA-65 ✓ |
| TC3 | Decimal normalization | RFC 8785 §3.2.2: 1.10 → 1.1, 100.0 → 100. String fields not normalized. |
ECDSA ✓, ML-DSA-65 ✓ |
| TC4 | 7-year retention mandate | Retention metadata included in signed bytes; nested object keys sorted recursively; future dates accepted | ECDSA ✓, ML-DSA-65 ✓ |
| TC5 | Tamper detection | TC1 amount changed by 1 cent ("125.00" → "125.01"); original signatures applied to modified payload MUST fail both verifiers |
ECDSA REJECT, ML-DSA-65 REJECT |
TC5 — Tamper Detection Detail
TC5 is the security-critical assertion. The TC1 mandate has its
amount field changed from "125.00" to "125.01" (one cent
inflation). The TC1 signatures — computed over canonical bytes containing "125.00" —
are applied to the modified payload. Both ECDSA P-256 and ML-DSA-65 verification MUST return
false. Any implementation that returns true on this case has a critical
defect in its verification path.
{
"id": "tc5-tamper-detection",
"description": "TC1 +1 cent tamper — MUST fail for both ECDSA and ML-DSA-65",
"ecdsa_verify_with_original_sig": false,
"mldsa_verify_with_original_sig": false,
"fingerprints_match": false
}
In dual-sign mode, BOTH signatures MUST pass for the mandate to be accepted. A "partial verification" mode that accepts a mandate when only one of the two signatures passes is a security vulnerability — such an implementation degrades to single-algorithm security, defeating the purpose of the hybrid profile.
§5 Reference Implementation
The PQSafe reference implementation is available at github.com/PQSafe/pqsafe under the Apache-2.0 license. A browser-based verifier is available at pqsafe.xyz/verify (no build step required).
5.1 TypeScript
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa';
import canonicalize from 'canonicalize'; // RFC 8785 JCS
import { createHash } from 'node:crypto';
// --- Signing ---
// sdJwtHeader: the Base64url-encoded JWT protected header string
// sdJwtPayload: the Base64url-encoded JWT payload string
function pqSign(sdJwtHeader: string, sdJwtPayload: string, sk: Uint8Array): string {
// JWS Signing Input per RFC 7515 §7.2.1
const jwsInput = `${sdJwtHeader}.${sdJwtPayload}`;
// HashML-DSA: pre-hash with SHA-256 (normative per §2.4)
const fingerprint = createHash('sha256').update(jwsInput).digest();
// ML-DSA.Sign (Algorithm 2, FIPS 204 §5.2) — pass fingerprint as message
// Do NOT use hashMl_dsa65 — would double-hash and produce non-interoperable signatures
const sigma = ml_dsa65.sign(sk, fingerprint);
return Buffer.from(sigma).toString('base64url'); // 4412 chars, unpadded
}
// --- Verification ---
function pqVerify(sdJwtHeader: string, sdJwtPayload: string, pqSig: string, pk: Uint8Array): boolean {
const jwsInput = `${sdJwtHeader}.${sdJwtPayload}`;
const fingerprint = createHash('sha256').update(jwsInput).digest();
const sigma = Buffer.from(pqSig, 'base64url');
if (sigma.length !== 3309) return false; // reject wrong-length signatures
return ml_dsa65.verify(pk, fingerprint, new Uint8Array(sigma));
}
// Install: npm install @noble/post-quantum canonicalize
5.2 Python
import hashlib, base64
from cryptography.hazmat.primitives.asymmetric.dilithium import (
generate_private_key, MLDSAPrivateKey, MLDSAPublicKey
)
def pq_sign(sd_jwt_header: str, sd_jwt_payload: str, private_key: MLDSAPrivateKey) -> str:
# JWS Signing Input per RFC 7515 §7.2.1
jws_input = f"{sd_jwt_header}.{sd_jwt_payload}".encode('ascii')
# HashML-DSA: pre-hash with SHA-256 (normative per §2.4)
fingerprint = hashlib.sha256(jws_input).digest()
# ML-DSA.Sign (Algorithm 2, FIPS 204 §5.2)
sigma = private_key.sign(fingerprint)
return base64.urlsafe_b64encode(sigma).rstrip(b'=').decode('ascii')
def pq_verify(sd_jwt_header: str, sd_jwt_payload: str, pq_sig: str, public_key: MLDSAPublicKey) -> bool:
jws_input = f"{sd_jwt_header}.{sd_jwt_payload}".encode('ascii')
fingerprint = hashlib.sha256(jws_input).digest()
# Re-pad for standard base64 decoding
padding = '=' * (4 - len(pq_sig) % 4) if len(pq_sig) % 4 else ''
sigma = base64.urlsafe_b64decode(pq_sig + padding)
if len(sigma) != 3309:
return False
try:
public_key.verify(sigma, fingerprint)
return True
except Exception:
return False
# Install: pip install cryptography
5.3 Go and Rust
Go: github.com/cloudflare/circl/sign/mldsa/mldsa65 from Cloudflare's CIRCL library.
Rust: ml-dsa crate from the RustCrypto organization (pure Rust, no-std compatible).
In both languages: compute the SHA-256 digest of the JWS Signing Input (the
Base64url(header).Base64url(payload) ASCII string), then pass the 32-byte digest as
the message argument to the ML-DSA-65 sign or verify function. Encode the resulting signature bytes
as Base64url (RFC 4648 §5, unpadded) for the pq_sig header parameter value.
5.4 Performance
| Operation | Approx. latency (modern x86-64 or Apple M-series) |
|---|---|
| Key generation | ~0.5 ms |
| Sign | ~0.7 ms |
| Verify | ~0.3 ms |
These latencies are negligible relative to network round-trip times in AP2 payment flows. Throughput exceeding 1,000 operations per second per core is achievable.
§6 Backward Compatibility
6.1 Guarantees for Existing Verifiers
Existing AP2 SD-JWT verifiers that do not implement this profile encounter mandate JWTs with
pq_alg and pq_sig in the JWT header and MUST treat them as follows:
-
Unrecognized header parameters. Per RFC 7515 §4, JWT implementations SHOULD
ignore header parameters whose names are not recognized.
pq_algandpq_sigare subject to the same treatment. -
Classical signature unaffected. The
alg: "ES256"signature remains present and is verified independently. The PQ and classical verification paths are orthogonal. - No wire format change. The mandate remains an SD-JWT. No new serialization format, no CBOR wrapper, no changes to transport or AP2 authorization server configuration.
-
SD-JWT payload and
_sdstructure unchanged. No changes to the mandate payload, selective disclosure claims, or the_sd_algparameter.
6.2 Dual-Signing Transition Protocol
| Phase | Timeline | Behavior |
|---|---|---|
| Phase 0 — Announce | Now | Publish key rotation schedule; update API documentation |
| Phase 1 — Dual-sign optional | 2026 Q2–Q3 | Senders MAY attach pq_alg/pq_sig alongside classical alg |
| Phase 2 — Dual-sign required | 2026 Q4–2027 Q1 | All senders MUST include PQ header parameters; classical signature still verified |
| Phase 3 — PQ-only | 2027 Q2+ | Classical signature deprecated; ML-DSA-65 alone sufficient |
During Phase 3, receivers MAY reject classical-only mandates with a recommended minimum of 6 months' advance notice.
6.3 Key Rotation
ML-DSA-65 key pairs SHOULD be rotated at least annually for long-lived agent identities. Implementations MUST NOT reuse ML-DSA-65 key pairs for any purpose other than mandate signing — in particular, the secret key MUST NOT be used for encryption or key encapsulation.
§7 Security Considerations
7.1 Classical Security Level
ML-DSA-65 targets NIST security Category 3, providing at least 128 bits of classical-equivalent security. This matches or exceeds AES-128 and SHA-256, considered sufficient for sensitive financial data under NIST SP 800-57 through at least 2030.
7.2 Post-Quantum Security Level
The security of ML-DSA-65 rests on the conjectured hardness of the Module Learning With Errors (MLWE) and Module Short Integer Solution (MSIS) problems. No known quantum speedup analogous to Shor's algorithm applies to these problems. The best known quantum attacks (BKZ-based lattice sieving accelerated by Grover's algorithm) achieve only marginal speedups. NIST's Category 3 assessment accounts for these attacks in its parameter selection.
7.3 Hybrid vs. Pure-PQ — When to Use Which
During the transition period, dual-signing (classical + ML-DSA-65) is strictly stronger than either algorithm alone. An adversary capable of breaking ECDSA (via a CRQC) but not ML-DSA-65 cannot forge a dual-signed mandate. Conversely, an adversary capable of breaking ML-DSA-65 (currently hypothetical) but not ECDSA is also blocked.
Use pure ML-DSA-65 (Phase 3) when:
- Deployment is in a jurisdiction with a hard regulatory mandate for PQ-only cryptography.
- All verifier implementations have confirmed ML-DSA-65 support and no classical-only fallback path exists.
- Mandate size constraints make carrying dual signatures infeasible.
Use dual-signing (Phases 1–2) when:
- Some verifiers in the ecosystem are not yet PQ-capable.
- Regulatory requirements mandate documented transition rather than immediate cutover.
- Defense-in-depth against ML-DSA-65 implementation defects is desired.
7.4 Deterministic Signing and Nonce Elimination
Unlike ECDSA, ML-DSA-65 does not require a per-signature random nonce k. The
catastrophic nonce-reuse vulnerabilities that have historically caused private key extraction in
ECDSA deployments are not present in ML-DSA-65. This is a meaningful operational improvement for
automated agent systems where entropy sources may be constrained or poorly seeded.
7.5 Side-Channel Resistance
Implementations MUST use a constant-time ML-DSA-65 implementation. The @noble/post-quantum
library employs constant-time arithmetic. Implementers using other libraries MUST verify equivalent
guarantees before deploying in production.
7.6 Signature Malleability
ML-DSA-65 signatures are non-malleable. The verification algorithm validates the full internal structure of the signature encoding, making malleability infeasible. This is an improvement over certain ECDSA encoding variants where signature malleability has caused double-spend attacks.
7.7 Key Reuse Across Protocols
AP2 agents MUST NOT reuse their ML-DSA-65 key pair for any purpose other than AP2 mandate signing. Using the same key for both signing and encryption (or key encapsulation) introduces cross-protocol attacks. The ML-DSA-65 secret key format is incompatible with ML-KEM (NIST FIPS 203) by design.
7.8 Input Length Enforcement
Receivers MUST enforce input length limits on the pq_sig header parameter. A
conformant implementation MUST reject any pq_sig value that decodes to a byte length
other than exactly 3,309 bytes (ML-DSA-65 mode) or 3,373 bytes (hybrid mode). When resolving ML-DSA-65
public keys for verification, a RECOMMENDED maximum response body size is 16 KB, preventing buffer
exhaustion attacks via oversized responses.
§8 Open Questions
The following questions are submitted to the FIDO Alliance Payments TWG and the AP2 community for discussion. The authors have views on each but defer to the TWG's design authority.
- Q1 — NIST IR 8547 scope
- Has NIST IR 8547 ipd compliance been discussed in the context of AP2 mandate retention requirements? The 2030 deprecation and 2035 disallowance timeline proposed in the draft creates a planning obligation for regulated financial deployments.
- Q2 — Existing internal PQ work
- Has the AP2 team or the FIDO Payments TWG discussed ML-DSA-65 or any other PQ algorithm internally? The authors want to avoid duplicating work or creating conflicting extension tracks.
- Q3 — Verification failure semantics for unresolvable keys
- If
pq_sigis present and the corresponding public key cannot be resolved at verification time, should the profile mandate hard rejection (fail closed) or permit fallback to classical-only verification with a warning? The authors lean toward hard rejection for regulated deployments but recognize the deployment cost implication during the transition period. - Q4 — Mastercard Verifiable Intent alignment
- The Verifiable Intent contribution going through the Payments TWG is functionally analogous to
the SpendEnvelope construct in the reference implementation: a tamper-evident, user-authorized log
of what an agent is permitted to do. No public schema for Verifiable Intent has been released as
of this writing. Does the TWG see value in aligning the
pq_alg/pq_sigheader parameter extension with the Verifiable Intent document format? - Q5 — Scope: mandate credentials only, or also task results?
- This proposal scopes PQ signing to mandate credentials. A natural follow-on is PQ signing of task result records. Should this profile address task result signing, or should that be a separate follow-on contribution?
- Q6 — Retention-aware mandate field standardization
- The TC4 test vector includes a
retention_metadatafield with HKMA policy context. Should the TWG consider standardizing retention metadata fields as part of the mandate credential schema, given their relevance to the HNFL threat model?
§9 References
Normative References
- [FIPS-204]
- National Institute of Standards and Technology. "Module-Lattice-Based Digital Signature Standard."
FIPS Publication 204, August 2024.
https://doi.org/10.6028/NIST.FIPS.204 - [RFC-8785]
- Rundgren, A., Jordan, B., Erdtman, S. "JSON Canonicalization Scheme (JCS)." RFC 8785, June 2020.
https://www.rfc-editor.org/rfc/rfc8785 - [RFC-7515]
- Jones, M., Bradley, J., Sakimura, N. "JSON Web Signature (JWS)." RFC 7515, May 2015.
https://www.rfc-editor.org/rfc/rfc7515 - [RFC-2119]
- Bradner, S. "Key words for use in RFCs to Indicate Requirement Levels." RFC 2119, March 1997.
https://www.rfc-editor.org/rfc/rfc2119 - [RFC-8174]
- Leiba, B. "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words." RFC 8174, May 2017.
https://www.rfc-editor.org/rfc/rfc8174 - [RFC-4648]
- Josefsson, S. "The Base16, Base32, and Base64 Data Encodings." RFC 4648, October 2006.
https://www.rfc-editor.org/rfc/rfc4648 - [AP2-SPEC]
- AP2 Working Group. "AI Payments Protocol v2 Specification."
https://github.com/google-agentic-commerce/AP2
Informative References
- [NIST-IR-8547]
- National Institute of Standards and Technology. "Transition to Post-Quantum Cryptography Standards."
NIST IR 8547, 2024 (initial public draft).
https://csrc.nist.gov/pubs/ir/8547/ipd - [HKMA-QPI]
- Hong Kong Monetary Authority. "Quantum Preparedness Index (QPI)." Announced February 3, 2026.
https://www.hkma.gov.hk/ (verify canonical URL against HKMA primary source before citing) - [PSD2-ART69]
- European Parliament and Council. "Directive (EU) 2015/2366 on Payment Services in the Internal
Market (PSD2)." Article 69 — record-keeping for payment service providers. OJ L 337, 2015.
https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32015L2366 - [NOBLE-PQ]
- Miller, P. "@noble/post-quantum: Post-quantum cryptography for JavaScript."
https://github.com/paulmillr/noble-post-quantum - [CIRCL]
- Cloudflare. "CIRCL: Cloudflare Interoperable Reusable Cryptographic Library."
https://github.com/cloudflare/circl - [HARVEST-NOW]
- Mosca, M. "Cybersecurity in an Era with Quantum Computers: Will We Be Ready?" IEEE Security & Privacy, 2018.
- [PQSAFE-IMPL]
- PQSafe. "PQSafe AgentPay Reference Implementation." Apache-2.0.
https://github.com/PQSafe/pqsafe - [NIST-CSOR]
- National Institute of Standards and Technology. "Computer Security Objects Register — Algorithm Registration."
https://csrc.nist.gov/projects/computer-security-objects-register/algorithm-registration
§10 Authors and License
Authors
Raymond Chau
PQSafe AgentPay
raymond@pqsafe.xyz · github.com/PQSafe/pqsafe
Tris Au Yeung
PQSafe AgentPay
License
This specification document is released under the Apache License, Version 2.0. The reference implementation at github.com/PQSafe/pqsafe is also released under Apache-2.0. Copyright 2026 PQSafe.
Acknowledgments
The authors thank the AP2 working group for AP2 v0.1's foundational design choices — particularly
the mandate of RFC 8785 JCS canonicalization, which simplifies this extension substantially. Thanks
to Paul Miller for the @noble/post-quantum library and to the NIST post-quantum
cryptography standardization team for FIPS 204.
Document History
| Revision | Date | Summary |
|---|---|---|
| 00 | 2026-04-28 | Initial draft |
| 01 | 2026-05-01 | Full specification landing page; §3 wire format examples with JCS canonical form walkthrough; 5 test vector cases TC1–TC5; §6 backward-compatibility guarantees; §7 security considerations including hybrid vs. pure-PQ tradeoff; open questions expanded to 6 |
| 02 (v9) | 2026-05-01 | Core structural change: mechanism shifted from JSON payload fields (pq_signature, pq_algorithm) to JOSE header parameters (pq_alg, pq_sig) — realignment with AP2 v0.2 SD-JWT architecture. HashML-DSA signing input corrected to JWS Signing Input per RFC 7515 §7.2.1. Hybrid profile defined as single pq_alg value with explicit byte-concatenation format. Bank of Israel removed; PSD2 Article 69 retained. All encoding confirmed as Base64url (RFC 4648 §5, unpadded). NIST IR 8547 consistently marked as initial public draft. |