Contents Menu Expand Light mode Dark mode Auto light/dark, in light mode Auto light/dark, in dark mode Skip to content
libtracer
libtracer

Getting started

  • Getting started
  • Examples
    • In-process pub/sub
    • Pub/sub fan-out & dispatch cost
    • Wire codec round-trip
    • Wire codec deep-dive & throughput
    • Rope scatter-gather
    • Two nodes over a wire — FWD delivery
    • Composition axes
    • Register a vertex, and address it
    • Read and write
    • await
    • Creation
    • :children[]
    • Retirement
    • A HANDLER vertex
    • A STREAM vertex
    • Subscribe to one vertex
    • One edge, a whole subtree
    • Delivery terminates at the target
    • The delivery policy is per subscription
    • Unsubscribe & the release hook
    • Unsubscribing from inside a delivery
    • Retire drops a producer's subscriptions
    • The TLV header and the opt byte
    • Structured or opaque — one bit decides
    • A PATH body is packed segment records
    • The escape record
    • The trailer: CRC and timestamp
    • What the frame reader refuses
    • decode_into: a flat arena
    • tlv_view_t: a scattered frame
    • The segment, and its refcount
    • subview
    • borrow: the app's own bytes
    • The rope
    • subrope and the iovec egress
    • A bounded backend
    • A DEVICE link
    • A shared seam needs a thread-safe backend
    • The failable block seam
    • Two L0 seams, and the question that picks
    • A bump source
    • The upstream decides what the buffer means
    • A long-lived seam has to recycle
    • Classes do not share
    • A container that fails by value
    • The std::pmr adapter
    • Open by default, and the first ACE is the lock
    • The caller context is not the subject
    • access_mask is a bitfield
    • EVERYONE@ is reserved both ways
    • Effective ACL = own + inherited
    • expires_ns is checked against your clock
    • Two evaluators, and DENY
    • An ACL is a security document
    • The dst is a source route
    • Terminus or forward — one test decides
    • One NAME, one slot
    • A mount run is consumed whole
    • Three nodes, and a forwarder that stores nothing
    • The src you accumulated is the way home
    • A repeating flow buys its route back
    • A stale label is dropped and NACK'd
    • One seam, every wire technology
    • A kind is a NAME, resolved twice
    • DIAL and LISTEN are two constructors
    • A datagram already has boundaries
    • A stream has none, so the kind supplies them
    • No frame crosses until the Upgrade completes
    • The one BUS kind
    • One listener, many slots
    • Rust: a VALUE frame and its CRC trailer
    • Rust: an address is packed segment records
    • Rust: a remote write is one FWD frame
    • Rust: subscribing is a write
    • Rust: reading a node's :stats
    • TypeScript: a VALUE frame and its CRC trailer
    • TypeScript: write a remote vertex, then read it
    • TypeScript: subscribe to a remote producer
    • TypeScript: a remote failure is a typed error
    • TypeScript: dial a WebSocket link
    • ESP-IDF: the component, and nothing else
    • ESP-IDF: sizing the arena
    • ESP-IDF: one task, no pool locks
    • ESP-IDF: one link, and its max_frame
    • ESP-IDF: reading the sub-pools' :stats

Specification

  • The specification
    • Protocol v1 — the wire format

