1# SophaxChat Cryptography23SophaxChat implements the same cryptographic protocols as Signal, adapted for a fully serverless P2P mesh.45## Key Material67Each identity consists of two keypairs:89| Key | Algorithm | Purpose |10|-----|-----------|---------|11| Identity signing key | Ed25519 | Signs all WireMessages |12| Identity DH key | X25519 | X3DH + Sealed Sender ECDH |13| Signed prekey | X25519 | X3DH |14| One-time prekeys | X25519 | X3DH (consumed once) |1516All keys are stored in the iOS Keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`. Never exported, never backed up.1718---1920## X3DH (Extended Triple Diffie-Hellman)2122Used to establish a shared secret between Alice and Bob without a server.2324**Alice (sender) computes:**25```26EK_A = random X25519 keypair2728DH1 = ECDH(IK_A, SPK_B) // Alice identity × Bob signed prekey29DH2 = ECDH(EK_A, IK_B) // Alice ephemeral × Bob identity30DH3 = ECDH(EK_A, SPK_B) // Alice ephemeral × Bob signed prekey31DH4 = ECDH(EK_A, OPK_B) // Alice ephemeral × Bob one-time prekey (if available)3233masterSecret = HKDF-SHA256(0x00…(32B) || DH1 || DH2 || DH3 [|| DH4],34 salt=0x00…(32B),35 info="SophaxChat_X3DH_v1",36 len=64)3738rootKey = masterSecret[0:32]39chainKey = masterSecret[32:64]40```4142**Bob (receiver) computes the same DH values** using his private keys and Alice's public keys from `InitiateSessionMessage`. Both derive identical `rootKey` and `chainKey`.4344**Forward secrecy**: Alice's ephemeral key `EK_A` is discarded after use. If an attacker later compromises long-term keys, past sessions remain secure.4546---4748## Double Ratchet4950After X3DH, both parties run the Double Ratchet to derive per-message keys.5152### Ratchet Header (in every message)5354```json55{56 "dhPublicKey": "<base64 X25519 32B>",57 "previousChainLength": 0,58 "messageNumber": 059}60```6162### KDF Chains6364**Root ratchet** (on each DH ratchet step):65```66rootKey, chainKey = HKDF-SHA256(rootKey || ECDH(myDH, theirDH),67 info="SophaxChat_RootRatchet_v1",68 len=64)69```7071**Sending/receiving chain** (on each message):72```73messageKey, nextChainKey = HKDF-SHA256(chainKey,74 info="SophaxChat_ChainRatchet_v1",75 len=64)76```7778### Message Encryption7980```81nonce = random 12 bytes82ciphertext = ChaCha20-Poly1305(key=messageKey, nonce=nonce, plaintext=JSON(MessageContent))83wire = nonce || ciphertext || tag(16B)84```8586**Out-of-order delivery**: skipped message keys are cached (max 1000 per session) until the out-of-order message arrives.8788---8990## Group Messaging — Sender Keys (v2)9192Each group member maintains a KDF chain per sender.9394### Key Distribution9596When a member joins or after a membership change, the member sends a `senderKeyDistribution` message (encrypted via Double Ratchet) to all other members:9798```json99{100 "groupID": "UUID",101 "senderID": "alice_peerID",102 "chainKey": "<base64 32B>",103 "iteration": 0104}105```106107### Per-Message Key Derivation108109```110message_key = HKDF-SHA256(chain_key, info="SophaxChat_SenderKey_Message_v1", len=32)111next_chain = HKDF-SHA256(chain_key, info="SophaxChat_SenderKey_Chain_v1", len=32)112```113114Iteration counter is included in every `GroupWireMessage` so receivers can fast-forward their chain if they missed messages (up to ±100 iterations tolerance).115116### Group Message Encryption117118```119nonce = random 12 bytes120ciphertext = ChaCha20-Poly1305(key=message_key, nonce=nonce, plaintext=messageBody.utf8)121wire = nonce || ciphertext || tag(16B)122```123124### Key Rotation125126When a member leaves (`groupMemberLeft`), remaining members generate new chain keys and redistribute via `senderKeyDistribution`. The leaver cannot decrypt future messages.127128---129130## Sealed Sender131132Prevents relay nodes from learning who is messaging whom.133134```135EK_s = random X25519 keypair (ephemeral, per message)136sharedSecret = ECDH(EK_s.private, recipient.dhIdentityKeyPublic)137sealingKey = HKDF-SHA256(sharedSecret,138 info="SophaxChat_SealedSender_v1",139 len=32)140nonce = random 12 bytes141ciphertext = ChaCha20-Poly1305(sealingKey, nonce, JSON(innerWireMessage))142```143144Wire payload: `ephemeralPublicKey(32B) || nonce(12B) || ciphertext || tag(16B)`.145146---147148## Tor v3 .onion Address Derivation149150The app derives a stable .onion hostname from the Ed25519 identity signing key (no separate Tor key needed):151152```153pubkey = ed25519_signing_key_public // 32 bytes154checksum = SHA3-256(".onion checksum" || pubkey || 0x03)[0:2]155address = BASE32(pubkey || checksum || 0x03) + ".onion"156```157158This is Tor v3 SPEC (proposal 224). The address is deterministic — same identity key always produces the same .onion address.159160---161162## Safety Number163164Used for out-of-band identity verification (equivalent to Signal's safety number):165166```167safetyNumber = hex(SHA-256(mySigningKey || theirSigningKey || myDHKey || theirDHKey))168 formatted as 12 groups of 5 digits169```170171Displayed in `IdentityView` and as a QR code for scanning.172173---174175## Implementation Notes176177- All crypto uses Apple **CryptoKit** (iOS/macOS) — hardware-backed on devices with Secure Enclave.178-**ChaCha20-Poly1305** is preferred over AES-GCM for its resistance to timing attacks and nonce-misuse.179- Message keys are **erased from memory** immediately after use (Swift `withUnsafeMutableBytes` zero-fill).180- Keychain items use `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` — keys cannot leave the device.