Colophon

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

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#

  1. Introduction
  2. Conventions and terms
  3. Overview
  4. Domain keys in DNS
  5. The agent credential
  6. The signed message
  7. Verifying
  8. First contact and receiver control
  9. Security considerations
  10. Privacy considerations
  11. Versioning and extensions
  12. Conformance
  13. Registrations
  14. Relation to A2A and Colophon Tasks
  15. Open issues

Appendices:

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:

It needs no central registry, no HTTPS fetch, and no service run by any one company.

1.1 Principles#

  1. Plain email first. A signed message is a valid, readable email. A receiver that ignores Colophon loses nothing.
  2. 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.
  3. Built on what exists. DKIM, DNS, MIME and JOSE before anything new.
  4. Receiver in control. The receiver decides who gets in and what to act on. A sender cannot force its way in.
  5. 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.
  6. Least disclosure. An agent reveals who it acts for only as much as its principal allows.
  7. 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:

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.

  1. The domain's mail system sent it. The message passes DKIM, and the DKIM signing domain is exactly the From host.
  2. 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 From host publishes in DNS;
    • the agent signature, made with the key the credential names, covers the message's addresses, subject, text and attachments.
Two boxes, side by side, lead to one verdict. Proof 1, the domain's mail system sent it: a DKIM signature passes, its d= is exactly the From host, and it covers the Colophon-Signature header. It is checked at step 4, before any DNS lookup. Proof 2, this agent wrote it, is a chain of three: the domain key published in DNS at s1._colophon.acme.example signs the credential c, which lasts at most 24 hours and binds an agent key to one address; that agent key makes the agent signature b, over From, To, Cc, Reply-To, Subject, the text and the attachments. Both proofs lead to: verified, both proofs hold. A note says c and b travel together in one header: Colophon-Signature: v=0; c=…; b=….
Figure 1. The two proofs. The credential is in §5, the agent signature in §6.5, and the order of the checks in §7.2.

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.

A timeline of one credential, from iat to exp, which is at most 24 hours later, and on to exp plus 2 hours. Four bars. The credential is valid from iat to exp. The signer uses it from iat until 6 hours are left; it should not sign in the last 6 hours. A message may arrive from iat minus 5 minutes to exp plus 2 hours. The replay record for a verified message is kept from the moment it verifies to exp plus 2 hours, then for the verifier's delay D, shown as an arrow past the end. A bracket under the whole span says: revoke by not reissuing; the last credential's mail stops verifying within 26 hours.
Figure 2. One credential's times. The limits are in §7.7; the arrival check is step 14 of §7.2, and the replay record is in §7.6.

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>

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#

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:

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:

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:

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

Colophon never decides whether a message fits its scope. A receiver shows the scope and applies its own policy.

5.5 Issuing#

6. The signed message#

6.1 Message layout#

