RFC 0001: The Periplus Protocol

  • Status: Draft (v1)
  • Author: Matthew J. Harmon (mjh@itys.net)
  • Date: 2026-05-29
  • Reference implementation: the plexus Rust stack (prpd / periplus).
  • Supersedes: nothing. Derived from: the Reticulum Network Stack (see Appendix A).

Abstract

Periplus (abbreviated PRP, pronounced "perp") is a cryptographic mesh-networking protocol for media that make no promises: LoRa, packet radio, a serial line, a TCP socket — anything that moves bytes. Every destination is a public key; every address is the hash of a key, so to claim an address you must hold the key that hashes to it. Periplus is medium-agnostic, connectionless at its base, and self-certifying at its core. It is derived from, but not wire-compatible with, Reticulum: the identity and key derivations are domain-separated under a Periplus namespace, so a Periplus node and a Reticulum node compute different addresses and cannot read each other's traffic.

This document specifies the Periplus wire protocol and the Plexus extension suite built on it, in enough detail to implement an interoperable node.

1. Introduction

1.1 Relationship to Reticulum

Periplus began as a clean-room reimplementation of the Reticulum Network Stack (RNS) and was, for a time, byte-compatible with it. It has since forked into its own protocol. The fork is realized by domain separation at two cryptographic chokepoints (§2.2): the identity hash and the symmetric key derivation. The byte layout of most structures is inherited from the RNS design — Periplus owes Reticulum the whole shape — but the cryptographic values diverge, so the two networks share a design and are never mistaken for each other. Periplus is not affiliated with the upstream Reticulum project.

1.2 Scope & layering

This RFC specifies two layers:

  • The Periplus core protocol (§2–§11): identities, addressing, the packet format, authenticated encryption, announces, single-destination delivery, links, transport, resources, and interface access control. An implementation of this layer is a Periplus node.
  • The Plexus extension suite (§12–§15): the fd73:: IPv6 plane, enrollment/CA, naming/DNS, and the negotiated extensions (PQ links, hardware attestation, the Iroh control plane). These ride above the core through reserved context bytes and the announce app_data field; a node MAY implement any subset. The seam between the core and the extensions is normative: core conformance MUST NOT depend on any extension.

The names reflect the layering: Periplus is the protocol; Plexus is the Rust stack that implements it and adds the extension suite. Core domains are named periplus.*; Plexus-extension domains are named plexus.* (§2.2).

1.3 Requirements language

The key words MUST, MUST NOT, REQUIRED, SHALL, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL are to be interpreted as in RFC 2119.

1.4 Notation

denotes concatenation. trunc_n(x) is the first n bytes of x. Integers on the wire are big-endian unless stated. H(x) is SHA-256. Byte-string literals are written b"…". Field widths are in bytes.

2. Cryptographic primitives & domain separation

2.1 Algorithms

Purpose Algorithm
Key agreement X25519 (Curve25519 ECDH)
Signatures Ed25519 (verify_strict, no malleability)
Hash SHA-256 (identities, destinations, packet hashes)
Keyed/overlay hash BLAKE3 (the fd73:: plane, proof-of-work)
Symmetric AEAD the Token: AES-256-CBC + HMAC-SHA-256, encrypt-then-MAC (§5)
Key derivation HKDF-SHA-256 (RFC 5869)

All MAC and signature comparisons MUST be constant-time. Secret keys MUST be zeroized on drop. Implementations MUST use verify_strict (or equivalent) for Ed25519.

2.2 Domain-separation constants

These byte strings are the protocol's identity. The two periplus.* core domains are what diverge Periplus from Reticulum; changing any value defines a different, non-interoperable network.

Domain string Used in Layer
b"periplus.id.v1" identity-hash prefix (§3.2) core
b"periplus.kdf.v1" HKDF info for link & packet keys (§5, §8.2) core
b"plexus.plane.addr.v1" fd73:: plane address (§12.1) ext
b"plexus.plane.endorse.v1" CA endorsement signature (§13.2) ext
b"plexus.plane.pow.v1" enrollment proof-of-work (§13.3) ext
b"plexus.hw-attestation.v1" hardware identity attestation (§15.3) ext

