Implements quantum-resistant cryptographic primitives across all SDK languages: - Hybrid Ed25519 + Dilithium3 signatures (classical + post-quantum) - BIP-39 mnemonic support (12, 15, 18, 21, 24 words) - BIP-44 hierarchical key derivation (coin type 0x5359) - Post-quantum algorithms: Falcon (FIPS 206), SPHINCS+ (FIPS 205) - Key derivation: HKDF-SHA3-256, PBKDF2 - Hash functions: SHA3-256, BLAKE3, Keccak-256 Languages: JavaScript/TypeScript, Python, Go, Rust, Flutter/Dart, Java, Kotlin, Swift, C, C++, C#/.NET, Ruby
347 lines
13 KiB
Swift
347 lines
13 KiB
Swift
import Foundation
|
|
|
|
/// Synor Crypto SDK for Swift
|
|
///
|
|
/// Quantum-resistant cryptographic primitives for the Synor blockchain.
|
|
///
|
|
/// ```swift
|
|
/// let config = CryptoConfig(apiKey: "your-api-key")
|
|
/// let crypto = SynorCrypto(config: config)
|
|
///
|
|
/// // Generate a mnemonic
|
|
/// let mnemonic = try await crypto.mnemonic.generate(wordCount: 24)
|
|
/// print("Backup words: \(mnemonic.phrase)")
|
|
///
|
|
/// // Create keypair from mnemonic
|
|
/// let keypair = try await crypto.keypairs.fromMnemonic(phrase: mnemonic.phrase, passphrase: "")
|
|
/// let address = keypair.address(for: .mainnet)
|
|
///
|
|
/// // Sign a message
|
|
/// let signature = try await crypto.signing.sign(keypair: keypair, message: "Hello!".data(using: .utf8)!)
|
|
/// ```
|
|
public class SynorCrypto {
|
|
private let config: CryptoConfig
|
|
private let session: URLSession
|
|
private let encoder: JSONEncoder
|
|
private let decoder: JSONDecoder
|
|
private var _closed = false
|
|
|
|
public let mnemonic: MnemonicClient
|
|
public let keypairs: KeypairClient
|
|
public let signing: SigningClient
|
|
public let falcon: FalconClient
|
|
public let sphincs: SphincsClient
|
|
public let kdf: KdfClient
|
|
public let hash: HashClient
|
|
|
|
public var defaultNetwork: Network { config.defaultNetwork }
|
|
public var isClosed: Bool { _closed }
|
|
|
|
public init(config: CryptoConfig) {
|
|
self.config = config
|
|
|
|
let sessionConfig = URLSessionConfiguration.default
|
|
sessionConfig.timeoutIntervalForRequest = config.timeout
|
|
self.session = URLSession(configuration: sessionConfig)
|
|
|
|
self.encoder = JSONEncoder()
|
|
encoder.keyEncodingStrategy = .convertToSnakeCase
|
|
|
|
self.decoder = JSONDecoder()
|
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
|
|
|
self.mnemonic = MnemonicClient()
|
|
self.keypairs = KeypairClient()
|
|
self.signing = SigningClient()
|
|
self.falcon = FalconClient()
|
|
self.sphincs = SphincsClient()
|
|
self.kdf = KdfClient()
|
|
self.hash = HashClient()
|
|
|
|
// Set parent references
|
|
self.mnemonic.crypto = self
|
|
self.keypairs.crypto = self
|
|
self.signing.crypto = self
|
|
self.falcon.crypto = self
|
|
self.sphincs.crypto = self
|
|
self.kdf.crypto = self
|
|
self.hash.crypto = self
|
|
}
|
|
|
|
public func healthCheck() async -> Bool {
|
|
do {
|
|
let result: [String: String] = try await get(path: "/health")
|
|
return result["status"] == "healthy"
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
public func getInfo() async throws -> [String: Any] {
|
|
try await get(path: "/info")
|
|
}
|
|
|
|
public func close() {
|
|
_closed = true
|
|
session.invalidateAndCancel()
|
|
}
|
|
|
|
// MARK: - Internal HTTP Methods
|
|
|
|
func get<T: Decodable>(path: String) async throws -> T {
|
|
guard !_closed else {
|
|
throw CryptoError(message: "Client has been closed", code: "CLIENT_CLOSED")
|
|
}
|
|
|
|
guard let url = URL(string: config.endpoint + path) else {
|
|
throw CryptoError(message: "Invalid URL", code: "INVALID_URL")
|
|
}
|
|
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "GET"
|
|
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
|
|
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
|
|
|
|
let (data, response) = try await session.data(for: request)
|
|
return try handleResponse(data: data, response: response)
|
|
}
|
|
|
|
func post<T: Decodable>(path: String, body: [String: Any]? = nil) async throws -> T {
|
|
guard !_closed else {
|
|
throw CryptoError(message: "Client has been closed", code: "CLIENT_CLOSED")
|
|
}
|
|
|
|
guard let url = URL(string: config.endpoint + path) else {
|
|
throw CryptoError(message: "Invalid URL", code: "INVALID_URL")
|
|
}
|
|
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "POST"
|
|
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
|
|
|
|
if let body = body {
|
|
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
|
} else {
|
|
request.httpBody = "{}".data(using: .utf8)
|
|
}
|
|
|
|
let (data, response) = try await session.data(for: request)
|
|
return try handleResponse(data: data, response: response)
|
|
}
|
|
|
|
private func handleResponse<T: Decodable>(data: Data, response: URLResponse) throws -> T {
|
|
guard let httpResponse = response as? HTTPURLResponse else {
|
|
throw CryptoError(message: "Invalid response", code: "INVALID_RESPONSE")
|
|
}
|
|
|
|
if httpResponse.statusCode >= 400 {
|
|
if let errorDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
let message = errorDict["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
|
|
let code = errorDict["code"] as? String
|
|
throw CryptoError(message: message, code: code)
|
|
}
|
|
throw CryptoError(message: "HTTP \(httpResponse.statusCode)", code: nil)
|
|
}
|
|
|
|
return try decoder.decode(T.self, from: data)
|
|
}
|
|
|
|
// MARK: - Sub-Clients
|
|
|
|
public class MnemonicClient {
|
|
weak var crypto: SynorCrypto?
|
|
|
|
public func generate(wordCount: Int = 24) async throws -> Mnemonic {
|
|
try await crypto!.post(path: "/mnemonic/generate", body: ["word_count": wordCount])
|
|
}
|
|
|
|
public func fromPhrase(phrase: String) async throws -> Mnemonic {
|
|
try await crypto!.post(path: "/mnemonic/from-phrase", body: ["phrase": phrase])
|
|
}
|
|
|
|
public func validate(phrase: String) async throws -> MnemonicValidation {
|
|
try await crypto!.post(path: "/mnemonic/validate", body: ["phrase": phrase])
|
|
}
|
|
|
|
public func toSeed(phrase: String, passphrase: String = "") async throws -> Data {
|
|
struct SeedResponse: Decodable { let seed: String }
|
|
let result: SeedResponse = try await crypto!.post(
|
|
path: "/mnemonic/to-seed",
|
|
body: ["phrase": phrase, "passphrase": passphrase]
|
|
)
|
|
return Data(base64Encoded: result.seed) ?? Data()
|
|
}
|
|
|
|
public func suggestWords(partial: String, limit: Int = 5) async throws -> [String] {
|
|
struct SuggestResponse: Decodable { let suggestions: [String] }
|
|
let result: SuggestResponse = try await crypto!.post(
|
|
path: "/mnemonic/suggest",
|
|
body: ["partial": partial, "limit": limit]
|
|
)
|
|
return result.suggestions
|
|
}
|
|
}
|
|
|
|
public class KeypairClient {
|
|
weak var crypto: SynorCrypto?
|
|
|
|
public func generate() async throws -> HybridKeypair {
|
|
try await crypto!.post(path: "/keypair/generate")
|
|
}
|
|
|
|
public func fromMnemonic(phrase: String, passphrase: String = "") async throws -> HybridKeypair {
|
|
try await crypto!.post(
|
|
path: "/keypair/from-mnemonic",
|
|
body: ["phrase": phrase, "passphrase": passphrase]
|
|
)
|
|
}
|
|
|
|
public func fromSeed(seed: Data) async throws -> HybridKeypair {
|
|
try await crypto!.post(
|
|
path: "/keypair/from-seed",
|
|
body: ["seed": seed.base64EncodedString()]
|
|
)
|
|
}
|
|
|
|
public func getAddress(publicKey: HybridPublicKey, network: Network) async throws -> Address {
|
|
try await crypto!.post(
|
|
path: "/keypair/address",
|
|
body: ["public_key": publicKey.toDict(), "network": network.rawValue]
|
|
)
|
|
}
|
|
}
|
|
|
|
public class SigningClient {
|
|
weak var crypto: SynorCrypto?
|
|
|
|
public func sign(keypair: HybridKeypair, message: Data) async throws -> HybridSignature {
|
|
try await crypto!.post(path: "/sign/hybrid", body: [
|
|
"secret_key": keypair.secretKey.toDict(),
|
|
"message": message.base64EncodedString()
|
|
])
|
|
}
|
|
|
|
public func verify(publicKey: HybridPublicKey, message: Data, signature: HybridSignature) async throws -> Bool {
|
|
struct VerifyResponse: Decodable { let valid: Bool }
|
|
let result: VerifyResponse = try await crypto!.post(path: "/sign/verify", body: [
|
|
"public_key": publicKey.toDict(),
|
|
"message": message.base64EncodedString(),
|
|
"signature": signature.toDict()
|
|
])
|
|
return result.valid
|
|
}
|
|
|
|
public func signEd25519(secretKey: Data, message: Data) async throws -> Data {
|
|
struct SignResponse: Decodable { let signature: String }
|
|
let result: SignResponse = try await crypto!.post(path: "/sign/ed25519", body: [
|
|
"secret_key": secretKey.base64EncodedString(),
|
|
"message": message.base64EncodedString()
|
|
])
|
|
return Data(base64Encoded: result.signature) ?? Data()
|
|
}
|
|
}
|
|
|
|
public class FalconClient {
|
|
weak var crypto: SynorCrypto?
|
|
|
|
public func generate(variant: FalconVariant = .falcon512) async throws -> FalconKeypair {
|
|
try await crypto!.post(path: "/falcon/generate", body: ["variant": variant.rawValue])
|
|
}
|
|
|
|
public func sign(keypair: FalconKeypair, message: Data) async throws -> FalconSignature {
|
|
try await crypto!.post(path: "/falcon/sign", body: [
|
|
"variant": keypair.variantEnum.rawValue,
|
|
"secret_key": keypair.secretKey.keyBytes.base64EncodedString(),
|
|
"message": message.base64EncodedString()
|
|
])
|
|
}
|
|
|
|
public func verify(publicKey: Data, message: Data, signature: FalconSignature) async throws -> Bool {
|
|
struct VerifyResponse: Decodable { let valid: Bool }
|
|
let result: VerifyResponse = try await crypto!.post(path: "/falcon/verify", body: [
|
|
"variant": signature.variantEnum.rawValue,
|
|
"public_key": publicKey.base64EncodedString(),
|
|
"message": message.base64EncodedString(),
|
|
"signature": signature.signatureBytes.base64EncodedString()
|
|
])
|
|
return result.valid
|
|
}
|
|
}
|
|
|
|
public class SphincsClient {
|
|
weak var crypto: SynorCrypto?
|
|
|
|
public func generate(variant: SphincsVariant = .shake128s) async throws -> SphincsKeypair {
|
|
try await crypto!.post(path: "/sphincs/generate", body: ["variant": variant.rawValue])
|
|
}
|
|
|
|
public func sign(keypair: SphincsKeypair, message: Data) async throws -> SphincsSignature {
|
|
try await crypto!.post(path: "/sphincs/sign", body: [
|
|
"variant": keypair.variantEnum.rawValue,
|
|
"secret_key": keypair.secretKey.keyBytes.base64EncodedString(),
|
|
"message": message.base64EncodedString()
|
|
])
|
|
}
|
|
|
|
public func verify(publicKey: Data, message: Data, signature: SphincsSignature) async throws -> Bool {
|
|
struct VerifyResponse: Decodable { let valid: Bool }
|
|
let result: VerifyResponse = try await crypto!.post(path: "/sphincs/verify", body: [
|
|
"variant": signature.variantEnum.rawValue,
|
|
"public_key": publicKey.base64EncodedString(),
|
|
"message": message.base64EncodedString(),
|
|
"signature": signature.signatureBytes.base64EncodedString()
|
|
])
|
|
return result.valid
|
|
}
|
|
}
|
|
|
|
public class KdfClient {
|
|
weak var crypto: SynorCrypto?
|
|
|
|
public func deriveKey(seed: Data, config: DerivationConfig = DerivationConfig()) async throws -> Data {
|
|
var body: [String: Any] = [
|
|
"seed": seed.base64EncodedString(),
|
|
"output_length": config.outputLength
|
|
]
|
|
if let salt = config.salt {
|
|
body["salt"] = salt.base64EncodedString()
|
|
}
|
|
if let info = config.info {
|
|
body["info"] = info.base64EncodedString()
|
|
}
|
|
|
|
struct KeyResponse: Decodable { let key: String }
|
|
let result: KeyResponse = try await crypto!.post(path: "/kdf/hkdf", body: body)
|
|
return Data(base64Encoded: result.key) ?? Data()
|
|
}
|
|
|
|
public func deriveFromPassword(password: Data, config: PasswordDerivationConfig) async throws -> Data {
|
|
struct KeyResponse: Decodable { let key: String }
|
|
let result: KeyResponse = try await crypto!.post(path: "/kdf/pbkdf2", body: [
|
|
"password": password.base64EncodedString(),
|
|
"salt": config.salt.base64EncodedString(),
|
|
"iterations": config.iterations,
|
|
"output_length": config.outputLength
|
|
])
|
|
return Data(base64Encoded: result.key) ?? Data()
|
|
}
|
|
}
|
|
|
|
public class HashClient {
|
|
weak var crypto: SynorCrypto?
|
|
|
|
public func sha3_256(data: Data) async throws -> Hash256 {
|
|
try await crypto!.post(path: "/hash/sha3-256", body: ["data": data.base64EncodedString()])
|
|
}
|
|
|
|
public func blake3(data: Data) async throws -> Hash256 {
|
|
try await crypto!.post(path: "/hash/blake3", body: ["data": data.base64EncodedString()])
|
|
}
|
|
|
|
public func keccak256(data: Data) async throws -> Hash256 {
|
|
try await crypto!.post(path: "/hash/keccak256", body: ["data": data.base64EncodedString()])
|
|
}
|
|
}
|
|
}
|