A signed message MUST have one of two layouts.

  1. The whole message is one text/plain part, not marked Content-Disposition: attachment.
  2. The top level is multipart/mixed, and:
    • its first child is text/plain and is not marked Content-Disposition: attachment. This is the body.
    • every other child is a single part, neither multipart/* nor message/*, with Content-Disposition: attachment. These are the attachments.

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:

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:

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:

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>
Tag Value
v 0
c the credential, compact JWS (§5)
b the agent signature, detached compact JWS (§6.5)

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:

Not covered:

DKIM still covers whatever the issuer's DKIM signature lists.

Two columns. On the left, the lines of the worked example message; on the right, what each line becomes in the signing input. From, with display name Booking agent, becomes from: the address only, lowercased. To becomes to: addresses only. Cc and Reply-To, absent here, become cc and reply_to, empty arrays when absent. Subject becomes subject: raw, as on the wire. Message-ID, Date, In-Reply-To and References are not signed. Colophon-Signature gives v and credential, the SHA-256 of c. DKIM-Signature is not in the input: it is proof 1, and it covers Colophon-Signature. In the body, multipart/mixed is one of the two allowed layouts; the text/plain part becomes text, the SHA-256 of the canonical text; the text/csv attachment becomes an attachments entry with its type and SHA-256, and its file name is not signed. Notes: grey text is not signed; a signed message has no text/html part, no nested multipart and no message part; Bcc is not signed either, and a recipient only on the envelope gets not verified, not_addressed.
Figure 3. What the agent signature covers, shown on the message of Appendix A.

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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 in a@x.example, , b@x.example, is malformed too.
  5. 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.
  6. Keep header order. Drop later duplicates.
  7. 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):

  1. Unfold, as above.
  2. 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.

  1. Decode the body part's transfer encoding, strictly. Attachments are decoded the same way.
    • quoted-printable follows 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, 8bit and binary are taken octet for octet. Verifiers do not check that 7bit data is 7-bit.
  2. 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.
  3. Split into lines at CRLF, CR or LF.
  4. Remove trailing SP and HTAB from each line.
  5. Remove empty lines at the end.
  6. Join the lines, ending each with CRLF. Empty text is the empty string.
  7. 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.

These are the rules libsodium applies by default. Signers that follow RFC 8032 meet them.

6.6 Signing#

The signer:

  1. 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.
  2. picks the credential for the From address. It SHOULD NOT sign with a credential that has less than 6 hours left before exp, so the message has time to arrive. Verifiers do not check this.
  3. builds the signing input from those bytes, signs it, and adds Colophon-Signature.
  4. hands the message to the mail system, which adds DKIM with d= equal to the From host.

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:

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

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

  3. 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-Results field (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 as header.d. The i= identity, reported as header.i, does not count on its own: it may name a subdomain of d=.

    Some boundary servers report only header.i. A verifier that has only header.i for a passing signature recovers its d= from the message. The signature's identity is its i= tag, decoded from DKIM quoted-printable (RFC 6376 §2.11), or @ and its d= when it has no i=. A field has no identity if its tag list does not parse (RFC 6376 §3.2), a tag appears twice, it has no d=, or its i= has an = that is not followed by two hexadecimal digits. If exactly one DKIM-Signature field in the message has an identity equal to header.i, the signature counts for that field's d=. 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.

  4. 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 +tag form, 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.

A column of seven stages between the message, as this receiver's server accepted it, and the verdict verified. Steps 1 to 3, structure: one Colophon-Signature header, one From address. Step 4, aligned DKIM: a DKIM pass for the From host, before any DNS lookup. Steps 5 to 8, parse: the header and the credential; iss is the From host. Steps 9 to 11, the one DNS lookup: the domain key at kid._colophon.From host. Steps 12 to 15, credential: its signature, claims and time window; from is From. Steps 16 to 19, message: layout, signing input, agent signature. Steps 20 and 21, this mailbox: listed in To or Cc; not verified here before. Every stage has a dashed line marked fails to one verdict: not verified, with the reason of the first failing step.
Figure 4. The steps below, in stages.
# 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:

7.3 Verdicts#

There are two verdicts.

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#

Which record is the key. The answer to the lookup is a set of TXT records. The verifier:

  1. joins each record's character strings, with nothing between them;
  2. parses each record as a tag list, as §6.2 does;
  3. discards every record that has an octet above 0x7F, does not parse, has a duplicate tag, or has no v=COLOPHON0 tag. These are not Colophon records;
  4. 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;
  5. if the record has no k=ed25519 tag, or no p tag, stops with key_not_found;
  6. if p is empty, stops with key_revoked;
  7. if p is not strict base64url of exactly 32 octets, or they are not a valid public key (§6.5), stops with key_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.

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.

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

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.

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:

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.

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#

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:

No other registration is sought:

14. Relation to A2A and Colophon Tasks#

A2A. Colophon is independent of A2A (the Agent2Agent protocol).

Colophon Tasks. A later document adds tasks on top of v0:

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.

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

  2. 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 the Colophon-Signature field 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.

  3. The formal model (Appendix C) is planned. v0 can be final without it. Colophon Tasks waits for it.

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

  5. 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 of boundary, charset and format break 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_found and key_revoked each 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 leave dns_limited out of the vectors.
    • DKIM identity (§7.1). Decided: a passing signature counts for its d=. A verifier whose boundary server reports only header.i recovers d= from the one DKIM-Signature field whose identity equals header.i, and counts nothing if there is not exactly one. Not chosen: count d= from a field whose d= is the domain of header.i and whose identity equals header.i, even when other fields share that identity: a signature by a parent domain with i= in a subdomain could then make an unverified field for the subdomain count; or header.d only, so that such a verifier must check DKIM itself.
    • Replay store unavailable (§7.6). Decided: a new reason, replay_unchecked, reported as temperror, which a receiver may hold and verify again within D (§7.8). Not chosen: report it as replay, a fail; 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 is replay_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 whose i= is not valid quoted-printable, has no identity. Not chosen: compare i= 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_found is.
    • Parameters (§6.1). The RFC 2231 form of boundary, charset or format breaks 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:

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.

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.

v0-05#

v0-04#

v0-03#

v0-02#

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.