The Katzenpost directory authority protocol.

Directory authority specification

Introduction

Katzenpost is a project for building mixnets tuned to different uses1. Each network starts with a fixed group of computers called directory authorities (DAs). We usually call them dirauths. Different parties operate these dirauths, whether or not they trust one another. Their shared configuration defines the network and which nodes may join it.

Namenlos, the first public post-quantum hybrid mixnet, is one Katzenpost deployment2. Operators create other networks by choosing parameters and allowed nodes, then distributing configurations to authorities, other servers, and end users.

The dirauths publish a signed network consensus document, or consensus, for each epoch, a fixed-length time period. Publication requires a simple majority to sign the same document and the network constraints to be met. The consensus gives users the network’s agreed view for that epoch. It supplies node keys, topology, and traffic rates for Katzenpost Sphinx packets. It is the network’s public-key infrastructure (PKI) document.

Nodes submit signed descriptors that announce their identities, addresses, keys, and roles. Authorities accept announcements only from allowed identities and include those that receive enough votes. Applications on one host SHOULD share one local kpclientd daemon. The daemon verifies the consensus before it uses the network. Nodes renew their announcements, and authorities repeat the process for the next epoch.

Namenlos network map

Configured Namenlos nodes and packet paths
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
flowchart TB
    %% panel: network
    accTitle: Configured Namenlos nodes and packet paths
    accDescr: Six configured directory authorities publish a consensus with at least four signatures. Users verify it through kpclientd. Packets use one of four gateways and one mix per layer. The mix layers contain two, two, and three mixes. Four service nodes host mixnet services. Courier services use four storage replicas. Each node role has its own icon and each node has its configured name.
    subgraph CONTROL["Directory authorities"]
        direction TB
        CONFIG["Shared authority configuration<br/>and allowed node identities"] --> AUTH["6 directory authorities<span class='node-roster authorities'><span class='node-badge role-authority'>annares</span><span class='node-badge role-authority'>gnunet</span><span class='node-badge role-authority'>luscus</span><span class='node-badge role-authority'>respectmy</span><span class='node-badge role-authority'>wauland</span><span class='node-badge role-authority'>windfall</span></span>"]
        AUTH -->|"4 or more matching signatures"| CONS["<img src='/docs/specs/dirauth/icons/directory.svg' alt='' width='32' height='32'/><br/>Consensus for one epoch<br/>Node keys, topology, and rates"]
    end
    subgraph PATH["Mixnet packet paths"]
        direction TB
        CLIENT["<img src='/docs/specs/dirauth/icons/client.svg' alt='' width='32' height='32'/><br/>User applications<br/>through kpclientd"] -->|"choose one gateway"| GW["4 gateways<span class='node-roster'><span class='node-badge role-gateway'>allmixedup</span><span class='node-badge role-gateway'>gnunet</span><span class='node-badge role-gateway'>haywood</span><span class='node-badge role-gateway'>waulandgateway</span></span>"]
        GW --> L1["Mix layer 1<span class='node-roster'><span class='node-badge role-mix'>windfall</span><span class='node-badge role-mix'>cryptonymity</span></span>"]
        L1 --> L2["Mix layer 2<span class='node-roster'><span class='node-badge role-mix'>omelas</span><span class='node-badge role-mix'>mixy</span></span>"]
        L2 --> L3["Mix layer 3<span class='node-roster'><span class='node-badge role-mix'>oneeyedman</span><span class='node-badge role-mix'>reed</span><span class='node-badge role-mix'>wauland</span></span>"]
        L3 -->|"choose one service node"| SN["4 service nodes<span class='node-roster'><span class='node-badge role-service'>annares</span><span class='node-badge role-service'>lilly</span><span class='node-badge role-service'>waulandservice</span><span class='node-badge role-service'>windfallservice</span></span>"]
        SN -. "courier storage requests" .-> REP["4 storage replicas<span class='node-roster'><span class='node-badge role-replica'>storagereplica0</span><span class='node-badge role-replica'>storagereplica1</span><span class='node-badge role-replica'>storagereplica2</span><span class='node-badge role-replica'>storagereplica3</span></span>"]
    end
    CONS -. "Consensus to client<br/>via gateway" .-> CLIENT
    class CONFIG,CLIENT local
    class AUTH,CONS directory
    class GW,L1,L2,L3,SN,REP link
    linkStyle 1,8 stroke:#f3bd66,stroke-width:2px
    linkStyle 2,3,4,5,6 stroke:#c3abff,stroke-width:2px
    linkStyle 7 stroke:#7acee9,stroke-width:2px
classDef directory fill:#48351f,stroke:#f3bd66,color:#ffffff
classDef packet fill:#342b4c,stroke:#c3abff,color:#ffffff
classDef link fill:#1c3c4c,stroke:#7acee9,color:#ffffff
classDef local fill:#34383d,stroke:#c3cbd5,color:#ffffff

This network uses the Namenlos configuration. Six dirauths require four matching signatures. Packet routes use one gateway, one mix per layer, and one service node. Only allowed nodes with enough votes enter the consensus. Prerequisites distinguish configured counts from publication minimums.

History

This protocol continues the original Katzenpost PKI design, described in KATZMIXPKI. That design already used configured authorities, node allow-lists, and a majority-signed consensus. The post-quantum hybrid protocol adds new key schemes, encodings, node roles, and voting phases.

Conventions

The glossary defines shared terms. MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY have the meanings in RFC 2119. Reference tables and requirements appear in linked appendices.

The post-quantum hybrid protocol

The network configuration fixes membership and keys. In each voting round, dirauths agree on a consensus of signed directory data. Participants exchange that data over authenticated connections under the security requirements.

Network membership and node roles

Configured authority keys define which directory authorities a participant trusts. The authorities’ shared allow-lists define which other nodes can enter the consensus.

Node roles

The node roles include gateways, which connect clients to the mixnet. Mixes relay packets through layers, and service nodes host application endpoints. Storage replicas support Pigeonhole. The topology groups mixes into ordered layers. Node keys follow the cryptographic schemes.

Prerequisites

Clients need a usable consensus to reach mixnet services under the network prerequisites. Authorities can publish that consensus only when the network requirements are met.

All parties in a network MUST use the same epoch duration, cryptographic schemes, and packet geometry.

Layers is the configured number of mix layers. MinNodesPerLayer is the minimum configured mix count per layer.

At startup and reload, an authority MUST require at least Layers * MinNodesPerLayer configured mixes, one gateway, and one service node. A fixed topology MUST also meet MinNodesPerLayer in each layer. At signing, every mix layer MUST be non-empty. At least one gateway and one service node MUST remain (R-16-6).

One authority cannot complete voting, which needs at least two contributors. Let A be the number of configured authorities. Publication requires floor(A/2)+1 matching signatures (threshold). Replica requirements depend on the storage service (Pigeonhole).

Namenlos configures six authorities, seven mixes across three layers, four gateways, four service nodes, and four storage replicas. Its fixed layers contain two, two, and three mixes.

Client software and configuration

Applications on one host SHOULD share one local kpclientd daemon through the thin_client interface.

kpclientd fetches the consensus through a configured gateway. It MUST verify the consensus before use. The client configuration supplies gateway pins and the authority verification keys. The Namenlos configurations give example values.

Mixnet services

A mixnet service provides an application endpoint on a service node. Clients use an accepted consensus to find the mixnet service and select a route to it.

A service node lists its mixnet services in its descriptor’s Kaetzchen field. Each entry names a capability and a client endpoint. Clients reach the service through the mixnet without revealing their network addresses to it. Authorities copy Kaetzchen into the consensus under R-5-9. Mixnet services have no separate descriptor.

The courier service forwards requests to Pigeonhole storage replicas. Couriers and replicas can fetch the consensus directly from authorities.

Epochs and timing

Epoch boundaries set deadlines for uploads, votes, and publication. Each consensus names the epoch it describes.

Constants and notation

Each consensus covers one fixed-length time period, called an epoch. The current epoch has a number, denoted N. Each epoch has the same duration, denoted P. T is the instant that epoch N starts. Elapsed time is t = now - T. An authority prepares a document for a voting epoch, denoted V. In a normal round, the voting epoch is the next epoch: V = N+1.

The notation reference gives definitions and Namenlos example values.

Time

The network divides time into epochs. Every participant MUST use the same epoch length and starting point.

Epoch 0 starts at Z = 2017-06-01 00:00:00 UTC. Epoch N starts at Z + N*P. The next epoch starts at Z + (N+1)*P. P ranges from 2 minutes to 7 days, with a default of 1200 seconds. R-9-3 defines how a party selects P. Namenlos leaves EpochDuration unset, which selects the default unless the environment overrides it.

Phase boundaries scale with P. operation defaults indexes operation defaults and fixed time values.

Repeating epoch cycle
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
flowchart TB
    %% panel: clock
    accTitle: Repeating epoch cycle
    accDescr: Epoch 0 starts at Z. Each turn lasts P, which is 1200 seconds or 20 minutes in the Namenlos configuration. At each boundary the epoch number increases, the start time advances by P, and elapsed time resets to zero.
    ZERO["Epoch 0 starts at Z<br/>N = 0, T = Z"] --> START(("Start epoch N<br/>at T<br/>t = 0"))
    START --> ELAPSED(("During epoch N<br/>0 <= t < P<br/>P = 20 min<br/>in Namenlos"))
    ELAPSED --> BOUNDARY(("After P seconds<br/>N = N + 1<br/>T = T + P"))
    BOUNDARY -->|"Next epoch<br/>t = 0"| START
    class ZERO,START,ELAPSED,BOUNDARY local
classDef directory fill:#48351f,stroke:#f3bd66,color:#ffffff
classDef packet fill:#342b4c,stroke:#c3abff,color:#ffffff
classDef link fill:#1c3c4c,stroke:#7acee9,color:#ffffff
classDef local fill:#34383d,stroke:#c3cbd5,color:#ffffff

An epoch includes its start and excludes its end. The exact end boundary belongs to the next epoch. The clock also gives till, the time until the next epoch. At an absolute instant now at or after Z, the epoch clock is:

Epoch clock
N       = floor((now - Z) / P)
T       = Z + N * P
t       = now - T
till    = P - t

Requirements R-9-1 through R-9-6.

Directory objects and signatures

Nodes announce their keys in descriptors. Authorities exchange signed proposals and publish a signed consensus. Producers MUST use canonical Concise Binary Object Representation (CBOR). Receivers MUST apply the decoding rules.

Descriptors

A descriptor announces one node’s identity, addresses, and keys. The node signs its upload so authorities can verify who sent it.

Mixes, gateways, and service nodes post a MixDescriptor. Storage replicas post a ReplicaDescriptor.

Descriptor fields

A node announces its identity, addresses, keys, and role in the descriptor fields. Its role determines the required fields.

An authority keeps SoftwareVersion and ContactInfo only when [Server].AcceptDescriptorMetadata is true, which defaults to false. Otherwise, it removes both fields after signature verification, before validation, storage, voting, and comparison.

Upload encoding and signatures

Mix servers and storage replicas use different upload structures. Each structure binds the descriptor to the node’s signature.

The upload types are SignedUpload and SignedReplicaUpload. LoopStats contains MixIdentityHash (32 bytes or null), Epoch (uint64), and SegmentRatios (64-byte segment ID to float64). To verify a signature, the authority decodes the upload, sets Signature to null, and re-encodes it canonically. The consensus preserves these descriptor encodings.

Validation stages

Authorities verify an upload before they admit its descriptor. Consumers also parse keys when they use them.

The upload handler parses the identity key and verifies the signature and session binding. Descriptor validation verifies names, addresses, roles, and the current mix key. An upload receiver accepts empty future mix-key values, but their indexes MUST remain within NumMixKeys. Replica uploads require a non-empty envelope-key map with non-empty values. Upload validation imposes no replica-epoch window. Consumers parse other keys when they use them. Authorities verify retained metadata at upload.

Length and parsing alone do not establish that a public key is non-degenerate. The X25519 parser and X448 parser require the scheme’s public-key length. Sphinx rejects an all-zero derived shared secret during packet construction and unwrapping (construction, unwrapping). CTIDH parsing also rejects the base curve E0 and verifies supersingularity.

Requirements R-5-1 through R-5-11.

Votes and voting certificates

A vote is an authority’s signed proposal for the next consensus. It contains node announcements, traffic rates, notices, and a commitment to a random contribution.

A commitment fixes the contribution before its reveal. A reveal opens that commitment so peers can verify the contribution.

A voting certificate carries a tallied document and the matched signed commitment/reveal pairs the sender holds. Peers use these certificates to reconcile contributions before computing the shared random value.

The consensus document

The consensus describes the network for one epoch. Nodes and clients use it to select peers and apply shared settings.

A consensus lists allowed nodes whose descriptors receive enough votes (R-16-2). It records the full configured set only for replicas. A consumer MUST NOT infer other configured node sets from it. Missing nodes can have failed to upload.

Traffic rates

Traffic rates control packet frequency and mix delays. Authorities vote on the rates that the consensus publishes.

A traffic rate measures events per millisecond. Its inverse is the mean waiting time in an exponential distribution. Rates use float64 values. R-16-3 tallies five rates. R-16-8 derives LambdaG. The mixing strategy defines how these rates control traffic. Authority configuration replaces zero with 0.00025 for each voted rate and rejects negative values. It does not test finiteness (defaults and validation).

Signed document fields

The consensus stores the network description in one signed body. Votes and voting certificates use the same document structure.

The Document structure defines the signed fields. Document validation verifies them in votes, voting certificates, and the consensus.

Full document validation rejects duplicate identities across Topology, GatewayNodes, and ServiceNodes, but not StorageReplicas. An empty PriorSharedRandom is valid only when GenesisEpoch equals Epoch. Consumer validation differs by fetch path (gateway consensus transfer).

The decoder treats null and absent keys as empty. An encoder MUST preserve the distinctions in the field reference.

Votes supply five rates, descriptors, and notices. Reconciled commit/reveal pairs supply the shared random value (SRV), which seeds automatic topology assignment. Epoch is V. R-18-7 to R-18-9 define genesis and history. LambdaG, geometry hash, scheme, and configured replicas come from local configuration and MUST agree across authorities. Signatures remain outside the body in its certificate envelope. Descriptor nesting follows upload encoding. Commit/reveal map values contain serialized certificate envelopes.

R-6-5 limits mixing delays. SafetyCap is 27632 milliseconds at Mu = 0.001 and 110525 at Mu = 0.00025. Debug.DisableServicePreDelay disables the service-node hold. The authority’s [Notice] table defaults to empty.

Deterministic CBOR

Producers MUST encode equal data as identical bytes. Deterministic CBOR defines the encoding that every participant uses.

Producers MUST encode descriptors, documents, and certificate envelopes with deterministic CBOR.

Requirements R-6-1 through R-6-7.

Keys and schemes

Identity signatures authenticate directory data. Link and Sphinx keys protect different parts of mixnet communication.

Nodes and authorities have identity signing keys and link key encapsulation mechanism (KEM) keys. Mix servers also have Sphinx keys per epoch. Replicas have envelope non-interactive key exchange (NIKE) keys per replica epoch. Sphinx uses NIKE or KEM keys. Every network uses the same replica envelope scheme. Parties MUST configure matching schemes. Signature scheme details give component sizes and platform defaults. The Namenlos profile defines the deployed byte formats.

Certificate envelopes and signing

A certificate envelope holds data and the signatures that authenticate it. Several authorities can sign the same data.

Envelope fields

The envelope separates the payload from its signatures. Authorities can add signatures without changing the payload.

A certificate envelope holds a payload and its signatures. The implementation defines five fields:

  • Version, a uint32 that is 0
  • Expiration, an epoch
  • KeyType, the scheme name
  • Certified, a byte string containing the payload bytes
  • Signatures, a map from signer hash to PublicKeySum256 and Payload

Producers MUST use canonical CBOR and the configured signature scheme name. Receiver decoding and envelope verification define accepted encodings and signature verification. The decoded envelope Version MUST be zero. KeyType MUST be nonempty and forms part of the signed message. The document schema has a separate Version field, “v1”.

Certificate signature input
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
flowchart TB
    %% panel: signer
    accTitle: Certificate signature input
    accDescr: Version, expiration, scheme name, and certified payload form the signed message. The envelope stores signatures outside that message.
    META["Version<br/>Expiration<br/>KeyType<br/>Certified: payload bytes"] --> MSG["signed message bytes"]
    MSG --> SIGN["sign with the identity key"]
    SIGN --> SIG["store signature in Signatures"]
    class META,MSG,SIG local
    class SIGN directory
classDef directory fill:#48351f,stroke:#f3bd66,color:#ffffff
classDef packet fill:#342b4c,stroke:#c3abff,color:#ffffff
classDef link fill:#1c3c4c,stroke:#7acee9,color:#ffffff
classDef local fill:#34383d,stroke:#c3cbd5,color:#ffffff

Certified contains document CBOR, a raw 40-byte commit/reveal, or one CBOR cert.Signature for sig commands. Descriptor uploads use separate structures.

Signature input

Every signature covers the same four envelope fields. The signature map itself remains outside that input.

The signed message concatenates four fields with no separator and no length prefix:

signed_message = LE_uint32(Version)
              || LE_uint64(Expiration)
              || KeyType, the UTF-8 bytes of the scheme name
              || Certified

Requirements R-7-1 through R-7-7.

Byte identity

Authorities can combine signatures only when they build identical consensus bytes. Shared encoding, ordering, and configuration make this possible.

Four threshold gates and consensus byte identity
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
flowchart TB
    %% panel: authority
    accTitle: Four threshold gates and consensus byte identity
    accDescr: With six authorities, require four matching parameter votes, four voting certificates, four surviving commit/reveal pairs, and four valid signatures. Local configuration and history also enter the signed bytes.
    QV{"4 matching parameter<br/>votes"} --> QC{"4 voting certificates"}
    QC --> QR{"4 surviving commit/reveal<br/>pairs"}
    INPUTS["Inputs<br/>Voted and reconciled values<br/>Carried history<br/>Local configuration"]
    QR --> INPUTS
    INPUTS --> BODY["Build the signed body"]
    BODY --> CBOR["Canonical CBOR bytes"]
    CBOR --> MSG["Certificate signing bytes"]
    MSG --> SIG["Signatures over identical bytes"]
    SIG --> TH{"4 valid signatures over<br/>identical bytes"}
    TH --> PUB["<img src='/docs/specs/dirauth/icons/directory.svg' alt='' width='32' height='32'/><br/>Published consensus"]
    class QV,QC,QR,BODY,SIG,TH directory
    class INPUTS,CBOR,MSG,PUB local
classDef directory fill:#48351f,stroke:#f3bd66,color:#ffffff
classDef packet fill:#342b4c,stroke:#c3abff,color:#ffffff
classDef link fill:#1c3c4c,stroke:#7acee9,color:#ffffff
classDef local fill:#34383d,stroke:#c3cbd5,color:#ffffff

Namenlos has A = 6. The general threshold is floor(A/2)+1. Document inputs, CBOR, signature input, SRV ordering and wire encoding MUST produce identical bytes. Different configuration or history can prevent signatures from combining.

Requirements R-26-1 through R-26-8.

A successful voting round

Nodes announce their keys for the next epoch. In a successful round, dirauths agree on one signed consensus. Clients fetch it through gateways and verify it before use.

Directory round
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
sequenceDiagram
    %% panel: network
    %% actors: N=other-servers,D=authority,G=gateway,C=client
    accTitle: Directory round
    accDescr: Mixes, service nodes, replicas, and gateways upload signed descriptors. Dirauths compare votes, reveal randomness, and collect majority signatures. Servers fetch the signed consensus. Clients fetch it through gateways and verify it before use. Each role follows its configured fetch schedule.
    autonumber
    box rgb(26,40,48)
        participant N as Mixes<br/>Service nodes<br/>Replicas
    end
    box rgb(49,38,23)
        participant D as Dirauths
    end
    box rgb(26,40,48)
        participant G as Gateway
    end
    box rgb(45,48,54)
        participant C as Client<br/>kpclientd
    end
    loop One successful round
        N->>D: Upload signed<br/>descriptors
        G->>D: Upload signed<br/>descriptor
        D->>D: Compare votes
        D->>D: Reveal randomness
        D->>D: Sign the same<br/>consensus
        D->>D: Publish at majority
        N->>D: Fetch consensus
        D-->>N: Signed consensus
        G->>D: Fetch consensus
        D-->>G: Signed consensus
        C->>G: Fetch consensus
        G-->>C: Signed consensus
        C->>C: Verify consensus
    end

Phases

Each voting round follows a fixed schedule. Different phases collect node announcements, exchange votes, and publish the next consensus.

The authority state machine starts each phase’s sends and computations at its boundary. The diagram uses P = 1200 seconds. Its times are offsets after T. Phase fractions measure elapsed time from T as a share of P.