Two additional wire-identity constants diverge from the pre-fork values: the AutoInterface discovery group id is b"periplus" (§11 note), and the well-known transport control destination app-name is prptransport (§9.1).

3. Identities & addressing

3.1 Identity

A Periplus identity is a pair of keypairs:

  • an X25519 keypair for encryption (key agreement), and
  • an Ed25519 keypair for signatures.

The private serialization is exactly 64 bytes: x25519_priv(32) ‖ ed25519_seed(32). The public identity is enc_pub(32) ‖ sig_pub(32), in that order — the order in which they are hashed and announced.

3.2 Identity hash

The identity hash is the node's fundamental address:

identity_hash = trunc_16( SHA256( "periplus.id.v1" ‖ enc_pub ‖ sig_pub ) )

Sixteen bytes — an IPv6-address width. The "periplus.id.v1" prefix is the fork's first divergence point: the same keypair hashes to one value under Reticulum (no prefix) and a different one under Periplus. To send from an address, a node must hold the keypair that hashes to it; source spoofing is the second-preimage problem on SHA-256, not a policy choice.

3.3 Destinations & address derivation

A destination names a service on an identity. Its address is:

name_hash   = trunc_10( SHA256( "app_name.aspect1.aspect2.…" ) )   // dotted, UTF-8
address     = trunc_16( SHA256( name_hash(10) ‖ identity_hash(16) ) )

This derivation is invariant across implementations and MUST be exact. Destination kinds:

  • Single — addressed to one identity; traffic is encrypted to that identity (§7) or carried over a link (§8).
  • Group — a shared-key destination: addressed exactly like a Single (SHA256(name_hash ‖ group_identity_hash)), but encrypted with a pre-shared 64-byte Token key rather than per-recipient ECDH.
  • Plain — unencrypted; used for control destinations (e.g. path requests, §9.1).
  • Link — the ephemeral per-link destination (§8); its address is the link id.

3.4 Name hashing

name_hash is truncated to 10 bytes (not 16); it is concatenated with the 16-byte identity hash before the final destination hash. Aspects are joined with ..

4. Packet format

4.1 Header

Every packet begins with a flags byte, a header-type-dependent address region, a hop counter, and a context byte.

byte 0 — flags:
  bit 7    bit 6     bit 5      bit 4        bits 3-2     bits 1-0
 [ifac]  [hdr_type][ctx_flag][transport_type][dest_type] [packet_type]

byte 1 — hops (u8)

H1 (hdr_type=0):  flags(1) hops(1) destination(16) context(1)            = 19 bytes
H2 (hdr_type=1):  flags(1) hops(1) transport_id(16) destination(16) context(1) = 35 bytes

transport_id is present only in H2 and names the next-hop transport node for routed delivery (§9). ifac (bit 7) marks interface-authenticated framing (§11); most paths leave it clear. ctx_flag reserves a context extension bit.

4.2 Type enumerations

packet_type   : Data=0  Announce=1  LinkRequest=2  Proof=3
dest_type     : Single=0  Group=1  Plain=2  Link=3
transport_type: Broadcast=0  Transport=1  Relay=2  Tunnel=3
header_type   : H1=0  H2=1

4.3 Context byte

The trailing context byte qualifies the packet body. Reserved values (high range is link/resource control):

Name Value Meaning
NONE 0x00 plain data / proof
REQUEST 0x09 link request payload (§8.4)
RESPONSE 0x0A link response payload (§8.4)
IP_DATAGRAM 0xF1 fd73:: plane fragment (§12.2)
EXT_LINK_UPGRADE 0xF0 PQ link upgrade (§15.2)
KEEPALIVE 0xFA link keepalive
LINKIDENTIFY 0xFB link identification (§8.3)
LINKCLOSE 0xFC link teardown
LINKPROOF 0xFD link data proof
LRRTT 0xFE link-request RTT reply (§8.1)
LRPROOF 0xFF link-request proof (§8.1)

