Canonical Payload
ML-DSA-65 verification binds to deterministic canonical bytes. The signer and PQ Cloud must verify the same byte sequence; JSON formatting, field order or an application-side reconstruction must not silently change what was signed.
V1 field order
domain project_id subject action key_version nonce body_sha256 claims
The current implementation serializes the payload in this order:
domain=<value>|project_id=<value>|subject=<value>|action=<value>|key_version=<value>|nonce=<value>|body_sha256=<value>|claims=<sorted key=value pairs>
claims are ordered by key before serialization. The ML-DSA-65 context used by the current verifier is RANNTA-PQ-CLOUD-ML-DSA-65-V1.
String and delimiter boundary
V1 serializes application string values as UTF-8 text and does not add an escaping layer. The characters |, , and = are structural delimiters in the canonical form. Integrators should therefore supply delimiter-free identifiers or encode arbitrary application values into an unambiguous representation such as hex or base64url before placing them in canonical fields or claims. PQ Cloud does not reinterpret or normalize those application values after signing.
Required negative test — recorded PASS
A one-byte change in the signed canonical message must fail verification. The final Phase 2 run verified the original payload locally and in production, then changed the payload without re-signing. Both the customer-side verifier and production /v1/hybrid/verify rejected the mutation.
Valid · PASSLocal one-byte mutation
Rejected · PASSProduction original
Valid · PASSProduction one-byte mutation
Rejected · PASS
Versioning
The canonical format and signing context are explicitly V1. A breaking change to field order, delimiters or signing context must be introduced as a new canonical/signing version rather than silently changing V1 bytes.
Implementation identity
- Canonicalizer source:
crates/pq-core/src/lib.rs. - Commit that introduced the current canonicalizer:
9089fe8839ebcd26b7bf0a8634041163cde3890f. - Release build ID:
RANNTA-PQ-CLOUD-PHASE2-20260911-01. - Final E2E harness commit:
0ddce50395b6b9ffd6e03d4aa882e1fe50d5d4b4.
Integrator requirements
- Do not sign a display string and submit a separately reconstructed payload.
- Bind
project_id,subject,action,key_versionand a uniquenonceto the authorization attempt. - If request-body integrity matters, place the application-defined SHA-256 value in
body_sha256before signing. - Treat canonicalization mismatch as Rejected; do not retry by weakening or dropping fields.