Authority phases, node uploads and fetches.

Fetch retries follow R-10-9 to R-10-11. The descriptor upload rules are in R-10-3.

Epoch phase schedule
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
sequenceDiagram
    %% panel: clock
    %% actors: N=other-servers,D=authority,G=gateway,C=client
    accTitle: Epoch phase schedule
    accDescr: Node uploads end at 125 seconds during Phase_DESC. Dirauth phases advance at 150-second boundaries. Mixes, gateways, and service nodes fetch at 750 seconds. Couriers, replicas, and clients fetch at 825 seconds. Clients fetch through gateways. Mixes, gateways, and service nodes admit next-epoch peers strictly after 1050 seconds. The next epoch starts at 1200 seconds.
    box rgb(26,40,48)
        participant N as Mixes<br/>Service nodes<br/>Replicas
    end
    box rgb(49,38,23)
        participant D as Dirauths
    end
    box rgb(26,40,48)
        participant G as Gateway
    end
    box rgb(45,48,54)
        participant C as Client<br/>kpclientd
    end
    Note over N,C: 0-150 s: Phase_DESC
    N->>D: Until 125 s:<br/>signed descriptors
    G->>D: Until 125 s:<br/>signed descriptor
    Note over D: 150-300 s:<br/>Phase_VOTE
    D->>D: Send votes
    Note over D: 300-450 s:<br/>Phase_REVEAL
    D->>D: Send reveals
    Note over D: 450-600 s:<br/>Phase_CERT
    D->>D: Tally and send<br/>certificates
    Note over D: 600-750 s:<br/>Phase_SIG
    D->>D: Build and sign<br/>the consensus<br/>Exchange<br/>signatures
    Note over N,C: 750-1050 s: Phase_PUB
    D->>D: Publish N+1<br/>at threshold
    N->>D: Mixes and<br/>service nodes:<br/>fetch N+1<br/>from 750 s
    D-->>N: Consensus N+1
    G->>D: Fetch N+1<br/>from 750 s
    D-->>G: Consensus N+1
    N->>D: Couriers and<br/>replicas:<br/>fetch N+1<br/>from 825 s
    D-->>N: Consensus N+1
    C->>G: Fetch N+1<br/>from 825 s
    G-->>C: Consensus N+1
    Note over N,C: 1050-1200 s: Phase_TAIL
    N->>N: Mixes and<br/>service nodes:<br/>admit N+1 peers<br/>strictly after<br/>1050 s
    G->>G: Admit N+1 peers<br/>strictly after<br/>1050 s
    D->>D: Gone if N+1<br/>is missing
    Note over N,C: 1200 s: Epoch N+1 starts

Each send window starts at its phase boundary and ends according to R-10-1 and R-10-8.

Requirements R-10-1 through R-10-14.

Phase_DESC. Descriptor upload

Nodes MUST attempt descriptor uploads to every configured authority. Retries MAY omit authorities that already accepted that descriptor. Nodes MAY fetch a consensus from any configured authority.

Nodes send signed announcements with post_descriptor or post_replica_descriptor. After upload validation, the authority applies the descriptor storage rules. It compares an existing descriptor before it tests whether it voted or published.

Descriptor storage and replay
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
stateDiagram-v2
    %% panel: authority
    direction TB
    accTitle: Descriptor storage and replay
    accDescr: A dirauth stores one live descriptor per command epoch and node identity after upload validation. The authority persists an accepted upload before retaining its descriptor. Identical canonical descriptors succeed even after voting or publication. A finality race can persist an upload without retaining its descriptor.
    state "No live descriptor" as Empty
    state "Descriptor retained" as Stored
    [*] --> Empty
    Empty --> Stored: No own vote<br/>or consensus:<br/>persist and retain,<br/>DescriptorOk 0
    Empty --> Empty: Own vote or consensus<br/>before or after writing:<br/>DescriptorConflict 2
    Stored --> Stored: Same: DescriptorOk 0<br/>Changed:<br/>DescriptorConflict 2

An authority MUST serialize uploads per node identity across epochs. Different identities MAY proceed concurrently. DescriptorInternalError triggers an uploader retry. A failure to persist the upload is fatal to the authority. DescriptorInvalid, DescriptorForbidden, and DescriptorConflict are final for that authority, as is a strict QUIC pin mismatch (R-24-11). R-14-10 defines durable storage before acknowledgment.

Requirements R-14-1 through R-14-10.

Phase_VOTE, Phase_REVEAL. Commit and reveal

Authorities exchange random contributions in two steps. Each authority commits to a value before it reveals that value.

With RN a 32-byte random value and H = BLAKE2b-256, each commit or reveal is 40 bytes:

Commit and reveal values
RN     = 32 bytes from the random source
REVEAL = BE_uint64(epoch) || H(RN)
COMMIT = BE_uint64(epoch) || H(REVEAL)
Commit and reveal exchange
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
sequenceDiagram
    %% panel: authority
    %% actors: A=authority,B=authority
    accTitle: Commit and reveal exchange
    accDescr: Dirauth 1 sends its vote and commitment to Dirauth 2. During the reveal phase it sends the signed reveal. Dirauth 2 checks the reveal against the stored commitment and returns the relevant status.
    box rgb(49,38,23)
        participant A as Dirauth 1
    end
    box rgb(49,38,23)
        participant B as Dirauth 2
    end
    Note over A,B: Phase_VOTE, from P/8
    A->>B: vote 22, Epoch V, PublicKey<br/>Document with one commit
    B-->>A: vote_status 23, VoteOk 0
    Note over A,B: Phase_REVEAL, from 2/8
    A->>B: reveal 25, PublicKey<br/>Signed 40-byte reveal
    B->>B: Verify reveal<br/>against stored<br/>commit
    B-->>A: reveal_status 26, RevealOk 8
    Note over B: No stored commit:<br/>RevealTooEarly 9<br/>From 3/8:<br/>RevealTooLate 13

R-15-8 prevents an authority from contributing a late commit during the reveal phase. Without this restriction, it could observe other reveals before deciding whether to contribute its own committed value.

Requirements R-15-1 through R-15-8.

Phase_CERT, Phase_SIG. Tabulation

Each authority counts votes for node announcements and traffic settings. Only results with enough matching votes enter the consensus.

The tally compares descriptor and parameter bytes. R-16-3 keeps signed zeros separate and groups NaNs. History comes from the prior consensus, not a vote tally. R-16-6 separates startup provisioning from per-epoch topology usability.

Requirements R-16-1 through R-16-8.

Phase_CERT. Voting certificates

Authorities exchange certificates that summarize the contributions they received. Other authorities verify this evidence before they compute the shared random value.

A voting certificate contains the tallied document and commit/reveal pairs in a signed envelope. Authorities send it between the reveal and signature phases. The receiver verifies voting certificates before it computes the SRV.

Voting certificate exchange
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
sequenceDiagram
    %% panel: authority
    %% actors: A=authority,B=authority
    accTitle: Voting certificate exchange
    accDescr: Dirauth 1 tallies its votes and sends its voting certificate. Dirauth 2 verifies the cited signatures and pairs under the document validation rules, then returns a certificate status.
    box rgb(49,38,23)
        participant A as Dirauth 1
    end
    box rgb(49,38,23)
        participant B as Dirauth 2
    end
    Note over A,B: Phase_CERT, from 3/8
    A->>A: Tally votes<br/>Build full<br/>document
    A->>B: cert 29, Epoch V, PublicKey<br/>Document with every held pair
    B->>B: Verify cited<br/>signatures from<br/>authorized<br/>authorities
    B->>B: Validate reveals<br/>against commits
    B-->>A: cert_status 30, CertOk 14
    Note over B: Validation failure:<br/>CertNotSigned 17<br/>Sender's vote<br/>missing:<br/>CertTooEarly 19<br/>From 4/8:<br/>CertTooLate 15

Document validation treats exactly one commit as a vote and rejects a reveal alongside it. A certificate built from matched commitments and reveals needs at least two pairs to pass validation. The zero-commit receiver case does not inspect reveals. A one-authority network cannot complete this phase. An authority sends no voting certificate if its tally lacks a usable topology or it has exactly one pair. It still advances to the next phase.

Reconciliation compares complete serialized commit and reveal certificate envelopes, after signature verification. Equal 40-byte payloads do not establish envelope equality. Differences in signatures or other envelope bytes can produce a conflict. R-6-1 permits an additional signature during individual-envelope verification. It does not change the equality comparison during reconciliation. The SRV calculation uses the extracted 40-byte reveal, not its envelope.

Requirements R-17-1 through R-17-9.

Phase_SIG. Shared random value and topology

Authorities combine verified random contributions into one shared value. This value controls automatic mix placement and links the round to the previous round.

The authority needs at least a threshold of voting certificates before reconciliation. It needs the same threshold of surviving commit/reveal pairs before it computes the SRV. The epoch in this calculation is the voting epoch V. H is BLAKE2b-256 and PREV is the previous epoch’s SRV or 32 zero bytes. The authority computes the shared random value as follows:

SRV = H( "shared-random"
      || BE_uint64(epoch)
      || identity_hash(1) || reveal(1)
      || ...
      || identity_hash(k) || reveal(k)
      || PREV )

Each reveal is the 40-byte certified payload of that authority’s signed reveal. R-18-2 gives the order of the k pairs.

The Namenlos authority config pins three fixed layers. With gateways configured, document construction requires a fixed Topology. Omitting it reaches a nil topology during LambdaG calculation. The automatic assignment subroutine defines automatic layer placement. An empty resulting layer prevents signing.

Shared random value and automatic topology
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
flowchart TB
    %% panel: authority
    accTitle: Shared random value and automatic topology
    accDescr: Hash the domain label, voting epoch, ordered identity and reveal pairs, and previous shared random value. The output seeds the automatic topology draw, which also uses prior placement when available.
    INPUT["Inputs in byte order:<br/>label shared-random<br/>BE_uint64(V)<br/>identity / reveal pairs in R-18-2 order<br/>previous SRV or 32 zero bytes"] --> HASH["BLAKE2b-256"]
    HASH --> SRV["SharedRandomValue:<br/>32 bytes"]
    SRV --> STREAM["ChaCha20:<br/>8-byte zero nonce"]
    STREAM --> DRAW["Automatic topology draw<br/>with prior placement, if held"]
    DRAW --> TOPOLOGY["Topology:<br/>mix layers"]
    class INPUT,HASH,SRV,STREAM,DRAW,TOPOLOGY directory
classDef directory fill:#48351f,stroke:#f3bd66,color:#ffffff
classDef packet fill:#342b4c,stroke:#c3abff,color:#ffffff
classDef link fill:#1c3c4c,stroke:#7acee9,color:#ffffff
classDef local fill:#34383d,stroke:#c3cbd5,color:#ffffff

Requirements R-18-1 through R-18-10.

The topology draw and layer assignment define the complete automatic placement algorithms.

Phase_SIG, Phase_PUB. Signatures and publication

Each authority signs the consensus it built. Matching signatures let the authorities publish that document for the network.

Signature receipt and publication
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
sequenceDiagram
    %% panel: network
    %% actors: A=authority,B=authority,N=publisher
    accTitle: Signature receipt and publication
    accDescr: Each dirauth signs its own consensus and sends the signature to every peer. A receiver verifies the envelope before storing the inner signature. At publication it attaches only signatures that authenticate its own consensus, then publishes at threshold.
    box rgb(49,38,23)
        participant A as Dirauth 1
    end
    box rgb(49,38,23)
        participant B as Dirauth 2
    end
    box rgb(26,40,48)
        participant N as Fetching node
    end
    Note over A,B: Phase_SIG, from 4/8
    A->>A: Tally votes<br/>Compute SRV<br/>Build and sign<br/>doc V
    A->>B: sig 27, Epoch V<br/>Signed cert.Signature envelope
    B->>B: Verify envelope<br/>Parse and store<br/>inner signature
    B-->>A: sig_status 28, SigOk 20
    Note over A,B: Every dirauth sends to every peer
    Note over A,B: Phase_PUB, at 5/8
    A->>A: Verify stored<br/>inner signatures<br/>against own<br/>consensus
    A->>A: Attach valid<br/>signatures<br/>At threshold:<br/>publish and<br/>persist doc V
    N->>A: get_consensus 18, Epoch N+1
    A-->>N: consensus 19, ConsensusOk 0

Requirements R-19-1 through R-19-4.

Receiver states, failures, and retention

Receiver state controls message acceptance. Epoch windows limit how long documents and keys remain usable.

The authority state machine

After startup or restart, an authority enters bootstrap to select its voting epoch. Each authority runs the same sequence of voting steps. Timers move the authority from one step to the next. If processing misses a deadline, the next timer fires immediately. Remaining phases run in sequence. If an action panics, the authority recovers and retries that state after one second.

Authority state machine
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
stateDiagram-v2
    %% panel: authority
    accTitle: Authority state machine
    accDescr: Bootstrap selects a voting epoch. Descriptor, vote, reveal, certificate, and signature states advance on timers. Publication succeeds at threshold or returns to bootstrap.
    state "Signatures" as accept_signature
    [*] --> bootstrap: start or<br/>restart
    bootstrap --> accept_desc: t <= P/8, V =<br/>N+1
    bootstrap --> bootstrap: t > P/8, wait<br/>for next epoch
    accept_desc --> accept_vote: 1/8, send vote
    accept_vote --> accept_reveal: 2/8, send<br/>reveal
    accept_reveal --> accept_cert: 3/8, tally and<br/>send cert
    accept_cert --> accept_signature: 4/8, build and<br/>sign consensus
    accept_signature --> accept_desc: 5/8, threshold<br/>reached,<br/>publish
    accept_signature --> bootstrap: 5/8, no<br/>threshold

Requirements R-12-1 through R-12-5.

Acceptance windows and status codes

An authority accepts messages only for the round it is processing. Response codes tell a sender whether to retry or stop.

An authority can publish consensus N during the preceding epoch, N-1. Its scheduled publication time is T - 3*P/8, before epoch N starts. After successful publication, it advances V to N+1 and can accept votes and commits for that voting epoch.

Vote, reveal, cert, and sig handlers use V and authority state to decide acceptance. They do not compare elapsed time. Handlers evaluate phase and duplicate status only for messages at epoch V. Vote, voting certificate, and signature handlers verify the command’s Epoch field. The reveal handler verifies the epoch in the signed payload and ignores the command’s Epoch field.

After building its voting certificate, an authority can still accept a valid vote for the consensus tally until it enters the signature state. Its existing voting certificate does not change. NotFound permits a retry. Gone is final for N+1 and past epochs. A courier also retries Gone for its current epoch.

Requirements R-13-1 through R-13-3.

Clocks and retention

Authorities manage clock and retention settings outside this protocol. Operators SHOULD keep clocks within a few seconds of UTC. The timing margins depend on delivery time, retries, and successful preceding phases. They are not clock synchronization targets.

A reveal, detached-signature envelope, or commit for N+1 passes expiration verification only through the receiver’s epoch N. A document, vote, or voting certificate for N remains unexpired through epoch N+4. Other validation rules still apply.

The replica clock gives RE = floor((now - Z) / 604800 seconds), independently of P. A client MAY adjust its clock from the gateway handshake.

Independent epoch and key lifetimes
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
flowchart TB
    %% panel: clock
    accTitle: Independent epoch and key lifetimes
    accDescr: Mixnet epochs have length P. Mix servers retain keys N-1 through N+3 and publish N+1 through N+3. Replica epochs always last seven days. Shared-random history rotates every 504 mixnet epochs, except initialization takes precedence.
    subgraph M["<img src='/docs/specs/dirauth/icons/mix.svg' alt='' width='32' height='32'/><img src='/docs/specs/dirauth/icons/gateway.svg' alt='' width='32' height='32'/><img src='/docs/specs/dirauth/icons/service.svg' alt='' width='32' height='32'/><br/>Mixes, gateways, and service nodes<br/>Mix keys: epoch length P"]
        direction TB
        MK["Retain mix keys N-1 through N+3"]
        MK --> USE["Unwrap with N-1, N, and N+1"]
        USE --> ADV["Advertise N+1 through N+3"]
    end
    subgraph R["<img src='/docs/specs/dirauth/icons/replica.svg' alt='' width='32' height='32'/><br/>Replica clock: 7 days"]
        RK["Publish RE and RE+1,<br/>plus RE-1 while retained"]
        RK --> DEL["Older key deletion:<br/>3-hour grace"]
    end
    subgraph H["<img src='/docs/specs/dirauth/icons/authority.svg' alt='' width='32' height='32'/><br/>Dirauth shared-random history"]
        INIT["Initialize at genesis or empty history"]
        INIT --> ROT["Otherwise rotate every 504 epochs"]
    end
    M ~~~ R ~~~ H
    class MK,USE,ADV packet
    class RK,DEL local
    class INIT,ROT directory
classDef directory fill:#48351f,stroke:#f3bd66,color:#ffffff
classDef packet fill:#342b4c,stroke:#c3abff,color:#ffffff
classDef link fill:#1c3c4c,stroke:#7acee9,color:#ffffff
classDef local fill:#34383d,stroke:#c3cbd5,color:#ffffff

504 epochs equal seven days only at P = 1200 seconds. Replica epochs do not scale with P.

Requirements R-11-1 through R-11-6.

Connections and messages

Directory commands travel over encrypted links. Each connection also applies peer authorization and resource limits.

Transport layers

Directory commands carry signed CBOR objects directly over PQ Noise, outside Sphinx packets. The direct consensus reply gives the directory encoding and link records. PQ Noise protects each connection over TCP or QUIC.

Descriptor map keys and URL schemes select transports and address protocols. The mix server’s address conversion places tcp:// IPv4 addresses under tcp4 and tcp:// IPv6 addresses under tcp6.

Mix-server dialing selects map keys tcp4, tcp6, and quic (transport selectors). A tcp entry alone cannot provide a mix-server destination. Replicas instead preserve the lowercased URL scheme as the descriptor map key (replica conversion). Their outgoing connections use ClientTransports, which includes tcp (replica dialing). Upload validation accepts more URL schemes than mix servers publish (R-5-6).

Authorities use configured address lists, not descriptors. Their listeners accept tcp:// and quic:// URLs only (listeners). Mixes, gateways, and service nodes MUST advertise a reachable tcp4 or tcp6 entry. Replicas MUST advertise a reachable tcp entry. Authorities MUST advertise a reachable tcp:// endpoint. A dialer uses QUIC only with the peer’s pinned QUIC key hash (QUIC transport).

PQ Noise carries commands over each transport. An authority PKI session carries one command and one reply unless configuration enables persistent peer connections (R-22-15). kpclientd uses a persistent gateway session for packets, replies, and consensus retrieval (connection loop).

An authority MUST accept only commands allowed for the sender’s role. It MUST drop others without reply (R-23-4).

Wire protocol

A command carries one request or response. PQ Noise encrypts each command and its length before it sends them.

Commands travel over the PQ Noise link: pattern PqXX, ChaChaPoly, BLAKE2b-512, the configured KEM and a one-byte prologue 0x03. The handshake profile defines its four flights. The responder MUST send NoOp as its first transport command. Each command uses two encrypted Noise transport messages in the data-transfer phase. The first encrypts the four-byte big-endian command-ciphertext length. Its tag makes this header 20 bytes. The second encrypts the command with a separate 16-byte tag. The length includes the second tag. Each endpoint MUST rekey the affected direction after both encrypted messages (sender, receiver).

Before encryption, a command has a 6-byte header, command_id (1) || reserved (1, zero) || BE_uint32(body_length), and a body. PublicKey is the sender’s full marshaled identity key. The command reference defines each Payload type.

Six-byte plaintext command header
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
flowchart TB
    %% panel: data
    accTitle: Six-byte plaintext command header
    accDescr: Byte zero is command_id. Byte one is zero. Bytes two through five are the unsigned big-endian body length. The declared body follows, then any zero padding.
    CID["Byte 0<br/>command_id: 1 byte"] --> ZERO["Byte 1<br/>reserved: zero"]
    ZERO --> LENGTH["Bytes 2-5<br/>body length: uint32, big-endian"]
    LENGTH --> BODY["Body: the declared number of bytes"]
    BODY --> PADDING["Optional zero padding"]
    class CID,ZERO,LENGTH,BODY local
    class PADDING local
classDef directory fill:#48351f,stroke:#f3bd66,color:#ffffff
classDef packet fill:#342b4c,stroke:#c3abff,color:#ffffff
classDef link fill:#1c3c4c,stroke:#7acee9,color:#ffffff
classDef local fill:#34383d,stroke:#c3cbd5,color:#ffffff

The wire session selects the Noise primitives. Noise uses BLAKE2b-512. The directory’s H is BLAKE2b-256. The command encoders define the bodies and command constants define their IDs.