RFCs and decisions

  • ADR and RFC index
    • RFC 0001 — Protocol-v1 wire-format consistency consolidation
    • RFC 0002 — Protocol error model: the tr:: concept namespace
    • RFC 0003 — Concrete-path delivery for bridged wildcard subscriptions
    • RFC 0004 — Remote operation addressing: path-as-route + the FWD/FIELD frames
    • RFC 0005 — Subtree subscriptions: vertical bubbling, branch-write decomposition, write-creates
    • RFC 0006 — Nesting depth is receiver-resource-bounded: the fixed cap of 32 is removed
    • RFC 0007 — SUBSCRIBER delivery terminates at the target: no automatic re-dispatch to the target’s subscribers
    • RFC 0008 — Vertex operations: assign and propagate; structural selective propagation; value-agnostic per-vertex delivery_mode
    • RFC 0009 — Vertex removal and subscriber eviction
    • RFC 0010 — Owner-writable application property fields: the field descriptor table, the reserved settings.app namespace, and owner-defined :schema
    • RFC 0011 — Node identity facet: a wire-readable, pre-auth :identity field serving the ADR-0045 ed25519 TOFU public key at every vertex
    • RFC 0013 — Readable creatable-child-type catalog: the :children.schema read
    • RFC 0014 — Creator endpoint: connection lifecycle and link liveness
    • RFC 0016 — Composed branch read: a plain READ of a branch serves the folded POINT tree of its registered subtree
    • RFC 0017 — Element addressing: [n] on the value plane, and per-element delivery
    • RFC 0018 — Packed path segments: a PATH body becomes length-prefixed records
    • RFC 0019 — Path depth is bounded by bytes: the 32-segment PATH cap is deleted
    • RFC 0020 — A bus link’s connection NAME is not a routable next-hop (reject, never broadcast, on the request plane)
    • RFC 0021 — The frame of reference of a wire SUBSCRIBER’s PATH target
    • RFC 0022 — Delivery policy is per-subscription; settings_t dissolves
    • RFC 0023 — The path segment cap is repriced: 32 → 255, derived from the wire’s own widths
    • RFC 0024 — Bound paths: node-scoped vertex-ref source routing
    • RFC 0025 — Stream-class values: delivery classes over the rope primitive
    • RFC 0026 — The ACE access_mask canonical wire width is u32
    • RFC 0027 — Label-switched path compression: minting a per-host path label across the wire
    • RFC 0028 — The lean value path: one block per publish, copy-or-share by size, retention per vertex, sync as a trait
    • RFC 0029 — One path primitive: the owner-issued (index, generation) pair, carried per hop, local = forwarded
    • RFC 0030 — The host API walks the graph: a graph-owned path object, creation refused by default, the reply as a remote write, AWAIT and REPLY retired
    • RFC 0031 — Bus-session anchors are child vertices under their door: one walk and one gate reach a session, send-through is directed
    • RFC 0032 — Delete COMPACT and the per-link handle tables: every stream rides the chain, and no hop holds state for it
    • RFC 0033 — A minimal Noise link binding: NNpsk0 over a datagram carrier

Reference

  • Reference (descriptive)
    • Overview — the six-layer model
    • Module catalog & composition
    • Deployment profiles
    • Concurrency & scaling
    • Reclamation policy
    • Memory substrate
    • Views & ownership
    • Data format
    • Protocol-defined TLVs
    • Graph model
    • Addressing
    • Communication flows
    • User data packing
    • Vertex roles & aggregation
    • Host embedding
    • Network formation
    • CAN transport
    • WebSocket session authentication
    • Noise link binding
    • Composition over the network
    • Transports are vertices
    • Bindings map
    • ROS 2 integration (rmw_tracer)
    • Backpressure & sizing
  • Design notes
    • Concurrency & scaling
      • Scaling and serialization
      • Write and delivery path
    • Zero-copy and flatten
    • Build configuration
      • The configuration space
    • Failable allocation and backpressure

C++ API reference

  • C++ API reference
    • Interface map
    • File map — header to page
    • status & errors — result taxonomy
    • config — the build's traits type
    • instrumentation — reachability counters
    • segment — refcounted bytes
    • backends — allocators
    • views — view_t / rope_t / cast
    • frame-codec — TLV codec + CRC
    • Wire format, bit by bit
    • path — addressing
    • graph — vertices & dispatch
    • security & ACL — access control
    • security & Noise — the Noise link's crypto
    • fwd-router — FWD routing and the /net plane
    • transport — the wire
    • connection config — the SPEC config keys
    • can — the header-elided CAN stack

Interoperate

  • Interoperability
  • Build a custom device
  • A production ESP32 node
  • Capability matrix
  • Implementation registry

Evidence

  • Performance & conformance
  • Test report

Glossary

  • Context glossary

Start here

  • Route by intent
Back to top

security & Noise — the Noise link’s crypto backend¶

In one paragraph

The Noise link binding (RFC-0033) carries frames inside a Noise_NNpsk0_25519_ChaChaPoly_SHA256 session. This module is its cryptographic half: the handshake and transport steps, written once over a crypto backend chosen at compile time (OpenSSL, libsodium or mbedTLS through PSA). The core stays crypto-free. A build that does not name a backend compiles none of this and links no crypto library. Everything works on caller memory: no allocation of its own, no clock and no randomness. On every backend, nothing allocates before a first message’s PSK tag is proven, and nothing allocates per frame.