RESOURCE*, CACHE_REQUEST, PATH_RESPONSE, COMMAND*, and CHANNEL occupy the low range; their values are fixed by the codec and listed in §17. 0xF1 and 0xF0 are allocated from the otherwise-unused 0x0F..0xF9 gap for Plexus extensions.

4.4 MTU

The base link MTU is 500 bytes. Payloads larger than the usable per-packet space are carried by the resource machinery (§10) or, for IP, the plane fragmentation shim (§12.2). Implementations MUST negotiate the effective MTU down to the narrowest interface on a path.

5. The Token (authenticated encryption)

All confidential payloads use the Token, a modified-Fernet AEAD:

Token(plaintext) = IV(16) ‖ AES-256-CBC( PKCS7(plaintext) ) ‖ HMAC-SHA-256( IV ‖ CT )
  • Encrypt-then-MAC. The HMAC covers IV ‖ ciphertext.
  • No version byte and no timestamp — deliberately, to avoid metadata leakage.
  • HMAC verification MUST be constant-time; decryption MUST verify the MAC before unpadding.
  • Overhead is 16 + 32 = 48 bytes plus PKCS7 padding.

The 64-byte key material is split: bytes 0..32 are the HMAC (signing) key, bytes 32..64 are the AES (encryption) key.

6. Announces

The announce is the protocol's nervous system: a signed, self-authenticating advertisement of a destination and its keys, flooded across the mesh.

6.1 Payload

[ enc_pub(32) ] [ sig_pub(32) ] [ name_hash(10) ] [ random_hash(10) ]
[ ratchet(32, optional) ] [ signature(64) ] [ app_data(variable) ]

6.2 Signature & verification

The Ed25519 signature covers:

destination_hash ‖ enc_pub ‖ sig_pub ‖ name_hash ‖ random_hash ‖ [ratchet] ‖ app_data

A receiver MUST: (1) recompute the destination hash from name_hash and the identity hash of (enc_pub, sig_pub) and confirm it matches the packet's destination; (2) verify the signature with sig_pub. Only then is the announce accepted, the keys learned, and a path recorded (§9.1). Because the signature covers app_data, any extension data (§15.1) is authenticated by the same act that authenticates the identity.

6.3 app_data and extension TLVs

app_data is opaque to the core. Extension-aware nodes append a TLV sequence after a two-byte sentinel (§15.1); core nodes treat the trailing bytes as opaque and ignore them. An announce carrying no extensions is byte-identical to one with none.

7. Single-destination (opportunistic) delivery

A node MAY send to a Single destination without a link, encrypting per-packet:

token_wire = ephemeral_x25519_pub(32) ‖ Token(plaintext)

The sender generates an ephemeral X25519 key, performs ECDH against the destination's encryption key (or its current ratchet, §8.5), derives a Token key (§8.2, salted with the recipient identity hash), and encrypts. The recipient tries its ratchet keys newest -first, then its static key.

Proofs. A destination MAY adopt a PROVE_ALL strategy: on successfully decrypting an inbound single packet, it returns a Proof/Single packet addressed to trunc_16(packet_hash) carrying an Ed25519 signature over the full 32-byte packet hash (implicit-proof form, 64-byte signature). The original sender verifies the signature against the destination identity and measures round-trip time. This is the prping ack-and-forth.

A link is an encrypted, bidirectional, forward-secret channel between two identities.

8.1 Handshake (three packets)

  1. LinkRequest (initiator → destination): ephemeral X25519 pubkey, signing pubkey, and a truncated link id.
  2. LRRTT (destination → initiator, context 0xFE): ephemeral X25519 pubkey, signing pubkey, a signed proof binding the link, and an RTT measurement.
  3. LRPROOF (initiator → destination, context 0xFF): acknowledgment.

