Skip to main content

Module note

Module note 

Source
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:

  • 0x06 is single-algorithm timestamped ML-DSA-44 (sub)tree cosignatures (per c2sp.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) and 0x04 (timestamped Ed25519 cosignatures) likewise describe other constructions.
  • 0xfa0xfe are 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).
SignedNote
A parsed signed note: the signed text plus its (still unverified) signature lines.
VerifierKey
A trusted verifier key: the data needed to recognize and check signatures from one key.

Enums§

SignatureType
A note signature algorithm, identified by its C2SP signed-note type 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-note type identifier for the metamorphic-crypto hybrid composite signature (ML-DSA + classical, strict-AND).

Functions§

sign_ed25519
Sign text with a raw Ed25519 seed, producing a Signature line for the given key name.
sign_hybrid
Sign text with a metamorphic-crypto hybrid composite secret key (base64 tag || classical_seed || ml_dsa_seed), producing an additive PQ Signature line for the given key name.