What it does¶

security_noise.hpp holds the protocol, written once over a backend type B:

  • symmetric_state_t<B> is Noise’s SymmetricState (ck, h and the handshake cipher key), a plain value that copies. psk_state builds the state after the prologue and the psk token. That state is the same for every handshake on a link, so a link computes it once and starts each handshake from a copy (RFC-0033 §5.8).

  • handshake_t<B> is one side of the handshake over RFC-0033’s datagrams: write_first and read_second for the initiator, read_first and write_second for the responder, then split. read_first checks the PSK tag with no Diffie-Hellman and no key generation. The caller checks the counter against its mark, and only write_second runs ee. So a message under a wrong PSK, or a replayed one, costs one MixHash, one HKDF and one tag check, and no memory. The tag is checked with the link’s handshake cipher, a backend cipher the link builds once beside its PSK state and passes to each handshake_t, which re-keys it per message. An all-zero X25519 result is refused (LOW_ORDER) whatever the backend does.

  • transport_cipher_t<B> holds a session’s two transport keys, keyed into the backend once at split. seal writes the type byte, the nonce, the ciphertext and the tag, in place when the frame already sits at its payload offset. parse_transport_header reads the type and the nonce before any decryption, which is where the replay pre-check goes, and open then decrypts in place. Nonces at or above 2^62 are refused on both sides. A cipher takes one call at a time: the link serialises each direction’s senders, or keeps one session cipher per sending thread.

Every refusal is a refusal_t, which the link maps onto RFC-0033 §5.9’s counters.

The link half (the slots, the counter and the mark, the replay window, the key phase, the now deadlines and the counters) is the next slice of #2064, built on these types.

Choosing a backend¶

One CMake cache variable, LIBTRACER_NOISE_CRYPTO, picks the backend. Any value but none makes the header-only target libtracer_noise, which carries the library and a LIBTRACER_NOISE_CRYPTO_<BACKEND> define. security_noise.hpp turns that define into the alias tr::net::noise::default_crypto_t. There is no runtime switch.

value

backend

library

notes

none (default)

—

—

the module is not compiled

openssl

openssl_crypto_t

libcrypto 3

the fastest AEAD on a host at 1 KiB and above; needs the deprecated SHA256_* calls (not a no-deprecated build)

sodium

sodium_crypto_t

libsodium

no allocation at all, handshake included

psa

psa_crypto_t

mbedTLS 3.6.1+ or 4.x through PSA

the ESP-IDF backend; needs a sized static key-slot table (below)

A backend is a type that meets the crypto_backend concept (security_noise_crypto.hpp): SHA-256, Noise’s HKDF, an X25519 key pair and a ChaCha20-Poly1305 cipher. hash, hkdf and the cipher’s set_key, seal and open must not allocate; building a cipher and the X25519 calls may. A test can wrap a backend in another compile-time policy, as security_noise_test does to count the X25519 calls a refused first message makes (none).

The PSA backend needs three things from the mbedTLS build:

  • MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS, or every PSA call copies its buffers through the heap. ESP-IDF’s port sets it.

  • MBEDTLS_PSA_STATIC_KEY_SLOTS without MBEDTLS_PSA_KEY_STORE_DYNAMIC (mbedTLS 3.6.1 and later), or every key import allocates, a first message’s handshake key included. security_noise_psa.hpp refuses to build without it.

  • A slot table sized by the app, because every PSA user in the image shares it. The rule:

    setting

    at least

    MBEDTLS_PSA_KEY_SLOT_COUNT

    6 per Noise link (two sessions of two keys during a rekey, the handshake cipher, the ephemeral key), plus the peak of the image’s other PSA users

    MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE

    the largest key any of them imports; Noise’s keys are all 32 B

    A Noise-only image needs 8 slots of 32 B: 360 B of .bss on an ESP32-C6, and the configuration the CI psa leg tests (8 is the fewest the two suites pass with, holding both ends of a session in one process). Rough figures for a mixed image at 16 slots: about 2 KB with TLS that verifies only ECDSA P-256 or X25519 peers, about 5 KB with RSA-2048 server certificates, about 20 KB if the image holds its own RSA-2048 private key. Left unsized, mbedTLS sizes 32 slots for the largest key type enabled: 76,616 B at ESP-IDF’s defaults, which enable RSA-4096 key pairs. That figure is the unsized default, not the cost of Noise. A table that is too small fails at run time, when an import is refused, not at build. ESP-IDF has no option for any of this: the Kconfig help of CONFIG_LIBTRACER_NOISE_CRYPTO_PSA shows the header an app passes as MBEDTLS_USER_CONFIG_FILE.

  • A NARROW image that needs PSA for nothing else should use libsodium (ESP-IDF’s component registry has a port). It allocates nothing, has no global table, and needs no mbedTLS change. The backend is a compile-time choice, so the swap is one define.

  • On ESP-IDF, CONFIG_MBEDTLS_CHACHA20_C and CONFIG_MBEDTLS_CHACHAPOLY_C, which are off by default. The Kconfig option selects them.

