Skip to content

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.key to 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

FormatSizeEncodingUse Case
Raw Private32 bytesBinaryInternal use, identity.key file
Raw Public32 bytesBinaryInternal use, wire protocol
Private Key64 bytesHex / Base58Export/import, QR codes
Public Key32 bytesHex / Base58Sharing, verification
Peer IDVariableMultibase (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 = 1

Permissions

identity.key: 600 (owner read/write only)
config.toml:  644
cache/:       700
logs/:        700
plugins/:     755

Cryptographic 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

ActorMust VerifyAction on Failure
RelayEvery packetDrop, do NOT forward
RecipientEvery packetDrop, do NOT process
SenderN/ASigns 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

  1. Packet ID (BLAKE3): Unique per packet content — duplicates detected
  2. Sequence Numbers: Per-sender monotonic counter — replays detected
  3. Timestamps: Reject packets older than 5 minutes (configurable)
  4. 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

ThreatMitigation
Packet forgeryEd25519 signatures, verified by all
Replay attacksPacket ID cache, sequence numbers, timestamps
Traffic analysisTransport encryption (Noise), optional frequency encryption
Sybil attackIdentity cost (key gen), no central registry
Eclipse attackMultiple bootstrap, local discovery, Kademlia diversity
MITMNoise handshake authenticates peers
Key compromiseRotate identity (generate new), revoke old

Best Practices

  1. Never share private key — not even with "support"
  2. Backup identity.key — lose it = lose identity
  3. Use protected frequencies for sensitive comms
  4. Verify peer identities out-of-band for high-value comms
  5. Monitor sequence gaps — detect missed packets
  6. 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

Mermaid: Identity & Crypto Flow

Released under the MIT License.