From 870f10cb75413f58848129d90a71c00c532245ea Mon Sep 17 00:00:00 2001 From: Nic Reich Date: Sat, 18 Jul 2026 01:36:30 -0400 Subject: [PATCH 1/8] Remove unused CommonCrypto import for Linux compatibility --- .../BlockchainClient/Helpers/AskCoin-HD/Keychain.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift b/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift index afd89b21e..48ae2ec12 100644 --- a/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift +++ b/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift @@ -5,7 +5,6 @@ // Created by 仇弘扬 on 2017/8/14. // Copyright © 2017年 askcoin. All rights reserved. // -import CommonCrypto import Foundation let BTCKeychainMainnetPrivateVersion: UInt32 = 0x0488_ADE4 From ac0e728e8ac399e2986a7a85e71b000484f02f68 Mon Sep 17 00:00:00 2001 From: Nic Reich Date: Sat, 1 Aug 2026 16:12:18 -0400 Subject: [PATCH 2/8] fix: add FoundationNetworking guard for URLRequest/URLSession on Linux --- Sources/SolanaSwift/Extensions/URLRequest+Extensions.swift | 3 +++ Sources/SolanaSwift/Helpers/URLSession+NetworkManager.swift | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/Sources/SolanaSwift/Extensions/URLRequest+Extensions.swift b/Sources/SolanaSwift/Extensions/URLRequest+Extensions.swift index 1f4aa54b4..3b8243d70 100644 --- a/Sources/SolanaSwift/Extensions/URLRequest+Extensions.swift +++ b/Sources/SolanaSwift/Extensions/URLRequest+Extensions.swift @@ -1,4 +1,7 @@ import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif public extension URLRequest { func cURL(pretty: Bool = false) -> String { diff --git a/Sources/SolanaSwift/Helpers/URLSession+NetworkManager.swift b/Sources/SolanaSwift/Helpers/URLSession+NetworkManager.swift index d92377aa1..9ba0e8270 100644 --- a/Sources/SolanaSwift/Helpers/URLSession+NetworkManager.swift +++ b/Sources/SolanaSwift/Helpers/URLSession+NetworkManager.swift @@ -1,5 +1,9 @@ import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +#if canImport(FoundationNetworking) extension URLSession: NetworkManager { public func requestData(request: URLRequest) async throws -> Data { let (data, _): (Data, URLResponse) @@ -7,3 +11,4 @@ extension URLSession: NetworkManager { return data } } +#endif // canImport(FoundationNetworking) From 0fb9c2fbfbe0b95fdcaa4baf2a5382cae1e804ff Mon Sep 17 00:00:00 2001 From: Nic Reich Date: Sat, 1 Aug 2026 12:13:36 -0400 Subject: [PATCH 3/8] fix: guard Accelerate.vecLib import behind canImport for Linux --- Sources/SolanaSwift/BlockchainClient/Helpers/Int2X/UInt2X.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SolanaSwift/BlockchainClient/Helpers/Int2X/UInt2X.swift b/Sources/SolanaSwift/BlockchainClient/Helpers/Int2X/UInt2X.swift index 9def12274..71819d7a5 100644 --- a/Sources/SolanaSwift/BlockchainClient/Helpers/Int2X/UInt2X.swift +++ b/Sources/SolanaSwift/BlockchainClient/Helpers/Int2X/UInt2X.swift @@ -1,4 +1,6 @@ +#if canImport(Accelerate) import Accelerate.vecLib +#endif import Foundation public typealias UInt1X = FixedWidthInteger & BinaryInteger & UnsignedInteger & Codable From ae4b5b6f846d809baeeff5ad6b5199106ae6c715 Mon Sep 17 00:00:00 2001 From: Nic Reich Date: Sat, 1 Aug 2026 17:52:23 -0400 Subject: [PATCH 4/8] fix: wrap URLSession/URLSessionWebSocketTask conformances behind platform guard in SocketInterfaces --- Sources/SolanaSwift/Socket/SocketInterfaces.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SolanaSwift/Socket/SocketInterfaces.swift b/Sources/SolanaSwift/Socket/SocketInterfaces.swift index 89e09d5b2..82eb8d24d 100644 --- a/Sources/SolanaSwift/Socket/SocketInterfaces.swift +++ b/Sources/SolanaSwift/Socket/SocketInterfaces.swift @@ -6,11 +6,13 @@ public protocol WebSocketTaskProvider { func createWebSocketTask(with url: URL) -> WebSocketTask } +#if canImport(FoundationNetworking) || canImport(Darwin) extension URLSession: WebSocketTaskProvider { public func createWebSocketTask(with url: URL) -> WebSocketTask { webSocketTask(with: url) } } +#endif /// Abstract websocket task, default is URLSessionWebSocketTask public protocol WebSocketTask { @@ -21,7 +23,9 @@ public protocol WebSocketTask { func sendPing(pongReceiveHandler: @escaping (Error?) -> Void) } +#if canImport(FoundationNetworking) || canImport(Darwin) extension URLSessionWebSocketTask: WebSocketTask {} +#endif /// Delegate for listening socket's events public protocol SolanaSocketEventsDelegate: AnyObject { From c483ca2e0eebd48f24ee222a5937022f5e577419 Mon Sep 17 00:00:00 2001 From: Nic Reich Date: Wed, 12 Aug 2026 09:21:34 -0400 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20swap=20Boilertalk/secp256k1.swift=20?= =?UTF-8?q?=E2=86=92=2021-DOT-DEV/swift-secp256k1=20(P256K)=20for=20libsec?= =?UTF-8?q?p256k1=20name=20collision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index ce1a6c617..afc6af6fd 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ let package = Package( ], dependencies: [ // Main depedencies - .package(url: "https://github.com/Boilertalk/secp256k1.swift.git", from: "0.1.0"), + .package(url: "https://github.com/21-DOT-DEV/swift-secp256k1.git", from: "0.1.0"), .package(url: "https://github.com/bitmark-inc/tweetnacl-swiftwrap.git", from: "1.0.2"), .package(url: "https://github.com/bigearsenal/task-retrying-swift.git", from: "2.0.0"), @@ -29,7 +29,7 @@ let package = Package( .target( name: "SolanaSwift", dependencies: [ - .product(name: "secp256k1", package: "secp256k1.swift"), + .product(name: "P256K", package: "swift-secp256k1"), .product(name: "TweetNacl", package: "tweetnacl-swiftwrap"), .product(name: "Task_retrying", package: "task-retrying-swift"), ] From 4a6c6da9518c2dd7f0c2872ed7a7297fbabf1c6b Mon Sep 17 00:00:00 2001 From: Nic Reich Date: Wed, 12 Aug 2026 09:26:29 -0400 Subject: [PATCH 6/8] fix: update CKSecp256k1.swift to import P256K (was secp256k1) --- Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift b/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift index 06bb16315..8e8b74f4a 100644 --- a/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift +++ b/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift @@ -1,5 +1,5 @@ import Foundation -import secp256k1 +import P256K struct CKSecp256k1 { /* From adeff78858f6b56d299538dd75a6184c3dea5950 Mon Sep 17 00:00:00 2001 From: Nic Reich Date: Wed, 12 Aug 2026 17:07:24 -0400 Subject: [PATCH 7/8] fix: correct P256K API usage in CKSecp256k1; fix JSONRPCAPIClient convenience init placement --- .../Networking/JSONRPCAPIClient.swift | 9 +- .../Helpers/AskCoin-HD/Keychain.swift | 3 + .../Helpers/CKSecp256k1.swift | 178 ++++-------------- 3 files changed, 48 insertions(+), 142 deletions(-) diff --git a/Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift b/Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift index 0fc2507bf..9f10935c8 100644 --- a/Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift +++ b/Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift @@ -10,11 +10,18 @@ public class JSONRPCAPIClient: SolanaAPIClient { public let endpoint: APIEndPoint private let networkManager: NetworkManager - public init(endpoint: APIEndPoint, networkManager: NetworkManager = URLSession(configuration: .default)) { + public init(endpoint: APIEndPoint, networkManager: NetworkManager) { + self.endpoint = endpoint self.networkManager = networkManager } + #if canImport(Darwin) + public convenience init(endpoint: APIEndPoint) { + self.init(endpoint: endpoint, networkManager: URLSession(configuration: .default)) + } + #endif + // MARK: - public func getTransaction( diff --git a/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift b/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift index 48ae2ec12..f41054c11 100644 --- a/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift +++ b/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift @@ -1,3 +1,6 @@ +#if canImport(CommonCrypto) +import CommonCrypto +#endif // // ASKKeychain.swift // AskCoin-HD diff --git a/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift b/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift index 8e8b74f4a..4774f4895 100644 --- a/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift +++ b/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift @@ -2,155 +2,51 @@ import Foundation import P256K struct CKSecp256k1 { - /* - + (NSData *)generatePublicKeyWithPrivateKey:(NSData *)privateKeyData compression:(BOOL)isCompression - { - secp256k1_context *context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - const unsigned char *prvKey = (const unsigned char *)privateKeyData.bytes; - secp256k1_pubkey pKey; + // MARK: - Public key from private key (called by Keychain.swift) + // Returns compressed (33-byte) or uncompressed (65-byte) pubkey bytes. - int result = secp256k1_ec_pubkey_create(context, &pKey, prvKey); - if (result != 1) { - return nil; - } - - int size = isCompression ? 33 : 65; - unsigned char *pubkey = malloc(size); - - size_t s = size; - - result = secp256k1_ec_pubkey_serialize(context, pubkey, &s, &pKey, isCompression ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED); - if (result != 1) { - return nil; - } - - secp256k1_context_destroy(context); - - NSMutableData *data = [NSMutableData dataWithBytes:pubkey length:size]; - free(pubkey); - return data; - } - */ - static func generatePublicKey(withPrivateKey privateKeyData: Data, compression isCompression: Bool) -> Data? { - let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN))! - let prvKey = privateKeyData.bytes - var pKey = secp256k1_pubkey() - - var result = secp256k1_ec_pubkey_create(context, &pKey, prvKey) - if result != 1 { - return nil - } - - let size = isCompression ? 33 : 65 - let pubkey = UnsafeMutablePointer.allocate(capacity: size) - var s = size - - result = secp256k1_ec_pubkey_serialize( - context, - pubkey, - &s, - &pKey, - UInt32(isCompression ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED) - ) - if result != 1 { - return nil + static func generatePublicKey(withPrivateKey privateKeyBytes: Data, + compression: Bool) throws -> Data { + let privKey = try P256K.Signing.PrivateKey(dataRepresentation: privateKeyBytes) + if compression { + return privKey.publicKey.compressedRepresentation + } else { + return privKey.publicKey.uncompressedRepresentation } - - secp256k1_context_destroy(context) - - let data = NSMutableData(bytes: pubkey, length: size).copy() - return data as? Data } - /* - + (NSData *)compactSignData:(NSData *)msgData withPrivateKey:(NSData *)privateKeyData - { - secp256k1_context *context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); - - const unsigned char *prvKey = (const unsigned char *)privateKeyData.bytes; - const unsigned char *msg = (const unsigned char *)msgData.bytes; - - unsigned char *siga = malloc(64); - secp256k1_ecdsa_signature sig; - int result = secp256k1_ecdsa_sign(context, &sig, msg, prvKey, NULL, NULL); - - result = secp256k1_ecdsa_signature_serialize_compact(context, siga, &sig); - - if (result != 1) { - return nil; - } - - secp256k1_context_destroy(context); - - NSMutableData *data = [NSMutableData dataWithBytes:siga length:64]; - free(siga); - return data; - } - */ - static func compactSignData(msgData: Data, withPrivateKey privateKeyData: Data) -> Data? { - let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN))! - let prvKey = privateKeyData.bytes - let msg = msgData.bytes - let siga = UnsafeMutablePointer.allocate(capacity: 64) - var sig = secp256k1_ecdsa_signature() + // MARK: - Sign + // Returns a 64-byte compact signature (r || s). - var result = secp256k1_ecdsa_sign(context, &sig, msg, prvKey, nil, nil) - result = secp256k1_ecdsa_signature_serialize_compact(context, siga, &sig) + static func sign(message: Data, with privateKeyBytes: Data) throws -> Data { + let privKey = try P256K.Signing.PrivateKey(dataRepresentation: privateKeyBytes) + let sig = try privKey.signature(for: message) + // compactRepresentation is 64 bytes (r || s big-endian) + return sig.compactRepresentation + } - if result != 1 { - return nil + // MARK: - Verify + + static func verify(signature signatureData: Data, + message messageData: Data, + publicKeyData: Data) throws -> Bool { + let pubKey: P256K.Signing.PublicKey + switch publicKeyData.count { + case 33: + pubKey = try P256K.Signing.PublicKey(dataRepresentation: publicKeyData, + format: .compressed) + case 65: + pubKey = try P256K.Signing.PublicKey(dataRepresentation: publicKeyData, + format: .uncompressed) + default: + throw CKSecp256k1Error.invalidPublicKeyLength } - - secp256k1_context_destroy(context) - - let data = NSMutableData(bytes: siga, length: 64).copy() - return data as? Data + let sig = try P256K.Signing.ECDSASignature(compactRepresentation: signatureData) + return pubKey.isValidSignature(sig, for: messageData) } +} - /* - + (NSInteger)verifySignedData:(NSData *)sigData withMessageData:(NSData *)msgData usePublickKey:(NSData *)pubKeyData - { - secp256k1_context *context = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); - - const unsigned char *sig = (const unsigned char *)sigData.bytes; - const unsigned char *msg = (const unsigned char *)msgData.bytes; - - const unsigned char *pubKey = (const unsigned char *)pubKeyData.bytes; - - secp256k1_pubkey pKey; - int pubResult = secp256k1_ec_pubkey_parse(context, &pKey, pubKey, pubKeyData.length); - if (pubResult != 1) return -3; - - secp256k1_ecdsa_signature sig_ecdsa; - int sigResult = secp256k1_ecdsa_signature_parse_compact(context, &sig_ecdsa, sig); - if (sigResult != 1) return -4; - - int result = secp256k1_ecdsa_verify(context, &sig_ecdsa, msg, &pKey); - - secp256k1_context_destroy(context); - return result; - }*/ - static func verifySignedData(sigData: Data, withMessageData msgData: Data, - usePublickKey pubKeyData: Data) -> Int32 - { - let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))! - let sig = sigData.bytes - let msg = msgData.bytes - - let pubKey = pubKeyData.bytes - var pKey = secp256k1_pubkey() - - let pubResult = secp256k1_ec_pubkey_parse(context, &pKey, pubKey, pubKeyData.count) - if pubResult != 1 { return -3 } - - var sig_ecdsa = secp256k1_ecdsa_signature() - let sigResult = secp256k1_ecdsa_signature_parse_compact(context, &sig_ecdsa, sig) - if sigResult != 1 { return -4 } - - let result = secp256k1_ecdsa_verify(context, &sig_ecdsa, msg, &pKey) - - secp256k1_context_destroy(context) - return result - } +enum CKSecp256k1Error: Error { + case invalidPublicKeyLength } From bdadc9cd402fc1ff347644ea743cbe4603ecb6ac Mon Sep 17 00:00:00 2001 From: Nic Reich Date: Wed, 12 Aug 2026 17:15:20 -0400 Subject: [PATCH 8/8] fix: use dataRepresentation for PublicKey; remove Darwin guard; add try in Keychain --- .../Networking/JSONRPCAPIClient.swift | 2 -- .../Helpers/AskCoin-HD/Keychain.swift | 2 +- .../Helpers/CKSecp256k1.swift | 22 +++++++------------ 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift b/Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift index 9f10935c8..312f6159a 100644 --- a/Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift +++ b/Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift @@ -16,11 +16,9 @@ public class JSONRPCAPIClient: SolanaAPIClient { self.networkManager = networkManager } - #if canImport(Darwin) public convenience init(endpoint: APIEndPoint) { self.init(endpoint: endpoint, networkManager: URLSession(configuration: .default)) } - #endif // MARK: - diff --git a/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift b/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift index f41054c11..12ec1c99a 100644 --- a/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift +++ b/Sources/SolanaSwift/BlockchainClient/Helpers/AskCoin-HD/Keychain.swift @@ -98,7 +98,7 @@ public class Keychain: NSObject { guard let prvKey = self.privateKey else { return nil } - return CKSecp256k1.generatePublicKey(withPrivateKey: prvKey, compression: true) + return try CKSecp256k1.generatePublicKey(withPrivateKey: prvKey, compression: true) }() // MARK: - Extended private key diff --git a/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift b/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift index 4774f4895..fe46bd347 100644 --- a/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift +++ b/Sources/SolanaSwift/BlockchainClient/Helpers/CKSecp256k1.swift @@ -3,26 +3,25 @@ import P256K struct CKSecp256k1 { - // MARK: - Public key from private key (called by Keychain.swift) + // MARK: - Public key from private key // Returns compressed (33-byte) or uncompressed (65-byte) pubkey bytes. static func generatePublicKey(withPrivateKey privateKeyBytes: Data, compression: Bool) throws -> Data { let privKey = try P256K.Signing.PrivateKey(dataRepresentation: privateKeyBytes) if compression { - return privKey.publicKey.compressedRepresentation + // format: .compressed → dataRepresentation returns 33 bytes + return privKey.publicKey.dataRepresentation } else { return privKey.publicKey.uncompressedRepresentation } } - // MARK: - Sign - // Returns a 64-byte compact signature (r || s). + // MARK: - Sign — returns 64-byte compact (r || s) static func sign(message: Data, with privateKeyBytes: Data) throws -> Data { let privKey = try P256K.Signing.PrivateKey(dataRepresentation: privateKeyBytes) let sig = try privKey.signature(for: message) - // compactRepresentation is 64 bytes (r || s big-endian) return sig.compactRepresentation } @@ -31,17 +30,12 @@ struct CKSecp256k1 { static func verify(signature signatureData: Data, message messageData: Data, publicKeyData: Data) throws -> Bool { - let pubKey: P256K.Signing.PublicKey - switch publicKeyData.count { - case 33: - pubKey = try P256K.Signing.PublicKey(dataRepresentation: publicKeyData, - format: .compressed) - case 65: - pubKey = try P256K.Signing.PublicKey(dataRepresentation: publicKeyData, - format: .uncompressed) - default: + let format: P256K.Format = publicKeyData.count == 33 ? .compressed : .uncompressed + guard publicKeyData.count == 33 || publicKeyData.count == 65 else { throw CKSecp256k1Error.invalidPublicKeyLength } + let pubKey = try P256K.Signing.PublicKey(dataRepresentation: publicKeyData, + format: format) let sig = try P256K.Signing.ECDSASignature(compactRepresentation: signatureData) return pubKey.isValidSignature(sig, for: messageData) }