What it costs¶

Allocation calls, counted process-wide (malloc and its family) by security_noise_test on the host (GCC 13, glibc). The library’s own allocations are included. The zero rows are asserted.

openssl (3.0.13)

sodium (1.0.18)

psa (mbedTLS 4.1.1)

psa (mbedTLS 3.6.5)

per link: PSK state and handshake cipher

3

0

0

0

per session: the two transport ciphers

6

0

0

0

handshake, initiator

34

0

6200

6244

handshake, responder

34

0

6200

6244

a first message read, refused (wrong PSK) or accepted (a replay)

0

0

0

0

per frame (seal + open), 0 B to 65519 B

0

0

0

0

The handshake rows are the X25519 calls: EVP_PKEY objects on OpenSSL, and mbedTLS’s bignum arithmetic on PSA, paid once per session and only after the PSK is proven. The #2065 harness measures the time of each step on each backend.

Conformance¶

conformance_runner replays every tests/conformance/vectors/v1/noise/<case>/transcript.json on the build’s backend and compares each recorded value: the symmetric state after every token, both handshake messages, the handshake hash, the transport keys and the transport datagrams. The first case is RFC-0033 Appendix A. Two third-party Noise implementations reproduce it too, as the RFC’s §15 Q6 ruling requires: noiseprotocol (Python) and snow (Rust, over its own primitives). Their scripts and results sit next to the transcript.

API reference¶

template<class B>
concept crypto_backend¶
#include <security_noise_crypto.hpp>

The compile-time contract of a Noise crypto backend, as this file’s comment states it.

template<crypto_backend B>
class symmetric_state_t¶

Noise’s SymmetricState (Noise §5.2): the chaining key, the handshake hash and the handshake cipher key with its nonce.

The cipher key is held as bytes, and each EncryptAndHash / DecryptAndHash keys the caller’s backend cipher for its one message, so the state is a plain value: it copies, and a copy of the state after the PSK starts each handshake. Its destructor wipes it.

Template Parameters:

B – The crypto backend.

Public Functions

symmetric_state_t(const symmetric_state_t&) = default¶

Copy the state (the handshake start).

symmetric_state_t &operator=(const symmetric_state_t&) = default¶

Copy-assign the state.

inline bool initialize(std::span<const std::byte> protocol_name)¶

Noise InitializeSymmetric(protocol_name) for a name longer than HASHLEN.

Returns:

False if the name is not longer than 32 bytes or the hash failed.

inline bool mix_hash(std::span<const std::byte> data)¶

h = HASH(h || data), for data up to kMaxMixHash bytes.

inline bool mix_key(std::span<const std::byte> ikm)¶

ck, k = HKDF(ck, ikm, 2), then InitializeKey(k).

inline bool mix_key_and_hash(std::span<const std::byte> ikm)¶

ck, temp_h, k = HKDF(ck, ikm, 3), MixHash(temp_h), InitializeKey(k).

inline bool encrypt_and_hash(typename B::aead_t &aead, std::span<const std::byte> pt, std::byte *out)¶

Noise EncryptAndHash: seal pt with h as associated data into out (pt.size() + kTagLen bytes), then mix the ciphertext into h.

NNpsk0’s first token is psk, so a key is always set before any payload: the unkeyed pass-through Noise defines for other patterns is refused here instead of carried.

Parameters:

aead – The cipher to seal with, keyed here with k and cleared again after use.

inline bool decrypt_and_hash(typename B::aead_t &aead, std::span<const std::byte> ct, std::byte *out)¶