Direct authority fetchers send empty additional data to identify an unauthenticated client. kpclientd’s gateway handshake carries a generated queue identifier and keeps the session open.

Gateway consensus transfer

Clients fetch the consensus through their gateway connection. The gateway compresses the document and sends it in chunks.

kpclientd MUST fetch the consensus through a configured gateway with get_consensus2. It uses its existing gateway connection. On success, the gateway gzip-compresses the signed consensus envelope and sends consensus2 chunks numbered 0 through ChunkTotal-1. The chunk capacity comes from the connection’s configured packet geometry. An error uses one empty chunk, ChunkNum 0 and ChunkTotal 1, with the consensus status code below (gateway handler).

The client MUST verify chunk order, totals, and sizes before it decompresses the envelope. It MUST verify the authority signature threshold, requested epoch, and local geometry before using the consensus. Chunks carry one document. Gateway commands have separate padding and size bounds from direct authority sessions.

The direct authority fetcher also calls IsDocumentWellFormed before accepting a consensus (fetch validation). kpclientd’s gateway path uses Deserialize to verify the signature threshold and decode the document (Deserialize). It also verifies epoch and geometry, but does not call IsDocumentWellFormed (client verification).

A client reports Gone only when replies from queried authorities make a signing threshold impossible. With fewer Gone replies, it can retry.

Receive size ceiling

A size ceiling bounds the largest directory command a receiver accepts. The default ceiling depends on network size and cryptographic sizes.

R-22-17 uses EstimateConsensusSize. The ceiling bounds the command ciphertext, including its 16-byte tag. The encrypted 20-byte length header is separate. Let sign and sig be the PKI scheme’s public-key and signature sizes. Let link, sphinx and envelope be the public-key sizes of the wire KEM, Sphinx scheme, and replica NIKE. Let nodes, replicas, and authorities be their counts.

mix      = sign + link + 3 * sphinx + 4096
replica  = sign + link + 3 * envelope + 4096
unsigned = 8192 + nodes * mix + replicas * replica
signed   = unsigned + authorities * (sig + 96)
                   + 2 * authorities * (sig + 96 + 40)
ceiling  = min(500000000, max(262144, 2 * signed))

The factor 3 counts per-epoch keys. The 4096 and 8192 allowances cover names, addresses, service data and framing. The 96 covers the signature key hash and framing. The 40 counts a shared-random value. The second authority term includes commit and reveal envelopes in a voting certificate. The final factor 2 provides headroom.

An authority uses its configured counts of mixes, gateways, service nodes, replicas, and authorities. Before its first consensus, a client uses 64 nodes and 16 replicas. A configured MaxConsensusSize above zero replaces the calculated ceiling for that party.

Requirements R-22-1 through R-22-18.

Authentication and authorization

Authorities distinguish configured nodes from unauthenticated clients. Each role has its own permitted commands.

The authority listener authorizes peers by role. Gateway clients use a separate path. Allow-lists map identity hashes to names, adding ReplicaID for replicas and a pinned link key for authorities. Lookup order is mix/gateway/service, authority, then replica. Unauthenticated fetchers, including couriers and replicas, send empty additional data and a fresh random link key. R-23-7 permits unpinned nodes to upload. Their identity signature and session-key binding remain mandatory.

Requirements R-23-1 through R-23-7.

Authority message delivery

Authorities send each voting message to every peer. A retry loop handles temporary failures while the voting schedule continues.

All four message families use one retry loop. Default backoff starts at 10 ms. A 3-second cap applies before 0.2 jitter, so delays can approach 3.6 seconds (calculation). Phase sends use the deadline, with an additional retry cap when PeerRetryMaxAttempts is positive. Bootstrap defaults to 20 retries plus the initial attempt. After a bounded wait, the loop can start an attempt without testing the deadline again. The state machine advances independently of delivery. A peer needs the sender’s vote and commit before it can accept the reveal.

Requirements R-21-1 through R-21-4.

QUIC transport

A node or authority MAY listen on QUIC as well as TCP. PQ Noise authenticates the peer over one QUIC stream. QUIC also requires a pinned TLS key. The dialer MUST compare SHA-256 of the certificate’s SubjectPublicKeyInfo with QUICKeyHash. For nodes, the hash comes from the descriptor. Authorities use configured hashes. Before its first consensus, a client uses its configured gateway hash.

Mixes, service nodes, and replicas rotate TLS keys each epoch. Each key comes from a random Ed25519 seed in DataDir/quic. Gateways with QUIC listeners and authorities keep one long-term key in DataDir/quic-tls.key. The authority’s -g option writes this key and quic-tls.hash. TCP-only authorities neither read nor create the key file.

The QUIC configuration supplies defaults for ALPN, CurvePreferences, AddressValidation, KeepAlivePeriod, KeepAliveJitter, PinMismatch, and CloseLinger.

Requirements R-24-1 through R-24-15.

Connection bounds and operation

One listener carries descriptor uploads, voting, and consensus retrieval. The listener applies connection limits and deadlines before and after the handshake.

An authority MUST refuse connections above a nonzero pool cap before the handshake. Each epoch, it updates the peer pool from consensus and configured authority addresses. A failed refresh keeps the previous set. A panic in one connection handler MUST NOT stop the authority. Persistent authority connections default to off.

Stored state carries a schema version. An authority MUST refuse to start if that version differs from the expected version. It MUST also refuse to start unless its configured authority list contains its own identity and link key.

An operator MAY reload the authorized node set. Every authority MUST reload the same set in the same epoch, or their votes disagree. An authority MUST reject a reload that would fail startup validation (R-16-6) and keep the old set.

Mixnet packet paths

Sphinx packets protect routing information and payloads through successive hops. For a client forward path, Namenlos uses a gateway, three mixes, and a service node. Each relay removes one Sphinx layer and forwards the packet over a separately authenticated PQ Noise link.

The forward path selects these five hops from the consensus. Sphinx protection spans the route. PQ Noise protects only one neighbor connection. Sphinx packets retain their configured length as each hop unwraps them. The Sphinx packet format defines the byte layout.

Security considerations

Clients trust the configured authority keys. A threshold of authorities can sign a network view that clients accept.

  • Any two signing quorums overlap in at least 2*threshold - A authorities. With a simple majority, this minimum is two for even A and one for odd A. Quorum intersection prevents conflicting signed consensuses only if the overlap exceeds the number of dishonest authorities and honest authorities sign once per epoch. At A = 3 or A = 5, one dishonest signer can belong to both quorums. A simple majority cannot guarantee agreement with two dishonest authorities, regardless of A. Crash and omission faults do not create conflicting signed consensuses if honest authorities sign once per epoch.
  • Verified conflicting commits or reveals exclude their signer from the SRV calculation only (R-17-4). A dishonest authority can still send different descriptor sets to different peers and prevent matching consensus bytes.
  • Authorities with different PriorSharedRandom rules can sign different bytes at multiples of 504. Only a group with threshold matching signatures can publish its document.
  • Nodes can authenticate peers with descriptors from N, N-1, and N-2. A superseded link key can remain valid for up to three epochs while the current consensus still lists that node. Removing a node from all authority allow-lists prevents its inclusion in later consensuses. Nodes reject its older descriptors when they apply a current consensus that omits it (authentication). A key listed only in N+1 becomes eligible strictly after 7/8 of N (R-11-2). A peer key that cannot be marshaled MUST fail authentication without stopping the node.
  • A consensus records accepted descriptors, not live node availability. Clients can select a failed node from a cached document and lose packets sent to it. An absent descriptor can mean a missing upload, insufficient votes, or removal from the allow-lists.
  • A mix creates a Sphinx key pair for each epoch and removes expired keys. The key manager maintains this window. Forward secrecy depends on erasing expired private keys, including any copies. Epoch changes also limit the lifetime of the replay cache.
  • A node retains peer connections while the peer remains authorized by the applicable documents (R-11-2). Authentication and permission to send can change at an epoch boundary.
  • Five authorities require three signatures. Six require four. Both configurations can publish with two authorities unavailable, but not three. Quorum overlap determines agreement when authorities sign conflicting documents.
  • Without node link-key pinning (authentication and authorization) anyone could present a node’s identity hash, hold its per-identity slots and keep it from uploading.
  • With fresh authority connections and no retries, each send phase can require A*(A-1) peer handshakes. Retries can increase this count. HandshakeTimeoutSec defaults to 3 seconds. Persistent authority connections can reduce the count (connection bounds and operation).

Appendix A. Formats and validation

Producers MUST encode CBOR objects using CDDL. Command and transport-record encodings MUST follow ABNF. Encoding and validation rules also govern canonical bytes, signatures, authorization, and epoch acceptance.

Key Value Explanation
Fields Descriptors, documents, envelopes, CDDL Field names, types, null, and omission
Byte identity R-6-3, signature input, R-26-2 through R-26-8, conformance vectors Canonical byte identity
Key validation R-5-1 through R-5-4, R-14-2 through R-14-8, R-23-6 Key sizes, parsing, and identity/session binding
Descriptor fields R-5-5 through R-5-11 Names, roles, addresses, services, and metadata
Network configuration prerequisites, R-16-6, R-26-1 Authority configuration and topology minima
Time time through acceptance windows and status codes, R-18-8, R-18-9 Clock, phase, expiry, and retention windows
Wire protocol wire protocol, ABNF Receive ceiling, command fields, and status values
Retries authority message delivery, R-22-18 Retries and operation budgets
QUIC QUIC transport, option limits below QUIC pinning, streams, rotation, and options
Connections connection bounds and operation Connection pools, handlers, identity slots, and first-command deadline

Configuration defaults and limits come from authority configuration and QUIC options.

Key Value Explanation
Mu, LambdaP, LambdaL, LambdaM, LambdaR 0.00025 Zero selects the default. Negative values fail configuration validation. Finiteness is not tested.
Layers / MinNodesPerLayer 3 / 2 Values at most zero select the defaults. Layers above 3 fail. R-16-6 defines provisioning minima.
AcceptDescriptorMetadata / Notice false / empty
AllowHostnameAddresses false Authority URL addresses require IP literals, except onion URLs. Metrics addresses require IP literals too (policy).
PeerRetryBaseDelay / PeerRetryMaxDelay / PeerRetryJitter 10 ms / 3 s / 0.2 Zero selects the defaults. Jitter follows the delay cap.
QUIC ALPN h3 Empty selects the default. Maximum 255 bytes.
QUIC KeepAlivePeriod / KeepAliveJitter 45 s, or 20 s for clients / 0.25 The period MUST be non-negative. Jitter MUST be at least 0 and less than 1.
QUIC CloseLinger / PinMismatch / AddressValidation 30 s / fallback / always 0 / empty / empty select the defaults. Negative linger fails. Other modes follow R-24-11 and R-24-14.
QUIC CurvePreferences TLS defaults Empty selects the defaults. No duplicate or unknown names.

TLS curve names are X25519, X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, CurveP256, CurveP384, and CurveP521. These local settings do not replace network-wide scheme agreement.

Let M be the positive decompressed limit. Set C = M + 18 + 5 * (floor(M/16384) + 2). The dechunker requires:

  • Sequential chunk numbers starting at zero, with one constant total between 1 and C.
  • A non-empty first payload of size S and total at most ceil(C/S).
  • Every non-final payload exactly S bytes, and the final payload between 1 and S bytes.
  • At most C compressed bytes and M decompressed bytes.

Error responses use a single empty chunk instead. Signature, epoch, and geometry verification follow reassembly.

Appendix B. Existing TLA+ models

Katzenpost includes TLA+ models for authority voting, node state, key retention, and client retransmission.

Key Value Explanation
VotingAuthority VotingAuthority.tla, guide Voting, quorum agreement, equivocation, and chained epochs.
MixNode MixNode.tla, guide Packet processing and scheduler state.
MixKeys MixKeys.tla, guide Key retention, replay state, and restart.
ClientARQ ClientARQ.tla, guide Retransmission and connection changes.

Appendix C. Field and encoding reference

Descriptors and consensus documents carry node identities, network parameters, and cryptographic material. Their encodings determine the signed bytes.

Directory formation overview

Nodes submit descriptors, and authorities reconcile contributions before signing a consensus.

Directory publication
%%{init: {"flowchart":{"diagramPadding":6,"useMaxWidth":false},"gantt":{"useMaxWidth":false},"look":"classic","packet":{"bitWidth":7,"bitsPerRow":48,"rowHeight":48,"useMaxWidth":false},"sequence":{"actorFontSize":14,"actorMargin":24,"diagramMarginX":24,"height":96,"messageFontSize":14,"messageMargin":22,"noteMargin":10,"useMaxWidth":false,"width":128,"wrap":false},"state":{"useMaxWidth":false},"theme":"base","themeCSS":".edgeLabel p { font-family: sans-serif; font-size: 13px; line-height: 1.4; color: #e6edf3; background-color: #34383d; border: 1px solid #c3cbd5; border-radius: 4px; padding: 3px 7px; margin: 0; } .edgeLabel .labelBkg { fill: #34383d; }","themeVariables":{"activationBkgColor":"#48351f","activationBorderColor":"#f3bd66","activeTaskBkgColor":"#48351f","activeTaskBorderColor":"#f3bd66","actorBkg":"#1c3c4c","actorBorder":"#7acee9","actorLineColor":"#7acee9","actorTextColor":"#e6edf3","altSectionBkgColor":"#24282d","background":"#24282d","clusterBkg":"#24282d","clusterBorder":"#c3cbd5","darkMode":true,"edgeLabelBackground":"#24282d","fontFamily":"sans-serif","fontSize":"14px","gridColor":"#59616b","labelBoxBkgColor":"#34383d","labelBoxBorderColor":"#c3cbd5","labelTextColor":"#e6edf3","lineColor":"#c3cbd5","noteBkgColor":"#34383d","noteBorderColor":"#c3cbd5","noteTextColor":"#e6edf3","packet":{"blockFillColor":"#48351f","blockStrokeColor":"#f3bd66","endByteColor":"#c3cbd5","labelColor":"#e6edf3","startByteColor":"#c3cbd5","titleColor":"#e6edf3"},"primaryBorderColor":"#c3cbd5","primaryColor":"#34383d","primaryTextColor":"#e6edf3","secondaryColor":"#34383d","secondaryTextColor":"#e6edf3","sectionBkgColor":"#34383d","sectionBkgColor2":"#1c3c4c","signalColor":"#7acee9","signalTextColor":"#e6edf3","taskBkgColor":"#48351f","taskBorderColor":"#f3bd66","taskTextColor":"#e6edf3","taskTextOutsideColor":"#e6edf3","tertiaryColor":"#24282d","tertiaryTextColor":"#e6edf3","textColor":"#e6edf3","todayLineColor":"#f3bd66"}} }%%
flowchart TB
    %% panel: network
    accTitle: Directory publication
    accDescr: Nodes upload descriptors. Authorities reconcile votes and random contributions, exchange signatures, and serve a threshold-signed consensus.
    N(["<img src='/docs/specs/dirauth/icons/mix.svg' alt='' width='32' height='32'/><img src='/docs/specs/dirauth/icons/gateway.svg' alt='' width='32' height='32'/><img src='/docs/specs/dirauth/icons/service.svg' alt='' width='32' height='32'/><img src='/docs/specs/dirauth/icons/replica.svg' alt='' width='32' height='32'/><br/>Mixes, gateways,<br/>service nodes, and replicas"])
    subgraph D["<img src='/docs/specs/dirauth/icons/authority.svg' alt='' width='32' height='32'/><br/>Dirauths"]
        direction TB
        V["Exchange votes and reveals"] --> C["Reconcile commit and reveal pairs"]
        C --> B["Build the local consensus bytes"]
        B --> S["Exchange signatures"]
        S --> P["Publish at the signing threshold"]
    end
    N -- "Signed descriptors" --> D
    D --> CONS["<img src='/docs/specs/dirauth/icons/directory.svg' alt='' width='32' height='32'/><br/>Signed consensus"]
    CONS -- "get_consensus responses" --> R["<img src='/docs/specs/dirauth/icons/mix.svg' alt='' width='32' height='32'/><img src='/docs/specs/dirauth/icons/gateway.svg' alt='' width='32' height='32'/><img src='/docs/specs/dirauth/icons/service.svg' alt='' width='32' height='32'/><img src='/docs/specs/dirauth/icons/replica.svg' alt='' width='32' height='32'/><br/>Nodes and couriers"]
    class N,R link
    class V,C,B,S,P directory
    class CONS local
classDef directory fill:#48351f,stroke:#f3bd66,color:#ffffff
classDef packet fill:#342b4c,stroke:#c3abff,color:#ffffff
classDef link fill:#1c3c4c,stroke:#7acee9,color:#ffffff
classDef local fill:#34383d,stroke:#c3cbd5,color:#ffffff

The network map includes all configured node roles.

Descriptor fields

Mixes, gateways, and service nodes use MixDescriptor. Storage replicas use ReplicaDescriptor.

Required fields describe producer output. Receiver decoding, descriptor validation, and upload validation define acceptance of missing fields and alternate encodings.

Key Value Explanation
Name text Required in both types.
Epoch uint64 Required in both types.
IdentityKey byte string Required in both types.
LinkKey byte string Required in both types.
MixKeys map from uint64 to byte string Required in MixDescriptor. Maps epochs to keys.
EnvelopeKeys map from uint64 to byte string Required in ReplicaDescriptor. Maps replica epochs to keys.
Addresses map from text to a list of text Required in both types. Maps transports to addresses. Replicas cannot advertise onion addresses.
Kaetzchen map from text to a map from text to any MixDescriptor, null when unset. Service nodes only (R-5-9).
KaetzchenAdvertizedData map from text to a map from text to any MixDescriptor, null when unset.
IsGatewayNode boolean Required in MixDescriptor.
IsServiceNode boolean Required in MixDescriptor.
LoadWeight uint8 Required in MixDescriptor. Reserved.
AuthenticationType text Required in MixDescriptor. Gateways use “tofu”, other nodes use empty text.
Version text MixDescriptor only. The standard producer emits empty text. Receivers also accept other text, including “v0”. ReplicaDescriptor omits this field.
ReplicaID uint8 Required in ReplicaDescriptor.
SoftwareVersion text Both types, omitted when empty. Retained only under AcceptDescriptorMetadata (R-5-11).
ContactInfo text Both types, omitted when empty. Retained only under AcceptDescriptorMetadata (R-5-11).
QUICKeyHash byte string Both types, omitted when empty. Zero to three 32-byte hashes (QUIC transport).

Traffic rates

Consensus traffic rates control packet delays and transmission frequency.

Key Value Explanation
Mu 0.001 Per-hop delay rate.
LambdaP 0.001 Client send rate, with drop decoys when its queue is empty.
LambdaL 0.0005 Client loop decoy rate.
LambdaM 0.0005 Mix loop decoy rate.
LambdaR 0.02 Courier and replica decoy rate.
LambdaG derived (R-16-8) Gateway loop decoy rate.

Signed document fields

The document body contains the topology, traffic rates, and signed directory contributions.

Key Value Explanation
Epoch unsigned integer Required.
GenesisEpoch unsigned integer Required, nonzero.
Mu, LambdaP, LambdaL, LambdaM, LambdaR float Required.
LambdaG float Required.
Topology array of arrays of byte strings Required. One entry per layer, mixes only. An empty layer is null, valid only in a vote.
GatewayNodes, ServiceNodes array of byte strings Required, [] when empty.
StorageReplicas array of descriptor maps Required, [] when empty.
ConfiguredReplicaIDs byte string Omitted when empty.
ConfiguredReplicaIdentityKeys array of byte strings Omitted when empty.
SharedRandomCommit map from a 32-byte string to a byte string Consensus: null. Vote: one commit. Voting certificate: each available commit/reveal pair.
SharedRandomReveal map from a 32-byte string to a byte string Consensus and vote: null. Voting certificate: each available commit/reveal pair.
SharedRandomValue byte string Required, 32 bytes. All zero in votes and voting certificates (shared random value and topology).
PriorSharedRandom array of byte strings Required. Two history values, rotated every 504 epochs. [] in a vote without history (shared random value and topology).
SphinxGeometryHash byte string Required. BLAKE2b-256 of canonical Sphinx geometry.
Version text Required, “v1”.
PKISignatureScheme text Required.
MinClientVersion, ClientNotice text Omitted when empty.

Transport addresses

Descriptor address maps select link transports and endpoint URLs.

Key Value Explanation
tcp4 tcp://, Namenlos: every mix, gateway, and service node TCP over IPv4 for mixes, gateways, and service nodes
tcp6 tcp://, Namenlos: some mixes, gateways, and service nodes TCP over IPv6 for mixes, gateways, and service nodes
tcp tcp://, Namenlos: every storage replica TCP for storage replicas. Also a client transport selector
quic quic://, Namenlos: none QUIC over UDP, with a pinned TLS key (QUIC transport)
onion onion://, Namenlos: one gateway gateway endpoint for clients through Tor
ws ws://, Namenlos: none additional client transport selector

