import Foundation import CryptoKit /// End-to-end encryption for revealed conversations. /// /// Uses X25519 key agreement + ChaChaPoly AEAD, the same primitives family /// used by Signal. The server only ever sees ciphertext and opaque key IDs — /// it cannot read message contents. struct CryptoManager { // MARK: - Key pair func generateKeyPair() -> (privateKey: Curve25519.KeyAgreement.PrivateKey, publicKey: Curve25519.KeyAgreement.PublicKey) { let privateKey = Curve25519.KeyAgreement.PrivateKey() return (privateKey, privateKey.publicKey) } // MARK: - Session establishment /// Derive a shared symmetric session key from two key pairs. func deriveSessionKey( myPrivate: Curve25519.KeyAgreement.PrivateKey, theirPublic: Curve25519.KeyAgreement.PublicKey ) throws -> SymmetricKey { let shared = try myPrivate.sharedSecretFromKeyAgreement(with: theirPublic) // Salt with a fixed app domain string to keep keys per-app. return shared.hkdfDerivedSymmetricKey( using: SHA256.self, salt: Data("proximity.e2e.v1".utf8), sharedInfo: Data(), outputByteCount: 32 ) } // MARK: - Message encryption func encrypt(_ plaintext: Data, using key: SymmetricKey) throws -> Data { let sealed = try ChaChaPoly.seal(plaintext, using: key) return sealed.combined } func decrypt(_ combined: Data, using key: SymmetricKey) throws -> Data { let box = try ChaChaPoly.SealedBox(combined: combined) return try ChaChaPoly.open(box, using: key) } // MARK: - Signatures (for token authenticity) func sign(_ data: Data, with key: Curve25519.Signing.PrivateKey) throws -> Data { try key.signature(for: data) } func verify(_ data: Data, signature: Data, with key: Curve25519.Signing.PublicKey) -> Bool { key.isValidSignature(signature, for: data) } }