Noise DecryptAndHash: open ct with h as associated data into out (ct.size() - kTagLen bytes), then mix the ciphertext into h.

Parameters:

aead – The cipher to open with, keyed here with k and cleared again after use.

Returns:

False on a bad tag, or with no key set; h and the nonce are then unchanged.

inline bool split(key32_t &k1, key32_t &k2) const¶

Noise Split(): k1 encrypts initiator to responder, k2 the reverse.

inline const key32_t &chaining_key() const noexcept¶

The chaining key ck.

inline const key32_t &handshake_hash() const noexcept¶

The handshake hash h; after the last message, the channel binding.

inline const key32_t &cipher_key() const noexcept¶

The handshake cipher key k, meaningful only while has_key holds.

inline bool has_key() const noexcept¶

Whether a cipher key is set (Noise HasKey).

Public Static Attributes

static constexpr std::size_t kMaxMixHash = 64¶

The largest input mix_hash takes: a public key or a hash.

template<crypto_backend B>
bool tr::net::noise::psk_state(const key32_t &psk, symmetric_state_t<B> &out)¶

The symmetric state every handshake on a link starts from: InitializeSymmetric, MixHash(prologue) and the psk token’s MixKeyAndHash(psk) (RFC-0033 §5.8).

The link computes it once when it is built, and again only when its PSK is replaced.

template<crypto_backend B>
class handshake_t¶

One side’s NNpsk0 HandshakeState over RFC-0033’s datagrams.

The initiator calls write_first, then read_second; the responder calls read_first, then write_second. Either then calls split. A refused step leaves the state where it was, so a link can read a datagram into a scratch copy and keep its handshake slot untouched when it is refused.

Each handshake message is sealed or opened with the link’s handshake cipher, passed in and keyed for each message and cleared after it, so it holds no key between messages (RFC-0033 §6.8). The link builds it once, beside its PSK state, so a first message allocates nothing. Handshakes may share it while their calls do not overlap.

Template Parameters:

B – The crypto backend.

Public Functions

inline handshake_t(role_t role, const symmetric_state_t<B> &keyed, typename B::aead_t &cipher)¶

Start a handshake from the link’s PSK state (psk_state).

Parameters:
  • role – This side.

  • keyed – The state after the prologue and the PSK; copied.

  • cipher – The link’s handshake cipher; it must outlive this handshake.

inline std::expected<void, refusal_t> write_first(const key32_t &e_priv, first_payload_t payload, std::span<std::byte, kFirstMessageBytes> out)¶

Initiator: write -> psk, e with payload into the 58-byte out.

Parameters:

e_priv – The ephemeral private key, from the application’s random source: fresh for each handshake, never reused, and wiped by the caller after this call (RFC-0033 §6.8).

inline std::expected<first_payload_t, refusal_t> read_first(std::span<const std::byte> datagram)¶

Responder: check and read a first message, with no Diffie-Hellman.

Return values:
  • MALFORMED – Not 58 bytes, or not type 0x01.

  • AUTH – The PSK tag did not verify.

  • PAYLOAD – Flag bits 7-1 are set.

Returns:

The authentic payload; the caller checks its counter before answering.

inline std::expected<void, refusal_t> write_second(const key32_t &e_priv, std::uint8_t flags, std::span<std::byte, kSecondMessageBytes> out)¶

Responder: write <- e, ee with the flags byte into the 50-byte out.

Parameters:

e_priv – The ephemeral private key, from the application’s random source: fresh for each handshake, never reused, and wiped by the caller after this call (RFC-0033 §6.8).

Return values:

LOW_ORDER – DH(e, re) is all zero; nothing is written.

inline std::expected<std::uint8_t, refusal_t> read_second(std::span<const std::byte> datagram)¶

Initiator: check and read a second message.

Return values:
  • MALFORMED – Not 50 bytes, or not type 0x02.

  • LOW_ORDER – DH(e, re) is all zero.

  • AUTH – The tag did not verify.

  • PAYLOAD – Flag bits 7-2 are set.

Returns:

The authentic flags byte (bit 0 the key phase, bit 1 fresh).

inline std::expected<void, refusal_t> split(transport_cipher_t<B> &out) const¶

After the last message: key out for this side’s two directions.