Directory commands

Directory peers exchange descriptors, votes, reveals, certificates, and signatures through numbered wire commands.

Key Value Explanation
get_consensus ID 18. BE_uint64(Epoch) Sent by client, mix, gateway, service node, authority. A replica and a courier send it as clients (authentication and authorization)
consensus ID 19. ErrorCode (1) || Payload: consensus certificate envelope on success Sent by authority, gateway
post_descriptor ID 20. BE_uint64(Epoch) || Payload: SignedUpload CBOR Sent by mix, gateway, service node
post_descriptor_status ID 21. ErrorCode (1) Sent by authority
post_replica_descriptor ID 11. BE_uint64(Epoch) || Payload: SignedReplicaUpload CBOR Sent by replica
post_replica_descriptor_status ID 10. ErrorCode (1) Sent by authority
vote, reveal, cert, sig ID 22, 25, 29, 27. BE_uint64(Epoch) || PublicKey || Payload: certificate envelope Sent by authority
vote_status, reveal_status, cert_status, sig_status ID 23, 26, 30, 28. ErrorCode (1) Sent by authority
get_consensus2 ID 32. BE_uint64(Epoch), upstream padded Sent by kpclientd to gateway
consensus2 ID 31. ErrorCode (1) || BE_uint32(ChunkNum) || BE_uint32(ChunkTotal) || Payload, downstream padded Sent by gateway to kpclientd
get_vote ID 24. reserved

Connection limits

Limits bound message sizes, timeouts, connection pools, and concurrent handlers.

Key Value Explanation
PKI message ceiling when not derived 2 MiB Fallback ceiling.
receive chunk 64 KiB Maximum chunk size.
handshake, receive, and send deadlines 3 seconds, 30 seconds, 30 seconds Operation deadlines.
persistent-connection idle 2 minutes Idle timeout.
accept pools by source address: client, peer, loopback 1024 each 0 means unlimited.
per-address cap in the client and peer pools, IPv6 by /64 64 Client and peer pool limit, with IPv6 grouped by /64.
concurrent handlers 64 A client runs only below half of them, and never blocks.
connections per proven identity 8 Per-identity connection limit.
first-command deadline 5 seconds Time to receive the first command.
hostname resolution per lookup 2 seconds A party never resolves an onion address.

Signature schemes

Ed25519 Sphincs+ concatenates an Ed25519 signature and a SPHINCS+ signature. Ed25519 has a 32-byte public key and a 64-byte signature. SPHINCS+ runs at the sphincs-shake-256f parameter set, with a 64-byte public key and a 49856-byte signature (parameter selection, sizes). The hybrid implementation adds the component sizes. A verifier MUST verify both components (verification). On Windows the authority default is Ed25519 alone. A network that mixes platforms MUST configure the same signature scheme explicitly.

Deterministic CBOR

R-6-3 applies these encoding requirements to descriptors, documents, and certificate envelopes:

  • Map keys MUST use the descriptor fields, document fields, and envelope fields. They MUST be sorted by encoded key length first, then bytewise. This is the canonical order of RFC 7049 section 3.9, retained in RFC 8949 section 4.2.6.
  • Integers MUST use their shortest form. All lengths MUST be definite.
  • Floats MUST use the shortest of float16, float32, and float64 that represents them exactly. NaN MUST be 0xf97e00. Infinities MUST use float16.
  • Each field MUST be present, omitted, or null as the field tables specify. A nil map or list encodes as null. An empty map encodes as {}, and an empty list as []. These are distinct byte sequences.

RFC 8949 section 4.2.3 sorts keys bytewise without comparing length first. A library that supports both orders MUST use the length-first order here.

Reply status codes

A response status tells the sender whether its command succeeded or why it failed.

Key Value Explanation
consensus ConsensusOk 0, ConsensusNotFound 1, ConsensusGone 2 Response conditions.
descriptor DescriptorOk 0, DescriptorInvalid 1, DescriptorConflict 2, DescriptorForbidden 3, DescriptorInternalError 4 Response conditions.
vote VoteOk 0, VoteTooEarly 1, VoteTooLate 2, VoteNotAuthorized 3, VoteNotSigned 4, VoteMalformed 5, VoteAlreadyReceived 6, VoteNotFound 7 Response conditions.
reveal RevealOk 8, RevealTooEarly 9, RevealNotAuthorized 10, RevealNotSigned 11, RevealAlreadyReceived 12, RevealTooLate 13 Response conditions.
cert CertOk 14, CertTooLate 15, CertNotAuthorized 16, CertNotSigned 17, CertAlreadyReceived 18, CertTooEarly 19 Response conditions.
sig SigOk 20, SigNotAuthorized 21, SigNotSigned 22, SigTooLate 23, SigTooEarly 24, SigAlreadyReceived 25, SigInvalid 26 Response conditions.

Protocol components

The protocol combines network configuration, voting, signed objects, and authenticated transport.

Key Value Explanation
Configuration constants and notation, prerequisites constants and prerequisites
Roles services, roles, keys, and schemes services, keys, and schemes
Formats descriptors to certificate envelopes and signing descriptors, documents, and envelopes
Clients client software and configuration applications and kpclientd
Time time to clocks and retention epochs, phases, and clocks
Phases the authority state machine to signatures and publication the authority state machine
Transport transport layers to connection bounds and operation delivery, wire, authentication, and QUIC
Properties byte identity, security considerations byte identity and security

Constants and notation

Epoch and quorum parameters set the voting schedule and signature threshold.

Key Value Explanation
A 6 Number of authorities.
N runtime Current epoch number.
T Z + N * P Start time of epoch N.
threshold 4 floor(A/2)+1. Matching votes or valid signatures required.
dissenters 2 floor(A/2)-1. Logging only, without effect on the result.
P 1200 seconds, default Epoch duration. EpochDuration is unset.
epoch 0 2017-06-01 00:00:00 UTC Start of the first epoch.
V N+1 in a healthy round Authority voting epoch.
t 0 to less than P Elapsed time in N. A fraction such as 5/8 means t = 5P/8.
W 150 seconds P/8. Phase width.
buf 5 seconds Authority retry-loop deadline margin, when time permits.
U 125 seconds 5P/48. Descriptor upload cutoff.
F 825 seconds 11P/16. Client, courier, and storage replica fetch time.
H BLAKE2b-256 Hash function.
identity hash 32 bytes H of the marshaled identity public key.
Layers 3 Number of mix layers.
topology mode fixed: 2, 2, 3 Mixes in each layer.
MinNodesPerLayer 1 Startup minimum per layer.
NumMixKeys 3 Sphinx key epochs per descriptor.
Mu 0.001 Per-hop mixing delay rate.
LambdaP 0.001 Client send rate.
LambdaL 0.0005 Client loop rate.
LambdaM 0.0005 Mix loop rate.
LambdaR 0.02 Courier and replica decoy rate.
PKISignatureScheme Ed25519 Sphincs+ Descriptor and directory signatures.
WireKEMScheme KYBER768-X25519 Link key KEM.
SphinxGeometry.NIKEName x25519 Sphinx mix key scheme.
EnvelopeScheme CTIDH1024-X25519 Replica envelope NIKE, fixed for every network.
NrHops 5 Hops per Sphinx path.
UserForwardPayloadLength 2000 bytes Maximum user payload.

Prerequisites

A network’s configuration supplies trusted authorities, allowed nodes, cryptographic schemes, and transport addresses.

Key Value Explanation
Authorities 6: annares, gnunet, luscus, respectmy, wauland, windfall Fixed and known to every party before the first round.
threshold 4 of 6 Each round needs 4 reachable authorities in agreement, or no consensus.
clocks not specified in these configs Operators synchronize clocks under the clock and skew rules.
EpochDuration unset, default 1200 seconds Select P under R-9-3. Runtime overrides can change it.
allow-lists 7 mixes, 4 gateways, 4 service nodes, 4 replicas Authorities MUST hold identical lists and reload them in the same epoch (connection bounds and operation).
PKISignatureScheme Ed25519 Sphincs+ Descriptor and directory signatures (roles, keys, and schemes).
WireKEMScheme KYBER768-X25519 PQ Noise link key KEM.
SphinxGeometry.NIKEName / KEMName x25519 / empty Selects NIKE Sphinx.
SphinxGeometry 5 hops, 3082-byte packets, 2000-byte user payload All geometry fields MUST agree (byte identity).
link protocol PqXX, ChaCha20-Poly1305, BLAKE2b-512, prologue 0x03 Every party MUST use this PQ Noise protocol and the configured wire KEM (wire protocol).
Addresses IPv4 TCP: all nodes and authorities. IPv6 TCP: some. Nodes and authorities MUST publish a reachable TCP address. IPv6-only peers need IPv6 (transport layers).
authority key pins 6 identity and link key pairs per authority Each authority MUST hold every authority’s identity key and pin its link key, including its own (connection bounds and operation).
DataDir annares: /var/lib/katzenpost/authority/annares-pq-authority Authorities MUST store descriptors and documents durably and stop on storage failure (R-14-10).

Mixnet services

Service nodes advertise mixnet services through the Kaetzchen descriptor field.

Key Value Explanation
echo capability echo, endpoint +echo, 4 service nodes Returns the request payload. The ping tool uses it to test a route and measure delay.
courier capability courier, endpoint courier, 3 service nodes Forwards client read and write requests to Pigeonhole storage replicas and returns their replies.

Node roles

Descriptors identify node roles and their published keys.

Key Value Explanation
mix MixDescriptor Listed in Topology layers.
gateway node MixDescriptor with IsGatewayNode Listed in GatewayNodes.
service node MixDescriptor with IsServiceNode Listed in ServiceNodes.
storage replica ReplicaDescriptor Listed in StorageReplicas.
authority none Listed in every configuration.

Key schemes

Parties use identity signatures, link KEMs, Sphinx keys, and replica envelope keys for separate tasks.

Key Value Explanation
PKI signature Ed25519 Sphincs+. Sizes: public key 96 bytes, private key 192 bytes, signature 49920 bytes. signs descriptors, votes, reveals, voting certificates, and the consensus
wire KEM configured, no default. Sizes: by scheme. the KEM of the PQ Noise link key
Sphinx NIKE or KEM configured, no default. Sizes: by scheme. the scheme of a mix key
replica envelope NIKE CTIDH1024-X25519, public key 160 bytes Fixed for every network.

Client software and configuration

The client daemon loads its listeners, gateway pins, and authority keys from client.toml.

Key Value Explanation
thinclient.toml Dial.Unix.Address = @katzenpost Selects the daemon’s socket through [Dial.Unix] or [Dial.Tcp].
client.toml Listen.Unix.Address = @katzenpost Sets kpclientd’s listener, pinned gateways, authority keys, cryptographic schemes, and Sphinx geometry.

Authority phases

Authorities collect descriptors and exchange voting material on the epoch schedule. The times below are fractions of the epoch duration P. Each phase includes its start and excludes its end.

Key Value Explanation
Phase_DESC 0 to before 1/8 Accept descriptors for N+1.
Phase_VOTE 1/8 to before 2/8 Build and send the vote for V.
Phase_REVEAL 2/8 to before 3/8 Send the reveal.
Phase_CERT 3/8 to before 4/8 Consume reveals, tally, and send the voting certificate.
Phase_SIG 4/8 to before 5/8 Verify voting certificates, compute the SRV, tally again, sign, and send the signature.
Phase_PUB 5/8 to before 7/8 Combine signatures, publish N+1, and set V = N+2.
Phase_TAIL 7/8 to before 1 Answer Gone for N+1 if no document is available.

Phases: Mix, gateway, service node

Mixes, gateways, and service nodes upload next-epoch descriptors before they fetch the next consensus.

Key Value Explanation
Mix, gateway, service node 0 Rotate keys and upload for N+1 until U.
Storage replica Until U Upload the replica descriptor.
Mix, gateway, service node 5/8 Fetch N+1.
Replica, courier, client F Fetch N+1.
Mix, gateway, service node After 7/8 Admit N+1 peers.

Clocks and retention

Epoch windows determine key retention, document expiry, and retry timing.

Key Value Explanation
authority behind a peer, for reveal, cert, sig 145 seconds W - buf - delivery
authority behind a peer, for a vote 295 seconds, 445 seconds 2W - buf - delivery to reach the voting certificate, 3W - buf - delivery to reach the consensus
authority ahead of a peer 600 seconds 4W for a vote. Other messages can receive TooEarly. All four families retry under authority message delivery
node behind an authority 25 seconds P/48, from U to P/8
node ahead of an authority 112.5 seconds U minus P/96
mix ahead of every authority, fetching at 5/8 300 seconds 2W with retries
client ahead of its gateway 75 seconds P/16 minus the gateway’s fetch latency

Clocks and retention: authority

Authorities retain voting state and limit retries using separate timing settings.

Key Value Explanation
authority N-3 to N N+1 from 5/8
mix, gateway, service node N-2 to N N+1 from 5/8
replica, courier N-2 to N N+1 from F
client N-1, N N+1 from F

Acceptance windows and status codes

Receivers use the request epoch and local state to select a response status.

Key Value Explanation
descriptor Phase_DESC of N receives DescriptorOk Outside {N-1, N, N+1}: DescriptorInvalid. After vote construction: DescriptorConflict. Byte-identical re-upload: DescriptorOk.
vote 5/8 of N-1 to 4/8 of N receives VoteOk or VoteAlreadyReceived Epoch errors. Accepted duplicate: VoteAlreadyReceived. Closed phase: VoteTooLate.
reveal from the sender’s vote to 3/8 receives RevealOk Epoch errors. Accepted duplicate: RevealAlreadyReceived. Closed phase: RevealTooLate. Missing sender commit: RevealTooEarly.
cert from the sender’s vote to 4/8 receives CertOk Epoch errors. Accepted duplicate: CertAlreadyReceived. Closed phase: CertTooLate. Missing sender vote: CertTooEarly.
sig 5/8 of N-1 to 5/8 of N: SigOk Epoch errors.
GetConsensus(e), e at most N held: ConsensusOk Missing document: ConsensusGone.
GetConsensus(N+1) from 5/8: ConsensusOk Before 5/8: ConsensusNotFound. Missing after 7/8: ConsensusGone.
GetConsensus(e), e at least N+2 ConsensusNotFound No acceptance window.

Commands by role

The authenticated peer role determines which directory commands the receiver accepts.

Key Value Explanation
kpclientd gateway node Commands: get_consensus2
gateway node kpclientd Commands: consensus2
unauthenticated authority client authority Commands: get_consensus
mix, gateway node, service node authority Commands: get_consensus, post_descriptor
storage replica authority Commands: post_replica_descriptor, and get_consensus as a client
courier authority Commands: get_consensus as a client
authority authority Commands: get_consensus, vote, reveal, cert, sig

Appendix D. Requirements

R-s-n identifies requirement n in group R-s. For example, R-6-3 is the third requirement in R-6. The consensus document.

R-5. Descriptors

Nodes publish signed descriptors with identity keys, addresses, and role fields.

Key Value Explanation
R-5-1 node / on build A producer MUST include a non-empty IdentityKey, LinkKey, each MixKeys value, and each EnvelopeKeys value required by its descriptor type. A mix upload MUST include a MixKeys value for the command epoch.
R-5-2 node / on build Each key field MUST be exactly the public key size of its scheme.
R-5-3 node / on build Each key field MUST parse under its scheme.
R-5-4 Sphinx sender or receiver / on key use NIKE Sphinx MUST reject an all-zero derived shared secret. X25519 and X448 parsing alone does not reject small-order points. CTIDH parsing rejects the base curve E0, the all-zero encoding, and non-supersingular curves. Upload verification follows the validation stages.
R-5-5 node / on build, on upload The name MUST contain 1 to 32 bytes. This is a byte limit, not a character count.
R-5-6 node / on build, authority / on upload A descriptor MUST list at least one transport, with no empty address list. Producers MUST use the map keys and URL schemes in transport layers. Upload validation MUST apply AddressShape. It accepts tcp, tcp4, tcp6, ws, and quic URLs if the host component permits separation into host and port. It accepts onion URLs in gateway descriptors only. This syntax rule does not require a numeric port or equality between the map key and URL scheme.
R-5-7 mix / on build, authority / on upload A producer MUST include a mix key for its descriptor epoch and none outside the NumMixKeys window that starts there. An upload receiver MUST select that window from the command epoch, require a nonempty key for that epoch, and reject keys outside that window. It does not compare the descriptor’s Epoch with the command epoch or require nonempty future key values (descriptor validation).
R-5-8 node / on build, on upload A descriptor MUST NOT set both IsGatewayNode and IsServiceNode.
R-5-9 node / on build, on upload Only service nodes MAY carry non-null Kaetzchen. Others MUST NOT supply even an empty map. Each capability MUST have a non-empty name and a parameter map containing endpoint text of 1 to 32 bytes, RecipientIDLength. Capabilities MAY share an endpoint.
R-5-10 storage replica / on build, on upload A replica MUST publish envelope keys for RE and RE+1, plus RE-1 when retained. These are replica epochs, independent of mixnet Epoch (clocks and retention). Upload verification requires a non-empty map with non-empty values, but imposes no replica-epoch window (producer).
R-5-11 authority / on upload, on restore, on encode With AcceptDescriptorMetadata false, an authority MUST remove SoftwareVersion and ContactInfo after signature verification, before validation, in-memory storage, voting, and comparison. The persisted signed upload retains its original bytes. Otherwise, SoftwareVersion MUST contain at most 128 printable ASCII bytes and ContactInfo at most 256, in 0x20..0x7e. Producers MUST omit empty metadata fields when encoding. Invalid retained metadata MUST receive DescriptorInvalid when the ordered upload procedure reach metadata validation.

R-6. The consensus document

Nodes and clients use the consensus to select peers and apply network settings.

Key Value Explanation
R-6-1 authority / on build, on validation Producers MUST use BLAKE2b-256 of the signer’s identity public key as each SharedRandomCommit and SharedRandomReveal map key. Each producer envelope MUST carry a valid signature by that identity. Receivers MUST apply the document-validation algorithm, including its zero-commit and one-commit cases. An additional signature by another identity does not invalidate the required signature (envelope verification).
R-6-2 authority / Phase_PUB A published consensus MUST carry no commits and no reveals.
R-6-3 producer / on encode Descriptors, documents, and certificate envelopes MUST use the deterministic CBOR encoding. Receiver decoding defines acceptance and re-encoding.
R-6-4 authority / on build Detached signatures MUST NOT be part of the signed body.
R-6-5 mix, gateway, service node / on packet handling Before subtracting queue dwell time, forwarding nodes MUST drop packets whose advertised delay exceeds P * NumMixKeys. Mixes and gateways MUST drop packets whose remaining per-hop delay exceeds the limit. With service pre-delay enabled, service nodes MUST apply that limit to the advertised last-hop delay (delay queue). The selected cap MUST be SafetyCap(consensus Mu) when nonzero. Otherwise, it MUST be a positive configured fallback, or the built-in SafetyCap(0.001) of 27632 ms if none is positive. The final limit MUST be min(selected cap, P * NumMixKeys) (selection). Both paths SHOULD warn if fallback or consensus caps differ from the built-in value by more than a factor of 4 (scheduler, service).
R-6-6 consumer / on fetch A consumer MUST treat a SphinxGeometryHash mismatch in a threshold-signed consensus as a failed fetch, not a fatal error.
R-6-7 authority, client / at startup, Phase_CERT, Phase_SIG MinClientVersion MUST be at most 32 bytes and ClientNotice at most 512, both printable ASCII 0x20..0x7e. An authority MUST propose its configured pair and reject invalid startup values. It MUST tally the pair separately from rates. It MUST publish a pair present in at least threshold well-formed votes. Otherwise it MUST omit the notice without preventing consensus. It MUST log malformed vote pairs and exclude them from this tally only. Clients MUST NOT act on either field.

R-7. Certificate envelopes and signing

A certificate envelope binds a payload to one or more identity signatures.

Key Value Explanation
R-7-1 any party / on build A certificate envelope MUST carry Version, Expiration, KeyType, Certified, and Signatures.
R-7-2 any party / on sign, on verify The signed message MUST be the signature-input concatenation, with little-endian integers and no length prefixes.
R-7-3 any party / on verify A verifier MUST accept a signature only while its current epoch is less than Expiration.
R-7-4 authority / on build A document’s Expiration MUST be Epoch + 5.
R-7-5 any party / on build A party MUST verify each signature over the same message before adding it by signer hash to a multi-signed document.
R-7-6 any party / on parse A parser MUST reject a certificate envelope whose decoded Version is not 0. An absent or null Version decodes to 0 under the receiver decoding rules. Producers MUST include Version under R-7-1.
R-7-7 producer, document validator / on build, on validation A producer MUST set document Version to “v1”. Full document validation MUST reject any other value. ParseDocument alone does not enforce this requirement, and kpclientd’s gateway path does not repeat it (gateway consensus transfer).

