IFM Identity & Cryptography
Overview
IFM uses cryptographic identity instead of accounts. No emails, passwords, or central authorities. Each node generates an Ed25519 key pair once, which becomes its permanent identity.
Identity Model
Key Properties
- Generated once at node initialization
- Permanent unless user explicitly regenerates
- Portable — copy
identity.keyto move identity between devices - No recovery — lost private key = lost identity (by design)
Ed25519 Key Generation
rust
use ed25519_dalek::{SigningKey, VerifyingKey};
use rand::rngs::OsRng;
fn generate_identity() -> (SigningKey, VerifyingKey) {
let mut csprng = OsRng;
let signing_key = SigningKey::generate(&mut csprng);
let verifying_key = signing_key.verifying_key();
(signing_key, verifying_key)
}Key Formats
| Format | Size | Encoding | Use Case |
|---|---|---|---|
| Raw Private | 32 bytes | Binary | Internal use, identity.key file |
| Raw Public | 32 bytes | Binary | Internal use, wire protocol |
| Private Key | 64 bytes | Hex / Base58 | Export/import, QR codes |
| Public Key | 32 bytes | Hex / Base58 | Sharing, verification |
| Peer ID | Variable | Multibase (base58btc) | libp2p, DHT, human-readable |
Peer ID Encoding (libp2p standard)
Identity Storage
File: identity.key
toml
# Binary format (preferred)
# 32 bytes raw private key
# OR TOML format (human-readable)
[identity]
private_key = "base58_encoded_64_byte_keypair"
created_at = 1699999999
version = 1Permissions
identity.key: 600 (owner read/write only)
config.toml: 644
cache/: 700
logs/: 700
plugins/: 755Cryptographic Primitives
1. Ed25519 (Signatures)
- Use: Packet signing, identity verification
- Library:
ed25519-dalek - Key size: 32 bytes private, 32 bytes public
- Signature size: 64 bytes
- Security: ~128-bit
2. BLAKE3 (Hashing)
- Use: Packet IDs, frequency Topic IDs, content hashes, deduplication
- Library:
blake3 - Output: 32 bytes (256-bit)
- Speed: ~5 GB/s (software), hardware accelerated
- Security: 256-bit collision resistance
3. ChaCha20-Poly1305 (AEAD Encryption)
- Use: Protected/hidden frequency payload encryption
- Library:
chacha20poly1305 - Key size: 32 bytes
- Nonce size: 12 bytes
- Tag size: 16 bytes
- Security: 256-bit
4. HKDF-SHA256 (Key Derivation)
- Use: Deriving encryption keys from shared secrets
- Library:
hkdf - Salt:
"ifm-frequency"(protocol constant) - Info: Canonical frequency string
5. Noise Protocol (Transport Encryption)
- Use: QUIC/WebRTC transport encryption
- Pattern:
Noise_XK_25519_ChaChaPoly_BLAKE2s - Library:
snow(via libp2p) - Handshake: 1-RTT, forward secrecy
6. zstd (Compression)
- Use: Large payload compression
- Library:
zstd - Level: 3 (balanced speed/ratio)
- Threshold: > 1 KB payloads
Key Derivation for Protected Frequencies
Shared Secret Establishment
Protected frequencies require a shared secret established out-of-band:
- QR code exchange
- Secure messenger (Signal, etc.)
- Physical meeting
- Password-based (PBKDF2/Argon2) — less secure
Key Derivation
rust
use hkdf::Hkdf;
use sha2::Sha256;
fn derive_frequency_key(shared_secret: &[u8], canonical_frequency: &str) -> ([u8; 32], [u8; 12]) {
let hkdf = Hkdf::<Sha256>::new(
Some(b"ifm-frequency"), // salt
shared_secret // IKM
);
let mut okm = [0u8; 44]; // 32 byte key + 12 byte nonce prefix
hkdf.expand(canonical_frequency.as_bytes(), &mut okm)
.expect("HKDF expand failed");
let mut key = [0u8; 32];
let mut nonce_prefix = [0u8; 12];
key.copy_from_slice(&okm[0..32]);
nonce_prefix.copy_from_slice(&okm[32..44]);
(key, nonce_prefix)
}Per-Packet Nonce Construction
rust
fn build_nonce(nonce_prefix: &[u8; 12], sequence: u64) -> [u8; 12] {
let mut nonce = *nonce_prefix;
nonce[8..12].copy_from_slice(&sequence.to_be_bytes());
nonce
}Nonce Format: [8-byte prefix][4-byte sequence (big-endian)]
- Prefix derived from shared secret + frequency
- Sequence ensures uniqueness per packet
- Big-endian for network byte order compatibility
Packet Signing & Verification
Signing (Sender)
rust
fn sign_packet(signing_key: &SigningKey, packet: &PacketWithoutSig) -> [u8; 64] {
let bytes = packet.to_bytes(); // All fields except signature
signing_key.sign(&bytes).to_bytes()
}Verification (Relay & Recipient)
rust
fn verify_packet(verifying_key: &VerifyingKey, packet: &PacketWithoutSig, sig: &[u8; 64]) -> bool {
let bytes = packet.to_bytes();
verifying_key.verify_strict(&bytes, &Signature::from_bytes(sig)).is_ok()
}Verification Rules
| Actor | Must Verify | Action on Failure |
|---|---|---|
| Relay | Every packet | Drop, do NOT forward |
| Recipient | Every packet | Drop, do NOT process |
| Sender | N/A | Signs before sending |
Transport Encryption (Noise)
Handshake Pattern: Noise_XK
- XK: Initiator knows responder's static key (from Peer ID)
- 25519: Curve25519 for DH
- ChaChaPoly: AEAD for transport packets
- BLAKE2s: Hash for key derivation
Result
- Encrypted, authenticated transport
- Forward secrecy (ephemeral keys)
- Identity authentication (static keys)
- 1-RTT handshake
Replay Protection
Mechanisms
- Packet ID (BLAKE3): Unique per packet content — duplicates detected
- Sequence Numbers: Per-sender monotonic counter — replays detected
- Timestamps: Reject packets older than 5 minutes (configurable)
- Dedup Cache: LRU cache of recent packet IDs (100k entries)
Replay Detection Flow
Duplicate Detection
Dedup Cache
- Structure: LRU hash set of packet_ids
- Capacity: 100,000 entries (configurable)
- Memory: ~3.2 MB (32 bytes × 100k)
- Eviction: Least recently used
Cache Key
packet_id = BLAKE3(packet_without_signature)- Same packet → same ID → detected as duplicate
- Relay decrementing TTL does NOT change packet_id (forwards original)
- Signature verification uses original bytes
Security Considerations
Threat Model
| Threat | Mitigation |
|---|---|
| Packet forgery | Ed25519 signatures, verified by all |
| Replay attacks | Packet ID cache, sequence numbers, timestamps |
| Traffic analysis | Transport encryption (Noise), optional frequency encryption |
| Sybil attack | Identity cost (key gen), no central registry |
| Eclipse attack | Multiple bootstrap, local discovery, Kademlia diversity |
| MITM | Noise handshake authenticates peers |
| Key compromise | Rotate identity (generate new), revoke old |
Best Practices
- Never share private key — not even with "support"
- Backup identity.key — lose it = lose identity
- Use protected frequencies for sensitive comms
- Verify peer identities out-of-band for high-value comms
- Monitor sequence gaps — detect missed packets
- Rotate identities periodically for long-term anonymity
Cryptographic Agility
Algorithm Identifiers
rust
enum SignatureAlgorithm {
Ed25519 = 0x01,
// Future: Ed448, Schnorr, etc.
}
enum HashAlgorithm {
Blake3 = 0x01,
// Future: SHA3-256, etc.
}
enum EncryptionAlgorithm {
ChaCha20Poly1305 = 0x01,
// Future: AES-GCM, etc.
}
enum KeyDerivationAlgorithm {
HkdfSha256 = 0x01,
// Future: Argon2, etc.
}Version Negotiation
- Protocol version in packet header
- Capability advertisement in HELLO/WELCOME
- Graceful degradation for unknown algorithms