inline const symmetric_state_t<B> &symmetric_state() const noexcept¶

The symmetric state so far; after the last message, h is the handshake hash.

template<crypto_backend B>
class transport_cipher_t¶

The two transport keys of one session, keyed into the backend once at Split(): seal toward the peer, open from it (Noise §11.4, explicit nonces).

Nonce choice, the replay window and the key phase are the link’s (RFC-0033 §5.5, §5.6); this type applies a given nonce and phase. seal and open allocate nothing, and take one call at a time per direction: a link serialises its senders, or keeps one session cipher per sending thread (the backend contract).

Template Parameters:

B – The crypto backend.

Public Functions

inline bool set_keys(const key32_t &send, const key32_t &recv)¶

Key both directions (what Split() feeds).

inline std::expected<std::size_t, refusal_t> seal(std::uint8_t phase, std::uint64_t n, std::span<const std::byte> frame, std::span<std::byte> out)¶

Write one transport datagram: the type byte for phase, nonce n, then frame sealed with empty associated data, and its tag.

frame may already sit at out.data() + kTransportHeaderBytes, which seals in place; any other overlap with out is undefined.

Return values:
  • MALFORMED – phase is not 0 or 1, the frame is above kMaxFrameBytes, or out is too small.

  • NONCE_BOUND – n is at or above 2^62.

Returns:

The datagram size, frame.size() + kTransportOverhead.

inline std::expected<std::span<std::byte>, refusal_t> open(const transport_header_t &h, std::span<std::byte> datagram)¶

Check the tag of datagram and decrypt it in place.

Parameters:

h – The header parse_transport_header read from datagram.

Return values:

AUTH – The tag did not verify (the bytes are then unspecified).

Returns:

The plaintext, inside datagram at offset kTransportHeaderBytes; empty for a confirmation or an acknowledgement.

inline std::expected<transport_header_t, refusal_t> tr::net::noise::parse_transport_header(std::span<const std::byte> datagram) noexcept¶

Read a transport datagram’s type and nonce, with no decryption: the steps that come before the replay pre-check (RFC-0033 §5.5 steps 1 and 2).

Return values:
  • MALFORMED – Fewer than 25 bytes, or a type byte other than 0x04 / 0x05.

  • NONCE_BOUND – The nonce is at or above 2^62.

enum class tr::net::noise::refusal_t : std::uint8_t¶

Why a step refused, mapped by the link onto RFC-0033 §5.9’s counters.

MALFORMED and NONCE_BOUND are malformed_rx. AUTH, PAYLOAD and LOW_ORDER on a handshake message are noise_handshake_failed; AUTH on a transport message is noise_decrypt_failed. STATE is a caller error, and BACKEND a library failure.

Values:

enumerator MALFORMED¶

Wrong size or reserved type byte; nothing was decrypted.

enumerator AUTH¶

The tag did not verify.

enumerator PAYLOAD¶

An authentic handshake payload with reserved flag bits set.

enumerator LOW_ORDER¶

The peer’s ephemeral key gave an all-zero X25519 result.

enumerator NONCE_BOUND¶

A transport nonce at or above 2^62.

enumerator STATE¶

The step was called out of order for this role.

enumerator BACKEND¶

The crypto library failed.

enum class tr::net::noise::msg_type_t : std::uint8_t¶

The datagram type byte (RFC-0033 §5.3); every other value is reserved.

Values:

enumerator FIRST¶

Handshake, first message (-> psk, e).

enumerator SECOND¶

Handshake, second message (<- e, ee).

enumerator TRANSPORT_PHASE_0¶

Transport message, key phase 0.

enumerator TRANSPORT_PHASE_1¶

Transport message, key phase 1.

constexpr std::size_t tr::net::noise::kFirstMessageBytes = 1 + kDhLen + 9 + kTagLen¶

A first handshake message’s datagram: type, e.pub, payload (9) and tag.

constexpr std::size_t tr::net::noise::kSecondMessageBytes = 1 + kDhLen + 1 + kTagLen¶

A second handshake message’s datagram: type, e.pub, payload (1) and tag.

constexpr std::size_t tr::net::noise::kTransportHeaderBytes = 1 + 8¶

A transport datagram’s clear header: the type byte and the u64 nonce.

constexpr std::size_t tr::net::noise::kTransportOverhead = kTransportHeaderBytes + kTagLen¶