R-9. Time

Participants use the same epoch clock to select current and next-epoch data.

Key Value Explanation
R-9-1 any party / at startup P MUST be the EpochDuration selected by R-9-3. Epoch 0 starts at 2017-06-01 00:00:00 UTC.
R-9-2 any party / at startup P MUST be a whole number of seconds from 2 minutes to 7 days. A party MUST refuse to start on any other value.
R-9-3 any party / at startup A party MUST set P once per process, before any epoch arithmetic. It MUST use the configured value. Without one, it MUST use the environment value and log a deprecation warning. Without either, it MUST use the default. A party MUST refuse to start if configuration and environment values differ, or if it receives a second, different value.
R-9-4 any party / always All parties in a network MUST use the same P. No document or message carries it.
R-9-5 any party / always The epoch number N, elapsed time t, and remaining time till MUST follow the clock formula. The start instant belongs to the epoch. A time before epoch 0 is an error.
R-9-6 authority / always The PriorSharedRandom rotation period MUST be 504 epochs at every P (one week only at 1200 seconds).

R-10. Phases

The voting phases schedule uploads, authority exchanges, and consensus publication.

Key Value Explanation
R-10-1 authority / every send phase An authority MUST calculate its retry deadline from the time until the phase boundary, treating negative time as zero. It MUST subtract buf only when more than buf remains. This deadline does not cancel in-progress attempts (authority message delivery).
R-10-2 any party / always Phase boundaries MUST use the phase fractions and the acting party’s clock. Each phase includes its start. Bootstrap skips the round only after P/8. Gone and N+1 peer authentication apply strictly after 7/8. The sender upload cutoff U is exclusive. Authorities may accept uploads after U until vote construction (R-10-5). Each timer MUST target its phase boundary.
R-10-3 node / Phase_DESC, before U A node MUST upload its descriptor for N+1 at or after the start of N and before U. A replica MUST retry every second. After an unsuccessful post, a node MUST post again every P/96 while the window is open. A DescriptorConflict from an authority that accepted an earlier attempt counts as acceptance.
R-10-4 authority / Phase_VOTE, at P/8 An authority MUST build its vote for N+1 at P/8 from exactly the descriptors it holds at that instant.
R-10-5 authority / on upload After the live validation pass, an authority MUST apply the ordered upload-storage procedure. It MUST answer DescriptorConflict to a new descriptor for an epoch whose document or own vote it already holds. It MUST first compare an existing descriptor, returning DescriptorOk for the same canonical descriptor bytes and DescriptorConflict for a change.
R-10-6 authority / on receive An authority MUST apply the state and error precedence in message acceptance. New reveals close in accept_cert and accept_signature. New votes and voting certificates close in accept_signature. The sig handler has no state gate. The stored voting epoch controls its epoch comparisons. State-machine transitions normally place these cuts at 3/8, 4/8, and advancement of V at 5/8.
R-10-7 authority / at 3/8, 4/8, 5/8 An authority MUST tally votes at 3/8 for its voting certificate and again at 4/8 for its consensus. It MUST process collected reveals at 3/8, voting certificates at 4/8, and signatures at 5/8. Each action MUST run at the corresponding state-machine wake.
R-10-8 authority / every send phase Each authority MUST start delivery at the corresponding phase wake. Retry-deadline targets are 2/8 for votes, 3/8 for reveals, 4/8 for voting certificates, and 5/8 for signatures. R-10-1 applies the buffer. These targets do not guarantee completion before the boundary.
R-10-9 mix, gateway node, service node / Phase_PUB, from 5/8 A mix, a gateway, or a service node MUST first request doc N+1 at 5/8. It MUST retry every P/16 until it holds the document or it receives Gone. While doc N is missing it MUST fetch every P/96 until it posts its descriptor, and every P/16 after that.
R-10-10 courier, storage replica / Phase_PUB, from F A courier or a replica MUST first request doc N+1 at F, and MUST retry every P/32 until it holds the document or receives Gone. While doc N is missing it MUST fetch every P/32.
R-10-11 client / Phase_PUB, from F A client MUST first request doc N+1 from its gateway at F. It MUST retry every P/16 until it receives the document or Gone. While doc N is missing it MUST request it every P/16, and MUST use doc N-1 if available.
R-10-12 mix, gateway, service node, courier, replica, client / every fetch pass Mixes, gateways, service nodes, couriers, and replicas MUST use min(3 minutes, P/8) for each fetch pass. kpclientd MUST use a separate 30-second timeout per document. Authority bootstrap uses R-12-2’s retry budget.
R-10-13 mix, gateway, service node / epoch boundary At the start of epoch N these nodes MUST retain mix keys N-1 through N+3. They MUST publish N+1 through N+3 in their descriptors. They MUST unwrap with keys N-1, N, and N+1.
R-10-14 client / epoch boundary A client MUST wake at the epoch boundary and adopt doc N+1 there, without refetching a cached document it already verified.

R-11. Clocks and retention

Authorities, nodes, and clients apply separate clock and retention limits.

Key Value Explanation
R-11-1 authority / every wake An authority MUST drop documents older than N-3 at every state-machine wake.
R-11-2 node / on authentication, after 7/8 A node MUST authenticate a peer from documents N, N-1, and N-2, and, strictly after 7/8, from N+1. An incoming peer that only N+1 lists MAY send from that instant. A node MUST evaluate an outgoing link’s send permission again at the epoch boundary.
R-11-3 courier, storage replica / on authentication A courier or replica MUST authenticate peers from documents N-1, N, and N+1.
R-11-4 client / on clock adjustment A client that adjusts its clock MUST record skew = local - responder. It MUST then use local - skew. It MUST NOT apply a skew larger than P/8.
R-11-5 authority / on build A signature, a commit, and a reveal MUST carry Expiration V. A vote, a voting certificate, and a document MUST carry Expiration Epoch + 5.
R-11-6 node, client / always A node MUST keep documents for epochs at or above N-2 and a client at or above N-1.

R-12. The authority state machine

Each authority advances through voting states on the epoch schedule.

Key Value Explanation
R-12-1 authority / always The authority MUST implement the bootstrap, descriptor, vote, reveal, certificate, and signature acceptance states. The state labels identify the reference transitions.
R-12-2 authority / bootstrap For each missing document N-1 and N, an authority MUST start a background fetch with a 2-minute retry budget. The budget does not cancel an in-progress attempt. It MUST decide V by R-12-4 without waiting for those fetches.
R-12-3 authority / Phase_PUB, at 5/8 At 5/8 an authority that holds a threshold-signed document MUST store doc V, set V = V + 1, and wake at P/8 of N+1. An authority that holds none MUST enter bootstrap, set V = N+2, and wake at the boundary.
R-12-4 authority / bootstrap In bootstrap, at t of at most P/8, an authority MUST set V = N+1 and wake at P/8. At t greater than P/8 it MUST stay in bootstrap, set V = N+2, and wake at the boundary.
R-12-5 authority / on restart A restarted authority MUST start in bootstrap and attempt restoration for N-1, N, and N+1. For each persisted document, it MUST run threshold verification, parse the document, compare its epoch with its storage key, and skip failures. It MUST apply descriptor restoration to persisted signed uploads and use the current AcceptDescriptorMetadata setting. Restoration does not repeat live session binding, QUICKeyHash validation, or the live late-upload cut. A late conflict can reject an upload after persistence, so storage does not prove prior acceptance.

R-13. Acceptance windows and status codes

Message acceptance windows determine the status returned to a sender.

Key Value Explanation
R-13-1 authority / on get_consensus An authority MUST answer GetConsensus under the availability table: Ok when held, NotFound for N+1 through 7/8 and for N+2 on, Gone otherwise.
R-13-2 gateway node / on consensus retrieval A gateway MUST answer ConsensusGone for an epoch below N-1 or an epoch marked as a permanent fetch failure. Permanent failures are an authority’s Gone response, a Sphinx geometry mismatch, or failure to build the document cache. For other epochs, it MUST answer ConsensusOk if it has the document, and ConsensusNotFound otherwise.
R-13-3 authority / on every rejection An authority MUST log every epoch-gated rejection of a peer message with the local epoch, the elapsed time, and the phase. It MUST log a rejected descriptor upload with that upload’s epoch.

R-14. Phase_DESC. Descriptor upload

Authorities accept descriptor uploads from authorized nodes.

Key Value Explanation
R-14-1 authority / Phase_DESC, on upload An authority MUST accept descriptor uploads only for epochs N-1, N, and N+1, and answer DescriptorInvalid otherwise.
R-14-2 authority / Phase_DESC, on upload The descriptor’s identity hash MUST be pinned for the claimed role and the announced name MUST match the pinned name.
R-14-3 authority / Phase_DESC, on upload A replica’s ReplicaID MUST match the pinned identifier.
R-14-4 authority / on upload SignedUpload and SignedReplicaUpload signature verification MUST use the canonical encoding of the decoded upload with Signature set to null (upload validation). This verification MUST succeed. It does not verify the upload signature’s PublicKeySum256.
R-14-5 authority / Phase_DESC, on upload The identity hash presented on the link MUST equal the descriptor’s identity hash.
R-14-6 authority / Phase_DESC, on upload An authority MUST verify the identity key length against the scheme before it parses the key.
R-14-7 authority / on upload After live validation succeeds, an authority MUST compare re-uploads for the same command epoch and identity using canonical descriptor bytes after the metadata policy. It MUST return DescriptorConflict for a changed descriptor and DescriptorOk for an unchanged descriptor. The comparison excludes the upload signature and LoopStats (storage order).
R-14-8 authority / on upload An authority MUST apply the ordered live upload procedure and storage procedure. Core descriptor validation and its caller-selected epoch follow descriptor validation. Key parsing and degeneracy tests at key use follow R-5-4.
R-14-9 authority / always Every authority MUST use the same AcceptDescriptorMetadata setting in an epoch. With mixed settings, a descriptor containing metadata needs threshold authorities to retain it or threshold authorities to remove it. Otherwise, neither form reaches the identical-bytes threshold of R-16-2.
R-14-10 authority / on upload For a new upload, an authority MUST persist the original signed upload before it returns DescriptorOk. Persistence MUST also precede insertion into the in-memory upload set. The authority MUST recheck the vote and document cut before that insertion. A cut reached during persistence produces DescriptorConflict. An unchanged existing descriptor returns DescriptorOk without a new write. Storage failure MUST produce DescriptorInternalError and trigger fatal shutdown (storage algorithm).

R-15. Phase_VOTE, Phase_REVEAL. Commit and reveal

Authorities commit to random values before revealing them.

Key Value Explanation
R-15-1 authority / on build, on receive Producers MUST use the commit and reveal formulas, each with exactly 40 bytes: an 8-byte epoch and a 32-byte digest or value. A reveal receiver MUST reject fewer than 8 certified bytes before reading the epoch. It MUST apply the later 40-byte and commitment-opening requirements in the order specified by message acceptance.
R-15-2 authority / Phase_VOTE, on receive A vote MUST carry the sender’s commit, or be answered VoteMalformed.
R-15-3 authority / on receive When the ordered reveal procedure reach commitment verification, the reveal MUST open the sender’s stored commitment for the same epoch. Failure at that condition MUST produce RevealNotSigned.
R-15-4 authority / Phase_VOTE, Phase_REVEAL, on build Commit and reveal MUST each be signed with the sender’s identity key.
R-15-5 authority / Phase_VOTE, Phase_REVEAL, on send An authority MUST send identical signed commit envelopes and identical signed reveal envelopes to every peer. Conflicting verified envelope bytes exclude the signer from the SRV calculation under R-17-4.
R-15-6 authority / on receive A receiver MUST store a reveal only if it holds the sender’s commitment. If earlier conditions hold and that commitment is missing, it MUST return RevealTooEarly under the ordered reveal procedure.
R-15-7 authority / on receive A vote MUST pass VoteDocument, which omits the usable-topology requirement. It MUST also contain exactly one commitment under the sender’s identity hash and no reveals. Failure at these conditions MUST produce VoteMalformed, after the preceding conditions in message acceptance.
R-15-8 authority / on receive A receiver MUST store a received vote’s commitment only in accept_desc or accept_vote. If it accepts a vote in bootstrap, accept_reveal, or accept_cert, it MUST store the vote without its commitment and log a warning. A later reveal follows message acceptance, including state closure before the commitment-presence test.

R-16. Phase_CERT, Phase_SIG. Tabulation

Authorities tally votes for descriptors and traffic rates.

Key Value Explanation
R-16-1 authority / Phase_CERT, Phase_SIG Tabulation MUST require at least threshold votes for the epoch.
R-16-2 authority / Phase_CERT, Phase_SIG An authority MUST include a descriptor only if its allow-list authorizes it and at least threshold votes contain identical serialized bytes.
R-16-3 authority / Phase_CERT, Phase_SIG An authority MUST adopt a parameter set only with at least threshold votes. It groups the votes by the canonical CBOR map {Mu, LambdaP, LambdaL, LambdaM, LambdaR}, with keys in canonical order and floats as in R-6-3. Two sets group together if and only if each rate is bit-identical to its counterpart, or both of that pair are NaN.
R-16-4 authority / Phase_CERT, Phase_SIG dissenters MUST be floor(A/2)-1, and it MUST NOT decide any output. An authority logs a parameter set that has fewer than threshold votes and at least dissenters votes.
R-16-5 authority / Phase_CERT, Phase_SIG An authority MUST sort included nodes and replicas by identity-key hash.
R-16-6 authority / at startup, on reload, on sign Layers defaults to 3 and MinNodesPerLayer defaults to 2. An authority MUST reject Layers above 3. At startup and reload, the allow-lists MUST contain at least Layers * MinNodesPerLayer mixes, one gateway, and one service node. A fixed topology MUST contain at least MinNodesPerLayer mixes per layer. At signing, a topology MUST have non-empty layers, a gateway, and a service node. It does not require MinNodesPerLayer.
R-16-7 authority / Phase_SIG, on sign An authority MUST refuse to sign a document that is not well formed.
R-16-8 authority / on document construction Each authority MUST derive LambdaG from its configured topology, gateway count, and rates using gateway rate arithmetic. This value is outside the voted parameter key. With gateways present, the tagged document builder requires a fixed topology.

R-17. Phase_CERT. Voting certificates

Authorities verify voting certificates before reconciling random contributions.

Key Value Explanation
R-17-1 authority / Phase_CERT An authority MUST send its voting certificate between the reveal and signature phases. It MUST include only pairs for which it has both the commit and reveal. R-17-6 defines when it sends none.
R-17-2 authority / Phase_SIG, on verify Each commit and reveal cited in a voting certificate MUST be signed by an authorized authority.
R-17-3 authority / Phase_SIG, on verify Each cited reveal MUST open its commit for the matching epoch.
R-17-4 authority / Phase_SIG During ordered reconciliation, two encountered verified current-epoch commit or reveal envelopes with different serialized bytes MUST exclude their signer from the SRV calculation (R-17-8). The comparison includes envelope metadata and signatures, not only Certified. The SRV MUST use the pairs that survive ordered reconciliation.
R-17-5 authority / on receive A voting-certificate upload MUST pass the ordered certificate procedure. These bind its declared key to the connected peer. They compare its document epoch with V and require a nonempty reveal map. They require a stored vote from the sender before full document validation.
R-17-6 authority / Phase_CERT, on send A voting certificate MUST contain the tallied document. An authority MUST send it only if that document passes validation, including the usable-topology requirement. Otherwise, it MUST send none.
R-17-7 authority / Phase_SIG, on verify An authority MUST follow ordered reconciliation, including its empty accumulators, sorted traversal, exclusions, skips, early exits, and final deletion.
R-17-8 authority / Phase_SIG, on verify An encountered entry that fails verification MUST exclude the voting certificate’s author and stop that certificate’s entry loop. An authority MUST exclude a cited authority only for two verified current-epoch commits or reveals that differ under that authority’s own key.
R-17-9 authority / on receive A receiver MUST run FullDocument at the point specified by certificate acceptance. Failure there MUST reject the whole certificate with CertNotSigned and log the sender, epoch, failed rule, and cited authorities. With two or more commitments, this verifies a matching reveal for every commitment. With zero commitments, the validator does not inspect the reveal map.

R-18. Phase_SIG. Shared random value and topology

Authorities derive the shared random value and assign mix layers.

Key Value Explanation
R-18-1 authority / Phase_SIG, at 4/8 The SRV MUST use the shared-random digest.
R-18-2 authority / Phase_SIG, at 4/8 An authority MUST sort reveals by descending certified bytes, breaking ties by ascending identity hash. Each pair contributes the identity hash followed by the reveal.
R-18-3 authority / Phase_SIG, at 4/8 PREV MUST be the previous epoch’s SRV if held, else 32 zero bytes.
R-18-4 authority / Phase_SIG, at 4/8 An authority MUST hold voting certificates from at least threshold authorities before reconciliation. It MUST compute the SRV only if at least threshold commit/reveal pairs remain after reconciliation.
R-18-5 authority / Phase_SIG, at 4/8 Automatic assignment MUST seed its generator with the SRV and follow layer assignment. Fixed assignment MUST preserve configured order and use no random draws.
R-18-6 authority / Phase_SIG, on build PriorSharedRandom MUST be restored from the previous consensus and carried unchanged, except for initialization under R-18-8 and rotation under R-18-9.
R-18-7 authority / Phase_VOTE, at P/8 At vote construction, an authority MUST copy GenesisEpoch from document V-1 if available, or use V otherwise. An older retained document MUST NOT supply this value. It restores shared-random history from V-1 when available. GenesisEpoch remains fixed for the round. Later document construction reads the stored history and prior topology.
R-18-8 authority / Phase_SIG, on build With an empty history or at the genesis epoch, PriorSharedRandom MUST be [srv, srv].
R-18-9 authority / Phase_SIG, on build Initialization under R-18-8 MUST take precedence. Otherwise, at V mod 504 = 0, an authority MUST set PriorSharedRandom to [srv, previous first element]. It MUST rotate once, using the real SRV.
R-18-10 authority / Phase_SIG, on build Topology generation MUST read only those layers of the prior consensus that exist in the configured layer count.

R-19. Phase_SIG, Phase_PUB. Signatures and publication

Authorities exchange consensus signatures and publish when the threshold is met.

Key Value Explanation
R-19-1 authority / Phase_SIG, at 4/8 Each authority MUST sign its own consensus and send the detached signature to every peer.
R-19-2 authority / on receipt, on publication On receipt, an authority MUST follow ReceiveSig. It MUST run verification on the enclosing certificate, then verify for an existing message. For a new message, it MUST decode and store the inner Signature. SigOk acknowledges receipt, not agreement on consensus bytes. At publication, it MUST attach only inner signatures whose verification over its own consensus succeeds (attachment).
R-19-3 authority / Phase_PUB, at 5/8 An authority MUST publish and persist a consensus with at least threshold valid signatures.
R-19-4 client / on fetch A client MUST apply the same threshold verification when it fetches a consensus.

R-21. Authority message delivery

Authorities deliver voting messages through peer connections and retry within phase deadlines.

Key Value Explanation
R-21-1 authority / every send Each candidate address MUST use the configured DialTimeoutSec, 30 seconds by default. The retry deadline limits waits between attempts, but does not cancel an in-progress attempt.
R-21-2 authority / on retry Votes, reveals, voting certificates, and signatures MUST retry transient errors and their matching TooEarly responses under the shared retry policy. A non-TooEarly response MUST end that send.
R-21-3 authority / every send Phase sends MUST use R-10-1’s retry deadline. Bootstrap MUST use R-12-2’s 2-minute retry budget. Both MUST use PeerRetryMaxAttempts when positive, permitting an initial attempt plus that many retries. Without this setting, bootstrap defaults to 20 retries per peer.
R-21-4 authority / on retry Each retry MUST preserve the command’s serialized payload bytes. Commit and reveal envelopes MUST also be byte-identical across peers, including their metadata and signatures. The sender MUST reuse these bytes rather than sign them again.

R-22. Wire protocol

Directory wire commands carry signed protocol objects over PQ Noise.