The link id is a truncated SHA-256 over the public link material; both sides derive it identically.

8.2 Key derivation

After the handshake both ends hold the same DerivedKey:

DerivedKey = HKDF-SHA-256( salt = link_id(16), ikm = ecdh_shared, info = "periplus.kdf.v1", L = 64 )

The info = "periplus.kdf.v1" is the fork's second divergence point: identical ECDH secret and salt yield different keys under Periplus than under Reticulum, so link and single-destination ciphertext is mutually unreadable. For single-destination packets (§7) the salt is the recipient identity hash instead of a link id; everything else is identical. The 64-byte output feeds a Token (§5).

8.3 Identification

A link is anonymous until a party proves who it is. The LINKIDENTIFY packet (context 0xFB) carries enc_pub(32) ‖ sig_pub(32) ‖ Ed25519_sign(link_id ‖ pubkeys), encrypted over the link. The receiver reconstructs the identity, verifies the signature over link_id ‖ pubkeys, and thereafter attributes the link to that identity hash. This is the basis of admission control (§8.4) and of the plane's reverse-path filter (§12.3).

8.4 Requests & responses, and admission

Over an established link a node MAY issue a REQUEST (context 0x09) and receive a RESPONSE (0x0A); payloads are msgpack. The request id is the truncated hash of the REQUEST packet, derived identically by both ends.

Admission is authenticate-once: the handshake's ECDH + identify is the only asymmetric operation; thereafter the link is a settled symmetric channel and the peer's identity is a fact attached to it. A node enforces a policy — allow-all, allow-none, or an allow-list of identity hashes — by set membership against the link's proven identity (§8.3). Unauthorized requests are dropped silently, returning no signal that would confirm a guessed destination.

8.5 Ratchets & forward secrecy

Handshake key ephemerality gives per-link forward secrecy. A destination MAY advertise a rotating ratchet X25519 key in its announce (§6.1); senders use the current ratchet in place of the static encryption key, and the destination retains a window of recent ratchet privates to decrypt in-flight traffic. Ratchet rotation refreshes key material over a destination's lifetime without changing its address.

8.6 Lifecycle

States: Pending → Handshaking → Active → (Stale) → Closed. A link with no traffic for KEEPALIVE × STALE_FACTOR becomes stale; a half-open link MUST be reaped after a bounded handshake timeout. Implementations MUST bound the number of concurrent links.

9. Transport & routing

9.1 Path requests & the path table

Reachability is learned from announces, not configured. A node records, per destination: the next-hop, the interface, the hop distance, and the time learned. A node that has not heard a destination MAY send a path request to the well-known Plain control destination prptransport.path.request; a transport node that knows a path replies, and the announce that follows populates the requester's table.

9.2 Hops, transport nodes, relays

The header hops byte counts transport relays traversed (the raw wire count; a direct peer is 0 hops on the wire). A transport node (transport_type = Transport, header H2) rewrites transport_id, increments hops, and forwards toward the next hop, never back out the interface a packet arrived on. Relay tables MUST be bounded.

10. Resources

Payloads exceeding a single packet are transferred as a resource: an advertisement announces the transfer (total size, part count, hash, flags), followed by the parts.

  • Part count and transfer size MUST be bounded on receipt to prevent allocation abuse.
  • Compression: a resource MAY be bzip2-compressed when that is smaller; the FLAG_COMPRESSED advertisement bit signals it. The content hash is computed over the uncompressed payload.
  • Reliability is per-part with acknowledgement; segmentation supports transfers that exceed the windowed buffer.

11. Interface access control (IFAC)

An interface MAY be placed on a virtual network: a shared network_name + passphrase derive an IFAC identity, and every packet on that interface is masked and authenticated.

