updates

keys bound to the transcript, the suite and the scheme version

every secret and every key now depends on the transcript of its exchange, on the suite and on the scheme version. two sides that differ in one byte of the transcript get different keys, and the handshake or the setup fails.

a change to the key schedule reached the main branch. it takes no byte on the wire, and the numbers on the research page are the same preliminary series as before.

every key depends on the whole exchange

until now the derived keys did not depend on the whole exchange. an agreement used a fixed label with one value as its salt, and the agreement of a link was bound to the initiator’s ephemeral key alone.

now every key exchange has a transcript: its public data in one unambiguous layout. for a circuit setup that is the format version, the hop index, the identifier of the link into the node, the node’s onion key and the client’s ephemeral key. for a link it is the version, the mode, the ephemeral keys of both sides as they crossed the wire and, on an authenticated link, the responder’s link key.

the hash of the transcript, the suite and the scheme version go into every agreement and into the derivation of every key. the crypto interface derives no key without a transcript, and a caller names only the purpose of a key, never a label of its own. on an authenticated link the two agreements, of the two ephemeral keys and of the initiator’s ephemeral key with the responder’s link key, are chained, so the frame keys depend on both.

a node now puts the bytes of its own public key into the transcript, so at start and before it takes every new onion key it checks that the public key agrees with the private one, and refuses a pair that does not.

why it matters: this is the countermeasure against a replaced party or parameter of a key agreement, be it a public key, the link mode, the suite or the version. if the two sides differ in one byte of the transcript they get different keys, the first aead check fails, and the handshake or the setup fails with it. a copy of a setup cell that carries another encoding of the ephemeral key no longer opens the layer either.

the wire stays the same, versions do not mix

the binding takes no byte: the cell is still 512 bytes, a setup cell still carries four hops on c25519 and three on gost, and the hello, the answer and the frames of a link carry no field for it.

there is no negotiation of the scheme version. nodes and clients from before this change, v0.2.0 included, do not work with the new ones: the link handshake ends at the confirmation frame with no cause named. nodes and clients are updated together.

the binding has no switch, like the replay tag of a setup cell and the link confirmation frame: a build without it would be a second key schedule, that is, a way to downgrade. no experiment block measures its contribution; the benchmarks time its parts on both suites.

how it was checked

  • golden vectors on both suites pin the transcripts and their hashes, every derived key and the chaining of two agreements. every pinned value was recomputed by an independent implementation outside the repository, checked first against the examples of the standards: no mismatch.
  • every binding was broken on purpose: a part of the transcript dropped or swapped, the hash left out of a derivation, link keys taken from one agreement alone, purposes swapped, a context of the other suite accepted. every such change made a named test assertion fail.
  • tests on both suites: a setup layer does not open with another onion key, hop index or link identifier, and a changed mode byte or another link key at the responder fails the link handshake. the keys of every hop of a chain are recomputed in a test from a transcript written out byte by byte.
  • the change was deployed to the testbed cluster: five relays enrolled, a circuit of three hops, round trips pass, and a client with a foreign anchor is refused.

what this does not fix

  • the transcript binds keys, not the identity of a node. a node that publishes another node’s link key in its own descriptor is not caught by this: the chain check does not compare link keys.
  • the responder of a link still does not authenticate the initiator, and the handshake has no explicit confirmation from it. authenticating the initiator on links between nodes is planned.
  • only the tests keep the content of the transcript right. there is no formal model of the setup or of the link handshake; a model of the setup is planned.
  • the key schedule follows the shape of noise exchanges but is not noise, so the published noise test vectors do not apply. the scheme has vectors of its own.
  • frame keys and hop keys do not change while a link or a circuit lives. rotation inside them is planned.

next on the roadmap are still a recipient client with an end-to-end layer and deniable authentication between clients.

the full list is on the limits page and in limitations. the transcripts, the labels and the formulas of both suites are in the section “key derivation” of crypto, the threats and countermeasures in the threat model.