Key Value Explanation
R-22-1 any party / always Status codes MUST use the reply status reference for their reply-command family. Different families reuse some numeric values.
R-22-2 authority / on receive Epoch comparison MUST follow the ordered message procedure. At that step, an epoch below V MUST produce the command’s TooLate code. An epoch above V MUST produce its TooEarly code. A reveal uses the epoch in its verified signed body. The command Epoch field has no effect on that comparison.
R-22-3 authority / on receive Vote, reveal, cert, and sig handlers MUST require the stored voting epoch V, with the command-specific state conditions in message acceptance. These handlers do not compare elapsed time with phase deadlines. The sig handler has no state gate.
R-22-4 authority / on receive An authority MUST answer NotAuthorized unless a vote, reveal, cert, or sig declares an authorized authority key matching the link-authenticated peer.
R-22-5 authority / on receive The first accepted vote, reveal, certificate, or signature from an identity for V MUST retain its slot. A duplicate MUST produce AlreadyReceived only when the ordered message procedure reach that command’s duplicate test. Earlier authorization, epoch, state, parsing, or verification can produce another status.
R-22-6 authority / on build, on receive At a signature-verification failure, the ordered message procedure MUST return the command’s NotSigned status. For a new sig message, failure to decode the verified body as Signature MUST return SigInvalid. A producer MUST encode that body as canonical CBOR containing PublicKeySum256, a 32-byte value, and Payload, the signature bytes. Canonical order places Payload first. Receiver acceptance follows CBOR decoding.
R-22-7 authority / Phase_DESC, on upload Descriptor uploads MUST be answered with DescriptorInvalid, DescriptorConflict, and DescriptorForbidden as descriptor uploads and authentication and authorization specify.
R-22-8 any party / on build, on parse Every command MUST use the 6-byte header, big-endian integers, and zero padding.
R-22-9 any party / on build, on parse Command bodies MUST follow the command reference, with the full identity public key in vote, reveal, cert, and sig.
R-22-10 any party / on send A sender MUST NOT pad a command on a direct authority PKI link. Gateway get_consensus2 and consensus2 commands MUST use the upstream and downstream padding of their connection, respectively.
R-22-11 authority client / after get_consensus The initiator MUST end a direct authority consensus-fetch session after get_consensus. This does not close kpclientd’s persistent gateway session after get_consensus2.
R-22-12 authority, client / on handshake The responder MUST send NoOp as the first encrypted command. The client MUST reject any other command there.
R-22-13 client / on handshake A client MUST authenticate an authority with AD[:32] = H(identity key) and the pinned static link key. It MUST reject additional data shorter than 32 bytes before it reads that data.
R-22-14 any party / every command Each command MUST use the two encrypted Noise messages: a 20-byte encrypted length header, then the command ciphertext. The header’s four-byte big-endian value MUST include the command’s 16-byte tag. The sender MUST rekey its sending direction after both messages. The receiver MUST rekey its receiving direction after authenticating both messages.
R-22-15 any party / every command An authority PKI session MUST carry one command and one reply on a fresh connection. Configuration may enable persistent authority peer connections. Client-gateway sessions remain persistent and carry multiple commands.
R-22-16 client / on fetch A direct authority fetcher MUST use get_consensus and require a threshold-signed, well-formed document matching the requested epoch. kpclientd MUST use get_consensus2 through its gateway and verify the signature threshold, requested epoch, and local geometry. Its gateway path does not call IsDocumentWellFormed.
R-22-17 any party / on receive An authority PKI session MUST bound received commands by the receive size ceiling. It MUST read the body in chunks of at most 64 KiB.
R-22-18 authority / every send An authority MUST use configured HandshakeTimeoutSec and ResponseTimeoutSec, which default to 3 and 30 seconds. These operation budgets do not inherit the phase retry deadline.

R-23. Authentication and authorization

Session authentication and authorization bind commands to configured identities and roles.

Key Value Explanation
R-23-1 authority / always Allow-lists MUST be keyed by BLAKE2b-256 of the marshaled identity key (roles, keys, and schemes).
R-23-2 authority, node / on handshake An authority MUST treat a peer with zero-length additional data as an unauthenticated client. A node MUST present a 32-byte hash that an allow-list holds. An authority MUST also present its pinned link key.
R-23-3 authority / Phase_DESC, on upload A descriptor upload MUST come from an authenticated node of the descriptor’s kind.
R-23-4 authority / on receive Allowed commands MUST follow commands by role. An authority MUST drop unauthorized commands without reply and close unless this is a persistent authority connection. Authorization follows body parsing, so R-22-17’s ceiling applies before dispatch.
R-23-5 authority / on handshake The responder MUST read one command from every connection that completes the handshake. It MUST read that command before it applies per-command authorization. It MUST drop a connection that sends no command within the first-command deadline.
R-23-6 authority / Phase_DESC, on upload An authority MUST answer DescriptorForbidden unless the descriptor’s LinkKey matches the session’s link key in a constant-time comparison.
R-23-7 authority / on handshake An authority MUST NOT pin a node session unless its link key matches an accepted descriptor for that identity in N-1, N, or N+1. An authority MUST allocate per-identity slots to authorities and pinned nodes only. Unpinned nodes MUST use global and per-address bounds only, permitting first uploads and key rotation. A claimed identity hash alone does not establish pinning.

R-24. QUIC transport

Peers authenticate QUIC endpoints with pinned keys.

Key Value Explanation
R-24-1 node, authority / Phase_DESC, on upload A descriptor MAY carry QUICKeyHash, a concatenation of zero to three 32-byte hashes. A descriptor omits it when empty, so a TCP-only descriptor does not change. An authority MUST answer DescriptorInvalid to any other length. It verifies the length at upload only, after the link-key verification of R-23-6 and before authorization. R-5-11 does not remove the field.
R-24-2 node, authority / at startup A QUIC listener’s TLS key MUST be an Ed25519 key from a random seed kept on disk, and QUICKeyHash MUST be SHA-256 of its SubjectPublicKeyInfo.
R-24-3 any party / on dial A dialer MUST NOT dial a QUIC address of a peer for which it holds no QUIC key hash. It MUST abort a QUIC connection whose certificate SubjectPublicKeyInfo hash matches none of the hashes it holds. It MUST compare them in constant time.
R-24-4 authority / at startup, on dial An authority entry MAY carry one 32-byte QUICKeyHash, written in hex. A dialer MUST pin it, and MUST NOT dial that authority over QUIC without it. An authority that listens on QUIC and whose own entry carries one MUST refuse to start unless its QUIC key file exists and matches it.
R-24-5 gateway node, authority / on build, at startup For common TCP reachability, a gateway MUST advertise a tcp4 or tcp6 map entry with a tcp:// URL. An authority MUST configure tcp:// in its [Server] Addresses and own [[Authorities]] entry. Authority listeners support tcp:// and quic:// only. Gateways MAY advertise onion endpoints for clients. An authority warns about a QUIC gateway descriptor without a TCP or onion alternative. The authority PKI client randomizes addresses, while inter-authority sends use filtered configuration order (client, peer sends).
R-24-6 node, authority / at startup A QUIC listener’s stateless reset key MUST derive from its identity private key through HKDF-SHA256. The derivation MUST use no salt and the label “katzenpost quic stateless reset”. This preserves reset-key continuity across restarts.
R-24-7 any party / every connection A QUIC connection MUST carry one stream: a listener MUST allow one incoming stream and a dialer none, and a second stream MUST be refused.
R-24-8 any party / every connection A QUIC link MUST select a keepalive period per connection. The default is 45 seconds for nodes and authorities, or 20 seconds for clients, multiplied by 1 +/- 0.25. quic-go limits the period to half the idle timeout. QUIC MaxIdleTimeout defaults to 2 minutes. Wire read deadlines can be shorter. The client configures 15 seconds. The handshake MUST use the connect deadline.
R-24-9 mix, service node, storage replica / on build A mix, a service node, or a replica with a QUIC listener MUST present one QUIC key for each epoch. In the descriptor for epoch e it MUST publish the hashes of the keys it holds for e-1, e, and e+1.
R-24-10 gateway node, node / at startup, on build A gateway with a QUIC listener MUST present one long-term QUIC key. It MUST publish that key’s single hash in every descriptor. It MUST refuse to start without that key file. A node without a QUIC listener MUST NOT set QUICKeyHash.
R-24-11 any party / on dial After a pin mismatch a dialer MUST choose one behaviour for the rest of that epoch. Under fallback, the default, it dials that peer at non-QUIC addresses only. Under strict, it dials that peer over pinned QUIC only. A node under strict MUST count the mismatch as final for its upload to that authority in that epoch.
R-24-12 node / on build A node MUST omit QUICKeyHash from its descriptor unless every authority entry in its configuration carries a QUICKeyHash. No authority that drops the field then receives it (R-26-8).
R-24-13 any party / on close A QUIC close MUST close the stream’s send side and allow data to drain before connection closure. This wait MUST end when the peer closes or CloseLinger expires. CloseLinger defaults to 30 seconds, and 0 selects that default. Close does not guarantee delivery.
R-24-14 any party / at startup, every connection All parties in a network MUST offer the same ALPN, which defaults to “h3”. A listener MUST answer a new connection with a Retry unless its configuration says otherwise. AddressValidation takes three values. The default is always. Under-load starts at 128 handshakes in flight. The third value is off.
R-24-15 client, any party / on dial A party whose links go through a proxy MUST NOT dial QUIC. A client MUST dial QUIC only when its PreferedTransports lists quic. It MUST then pin the QUICKeyHash of the gateway’s descriptor, or of its PinnedGateways entry before it holds a consensus.

R-26. Byte identity

Authorities require identical consensus bytes before combining signatures.

Key Value Explanation
R-26-1 authority, client / always An authority’s threshold MUST be floor(A/2)+1 over the authority identity keys deduplicated by hash, its own key included. A client MUST use floor(A/2)+1 over its configured authority list without deduplication, so a list that names one authority twice raises its threshold.
R-26-2 any party / on build, on parse SphinxGeometryHash MUST be BLAKE2b-256 of canonical Geometry CBOR (hash, structure). Its 12 integer fields and text fields NIKEName and KEMName appear in the conformance vectors and the CDDL schema. Field names are map keys. R-6-3’s length-first order applies.
R-26-3 node / on build A SignedUpload MUST be the canonical CBOR of Signature, MixDescriptor, and LoopStats. A SignedReplicaUpload MUST be the canonical CBOR of Signature and ReplicaDescriptor, with no LoopStats. Both MUST be signed with Signature nil. The MixDescriptor value MUST be a byte string that holds the descriptor’s own canonical CBOR, and the ReplicaDescriptor value MUST be an inline map.
R-26-4 authority / Phase_SIG, on build The automatic topology generator MUST be ChaCha20 keyed by the SRV with an 8-byte zero nonce and initial counter zero. The generated topology is in the signed body (byte identity), so a party MUST use the draw algorithm.
R-26-5 any party / on handshake The link handshake MUST be PqXX with the one-byte prologue 0x03.
R-26-6 any party / every frame A session MUST serialize nonce use and message operations separately in each direction. Concurrent operations on one direction can reuse a nonce.
R-26-7 authority / on build ConfiguredReplicaIDs and ConfiguredReplicaIdentityKeys MUST carry the full configured replica set sorted by identity key. ConfiguredReplicaIDs is one byte string of the identifiers in that order, not an array of integers, because its Go type is a byte slice. the consensus document gives the encoding.
R-26-8 authority, operator / always A new descriptor or document field MUST be omitted when empty. It MUST remain empty unless an authority configuration option enables it. That option MUST be false or empty by default. Operators MUST wait until every authority can decode the field before enabling it. Otherwise, an older authority can discard the unknown field and sign different bytes. This applies to retained descriptor metadata and notices. AcceptDescriptorMetadata defaults to false, and the [Notice] table defaults to empty.

Appendix E. Cryptographic configuration

Namenlos uses hybrid identity signatures and post-quantum link keys.

Key Value Explanation
PKISignatureScheme Ed25519 Sphincs+ The identity signature profile defines signatures, raw keys, PEM files, and identity hashes.
WireKEMScheme KYBER768-X25519 The wire profile defines the KEM and keys. The handshake profile defines the four flights.
SphinxGeometry.NIKEName x25519 Sphinx uses 32-byte X25519 public keys.
Replica envelope NIKE CTIDH1024-X25519 This fixed scheme uses the 160-byte hybrid NIKE encoding.

Appendix F. Receiver and construction algorithms

Receivers parse signed inputs, authorize senders, verify epochs, and construct the consensus. A receiver can accept an input that violates a producer requirement.

Receivers MUST evaluate the applicable algorithm’s conditions in order and return its first failure. A later condition does not change an earlier status. Decoded maps have no iteration order unless an operation sorts them.

Go reference implementation

The Katzenpost reference implementation uses the Go programming language. Internal data types and library calls can differ between languages. Compatible implementations MUST produce the same accepted values, canonical bytes, and consensus fields.

The Go CBOR library determines conversion and normalization behavior for received objects. The conversion of binary64 values to int64 can depend on the Go runtime and target architecture. The receiver decoding rules retain those distinctions.

Go’s math.Log computes the natural logarithm for the gateway rate. Other mathematics libraries can round the result differently. A different LambdaG encoding changes the signed consensus bytes. Compatible results require the binary64 evaluation order and logarithm constants.

CBOR decoding

The reference uses github.com/fxamacker/cbor/v2 v2.9.1 with DecOptions{} for these objects (pinned dependency). Encoding with CanonicalEncOptions does not configure the decoder. Each Unmarshal call first verifies the structure and limits of one complete CBOR item. It rejects trailing bytes, then decodes that item into the destination type. If either step returns an error, the caller rejects the result, including any fields set before the error (decoder).

Key Value Explanation
Nesting limit 32 The structural checker starts at depth zero. Each array or map adds one. In a consecutive run of tags, each tag after the first adds one. The first tag does not add a level. The checker examines the content at that depth.
Array limit 131072 elements This applies to each definite-length or indefinite-length array, including arrays in skipped fields.
Map limit 131072 pairs This applies to each definite-length or indefinite-length map, before the decoder collapses duplicate keys.
Byte and text string limits No separate decoder option The available input and the wire message ceiling limit their encoded size. The decoder permits indefinite-length strings.
Canonical form Not required by the decoder The decoder accepts non-minimal integer and length encodings, non-canonical map order, and supported wider floating-point encodings. This does not permit malformed CBOR.
Indefinite lengths Allowed Arrays, maps, byte strings, and text strings can use the indefinite-length form.
Tags Allowed When decoded, tag 0 requires text, tag 1 requires an integer or float, and tags 2 and 3 require bytes. The decoder removes self-described CBOR tags. Other tags around ordinary typed fields decode through to their content after pointer allocation. Untyped values retain unrecognized tags. Only structural validation applies to tags in skipped values.
Unknown struct fields Ignored The decoder skips unmatched text keys, unmatched integer keys that fit int64, and their values. It rejects byte strings, arrays, maps, and other unsupported types as struct field names.
Struct field matching Exact name, then case-insensitive fallback Matching uses the field name or its CBOR tag. For example, the document history field uses PriorSharedRandom.
Duplicate struct fields Retain the first matched field The decoder skips later keys that match the same destination field, including differently capitalized keys. A malformed value in the first occurrence still causes a decoding error.
Duplicate map entries Retain the last decoded entry This applies after conversion of the key to its destination type. The decoder does not return a duplicate-key error. A type error in an earlier entry still causes a decoding error.
Absent fields Unchanged destination value These receivers decode into new objects, so absent fields have their zero value: zero, false, an empty string, or a nil pointer, map, or slice. The later algorithms enforce field presence.
null and undefined Nil, or no change When untagged, they set pointers, maps, slices, and interface values to nil. For scalar values, fixed arrays, and ordinary structs, they leave the destination unchanged without a type error. A new scalar or struct therefore remains zero-valued.
Tagged pointers Allocate before removing tags The decoder allocates and dereferences pointers before it removes tags. An otherwise accepted tag around null or undefined leaves a new ordinary struct pointer allocated, with zero-valued fields.
Scalar map temporaries Retain the preceding decoded value Each map uses separate key and value temporaries. Booleans, integers, floats, and strings reuse their temporary across entries. Each temporary starts at zero. A null or undefined scalar therefore retains the preceding decoded key or value. Other destination types reset between entries.
Empty containers Distinct from nil containers An empty map or array decodes as an allocated empty map or slice. This distinction affects conditions such as Kaetzchen == nil and subsequent canonical encoding.
Text UTF-8 when decoded Decoded text strings, including field names, MUST contain valid UTF-8. The decoder verifies the CBOR structure of values in unknown or duplicate struct fields, but skips their text decoding. The default decoder does not convert byte strings to string fields.
Unsigned integer fields A supported numeric value that fits the destination Unsigned integers, unsigned bignums that fit uint64, and unassigned simple values 0 through 19 or 32 through 255 convert to the destination integer. Negative integers and floats fail. A uint8 field therefore receives only 0 through 255, apart from the null behavior above.
Floating-point fields CBOR integers, floats, and supported numeric conversions Unsigned integers through uint64, negative integers through int64, corresponding bignums within those ranges, and the simple values above convert to the destination float. The decoder permits NaN and positive or negative infinity.
Byte slices Byte strings or arrays of byte values Arrays decode element by element using the uint8 conversion above. Tags 2 and 3 around bytes can also decode their contained bytes into a byte slice or fixed byte array.
Fixed byte arrays Copy, truncate, or zero-fill A byte string or array can decode into [32]byte. The decoder discards extra elements and zero-fills a short value. It does not require an encoded length of 32 for a hash field or hash map key.
Arbitrary parameter scalars Default interface decoding Booleans remain booleans. Positive integers become uint64. Negative integers become int64, or big.Int if necessary. Floats become float64. Bytes become byte slices.
Arbitrary parameter containers Default interface decoding Arrays become interface slices. Maps become maps with interface keys and values. Byte-string map keys use the comparable cbor.ByteString type. Unhashable map keys fail.
Arbitrary parameter tags and simple values Default interface decoding Tags 0 and 1 decode as time values. Tags 2 and 3 decode as bignums. Unrecognized tags decode as cbor.Tag. Unassigned simple values decode as cbor.SimpleValue.

These settings follow the decoder options, struct field rules, map decoding, byte conversion, and structural conditions. The limits apply again when the receiver decodes an embedded byte string as another CBOR object. They do not impose a combined nesting limit across separate decodes of an envelope, its certified payload, and an embedded descriptor.

The producer encodes a MixDescriptor as a byte string containing its CBOR map. The receiver invokes MixDescriptor.UnmarshalBinary on that byte string. The default decoder also accepts a direct CBOR map for this destination type. A ReplicaDescriptor has no UnmarshalBinary method and uses a direct map. Its Unmarshal method does not give it the byte-string conversion used for a mix descriptor. Descriptor methods, replica methods.

Canonical encoding after decoding

Upload verification and descriptor tallying encode decoded values again. Arbitrary mixnet service parameters therefore use these conversions before canonical encoding.

Key Value Explanation
Time tags 0 and 1 Untagged whole Unix seconds Tag 0 parses RFC 3339 text. Tag 1 converts numeric seconds. Both produce a time value. Encoding discards its fractional seconds.
Fractional numeric time Truncate, then normalize Split the binary64 value into whole seconds truncated toward zero and a signed fractional remainder. Multiply the fraction by 1000000000. Truncate that product toward zero for nanoseconds. Normalize negative nanoseconds into the preceding second. Encode the resulting whole seconds.
Time bounds Signed 64-bit integer seconds Integer input outside this range fails. For finite floats, compare whole seconds against binary64 -2^63 and +2^63. Values outside those bounds fail. Conversion at exactly +2^63 depends on the Go implementation. Go 1.26.10 on Linux/amd64 converts it to -2^63.
Zero and nonfinite times CBOR null The zero time is 0001-01-01T00:00:00Z. Tag 1 NaN and either infinity decode to that zero time.
Bignums Shortest integer or bignum Tags 2 and 3 decode to integers. Encode them without a tag when major type 0 or 1 can represent them. Otherwise, encode the shortest bignum magnitude.
Unrecognized tags Tag number and encoded content Preserve the tag number and recursively encode the decoded content.
Simple values Original simple value, or null Preserve unassigned simple values. Undefined decodes to nil and encodes as null.

The time decoder, time encoder, and encoding defaults define these conversions. The map decoder defines scalar reuse. These byte examples use hexadecimal CBOR. The receiver tests verify them against the pinned decoder.

Key Value Explanation
c1f93e00 01 Time 1.5 becomes integer 1.
c1f9b800 20 Time -0.5 becomes integer -1.
c1fbbddb7cdfd9d7bdbb 00 Time -0.0000000001 truncates to zero nanoseconds.
c074303030312d30312d30315430303a30303a30305a f6 The zero time becomes null.
c1f97e00, c1f97c00, c1f9fc00 f6 NaN and either infinity become null.
c24101 01 Bignum 1 becomes integer 1.
d903e8f7 d903e8f6 An unknown tag retains its number. Its undefined content becomes null.
LoopStats: d903e8f6, d9d9f7f6, d903e8f7, d9d9f7f7 a36545706f6368006d5365676d656e74526174696f73f66f4d69784964656e7469747948617368f6 Tagged null or undefined allocates a zero-valued LoopStats. Upload verification uses this map instead of null. Bare f6 or f7 leaves the pointer nil and encodes as f6.
a2054161f64162 or a2054161f74162 a1054162 A map with unsigned integer keys reuses key 5 for null or undefined. The last entry replaces its bytes.
a26161f93c006162f6 a26161f93c006162f93c00 A map with text keys and floating-point values reuses value 1 for null.