ifac_key      = HKDF-SHA-256( salt = IFAC_SALT, ikm = SHA256(SHA256(netname) ‖ SHA256(netkey)), L = 64 )
ifac_identity = Identity::from_bytes( ifac_key )
tag           = trunc_last_n( Ed25519_sign( ifac_identity, clean_packet ) )       // n = IFAC size
masked        = packet  XOR  HKDF-SHA-256( salt = ifac_key, ikm = tag )            // tag left in clear

The header IFAC flag (bit 7) is set. On receipt a node unmasks, authenticates the tag, and drops on failure — before any further parsing. A node MUST drop IFAC-flagged packets on a plain interface and plain packets on an IFAC interface. Default IFAC size is 16 bytes (8 on RNode/KISS/AX.25). Note: the default AutoInterface discovery group id is b"periplus" (diverged from the pre-fork value).

12. The fd73:: plane (IPv6 overlay) — Plexus extension

The plane carries ordinary IPv6 over Periplus links, entered through a TUN device, so unmodified applications (ping6, ssh) ride the mesh.

12.1 Plane address

plane_address = fd73:: ‖ trunc_14( BLAKE3( "plexus.plane.addr.v1" ‖ enc_pub ‖ sig_pub ) )

A 112-bit suffix under the fd73::/16 ULA prefix. The hash is over the keys, not a certificate, so the address is as durable as the identity — a cert may be reissued, endorsed, or revoked without the address moving. fd73::/16 is a deliberate, disjoint sibling of any structured fd53::/16-style mesh.

12.2 Fragmentation

IPv6 offers 1280-byte datagrams; a link's usable payload is smaller. The plane prepends a 6-byte shim under context 0xF1:

[ id: u32 ] [ index: u8 ] [ count: u8 ] ‖ fragment

Reassembly is best-effort: bounded table, ~3-second timeout, no retransmission. A dropped fragment is a dropped datagram, per the IP contract; reliability belongs to the layers above.

12.3 Reverse-path filtering

An inbound datagram is delivered only if its inner IPv6 source equals the plane_address derived from the keys that proved themselves (§8.3) on the link it arrived on. Source spoofing is a hash mismatch, not a firewall rule.

12.4 Neighbor discovery

There is no broadcast segment; the TUN comes up NOARP. Address↔key bindings are learned from the announce stream (§6) — hearing an announce is neighbor discovery, with resolution and authentication folded into one event.

13. Enrollment & endorsement — Plexus extension

A self-certifying address is sovereign; a network may still wish to recognize members.

13.1 Tiers

  1. Self-signed — a node mints its own plane cert (a body + an Ed25519 self-signature) and joins an open plane trust-on-first-use.
  2. Endorsed — a root signs the node's identity (§13.2), adding a name and an expiry.
  3. Open + proof-of-work — a public plane MAY require a key-bound PoW (§13.3) to enroll without being free to flood. A CA MAY also rate-limit.

13.2 Endorsement signature

endorsement = Ed25519_sign_root( "plexus.plane.endorse.v1" ‖ sig_pub ‖ not_after ‖ name )

