Colophon v0: Agent Signatures for Email
Draft v0-06, 2026-11-09. For review. Not for production use. Editor: Jorge Vivas (Fluxtopus). Licence: Apache 2.0.
Abstract#
Colophon lets an AI agent sign the email it sends.
A receiver can then check three things, from DNS, the message, and its own delivery facts (when the message arrived, which DKIM signatures passed, and which mailbox it was delivered to):
- which agent wrote the message;
- which domain issued that agent and stands behind it;
- what the domain says the agent acts for: its principal, as far as the principal allows, and a short statement of its authority.
Colophon adds one header to an ordinary email. A receiver that does not know Colophon sees a normal message.
v0 is the identity layer only. Tasks, offers and agreements come later, in a separate document, Colophon Tasks, built on this one.
Contents#
- Introduction
- Conventions and terms
- Overview
- Domain keys in DNS
- The agent credential
- The signed message
- Verifying
- First contact and receiver control
- Security considerations
- Privacy considerations
- Versioning and extensions
- Conformance
- Registrations
- Relation to A2A and Colophon Tasks
- Open issues
Appendices:
- A. Worked example
- B. Test vectors
- C. Formal model
- D. Decision log
- E. Changes
1. Introduction#
Agents already send email: they book, buy, schedule, follow up and hand work to one another. A receiver cannot tell an agent's message from a person's. If it is an agent's, the receiver cannot tell whose agent it is or what it may do.
DKIM proves that a domain's mail system sent a message. It does not say which agent of that domain wrote it, who that agent acts for, or what it is allowed to do.
Colophon adds that. It is built on what email already has:
- DKIM, for the domain;
- DNS, for keys;
- MIME, for the message;
- JOSE (JWS, JWK, EdDSA) and JCS, for signatures.
It needs no central registry, no HTTPS fetch, and no service run by any one company.
1.1 Principles#
- Plain email first. A signed message is a valid, readable email. A receiver that ignores Colophon loses nothing.
- Verifiable by anyone. Any implementation checks a message from DNS, the message itself, and the receiver's own delivery facts: arrival time, DKIM result and mailbox.
- Built on what exists. DKIM, DNS, MIME and JOSE before anything new.
- Receiver in control. The receiver decides who gets in and what to act on. A sender cannot force its way in.
- Agents act on a person's authority. A person delegates authority to an agent, and the agent acts on its own within it. Every credential traces to that person.
- Least disclosure. An agent reveals who it acts for only as much as its principal allows.
- Small core. v0 does signed messages and first contact. Everything else is an extension or a later document.
1.2 What v0 does not do#
v0 has:
- no task states;
- no offers or agreements;
- no machine-readable request format;
- no discovery or directory of agents;
- no payments;
- no transport other than email.
It does not judge whether an agent is trustworthy, and it does not enforce what an agent may do. It states who spoke, for whom, with what stated authority, and whether the message is intact.
2. Conventions and terms#
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.
| Term | Meaning |
|---|---|
| Agent | Software that sends email on someone's behalf and signs it with its own key. |
| Principal | The person, or organisation, whose authority the agent carries. |
| Issuer | The domain that issues credentials to agents. It is the host of the agent's address, and its mail system sends the agent's mail. Usually it is a mail provider acting for the domain. |
| Domain key | An Ed25519 key pair of the issuer. The public key is published in DNS under a selector. |
| Selector | A DNS label that names one domain key, as in DKIM. |
| Agent key | An Ed25519 key pair held for one agent address. |
| Credential | A short-lived statement, signed with a domain key. It binds an agent key to one address, a principal disclosure and an optional scope. |
| Agent signature | A signature by the agent key over the message's signing input. |
| Signing input | A JSON object built from the message. Signer and verifier build it the same way. |
| Signer | The software that signs a message with an agent key, before the message leaves the issuer's mail system. |
| Verifier | The software at the receiver that checks a message. |
| Receiver | The mail system and mailbox that accepted the message. |
| Arrival time | The moment the receiver's mail server accepted the message in SMTP (the end of the DATA phase). |
| Verdict | The verifier's result: verified, or not verified with a reason. |
Values written in code are exact. "JCS" is the JSON Canonicalization Scheme (RFC 8785). Times are integer seconds since the Unix epoch, UTC.
"base64url" is the URL-safe alphabet without padding (RFC 4648 §5), decoded strictly. A value with padding, with + or /, with any other character, or with unused bits that are not zero is invalid. This holds everywhere Colophon uses base64url, the DNS record included.
A grammar written as a regular expression must match the whole value. A value with anything before or after the match, a line break included, does not match.
The figures are informative. Where a figure and the text differ, the text is right.
3. Overview#
A Colophon message carries two proofs. A receiver believes "agent X of acme.example" only when both hold.
- The domain's mail system sent it. The message passes DKIM, and the DKIM signing domain is exactly the
Fromhost. - This agent wrote it. The message carries a credential and an agent signature in one header,
Colophon-Signature:- the credential is signed by a domain key that the
Fromhost publishes in DNS; - the agent signature, made with the key the credential names, covers the message's addresses, subject, text and attachments.
- the credential is signed by a domain key that the
The issuer reissues each credential automatically, at least once a day. To revoke an agent, the issuer stops reissuing its credential. The credential then lapses within 26 hours: its 24-hour life plus a 2-hour grace for mail in transit.
Revocation does not end a stolen agent key by itself. Every message carries a credential, so whoever holds the key can reuse one. A stolen key stays usable until the issuer stops certifying it, and for up to 26 hours after (§5.2, §9).
The verifier checks the message as its own mail server accepted it. The result is verified, or not verified with a reason. A message that is not verified is still delivered as plain email. Colophon never blocks delivery.
4. Domain keys in DNS#
4.1 The record#
An issuer publishes each domain key as a TXT record at:
<selector>._colophon.<host>
Here <host> is the domain of the agent addresses: the right-hand side of the @.
The record is a tag list in the style of DKIM (RFC 6376 §3.2):
v=COLOPHON0; k=ed25519; p=<base64url of the 32-byte Ed25519 public key>
vMUST beCOLOPHON0.kMUST beed25519.pis the raw 32-octet public key (RFC 8032), in base64url. An emptyp=means the key is revoked.- The record is parsed like the header (§6.2): all whitespace is removed, then it is a list of
name=valueitems separated by;. Unknown tags are ignored. A record with a duplicate tag is not a Colophon record. - If a TXT record has several character strings, they are joined with nothing between them before parsing, as in DKIM.
- A record is a string of octets, and is never decoded as text. A record with an octet above 0x7F is not a Colophon record (§7.4).
A selector is one DNS label: lowercase letters, digits and hyphens, from 1 to 63 characters, not starting or ending with a hyphen. Its grammar is [a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?.
Other TXT records may share the name. §7.4 says exactly which record a verifier uses, and what it concludes when there is none, or more than one. An issuer MUST publish exactly one Colophon record at each selector.
4.2 Rotation and removal#
- Several selectors MAY be live at once, so keys rotate as DKIM keys do.
- A retired selector SHOULD stay published for at least 7 days after the last credential it signed.
- A compromised domain key MUST be removed, or set to an empty
p=, at once. - The record's TTL SHOULD be at most 1 hour. The TTL is the most time a removal can take to reach verifiers.
4.3 Domains run by a provider#
A domain whose mail a provider runs SHOULD publish a small fixed set of selectors, for example s1 and s2. Each is a CNAME to a name the provider controls:
s1._colophon.customer.example. CNAME s1.customer-example.colophon-keys.provider.example.
s2._colophon.customer.example. CNAME s2.customer-example.colophon-keys.provider.example.
The provider then rotates keys under those names, and the customer never touches DNS again. This is how many providers handle DKIM.
A single CNAME at _colophon.<host> does not work: a CNAME does not redirect the names below it (RFC 1034 §3.6.2).
5. The agent credential#
5.1 Format#
A credential is a JWS in compact serialization (RFC 7515), with an Ed25519 signature (EdDSA, RFC 8037) by a domain key.
Protected header
| Member | Value |
|---|---|
alg |
EdDSA |
typ |
colophon-cred+jwt |
kid |
the selector of the domain key |
A verifier MUST reject a protected header that contains crit. It ignores any other member.
Claims (payload)
| Claim | Required | Value |
|---|---|---|
iss |
yes | A string: the issuer host, in lowercase. It MUST equal the host of from. |
sub |
yes | A string: the agent ID, unique within the issuer. Grammar: [A-Za-z0-9._-]{1,64}. |
from |
yes | A string: the one address the agent sends from, already in canonical form (§6.4). |
cnf |
yes | An object: {"jwk": {"kty": "OKP", "crv": "Ed25519", "x": "<base64url public key>"}}, where x decodes to 32 octets that are a valid public key (§6.5). This is the agent key, as a proof-of-possession key (RFC 7800). |
principal |
yes | An object: the disclosure (§5.3). |
scope |
no | A string: a statement of the agent's authority, in plain words (§5.4). |
iat |
yes | An integer: the time the credential was issued. |
exp |
yes | An integer: the time it expires. exp - iat MUST be more than 0 and at most 86,400 (24 hours). |
Each claim MUST have the JSON type its row gives, and match its grammar where one is given. Otherwise the credential is invalid (§7.2 step 13). A string is never read as a number, nor a number as a string.
JSON rules. The payload, and the protected headers of both JWS in a message, MUST be I-JSON (RFC 7493), with these rules made exact:
- member names are unique;
- every number is an integer between -(2^53 - 1) and 2^53 - 1, written without a fraction or an exponent.
1.0,1e3,NaNandInfinityare not allowed; - no string, member names included, holds a surrogate (U+D800 to U+DFFF) or a noncharacter (U+FDD0 to U+FDEF, and every code point that ends in FFFE or FFFF), written as it is or as a
\uescape (RFC 7493 §2.1). A lone surrogate escape such as\ud800is not allowed. An escaped surrogate pair stands for the one character it encodes, and is allowed if that character is.
JSON that breaks these rules makes the message malformed (§7.2 step 7).
The payload SHOULD be serialized with JCS. A verifier does not depend on this: it checks the signature over the bytes as sent.
The whole credential in compact form MUST be at most 2,048 octets, and the whole header value at most 2,560 (§6.2). Each field cap in this section is a maximum on its own. Not every field can be at its cap at once: a credential with every field at its maximum, made of characters that JSON escapes, comes to about 2,600 octets. The total caps bind, and the issuer MUST keep within them. The credential in Appendix A is 590 octets.
A verifier MUST ignore claims it does not know. Claim names without a colon (:) are reserved for this specification. An extension claim name MUST contain a colon, for example example.com:tier (§11).
5.2 One credential per address#
A credential names exactly one address. Each address has its own credential, agent ID and agent key, in every disclosure mode. To a receiver, an agent is an address.
The issuer:
- MUST NOT give two addresses the same
subor the same agent key; - SHOULD keep an address's
substable across reissues, so a counterparty sees the same agent each time; - SHOULD replace the agent key regularly, for example every 30 days, and at once when it may have been compromised. A new key under the same
subis the same agent to a counterparty. Regular replacement bounds how long an unnoticed theft lasts (§9). This is guidance: verifiers check nothing about a key's age; - decides how it groups addresses that belong to one agent. That grouping is outside the protocol.
5.3 Principal disclosure#
principal is one of three objects. No other members are allowed in them.
| Mode | Object | What the receiver learns |
|---|---|---|
| withheld | {"mode": "withheld"} |
The issuer says it knows the principal. Nothing more. This is the default. |
| pseudonymous | {"mode": "pseudonymous", "id": "<id>"} |
A pseudonym for the principal that only this counterparty sees. Grammar: [A-Za-z0-9._-]{1,64}. |
| named | {"mode": "named", "name": "<name>"} |
The principal's name or address, as the issuer attests it. 1 to 128 UTF-8 octets, without the characters §5.4 forbids. |
Rules for issuers:
- No credential without a principal. An issuer MUST NOT issue a credential it cannot trace to the principal whose authority it carries. Withheld mode hides that link from the receiver. The link must still exist.
- The principal chooses. Anything above withheld is the principal's choice.
- Pseudonyms are pairwise. In pseudonymous mode, the agent writes to each counterparty from its own address, so it has its own credential, agent ID, agent key and
idfor each (§5.2). The issuer MUST NOT reuse anidacross counterparties. What counts as one counterparty is the issuer's choice. - Only the issuer raises disclosure. Disclosure is set by the credential. A sender cannot raise it per message.
Email's own headers limit what a mode can hide. A From address that names the principal names them whatever the credential says. Anonymity needs an alias address.
A disclosed principal is only as reliable as the issuer that attests it.
5.4 Scope#
scope is an optional statement, in plain words, of what the agent may do. For example: "Books restaurant tables for one person, up to $500 per booking."
- 1 to 280 UTF-8 octets.
- None of these characters: the controls U+0000 to U+001F and U+007F to U+009F, and the bidirectional controls U+061C, U+200E, U+200F, U+202A to U+202E and U+2066 to U+2069. They can make text display in an order other than the one signed. Other format characters, such as U+200D ZERO WIDTH JOINER, are allowed: emoji need them.
- It is part of the credential, so the domain signs it and the agent cannot change it per message.
- A change takes effect only as credentials are replaced. Messages under an earlier credential still verify, with the earlier scope, until that credential lapses: up to 26 hours.
Colophon never decides whether a message fits its scope. A receiver shows the scope and applies its own policy.
5.5 Issuing#
- An issuer SHOULD issue credentials that last about 24 hours, and reissue them before they expire.
- An issuer MUST certify only an agent key it has proof of possession for, or one it holds itself.
- Before revealing a withheld principal, to whom and when, an issuer follows law and its own terms. That is outside the protocol.
6. The signed message#
6.1 Message layout#
A signed message MUST have one of two layouts.
- The whole message is one
text/plainpart, not markedContent-Disposition: attachment. - The top level is
multipart/mixed, and:- its first child is
text/plainand is not markedContent-Disposition: attachment. This is the body. - every other child is a single part, neither
multipart/*normessage/*, withContent-Disposition: attachment. These are the attachments.
- its first child is
Nothing else is allowed. In particular, a signed message has no text/html alternative and no nested multiparts. HTML is what a person would see in place of the signed text, and it is what gateways rewrite most.
Media types, subtypes, parameter names, the Content-Disposition value and transfer encodings are compared case-insensitively. So are the charset and format values. A missing Content-Type means text/plain (RFC 2045 §5.2).
The body part:
- MUST use charset
utf-8orus-ascii, by exactly those names. A missing charset meansus-ascii. Aliases such asutf8are not accepted. - MUST NOT use
format=flowed. Verifiers do not undo it.
Every part MUST use one of the transfer encodings 7bit, 8bit, binary, quoted-printable or base64. A missing Content-Transfer-Encoding means 7bit. For the body, quoted-printable or base64 is RECOMMENDED, because they survive relays that rewrap long lines.
A forwarded email cannot be a message/* attachment: libraries serialize those parts differently. In v0 it is quoted in the text, or attached under another type.
In every part, the top level included, Content-Type, Content-Transfer-Encoding and Content-Disposition each appear at most once, and Content-Type is type/subtype, then optionally ; and parameters. A part that breaks this makes the message malformed. Of Content-Disposition, only the value before the first ; is read.
Header sections. The message's header section, and each part's, is read line by line. A line ends at CRLF, or at LF alone. Each line is either a header field, which starts with a name of printable ASCII characters other than : followed by : (RFC 5322 §2.2), or a continuation of the field before it, which starts with SP or HTAB. The first empty line ends the header section. Any other line, a continuation with no field before it, or a CR that is not followed by LF, makes the message malformed. In a body, a CR alone is data: in the text it ends a line (§6.4).
Parameters. Verifiers read parameters only from the Content-Type of the top level and of the body part. There they follow RFC 2045 §5.1, made exact:
- each parameter is
;, a token,=, then a token or a quoted string. SP and HTAB are allowed around each of these. Comments are not. - empty items, such as a trailing
;, are ignored. In a quoted string, a backslash and the character after it stand for that character. - a parameter without
=, a value that is neither a token nor a quoted string, and a parameter name that appears twice make the message malformed. Parameter names compare case-insensitively. - the RFC 2231 forms of
boundary,charsetandformat, such ascharset*=utf-8''utf-8orboundary*0, are not used in v0. Where the verifier reads that parameter, a message with one breaks the layout:boundaryin amultipart/mixedtop level, andcharsetandformatin the body part. Elsewhere they are ignored like other RFC 2231 parameters, for examplecharset*in amultipart/mixedtop level.
Of an attachment's Content-Type, only type/subtype is read, so its parameters, a file name in any form included, never change the verdict.
Multipart. A multipart/mixed top level follows RFC 2046 §5.1.1, strictly:
boundaryis 1 to 70 characters of RFC 2046'sbchars, and does not end in SP. A missing or invalid boundary makes the message malformed.- a delimiter line is
--and the boundary, then--for the close delimiter, then optionally SP and HTAB. Any other line that starts with--and the boundary makes the message malformed. - the line end just before a delimiter line belongs to the delimiter.
- there is at least one part, and a close delimiter. A delimiter line directly followed by another delimiter line, a part of zero octets, makes the message malformed.
- text before the first delimiter (the preamble) and after the close delimiter (the epilogue) is ignored. A line in the epilogue that starts with
--and the boundary makes the message malformed. - the top level's transfer encoding is
7bit,8bitorbinary(RFC 2045 §6.4). Any other breaks the layout.
6.2 The Colophon-Signature header#
A signed message carries exactly one Colophon-Signature header field. It is a tag list:
Colophon-Signature: v=0; c=<credential>; b=<agent signature>
- The signer MAY fold the header anywhere, including inside values (RFC 5322 §2.2.3).
- Before parsing, a verifier removes all whitespace: SP, HTAB, CR and LF.
- The value is then a list of items separated by
;. Empty items are ignored. Every other item isname=value: the name matches[a-z][a-z0-9_]*, and the value may be empty. An item without=, a name that does not match (V, for example), or a duplicate name makes the header malformed. Unknown tags are ignored. - With whitespace removed, the value MUST be at most 2,560 octets, and
cat most 2,048. A larger one is malformed. In practice the value is about 700 octets. With the four members of §6.5 alone, a 2,048-octet credential gives a value of about 2,310 octets; the rest of the 2,560 is room for extensions. - The issuer's DKIM signature MUST cover
Colophon-Signature, and SHOULD use relaxed header canonicalization (RFC 6376 §3.4.2) so that refolding in transit does not break DKIM. Verifiers do not check this.
6.3 The signing input#
The signing input is this JSON object, serialized with JCS:
| Member | Value |
|---|---|
v |
0 (a number) |
from |
the canonical From address (§6.4). There MUST be exactly one. |
to |
the canonical To addresses, an array |
cc |
the canonical Cc addresses, an array |
reply_to |
the canonical Reply-To addresses, an array |
subject |
the canonical subject (§6.4) |
text |
base64url of SHA-256 over the canonical text (§6.4), encoded as UTF-8 |
attachments |
an array with one entry per attachment, in MIME order: {"type": ..., "sha256": ...} |
credential |
base64url of SHA-256 over the c tag value after whitespace removal (§6.2): the credential in compact form, as ASCII |
An attachment entry is:
type: the media type and subtype, lowercase, without parameters. A missingContent-Typemeanstext/plain.sha256: base64url of SHA-256 over the part's body after its transfer encoding is decoded, as §6.4 says for the text. For7bit,8bitandbinarythese are the body octets as they appear, line endings included. The line ending just before the next boundary belongs to the boundary (RFC 2046 §5.1.1).
Not covered:
Message-IDandDate, which some providers replace on sending;- display names, which servers re-encode;
- attachment file names, which libraries decode differently. A receiver SHOULD NOT present a file name as part of what the agent signed;
In-Reply-ToandReferences, which servers rewrite, and which nothing in v0 depends on;- all other headers.
DKIM still covers whatever the issuer's DKIM signature lists.
6.4 Canonical forms#
Header fields. A verifier reads From, To, Cc, Reply-To, Subject and Colophon-Signature. Field names are compared case-insensitively. Each value is unfolded first: every line end (§6.1) followed by SP or HTAB is removed. The octets MUST be valid UTF-8 (RFC 6532); otherwise the message is malformed.
Addresses. From, To, Cc and Reply-To use a strict subset of RFC 5322, so that every verifier reads them alike. A value outside the subset makes the message malformed. Verifiers do not repair it.
- The value is a list of mailboxes separated by commas. Each mailbox, with SP and HTAB trimmed at both ends, is either an addr-spec alone, or a display name followed by an addr-spec in angle brackets. The display name may be empty:
<reservations@bistro.example>is a mailbox. - A display name is made of atext,
., SP, HTAB, non-ASCII characters and quoted strings. Anything else in it, an unquoted@for example, is malformed. - An addr-spec is
local@domain, with no whitespace:- the local part is a dot-atom of atext and non-ASCII characters (RFC 6532). A quoted local part, such as
"john doe"@example.com, is malformed; - the domain is two or more labels separated by
.. Each label is 1 to 63 ASCII letters, digits and hyphens, and does not start or end with a hyphen. An internationalized domain MUST be written as A-labels (xn--...). Verifiers do not convert or check A-labels. A non-ASCII domain, a domain literal such as[192.0.2.1], and a trailing dot are malformed.
- the local part is a dot-atom of atext and non-ASCII characters (RFC 6532). A quoted local part, such as
- Outside quoted strings,
(,),:,;,[,]and\are malformed, and so is any<or>beyond one pair per mailbox. This rules out comments, groups (undisclosed-recipients:;included), domain literals and route syntax. An empty member, as ina@x.example, , b@x.example, is malformed too. - Canonical form: lowercase the ASCII letters A to Z in the local part and the domain. Leave all other octets as they are. Apply no Unicode normalization and no other case folding. Two addresses are equal when their canonical forms are equal octet for octet.
- Keep header order. Drop later duplicates.
- If the header is absent, the list is
[]. If it appears more than once, or is empty, the message is malformed.
Subject. The subject is signed raw, as it appears on the wire, the way DKIM's relaxed header canonicalization treats it (RFC 6376 §3.4.2):
- Unfold, as above.
- Each run of SP and HTAB becomes one SP, and SP is trimmed at both ends.
RFC 2047 encoded words are not decoded: =?utf-8?q?caf=C3=A9?= is signed as those 21 characters. The issuer's DKIM signature covers Subject, so the verifier sees the octets the signer saw, and no verifier needs a decoder to agree with another. A receiver decodes the subject only to show it (§9).
An absent Subject is "". More than one Subject makes the message malformed.
Text.
- Decode the body part's transfer encoding, strictly. Attachments are decoded the same way.
quoted-printablefollows RFC 2045 §6.7. A line ends at CRLF or LF. SP and HTAB at the end of a line are removed. A line that then ends in=joins the next. Every other line end decodes to CRLF. An=that is not followed by two hex digits is malformed. Any other octet, an 8-bit one or a CR alone included, stands for itself.base64: remove SP, HTAB, CR and LF, then decode as RFC 4648 §4, padding required. Any other character, or wrong padding, is malformed. Unused bits are ignored.7bit,8bitandbinaryare taken octet for octet. Verifiers do not check that7bitdata is 7-bit.
- Decode the charset to Unicode. Bytes that are invalid in the charset make the message malformed. A byte order mark is text, and is kept.
- Split into lines at CRLF, CR or LF.
- Remove trailing SP and HTAB from each line.
- Remove empty lines at the end.
- Join the lines, ending each with CRLF. Empty text is the empty string.
- Encode as UTF-8. Apply no Unicode normalization.
6.5 The agent signature#
The agent signature is a JWS with a detached payload (RFC 7515 Appendix F), signed with the agent key, EdDSA. The payload is the JCS signing input. It is not sent: the verifier rebuilds it from the message.
It travels as <protected header>..<signature>, the compact form with the payload left out.
Protected header
| Member | Value |
|---|---|
alg |
EdDSA |
typ |
colophon-sig |
iat |
when the signer signed, an integer |
nonce |
a random value, at least 96 bits, in base64url. Grammar: [A-Za-z0-9_-]{16,64}. |
A verifier MUST reject crit, and ignores other members.
The signature is computed as in RFC 7515: Ed25519 over ASCII(BASE64URL(header) || "." || BASE64URL(JCS(signing input))).
Ed25519 verification. Both signatures in a message, the credential's and the agent's, are checked as RFC 8032 §5.1.7 says, with the choices it leaves open made exact. Ed25519 libraries differ on them, and a verifier that is more lenient than these rules accepts signatures that others reject.
- A public key, the domain key or the agent key, is valid when it decodes as RFC 8032 §5.1.3 says, which fails when the encoded y is p (2^255 - 19) or more, and the point is not of small order: 8 times the point is not the identity. The identity key is invalid, for example: under it, the signature R = identity, S = 0 verifies for every message. An invalid domain key is
key_not_found(§7.4). An invalid agent key makes the credential invalid (§5.1). - In a signature R || S, S is less than the group order L, and R is not of small order.
- With k = SHA-512(R || A || M) mod L, the signature verifies when the encoding of [S]B - [k]A equals R, octet for octet. This is the equation without the cofactor. A signature that only the cofactored equation, [8][S]B = [8]R + [8][k]A, accepts does not verify.
These are the rules libsodium applies by default. Signers that follow RFC 8032 meet them.
6.6 Signing#
The signer:
- builds the message in its final form, as it will leave the issuer's mail system. Footers, banners and subject tags are added first, and the subject is encoded as it will be sent.
- picks the credential for the
Fromaddress. It SHOULD NOT sign with a credential that has less than 6 hours left beforeexp, so the message has time to arrive. Verifiers do not check this. - builds the signing input from those bytes, signs it, and adds
Colophon-Signature. - hands the message to the mail system, which adds DKIM with
d=equal to theFromhost.
Anything that changes a covered part after signing makes the message not verified. A relay that appends a footer is the most common case.
7. Verifying#
7.1 Inputs#
A verifier works on four inputs from the receiving system, never from the message:
-
The message as accepted. These are the bytes the receiver's mail server accepted, before anything at the receiver changes them, such as banners, subject tags or rewritten links.
-
The arrival time. This is when the receiver's mail server accepted the message in SMTP. A verifier MUST use it, not the time it happens to run. A queued, delayed or reprocessed message keeps its arrival time.
-
The DKIM results. These come from the receiver's own checks, in one of two ways:
- the verifier checks the message's DKIM signatures itself (RFC 6376);
- the verifier reads an
Authentication-Resultsfield (RFC 8601) added by the receiver's own boundary mail server, identified by its authserv-id. It MUST ignore any such field that came with the message.
A passing signature counts for its
d=domain, reported asheader.d. Thei=identity, reported asheader.i, does not count on its own: it may name a subdomain ofd=.Some boundary servers report only
header.i. A verifier that has onlyheader.ifor a passing signature recovers itsd=from the message. The signature's identity is itsi=tag, decoded from DKIM quoted-printable (RFC 6376 §2.11), or@and itsd=when it has noi=. A field has no identity if its tag list does not parse (RFC 6376 §3.2), a tag appears twice, it has nod=, or itsi=has an=that is not followed by two hexadecimal digits. If exactly oneDKIM-Signaturefield in the message has an identity equal toheader.i, the signature counts for that field'sd=. Identities are equal when their local parts are equal octet for octet and their domains are equal ignoring case. Otherwise, with no such field or more than one, the signature does not count. -
The mailbox. This is the mailbox this copy was delivered to: a stable identifier the receiver chooses, and every address the mailbox receives at, its own and its aliases. The receiver maps the envelope recipient (RCPT TO) to the mailbox. Two envelope recipients that reach one mailbox, such as an address and its
+tagform, are one mailbox.
A verifier SHOULD verify soon after arrival. The domain key in DNS may change later.
7.2 Steps#
A verifier runs these steps in order. The first step that fails gives the verdict not verified, with the reason shown.
| # | Check | Reason if it fails |
|---|---|---|
| 1 | The message has a Colophon-Signature header. |
no_signature |
| 2 | It has exactly one. | multiple_signatures |
| 3 | The header section is well formed (§6.1), and From has exactly one address. Let H be its host. |
malformed |
| 4 | At least one DKIM signature passed with d= equal to H (compared case-insensitively). If none did, stop here, before any DNS lookup. |
dkim_unaligned |
| 5 | With whitespace removed, the header value is at most 2,560 octets, parses as a tag list (§6.2), and has a v tag. |
malformed |
| 6 | v is 0. |
unsupported_version |
| 7 | It has c and b, and c is at most 2,048 octets. Both parse as compact JWS in strict base64url, b with an empty payload. Their protected headers and the claims are JSON objects that follow the JSON rules of §5.1. |
malformed |
| 8 | The credential header has alg EdDSA, typ colophon-cred+jwt, a valid selector in kid, and no crit. iss equals H exactly, octet for octet, so it must be lowercase. |
credential_invalid |
| 9 | Look up TXT at <kid>._colophon.H (§7.4). |
dns_error, dns_limited |
| 10 | The answer holds exactly one Colophon record, and it has k=ed25519 and a p tag (§7.4). |
key_not_found |
| 11 | p is not empty, and is a valid public key (§6.5) of 32 octets in strict base64url (§7.4). |
key_revoked if empty, otherwise key_not_found |
| 12 | The credential's signature verifies with that key (§6.5). | credential_invalid |
| 13 | Every claim has its JSON type and grammar (§5.1 to §5.4): sub, from, principal, scope, cnf with a valid public key (§6.5), and 0 < exp - iat <= 86400. |
credential_invalid |
| 14 | iat - 300 <= arrival <= exp + 7200. |
credential_expired |
| 15 | from equals the canonical From address. |
from_mismatch |
| 16 | The layout is valid (§6.1). Parts are checked from the top, and the first failure gives the reason. For each part, its header section and Content-* fields are checked first: a line that is not a field, a duplicate field, or a Content-Type or parameter that does not parse is malformed. Then its type, charset, format, transfer encoding and disposition: a failure there is layout. The multipart structure is checked after the top level's type and transfer encoding, and before the parts: a failure there is malformed. |
layout, malformed |
| 17 | The signing input can be built (§6.3, §6.4). | malformed |
| 18 | The signature header has alg EdDSA, typ colophon-sig, a nonce that matches its grammar, an integer iat, and no crit. iat <= min(exp, arrival + 300). |
signature_invalid |
| 19 | The agent signature verifies with the key in cnf, over the rebuilt signing input (§6.5). |
signature_invalid |
| 20 | To or Cc lists an address of the mailbox (§7.5). |
not_addressed |
| 21 | The message is verified within the verifier's delay D after arrival, the replay store can be read and written, and the tuple (mailbox, iss, sub, nonce) has not verified before (§7.6). |
replay_unchecked, replay |
If every step passes, the verdict is verified.
Steps 1 and 2 count the header lines that start a field named Colophon-Signature, before step 3 checks the rest of the header section.
A verifier does at most one DNS lookup and two Ed25519 verifications per message.
The constants:
- 300 seconds allows for clocks that run fast.
- 7,200 seconds is the arrival grace: time in transit after a credential expires.
7.3 Verdicts#
There are two verdicts.
- Verified. The verifier reports the agent:
iss, the issuing domain;from, the address;sub, the agent ID;principal;scope.
- Not verified, with one reason from §7.2. A message that is not verified is plain email: the receiver handles it as it would without Colophon.
The reason is for diagnosis. A receiver SHOULD NOT tell the user a different story for each reason. A changed text and a forgery both fail as signature_invalid.
A verdict never blocks delivery by itself. Receivers may still apply their own policies.
§7.8 says how a verifier reports the verdict to the software after it.
7.4 DNS lookups#
- A verifier MUST look up a key only for the domain that passed aligned DKIM (step 4). This keeps unauthenticated mail from making the verifier query any domain it names.
- A verifier MUST NOT make the lookup while the SMTP session is open.
- A lookup has 2 seconds, wall clock, retries included. A timeout or a server failure is
dns_error. - Answers are cached for their TTL. A name with no usable record is cached for at least 5 minutes.
- A verifier MUST limit misses. A miss is a lookup that does not give a key: one that ends in
dns_error,key_not_foundorkey_revoked(§7.2 steps 9 to 11). An answer whose one Colophon record has a key that is not valid ends inkey_not_found, so it is a miss. An answer from the cache is not a lookup, and a refused lookup is not a miss. After 20 misses for one domain in an hour (RECOMMENDED), it refuses uncached lookups for that domain until the hour is over. A refused lookup isdns_limited. Lookups that give a key do not count, even when the credential's signature then fails. Any sender at a shared domain can still spend the budget with made-up selectors, sodns_limitedis a plain not verified: a receiver MUST NOT treat it as evidence against the sender. - Like DKIM, a lookup tells the sender's DNS operator that a message arrived. See §10.
Which record is the key. The answer to the lookup is a set of TXT records. The verifier:
- joins each record's character strings, with nothing between them;
- parses each record as a tag list, as §6.2 does;
- discards every record that has an octet above 0x7F, does not parse, has a duplicate tag, or has no
v=COLOPHON0tag. These are not Colophon records; - if no Colophon record remains, or more than one, stops with
key_not_found. NXDOMAIN and an answer with no TXT records count as none; - if the record has no
k=ed25519tag, or noptag, stops withkey_not_found; - if
pis empty, stops withkey_revoked; - if
pis not strict base64url of exactly 32 octets, or they are not a valid public key (§6.5), stops withkey_not_found.
Otherwise p is the domain key.
7.5 Addressed to this mailbox#
A captured message passes DKIM again if it is resent unchanged to someone else. Step 20 stops that. A message verifies only for a mailbox it was addressed to in To or Cc.
- The mailbox's addresses are its own addresses and aliases (§7.1).
- A receiver MAY also count addresses it knows forward to the mailbox, for example an old address the user set up to forward.
- A recipient that was only on the envelope, such as a
Bccrecipient or an unknown forward, gets not verified (not_addressed). A sender that wants a recipient to verify lists them inToorCc.
Signed copies for Bcc recipients are a possible extension (§11).
7.6 Replays#
The verifier remembers each (mailbox, iss, sub, nonce) of a message it verified. It keeps the tuple until the credential's exp, plus the 2-hour grace, plus D. D is the longest time the verifier allows between a message's arrival and its verification: 24 hours, for example. After that, a copy that arrives fails step 14, and a copy that arrived in time is past D. A receiver that verifies messages again after a failure or an outage chooses a D that covers its retry window.
-
A verifier that keeps each tuple only until
exp+ 2 hours + D MUST returnreplay_uncheckedfor a message verified more than D after its arrival: its tuple may already be gone from the store. A verifier that keeps every tuple until it no longer verifies any message that could carry it need not check D. -
The verifier checks D before it reads the store. A late message costs no read, and claims no tuple.
-
If the store cannot be read or written, the verifier MUST NOT return verified. The verdict is
replay_unchecked, which §7.8 reports astemperror: verifying again once the store is back may pass. -
The tuple names the mailbox, not the envelope recipient. A message verifies at most once per mailbox: a copy sent again to the same mailbox, through an alias or a
+tagaddress, is a replay. One message delivered to two mailboxes at the same receiver verifies for each. -
The check comes after the signatures, so a forged message cannot fill the store.
-
A receiver MAY remove duplicate deliveries, such as SMTP retries, by
Message-IDbefore verifying. A second copy that reaches the verifier is not verified (replay).
7.7 Limits#
These values are set for v0. A later version may revise them from real traffic. The 30-day agent-key replacement is an example, not a limit (§5.2).
| Item | Limit |
|---|---|
Colophon-Signature value, whitespace removed |
2,560 octets |
Credential (c), whitespace removed |
2,048 octets |
Credential lifetime (exp - iat) |
at most 24 hours |
| Credential life left when signing | at least 6 hours (SHOULD; not checked) |
Arrival grace after exp |
2 hours |
| Clock skew | 5 minutes |
scope |
1 to 280 UTF-8 octets |
| Named principal | 1 to 128 UTF-8 octets |
| JSON numbers | integers within ±(2^53 - 1) |
sub, pseudonym id |
1 to 64 characters, [A-Za-z0-9._-] |
nonce |
16 to 64 base64url characters, at least 96 random bits |
| DNS lookup budget | 2 seconds, wall clock, retries included |
| Negative cache | at least 5 minutes |
| DNS misses per domain | 20 per hour (RECOMMENDED) |
| Work per message | at most 1 DNS lookup and 2 Ed25519 verifications |
| Message size | none of its own: the receiver's SMTP limit applies (RFC 1870) |
| Multipart boundary | 1 to 70 characters (RFC 2046) |
| Delay from arrival to verification (D) | the verifier's choice, 24 hours for example; replay tuples are kept for it |
| Agent key replaced | every 30 days, for example (SHOULD) |
| Domain key TTL | at most 1 hour (SHOULD) |
| Retired selector kept | at least 7 days after last use (SHOULD) |
7.8 Reporting the verdict#
A verifier SHOULD report its verdict to the software after it, such as the mail store, a mail client, a filter or an agent, in an Authentication-Results header field (RFC 8601), with the method colophon. Mail systems already report DKIM, SPF and DMARC there, so consumers need no new header and no new parser. Reporting is not required for conformance (§12). The verdict is.
The method is colophon, version 1, written without a version: colophon=pass. The protocol version travels in the v tag (§11).
| Result | Verdict | §7.2 reasons |
|---|---|---|
pass |
verified | none |
none |
not verified | no_signature |
temperror |
not verified | dns_error, dns_limited, replay_unchecked |
fail |
not verified | every other reason |
-
Reason. Every result but
passcarries its §7.2 reason code in thereasonclause, spelled exactly as there:colophon=fail reason="signature_invalid". -
What a result means.
temperrormay pass on a later attempt, and is never evidence against the sender (§7.4). A receiver MAY hold a message whose result istemperrorand verify it again later, rather than deliver it as not verified. The later attempt uses the same arrival time (§7.1), so it must come within D (§7.6).failis not evidence of forgery: aBcccopy and a text a mail system rewrote fail as a forgery does. A receiver SHOULD handlefailas it handles mail with no Colophon signature. As §7.3 says, the reason is for diagnosis. -
Properties. With
pass, and only withpass, the verifier reports these properties, all of ptypeheader:Property Value header.fromfrom, the verified address, in canonical form (§6.4)header.subsub, the agent IDheader.skid, the selector of the domain keyheader.principalthe principal's mode:withheld,pseudonymousornamedheader.credthe credentialmember of the signing input (§6.3): base64url of the SHA-256 ofcissis not reported: it is the host ofheader.from(§5.1). In an internationalized message,header.fromcarries the address as it is (RFC 6532). -
What is not reported. The pseudonym
id, the named principal's name, and the scope. They are free text the sender controls, and echoing them into a field that filters, logs and mail clients parse invites quoting bugs and display tricks. A consumer that needs them decodes the credential from the message's ownColophon-Signatureand checks that its SHA-256 equalsheader.cred. It does not verify the message again: that would trip the replay check (§7.6). Nothing is reported with a result other thanpass, so a failed result never echoes data an attacker chose. -
One result per copy. Steps 20 and 21 depend on the mailbox, so a result holds for one delivered copy. A server that adds the field before it expands recipients MUST evaluate each recipient separately, or leave the method out.
-
Whose result. The receiver adds the field under its own authserv-id, and removes any field that claims that authserv-id but came with the message (RFC 8601 §5). A consumer MUST trust a
colophonresult only under an authserv-id it trusts. A sender can writecolophon=passinto its own message, and it counts for nothing. -
Who adds it. Any verifier may: a boundary mail server, a delivery agent, or an application that verifies on intake, each under its own authserv-id.
The message of Appendix A, as mbx-reservations receives it:
Authentication-Results: mx.bistro.example;
dkim=pass header.d=acme.example;
colophon=pass header.from=maple-tiger-27@acme.example header.sub=ag_7Hq2
header.s=s1 header.principal=pseudonymous
header.cred=QyKlpHCC1M-TdbviC2iuOUhIpLY1HxufeJRGlEgEYSo
The same message, delivered again to that mailbox:
Authentication-Results: mx.bistro.example;
dkim=pass header.d=acme.example;
colophon=fail reason="replay"
8. First contact and receiver control#
v0 has no special message for an introduction. A stranger agent's first message is a signed message like any other.
- The receiver decides who gets in. Holding, admitting, asking a person, and blocking are the receiver's policy. Colophon supplies the evidence, never the admission rule.
- The evidence. It is the verdict and, when verified, the agent's domain, address, agent ID, principal as far as disclosed, and scope.
- Anything offered in support is content. For example: "Bob at partner.example suggested I write", a forwarded message, or a code the receiver gave out. The receiver, or its agent, judges it like any text.
- A forwarded Colophon message cannot be verified by the receiver in v0. The receiver may look up keys only for the domain that passed DKIM on the outer message (§7.4). An introducer who wants to be verified writes to the receiver directly, or copies the stranger in.
- No automatic replies to agents. A receiver SHOULD NOT tell a sending agent that its message was held. Silence tells address scrapers nothing, and it avoids backscatter.
- Agent addresses are worth protecting. An issuer SHOULD keep agent addresses from becoming widely known, for example with an alias per counterparty, and replace an address that is abused. A counterparty knows the agent by its address, so a replacement starts a new pairing.
9. Security considerations#
What "verified" means. The From host's mail system sent the message, and the agent key that domain certified for that address signed its addresses, subject, text and attachments. The credential was valid on arrival. The message was addressed to this mailbox in To or Cc, and had not verified for this mailbox before.
It does not mean:
- that the agent is honest or competent;
- that the request fits its scope;
- that the principal exists outside the issuer's word.
Sender text is untrusted. scope, a named principal, sub, the subject and the text are all written by the sender's side. A receiver MUST show them as the sender's claims. When it passes them to a language model, it MUST mark them as data, never as instructions.
Display names are not signed. From: "Your Bank" <maple-tiger-27@acme.example> verifies. Receivers SHOULD show the address and the issuing domain, not the display name, next to a verified verdict.
The subject is signed as octets. A receiver decodes encoded words to show the subject, and mail libraries disagree on unusual input: a character split across two encoded words, an unknown charset, invalid octets. The signature binds the octets, not one reading of them. A receiver SHOULD show a subject it cannot decode cleanly in its raw form, not a repaired one. A signer SHOULD send an ASCII subject as plain text and encode a non-ASCII subject as whole utf-8 encoded words, or as UTF-8 (RFC 6532).
Ed25519 edge cases. Ed25519 libraries disagree on non-canonical encodings, keys and points of small order, and the cofactor. A key of small order, the identity above all, lets one signature verify for every message. §6.5 fixes every one of these choices, so that such keys are rejected and every verifier reaches the same verdict on every signature.
Lookalike domains. Colophon proves which domain spoke, not that it is the domain the user expects. A receiver SHOULD make internationalized and lookalike domains visible, for example by showing A-labels or by flagging confusable characters.
A stolen agent key can sign only for its one address, and still needs the domain's mail system to pass DKIM. It is not bounded by the credential's 24 hours. Credentials are public: every message the agent sent carries one, so a thief who holds the key can attach any current credential for it. The key stays usable for as long as the issuer certifies it, and for up to 26 hours after the issuer stops. Ending it is the issuer's job: it replaces the agent key (§5.2). Regular replacement bounds how long an unnoticed theft lasts.
A leaked domain key lets the attacker mint credentials for any address on the host. To pass step 4, the attacker also needs to send through the domain's DKIM-signing mail system. Removing the key reaches verifiers within the record's TTL.
A compromised mail system and domain key together let an attacker send as any agent of that domain. That is the issuer's full compromise. Colophon cannot do better than the issuer it trusts.
Addresses that differ only in case. Verifiers lowercase the ASCII letters A to Z in local parts, and compare every other octet as it is, with no Unicode normalization or case folding (§6.4). A mail system may still deliver two such addresses to one place, for example by folding the case of non-ASCII letters. An issuer MUST NOT issue credentials for two addresses that are equal after ASCII lowercasing, or that its mail system treats as one address (such as variants that differ only in case). Several addresses that deliver to one mailbox MAY each have a credential only if all of them are issued to the same agent for the same principal. An agent's pairwise addresses (§5.3) are one such case: each has its own credential (§5.2).
Resending a captured message. DKIM passes again on an unchanged resend. The addressing check (§7.5) and the replay store (§7.6) stop it from verifying anywhere it was not sent.
Unsigned headers. Sender, Resent-* and every other header §6.3 does not list are not signed by the agent. A receiver MUST NOT present them as part of what the agent signed.
Relays that change messages. Mailing lists and gateways that change a covered part or break DKIM make a message not verified. This is deliberate: a verdict that could be true for some receivers and false for others would make implementations disagree. ARC (RFC 8617) is not used in v0.
Strict alignment. The DKIM domain must equal the From host. Relaxed alignment would need the Public Suffix List, and its versions differ between implementations. A provider that signs subdomain mail with the parent domain must sign Colophon mail with the exact host.
DKIM strength. Which DKIM signatures pass, including the minimum key size and the accepted algorithms, is left to the receiver's DKIM verifier, which follows RFC 8301. Colophon adds no rule of its own.
Denial of service.
- Key lookups happen only after aligned DKIM passes, have a 2-second budget, and misses are rate-limited.
- A message costs at most one DNS lookup and two Ed25519 verifications.
- Size limits (§7.7) bound parsing. Colophon sets no limit on the message itself: the receiver's SMTP size limit applies (RFC 1870), and parsing and hashing take time linear in the message's size. Grammars are strict, and verifiers reject what they cannot read rather than repair it.
- JSON must be I-JSON, which rejects duplicate keys, a common source of parser disagreement.
Time. Verification uses arrival time, so a signer cannot backdate. The signature's iat is only checked not to be later than the credential's exp, or than arrival plus 5 minutes.
Long-term proof is out of scope. Colophon verifies a message when it arrives. Proving years later what was said is a records question between one party, its archive, and perhaps a court. An extension may add timestamps or key transparency. A receiver that needs proof later SHOULD keep the DNS record it verified against, next to the message.
10. Privacy considerations#
Being an agent is always disclosed. A signed message says an agent wrote it. Who it acts for is disclosed only as far as the principal chooses (§5.3).
Unlinkability depends on addresses. A credential names one address, its own agent ID and its own key (§5.2), so its identifiers do not link two of an agent's addresses. Whether counterparties can link an agent depends on whether its issuer gives each counterparty its own address.
Credentials can still link. Credentials minted in one batch carry the same iat and exp, and the same scope wording can repeat across addresses. Two counterparties who compare notes can link them. An issuer SHOULD spread reissue times across an agent's addresses, and SHOULD keep scope wording generic.
The domain links. Every message shows its domain, as DKIM already does. Unlinkability across counterparties holds only when many principals share the domain. On a domain with one owner, the domain names the owner.
Other things link. Writing style, timing, scope wording, other headers such as Received and Message-ID formats, and the content itself. Colophon does not hide them.
Withheld is not anonymous to the issuer. The issuer knows the principal (§5.3). When it reveals a withheld principal is a matter of law and its terms.
No directory. Nothing in Colophon lists a domain's agents or addresses. Selectors name domain keys, not agents.
DNS lookups. The sender's DNS operator can see that a receiver looked up a key, as with DKIM. Caching limits what it learns.
Other agent headers. A sender MAY also add the Agent-of header (draft-chuang-agent-attribution-header-fields), but only in named mode. In the other modes it would reveal the principal.
11. Versioning and extensions#
- Version. The header's
v=0, the record'sv=COLOPHON0, and the signing input's"v": 0mark this version. A verifier that does not support a message's version returnsunsupported_version. A later version may add members to the signing input, which is why the signing input carriesv. - Credential claims. Extensions add claims whose names contain a colon. The domain signs them. Verifiers ignore claims they do not know.
- Header tags. Unknown tags are ignored. A tag is not signed, so it MUST NOT carry anything a receiver relies on.
- JWS header members. Unknown members are ignored.
critis rejected in v0. - Possible extensions, none defined here:
- a structured scope vocabulary;
- a published agent card (A2A);
- DNS records per agent;
- ARC for trusted forwarders;
- a structured
notepart for machine-readable data; - timestamps for long-term proof;
- signed
Bcccopies. The issuer would send eachBccrecipient a separate copy whose signing input names that one address, which no header shows. The address has to be in the signing input, because a tag is not signed. A v0 verifier ignores the new tag, rebuilds the signing input without the address, and fails the copy at step 19 assignature_invalid, before the addressing check at step 20. It never verifies such a copy wrongly. The extension stays safe for v0 verifiers; only the reason code differs fromnot_addressed.
12. Conformance#
There are three roles.
| Role | Must |
|---|---|
| Issuer | Publish domain keys (§4), and issue credentials that follow §5, including the issuer rules in §5.2, §5.3 and §5.5. |
| Signer | Sign messages that follow §6. |
| Verifier | Follow §7 exactly, and reach the expected verdict on every published test vector. |
Two verifiers given the same four inputs (§7.1) and the same DNS answers MUST reach the same verdict and the same reason. That is the test of this specification. Where it fails, the specification is wrong and should be fixed.
13. Registrations#
None has been requested yet. Three will be filed when v0 is final:
- Underscored DNS name:
_colophon, for TXT, in the Underscored and Globally Scoped DNS Node Names registry (RFC 8552). - Header field:
Colophon-Signature, in the Permanent Message Header Field Names registry (RFC 3864), as provisional. - Authentication method:
colophon, version 1 (§7.8), in the Email Authentication Methods registry, with its five properties of ptypeheader:from,sub,s,principalandcred. Its four results,pass,none,temperrorandfail, go in the Email Authentication Result Names registry (RFC 8601). Both registries are Specification Required (RFC 8126 §4.6): they need a stable public specification and a designated expert's review, not an IETF document.
No other registration is sought:
- JWS
typvalues. Verifiers matchcolophon-cred+jwtandcolophon-sigexactly. RFC 7515 §4.1.9 leaves the processing oftypto the application, and v0 accepts no other spelling. They are not registered as media types: names in the standards tree need an IETF or other standards-body publication. - Claims.
from,principalandscopeare defined only for credentials of typecolophon-cred+jwt.
14. Relation to A2A and Colophon Tasks#
A2A. Colophon is independent of A2A (the Agent2Agent protocol).
- An agent that also speaks A2A may publish an Agent Card. A credential can point to it with an extension claim, whose name contains a colon, for example
a2a:card(§5.1, §11). - A bridge, if one is written, will be a separate informative document, after Colophon Tasks.
- Both use JOSE, so keys and signatures move between them without conversion.
- Mapping A2A task states onto email belongs to Colophon Tasks.
Colophon Tasks. A later document adds tasks on top of v0:
- a signed, machine-readable task part;
- requests, offers, acceptance and confirmation;
- the rules that decide when an agreement exists over slow, lossy email.
It reuses v0's credential and signature unchanged, and adds the task part to the signing input under a new version.
15. Open issues#
No open issue blocks v0 final. Issues 1, 2 and 5 are resolved, and stay here for the record.
-
Will the header survive? Resolved in v0-05: header carriage (§6.2) stays. The test is in
tests/header-survival/of this specification's repository.- Sender. A domain whose mail Amazon SES signs with DKIM.
- Messages. Three, signed as §6.6 says: a minimal one, with a header of about 700 octets; one at the 2,560-octet header cap, with a 280-octet scope, a 128-octet named principal and an extension member in the agent signature's protected header; and one with two attachments.
- Receivers. Amazon SES inbound, Gmail (Google Workspace) and Fastmail, and a Cloudflare Email Routing forward to Gmail.
- Check. The reference sketch verified each stored copy, with the DKIM results taken from that receiver's own
Authentication-Results. - Result. 12 of 12 copies arrived with the header intact, passed aligned DKIM, and verified. Every receiver kept the header's folding as sent.
Verifiers remove whitespace, so refolding is safe for Colophon. DKIM with simple header canonicalization is not, which is why §6.2 asks for relaxed. Anyone can run the test again against other mail systems, with fresh keys under a new selector; the directory's README says how.
-
Real mail in the vectors. Resolved in v0-05: the results are published as a summary, in
tests/header-survival/results/. For each copy, it gives the header state, the verdict, and theColophon-Signaturefield as sent and as stored. The stored messages are not published, because they carry mailbox addresses and the receivers' internal data. The vectors in Appendix B stay synthetic. -
The formal model (Appendix C) is planned. v0 can be final without it. Colophon Tasks waits for it.
-
Name. "Colophon" is final for this text, but its trademark clearance is pending. A conflict would force a new version with new wire names. Production deployment waits for the clearance.
-
Wire rules added in v0-06. Resolved in v0-06, on 2026-11-09. Building the Python library and a receiver against the sketch found places where the draft was silent and readers disagreed. v0-06 settles each of them, and each choice changes which messages verify. Every rule is decided as the draft states it. The alternatives that were considered stay here for the record.
- Line ends (§6.1). Decided: a line ends at CRLF or LF alone. A CR alone is malformed in a header section and is data in a body. Not chosen: CRLF only, which rejects messages that a mail store saved with LF line ends; or a CR alone ends a line everywhere, as v0-05's sketch did.
- Header lines (§6.1). Decided: a line that is neither a field nor a continuation is malformed. Not chosen: such a line ends the header section, as many mail libraries read it. Readers would then disagree on where the body starts.
- Parameters (§6.1). Decided: strict RFC 2045 in the top-level and body-part
Content-Type, where a duplicate or a parameter without=is malformed; the RFC 2231 forms ofboundary,charsetandformatbreak the layout; an attachment's parameters are not read. Not chosen: every RFC 2231 parameter breaks the layout, which also rejects attachments with non-ASCII file names, as mail clients commonly send them; or the first of two duplicates wins. - Multipart (§6.1). Decided: strict RFC 2046, with a valid boundary, a close delimiter, no empty parts and no delimiter in the epilogue, each malformed if broken. Not chosen: accept a missing close delimiter and drop empty parts, as lenient readers do; or report these as
layout. - Ed25519 (§6.5). Decided: canonical keys not of small order, S < L, R not of small order, and the equation without the cofactor, as libsodium does by default. Not chosen: the ZIP-215 rules, which accept every encoding and use the cofactor, so that batch and single verification agree; or no rule, which leaves verifiers to disagree on crafted signatures.
- DNS misses (§7.4). Decided:
dns_error,key_not_foundandkey_revokedeach count as a miss, so a domain whose DNS times out on purpose spends its budget too. Not chosen: count only lookups that return no Colophon record; or leavedns_limitedout of the vectors. - DKIM identity (§7.1). Decided: a passing signature counts for its
d=. A verifier whose boundary server reports onlyheader.irecoversd=from the oneDKIM-Signaturefield whose identity equalsheader.i, and counts nothing if there is not exactly one. Not chosen: countd=from a field whosed=is the domain ofheader.iand whose identity equalsheader.i, even when other fields share that identity: a signature by a parent domain withi=in a subdomain could then make an unverified field for the subdomain count; orheader.donly, so that such a verifier must check DKIM itself. - Replay store unavailable (§7.6). Decided: a new reason,
replay_unchecked, reported astemperror, which a receiver may hold and verify again within D (§7.8). Not chosen: report it asreplay, afail; or give no verdict, and leave the retry to the receiver. - Late verification (§7.6). Decided: tuples are kept until
exp+ 2 hours + D, where D is the verifier's longest delay from arrival to verification, chosen to cover its retry window, and a message verified later than D isreplay_unchecked. Not chosen: fix D in the spec, 24 hours for example, which covers retry windows of minutes to hours; or extend retention only, with no rule for messages verified later than D. - Message size (§7.7). Decided: no limit of Colophon's own; the receiver's SMTP size limit applies. Not chosen: a RECOMMENDED maximum size for signed messages.
Also on 2026-11-09, building the library raised six questions that the rules above left open. The text now answers each.
- DKIM identity (§7.1).
i=is compared after decoding DKIM quoted-printable, because the identity is the decoded value (RFC 6376 §3.5). A field whose tags do not parse, or whosei=is not valid quoted-printable, has no identity. Not chosen: comparei=as written, which is not the identity RFC 6376 defines. - Late verification (§7.6). The check of D is required of a verifier that removes tuples at
exp+ 2 hours + D, and only of it. The verifier checks D before the store, so a late message costs no read and claims no tuple. Not chosen: require the check of every verifier, including one that keeps tuples longer. - DNS misses (§7.4). An answer whose only Colophon record has a key that is not valid is a miss, as every lookup that ends in
key_not_foundis. - Parameters (§6.1). The RFC 2231 form of
boundary,charsetorformatbreaks the layout only where the verifier reads that parameter. Elsewhere it is ignored.
Appendix A. Worked example#
The reference sketch in reference/colophon_v0.py produces this example. Run uv run --with cryptography python reference/colophon_v0.py --example to reproduce it byte for byte. The keys are test keys derived from fixed strings. Never use them for anything else.
DNS
s1._colophon.acme.example. TXT "v=COLOPHON0; k=ed25519; p=fKF-JHnT8l0RiMibgA4oUYQKwUzyDxroUgxaRM6i6lI"
Credential: protected header and claims
{"alg":"EdDSA","kid":"s1","typ":"colophon-cred+jwt"}
{
"cnf": {"jwk": {"crv": "Ed25519", "kty": "OKP", "x": "lC9PrkLW2QmPaKqAeeCY94OM3M75MeD9Uyg6oThgOLE"}},
"exp": 1793952000,
"from": "maple-tiger-27@acme.example",
"iat": 1793865600,
"iss": "acme.example",
"principal": {"id": "p_Xk29", "mode": "pseudonymous"},
"scope": "Books restaurant tables for one person, up to $500 per booking.",
"sub": "ag_7Hq2"
}
Signing input, JCS. Shown here on several lines. It is one line when signed.
{"attachments":[{"sha256":"toHbGVby3dGckYAJkxBXPj9DGUnSCDiuIVwGV4oFR7w","type":"text/csv"}],
"cc":[],"credential":"QyKlpHCC1M-TdbviC2iuOUhIpLY1HxufeJRGlEgEYSo",
"from":"maple-tiger-27@acme.example","reply_to":[],"subject":"Table for 2, Friday 7 November, 19:30",
"text":"JO-XRoNsBAPLwS_XAckdl7Jmo2DmjMagPnYpKzTgLkA","to":["reservations@bistro.example"],"v":0}
Note that From is written Maple-Tiger-27@acme.example in the message and canonicalized to lowercase.
Message as sent, before the issuer's DKIM signature. Line endings are CRLF on the wire.
Colophon-Signature: v=0;
c=eyJhbGciOiJFZERTQSIsImtpZCI6InMxIiwidHlwIjoiY29sb3Bob24tY3JlZCtqd3QifQ.eyJj
bmYiOnsiandrIjp7ImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoibEM5UHJrTFcyUW1QY
UtxQWVlQ1k5NE9NM003NU1lRDlVeWc2b1RoZ09MRSJ9fSwiZXhwIjoxNzkzOTUyMDAwLCJmcm9tIj
oibWFwbGUtdGlnZXItMjdAYWNtZS5leGFtcGxlIiwiaWF0IjoxNzkzODY1NjAwLCJpc3MiOiJhY21
lLmV4YW1wbGUiLCJwcmluY2lwYWwiOnsiaWQiOiJwX1hrMjkiLCJtb2RlIjoicHNldWRvbnltb3Vz
In0sInNjb3BlIjoiQm9va3MgcmVzdGF1cmFudCB0YWJsZXMgZm9yIG9uZSBwZXJzb24sIHVwIHRvI
CQ1MDAgcGVyIGJvb2tpbmcuIiwic3ViIjoiYWdfN0hxMiJ9.RXZMLCGGSf1h2H2vm6R62FUeHwEpB
ltl4vqouA43gL24o5cCzUMx8tskVEgDWEOKcBRYHw8q4PuxNfqmGDNNAw;
b=eyJhbGciOiJFZERTQSIsImlhdCI6MTc5Mzg3MDEwMCwibm9uY2UiOiJabTl5TFdWNFlXMXdiR1V
0YjI1c2VRIiwidHlwIjoiY29sb3Bob24tc2lnIn0..lmIvRov2v6U3-dRU-n08mC-Rh4s6lYP36XV
3ve-OrsEkyXmHCjEIc2jLIgmzncjqL9KBWiF0Qqks4scg9ZS8Aw
From: Booking agent <Maple-Tiger-27@acme.example>
To: Reservations <reservations@bistro.example>
Subject: Table for 2, Friday 7 November, 19:30
Message-ID: <example-1@acme.example>
Date: Thu, 05 Nov 2026 09:15:00 +0000
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="=_colophon_example_1"
--=_colophon_example_1
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Hello,
Could you hold a table for 2 this Friday, 7 November, at 19:30?
One guest is vegetarian.
Thank you
--=_colophon_example_1
Content-Type: text/csv
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="booking.csv"
MIME-Version: 1.0
ZGF0ZSx0aW1lLHBhcnR5CjIwMjYtMTEtMDcsMTk6MzAsMgo=
--=_colophon_example_1--
Verifier inputs
| Input | Value |
|---|---|
| Arrival time | 1793870110 (2026-11-05T09:15:10Z) |
| DKIM | pass, d=acme.example |
| Envelope recipient | reservations@bistro.example |
| Mailbox | mbx-reservations, which receives at reservations@bistro.example and reservations+web@bistro.example |
| DNS | the record above, at s1._colophon.acme.example |
Verdict: verified. The agent:
iss:acme.examplefrom:maple-tiger-27@acme.examplesub:ag_7Hq2principal: pseudonymous,p_Xk29- the scope above.
Appendix B. Test vectors#
reference/colophon_v0.py runs each vector against the example in Appendix A. Run it without arguments: it prints one line per delivery and exits non-zero if any fails. Run it with --json to write the vectors as reference/vectors.json: each message as base64 of its octets, then one or more deliveries, each with the receiver's inputs and the expected result.
Each delivery also gives expected_authentication_results: the colophon result that §7.8 reports for it, as one line, without the authserv-id. With pass, its properties are in the order of the table in §7.8. Order and spacing are not significant in a real Authentication-Results field; the vectors fix one form so that a test can compare strings.
A vector's deliveries run in order and share one replay store. Unless a row says otherwise, each delivery has the inputs of Appendix A.
dkim_pass_identities holds the header.i of each passing DKIM signature that the boundary server reported without header.d. The verifier recovers its d= from the message (§7.1). Unless a row says otherwise, it is empty.
Two inputs stand for the receiver's state. dns_misses is the number of misses (§7.4) for the From host in the hour before the delivery, with no key cached; 20 or more means the lookup is refused. replay_store_available is false when the replay store cannot be read or written (§7.6). Unless a row says otherwise, they are 0 and true. A string in dns_txt stands for its UTF-8 octets. The vectors do not cover the delay D of §7.6.
| Change to the message or inputs | Expected |
|---|---|
| None | verified |
| ... delivered again to the same mailbox | replay |
... delivered again, to the alias reservations+web@bistro.example |
replay |
... delivered to a second mailbox, mbx-tables, which also receives at reservations@bistro.example |
verified |
| Colophon-Signature refolded in transit | verified |
| Display name changed | verified |
| Trailing spaces on a body line | verified |
| Charset name in capitals | verified |
| Attachment renamed (names are not signed) | verified |
| Subject refolded in transit | verified |
| Spaces in the subject collapsed | verified |
| Encoded words that decoders disagree on, signed as sent (never decoded) | verified |
| Non-ASCII subject, as the signer encoded it | verified |
| Unrelated TXT beside the record | verified |
| A Colophon record with a duplicate tag beside the record | verified |
| Arrived exactly at exp + grace | verified |
| Signed 1 hour before the credential's iat | verified |
| Body in 8bit with non-ASCII text | verified |
| 8-bit octets inside a quoted-printable body | verified |
| Lines end in LF alone | verified |
| A CR alone ends a body line | verified |
| Empty display name in To | verified |
| Quoted-pair in the charset | verified |
| Empty parameter items | verified |
| Attachment name in RFC 2231 form | verified |
| Attachment parameter that does not parse (not read) | verified |
| Boundary of 70 characters | verified |
| Preamble and epilogue | verified |
| Agent signature with another valid R (R need not be the RFC 8032 one) | verified |
| A non-ASCII Colophon record beside the record (not a Colophon record) | verified |
| 19 DNS misses for the host this hour | verified |
| Subject changed | signature_invalid |
| Subject re-encoded as an encoded word after signing | signature_invalid |
| Text changed | signature_invalid |
| Reply-To added | signature_invalid |
| Cc added | signature_invalid |
| Attachment changed | signature_invalid |
| Body starts with a BOM (a BOM is content) | signature_invalid |
| HTML part added | layout |
| Body in ISO-8859-1 | layout |
| Charset alias utf8 | layout |
| Body marked format=flowed | layout |
| Unknown transfer encoding | layout |
| Message/rfc822 attachment | layout |
| Charset in RFC 2231 form | layout |
| Transfer encoding on the multipart | layout |
| Second To field | malformed |
| Second Subject field | malformed |
| Second Content-Type in the body part | malformed |
| Quoted local part in Cc | malformed |
| Quoted local part in From | malformed |
| Group in To | malformed |
| Domain literal in Cc | malformed |
| Non-ASCII domain in Cc | malformed |
| Comment in To | malformed |
| Unquoted @ in a display name | malformed |
| Empty member in To | malformed |
| Invalid UTF-8 in the subject | malformed |
| Bad quoted-printable in the body | malformed |
| Bad base64 in the attachment | malformed |
| Missing v tag | malformed |
| Tag name in capitals | malformed |
| Padded base64url in c | malformed |
| A fraction in exp | malformed |
| A duplicate claim | malformed |
| Credential over 2,048 octets | malformed |
| Escaped surrogate in sub | malformed |
| Noncharacter U+FFFF in scope | malformed |
| A CR alone in To | malformed |
| A header line that is not a field | malformed |
| Charset given twice | malformed |
| A parameter without a value | malformed |
| Boundary given twice | malformed |
| Boundary of 71 characters | malformed |
| No close delimiter | malformed |
| Two delimiter lines in a row | malformed |
| A line that starts with the delimiter | malformed |
| A delimiter in the epilogue | malformed |
| Second Colophon-Signature | multiple_signatures |
| No Colophon-Signature | no_signature |
| DKIM passes only for mail.acme.example | dkim_unaligned |
| DKIM passes for mail.acme.example and acme.example | verified |
| DKIM passes for two unaligned domains | dkim_unaligned |
| DKIM passes for ACME.EXAMPLE | verified |
DKIM reported by header.i only, bookings+agent@acme.example, for a field with i=bookings=2Bagent@acme.example |
verified |
DKIM reported by header.i only, bookings=2Gagent@acme.example, for a field with that i=, which is not valid quoted-printable |
dkim_unaligned |
DKIM reported by header.i only, @Acme.Example, for a field with d=acme.example and no i= |
verified |
DKIM reported by header.i only, @acme.example, for two fields with that identity |
dkim_unaligned |
DKIM reported by header.i only, @acme.example, for a field with that i=, beside one with that i= whose tags do not parse |
verified |
| V=1 | unsupported_version |
| DNS lookup failed | dns_error |
| No record at the selector | key_not_found |
| Two Colophon records | key_not_found |
| A k=rsa Colophon record beside the record | key_not_found |
| Record without p | key_not_found |
| Key of the wrong length | key_not_found |
| Key in padded base64 | key_not_found |
| Record with empty p= | key_revoked |
| The record with a non-ASCII tag added | key_not_found |
| Domain key is the identity point (every credential verifies under it) | key_not_found |
| Domain key of 32 zero octets (small order) | key_not_found |
| Domain key not in canonical encoding | key_not_found |
| 20 DNS misses for the host this hour | dns_limited |
| Record holds a different key | credential_invalid |
| Iss in capitals | credential_invalid |
| Sub with a trailing newline | credential_invalid |
| Sub as a number | credential_invalid |
| No from claim | credential_invalid |
| From claim not in canonical form | credential_invalid |
| Lifetime of 25 hours | credential_invalid |
| Bidi control in scope | credential_invalid |
| Agent key of 31 octets | credential_invalid |
| Agent key is the identity point (every signature verifies under it) | credential_invalid |
| Agent signature with S + L in place of S | signature_invalid |
| Agent signature with R the identity point | signature_invalid |
| Agent signature that only a cofactored check accepts | signature_invalid |
| Arrived 1 second after exp + grace | credential_expired |
| From is another address on the same host | from_mismatch |
SMTPUTF8 local part, ASCII letters lowercased (José@acme.example, credential josé@acme.example) |
verified |
SMTPUTF8 local part with non-ASCII in another case (JOSÉ@acme.example, same credential) |
from_mismatch |
| Signature iat 10 minutes after arrival | signature_invalid |
| Delivered to a mailbox not in To or Cc (Bcc) | not_addressed |
| Replay store unavailable | replay_unchecked |
Some of these changes would also break DKIM in real mail. The vectors test the Colophon checks alone, with the DKIM result given as an input.
The sketch does not check DKIM or query DNS: both are inputs. It handles every address form v0 allows: dot-atom local parts, non-ASCII ones included, and ASCII domains. It reads header sections and MIME structure with its own parser, as §6.1 specifies, not with a mail library, and checks Ed25519 keys and signatures as §6.5 specifies.
Appendix C. Formal model#
A formal model of v0 is planned, in Tamarin. v0 can be final without it. Colophon Tasks waits for it.
- What it covers. Steps 4, 12 to 15 and 19 to 21 of §7.2: aligned DKIM, the credential's signature, claims, time and address, the agent signature, the addressing check and the replay check. It assumes perfect cryptography, and an attacker who controls the network, plays one side, or holds a stolen agent key.
- What it proves. Two lemmas:
- a verified message traces to a credential the domain issued for a delegating principal, and was sent through the domain's DKIM-signing mail system;
- a captured message cannot verify for a mailbox it was not addressed to, and verifies at most once per mailbox.
- Mutants. Two altered models must yield attack traces: one without step 20, and one without step 4. This shows the lemmas depend on those steps.
- Where it runs. In the repository's CI, on every change.
A verifier's bounded work per message is not a model property. §9 states it.
Appendix D. Decision log#
The design was settled one question at a time. Each decision has a longer record, with the options that were rejected.
| Decision | Choice |
|---|---|
| Where agent identity lives | A domain key in DNS under a selector, and a short-lived credential in each message. A published card is optional. |
| How a message binds to the agent | An Ed25519 JWS by the agent key over a JCS signing input, on top of DKIM. Plain text only. |
| Principal disclosure | Withheld by default, pseudonymous or named by the principal's choice. |
| No credential without a principal | The issuer must be able to trace every credential to a delegating principal. |
| Long-term proof | Not part of the protocol. |
| Messages with no request | Any agent email may be signed, and v0 has no acts at all. |
| Credentials checked on arrival | The credential must be valid when the receiver's server accepts the message. The signing time is advisory. |
| v0 is the identity layer | Tasks and agreements move to Colophon Tasks. |
| Scope | A plain-words statement, signed by the domain. Software never enforces it. |
| Both proofs required | Aligned DKIM, checked by the receiver's own mail system, plus the agent signature. Without DKIM, no key lookup. |
| A credential belongs to a mailbox | The credential names its address. DKIM alignment is strict. |
| Where the signature travels | A header, with a size cap. Bare addresses are signed, not raw headers. |
| Pseudonymous mode | A pairwise address, credential, agent ID and key per counterparty. |
| First contact | No introduction act. A first contact is a signed message, and support for it is content. |
| Verdicts | Two: verified, or not verified with a reason. The message is checked as the receiver accepted it, and the payload is rebuilt, not sent. |
| One credential per address | A single from address with its own agent ID and key, in every mode. |
| Agent key replacement | Guidance only: SHOULD replace regularly, every 30 days for example, and at once on suspected compromise. No key-age claim, and verifiers do not check it. |
| Addressing check | A message verifies only for a mailbox in To or Cc. Bcc copies and unknown forwards are not verified. Signed Bcc copies may come later as an extension. |
| Subject and Reply-To | Both signed. The subject is signed raw, not decoded. |
| Limits | Fixed for v0; v1 may revise them from traffic. |
| Message syntax | Header sections and MIME read strictly, with their own rules (§6.1): what a strict reader cannot read is malformed, not repaired. Decided in v0-06 (§15, issue 5). |
| Ed25519 | RFC 8032 with its open choices made exact: valid keys, S < L, R not of small order, no cofactor (§6.5). Decided in v0-06 (§15, issue 5). |
| Replay store | A verifier that cannot check the store does not verify. Tuples are kept for the verifier's delay from arrival to verification (§7.6). Decided in v0-06 (§15, issue 5). |
| Tag letters | c for the credential and b for the agent signature. |
| Encoding | base64url everywhere, the DNS record included, decoded strictly. |
| JWS form | A standard detached JWS, not RFC 7797 unencoded payloads: RFC 7797 needs crit, which v0 rejects. |
| Message expiry | None. The credential's exp and the arrival time bound a message. |
| Non-ASCII local parts | ASCII letters lowercased, other octets compared as they are, no Unicode normalization or folding. |
| Registrations | _colophon (RFC 8552), a provisional Colophon-Signature (RFC 3864), and the Authentication-Results method colophon (RFC 8601), filed at v0 final. No media types. |
| Reporting the verdict | An Authentication-Results method with four results and the reason code. Properties only on pass, and no free text the sender wrote. Recommended, not required for conformance. |
| Real-mail evidence | A published summary of the header-survival test. Stored messages stay private, and the vectors stay synthetic. |
| Formal model | Tamarin, planned. Not required for v0 final; required before Colophon Tasks. |
| Licence | Apache 2.0 for spec and code. The founder is editor until a second independent implementation exists. Changes go through public review. |
| Relation to A2A | Independent, with a defined bridge. |
Appendix E. Changes#
v0-06#
v0-06 answers the review of the reference sketch made while building the Python library. Where the draft was silent and readers could disagree, it now states one rule. The choices between wire behaviours were decided on 2026-11-09. §15, issue 5 records them, with the alternatives considered. The worked example's message, credential and signatures are unchanged, and every v0-05 vector keeps its verdict.
- Header sections and MIME. A line ends at CRLF or LF. A CR alone, and a header line that is neither a field nor a continuation, are malformed in a header section (§6.1).
Content-Typeparameters follow RFC 2045, read only in the top level and the body part. The multipart structure follows RFC 2046 strictly (§6.1, §7.2 step 16). - 8-bit text. Octets in an
8bitorbinarybody, and 8-bit octets in a quoted-printable one, are taken as they are (§6.4). The v0-05 sketch rejected them, and could not sign them. - Display names. An empty display name, as in
<reservations@bistro.example>, is allowed (§6.4). - JSON. Surrogates and noncharacters are not allowed in strings, escaped or not, and make the message malformed (§5.1).
- Ed25519. Keys must be valid, and signatures are checked with RFC 8032's open choices made exact (§6.5). The identity key and other keys of small order are rejected.
- DNS. A TXT record with an octet above 0x7F is not a Colophon record (§4.1, §7.4). A miss is defined (§7.4).
- DKIM results. A signature counts for its
d=, not itsi=. A verifier whose boundary server reports onlyheader.irecoversd=from the message (§7.1). - Replay store. A new reason,
replay_unchecked, reported astemperror, when the store cannot be checked or a message is verified too late. Tuples are kept for the verifier's delay D as well, which covers its retry window. A receiver may hold atemperrormessage and verify it again (§7.6, §7.8). - Limits. The message size, the boundary length and D are in §7.7.
- Vectors. Three new delivery inputs,
dkim_pass_identities,dns_missesandreplay_store_available, and 43 new deliveries: 123 in all (Appendix B). The sketch now reads MIME with its own parser. - Clarifications. Six questions from building the library are answered in the text (§15, issue 5): how
i=is decoded and when aDKIM-Signaturefield has no identity (§7.1), when a verifier must check D and in what order (§7.2 step 21, §7.6), that an invalid key is a miss (§7.4), and where the RFC 2231 rule applies (§6.1).
v0-05#
- Header survival. Resolved (§15, issue 1). Three test messages, the largest at the 2,560-octet cap, went to Amazon SES inbound, Gmail (Google Workspace) and Fastmail, and through a Cloudflare Email Routing forward to Gmail. All 12 copies arrived with the header intact and verified. Header carriage (§6.2) stays, so the fallback MIME part is not specified. No open issue blocks v0 final.
- Reporting the verdict. New §7.8: an
Authentication-Resultsmethod,colophon, with four results, the reason code, and five properties reported only withpass. Recommended, not required for conformance. This changes nothing on the wire and no verdicts. Each vector delivery gains its expectedcolophonresult (Appendix B), and the worked example's output ends with it. - Registrations. A third, the
colophonmethod and its result names, filed at v0 final with the other two (§13). - Real mail. The test's results are published as a summary: tables, and each copy's
Colophon-Signaturefield as sent and as stored. Stored messages are not checked in, and the vectors stay synthetic (§15, issue 2).
v0-04#
- Open issues. The numbers, local parts, wire choices and the formal model are settled, and §15 is rewritten. Header survival is the one blocker for v0 final, with a test plan (§15, issue 1). The name is final for this text, pending clearance.
- Limits. The values in §7.7 are set for v0.
- Wire choices. The tag letters, base64url, the standard detached JWS and the absence of a message-level
expare fixed (Appendix D). - Local parts. Non-ASCII octets compare as they are, with no normalization or folding (§6.4). An issuer must not issue credentials for two addresses its mail system treats as one (§9).
- Shared mailboxes. Several addresses that deliver to one mailbox may each have a credential only if all of them are issued to the same agent for the same principal (§9). This changes nothing on the wire and no vectors.
- Clarifications.
issis compared exactly and is lowercase (§5.1, §7.2 step 8). DKIM strength is left to the receiver's DKIM verifier (§9). No change on the wire. - Registrations. Two, filed at v0 final; no media types (§13).
- Header size. The typical header value is about 700 octets, not 800. The 2,560-octet cap stays: without extensions a value reaches about 2,310 octets, and the rest is room for extensions (§6.2, §15). This changes nothing on the wire and no vectors.
- A2A. The hook is an extension claim such as
a2a:card(§14). - Formal model. Planned in Tamarin, with two lemmas and two mutants (Appendix C).
- Vectors. Three for multiple DKIM signatures, two for SMTPUTF8 local parts, and one for
issin capitals: 80 deliveries. - Figures. Four informative figures: the two proofs and a credential's times (§3), what the signature covers (§6.3), and the verifier's stages (§7.2). Figure 1 replaces the text diagram in §3. No rule changed.
v0-03#
- Name. The protocol was formerly named CREST. It is renamed Colophon before publication, because another organization uses CREST in the security field. No rule changed. The wire names are now:
- the header
Colophon-Signature, formerlyCrest-Signature; - the DNS label
_colophon, formerly_crest; - the record tag
v=COLOPHON0, formerlyv=CREST0; - the token types
colophon-sigandcolophon-cred+jwt, formerlycrest-sigandcrest-cred+jwt.
- the header
- Tasks document. The later tasks document is named Colophon Tasks (§14).
- Reference files. The sketch is
reference/colophon_v0.py. Its test keys are derived from new fixed strings. - Vectors. Regenerated, since the token types are signed, and the example in Appendix A with them: still 74 deliveries. Earlier signatures do not verify under v0-03.
v0-02#
- Subject. The subject is signed raw: unfolded, whitespace collapsed, trimmed, with encoded words left as they are (§6.4). Verifiers no longer need an RFC 2047 decoder. A subject re-encoded after signing no longer verifies; a relay that does this breaks DKIM as well. Receivers decode only for display (§9).
- Addressing check. Kept as v0-01 proposed: a message verifies only for a mailbox listed in
ToorCc, andBcccopies and unknown forwards are not verified (§7.5). SignedBcccopies are listed as a possible extension (§11). - Agent key replacement. Stays guidance (SHOULD), with 30 days as an example. Verifiers check nothing about key age (§5.2).
- Open issues. §15 issues 1 (addressing check) and 4 (subject) are closed, and the others are renumbered.
- Vectors. Five subject vectors replaced: still 74 deliveries.
v0-01, from v0-00#
v0-01 applies the first round of review. The design is unchanged; the changes close places where implementations would disagree, and correct claims the mechanism did not support.
- Mailbox, not envelope recipient. The verifier's fourth input is the mailbox. The replay tuple uses it, so a copy resent through an alias is a replay (§7.1, §7.6).
- Stolen agent keys. A stolen key is usable while the issuer certifies it, not for at most 24 hours. Issuers SHOULD replace agent keys regularly (§3, §5.2, §9).
- DNS. One algorithm picks the key record (§7.4). The rate limit counts misses only, at 20 per domain per hour, and
dns_limitedis never a signal against the sender. - Strict parsing. Grammars match whole values (§2). Claims have exact JSON types, and JSON numbers are integers (§5.1). base64url is strict everywhere. Tag names are lowercase. A missing
vis malformed (§6.2, §7.2). - Addresses. A strict subset of RFC 5322: no groups, comments, domain literals or quoted local parts. Domains are ASCII, with A-labels for IDNs (§6.4).
- Subject. Strict RFC 2047 decoding, word by word,
utf-8andus-asciionly. Signing the raw subject instead is open (§15, issue 4). - MIME. Tokens compare case-insensitively. Fixed charset names, five transfer encodings, no
format=flowed, nomessage/*attachments, and no duplicateContent-*fields (§6.1). Strict quoted-printable and base64 (§6.4). - Signing input. Attachment file names are no longer signed (§6.3).
- Sizes. Credential 2,048 octets and header 2,560, with the total caps binding (§5.1, §6.2).
- Text in claims. Scope and named principals also forbid bidirectional controls (§5.4).
- Time. The lower bound on the signature
iatis gone, and the 6-hour rule is a SHOULD (§6.6, §7.2). - DKIM. The issuer's DKIM signature MUST cover
Colophon-Signature, with relaxed header canonicalization RECOMMENDED (§6.2). - Privacy. Batch reissue times and repeated scope wording can link addresses (§10).
- Vectors. 74 deliveries in Appendix B, and a machine-readable
reference/vectors.json.