Certificate parsing and signature verification

Certificate operations verify the signed payload and its expiration. H(key) is the hash of the serialized identity public key. N is the current local epoch number at verification.

Envelope(raw):
    Decode raw into a new Certificate, using receiver-decoding.
    Require Version == 0.
    Require N < Expiration.
    Require KeyType to be nonempty.
    Require Certified to be nonempty.
    Replace a nil Signatures map with an empty map.
    Return the decoded certificate.

VerifyEnvelope(key, raw):
    c = Envelope(raw).
    For each signature value s in c.Signatures, in unspecified map order:
        If s.PublicKeySum256 == H(key):
            Require len(s.Payload) == key.scheme.SignatureSize.
            Require key.scheme.Verify(key,
                LE32(c.Version) || LE64(c.Expiration) ||
                UTF8(c.KeyType) || c.Certified, s.Payload).
            Return c.Certified.
    Fail because no signature value identifies key.

ParseDocument(raw):
    c = Envelope(raw).
    Require at least one entry in c.Signatures.
    Create a new Document, with an empty detached-signature map.
    For each signature value s in c.Signatures:
        Set document.Signatures[s.PublicKeySum256] = s.
    Decode c.Certified into the document body, using receiver-decoding.
    Return the document.

ParseDocument does not run outer signature verification or document validation. In the reference, it calls GetCertified and GetSignatures separately. Each call decodes the envelope and repeats its validity tests before body decoding (envelope validation). The body excludes the detached Signatures field (document parsing).

Envelope verification does not compare the envelope map key with the inner signature’s PublicKeySum256. It searches signature values. It also does not compare KeyType with the verifier’s configured scheme name. It requires a nonempty KeyType and includes that string in the signed message (signature search).

If different map entries contain signatures with the same inner identity hash, the first matching value visited controls verification. Conflicting entries can therefore give an order-dependent result. Producers use matching outer and inner identity hashes and one signature per identity.

Threshold verification calls VerifyEnvelope for each configured verifier and counts distinct hashes of successful verifier keys. Unknown or invalid signatures do not invalidate an otherwise sufficient threshold. A threshold greater than the number of supplied verifier entries fails before verification. Duplicate configured keys cannot provide more than one successful identity. Threshold verification.

Descriptor validation

A dirauth applies MixShape(d, e) to mix descriptors and ReplicaShape(d, e) to replica descriptors. The caller supplies e. MixShape uses it for the key window. Neither algorithm uses the descriptor’s Epoch.

MixShape(d, e):
    Require d != nil.
    Require 1 <= byte_length(d.Name) <= 32.
    Require d.LinkKey and d.IdentityKey to be nonempty.
    Require d.MixKeys[e] to be nonempty.
    For each key epoch k in d.MixKeys:
        Require e <= k < e + 3.
    Require d.Addresses to be nonempty.
    For each address list in d.Addresses:
        Require the list to be nonempty.
        Check each URL with AddressShape(URL, d.IsGatewayNode, true).
    Require that IsGatewayNode and IsServiceNode are not both true.
    If IsServiceNode is false:
        Require Kaetzchen == nil.
    Otherwise, for each capability and parameter map in Kaetzchen:
        Require a nonempty capability name and a non-nil parameter map.
        Require parameter "endpoint" to exist and to be a string.
        Require 1 <= byte_length(endpoint) <= 32.

ReplicaShape(d, e):
    Require d != nil.
    Require 1 <= byte_length(d.Name) <= 32.
    Require d.LinkKey and d.IdentityKey to be nonempty.
    Require d.EnvelopeKeys to be nonempty.
    Require each EnvelopeKeys value to be nonempty.
    Require d.Addresses to be nonempty.
    For each address list in d.Addresses:
        Require the list to be nonempty.
        Check each URL with AddressShape(URL, false, false).

AddressShape(URL, isGateway, allowOnion):
    Parse the URL with the reference URL syntax.
    If its scheme is tcp, tcp4, tcp6, ws, or quic:
        Require its host component to split into host and port.
    Else if its scheme is onion:
        Require allowOnion and isGateway.
    Else:
        Fail.

The URL syntax is that of Go net/url.Parse, and host-port splitting is that of net.SplitHostPort. This is a syntax rule. It does not require a nonempty host, a numeric nonempty port, a particular address family, or equality between the address-map key and the URL scheme. The onion branch omits host-port validation. Additional URL components are not rejected by this validator. Address validation.

MixShape permits missing or empty future key values when their epoch lies in the permitted window. It does not require a consecutive set of three keys. It does not verify d.Epoch, d.Version, key lengths, or key decoding. A service can have nil or empty Kaetzchen, and multiple capabilities can use the same endpoint. Other capability parameters and KaetzchenAdvertizedData have no additional shape validation here. ReplicaShape does not use its epoch argument, does not verify envelope-key epochs, and does not verify ReplicaID against configuration. A replica descriptor has no Version field. Mix validation, replica validation.

Live descriptor uploads

Upload validation starts after the wire session and command parser accept the command. peerID is the session’s authenticated identity hash, peerLinkKey is its authenticated link public key, and e is the command epoch. The handler uses the same sequence for mix and replica uploads.

Key Value Explanation
1. Command epoch DescriptorInvalid on failure Require e to be the current local epoch minus one, the current epoch, or the current epoch plus one.
2. Decode upload DescriptorInvalid on failure Decode SignedUpload or SignedReplicaUpload, and require its descriptor pointer to be non-nil.
3. Session identity DescriptorForbidden on failure Require H(descriptor.IdentityKey) == peerID.
4. Identity-key length DescriptorInvalid on failure Require the public-key size of the configured PKI signature scheme. This precedes key decoding.
5. Decode identity key DescriptorForbidden on failure Decode the identity key with the configured PKI signature scheme.
6. Upload signature DescriptorForbidden on failure Run signature verification on the decoded upload as specified below.
7. Session link key DescriptorForbidden on failure Require byte equality between the descriptor’s link key and peerLinkKey.
8. QUIC hash length DescriptorInvalid on failure Require the byte length of QUICKeyHash to be 0, 32, 64, or 96.
9. Current authorization DescriptorForbidden on failure Verify identity, role, and pinned name. For a replica, also verify the pinned ReplicaID.
10. Metadata policy and shape DescriptorInvalid on failure Apply the metadata policy, then run MixShape(d, e) or ReplicaShape(d, e), and the metadata conditions below.
11. Store Status from storage Return DescriptorOk, DescriptorConflict, or DescriptorInternalError according to the storage algorithm below.

For step 6, require a non-nil signature with the configured scheme’s exact signature size. Make a new upload object with Signature = nil. Retain the decoded descriptor and, for a mix upload, the decoded LoopStats. Encode this new object canonically. Run signature verification on Signature.Payload over those bytes with the decoded descriptor identity key (upload signature operations).

This operation does not verify the upload signature’s PublicKeySum256 or include it in the preimage. The decoder already discarded unknown fields. The receiver therefore verifies the canonical encoding of the decoded object, rather than the received upload bytes.

For step 9, a mix descriptor with only IsGatewayNode set uses the gateway authorization map. A descriptor with only IsServiceNode set uses the service map. A descriptor with neither flag set uses the mix map. Both flags set cause rejection. The selected map MUST contain the identity hash and the exact descriptor name. A replica MUST have an identity hash, name, and ReplicaID that match one current storage-replica entry. Authorization.

For step 10, if AcceptDescriptorMetadata is false, clear ContactInfo and SoftwareVersion before validation and storage. Otherwise, retain them. Require SoftwareVersion to contain at most 128 bytes and ContactInfo to contain at most 256 bytes. Each retained byte MUST be in the inclusive range 0x20 through 0x7e. Empty strings are valid (printable-string validation).

The implementation evaluates the core shape validation, the software-version validation, and the contact-information validation, and joins their errors. Any error gives the same wire status. The signature verification always occurs before metadata removal (metadata policy).

The handler does not compare descriptor.Epoch with e. For a mix, e selects the required mix key and its permitted window. For a replica, shape validation leaves both descriptor and envelope-key epochs unchecked. Storage uses e in both cases.

The command handlers do not require QUICKeyHash to match a QUIC session key (replica upload order). They also do not require a gateway to advertise a TCP or onion address. A QUIC-only gateway can produce a warning after acceptance (mix upload order).

Upload storage and replay

Storage serializes upload decisions for an identity hash. After metadata policy, the receiver compares the incoming descriptor with any descriptor stored for (command epoch, identity hash). Comparison uses canonical descriptor bytes, and excludes the upload signature and LoopStats.

StoreUpload(e, identity, rawUpload, decodedDescriptor):
    If a descriptor already exists at (e, identity):
        Canonically encode both descriptors.
        If either encoding fails: return DescriptorInternalError.
        If the encodings differ: return DescriptorConflict.
        Return DescriptorOk.
    If a document for e exists, or this authority's own vote for e exists:
        Return DescriptorConflict.

    Persist the original rawUpload at (e, identity).
    If persistence fails:
        Report a fatal persistence error, and return DescriptorInternalError.

    Recheck state while committing the in-memory descriptor:
        If a descriptor now exists at (e, identity): return DescriptorOk.
        If a document or this authority's own vote now exists for e:
            Return DescriptorConflict.
        Store decodedDescriptor, and notify the state worker.
    Return DescriptorOk.

An unchanged upload can therefore succeed after the authority votes or publishes, because comparison precedes the late-upload condition. The nominal upload deadline causes a warning. It is not itself a rejection condition. Persistence can succeed before a later finality comparison produces DescriptorConflict, so a conflict response does not prove the absence of a write. The live descriptor has the applied metadata policy, while the stored signed upload preserves its original bytes (storage and vote cut).

Document validation

The shared base validator operates on a parsed body and a set of verifier keys. It does not authenticate the document envelope. VerifyEnvelope includes the current-epoch expiration condition for each embedded commitment or reveal.

BaseDocument(d, verifiers):
    Require d.Version == "v1".
    Require d.GenesisEpoch != 0.
    If PriorSharedRandom is empty:
        Require d.GenesisEpoch == d.Epoch.
    Build verifierByHash from verifiers.

    For each (identity, signedCommit) in SharedRandomCommit:
        Require verifierByHash[identity] to exist.
        commit = VerifyEnvelope(verifierByHash[identity], signedCommit).
        Require len(commit) == 40.
        Require BE64(commit[0:8]) == d.Epoch.
        If SharedRandomCommit has exactly one entry:
            Require SharedRandomReveal to have zero entries.
        Otherwise:
            Require reveal count == commitment count.
            Require SharedRandomReveal[identity] to exist.
            reveal = VerifyEnvelope(verifierByHash[identity], that entry).
            Require len(reveal) == 40.
            Require BE64(reveal[0:8]) == BE64(commit[0:8]).
            Require H(reveal) == commit[8:40].

    seen = empty identity-hash set.
    For each descriptor in Topology, in layer and list order:
        Run MixShape(descriptor, d.Epoch).
        Require H(descriptor.IdentityKey) not in seen, then add it.
    For each descriptor in GatewayNodes:
        Run MixShape(descriptor, d.Epoch).
        Require descriptor.IsGatewayNode.
        Require H(descriptor.IdentityKey) not in seen, then add it.
    For each descriptor in ServiceNodes:
        Run MixShape(descriptor, d.Epoch).
        Require descriptor.IsServiceNode.
        Require H(descriptor.IdentityKey) not in seen, then add it.
    For each descriptor in StorageReplicas:
        Run ReplicaShape(descriptor, d.Epoch).

VoteDocument(d, verifiers):
    Run BaseDocument(d, verifiers).

FullDocument(d, verifiers):
    Run BaseDocument(d, verifiers).
    Require Topology to contain at least one layer.
    Require every layer to contain at least one descriptor.
    Require GatewayNodes to contain at least one descriptor.
    Require ServiceNodes to contain at least one descriptor.

If SharedRandomCommit is empty, the commitment loop does not run. The base validator then neither rejects nonempty SharedRandomReveal nor runs verification on it. The one-commit case rejects every nonempty reveal map. The two-or-more-commit case requires the same identity set and a valid matching reveal for each commitment (commitment opening). The vote handler adds its own exact count and sender conditions below (document validators).

The base validator does not require role flags to be clear for entries in Topology. It does require the appropriate flag for entries in the gateway and service lists, and the core descriptor validation rejects contradictory flags. It rejects repeated identity hashes across those three collections. It does not verify replica identity uniqueness, replica-ID uniqueness, or identity overlap with replicas. It does not consult the receiver’s node authorization tables.

This document validator omits descriptor metadata and QUIC hash lengths. It does not parse descriptor keys, compare descriptor epochs, or require a configured topology size. It also omits delay and rate ranges, floating-point finiteness, SharedRandomValue length, and geometry-hash length or equality. It does not constrain the body PKI scheme name, notice text, or the length and contents of nonempty PriorSharedRandom. Such producer rules or conditions in a later consumer are distinct from this validator (complete base-validation body).

Authority message acceptance

Authority message validation starts after wire command decoding and authority-role dispatch. Command decoding verifies the minimum command length. It takes the declared key from the fixed-size public-key field, then decodes it with the configured signature scheme. A decoding failure does not reach these handlers (command decoding).

The session role controls command dispatch (role dispatch):

  • A client session can request consensus.
  • A mix, gateway, or service session can request consensus or post a mix descriptor.
  • A replica session can only post a replica descriptor.
  • An authority session can request consensus or send Vote, Reveal, Cert, or Sig.

An unsupported command for the session role receives no command status.

V is the receiver’s current votingEpoch, and S is its current state. Message authorization first compares H(command.PublicKey) with the session identity hash, then verifies membership in the configured authority authorization map. Either failure returns that command’s NotAuthorized status. Message validation and storage are atomic with respect to the state machine.

ReceiveVote(command):
    Check authorization; otherwise return VoteNotAuthorized.
    If command.Epoch < V: return VoteTooLate.
    If command.Epoch > V: return VoteTooEarly.
    Ensure the vote and commitment maps for V exist.
    If this sender already has a vote for V: return VoteAlreadyReceived.
    If S == accept_signature: return VoteTooLate.
    VerifyEnvelope(command.PublicKey, command.Payload);
        on failure, return VoteNotSigned.
    d = ParseDocument(command.Payload);
        on failure, return VoteNotSigned.
    If d.Epoch != V: return VoteMalformed.
    Run VoteDocument(d, verifiers);
        on failure, return VoteMalformed.
    Require exactly one commitment and zero reveals;
        otherwise return VoteMalformed.
    Require the commitment map to contain this sender's identity;
        otherwise return VoteMalformed.
    Store d as this sender's vote for V.
    If S == accept_desc or S == accept_vote:
        Store the sender's commitment for V.
    Return VoteOk.

ReceiveReveal(command):
    Check authorization; otherwise return RevealNotAuthorized.
    reveal = VerifyEnvelope(command.PublicKey, command.Payload);
        on failure, return RevealNotSigned.
    If len(reveal) < 8: return RevealNotSigned.
    e = BE64(reveal[0:8]).
    If e < V: return RevealTooLate.
    If e > V: return RevealTooEarly.
    If S == accept_cert or S == accept_signature:
        If this sender already has a reveal for V:
            Return RevealAlreadyReceived.
        Return RevealTooLate.
    If no commitment exists for this sender at V: return RevealTooEarly.
    commit = VerifyEnvelope(command.PublicKey, the stored commitment);
        on failure, return RevealNotSigned.
    Require len(commit) == 40 and len(reveal) == 40,
        equal eight-byte epoch prefixes, and H(reveal) == commit[8:40];
        otherwise return RevealNotSigned.
    If this sender already has a reveal for V: return RevealAlreadyReceived.
    Store command.Payload as this sender's signed reveal for V.
    Return RevealOk.

ReceiveCert(command):
    Check authorization; otherwise return CertNotAuthorized.
    If command.Epoch < V: return CertTooLate.
    If command.Epoch > V: return CertTooEarly.
    If S == accept_signature:
        If this sender already has a certificate for V:
            Return CertAlreadyReceived.
        Return CertTooLate.
    VerifyEnvelope(command.PublicKey, command.Payload);
        on failure, return CertNotSigned.
    d = ParseDocument(command.Payload);
        on failure, return CertNotSigned.
    If d.Epoch != V: return CertNotSigned.
    If d.SharedRandomReveal is empty: return CertNotSigned.
    If this sender has no stored vote for V: return CertTooEarly.
    Run FullDocument(d, verifiers);
        on failure, return CertNotSigned.
    If this sender already has a certificate for V: return CertAlreadyReceived.
    Store d as this sender's certificate for V.
    Return CertOk.

ReceiveSig(command):
    Check authorization; otherwise return SigNotAuthorized.
    If command.Epoch < V: return SigTooLate.
    If command.Epoch > V: return SigTooEarly.
    body = VerifyEnvelope(command.PublicKey, command.Payload);
        on failure, return SigNotSigned.
    If this sender already has a signature for V: return SigAlreadyReceived.
    Decode body into a new Signature, using receiver-decoding;
        on failure, return SigInvalid.
    Store that Signature under this sender's identity for V.
    Return SigOk.

The reveal handler does not use command.Epoch for its epoch decision. It uses the signed body’s epoch prefix. It verifies the full 40-byte length only after the epoch, state, and stored-commitment conditions (vote and reveal handlers).

The signature handler has no state gate. It verifies the outer envelope signature before the duplicate test, but it does not decode a duplicate’s inner signature. For a new signature, it omits inner-signature verification, sender-hash comparison, and payload-length conditions at receipt. A valid outer envelope whose certified body is CBOR null can therefore decode to a zero-valued inner Signature and receive SigOk. Publication verifies whether collected signatures authenticate the locally constructed consensus (signature handler).

There is no general rule that limits a command to the state named after that command. For a matching epoch, the receiver can store a new valid vote in bootstrap, accept_desc, accept_vote, accept_reveal, or accept_cert. It stores the commitment only in accept_desc and accept_vote. It can store a valid certificate before accept_cert if its sender’s vote already exists (certificate handler). It can store a reveal before accept_reveal if its commitment exists.

The explicit closed-state conditions still apply. Bootstrap does not reject all messages.

When t <= P/8, the bootstrap transition sets V to N+1. When t > P/8, it sets V to N+2 and remains in bootstrap until the next epoch. Successful publication increments V. Failed publication sets it to N+2 and returns to bootstrap (publication transition). Epoch comparisons use this stored V, rather than a fresh calculation from the nominal phase deadline.

Consensus reception and restoration

Network retrieval, client deserialization, and restoration apply different validation sequences.

Key Value Explanation
Network fetch Threshold, parse, full validation, epoch The standalone client and authority background fetch first require a successful Consensus response. They run threshold verification, parse the document, run FullDocument, and then require the requested epoch, in that order.
Client Deserialize Threshold, then parse This operation does not run FullDocument or compare against a requested epoch.
Persisted consensus Threshold, parse, storage epoch Restoration verifies the threshold, parses the document, and compares its epoch with the database epoch key. It does not run FullDocument.

The network fetch operations do not compare the geometry hash in this sequence. An application that verifies its local geometry does so separately. Client fetch and deserialize, authority fetch, consensus restoration.

Descriptor restoration and node reload

At startup, the reference restores descriptor and document records for the current local epoch minus one, the current epoch, and the current epoch plus one. It creates fresh in-memory vote, commitment, reveal, certificate, and signature maps. It does not restore these round messages. It then starts in bootstrap (state initialization).

For each descriptor record, the database bucket supplies the upload epoch e, and the record key supplies the expected identity hash. The restoration sequence is:

RestoreUpload(rawUpload, e, storedIdentity):
    Decode the corresponding signed upload.
    Require a non-nil descriptor.
    Require the configured identity public-key length, and decode the key.
    Verify the signed upload over its canonical unsigned form.
    Apply the current AcceptDescriptorMetadata policy.
    Run MixShape(descriptor, e) or ReplicaShape(descriptor, e),
        and validate retained SoftwareVersion and ContactInfo.
    Require H(descriptor.IdentityKey) == storedIdentity.
    Require current identity, role, name, and, for replicas, ReplicaID authorization.
    Insert the descriptor under (e, storedIdentity).

A failed descriptor validation discards that record and allows restoration to continue. A database record key whose identity-hash length is not 32 bytes causes a panic before these descriptor validations (restoration loop). Restoration does not recheck QUICKeyHash, bind the descriptor to a live session link key, or apply the live late-upload cut. The receiver runs signature verification before metadata removal. Thus, a different AcceptDescriptorMetadata setting can change the restored in-memory metadata without a change to the stored upload (restored-upload validation).