What a transport datagram adds to its frame: the header and the tag.

constexpr std::size_t tr::net::noise::kMaxFrameBytes = 65535 - kTagLen¶

The largest frame one Noise message carries: 65535 minus the tag (§5.3).

constexpr std::uint64_t tr::net::noise::kNonceLimit = std::uint64_t{1} << 62¶

No transport nonce at or above this is sent or decrypted (§5.5).

struct first_payload_t¶

The first message’s 9-byte payload (RFC-0033 §5.2).

Public Members

std::uint8_t flags = 0¶

Bit 0 fresh; bits 7-1 zero.

std::uint64_t counter = 0¶

The initiator counter, u64 little-endian on the wire.

struct transport_header_t¶

A transport datagram’s clear header, read before any decryption (§5.5).

Public Members

std::uint8_t phase = 0¶

The key phase, 0 or 1 (from the type byte).

std::uint64_t nonce = 0¶

The nonce, below kNonceLimit.

struct openssl_crypto_t¶

libcrypto (OpenSSL 3): EVP_CIPHER_CTX per cipher, EVP_PKEY for X25519.

Public Static Functions

static inline bool init()¶

Nothing to set up in OpenSSL 3.

static inline bool hash(std::span<const std::byte> in, key32_t &out)¶

SHA-256 of in, on a stack SHA256_CTX (no allocation).

static inline bool hkdf(const key32_t &ck, std::span<const std::byte> ikm, std::span<std::byte> out)¶

Noise HKDF over hash, through the shared stack HMAC.

Public Static Attributes

static constexpr const char *kName = "openssl"¶

The backend’s name in reports.

struct aead_t¶

A ChaCha20-Poly1305 cipher: one EVP_CIPHER_CTX, allocated when it is built.

Public Functions

inline aead_t()¶

Allocate the context and set its cipher; a failure shows at set_key.

inline bool set_key(const key32_t &k)¶

Key the context in place, replacing any previous key (no allocation).

inline void clear()¶

Overwrite the key in the context with zeros (no allocation).

inline bool seal(std::uint64_t n, std::span<const std::byte> ad, std::span<const std::byte> pt, std::byte *out)¶

Seal pt under nonce n; out takes pt.size() + kTagLen bytes.

inline bool open(std::uint64_t n, std::span<const std::byte> ad, std::span<const std::byte> ct, std::byte *out)¶

Open ct under nonce n into out; false on a bad tag.

struct dh_key_t¶

An X25519 key pair held as an EVP_PKEY.

Public Functions

inline bool set(const key32_t &priv, key32_t &pub)¶

Import priv as the private key and export its public key.

inline bool agree(const key32_t &peer, key32_t &out) const¶

X25519 with peer; libcrypto refuses an all-zero result itself.

struct sodium_crypto_t¶

libsodium: stateless calls, keys held in the backend’s own objects.

Public Static Functions

static inline bool init()¶

sodium_init; idempotent, false only if the library cannot run here.

static inline bool hash(std::span<const std::byte> in, key32_t &out)¶

SHA-256 of in.

static inline bool hkdf(const key32_t &ck, std::span<const std::byte> ikm, std::span<std::byte> out)¶

Noise HKDF over hash, through the shared stack HMAC.

Public Static Attributes

static constexpr const char *kName = "sodium"¶

The backend’s name in reports.

struct aead_t¶

A ChaCha20-Poly1305 (IETF) cipher: its key.

Public Functions

inline bool set_key(const key32_t &k)¶

Store the key.

inline void clear()¶

Wipe the key.

inline bool seal(std::uint64_t n, std::span<const std::byte> ad, std::span<const std::byte> pt, std::byte *out)¶

Seal pt under nonce n; out takes pt.size() + kTagLen bytes.

inline bool open(std::uint64_t n, std::span<const std::byte> ad, std::span<const std::byte> ct, std::byte *out)¶

Open ct under nonce n into out; false on a bad tag.

struct dh_key_t¶

An X25519 key pair: the private scalar.

Public Functions

inline bool set(const key32_t &priv, key32_t &pub)¶

Take priv as the private key and compute its public key.

inline bool agree(const key32_t &peer, key32_t &out) const¶

X25519 with peer; libsodium refuses an all-zero result itself.

struct psa_crypto_t¶

