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(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(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(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()]) } } }