78 lines
3.1 KiB
Swift
78 lines
3.1 KiB
Swift
import Foundation
|
|
import CryptoKit
|
|
|
|
/// X25519 Diffie-Hellman key agreement
|
|
enum X25519Crypto {
|
|
|
|
// MARK: - Key Generation
|
|
|
|
/// Generate X25519 keypair
|
|
static func generateKeypair() -> (privateKey: Curve25519.KeyAgreement.PrivateKey, publicKey: Curve25519.KeyAgreement.PublicKey) {
|
|
let privateKey = Curve25519.KeyAgreement.PrivateKey()
|
|
return (privateKey, privateKey.publicKey)
|
|
}
|
|
|
|
// MARK: - Serialization
|
|
|
|
/// Serialize X25519 private key to 32 raw bytes
|
|
static func serializePrivate(_ key: Curve25519.KeyAgreement.PrivateKey) -> Data {
|
|
key.rawData // 32 bytes
|
|
}
|
|
|
|
/// Serialize X25519 public key to 32 raw bytes
|
|
static func serializePublic(_ key: Curve25519.KeyAgreement.PublicKey) -> Data {
|
|
key.rawData // 32 bytes
|
|
}
|
|
|
|
/// Load X25519 private key from 32 raw bytes
|
|
static func loadPrivate(_ data: Data) throws -> Curve25519.KeyAgreement.PrivateKey {
|
|
guard data.count == 32 else {
|
|
throw CryptoError.invalidKeyData("X25519 private key must be 32 bytes")
|
|
}
|
|
return try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: data)
|
|
}
|
|
|
|
/// Load X25519 public key from 32 raw bytes
|
|
static func loadPublic(_ data: Data) throws -> Curve25519.KeyAgreement.PublicKey {
|
|
guard data.count == 32 else {
|
|
throw CryptoError.invalidKeyData("X25519 public key must be 32 bytes")
|
|
}
|
|
return try Curve25519.KeyAgreement.PublicKey(rawRepresentation: data)
|
|
}
|
|
|
|
// MARK: - Diffie-Hellman
|
|
|
|
/// Perform X25519 DH key agreement. Returns 32-byte shared secret.
|
|
/// Matches Python: x25519_dh(private_key, public_key)
|
|
static func dh(_ privateKey: Curve25519.KeyAgreement.PrivateKey, _ publicKey: Curve25519.KeyAgreement.PublicKey) throws -> Data {
|
|
let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: publicKey)
|
|
// Extract raw bytes from SharedSecret
|
|
return sharedSecret.withUnsafeBytes { Data($0) }
|
|
}
|
|
|
|
// MARK: - Ed25519 → X25519 Key Conversion
|
|
|
|
/// Convert Ed25519 private key to X25519 private key.
|
|
/// SHA-512(seed) → take first 32 bytes → clamp per RFC 7748
|
|
/// Matches Python: ed25519_private_to_x25519(ed_private)
|
|
static func fromEd25519Private(_ edPrivate: Curve25519.Signing.PrivateKey) throws -> Curve25519.KeyAgreement.PrivateKey {
|
|
let raw = edPrivate.rawData // 32 bytes seed
|
|
// SHA-512 of the seed
|
|
let hash = SHA512.hash(data: raw)
|
|
var clamped = Data(hash.prefix(32))
|
|
// Clamp per RFC 7748
|
|
clamped[0] &= 248
|
|
clamped[31] &= 127
|
|
clamped[31] |= 64
|
|
return try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: clamped)
|
|
}
|
|
|
|
/// Convert Ed25519 public key to X25519 public key.
|
|
/// Uses Montgomery birational map: u = (1+y)/(1-y) mod p
|
|
/// Matches Python: ed25519_public_to_x25519(ed_public)
|
|
static func fromEd25519Public(_ edPublic: Curve25519.Signing.PublicKey) throws -> Curve25519.KeyAgreement.PublicKey {
|
|
let x25519Bytes = FieldArithmetic.ed25519PublicToX25519(edPublic.rawData)
|
|
return try Curve25519.KeyAgreement.PublicKey(rawRepresentation: x25519Bytes)
|
|
}
|
|
}
|