A node reload verifies the new node counts using the running layer and minimum-node settings (node-count validation). It loads all proposed node identity keys. It then replaces the mix, gateway, service, replica, and reverse-key tables as one operation. Any failure retains the current tables (node reload).

Reload does not change the startup-only Server, Authorities, Logging, Parameters, Debug, Topology, or SphinxGeometry settings. It also does not remove accepted descriptors or existing round messages. New uploads use the new authorization tables. Descriptor selection from the vote tally verifies current authorization again. Thus an accepted descriptor can remain in memory after a reload but fail later selection (tally authorization).

Document construction

Votes, voting certificates, and consensuses share the document body format. Their inputs differ as follows. V is the stored voting epoch.

Key Value Explanation
Vote Local descriptors, configured rates and notice, zero[32] SRV Copy GenesisEpoch and prior-random history from document V-1 if present. Otherwise set GenesisEpoch to V and retain stored history. Include this authority’s signed commitment only. Leave reveals null.
Voting certificate Tallied descriptors, rates and notice, zero[32] SRV Include only locally held commitments with a matching held reveal. Both maps are present, including when empty. Sign, then run FullDocument before storing or sending it.
Consensus Tallied descriptors, rates and notice, computed SRV First require the certificate threshold, reconcile pairs, and require the surviving-pair threshold. Compute SRV and update history. Build, run FullDocument and the configured topology test, then sign. Leave commit and reveal maps null.

The builder sets Version to “v1”, Epoch to V, and GenesisEpoch to the stored genesis epoch. It copies the supplied five voted rates. LambdaG comes from local configuration under gateway rate arithmetic. It copies the configured PKISignatureScheme and geometry hash, the supplied notice, and stored prior-random history. Configured replica IDs and keys include the full authorized set in R-26-7 order. Empty optional fields follow the field reference.

Split mix descriptors by flags: gateway first, then service node, then ordinary mix. Place ordinary mixes by the configured fixed topology order. Gateway and service lists retain the supplied descriptor order. Replica descriptors retain their supplied order. The vote uses locally collected descriptors. Tallying sorts the included descriptors and replicas for certificate and consensus construction. Voting documents therefore need not share one descriptor order across different authors.

Before building the consensus, initialize history to [SRV, SRV] when history is empty or V equals GenesisEpoch. Otherwise, at V modulo 504 equal to zero, set history to [SRV, old_history[0]]. Other rounds preserve history. This update precedes tallying and final document validation. It changes stored state even if a later construction condition fails.

The tagged constructors and common builder implement these steps.

Ordered reconciliation

Reconciliation processes accepted voting certificates. It compares signed envelope bytes, not only their certified payloads. Certificate authors and cited identities use ascending 32-byte identity-hash order.

Start with empty commit and reveal maps. Do not seed them from separately received votes or reveals. For each entry, verify these conditions in order:

  1. The cited identity is an authorized authority.
  2. Its commit envelope passes certificate verification.
  3. A reveal envelope exists under the same identity.
  4. That reveal envelope passes certificate verification under that identity’s key.
  5. Both certified payloads have 40 bytes and start with BE_uint64(V).
  6. The commit’s last 32 bytes equal H of the entire reveal payload.

A failed condition excludes the certificate author and stops its entry loop. A verified pair enters reconciliation.

bad = empty set
commits = empty map
reveals = empty map

for author in sorted(certificate authors):
    if author in bad:
        continue
    certificate = certificates[author]
    for signer in sorted(certificate.commit identities):
        if signer in bad:
            continue
        if the ordered pair checks fail:
            bad.add(author)
            break
        commit = certificate.commits[signer]
        reveal = certificate.reveals[signer]
        if signer not in commits:
            commits[signer] = commit
        else if commits[signer] != commit:
            bad.add(signer)
        if signer not in reveals:
            reveals[signer] = reveal
        else if reveals[signer] != reveal:
            bad.add(signer)

for signer in bad:
    delete commits[signer]
    delete reveals[signer]

The reveal comparison still runs when the commit comparison excludes its signer. An exclusion does not remove earlier entries attributed to other identities. A certificate author excluded during its own entry loop does not automatically stop that loop. Only a failed pair condition stops it. At each certificate’s turn, skip its author if already excluded.

The resulting maps supply the surviving-pair threshold and SRV. The tagged reconciliation implements this order.

The topology draw

The shared random value seeds one deterministic generator. Every authority MUST consume that generator in the same order.

The generator uses original ChaCha20 with a 256-bit SRV key, an eight-byte zero nonce, and initial 64-bit block counter zero. It reads consecutive keystream bytes without reset. It MUST stop before the counter wraps. Every draw from the topology generator MUST use this integer algorithm.

  • One draw, called Int63, reads 8 bytes of keystream and clears the top bit of the last byte. It then reads those 8 bytes as a little-endian unsigned 64-bit integer.
  • Int31 is Int63 shifted right by 32 and read as a signed 32-bit integer.
  • Intn(n), for n of at least 1 and at most 2^31-1, is as follows. When n is a power of two, it is Int31 masked with n-1. In every other case the party draws Int31 again while the value is above 2^31-1-(2^31 mod n), and then takes the value modulo n. A larger n is outside this definition. n is the size of the input list, which may include all mixes.
  • The permutation of n items MUST iterate i from 0 to n-1 inclusive. At each step it draws j = Intn(i+1), then sets m[i] to m[j], then sets m[j] to i.

The iteration at i = 0 MUST NOT be skipped. It assigns m[0] = 0 and consumes one draw. Skipping it changes all later draws.

Layer assignment

The assignment algorithm places accepted mixes into layers. A fixed topology takes precedence over automatic placement.

R-18-5 uses the following assignment algorithms. Sort input mix descriptors by identity-key hash (R-16-5). The prior consensus is the document for V-1. An older retained document does not select this algorithm. Layer indexes start at zero. Each append preserves the layer’s order.

With no prior consensus and no configured fixed topology, an authority MUST:

  1. Start a fresh generator keyed by the SRV and draw Perm(number of input mixes).
  2. Read mixes in that permutation order. Append each to successive layers, wrapping from the last layer back to layer zero.

With a prior consensus and no configured fixed topology, an authority MUST:

  1. Start one fresh generator keyed by the SRV. Create Layers empty output lists and a pending set containing every input mix, keyed by identity hash.
  2. Set target = floor(number of input mixes / Layers). Set priorLayers to the smaller of the prior layer count and Layers.
  3. Visit prior layers 0 through priorLayers-1 in order. For each layer, draw Perm of its full prior list, including absent nodes. Visit entries in that order. If the output layer contains target nodes, stop visiting entries in that layer. Otherwise, append a prior entry’s current descriptor if its identity remains pending. Remove each appended identity from the pending set.
  4. Sort the remaining descriptors by identity hash. Draw Perm of this pending list with the same generator. Keep one cursor into that permutation for the next steps.
  5. Visit output layers 0 through priorLayers-1. Fill each to target by appending the next pending descriptor and advancing the cursor.
  6. Starting at layer zero, append each remaining pending descriptor to successive layers, wrapping to layer zero after the last layer. Preserve descriptors already appended in steps 3 and 5.

An empty permutation consumes no draws. Draw each prior-layer permutation in full, even when target is zero or the algorithm uses few entries. Then draw the pending permutation from the same generator.

With a configured fixed topology, an authority MUST visit configured layers and their identity lists in configuration order. It appends the current descriptor for each included identity and skips identities with no included descriptor. It uses no random draws for this assignment. This case takes precedence over prior-consensus placement.

Gateway rate arithmetic

LambdaG uses the configured largest layer size n and configured gateway count g. It does not use the number of surviving descriptors. If g is zero or n is at most one, add configured LambdaP and LambdaL. Otherwise, evaluate (((n * n) * Log(n)) * Mu) / g from left to right. Convert n and g to binary64 first. Mu, LambdaP, and LambdaL are binary64 configuration values.

Round every arithmetic operation to binary64 with nearest-even rounding. Do not fuse multiply-add operations. The recorded bit patterns come from separate multiply and add operations. Go can emit fused multiply-add on arm64. On such a platform, evaluate each product and sum separately. Parentheses fix evaluation order. The logarithm constants are binary64 bit patterns.

Key Value Explanation
SQRT_HALF 3fe6a09e667f3bcd Reduction boundary, sqrt(2)/2.
LN2_HI 3fe62e42fee00000 High part of ln(2).
LN2_LO 3dea39ef35793c76 Low part of ln(2).
C1 3fe5555555555593 Polynomial coefficient.
C2 3fd999999997fa04 Polynomial coefficient.
C3 3fd2492494229359 Polynomial coefficient.
C4 3fcc71c51d8e78af Polynomial coefficient.
C5 3fc7466496cb03de Polynomial coefficient.
C6 3fc39a09d078c69f Polynomial coefficient.
C7 3fc2f112df3e5244 Polynomial coefficient.

For positive integer n of at least two, define Log as follows. Decompose its binary64 value exactly as fraction * 2^exponent, with 0.5 <= fraction < 1.

if fraction < SQRT_HALF:
    fraction = fraction * 2
    exponent = exponent - 1
f = fraction - 1
k = binary64(exponent)
s = f / (2 + f)
z = s * s
w = z * z
a = z * (C1 + w * (C3 + w * (C5 + w * C7)))
b = w * (C2 + w * (C4 + w * C6))
r = a + b
q = (0.5 * f) * f
Log(n) = (k * LN2_HI) - ((q - ((s * (q + r)) + (k * LN2_LO))) - f)

The Go logarithm uses this reduction and polynomial. For n = 3, Log(n) has bits 3ff193ea7aad030a. The tagged rate calculation uses these configured inputs.

Appendix G. References

Network operation depends on the packet, transport, client, storage, and certificate protocols.

Key Value Explanation
KATZMIXNET Mix network design Routing, mixing, and cover traffic.
KATZKEMSPHINX KEM Sphinx Packet headers using a KEM.
KATZTHINCLIENT Thin client The local library and daemon interface.
KATZDECOY Mix decoy statistics The design behind LoopStats.
KATZREPLAY Replay detection Epoch keys and replay caches.
KATZGLOSSARY Glossary Terms shared across the specifications.
KATZPKI Original PKI specification, KATZMIXPKI citation The original design, which this protocol continues.
KATZWIRE Katzenpost Wire Protocol Specification. /docs/specs/wireprotocol The PQ Noise link this protocol runs over.
KATZSPHINX Sphinx Packet Format Specification. /docs/specs/sphinx The packet format the Sphinx keys and the geometry of byte identity belong to.
KATZPIGEON Pigeonhole Specification. /docs/specs/pigeonhole The storage system the courier service and the storage replicas serve.
KATZCERT Certificate Format Specification. /docs/specs/certificate The certificate envelope.
RFC2119 Bradner, S. Key words for use in RFCs to Indicate Requirement Levels. RFC 2119, March 1997.
RFC7049 Bormann, C., Hoffman, P. Concise Binary Object Representation (CBOR). RFC 7049, October 2013. Section 3.9 gives the canonical map key order this protocol uses.
RFC8949 Bormann, C., Hoffman, P. Concise Binary Object Representation (CBOR). RFC 8949, December 2020.
RFC9000 Iyengar, J., Thomson, M. QUIC, a UDP-Based Multiplexed and Secure Transport. RFC 9000, May 2021.
RFC5869 Krawczyk, H., Eronen, P. HMAC-based Extract-and-Expand Key Derivation Function (HKDF). RFC 5869, May 2010.
PQNOISE Angel, Y., Dowling, B., Hulsing, A., Schwabe, P., Weber, F. Post Quantum Noise. The PqXX pattern.
SPHINCSPLUS Bernstein, P., Hulsing, A., Kolbl, S., Niederhagen, R., Rijneveld, J., Schwabe, P. SPHINCS+. The signature half of Ed25519 Sphincs+.
TORSRV Tor Shared Random Subsystem Specification. The commit-and-reveal design of shared random value and topology follows it.
BLAKE2 Aumasson, J., Neves, S., Wilcox-O’Hearn, Z., Winnerlein, C. BLAKE2. RFC 7693 H in constants and notation.

Appendix H. Conformance and vectors

Conformance testing MUST cover:

  1. A named test passes for every protocol requirement.
  2. A mixed network with the reference implementation produces matching, threshold-signed consensuses under successful-round conditions. This test includes an epoch divisible by 504.
epoch   245448 (245448 mod 504 == 0; 2026-10-01 00:00 UTC)
RN      0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
REVEAL  000000000003bec8441edc56cebc8e285d02267aa650819f15add7b06ef9b41b2690128dce655924
COMMIT  000000000003bec8e95138c6f44dfe875fa3daa92364a1ece9b315f25a7c7777f6f2064745a254c0

KEY_A   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
REV_A   000000000003bec8f15f1ea681af55bcdc2aff6ae09ca8ca122c84c4770d9571293f73e5d1e032f3
KEY_B   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
REV_B   000000000003bec8afa49d096346b9834fdc42890e35cae35b3ff4546094e747aa5468fe7a961334
PREV    (absent: 32 zero bytes)
order   A then B (descending by reveal bytes)
SRV     702b1bb93f15155761dc058d56edd07d2a5127478566f520366fb4b490a5e8ae

BODY contains the Certified bytes of a document’s envelope, encoded under R-6-3. This encoding fixture has one empty layer, no nodes, no commits, no reveals, and short byte strings. It is not a well-formed document.

Epoch 245448, GenesisEpoch 245439, Mu 0.001, LambdaP 0.5, LambdaL 0.0005,
LambdaM 0.0005, LambdaG 0.25, LambdaR 0.02, Topology [nil], GatewayNodes [],
ServiceNodes [], StorageReplicas [], SharedRandomValue 00000000,
PriorSharedRandom [01, 02], SphinxGeometryHash aa, Version "v1",
PKISignatureScheme "Ed25519", every other field empty or nil

BODY
b3624d75fb3f50624dd2f1a9fc6545706f63681a0003bec8674c616d62646147
f93400674c616d6264614cfb3f40624dd2f1a9fc674c616d6264614dfb3f4062
4dd2f1a9fc674c616d62646150f93800674c616d62646152fb3f947ae147ae14
7b6756657273696f6e62763168546f706f6c6f677981f66c476174657761794e
6f646573806c47656e6573697345706f63681a0003bebf6c536572766963654e
6f646573806f53746f726167655265706c6963617380715072696f7253686172
656452616e646f6d82410141027153686172656452616e646f6d56616c756544
0000000072504b495369676e6174757265536368656d65674564323535313972
53686172656452616e646f6d436f6d6d6974f67253686172656452616e646f6d
52657665616cf672537068696e7847656f6d657472794861736841aa

PARAMKEY groups the example network’s five voted rates under R-16-3.

Mu 0.001, LambdaP 0.001, LambdaL 0.0005, LambdaM 0.0005, LambdaR 0.02

PARAMKEY (81 bytes)
a5624d75fb3f50624dd2f1a9fc674c616d6264614cfb3f40624dd2f1a9fc674c
616d6264614dfb3f40624dd2f1a9fc674c616d62646150fb3f50624dd2f1a9fc
674c616d62646152fb3f947ae147ae147b

GEOMETRY_CBOR and SPHINXGEOMETRYHASH encode the example network’s Sphinx geometry (R-26-2). The list gives the 14 fields in struct order. CBOR uses the length-first key order of R-6-3.

NrHops 5, PacketLength 3082, HeaderLength 476, RoutingInfoLength 410,
PerHopRoutingInfoLength 82, SURBLength 572, SphinxPlaintextHeaderLength 2,
PayloadTagLength 32, ForwardPayloadLength 2574, UserForwardPayloadLength 2000,
NextNodeHopLength 65, SPRPKeyMaterialLength 64, NIKEName "x25519", KEMName ""

GEOMETRY_CBOR (273 bytes)
ae664e72486f707305674b454d4e616d6560684e494b454e616d656678323535
31396a535552424c656e67746819023c6c4865616465724c656e6774681901dc
6c5061636b65744c656e677468190c0a705061796c6f61645461674c656e6774
681820714e6578744e6f6465486f704c656e677468184171526f7574696e6749
6e666f4c656e67746819019a74466f72776172645061796c6f61644c656e6774
68190a0e75535052504b65794d6174657269616c4c656e677468184077506572
486f70526f7574696e67496e666f4c656e6774681852781855736572466f7277
6172645061796c6f61644c656e6774681907d0781b537068696e78506c61696e
746578744865616465724c656e67746802

SPHINXGEOMETRYHASH (32 bytes)
ed57ca3bc2165a0483e0d16d97511e15f318ed5cedfc3e7388c85ee8a2c0fb47

The message ceiling is 264512 bytes for these synthetic inputs. The synthetic key sizes differ from the Namenlos keys.

sign 32, sig 64, link 1216, sphinx 32, envelope 1024
nodes 16, replicas 4, authorities 6

mix      = 32 + 1216 + 3 * 32 + 4096            = 5440
replica  = 32 + 1216 + 3 * 1024 + 4096          = 8416
unsigned = 8192 + 16 * 5440 + 4 * 8416          = 128896
signed   = 128896 + 6 * (64 + 96)
                  + 2 * 6 * (64 + 96 + 40)      = 132256
ceiling  = 2 * 132256 = 264512, above the 262144 floor

CERT_CBOR holds REVEAL in an envelope with an artificial expiration and a short signature. It tests encoding only. Signature verification fails.

Version 0, Expiration 245453, KeyType "Ed25519", Certified = REVEAL above,
one signature keyed by 32 bytes of 0xaa, with Payload 01020304

CERT_CBOR (204 bytes)
a5674b65795479706567456432353531396756657273696f6e00694365727469
666965645828000000000003bec8441edc56cebc8e285d02267aa650819f15ad
d7b06ef9b41b2690128dce6559246a45787069726174696f6e1a0003becd6a53
69676e617475726573a15820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaa2675061796c6f616444010203046f5075626c69
634b657953756d3235365820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaa

SIGNEDUPLOAD_CBOR uses the upload encoding. Its MixDescriptor byte string starts with 0x58cb, which encodes a length of 203 bytes. The contents are the descriptor’s own canonical CBOR. Signature and LoopStats are inline maps.

Signature {PublicKeySum256 32 bytes of 0xbb, Payload 0506}
MixDescriptor {Name "mix1", Epoch 245448, IdentityKey bb, LinkKey cc,
  MixKeys {245448: dd}, Addresses {"tcp": ["tcp://127.0.0.1:1"]},
  every other field empty, nil, or false}
LoopStats {MixIdentityHash 32 bytes of 0xbb, Epoch 245448,
  SegmentRatios {01 then 63 zero bytes: 0.5}}

SIGNEDUPLOAD_CBOR (448 bytes)
a3694c6f6f705374617473a36545706f63681a0003bec86d5365676d656e7452
6174696f73a15840010000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000f938006f4d69784964656e74697479486173685820bbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb695369
676e6174757265a2675061796c6f61644205066f5075626c69634b657953756d
3235365820bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbb6d4d697844657363726970746f7258cbad644e616d65646d697831
6545706f63681a0003bec8674c696e6b4b657941cc674d69784b657973a11a00
03bec841dd6756657273696f6e6069416464726573736573a163746370817174
63703a2f2f3132372e302e302e313a31694b6165747a6368656ef66a4c6f6164
576569676874006b4964656e746974794b657941bb6d4973476174657761794e
6f6465f46d4973536572766963654e6f6465f47241757468656e746963617469
6f6e5479706560774b6165747a6368656e416476657274697a656444617461f6

Automatic topology assignment starts with this SRV, no prior consensus, six mixes, and Layers 3. The authority permutes all six mixes and assigns them to successive layers, under the layer assignment algorithm. The mix numbers 0 through 5 represent ascending identity-key hash order (R-16-5).

SRV     702b1bb93f15155761dc058d56edd07d2a5127478566f520366fb4b490a5e8ae
Perm(6) = [1 5 2 4 3 0]

layer 0 = mix 1, mix 4
layer 1 = mix 5, mix 3
layer 2 = mix 2, mix 0

Direct consensus reply

A successful direct fetch from the Namenlos dirauth annares returns a consensus command. Let B be the byte length of its certificate envelope.

The Namenlos authority configuration sets the address and cryptographic schemes. The consensus command encoder adds the status and command header. The session encoder adds the two encrypted records after the PQ Noise handshake. TCP segments carry slices of the encrypted stream inside IPv4 datagrams. Their boundaries need not align with directory commands. IPv4 datagrams can fragment in transit. Directory commands travel directly between peers, outside Sphinx packets. The gateway consensus commands use different framing.


  1. Reference implementation: Katzenpost v0.0.107. ↩︎

  2. Namenlos started in 2022, as recorded in the 37C3 talk description. ↩︎

Last modified November 3, 2026: specs: process review feedback (11a70db8)