From reticulum-protocol
Reticulum's link system — encrypted channels with forward secrecy. Use when working with link establishment, link requests, ECDH, ephemeral keys, forward secrecy, channels, keep-alive, or link timeouts.
How this skill is triggered — by the user, by Claude, or both
Slash command
/reticulum-protocol:linksThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill provides knowledge about Reticulum's link system - encrypted channels with forward secrecy. Invoke when the user mentions link establishment, link request, ECDH, ephemeral keys, forward secrecy, keep-alive, or link timeouts.
This skill provides knowledge about Reticulum's link system - encrypted channels with forward secrecy. Invoke when the user mentions link establishment, link request, ECDH, ephemeral keys, forward secrecy, keep-alive, or link timeouts.
Links are abstract encrypted channels between two nodes providing:
Unlike traditional connections, links are not tied to specific interfaces or IP addresses. They're abstract channels identified by link hash.
PENDING = 0x00 # Link request sent, awaiting response
HANDSHAKE = 0x01 # Link proof received, deriving keys
ACTIVE = 0x02 # Link established and operational
STALE = 0x03 # No traffic, final keep-alive sent
CLOSED = 0x04 # Link terminated
PENDING → HANDSHAKE → ACTIVE → STALE → CLOSED
↓ ↓
└──────── CLOSED ──────┘
PENDING: Initial state after sending link request. Waiting for link proof from destination.
HANDSHAKE: Link proof received and validated. Performing ECDH key exchange to derive shared secret.
ACTIVE: Link fully established. Data can be exchanged with encryption and forward secrecy.
STALE: No traffic received for STALE_TIME (720s). Final keep-alive sent. Link will timeout if no response.
CLOSED: Link terminated. Reasons: timeout, explicit close, or error.
ECPUBSIZE = 64 # Public key size (32B encryption + 32B signing)
KEYSIZE = 32 # Symmetric key size (AES-256)
ESTABLISHMENT_TIMEOUT_PER_HOP = 6 # Seconds per hop for establishment
KEEPALIVE = 360 # Keep-alive interval (seconds)
STALE_TIME = 720 # Time before link considered stale (2×KEEPALIVE)
STALE_GRACE = 5 # Grace period before timeout (seconds)
KEEPALIVE_TIMEOUT_FACTOR = 4 # Multiplier for RTT-based timeout
ECPUBSIZE (64 bytes): Combined size of X25519 encryption public key (32B) + Ed25519 signing public key (32B). Sent together in link request.
ESTABLISHMENT_TIMEOUT_PER_HOP (6 seconds): Timeout per hop. For 10-hop path: 10 × 6 = 60 seconds total timeout.
KEEPALIVE (360 seconds): Interval for sending keep-alive packets when link is idle. Ensures both ends know link is alive.
STALE_TIME (720 seconds): After no traffic for this duration, link enters STALE state and sends final keep-alive.
STALE_GRACE (5 seconds): Additional grace period after final keep-alive before declaring timeout.
Link establishment uses exactly 3 packets totaling 297 bytes:
Sent by initiator to destination:
[HEADER 19B][LKi 64B]
Total: 83 bytes
LKi = encryption_public_key (32B) + signing_public_key (32B)
Header (19 bytes):
LKi (64 bytes): Initiator's ephemeral public keys
With optional MTU signalling (+3 bytes): 86 bytes total
Sent by destination back to initiator:
[HEADER 19B][SIGNATURE 64B][LKr 32B]
Total: 115 bytes
SIGNATURE = Ed25519 signature (64B)
LKr = responder encryption public key (32B)
Header (19 bytes):
SIGNATURE (64 bytes): Ed25519 signature of link_id + LKr + responder_sig_pub + signalling_bytes using destination's persistent signing key. The signing public key is not transmitted — the initiator already knows it from the destination's identity.
LKr (32 bytes): Destination's ephemeral X25519 encryption public key
With optional MTU signalling (+3 bytes appended): 118 bytes total
Sent by initiator to measure round-trip time:
[HEADER 19B][ENCRYPTED_DATA 80B]
Total: 99 bytes
Header (19 bytes):
ENCRYPTED_DATA (80 bytes): Encrypted timestamp for RTT calculation
Links use Elliptic Curve Diffie-Hellman on Curve25519:
Li_privLi_pub = X25519(Li_priv, G) where G is generatorLi_pub in link request as part of LKiWhen link proof received:
4. Extract Lr_pub from link proof
5. Perform ECDH: shared_secret = X25519(Li_priv, Lr_pub)
6. Derive link keys via HKDF from shared_secret
Li_pubLr_privLr_pub = X25519(Lr_priv, G)shared_secret = X25519(Lr_priv, Li_pub)Lr_pub in link proofBoth sides now share the same shared_secret without ever transmitting it.
Link ID is SHA-256 hash of entire link request packet:
link_id = SHA256(link_request_packet)[:16] # Truncated to 16 bytes
This hash serves as:
Currently only AES-256-CBC is enabled:
MODE_AES256_CBC = 0x01
ENABLED_MODES = [MODE_AES256_CBC]
Reserved modes for future use:
Links send keep-alive packets when idle to detect timeouts.
# Send keep-alive if no traffic for KEEPALIVE seconds
if time_since_last_traffic >= KEEPALIVE:
send_keepalive()
Keep-alive packet:
keepalive_size = 20 bytes
keepalive_interval = 360 seconds
bits_per_second = (20 * 8) / 360 = 0.44 bits/second
Total cost: ~0.44 bits/second per active link
timeout = (RTT * KEEPALIVE_TIMEOUT_FACTOR) + STALE_GRACE
# Example: RTT = 2 seconds
timeout = (2 * 4) + 5 = 13 seconds
After entering STALE state (no traffic for 720s):
(RTT × 4) + 5 secondsLinks provide forward secrecy through:
If link keys compromised:
Links negotiate MTU during establishment (optional):
# 3-byte MTU signalling
mtu = 512 # Example MTU
mode = 0x01 # MODE_AES256_CBC
# Encode: 21 bits MTU + 3 bits mode
signalling_value = (mtu & 0x1FFFFF) + (((mode << 5) & 0xE0) << 16)
mtu_bytes = struct.pack(">I", signalling_value)[1:] # Last 3 bytes
Appended to link request (LKi + mtu_bytes) and link proof (LKr + signature + mtu_bytes).
Maximum data unit for link packets:
# Calculation from Link.py
MDU = floor((MTU - IFAC_MIN_SIZE - HEADER_MINSIZE - TOKEN_OVERHEAD) / AES_BLOCKSIZE) * AES_BLOCKSIZE - 1
# With MTU=500:
MDU = floor((500 - 1 - 19 - 48) / 16) * 16 - 1
MDU = floor(432 / 16) * 16 - 1
MDU = 27 * 16 - 1
MDU = 432 - 1 = 431 bytes
This is the maximum application data per link packet after encryption overhead.
Time Initiator Network Destination
---- --------- ------- -----------
t=0 Send LinkReq ─────────────────────────────→ Receive
State: PENDING Validate
Generate keys
ECDH
t=RTT/2 ←─────────────────── Send LinkProof
Receive State: ACTIVE
Validate signature
ECDH
Send RTT packet ───────────────────────────→ Receive
State: ACTIVE Measure RTT
t=RTT Link fully established with measured RTT
Total establishment time: ~1 RTT for 3-packet handshake
Link initiator reveals no identifying information:
Transport nodes store link entries:
link_table = {
link_id: hops
}
Entries timeout if not proven within establishment timeout period.
Links provide reliable delivery via:
For detailed protocol specifications:
references/link-establishment.md - Complete 7-step handshake processreferences/key-exchange.md - ECDH shared secret derivation and HKDFreferences/keepalive.md - Watchdog logic and timeout detectionreferences/wire-examples.py - Byte-level link request packet constructionRelated skills:
cryptography-identity - X25519, Ed25519, HKDF detailspackets-wire-format - Packet structure and contextstransport-routing - Link table and multi-hop routingresources - Large data transfer over linksSource References:
https://github.com/markqvist/Reticulum/RNS/Link.py (lines 65-200)https://github.com/markqvist/Reticulum/docs/source/understanding.rst (lines 518-577)npx claudepluginhub torlando-tech/reticulum-skills --plugin reticulum-protocolGrades QUIC network paths into GF(3) tiers (+1 excellent, 0 standard, -1 degraded) via BBRv3 analysis of RTT, bandwidth, loss, jitter, and pacing. For Iroh P2P routing.
Technical reference on Lightning Network channel factories, multi-party channels, LSP architectures, and Bitcoin Layer 2 scaling without soft forks.
Implements simplified Signal Double Ratchet for end-to-end messaging encryption using X25519 key exchange, HKDF derivation, and AES-256-GCM. Supports forward secrecy and out-of-order messages.