From reticulum-protocol
Reticulum's cryptographic primitives, identity structure, encryption tokens, and key derivation. Use when working with Ed25519, X25519, identity hashes, HKDF, token format, encryption, signatures, ratchets, or key derivation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/reticulum-protocol:cryptography-identityThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill provides knowledge about Reticulum's cryptographic primitives, identity structure, encryption tokens, and key derivation. Invoke when the user mentions cryptographic primitives, Ed25519, X25519, identity, HKDF, token format, encryption, signatures, or key derivation.
This skill provides knowledge about Reticulum's cryptographic primitives, identity structure, encryption tokens, and key derivation. Invoke when the user mentions cryptographic primitives, Ed25519, X25519, identity, HKDF, token format, encryption, signatures, or key derivation.
The primitives listed here are authoritative. Anything claiming to be Reticulum, but not using these exact primitives is not Reticulum, and possibly an intentionally compromised or weakened clone.
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic primitives, with widely available implementations that can be used both on general-purpose CPUs and on microcontrollers.
One of the primary considerations for choosing this particular set of primitives is that they can be implemented safely with relatively few pitfalls, on practically all current computing platforms.
Signatures:
ECDH Key Exchanges:
Key Derivation:
Encryption:
os.urandom() or betterMessage Authentication:
Hashing:
Encrypted tokens are based on the Fernet spec with modifications:
os.urandom() or betterToken Structure:
[IV 16B][CIPHERTEXT variable][HMAC 32B]
Total overhead: 48 bytes (TOKEN_OVERHEAD)
Full Encryption Token (as sent in packets):
[EPHEMERAL_PUBLIC_KEY 32B][IV 16B][CIPHERTEXT variable][HMAC 32B]
Total overhead: 80 bytes (32B ephemeral + 48B token)
In the default installation configuration, the X25519, Ed25519 and AES-256-CBC primitives are provided by OpenSSL (via the PyCA/cryptography package). The hashing functions SHA-256 and SHA-512 are provided by the standard Python hashlib. The HKDF, HMAC, Token primitives, and the PKCS7 padding function are always provided by the following internal implementations:
RNS/Cryptography/HKDF.pyRNS/Cryptography/HMAC.pyRNS/Cryptography/Token.pyRNS/Cryptography/PKCS7.pyReticulum identities are the foundation of all encrypted communication. An identity is a 512-bit keypair consisting of two 256-bit keys.
Private Keys (64 bytes total):
Public Keys (64 bytes total):
Identity Hash (16 bytes):
SHA256(pub_bytes)[:16]# Encryption keypair (X25519)
prv = X25519PrivateKey.generate()
prv_bytes = prv.private_bytes() # 32 bytes
pub = prv.public_key()
pub_bytes = pub.public_bytes() # 32 bytes
# Signing keypair (Ed25519)
sig_prv = Ed25519PrivateKey.generate()
sig_prv_bytes = sig_prv.private_bytes() # 32 bytes
sig_pub = sig_prv.public_key()
sig_pub_bytes = sig_pub.public_bytes() # 32 bytes
# Combined keys
private_key = prv_bytes + sig_prv_bytes # 64 bytes
public_key = pub_bytes + sig_pub_bytes # 64 bytes
# Identity hash
identity_hash = SHA256(public_key)[:16] # 16 bytes
KEYSIZE: 512 bits (64 bytes)
HASHLENGTH: 256 bits
TRUNCATED_HASHLENGTH: 128 bits (16 bytes)
SIGLENGTH: 512 bits (64 bytes)
Reticulum uses ECDH combined with HKDF and AES-256-CBC for encryption.
Generate ephemeral keypair:
ephemeral_key = X25519PrivateKey.generate()
ephemeral_pub = ephemeral_key.public_key()
ephemeral_pub_bytes = ephemeral_pub.public_bytes() # 32 bytes
Perform ECDH key exchange:
# Use recipient's public key (or ratchet if available)
target_public_key = recipient.pub # X25519PublicKey
shared_key = ephemeral_key.exchange(target_public_key) # 32 bytes
Derive encryption keys with HKDF:
derived_key = HKDF(
length=64, # 64 bytes (512 bits)
derive_from=shared_key, # ECDH shared secret
salt=recipient.hash, # Identity hash as salt
context=None # Optional context
)
signing_key = derived_key[:32] # HMAC key
encryption_key = derived_key[32:] # AES key
Create Token and encrypt:
token = Token(derived_key) # Creates token with both keys
# Generate random IV
iv = os.urandom(16)
# Pad plaintext with PKCS7
padded_plaintext = PKCS7.pad(plaintext)
# Encrypt with AES-256-CBC
ciphertext = AES_256_CBC.encrypt(
plaintext=padded_plaintext,
key=encryption_key,
iv=iv
)
# Calculate HMAC over IV + ciphertext
signed_parts = iv + ciphertext
hmac_tag = HMAC.new(signing_key, signed_parts).digest() # 32 bytes
# Assemble token
token_bytes = iv + ciphertext + hmac_tag
Prepend ephemeral public key:
final_token = ephemeral_pub_bytes + token_bytes
# [32B ephemeral pub][16B IV][variable ciphertext][32B HMAC]
Extract ephemeral public key:
peer_pub_bytes = ciphertext_token[:32]
peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
token_data = ciphertext_token[32:]
Perform ECDH with private key:
# Try with ratchets first (if available)
# Otherwise use identity private key
shared_key = recipient.prv.exchange(peer_pub) # 32 bytes
Derive keys with HKDF (same as encryption):
derived_key = HKDF(
length=64,
derive_from=shared_key,
salt=recipient.hash,
context=None
)
Verify HMAC and decrypt:
token = Token(derived_key)
# Extract components
iv = token_data[:16]
ciphertext = token_data[16:-32]
received_hmac = token_data[-32:]
# Verify HMAC
expected_hmac = HMAC.new(signing_key, iv + ciphertext).digest()
if received_hmac != expected_hmac:
raise ValueError("Token HMAC was invalid")
# Decrypt
padded_plaintext = AES_256_CBC.decrypt(
ciphertext=ciphertext,
key=encryption_key,
iv=iv
)
# Unpad
plaintext = PKCS7.unpad(padded_plaintext)
HKDF (HMAC-based Key Derivation Function) is used to derive strong encryption keys from ECDH shared secrets.
Function signature:
def hkdf(length=None, derive_from=None, salt=None, context=None)
Parameters:
length: Output key length in bytes (typically 64 for AES-256)derive_from: Input key material (ECDH shared secret)salt: Salt value (typically identity hash, 16 bytes)context: Optional context information (typically None or empty bytes)Extract phase (create pseudorandom key):
pseudorandom_key = HMAC_SHA256(salt, derive_from)
Expand phase (generate output key material):
block = b""
derived = b""
for i in range(ceil(length / 32)): # 32 = SHA-256 output length
block = HMAC_SHA256(pseudorandom_key, block + context + bytes([i + 1]))
derived += block
return derived[:length]
For Identity encryption:
length=64 (512 bits)derive_from=shared_key (from ECDH)salt=identity_hash (16 bytes)context=NoneFor Link encryption:
length=64 (512 bits) for AES-256-CBC mode, or 32 (256 bits) for AES-128-CBCderive_from=shared_key (from ECDH)salt=link_id (the 16-byte link identifier)context=NoneKey splitting:
derived_key = HKDF(length=64, ...)
signing_key = derived_key[:32] # First 32 bytes for HMAC
encryption_key = derived_key[32:] # Last 32 bytes for AES-256
Ratchets provide forward secrecy for SINGLE destinations by periodically rotating encryption keys.
RATCHET_INTERVAL: 1800 seconds (30 minutes)
RATCHET_COUNT: 512
RATCHET_EXPIRY: 2592000 seconds (30 days)
RATCHETSIZE: 256 bits (32 bytes)
A ratchet is simply an X25519 private key:
ratchet_prv = X25519PrivateKey.generate()
ratchet_prv_bytes = ratchet_prv.private_bytes() # 32 bytes
ratchet_pub = ratchet_prv.public_key()
ratchet_pub_bytes = ratchet_pub.public_bytes() # 32 bytes
Ratchet ID (10 bytes):
ratchet_id = SHA256(ratchet_pub_bytes)[:10]
When encrypting to a destination with a known ratchet:
# Use ratchet instead of identity public key
ratchet_pub = X25519PublicKey.from_public_bytes(ratchet_bytes)
shared_key = ephemeral_key.exchange(ratchet_pub)
# Rest of encryption is identical
derived_key = HKDF(
length=64,
derive_from=shared_key,
salt=identity_hash, # Still use identity hash as salt
context=None
)
Destination tries ratchets before falling back to identity key:
# Extract ephemeral public key from token
peer_pub = X25519PublicKey.from_public_bytes(token[:32])
# Try each retained ratchet
for ratchet_prv_bytes in retained_ratchets:
ratchet_prv = X25519PrivateKey.from_private_bytes(ratchet_prv_bytes)
shared_key = ratchet_prv.exchange(peer_pub)
try:
plaintext = decrypt_with_key(shared_key, token[32:])
return plaintext # Success
except:
continue # Try next ratchet
# If all ratchets fail, try identity private key
shared_key = identity_prv.exchange(peer_pub)
plaintext = decrypt_with_key(shared_key, token[32:])
Forward secrecy: Compromise of identity key doesn't reveal past messages encrypted with expired ratchets
Backward secrecy: Old ratchets eventually discarded, limiting exposure window
Transparent: Applications don't need to know about ratchets, handled by Identity layer
Optional: Destinations can choose whether to enable ratchets
When ratchet is enabled, announce packet includes:
[PUBLIC_KEY 64B][NAME_HASH 10B][RANDOM_HASH 10B][RATCHET_PUB 32B][SIGNATURE 64B][APP_DATA variable]
The signature covers: destination_hash + public_key + name_hash + random_hash + ratchet_pub + app_data
Ed25519 signatures authenticate data and prove identity ownership.
# Sign with identity's signing private key
signature = identity.sig_prv.sign(message) # 64 bytes
Signature length: Always 64 bytes
Deterministic: Same message + key always produces same signature
Fast: Optimized for embedded systems
# Verify with identity's signing public key
try:
identity.sig_pub.verify(signature, message)
return True # Valid signature
except:
return False # Invalid signature
Announces: Prove ownership of destination
destination_hash + public_key + name_hash + random_hash + ratchet + app_dataLink proofs: Prove destination accepted link
link_id + responder_pub_bytes + responder_sig_pub_bytes + signalling_bytesPacket proofs: Prove packet receipt
packet_hashCustom application data: Any data requiring authentication
Understanding encryption overhead is critical for packet sizing.
Minimum encryption overhead (identity encryption):
Ephemeral public key: 32 bytes
IV: 16 bytes
HMAC: 32 bytes
PKCS7 padding: 1-16 bytes (average 8.5)
----------------------------------
Total: 80-96 bytes (average 88.5)
Constants:
TOKEN_OVERHEAD: 48 bytes (IV + HMAC, not including ephemeral key)AES128_BLOCKSIZE: 16 bytesPadding calculation:
# PKCS7 always adds padding (1-16 bytes)
padding_length = 16 - (len(plaintext) % 16)
if padding_length == 0:
padding_length = 16
padded_length = len(plaintext) + padding_length
Ciphertext size:
ciphertext_size = len(plaintext) + padding_length
token_size = 32 + 16 + ciphertext_size + 32
# token_size = 80 + ciphertext_size
Private keys: Must be stored securely
Public keys: Can be freely distributed
Critical: All IVs and ephemeral keys must use cryptographically secure randomness
Source: os.urandom() or equivalent
random.random() or predictable sourcesOpenSSL backend: Strongly recommended for production
Pure Python backend: Only for systems without OpenSSL
HMAC verification: Must use constant-time comparison
Key derivation: HKDF inherently constant-time
For detailed specifications:
references/token-format.md - Token structure and encodingreferences/hkdf-derivation.md - HKDF detailed examplesreferences/ratchet-mechanism.md - Ratchet rotation and managementreferences/wire-examples.py - Complete encryption/decryption examplesRelated skills:
destinations - How identities relate to destinationslinks - Link-layer encryptionannounce-mechanism - Public key distribution via announcespackets-wire-format - How encrypted tokens appear in packetsSource References:
https://github.com/markqvist/Reticulum/RNS/Identity.py (Identity structure, encryption)https://github.com/markqvist/Reticulum/RNS/Destination.py (RATCHET_COUNT, RATCHET_INTERVAL constants)https://github.com/markqvist/Reticulum/RNS/Cryptography/Token.pyhttps://github.com/markqvist/Reticulum/RNS/Cryptography/HKDF.pyhttps://github.com/markqvist/Reticulum/RNS/Cryptography/HMAC.pyhttps://github.com/markqvist/Reticulum/docs/source/understanding.rst (lines 869-936)npx claudepluginhub torlando-tech/reticulum-skills --plugin reticulum-protocolProvides cryptography guidance on encryption (AES-256-GCM, ChaCha20), password hashing (Argon2id, bcrypt), signatures (Ed25519), TLS config, key management. Use for implementing or reviewing crypto.
Performs cryptographic operations using Apple CryptoKit: hashing (SHA256/SHA384/SHA512), HMAC, AES-GCM/ChaChaPoly encryption, public-key signing, ECDH key agreement, and Secure Enclave key storage.
Guides choice and use of asymmetric encryption algorithms: Ed25519 for signatures, X25519 for key exchange, RSA for legacy compatibility.