import Foundation /// Synor Privacy SDK client for Swift. /// Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments. public actor PrivacyClient { public static let version = "0.1.0" private let config: PrivacyConfig private let session: URLSession private var isClosed = false public init(config: PrivacyConfig) { self.config = config let sessionConfig = URLSessionConfiguration.default sessionConfig.timeoutIntervalForRequest = TimeInterval(config.timeoutMs) / 1000.0 self.session = URLSession(configuration: sessionConfig) } // MARK: - Confidential Transactions public func createConfidentialTx( inputs: [ConfidentialTxInput], outputs: [ConfidentialTxOutput] ) async throws -> ConfidentialTransaction { let body: [String: Any] = [ "inputs": inputs.map { $0.toDictionary() }, "outputs": outputs.map { $0.toDictionary() } ] return try await post("/privacy/confidential/create", body: body) } public func verifyConfidentialTx(_ tx: ConfidentialTransaction) async throws -> Bool { let body: [String: Any] = ["transaction": tx.toDictionary()] let response: [String: Any] = try await post("/privacy/confidential/verify", body: body) return response["valid"] as? Bool ?? false } public func createCommitment(value: String, blindingFactor: String) async throws -> Commitment { let body: [String: Any] = [ "value": value, "blinding_factor": blindingFactor ] return try await post("/privacy/commitment/create", body: body) } public func verifyCommitment(commitment: String, value: String, blindingFactor: String) async throws -> Bool { let body: [String: Any] = [ "commitment": commitment, "value": value, "blinding_factor": blindingFactor ] let response: [String: Any] = try await post("/privacy/commitment/verify", body: body) return response["valid"] as? Bool ?? false } public func createRangeProof( value: String, blindingFactor: String, minValue: Int64, maxValue: Int64 ) async throws -> RangeProof { let body: [String: Any] = [ "value": value, "blinding_factor": blindingFactor, "min_value": minValue, "max_value": maxValue ] return try await post("/privacy/range-proof/create", body: body) } public func verifyRangeProof(_ proof: RangeProof) async throws -> Bool { let body: [String: Any] = ["proof": proof.toDictionary()] let response: [String: Any] = try await post("/privacy/range-proof/verify", body: body) return response["valid"] as? Bool ?? false } // MARK: - Ring Signatures public func createRingSignature( message: String, ring: [String], signerIndex: Int, privateKey: String ) async throws -> RingSignature { let body: [String: Any] = [ "message": message, "ring": ring, "signer_index": signerIndex, "private_key": privateKey ] return try await post("/privacy/ring/sign", body: body) } public func verifyRingSignature(_ signature: RingSignature, message: String) async throws -> Bool { let body: [String: Any] = [ "signature": signature.toDictionary(), "message": message ] let response: [String: Any] = try await post("/privacy/ring/verify", body: body) return response["valid"] as? Bool ?? false } public func generateDecoys(count: Int, excludeKey: String? = nil) async throws -> [String] { var path = "/privacy/ring/decoys?count=\(count)" if let excludeKey = excludeKey { path += "&exclude=\(excludeKey.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? excludeKey)" } return try await get(path) } public func checkKeyImage(_ keyImage: String) async throws -> Bool { let response: [String: Any] = try await get("/privacy/ring/key-image/\(keyImage)") return response["spent"] as? Bool ?? false } // MARK: - Stealth Addresses public func generateStealthKeyPair() async throws -> StealthKeyPair { return try await post("/privacy/stealth/generate", body: [:]) } public func deriveStealthAddress(spendPublicKey: String, viewPublicKey: String) async throws -> StealthAddress { let body: [String: Any] = [ "spend_public_key": spendPublicKey, "view_public_key": viewPublicKey ] return try await post("/privacy/stealth/derive", body: body) } public func recoverStealthPrivateKey( stealthAddress: String, viewPrivateKey: String, spendPrivateKey: String ) async throws -> String { let body: [String: Any] = [ "stealth_address": stealthAddress, "view_private_key": viewPrivateKey, "spend_private_key": spendPrivateKey ] let response: [String: Any] = try await post("/privacy/stealth/recover", body: body) guard let privateKey = response["private_key"] as? String else { throw PrivacyError.response("Missing private_key") } return privateKey } public func scanOutputs( viewPrivateKey: String, spendPublicKey: String, fromBlock: Int64, toBlock: Int64? = nil ) async throws -> [StealthOutput] { var body: [String: Any] = [ "view_private_key": viewPrivateKey, "spend_public_key": spendPublicKey, "from_block": fromBlock ] if let toBlock = toBlock { body["to_block"] = toBlock } return try await post("/privacy/stealth/scan", body: body) } // MARK: - Blinding public func generateBlindingFactor() async throws -> String { let response: [String: Any] = try await post("/privacy/blinding/generate", body: [:]) guard let factor = response["blinding_factor"] as? String else { throw PrivacyError.response("Missing blinding_factor") } return factor } public func blindValue(value: String, blindingFactor: String) async throws -> String { let body: [String: Any] = [ "value": value, "blinding_factor": blindingFactor ] let response: [String: Any] = try await post("/privacy/blinding/blind", body: body) guard let blindedValue = response["blinded_value"] as? String else { throw PrivacyError.response("Missing blinded_value") } return blindedValue } public func unblindValue(blindedValue: String, blindingFactor: String) async throws -> String { let body: [String: Any] = [ "blinded_value": blindedValue, "blinding_factor": blindingFactor ] let response: [String: Any] = try await post("/privacy/blinding/unblind", body: body) guard let value = response["value"] as? String else { throw PrivacyError.response("Missing value") } return value } // MARK: - Lifecycle public func healthCheck() async -> Bool { if isClosed { return false } do { let response: [String: Any] = try await get("/health") return response["status"] as? String == "healthy" } catch { return false } } public func close() { isClosed = true session.invalidateAndCancel() } // MARK: - HTTP Helpers private func get(_ path: String) async throws -> T { try checkClosed() return try await executeWithRetry { let url = URL(string: "\(self.config.endpoint)\(path)")! var request = URLRequest(url: url) request.httpMethod = "GET" self.addHeaders(to: &request) return try await self.execute(request) } } private func post(_ path: String, body: [String: Any]) async throws -> T { try checkClosed() return try await executeWithRetry { let url = URL(string: "\(self.config.endpoint)\(path)")! var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = try JSONSerialization.data(withJSONObject: body) self.addHeaders(to: &request) return try await self.execute(request) } } private func addHeaders(to request: inout URLRequest) { request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("swift/\(Self.version)", forHTTPHeaderField: "X-SDK-Version") } private func execute(_ request: URLRequest) async throws -> T { let (data, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw PrivacyError.request("Invalid response") } if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 { return try JSONDecoder().decode(T.self, from: data) } if let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { let message = errorJson["message"] as? String ?? errorJson["error"] as? String ?? "Unknown error" let code = errorJson["code"] as? String throw PrivacyError.api(message: message, code: code, statusCode: httpResponse.statusCode) } throw PrivacyError.response("HTTP \(httpResponse.statusCode)") } private func executeWithRetry(_ block: () async throws -> T) async throws -> T { var lastError: Error? for attempt in 0..