The CA endorses without assigning: it never hands out an address (the address is the keys' already); it staples a name and an expiry onto an existing identity. Revocation is non-renewal. An endorsement MAY be held internally as provenance and need not be released on the wire.

13.3 Proof-of-work

PoW valid iff  BLAKE3( "plexus.plane.pow.v1" ‖ enc_pub ‖ sig_pub ‖ nonce )  has ≥ D leading zero bits

The work is bound to the keys, not the address, so a nonce cannot be reused for a different identity, and difficulty D MAY rise over time without any address moving.

13.4 The CA & the magic-DNS trigger

A node enrolls by asking the network a question: a DNS lookup of enroll.ca.fd73 (answered by the daemon on loopback, §14.3) mints the cert, grinds the PoW once, and registers with whatever CA the mesh advertises. No configuration or key ceremony.

14. Naming & resolution — Plexus extension

14.1 Two-tier naming

Naming is the honest answer to Zooko's triangle (human-meaningful, globally unique, decentralized — pick two):

  • Open petnames — self-asserted in the announce, learned trust-on-first-use, first-heard-wins. Decentralized and cheap; not unique. Conflicts are surfaced.
  • Endorsed names — signed into an endorsement by a CA (§13.2); unique and authenticated within that CA, at the cost of asking one.

14.2 host.fd73 resolution

A name resolves at <name>.host.fd73. The node runs its own UDP DNS server (no external resolver required):

  • AAAA for <name>.host.fd73 → the node's fd73:: plane address.
  • PTR in the fd73::/16 reverse zone (…3.7.d.f.ip6.arpa) → the name.
  • Queries outside those zones are forwarded upstream, so the daemon can be a host's only resolver.

ping6 <name>.host.fd73 and ssh <name>.host.fd73 therefore work unmodified.

14.3 Magic-DNS control plane

Control actions are loopback DNS lookups with TTL 0:

  • enroll.ca.fd73 — generate the cert + register with the CA (§13.4).
  • <name>.enroll.dns.fd73 — claim/rename to <name>.

The control plane is loopback-only; it MUST NOT be served to the network.

15. Extensions

15.1 Negotiation (the announce TLV mechanism)

Extensions ride in app_data after a two-byte sentinel; the signature (§6.2) authenticates them.

app_data := [ user_app_data ] ‖ EXT_SENTINEL ‖ ext_tlv*
EXT_SENTINEL := 0xEE 0xEE
ext_tlv      := type(u8) ‖ length(u16, big-endian) ‖ value[length]

A parser splits at the first sentinel and reads length-prefixed TLVs, stopping on truncation. With no TLVs, no sentinel is emitted. Type tags:

Tag Value Extension
PQ_LINK 0x01 post-quantum link offer (§15.2)
HW_ATTESTATION 0x02 hardware identity attestation (§15.3)
CAPABILITIES 0x03 capability bitfield
PLANE_NAME 0x04 self-asserted plane petname (§14.1)
IROH_NODEADDR 0x05 Iroh control/status reachability (§15.4)

0x06..0x7F are reserved for future standard extensions; 0x80..0xFF for vendor-private ones.

After the classical handshake (§8.1), extension-aware peers MAY upgrade a link with an ML-KEM-768 key exchange authenticated by ML-DSA-65, then rekey the link Token over classical_shared ‖ pq_shared (defense in depth: an attacker must break both X25519 and ML-KEM). Each node commits SHA256(ml_dsa_verifying_key) in its PQ_LINK TLV so peers bind to the PQ identity before upgrading. Two messages ride context 0xF0. The KEM is ephemeral per link.

15.3 Hardware-backed identity attestation

A hardware root (Ed25519) endorses a node by signing its signing key:

attestation = root_pub(32) ‖ Ed25519_sign_root( "plexus.hw-attestation.v1" ‖ node_sig_pub )

carried in the HW_ATTESTATION TLV. Because the signature covers the very key the announce proves, a valid attestation binds "a trusted root vouches for this identity" to the announce-proven identity and cannot be replayed onto another node's announce. A verifier checks it against a configured trust store of root public keys.

15.4 Iroh control + status plane

An OPTIONAL out-of-band channel for node status and control, beside the data plane, over Iroh QUIC keyed by the node's Ed25519 signing key (so a node's Iroh NodeId equals its Periplus signing key). A node advertises its NodeAddr (NodeId + direct socket addresses) in the IROH_NODEADDR TLV; peers dial it directly. Two ALPNs: a read-only status pull and an admin-gated control command, the latter reusing the same actions as the magic-DNS control plane (§14.3). This channel carries no data — the fd73:: plane over Periplus remains the data path.

