A cosignature is a statement by a cosigner that it verified the consistency of some Merkle Tree hash in a transparency log. This hash may be computed over the entire tree or a general subtree. Log clients can verify a quorum of cosignatures to prevent split-view attacks before trusting an inclusion proof. A cosigner may make additional statements relating to the subtree. Log clients that know about this can then be assured of additional cosigning properties.
Conventions used in this document §
Data structures are defined according to the conventions laid out in Section 3 of RFC 9846.
The base64 encoding used throughout is the standard Base 64 encoding specified
in RFC 4648, Section 4, with = padding. Implementations MUST use the
canonical base64 according to RFC 4648, Section 3.5.
U+ followed by four hexadecimal characters denotes a Unicode codepoint, to be
encoded in UTF-8. 0x followed by two hexadecimal characters denotes a byte
value in the 0-255 range. || denotes concatenation.
A time represented as a POSIX timestamp is the time converted to seconds since the Epoch, as defined by POSIX.1-2024.
A non-negative integer encoded as an ASCII decimal is a string containing the
base-10 representation of the integer with no extra leading zeros. Zero is
encoded as 0, not the empty string.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 RFC 2119 RFC 8174 when, and only when, they appear in all capitals, as shown here.
Cosigners §
Cosignatures are generated by cosigners. A cosigner signs Merkle Tree hashes from one or more transparency logs. For each (cosigner, log) tuple, all hashes from that log, signed by that cosigner, MUST be consistent with each other.
Different cosigners may take different roles in a transparency application, with different mechanisms for ensuring consistency with the log. For example:
- The operator of a log trusts its copy of the log and signs hashes by loading or computing them directly.
- A witness verifies consistency proofs against a saved copy of the latest tree head seen so far.
A cosigner is identified by its cosigner name. A cosigner name is a non-empty
UTF-8 string, at most 255 bytes long and containing no Unicode spaces, plus
characters (U+002B), or control characters (those below U+0020). It SHOULD be a
schema-less URL controlled by the cosigner, such as example.com/cosigner42.
This is only a recommendation to avoid collisions. Clients MUST NOT assume that
cosigner names follow this format, or that the URL corresponds to a reachable
endpoint.
For ecosystems that use OIDs for identification, the cosigner name MAY be the
string oid/ followed by an OID in dotted decimal form.
Logs are also identified by strings following the above construction. These identifiers are known as the log's origin. The same string MAY be both a log origin and a cosigner name.
Cosigners maintain private keys for one or more of the cosigner algorithms defined in this document. Clients are configured with tuples of (cosigner name, cosigner algorithm, public key), specifying the particular key they accept for each supported cosigner.
This document specifies two related types of cosignatures:
-
Checkpoint cosignatures, which attest to the entire latest complete state of the log, as of some time.
-
Subtree cosignatures, which attest to the state of some subtree, without reference to any time.
Some of the algorithms defined in this document can generate both types, while others can only generate one.
Checkpoint cosignatures §
A checkpoint cosignature signs over:
- A timestamp, the time at which the cosignature was generated, as a POSIX timestamp.
- A log origin, identifying the log being cosigned.
- A tree size, the number of leaves in the log.
- A tree hash, the Merkle Tree hash of the log, at the specified tree size.
The result is a byte string concatenating:
- The timestamp as an eight-byte, big-endian integer.
- The signature value as outputted by the cosignature algorithm.
Verifiers MAY reject checkpoint cosignatures with timestamps in the future.
Semantically, a checkpoint cosignature is a statement that the specified tree hash is consistent with all other historical views of the log observed by the cosigner. It is also a statement that, as of the specified time, this is the largest consistent tree the cosigner has observed for the log. Any additional statements by the cosigner, described below, also apply.
These semantics imply that checkpoint cosignatures MUST be monotonically increasing. That is, if a cosigner has generated a checkpoint cosignature for a log with tree size N, all of the cosigner's future checkpoint cosignatures for that log MUST have tree size at least N.
Subtree cosignatures §
A subtree cosignature signs over:
- A log origin, identifying the log being cosigned.
- A subtree of the log, defined by its
startandendvalues. - A subtree hash, the Merkle Tree hash of the specified subtree of the log.
The result is a byte string containing the signature value as outputted by the cosignature algorithm. Unlike a checkpoint cosignature, there is no timestamp.
Semantically, a subtree cosignature is a statement that the subtree with the specified hash is consistent with all other historical views of the log observed by the cosigner. No statement is made about the age of the subtree. Any additional statements by the cosigner, described below, also apply.
Additional statements §
A cosigner MAY make additional statements about a subtree or checkpoint. These additional statements need to be communicated out of band to those defining trust policies on cosigners. A given cosigner name MUST imply a single set of statements.
These statements MUST include the base cosignature semantics, and MAY include other statements that are non-conflicting. Examples of non-conflicting statements include "I also mirrored the log up until the checkpoint size" and "I certify the SAN ←→ public key associations in the log leaves". See c2sp.org/tlog-mirror for an example.
Cosignature algorithms §
This section specifies two cosignature algorithms: one based on ML-DSA-44, and one based on Ed25519. ML-DSA-44 SHOULD be used for new deployments.
Unlike the Ed25519 algorithm, the ML-DSA-44 algorithm is secure against quantum computers. Moreover, it commits to the cosigner's name, and supports both types of cosignatures. The ML-DSA-44 parameter set was selected because, at NIST Level 2, it provides some margin beyond the 128-bit security level.
ML-DSA-44 §
An ML-DSA-44 cosigner is configured with an ML-DSA-44 private key. ML-DSA-44 cosigners can generate both checkpoint cosignatures and subtree cosignatures.
In both cases, the signature value is computed according to FIPS 204, over a
CosignedSubtree structure, defined below:
struct {
uint8 label[12] = "subtree/v1\n\0";
opaque cosigner_name<1..2^8-1>;
uint64 timestamp;
opaque log_origin<1..2^8-1>;
uint64 start;
uint64 end;
uint8 hash[32];
} CosignedSubtree;
cosigner_name is the cosigner name.
timestamp is the input timestamp for checkpoint cosignatures and zero for
subtree cosignatures.
log_origin is the log origin.
start and end specify the subtree being signed. If generating a checkpoint
cosignature, the subtree is defined by setting start to zero and end to the
tree size.
hash is the root hash of the subtree being signed.
If start is non-zero, timestamp will always be zero. If start is zero,
timestamp may be zero or the current time, depending on whether the subtree
denoted by start and end is signed as the latest checkpoint, or an arbitrary
subtree.
Ed25519 §
An Ed25519 cosigner is configured with an Ed25519 private key. Ed25519 cosigners can only generate checkpoint cosignatures and not subtree cosignatures.
In addition to the parameters defined above, Ed25519 checkpoint cosignatures take an optional list of extension lines as input. Each extension line MUST be a non-empty UTF-8 string and MUST NOT include control characters (those below U+0020). They are opaque and no statement is made about extension lines with the signature.
The signature value is computed according to RFC 8032 over the concatenation of the following lines. Each line is terminated by a newline (U+000A):
- The fixed string
cosignature/v1, which provides domain separation - The string
time, a single space (0x20), and the timestamp encoded as an ASCII decimal - The log origin
- The tree size, encoded as an ASCII decimal
- The base64 encoding of the tree hash
- Zero or more lines, each containing one extension line, in order.
The following is an example of the signature input. The format after the first two lines is identical to the format used in c2sp.org/tlog-checkpoint.
cosignature/v1
time 1679315147
example.com/behind-the-sofa
20852163
CsUYapGGPo4dkMgIAUqom/Xajj7h2fB2MPA3j2jxq2I=
A cosigner operator that operates multiple Ed25519 cosigners (e.g. with distinct additional statements, see above) MUST use distinct public keys for each cosigner. The Ed25519 signed message format doesn't commit to the cosigner name, so the same public key can't be used across multiple cosigners.