ios_client
This commit is contained in:
139
ios_client 0.8.5/Kecalek/Crypto/X3DH.swift
Normal file
139
ios_client 0.8.5/Kecalek/Crypto/X3DH.swift
Normal file
@@ -0,0 +1,139 @@
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
/// X3DH key agreement protocol (Signal Protocol)
|
||||
enum X3DH {
|
||||
|
||||
// MARK: - Pre-Key Generation
|
||||
|
||||
/// Generate a signed pre-key (SPK).
|
||||
/// Returns (private, public, signature, id).
|
||||
/// Matches Python: generate_signed_prekey(identity_private)
|
||||
static func generateSignedPrekey(
|
||||
identityPrivate: Curve25519.Signing.PrivateKey
|
||||
) throws -> (privateKey: Curve25519.KeyAgreement.PrivateKey,
|
||||
publicKey: Curve25519.KeyAgreement.PublicKey,
|
||||
signature: Data,
|
||||
id: String) {
|
||||
let (spkPriv, spkPub) = X25519Crypto.generateKeypair()
|
||||
let spkPubBytes = X25519Crypto.serializePublic(spkPub)
|
||||
let signature = try Ed25519Crypto.sign(identityPrivate, data: spkPubBytes)
|
||||
return (spkPriv, spkPub, signature, UUID().uuidString)
|
||||
}
|
||||
|
||||
/// Generate a batch of one-time pre-keys.
|
||||
/// Matches Python: generate_one_time_prekeys(count=50)
|
||||
static func generateOneTimePrekeys(count: Int = 50) -> [(privateKey: Curve25519.KeyAgreement.PrivateKey,
|
||||
publicKey: Curve25519.KeyAgreement.PublicKey,
|
||||
id: String)] {
|
||||
(0..<count).map { _ in
|
||||
let (priv, pub) = X25519Crypto.generateKeypair()
|
||||
return (priv, pub, UUID().uuidString)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - X3DH Initiate (Alice)
|
||||
|
||||
/// Initiator side of X3DH.
|
||||
/// Returns (sharedSecret, ephemeralPrivate, ephemeralPublic).
|
||||
/// Matches Python: x3dh_initiate(ik_private_ed, ik_public_remote_ed, spk_remote, spk_signature, opk_remote?)
|
||||
static func initiate(
|
||||
ikPrivateEd: Curve25519.Signing.PrivateKey,
|
||||
ikPublicRemoteEd: Curve25519.Signing.PublicKey,
|
||||
spkRemote: Curve25519.KeyAgreement.PublicKey,
|
||||
spkSignature: Data,
|
||||
opkRemote: Curve25519.KeyAgreement.PublicKey? = nil
|
||||
) throws -> (sharedSecret: Data,
|
||||
ephemeralPrivate: Curve25519.KeyAgreement.PrivateKey,
|
||||
ephemeralPublic: Curve25519.KeyAgreement.PublicKey) {
|
||||
// Verify SPK signature
|
||||
let spkRemoteBytes = X25519Crypto.serializePublic(spkRemote)
|
||||
guard Ed25519Crypto.verify(ikPublicRemoteEd, signature: spkSignature, data: spkRemoteBytes) else {
|
||||
throw CryptoError.x3dhFailed("Invalid SPK signature")
|
||||
}
|
||||
|
||||
// Convert identity keys to X25519
|
||||
let ikX25519Private = try X25519Crypto.fromEd25519Private(ikPrivateEd)
|
||||
let ikX25519Remote = try X25519Crypto.fromEd25519Public(ikPublicRemoteEd)
|
||||
|
||||
// Generate ephemeral keypair
|
||||
let (ekPriv, ekPub) = X25519Crypto.generateKeypair()
|
||||
|
||||
// Debug: print key inputs (matching Python x3dh_respond)
|
||||
#if DEBUG
|
||||
print("DEBUG x3dh_initiate: ik_remote_ed = \(Ed25519Crypto.serializePublic(ikPublicRemoteEd).hexString)")
|
||||
print("DEBUG x3dh_initiate: ik_x25519_remote = \(X25519Crypto.serializePublic(ikX25519Remote).hexString)")
|
||||
print("DEBUG x3dh_initiate: ek_pub = \(X25519Crypto.serializePublic(ekPub).hexString)")
|
||||
print("DEBUG x3dh_initiate: spk_remote = \(spkRemoteBytes.hexString)")
|
||||
#endif
|
||||
|
||||
// DH computations
|
||||
let dh1 = try X25519Crypto.dh(ikX25519Private, spkRemote) // IK_A, SPK_B
|
||||
let dh2 = try X25519Crypto.dh(ekPriv, ikX25519Remote) // EK_A, IK_B
|
||||
let dh3 = try X25519Crypto.dh(ekPriv, spkRemote) // EK_A, SPK_B
|
||||
|
||||
// Debug: print DH outputs
|
||||
#if DEBUG
|
||||
print("DEBUG x3dh_initiate: dh1 = \(dh1.hexString)")
|
||||
print("DEBUG x3dh_initiate: dh2 = \(dh2.hexString)")
|
||||
print("DEBUG x3dh_initiate: dh3 = \(dh3.hexString)")
|
||||
#endif
|
||||
|
||||
var dhConcat = dh1 + dh2 + dh3
|
||||
if let opk = opkRemote {
|
||||
let dh4 = try X25519Crypto.dh(ekPriv, opk) // EK_A, OPK_B
|
||||
#if DEBUG
|
||||
print("DEBUG x3dh_initiate: dh4 = \(dh4.hexString)")
|
||||
#endif
|
||||
dhConcat += dh4
|
||||
}
|
||||
|
||||
// Derive shared secret
|
||||
let sharedSecret = CryptoUtils.hkdfDerive(
|
||||
inputKey: dhConcat,
|
||||
salt: Data(repeating: 0x00, count: 32),
|
||||
info: Data(Constants.x3dhInfo.utf8),
|
||||
length: 32
|
||||
)
|
||||
#if DEBUG
|
||||
print("DEBUG x3dh_initiate: shared_secret = \(sharedSecret.hexString)")
|
||||
#endif
|
||||
|
||||
return (sharedSecret, ekPriv, ekPub)
|
||||
}
|
||||
|
||||
// MARK: - X3DH Respond (Bob)
|
||||
|
||||
/// Responder side of X3DH.
|
||||
/// Returns sharedSecret.
|
||||
/// Matches Python: x3dh_respond(ik_private_ed, spk_private, ik_remote_ed, ek_remote, opk_private?)
|
||||
static func respond(
|
||||
ikPrivateEd: Curve25519.Signing.PrivateKey,
|
||||
spkPrivate: Curve25519.KeyAgreement.PrivateKey,
|
||||
ikRemoteEd: Curve25519.Signing.PublicKey,
|
||||
ekRemote: Curve25519.KeyAgreement.PublicKey,
|
||||
opkPrivate: Curve25519.KeyAgreement.PrivateKey? = nil
|
||||
) throws -> Data {
|
||||
let ikX25519Private = try X25519Crypto.fromEd25519Private(ikPrivateEd)
|
||||
let ikX25519Remote = try X25519Crypto.fromEd25519Public(ikRemoteEd)
|
||||
|
||||
let dh1 = try X25519Crypto.dh(spkPrivate, ikX25519Remote) // SPK_B, IK_A
|
||||
let dh2 = try X25519Crypto.dh(ikX25519Private, ekRemote) // IK_B, EK_A
|
||||
let dh3 = try X25519Crypto.dh(spkPrivate, ekRemote) // SPK_B, EK_A
|
||||
|
||||
var dhConcat = dh1 + dh2 + dh3
|
||||
if let opk = opkPrivate {
|
||||
let dh4 = try X25519Crypto.dh(opk, ekRemote) // OPK_B, EK_A
|
||||
dhConcat += dh4
|
||||
}
|
||||
|
||||
let sharedSecret = CryptoUtils.hkdfDerive(
|
||||
inputKey: dhConcat,
|
||||
salt: Data(repeating: 0x00, count: 32),
|
||||
info: Data(Constants.x3dhInfo.utf8),
|
||||
length: 32
|
||||
)
|
||||
|
||||
return sharedSecret
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user