c2sp.org/tlog-cosignature
This is the development version of this specification, rendered from the tip of the main branch; the latest release is v1.1.0.

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:

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:

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:

The result is a byte string concatenating:

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:

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 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.