# Canonicalization and signing

Certificate and vote signing uses **canonical JSON** with:
- keys sorted lexicographically (`sort_keys=True`);
- compact separators `(",", ":")` with no spaces;
- ASCII escaping (`ensure_ascii=True`);
- UTF-8 bytes of that string are the signed message.

Domain separation: the signed message is prefixed by a domain tag, e.g.
`CAIN42/PBFT/MEMBERSHIP/v1`, `CAIN42/PBFT/AUTHORIZATION-CERTIFICATE/v1`,
`CAIN42/PBFT/COMMIT/v1`, `CAIN42/PBFT/PREPARE/v1`. Readers must reject a vote of
one type presented as another (COMMIT vs PREPARE).

Signature algorithm: **Ed25519 (RFC 8032)** over the SHA-256 of the canonical
message where the certificate code specifies a digest, or directly over the domain
tag + canonical body where specified. The standalone verifier
`verify_pbft_qc_bundle.py` recomputes every signature from the bytes in
`QUORUM_CERTIFICATE.json`; run it to see the exact bytes it validates.

Not signed / not implemented: fields marked `"UNBOUND"` in a certificate body
(e.g. an expiry of `"UNBOUND"`) carry no signed bound; this bundle states that
rather than inventing a value.