16. Security considerations

  • Self-certifying addressing makes source spoofing ill-typed (§3.2, §12.3), but does not by itself authorize: admission is a separate set-membership check on a link-proven identity (§8.4).
  • Untrusted input is parsed before trust is decided. Implementations MUST: bound IFAC packet sizes before key derivation; bound resource part counts and transfer sizes; reap half-open links and cap concurrent links; bound relay and recall tables; write key files with restrictive permissions; and zeroize plaintext scratch buffers.
  • Constant-time MAC and signature checks are mandatory (§2.1, §5).
  • No metadata in the Token (no version, no timestamp) by design (§5).
  • The fork is a security boundary by construction: the domain-separated derivations (§2.2) mean cross-protocol traffic neither addresses nor decrypts, so a Periplus deployment is isolated from Reticulum without any allow/deny configuration.
  • Forward secrecy is per-link via ephemeral handshakes and ratchets (§8.5); single-destination opportunistic traffic has weaker secrecy and SHOULD prefer links for sustained exchange.

17. Constant & domain-separation registry

Domain strings: see §2.2. Sentinel / TLV tags: EXT_SENTINEL = 0xEEEE; tags 0x01..0x05 (§15.1). Header widths: H1 = 19 bytes, H2 = 35 bytes; AddressHash = 16 bytes; name_hash = 10 bytes; base MTU = 500. Type enums: §4.2. Context bytes: §4.3 (high range), plus the low-range RESOURCE_ADV, RESOURCE_REQ, RESOURCE_HMU, RESOURCE_PRF, RESOURCE_ICL, RESOURCE_RCL, CACHE_REQUEST, PATH_RESPONSE, COMMAND, COMMAND_STATUS, CHANNEL — authoritative values are pinned by the reference codec and its conformance vectors. Well-known destinations: prptransport.path.request (Plain), prptransport.remote.management (§9.1). Prefixes/ports: plane fd73::/16; AutoInterface discovery group b"periplus", default discovery UDP port 29716; plane DNS on loopback.

A future revision of this RFC SHOULD inline the full context-byte table from the reference codec; until then the codec and its frozen conformance vectors are authoritative for any value not given numerically here.

18. References

  • Normative: RFC 2119 (requirements language); RFC 5869 (HKDF); RFC 7748 (X25519); RFC 8032 (Ed25519); FIPS 197 / SP 800-38A (AES-CBC); FIPS 198 (HMAC); FIPS 203 (ML-KEM); FIPS 204 (ML-DSA); BLAKE3.
  • Informative — Plexus design records: ADR-0001 (gateway/extension seam), ADR-0002 (foreign-protocol adapters), ADR-0003 (resource segmentation), ADR-0004 (the fd73:: plane), ADR-0005 (enrollment), ADR-0006 (naming), ADR-0007 (Iroh control plane), ADR-0008 (the fork from Reticulum). ARCHITECTURE.md (the implementation baseline).
  • Lineage: the Reticulum Network Stack (the design Periplus derives from); cjdns and Yggdrasil (public-key-into-fc00::/8 self-certifying addressing).

Appendix A: divergence from Reticulum

Periplus is derived from Reticulum and inherits the shape of its structures: the packet header, the announce, the Token construction, the destination/identity derivations, links, resources, and IFAC are all recognizably Reticulum's design. Periplus is not wire-compatible. The divergence is deliberate and minimal — two cryptographic domain separations (§2.2):

  1. the identity hash gains the "periplus.id.v1" prefix (§3.2), so identities and all derived addresses differ; and
  2. symmetric key derivation gains the "periplus.kdf.v1" HKDF info (§8.2), so link and packet ciphertext is mutually unreadable.

Two further wire-identity values diverge (the AutoInterface group id b"periplus" and the prptransport control app-name). A consequence of (1) is that a Periplus node cannot interoperate with, route to, or be addressed by a Reticulum node — the fork's deliberate cost is leaving the existing Reticulum network. Attribution to the Reticulum project and the Reticulum-rs lineage is retained in the reference implementation's NOTICE.