Expand description
C2SP signed-note parsing, serialization, and verification.
A signed note is UTF-8 text followed by a blank line and one or more signature lines, each of the form:
— <key name> <base64(uint32 key id || signature)>\n(the leading character is an em dash, U+2014, then a space). The text the
signatures cover includes the final newline but not the separating blank
line. This module parses and serializes that wire format byte-for-byte
compatibly with the deployed ecosystem (Go’s sumdb/note, sigsum,
transparency-dev), and verifies classical Ed25519 witness/log signature
lines via the single-source-of-truth primitive
metamorphic_crypto::ed25519_verify.
§Key ids and verifier keys
The 4-byte key id binds a signature to a (name, signature-type, public key) tuple:
key id = SHA-256(key name || 0x0A || signature type || public key)[:4] (big-endian u32)A verifier key (vkey) is the text encoding a verifier shares:
<key name>+<hex(key id)>+<base64(signature type || public key)>§Additive hybrid post-quantum signatures (Slice 3)
The model is intentionally multi-signature and signature-type-tagged. A note
may carry any number of signature lines, and verifiers MUST ignore lines
from unknown keys. This is exactly what lets an additive hybrid
post-quantum signature line (SignatureType::MetamorphicHybrid) slot in
alongside the classical SignatureType::Ed25519 line with no format
churn: classical C2SP witnesses keep verifying (and co-signing) the Ed25519
line and can still recompute the tree, while our own PQ-aware verifiers and
monitors additionally check the composite line for post-quantum authenticity.
§Signature-type assignment (the central design decision)
The additive PQ primitive is the metamorphic-crypto composite signature
(metamorphic_crypto::sign / metamorphic_crypto::verify): ML-DSA
(FIPS 204) + a classical partner (Ed25519, or Ed448/ECDSA-P-521 in the
matched suites), strict-AND, with a 1-byte version/suite tag prefixing a
self-describing wire format, signing a length-prefixed context-framed message
(I2OSP(len(ctx),8) || ctx || msg). This construction matches no
C2SP-assigned signed-note signature type:
0x06is single-algorithm timestamped ML-DSA-44 (sub)tree cosignatures (perc2sp.org/tlog-cosignature): one algorithm, a timestamp prefix, and cosignature-specific note semantics. Reusing it would misrepresent our hybrid composite to real ML-DSA-44 cosignature verifiers.0x02(ECDSA) and0x04(timestamped Ed25519 cosignatures) likewise describe other constructions.0xfa–0xfeare reserved for future use by C2SP — not ours to claim.
C2SP provides exactly one correct escape: 0xff, “reserved for signature
types without an identifier byte assigned by this specification”, which it
RECOMMENDS be followed by “a longer identifier that is unlikely to collide”.
We therefore assign our composite the multi-byte type identifier
HYBRID_SIG_IDENTIFIER (0xff followed by a versioned namespace label).
This is forward-interop-safe: a C2SP verifier that doesn’t know our key
simply ignores the line (unknown key), and we never squat an assigned or
reserved byte.
The signature-type identifier participates in the key id and vkey exactly
as the spec describes (key id = SHA-256(name || 0x0A || type id || pubkey)[:4]; vkey = name+hex(id)+base64(type id || pubkey)); the spec’s
formula is defined over the full (multi-byte) type identifier, so nothing in
the key-id/vkey math changes — only the identifier is longer. The composite
public key material carried after the identifier is the metamorphic-crypto
public key bytes (tag || classical_pk || ml_dsa_pk); its leading tag
self-describes the (Suite, SecurityLevel) posture (see
VerifierKey::hybrid_posture_tag), which the Slice-5 policy layer can later
reconcile (declared == observed). The composite signature bytes carried after
the key id are the metamorphic-crypto signature blob verbatim.
§Signing context
The composite signs the note text under the fixed, versioned context
HYBRID_SIG_CONTEXT. This binds a hybrid note signature to its purpose and
is reproduced byte-identically across native Rust, WASM, and the Elixir NIF
(the framing is metamorphic-crypto’s I2OSP(len(ctx),8) || ctx || msg).
Because ML-DSA signing is hedged/randomized, composite signature bytes are
not reproducible, but verification is fully deterministic — so our KATs
pin the (deterministic) public key / vkey and lock a stored signature that
SignedNote::verify accepts byte-for-byte.
Structs§
- Signature
- A single signature line parsed from a note (not yet verified).
- Signed
Note - A parsed signed note: the signed text plus its (still unverified) signature lines.
- Verifier
Key - A trusted verifier key: the data needed to recognize and check signatures from one key.
Enums§
- Signature
Type - A note signature algorithm, identified by its C2SP
signed-notetype identifier (one or more bytes).
Constants§
- HYBRID_
SIG_ CONTEXT - The fixed, versioned signing context bound into every hybrid composite note
signature (metamorphic-crypto frames it as
I2OSP(len(ctx),8) || ctx || note_text). Changing this label is a breaking change to the hybrid line. - HYBRID_
SIG_ IDENTIFIER - The C2SP
signed-notetype identifier for the metamorphic-crypto hybrid composite signature (ML-DSA + classical, strict-AND).
Functions§
- sign_
ed25519 - Sign
textwith a raw Ed25519 seed, producing aSignatureline for the given key name. - sign_
hybrid - Sign
textwith a metamorphic-crypto hybrid composite secret key (base64tag || classical_seed || ml_dsa_seed), producing an additive PQSignatureline for the given key name.