mbedTLS through the PSA Crypto API: keys in volatile key slots.

Public Static Functions

static inline bool init()¶

psa_crypto_init; idempotent.

static inline bool hash(std::span<const std::byte> in, key32_t &out)¶

SHA-256 of in.

static inline bool hkdf(const key32_t &ck, std::span<const std::byte> ikm, std::span<std::byte> out)¶

Noise HKDF as PSA_ALG_HKDF(PSA_ALG_SHA_256) with salt = ck, empty info.

Public Static Attributes

static constexpr const char *kName = "psa"¶

The backend’s name in reports.

struct aead_t¶

A ChaCha20-Poly1305 cipher: its key, imported once into a volatile slot.

Public Functions

inline bool set_key(const key32_t &k)¶

Import k, replacing any previous key.

inline void clear()¶

Destroy the key, which wipes it and frees its slot.

inline bool seal(std::uint64_t n, std::span<const std::byte> ad, std::span<const std::byte> pt, std::byte *out)¶

Seal pt under nonce n; out takes pt.size() + kTagLen bytes.

inline bool open(std::uint64_t n, std::span<const std::byte> ad, std::span<const std::byte> ct, std::byte *out)¶

Open ct under nonce n into out; false on a bad tag.

struct dh_key_t¶

An X25519 key pair in a volatile key slot.

Public Functions

inline bool set(const key32_t &priv, key32_t &pub)¶

Import priv as a Montgomery key pair and export its public key.

inline bool agree(const key32_t &peer, key32_t &out) const¶

X25519 with peer through psa_raw_key_agreement.

Next
fwd-router — FWD source routing and the /net plane (L4)
Previous
security & ACL — access control on the graph
Copyright © 2026, avatarsd LLC
Made with Sphinx and @pradyunsg's Furo
On this page
  • security & Noise — the Noise link’s crypto backend
    • What it does
    • Choosing a backend
    • What it costs
    • Conformance
    • API reference
      • crypto_backend
      • tr::net::noise::symmetric_state_t
        • symmetric_state_t()
        • operator=()
        • initialize()
        • mix_hash()
        • mix_key()
        • mix_key_and_hash()
        • encrypt_and_hash()
        • decrypt_and_hash()
        • split()
        • chaining_key()
        • handshake_hash()
        • cipher_key()
        • has_key()
        • kMaxMixHash
      • psk_state()
      • tr::net::noise::handshake_t
        • handshake_t()
        • write_first()
        • read_first()
        • write_second()
        • read_second()
        • split()
        • symmetric_state()
      • tr::net::noise::transport_cipher_t
        • set_keys()
        • seal()
        • open()
      • parse_transport_header()
      • refusal_t
        • MALFORMED
        • AUTH
        • PAYLOAD
        • LOW_ORDER
        • NONCE_BOUND
        • STATE
        • BACKEND
      • msg_type_t
        • FIRST
        • SECOND
        • TRANSPORT_PHASE_0
        • TRANSPORT_PHASE_1
      • kFirstMessageBytes
      • kSecondMessageBytes
      • kTransportHeaderBytes
      • kTransportOverhead
      • kMaxFrameBytes
      • kNonceLimit
      • tr::net::noise::first_payload_t
        • flags
        • counter
      • tr::net::noise::transport_header_t
        • phase
        • nonce
      • tr::net::noise::openssl_crypto_t
        • init()
        • hash()
        • hkdf()
        • kName
        • tr::net::noise::openssl_crypto_t::aead_t
          • aead_t()
          • set_key()
          • clear()
          • seal()
          • open()
        • tr::net::noise::openssl_crypto_t::dh_key_t
          • set()
          • agree()
      • tr::net::noise::sodium_crypto_t
        • init()
        • hash()
        • hkdf()
        • kName
        • tr::net::noise::sodium_crypto_t::aead_t
          • set_key()
          • clear()
          • seal()
          • open()
        • tr::net::noise::sodium_crypto_t::dh_key_t
          • set()
          • agree()
      • tr::net::noise::psa_crypto_t
        • init()
        • hash()
        • hkdf()
        • kName
        • tr::net::noise::psa_crypto_t::aead_t
          • set_key()
          • clear()
          • seal()
          • open()
        • tr::net::noise::psa_crypto_t::dh_key_t
          • set()
          • agree()