diff --git a/Package.resolved b/Package.resolved index 2e4f946b..d2f6d516 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "Alamofire", - "repositoryURL": "https://github.com/Alamofire/Alamofire.git", - "state": { - "branch": null, - "revision": "eaf6e622dd41b07b251d8f01752eab31bc811493", - "version": "5.4.1" - } - }, { "package": "BigInt", "repositoryURL": "https://github.com/attaswift/BigInt", @@ -24,8 +15,8 @@ "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", "state": { "branch": null, - "revision": "5669f222e46c8134fb1f399c745fa6882b43532e", - "version": "1.3.8" + "revision": "12f2389aca4a07e0dd54c86ec23d0721ed88b8db", + "version": "1.4.3" } }, { @@ -37,15 +28,6 @@ "version": "1.0.2" } }, - { - "package": "RxSwift", - "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", - "state": { - "branch": null, - "revision": "7e01c05f25c025143073eaa3be3532f9375c614b", - "version": "6.1.0" - } - }, { "package": "SwiftProtobuf", "repositoryURL": "https://github.com/apple/swift-protobuf.git", diff --git a/Package.swift b/Package.swift index a3d68bca..1b127d3b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,11 +1,11 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.5 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "Zesame", - platforms: [.macOS(.v10_15), .iOS(.v13)], + platforms: [.macOS(.v12), .iOS(.v15)], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( @@ -15,17 +15,15 @@ let package = Package( dependencies: [ // Dependencies declare other packages that this package depends on. .package(url: "https://github.com/Sajjon/EllipticCurveKit.git", .upToNextMinor(from: "1.0.2")), - .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.3.8")), - .package(url: "https://github.com/ReactiveX/RxSwift.git", .exact("6.1.0")), - .package(name: "SwiftProtobuf", url: "https://github.com/apple/swift-protobuf.git", from: "1.6.0"), - .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.0")), + .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.4.3")), + .package(name: "SwiftProtobuf", url: "https://github.com/apple/swift-protobuf.git", from: "1.6.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "Zesame", - dependencies: ["EllipticCurveKit", "CryptoSwift", "RxSwift", "SwiftProtobuf", "Alamofire"], + dependencies: ["EllipticCurveKit", "CryptoSwift", "SwiftProtobuf"], exclude: ["Models/Protobuf/messages.proto"] ), .testTarget( diff --git a/README.md b/README.md index dc42d456..c7f4aef1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ +[![Language](https://img.shields.io/static/v1.svg?label=language&message=Swift%205%2E5&color=FA7343&logo=swift&style=flat-square)](https://swift.org) +[![Platform](https://img.shields.io/static/v1.svg?label=platforms&message=iOS%2015%20|%20macOS%2012&style=flat-square)](https://apple.com) +[![License](https://img.shields.io/cocoapods/l/Crossroad.svg?style=flat-square)](https://github.com/OpenZesame/Zesame/blob/main/LICENSE) + + # Zesame -Zesame is an *unofficial* Swift SDK for Zilliqa. It is written in Swift 5.3. This SDK contains cryptographic methods allowing you to create and restore a wallet, sign and broadcast transactions. The cryptographic methods are implemented in [EllipticCurveKit](https://github.com/Sajjon/EllipticCurveKit). This SDK uses Zilliqas [JSON-RPC API](https://apidocs.zilliqa.com/#introduction) +Zesame is an *unofficial* Swift SDK for Zilliqa. It is written in Swift 5.5. This SDK contains cryptographic methods allowing you to create and restore a wallet, sign and broadcast transactions. The cryptographic methods are implemented in [EllipticCurveKit][eck]. This SDK uses Zilliqas [JSON-RPC API](https://apidocs.zilliqa.com/#introduction) # Getting started @@ -47,47 +52,37 @@ Add the generated file `messages.pb.swift` to `Models` folder. You will find all dependencies inside the [Package.swift](https://github.com/OpenZesame/Zesame/blob/main/Package.swift), but to mention the most important: ## EllipticCurveKit -Zesame is dependent on the Elliptic Curve Cryptography of [EllipticCurveKit]((https://github.com/Sajjon/EllipticCurveKit)), for the generation of new wallets, restoration of existing ones, the encryption of your private keys into keystores and the signing of your transactions using [Schnorr Signatures](https://en.wikipedia.org/wiki/Schnorr_signature). +Zesame is dependent on the Elliptic Curve Cryptography of [EllipticCurveKit][eck], for the generation of new wallets, restoration of existing ones, the encryption of your private keys into keystores and the signing of your transactions using [Schnorr Signatures](https://en.wikipedia.org/wiki/Schnorr_signature). ## Other -- [RxSwift](https://github.com/ReactiveX/RxSwift): The library uses RxSwift for async programming. - - [JSONRPCKit](https://github.com/ollitapa/JSONRPCKit): For consuming the Zilliqa JSON-RPC API. # API -## Closure or Rx -This SDK contains two implementations for each method, one that uses [Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html)(a.k.a. "Callbacks") and one implementation using [RxSwift Observables](https://github.com/ReactiveX/RxSwift). -### Rx -```swift -DefaultZilliqaService.shared.rx.getBalance(for: address).subscribe( - onNext: { print("Balance: \($0.balance)") }, - onError: { print("Failed to get balance, error: \($0)") } -).disposed(by: bag) -``` +Have a look at [ZilliqaService.swift](https://github.com/OpenZesame/Zesame/blob/main/Sources/Zesame/Services/ZilliqaService.swift) for an overview of the functions, here is a snapshot of the current functions of the API: -### Closure ```swift -DefaultZilliqaService.shared.getBalance(for: address) { - switch $0 { - case .success(let balanceResponse): print("Balance: \(balanceResponse.balance)") - case .failure(let error): print("Failed to get balance, error: \(error)") - } +public protocol ZilliqaService: AnyObject { + + func getNetworkFromAPI() async throws -> NetworkResponse + func getMinimumGasPrice(alsoUpdateLocallyCachedMinimum: Bool) async throws -> MinimumGasPriceResponse + func verifyThat(encryptionPassword: String, canDecryptKeystore: Keystore) async throws -> Bool + + func createNewKeystore(encryptionPassword: String, kdf: KDF, kdfParams: KDFParams?) async throws -> Keystore + func restoreKeystore(from restoration: KeyRestoration) async throws -> Keystore + + func exportKeystore(privateKey: PrivateKey, encryptWalletBy password: String) async throws -> Keystore + func extractKeyPairFrom(keystore: Keystore, encryptedBy password: String) async throws -> KeyPair + + func send(transaction: SignedTransaction) async throws -> TransactionResponse + func getBalance(for address: LegacyAddress) async throws -> BalanceResponse + func sendTransaction(for payment: Payment, keystore: Keystore, password: String, network: Network) async throws -> TransactionResponse + func sendTransaction(for payment: Payment, signWith keyPair: KeyPair, network: Network) async throws -> TransactionResponse + + func hasNetworkReachedConsensusYetForTransactionWith(id: String, polling: Polling) async throws -> TransactionReceipt } -``` - -## Functions -Have a look at [ZilliqaService.swift](https://github.com/OpenZesame/Zesame/blob/main/Sources/Zesame/Services/ZilliqaService.swift) for an overview of the functions, here is a snapshot of the current functions of the reactive API (each function having a closure counterpart): -```swift -public protocol ZilliqaServiceReactive { - func createNewWallet() -> Observable - func exportKeystore(from wallet: Wallet, encryptWalletBy passphrase: String) -> Observable - func importWalletFrom(keyStore: Keystore, encryptedBy passphrase: String) -> Observable - func getBalance(for address: Address) -> Observable - func sendTransaction(for payment: Payment, signWith keyPair: KeyPair) -> Observable -} ``` # Explorer @@ -106,3 +101,5 @@ This SDK and the foundation EllipticCurveKit its built upon has been developed b # License **Zesame** is released under the [MIT License](LICENSE). + +[eck]: https://github.com/Sajjon/EllipticCurveKit diff --git a/Sources/Zesame/Cryptography/DerivedKey.swift b/Sources/Zesame/Cryptography/DerivedKey.swift index 0277ec92..7dd9eb6e 100644 --- a/Sources/Zesame/Cryptography/DerivedKey.swift +++ b/Sources/Zesame/Cryptography/DerivedKey.swift @@ -24,7 +24,6 @@ import Foundation import EllipticCurveKit -import CryptoSwift public struct DerivedKey { public let data: Data diff --git a/Sources/Zesame/Extensions/CryptoSwift+Scrypt+KDFParams.swift b/Sources/Zesame/Extensions/CryptoSwift+Scrypt+KDFParams.swift index bfc27b96..2215859c 100644 --- a/Sources/Zesame/Extensions/CryptoSwift+Scrypt+KDFParams.swift +++ b/Sources/Zesame/Extensions/CryptoSwift+Scrypt+KDFParams.swift @@ -23,9 +23,7 @@ // import Foundation -import CryptoSwift - -public typealias Scrypt = CryptoSwift.Scrypt +@_exported import class CryptoSwift.Scrypt public extension Scrypt { diff --git a/Sources/Zesame/Models/Manual/ExpressibleByAmount/ExpressibleByAmount+UnitConversion.swift b/Sources/Zesame/Models/Manual/ExpressibleByAmount/ExpressibleByAmount+UnitConversion.swift index 4a2df29e..71df0b1a 100644 --- a/Sources/Zesame/Models/Manual/ExpressibleByAmount/ExpressibleByAmount+UnitConversion.swift +++ b/Sources/Zesame/Models/Manual/ExpressibleByAmount/ExpressibleByAmount+UnitConversion.swift @@ -63,7 +63,7 @@ internal extension ExpressibleByAmount { decimalValueInTargetUnit = qaFittingInDecimal } else { fatalError("forgot case?") } - // Only round when for UnitX -> UnitX decimal value representation, i.e. ZilAmount(0.51) expressed in Zil rounds to 1.0 + // Only round when for UnitX -> UnitX decimal value representation, i.e. Amount(0.51) expressed in Zil rounds to 1.0 let tryToPerformRounding = targetUnit.exponent == self.unit.exponent if tryToPerformRounding, let rounding = rounding { diff --git a/Sources/Zesame/Models/Manual/KeyRestoration.swift b/Sources/Zesame/Models/Manual/KeyRestoration.swift index 468386ed..b0453aa7 100644 --- a/Sources/Zesame/Models/Manual/KeyRestoration.swift +++ b/Sources/Zesame/Models/Manual/KeyRestoration.swift @@ -25,7 +25,7 @@ import Foundation public enum KeyRestoration { - case privateKey(PrivateKey, encryptBy: String, kdf: KDF) + case privateKey(PrivateKey, encryptBy: String, kdf: KDF, kdfParams: KDFParams? = nil) case keystore(Keystore, password: String) } diff --git a/Sources/Zesame/Models/Manual/Payment/ZilAmount.swift b/Sources/Zesame/Models/Manual/Payment/Amount.swift similarity index 92% rename from Sources/Zesame/Models/Manual/Payment/ZilAmount.swift rename to Sources/Zesame/Models/Manual/Payment/Amount.swift index e0d2060d..01653556 100644 --- a/Sources/Zesame/Models/Manual/Payment/ZilAmount.swift +++ b/Sources/Zesame/Models/Manual/Payment/Amount.swift @@ -24,7 +24,7 @@ import Foundation -public struct ZilAmount: ExpressibleByAmount, Upperbound, Lowerbound { +public struct Amount: ExpressibleByAmount, Upperbound, Lowerbound, Hashable { public typealias Magnitude = Zil.Magnitude @@ -36,6 +36,6 @@ public struct ZilAmount: ExpressibleByAmount, Upperbound, Lowerbound { public let qa: Magnitude public init(qa: Magnitude) throws { - self.qa = try ZilAmount.validate(value: qa) + self.qa = try Amount.validate(value: qa) } } diff --git a/Sources/Zesame/Models/Manual/Payment/Payment.swift b/Sources/Zesame/Models/Manual/Payment/Payment.swift index faca6364..e79d675f 100644 --- a/Sources/Zesame/Models/Manual/Payment/Payment.swift +++ b/Sources/Zesame/Models/Manual/Payment/Payment.swift @@ -26,14 +26,14 @@ import Foundation public struct Payment { public let recipient: LegacyAddress - public let amount: ZilAmount + public let amount: Amount public let gasLimit: GasLimit public let gasPrice: GasPrice public let nonce: Nonce public init( to recipient: LegacyAddress, - amount: ZilAmount, + amount: Amount, gasLimit: GasLimit = .defaultGasLimit, gasPrice: GasPrice, nonce: Nonce = 0 @@ -57,7 +57,7 @@ public extension Payment { static func withMinimumGasLimit( to recipient: LegacyAddress, - amount: ZilAmount, + amount: Amount, gasPrice: GasPrice, nonce: Nonce = 0 ) -> Self { @@ -69,15 +69,15 @@ public extension Payment { return Qa(qa: Qa.Magnitude(gasLimit) * gasPrice.qa) } - static func estimatedTotalCostOfTransaction(amount: ZilAmount, gasPrice: GasPrice, gasLimit: GasLimit = .defaultGasLimit) throws -> ZilAmount { + static func estimatedTotalCostOfTransaction(amount: Amount, gasPrice: GasPrice, gasLimit: GasLimit = .defaultGasLimit) throws -> Amount { let fee = try estimatedTotalTransactionFee(gasPrice: gasPrice, gasLimit: gasLimit) let amountInQa = amount.asQa let totalInQa: Qa = amountInQa + fee - let tooLargeError = AmountError.tooLarge(max: ZilAmount.max) + let tooLargeError = AmountError.tooLarge(max: Amount.max) - guard totalInQa < ZilAmount.max.asQa else { + guard totalInQa < Amount.max.asQa else { throw tooLargeError } @@ -86,12 +86,12 @@ public extension Payment { // 21E9 so we lose the 1E-12 part. Thus we subtract 21E9 // to be able to keep the 1E-12 part and compare it against // the transaction cost. Ugly, but it works... - if totalInQa == ZilAmount.max.asQa { - let subtractedTotalSupplyFromAmount = totalInQa - ZilAmount.max.asQa + if totalInQa == Amount.max.asQa { + let subtractedTotalSupplyFromAmount = totalInQa - Amount.max.asQa if (subtractedTotalSupplyFromAmount + fee) != fee { throw tooLargeError } } - return try ZilAmount(qa: totalInQa) + return try Amount(qa: totalInQa) } } diff --git a/Sources/Zesame/Models/Manual/Wallet/Address/Address/Address.swift b/Sources/Zesame/Models/Manual/Wallet/Address/Address/Address.swift index dd7eb38a..6850dba2 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Address/Address/Address.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Address/Address/Address.swift @@ -27,7 +27,7 @@ import Foundation public enum Address: AddressChecksummedConvertible, StringConvertible, - Equatable, + Hashable, ExpressibleByStringLiteral { diff --git a/Sources/Zesame/Models/Manual/Wallet/Address/Bech32/Bech32Address.swift b/Sources/Zesame/Models/Manual/Wallet/Address/Bech32/Bech32Address.swift index f9b953cd..04ab720a 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Address/Bech32/Bech32Address.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Address/Bech32/Bech32Address.swift @@ -28,7 +28,7 @@ import Foundation public struct Bech32Address: AddressChecksummedConvertible, StringConvertible, - Equatable, + Hashable, ExpressibleByStringLiteral { @@ -126,7 +126,7 @@ public extension Bech32Address { } public extension Bech32Address { - struct DataPart: Equatable, CustomStringConvertible { + struct DataPart: Hashable, CustomStringConvertible { public let excludingChecksum: Bech32Data? public let checksum: Bech32Data @@ -159,7 +159,7 @@ public extension Bech32Address.DataPart { } public extension Bech32Address.DataPart { - struct Bech32Data: Equatable, CustomStringConvertible { + struct Bech32Data: Hashable, CustomStringConvertible { public let data: Data public init(_ data: Data) { diff --git a/Sources/Zesame/Models/Manual/Wallet/Address/HexString/HexString.swift b/Sources/Zesame/Models/Manual/Wallet/Address/HexString/HexString.swift index 7a004f02..e5ac7e4f 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Address/HexString/HexString.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Address/HexString/HexString.swift @@ -33,7 +33,7 @@ extension CharacterSet { } } -public struct HexString { +public struct HexString: Hashable { public let value: String init(_ value: String) throws { diff --git a/Sources/Zesame/Models/Manual/Wallet/Address/LegacyAddress/LegacyAddress.swift b/Sources/Zesame/Models/Manual/Wallet/Address/LegacyAddress/LegacyAddress.swift index f68506d1..f0789a86 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Address/LegacyAddress/LegacyAddress.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Address/LegacyAddress/LegacyAddress.swift @@ -26,7 +26,7 @@ import Foundation import EllipticCurveKit /// Checksummed legacy Ethereum style address, looking like this: `F510333720c5Dd3c3C08bC8e085e8c981ce74691` can also be instantiated with a prefix of `0x`, like so: `0xF510333720c5Dd3c3C08bC8e085e8c981ce74691` -public struct LegacyAddress: AddressChecksummedConvertible, HexStringConvertible, Equatable { +public struct LegacyAddress: AddressChecksummedConvertible, HexStringConvertible, Hashable { /// Checksummed hexstring representing the legacy Ethereum style address, e.g. `F510333720c5Dd3c3C08bC8e085e8c981ce74691` public let checksummed: HexString diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/AnyKeyDeriving.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/AnyKeyDeriving.swift index 9f1a98cc..45f551a8 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/AnyKeyDeriving.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/AnyKeyDeriving.swift @@ -23,7 +23,6 @@ // import Foundation -import CryptoSwift public struct AnyKeyDeriving: KeyDeriving { private let kdf: KDF @@ -33,15 +32,17 @@ public struct AnyKeyDeriving: KeyDeriving { self.kdfParams = kdfParams } - public func deriveKey(password: String, done: @escaping (DerivedKey) throws -> Void) throws { - let data: Data - switch kdf { - case .pbkdf2: - data = Data(try PBKDF2(kdfParams: kdfParams, password: password).calculate()) - case .scrypt: - data = Data(try Scrypt(kdfParams: kdfParams, password: password).calculate()) - } - let derivedKey = DerivedKey(data: data) - try done(derivedKey) + public func deriveKey(password: String) async throws -> DerivedKey { + try await Task(priority: .userInitiated) { + let data: Data + switch kdf { + case .pbkdf2: + data = Data(try PBKDF2(kdfParams: kdfParams, password: password).calculate()) + case .scrypt: + data = Data(try Scrypt(kdfParams: kdfParams, password: password).calculate()) + } + let derivedKey = DerivedKey(data: data) + return derivedKey + }.value } } diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDerivationFunction.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDerivationFunction.swift index 9d0b890b..ba7d6f45 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDerivationFunction.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDerivationFunction.swift @@ -25,19 +25,13 @@ import Foundation public typealias KDF = KeyDerivationFunction -public enum KeyDerivationFunction: String, Codable { - public static var `default`: KDF = .scrypt +public enum KeyDerivationFunction: String, Codable, Hashable { + public static var `default`: Self = .scrypt case scrypt case pbkdf2 } public extension KDF { - static var defaultParameters: KDFParams { - do { - return try KDFParams() - } catch { - fatalError("Incorrect implementation, should always be able to create default KDF params, unexpected error: \(error)") - } - } + static var defaultParameters: KDFParams = .default } diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDerivationFunctionParameters.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDerivationFunctionParameters.swift index 67bc1cea..c35e6ca0 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDerivationFunctionParameters.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDerivationFunctionParameters.swift @@ -29,7 +29,7 @@ public typealias KDFParams = KDF.Parameters public extension KDF { /// Same default values for parameters used by Zilliqa Javascript SDK: https://github.com/Zilliqa/Zilliqa-JavaScript-Library/blob/dev/packages/zilliqa-js-crypto/src/keystore.ts#L77-L82 - struct Parameters: Codable, Equatable { + struct Parameters: Codable, Hashable { /// "N", CPU/memory cost parameter, must be power of 2. let costParameterN: Int let costParameterC: Int @@ -48,14 +48,15 @@ public extension KDF { return Data(hex: saltHex) } - init( + public init( costParameterN: Int = 8192, costParameterC: Int = 262144, blockSize: Int = 8, parallelizationParameter: Int = 1, lengthOfDerivedKey: Int = 32, saltHex: String? = nil - ) throws { + ) throws { + self.costParameterN = costParameterN self.costParameterC = costParameterC self.blockSize = blockSize @@ -88,7 +89,7 @@ public extension KDF { } } -extension KDFParams { +extension KDF.Parameters { enum CodingKeys: String, CodingKey { /// Should be lowercase "n", since that is what Zilliqa JS SDK uses case costParameterN = "n" @@ -100,3 +101,7 @@ extension KDFParams { case saltHex = "salt" } } + +public extension KDF.Parameters { + static let `default`: Self = try! .init() +} diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDeriving.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDeriving.swift index e99bfd0d..c35d548f 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDeriving.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/KeyDeriving.swift @@ -25,5 +25,5 @@ import Foundation public protocol KeyDeriving { - func deriveKey(password: String, done: @escaping (DerivedKey) throws -> Void) throws + func deriveKey(password: String) async throws -> DerivedKey } diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Crypto.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Crypto.swift index c5649d11..48408d61 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Crypto.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Crypto.swift @@ -23,12 +23,11 @@ // import Foundation -import CryptoSwift import EllipticCurveKit public extension Keystore { - struct Crypto: Codable, Equatable { - public struct CipherParameters: Codable, Equatable { + struct Crypto: Codable, Hashable { + public struct CipherParameters: Codable, Hashable { /// "iv" let initializationVectorHex: String var initializationVector: Data { @@ -93,7 +92,7 @@ public extension Keystore { kdf: KDF, kdfParams: KDFParams, messageAuthenticationCodeHex: String - ) throws { + ) throws { guard encryptedPrivateKeyHex.count == Crypto.expectedLengthEncryptedPrivateKeyHex else { throw Error.encryptedPrivateKeyHexIncorrectLength(expectedLength: Crypto.expectedLengthEncryptedPrivateKeyHex, butGot: encryptedPrivateKeyHex.count) diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+KeyDeriving.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+KeyDeriving.swift index e408f353..297734f9 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+KeyDeriving.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+KeyDeriving.swift @@ -25,9 +25,10 @@ import Foundation extension Keystore: KeyDeriving { - public func deriveKey(password: String, done: @escaping (DerivedKey) throws -> Void) throws { - let kdf = self.crypto.kdf - let kdfParams = self.crypto.keyDerivationFunctionParameters - try AnyKeyDeriving(kdf: kdf, kdfParams: kdfParams).deriveKey(password: password, done: done) + public func deriveKey(password: String) async throws -> DerivedKey { + try await AnyKeyDeriving( + kdf: crypto.kdf, + kdfParams: crypto.keyDerivationFunctionParameters + ).deriveKey(password: password) } } diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Wallet+Export.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Wallet+Export.swift index e26b7e96..102afcd5 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Wallet+Export.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Wallet+Export.swift @@ -25,39 +25,33 @@ import Foundation import EllipticCurveKit -import CryptoSwift - public extension Keystore { static func from( privateKey: PrivateKey, encryptBy password: String, kdf: KDF, - kdfParams: KDFParams? = nil, - done: @escaping Done - ) throws { + kdfParams: KDFParams = KDF.defaultParameters + ) async throws -> Keystore { guard password.count >= Keystore.minumumPasswordLength else { - let error = Error.keystorePasswordTooShort( + throw Error.keystorePasswordTooShort( provided: password.count, minimum: Keystore.minumumPasswordLength ) - done(.failure(error)) - return } - let kdfParams = kdfParams ?? KDF.defaultParameters - - try AnyKeyDeriving(kdf: kdf, kdfParams: kdfParams).deriveKey(password: password) { derivedKey in - - let keyStore = try Keystore( - from: derivedKey, - privateKey: privateKey, - kdf: kdf, - parameters: kdfParams - ) - - done(Result.success(keyStore)) - } + let derivedKey = try await AnyKeyDeriving( + kdf: kdf, + kdfParams: kdfParams + ).deriveKey(password: password) + + return try Keystore( + from: derivedKey, + privateKey: privateKey, + kdf: kdf, + parameters: kdfParams + ) + } } diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Wallet+Import.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Wallet+Import.swift index 52d97176..0339a0ab 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Wallet+Import.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore+Wallet+Import.swift @@ -23,31 +23,26 @@ // import Foundation -import CryptoSwift +import class CryptoSwift.HMAC +import class CryptoSwift.AES +import struct CryptoSwift.CTR +import enum CryptoSwift.Padding import EllipticCurveKit public extension Keystore { - func toKeypair(encryptedBy password: String, done: @escaping Done) { - decryptPrivateKeyWith(password: password) { - switch $0 { - case .failure(let error): done(Result.failure(error)) - case .success(let privateKey): - let keyPair = KeyPair(private: privateKey) - done(.success(keyPair)) - } - } + func toKeypair(encryptedBy password: String) async throws -> KeyPair { + let privateKey = try await decryptPrivateKeyWith(password: password) + return KeyPair(private: privateKey) } - func decryptPrivateKeyWith(password: String, done: @escaping Done) { + func decryptPrivateKeyWith(password: String) async throws -> PrivateKey { guard password.count >= Keystore.minumumPasswordLength else { - let error = Error.keystorePasswordTooShort( + throw Error.keystorePasswordTooShort( provided: password.count, minimum: Keystore.minumumPasswordLength ) - done(.failure(error)) - return } let encryptedPrivateKey = crypto.encryptedPrivateKey @@ -55,26 +50,28 @@ public extension Keystore { let cipher = crypto.cipherType let expectedMAC = crypto.messageAuthenticationCodeHex.uppercased() do { - try deriveKey(password: password) { derivedKey in - let MAC = try calculateMac(derivedKey: derivedKey, encryptedPrivateKey: encryptedPrivateKey, iv: iv, cipherType: cipher).asHex.uppercased() - - guard MAC == expectedMAC else { - let error = Error.walletImport(.incorrectPassword) - done(.failure(error)) - return - } - - let aesCtr = try makeAesCtr(derivedKey: derivedKey, iv: iv) - let decryptedPrivateKey = try aesCtr.decrypt(Array(encryptedPrivateKey)).asHex - guard let privateKey = PrivateKey(hex: decryptedPrivateKey) else { - let error = Error.walletImport(.badPrivateKeyHex) - done(.failure(error)) - return - } - done(.success(privateKey)) + + let derivedKey = try await deriveKey(password: password) + + let MAC = try calculateMac( + derivedKey: derivedKey, + encryptedPrivateKey: encryptedPrivateKey, + iv: iv, + cipherType: cipher + ).asHex.uppercased() + + guard MAC == expectedMAC else { + throw Error.walletImport(.incorrectPassword) + } + + let aesCtr = try makeAesCtr(derivedKey: derivedKey, iv: iv) + let decryptedPrivateKey = try aesCtr.decrypt(Array(encryptedPrivateKey)).asHex + guard let privateKey = PrivateKey(hex: decryptedPrivateKey) else { + throw Error.walletImport(.badPrivateKeyHex) } + return privateKey } catch { - done(.failure(Error.decryptPrivateKey(error))) + throw Error.decryptPrivateKey(error) } } } @@ -91,7 +88,7 @@ private func calculateMac( return try HMAC( key: derivedKey.bytes, - variant: .sha256 + variant: .sha2(.sha256) ).authenticate( ((derivedKey.asData.suffix(16) + encryptedPrivateKey + iv + algo) as DataConvertible).bytes ).asData diff --git a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore.swift b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore.swift index 96d1408f..186e5db9 100644 --- a/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore.swift +++ b/Sources/Zesame/Models/Manual/Wallet/Keystore/Keystore.swift @@ -24,9 +24,8 @@ import Foundation import EllipticCurveKit -import CryptoSwift -public struct Keystore: Codable, Equatable { +public struct Keystore: Codable, Hashable { public static let minumumPasswordLength = 8 public let address: LegacyAddress diff --git a/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+Codable.swift b/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+Codable.swift deleted file mode 100644 index 12de1c91..00000000 --- a/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+Codable.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// MIT License -// -// Copyright (c) 2018-2019 Open Zesame (https://github.com/OpenZesame) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - -extension Wallet: Encodable { - public enum CodingKeys: String, CodingKey { - case keystore, address - } - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(keystore, forKey: .keystore) - try container.encode(address, forKey: .address) - } -} - -extension Wallet: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.keystore = try container.decode(Keystore.self, forKey: .keystore) - self.address = try container.decode(LegacyAddress.self, forKey: .address) - } -} diff --git a/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+CustomStringConvertible.swift b/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+CustomStringConvertible.swift deleted file mode 100644 index a902c996..00000000 --- a/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+CustomStringConvertible.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// MIT License -// -// Copyright (c) 2018-2019 Open Zesame (https://github.com/OpenZesame) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - -extension Wallet: CustomStringConvertible {} -public extension Wallet { - var description: String { - return "Wallet(address: '\(address)')" - } -} - diff --git a/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+Decrypt.swift b/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+Decrypt.swift deleted file mode 100644 index 70cc3099..00000000 --- a/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet+Decrypt.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// MIT License -// -// Copyright (c) 2018-2019 Open Zesame (https://github.com/OpenZesame) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - -public extension Wallet { - func decrypt(password: String, done: @escaping Done) { - keystore.toKeypair(encryptedBy: password, done: done) - } -} diff --git a/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet.swift b/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet.swift deleted file mode 100644 index 835e5c72..00000000 --- a/Sources/Zesame/Models/Manual/Wallet/Wallet/Wallet.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// MIT License -// -// Copyright (c) 2018-2019 Open Zesame (https://github.com/OpenZesame) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -public struct Wallet { - public let keystore: Keystore - public let address: LegacyAddress - - public init(keystore: Keystore) { - self.keystore = keystore - self.address = keystore.address - } -} diff --git a/Sources/Zesame/Networking/APIClient/APIClient.swift b/Sources/Zesame/Networking/APIClient/APIClient.swift index 7aecf1c9..e0e85fe7 100644 --- a/Sources/Zesame/Networking/APIClient/APIClient.swift +++ b/Sources/Zesame/Networking/APIClient/APIClient.swift @@ -22,8 +22,6 @@ // SOFTWARE. // -public typealias Done = (Swift.Result) -> Void - public protocol APIClient { - func send(method: RPCMethod, done: @escaping Done) where ResultFromResponse: Decodable + func send(method: RPCMethod) async throws -> T } diff --git a/Sources/Zesame/Networking/APIClient/DefaultAPIClient.swift b/Sources/Zesame/Networking/APIClient/DefaultAPIClient.swift index ebbe798b..3f609ba3 100644 --- a/Sources/Zesame/Networking/APIClient/DefaultAPIClient.swift +++ b/Sources/Zesame/Networking/APIClient/DefaultAPIClient.swift @@ -23,18 +23,15 @@ // import Foundation -import Alamofire public final class DefaultAPIClient: APIClient { - - private let session: Alamofire.Session - private unowned let interceptor: SimpleRequestAdapter - + private let session: URLSession + private let baseURL: URL + private let jsonDecoder = JSONDecoder() public init(baseURL: URL) { - let interceptor = SimpleRequestAdapter(baseURL: baseURL) - self.session = Alamofire.Session(interceptor: interceptor) - self.interceptor = interceptor + self.session = URLSession(configuration: .default) + self.baseURL = baseURL } } @@ -44,67 +41,31 @@ public extension DefaultAPIClient { } } -public extension DefaultAPIClient { - var baseURL: URL { interceptor.baseURL } -} -// MARK: - RequestInterceptor (RequestAdapter) -private extension DefaultAPIClient { - - final class SimpleRequestAdapter: RequestInterceptor { - fileprivate let baseURL: URL - init(baseURL: URL) { - self.baseURL = baseURL - } - - func adapt( - _ urlRequest: URLRequest, - for session: Alamofire.Session, - completion: @escaping (Result) -> Void - ) { - var urlRequest = urlRequest - if urlRequest.url?.absoluteString.isEmpty == true || urlRequest.url?.absoluteString == "/" { - urlRequest.url = baseURL - } - completion(.success(urlRequest)) - } - - func retry( - _ request: Alamofire.Request, - for session: Alamofire.Session, - dueTo error: Swift.Error, - completion: @escaping (Alamofire.RetryResult) -> Void - ) { - completion(.doNotRetry) - } - - } -} // MARK: - APIClient public extension DefaultAPIClient { - func send( - method: RPCMethod, - done: @escaping Done - ) where ResultFromResponse: Decodable { + enum HTTPError: Swift.Error { + case badStatusCode(expected: Int, butGot: Int) + } + + func send(method: RPCMethod) async throws -> T { let rpcRequest = RPCRequest(method: method) - - session.request(rpcRequest) - .validate() - .responseDecodable { (response: DataResponse, AFError>) in - switch response.result { - case .success(let successOrRPCError): - switch successOrRPCError { - case .rpcError(let rpcErrorOrDecodeToRPCErrorMetaError): - done(.failure(.api(.request(rpcErrorOrDecodeToRPCErrorMetaError)))) - case .rpcSuccess(let resultFromResponse): - done(.success(resultFromResponse)) + let urlRequest = try rpcRequest.asURLRequest(baseURL: baseURL) + do { + let (jsondata, response) = try await session.data(for: urlRequest) + if let httpURLResponse = response as? HTTPURLResponse { + let ok = 200 + if httpURLResponse.statusCode != ok { + let error = HTTPError.badStatusCode(expected: ok, butGot: httpURLResponse.statusCode) + throw Error.api(.request(error)) } - case .failure(let error): - done(.failure(.api(.request(error)))) } + return try jsonDecoder.decode(T.self, from: jsondata) + } catch { + throw Error.api(.request(error)) } } } diff --git a/Sources/Zesame/Networking/JSONRPC/RPCRequest.swift b/Sources/Zesame/Networking/JSONRPC/RPCRequest.swift index 58252d01..c7da6163 100644 --- a/Sources/Zesame/Networking/JSONRPC/RPCRequest.swift +++ b/Sources/Zesame/Networking/JSONRPC/RPCRequest.swift @@ -23,9 +23,8 @@ // import Foundation -import Alamofire -public struct RPCRequest: Encodable, URLRequestConvertible { +public struct RPCRequest: Encodable { public let rpcMethod: String private let _encodeValue: RPCMethod.EncodeValue? public let requestId: String @@ -66,31 +65,31 @@ public extension RPCRequest { } } -// MARK: - URLRequestConvertible public extension RPCRequest { - func asURLRequest() throws -> URLRequest { + + + + func asURLRequest(baseURL: URL) throws -> URLRequest { var components = URLComponents() components.path = "/" - guard let url = components.url else { - preconditionFailure("Failed to construct URL") + guard let relativeURL = components.url else { + preconditionFailure("Failed to construct relative URL") } + let url = baseURL.appendingPathComponent(relativeURL.path) + print("🛰 making network request to URL: `\(url.absoluteString)`") var urlRequest = URLRequest(url: url) // HTTP Method - urlRequest.httpMethod = Alamofire.HTTPMethod.post.rawValue + urlRequest.httpMethod = "POST" // Common Headers urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") - do { - let jsonData = try JSONEncoder().encode(self) - urlRequest.httpBody = jsonData - } catch { - throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) - } + let jsonData = try JSONEncoder().encode(self) + urlRequest.httpBody = jsonData return urlRequest } diff --git a/Sources/Zesame/Networking/JSONRPC/Responses/Balance/BalanceResponse.swift b/Sources/Zesame/Networking/JSONRPC/Responses/Balance/BalanceResponse.swift index 6e0e742d..23ff5e21 100644 --- a/Sources/Zesame/Networking/JSONRPC/Responses/Balance/BalanceResponse.swift +++ b/Sources/Zesame/Networking/JSONRPC/Responses/Balance/BalanceResponse.swift @@ -26,10 +26,10 @@ import Foundation // Encodable for test purposes, this type is never sent TO the API, only parsed FROM the API. public struct BalanceResponse: Codable { - public let balance: ZilAmount + public let balance: Amount public let nonce: Nonce - public init(balance: ZilAmount, nonce: Nonce) { + public init(balance: Amount, nonce: Nonce) { self.balance = balance self.nonce = nonce } @@ -40,7 +40,7 @@ public struct BalanceResponse: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.balance = try container.decode(ZilAmount.self, forKey: .balance) + self.balance = try container.decode(Amount.self, forKey: .balance) self.nonce = try container.decode(Nonce.self, forKey: .nonce) } } diff --git a/Sources/Zesame/Networking/JSONRPC/Responses/MinimumGasPrice/MinimumGasPriceResponse.swift b/Sources/Zesame/Networking/JSONRPC/Responses/MinimumGasPrice/MinimumGasPriceResponse.swift index 708f6823..6aa88fda 100644 --- a/Sources/Zesame/Networking/JSONRPC/Responses/MinimumGasPrice/MinimumGasPriceResponse.swift +++ b/Sources/Zesame/Networking/JSONRPC/Responses/MinimumGasPrice/MinimumGasPriceResponse.swift @@ -8,12 +8,12 @@ import Foundation public struct MinimumGasPriceResponse: Decodable { - public let amount: ZilAmount + public let amount: Amount public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let qaString = try container.decode(String.self) - self.amount = try ZilAmount(qa: qaString) + self.amount = try Amount(qa: qaString) } } diff --git a/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/StatusOfTransactionResponse+Receipt.swift b/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/StatusOfTransactionResponse+Receipt.swift index 263e9b64..ee159056 100644 --- a/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/StatusOfTransactionResponse+Receipt.swift +++ b/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/StatusOfTransactionResponse+Receipt.swift @@ -35,7 +35,7 @@ public extension StatusOfTransactionResponse.Receipt { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let costAsString = try container.decode(String.self, forKey: .totalGasCost) - self.totalGasCost = try ZilAmount(zil: costAsString) + self.totalGasCost = try Amount(zil: costAsString) self.isSent = try container.decode(Bool.self, forKey: .isSent) } } diff --git a/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/StatusOfTransactionResponse.swift b/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/StatusOfTransactionResponse.swift index ad59a229..fae07f3d 100644 --- a/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/StatusOfTransactionResponse.swift +++ b/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/StatusOfTransactionResponse.swift @@ -26,7 +26,7 @@ import Foundation public struct StatusOfTransactionResponse: Decodable { public struct Receipt { - public let totalGasCost: ZilAmount + public let totalGasCost: Amount public let isSent: Bool } diff --git a/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/TransactionReceipt.swift b/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/TransactionReceipt.swift index ab77ca53..05e809b6 100644 --- a/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/TransactionReceipt.swift +++ b/Sources/Zesame/Networking/JSONRPC/Responses/Transaction/TransactionReceipt.swift @@ -27,8 +27,8 @@ import Foundation // The receipt for a transaction that the network has reached consensus for. public struct TransactionReceipt { public let transactionId: String - public let totalGasCost: ZilAmount - public init(id: String, totalGasCost: ZilAmount) { + public let totalGasCost: Amount + public init(id: String, totalGasCost: Amount) { self.transactionId = id self.totalGasCost = totalGasCost } diff --git a/Sources/Zesame/Services/DefaultZilliqaService.swift b/Sources/Zesame/Services/DefaultZilliqaService.swift index 449d1a64..c7ca2d89 100644 --- a/Sources/Zesame/Services/DefaultZilliqaService.swift +++ b/Sources/Zesame/Services/DefaultZilliqaService.swift @@ -23,11 +23,10 @@ // import Foundation -import RxSwift import EllipticCurveKit -public final class DefaultZilliqaService: ZilliqaService, ReactiveCompatible { +public final class DefaultZilliqaService: ZilliqaService { public let apiClient: APIClient @@ -49,29 +48,25 @@ public extension DefaultZilliqaService { public extension DefaultZilliqaService { - func getNetworkFromAPI(done: @escaping Done) { - return apiClient.send(method: .getNetworkId, done: done) + func getNetworkFromAPI() async throws -> NetworkResponse { + try await apiClient.send(method: .getNetworkId) } - func getBalance(for address: LegacyAddress, done: @escaping Done) -> Void { - return apiClient.send(method: .getBalance(address), done: done) + func getBalance(for address: LegacyAddress) async throws -> BalanceResponse { + try await apiClient.send(method: .getBalance(address)) } - + func getMinimumGasPrice( - alsoUpdateLocallyCachedMinimum: Bool = true, - done: @escaping Done - ) -> Void { - return apiClient.send(method: .getMinimumGasPrice) { (result: Result) in - if case .success(let newMinimumPrice) = result { - if alsoUpdateLocallyCachedMinimum { - GasPrice.minInQa = newMinimumPrice.amount.qa - } - } - done(result) + alsoUpdateLocallyCachedMinimum: Bool = true + ) async throws -> MinimumGasPriceResponse { + let newMinimumPrice: MinimumGasPriceResponse = try await apiClient.send(method: .getMinimumGasPrice) + if alsoUpdateLocallyCachedMinimum { + GasPrice.minInQa = newMinimumPrice.amount.qa } + return newMinimumPrice } - func send(transaction: SignedTransaction, done: @escaping Done) { - return apiClient.send(method: .createTransaction(transaction), done: done) + func send(transaction: SignedTransaction) async throws -> TransactionResponse { + try await apiClient.send(method: .createTransaction(transaction)) } } diff --git a/Sources/Zesame/Services/Error.swift b/Sources/Zesame/Services/Error.swift index b407dbb1..5424cb87 100644 --- a/Sources/Zesame/Services/Error.swift +++ b/Sources/Zesame/Services/Error.swift @@ -23,10 +23,8 @@ // import Foundation -import Alamofire public enum Error: Swift.Error { - case api(API) case keystorePasswordTooShort(provided: Int, minimum: Int) case walletImport(WalletImport) diff --git a/Sources/Zesame/Services/Rx+ZilliqaService.swift b/Sources/Zesame/Services/Rx+ZilliqaService.swift deleted file mode 100644 index 09cf7638..00000000 --- a/Sources/Zesame/Services/Rx+ZilliqaService.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// MIT License -// -// Copyright (c) 2018-2019 Open Zesame (https://github.com/OpenZesame) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - -import RxSwift - - -import EllipticCurveKit - -extension Reactive: ZilliqaServiceReactive where Base: ZilliqaService {} -public extension Reactive where Base: ZilliqaService { - - func getNetworkFromAPI() -> Observable { - return callBase { - $0.getNetworkFromAPI(done: $1) - } - } - - - func getMinimumGasPrice(alsoUpdateLocallyCachedMinimum: Bool = true) -> Observable { - return callBase { - $0.getMinimumGasPrice(alsoUpdateLocallyCachedMinimum: alsoUpdateLocallyCachedMinimum, done: $1) - } - } - - func hasNetworkReachedConsensusYetForTransactionWith(id: String, polling: Polling) -> Observable { - return callBase { - $0.hasNetworkReachedConsensusYetForTransactionWith(id: id, polling: polling, done: $1) - } - } - - func verifyThat(encryptionPassword: String, canDecryptKeystore keystore: Keystore) -> Observable { - return callBase { - $0.verifyThat(encryptionPassword: encryptionPassword, canDecryptKeystore: keystore, done: $1) - } - } - - func createNewWallet(encryptionPassword: String, kdf: KDF = .default) -> Observable { - return callBase { - $0.createNewWallet(encryptionPassword: encryptionPassword, kdf: kdf, done: $1) - } - } - - func restoreWallet(from restoration: KeyRestoration) -> Observable{ - return callBase { - $0.restoreWallet(from: restoration, done: $1) - } - } - - func exportKeystore(privateKey: PrivateKey, encryptWalletBy password: String) -> Observable { - return callBase { - $0.exportKeystore(privateKey: privateKey, encryptWalletBy: password, done: $1) - } - } - - func getBalance(for address: LegacyAddress) -> Observable { - return callBase { - $0.getBalance(for: address, done: $1) - } - } - - func sendTransaction(for payment: Payment, keystore: Keystore, password: String, network: Network) -> Observable { - return callBase { - $0.sendTransaction(for: payment, keystore: keystore, password: password, network: network, done: $1) - } - } - - func sendTransaction(for payment: Payment, signWith keyPair: KeyPair, network: Network) -> Observable { - return callBase { - $0.sendTransaction(for: payment, signWith: keyPair, network: network, done: $1) - } - } - - func callBase(call: @escaping (Base, @escaping Done) -> Void) -> Observable { - return Single.create { [weak base] single in - guard let strongBase = base else { return Disposables.create {} } - call(strongBase, { - switch $0 { - case .failure(let error): - single(.failure(error)) - case .success(let result): - single(.success(result)) - } - }) - return Disposables.create {} - }.asObservable() - } -} diff --git a/Sources/Zesame/Services/ZilliqaService+DefaultImplementations.swift b/Sources/Zesame/Services/ZilliqaService+DefaultImplementations.swift index 3f332a1e..e87c3f49 100644 --- a/Sources/Zesame/Services/ZilliqaService+DefaultImplementations.swift +++ b/Sources/Zesame/Services/ZilliqaService+DefaultImplementations.swift @@ -25,7 +25,6 @@ import Foundation import EllipticCurveKit -import CryptoSwift var isRunningTests: Bool { return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil @@ -34,92 +33,71 @@ var isRunningTests: Bool { public extension ZilliqaService { - func verifyThat(encryptionPassword: String, canDecryptKeystore keystore: Keystore, done: @escaping Done) { - background { - keystore.decryptPrivateKeyWith(password: encryptionPassword) { result in - main { - done(.success(result.value != nil)) - } - } + func verifyThat( + encryptionPassword: String, + canDecryptKeystore keystore: Keystore + ) async throws -> Bool { + do { + _ = try await keystore.decryptPrivateKeyWith(password: encryptionPassword) + return true + } catch { + return false } } - func createNewWallet(encryptionPassword: String, kdf: KDF = .default, done: @escaping Done) { - background { - let privateKey = PrivateKey.generateNew() - let keyRestoration: KeyRestoration = .privateKey(privateKey, encryptBy: encryptionPassword, kdf: kdf) - self.restoreWallet(from: keyRestoration, done: done) - } + func createNewKeystore( + encryptionPassword: String, + kdf: KDF = .default, + kdfParams: KDFParams? = nil + ) async throws -> Keystore { + let privateKey = PrivateKey.generateNew() + let keyRestoration: KeyRestoration = .privateKey(privateKey, encryptBy: encryptionPassword, kdf: kdf, kdfParams: kdfParams) + return try await restoreKeystore(from: keyRestoration) } - func restoreWallet(from restoration: KeyRestoration, done: @escaping Done) { - background { [unowned self] in - switch restoration { - case .keystore(let keystore, let password): - keystore.decryptPrivateKeyWith(password: password) { - switch $0 { - case .failure(let error): - main { - done(.failure(error)) - } - case .success(let privateKey): - // We would like the SDK to always store Keystore on same format, so disregarding if we imported a keystore having KDF `pbkdf2` or `scrypt`, the stored KDF in the users wallet - // is the same, so that decrypting takes ~same time for every user. - if keystore.crypto.kdf == KDF.default || isRunningTests { - main { - let wallet = Wallet(keystore: keystore) - done(.success(wallet)) - } - } else { - let defaultKeyRestoration: KeyRestoration = .privateKey(privateKey, encryptBy: password, kdf: .default) - self.restoreWallet(from: defaultKeyRestoration, done: done) - } - } - } - case .privateKey(let privateKey, let newPassword, let kdf): - do { - try Keystore.from(privateKey: privateKey, encryptBy: newPassword, kdf: kdf) { - - guard case .success(let keystore) = $0 else { - done(.failure($0.error!)) - return - } - - main { - done(.success(Wallet(keystore: keystore))) - } - } - } catch { - main { - done(.failure(Error.walletImport(.keystoreError(error)))) - } - } + func restoreKeystore(from restoration: KeyRestoration) async throws -> Keystore { + switch restoration { + case .keystore(let keystore, let password): + let privateKey = try await keystore.decryptPrivateKeyWith(password: password) + + // We would like the SDK to always store Keystore on same format, so disregarding if we imported a keystore having KDF `pbkdf2` or `scrypt`, the stored KDF in the users wallet + // is the same, so that decrypting takes ~same time for every user. + if keystore.crypto.kdf == KDF.default || isRunningTests { + return keystore + } else { + let defaultKeyRestoration: KeyRestoration = .privateKey(privateKey, encryptBy: password, kdf: .default) + return try await restoreKeystore(from: defaultKeyRestoration) } - } - } - - func exportKeystore( - privateKey: PrivateKey, - encryptWalletBy password: String, - kdf: KDF = .default, - done: @escaping Done - ) { - background { + + case .privateKey(let privateKey, let newPassword, let kdf, let kdfParams): do { - try Keystore.from( - privateKey: privateKey, - encryptBy: password, - kdf: kdf) { newKeystoreResult in - main { - done(newKeystoreResult) - } - } + return try await Keystore.from( + privateKey: privateKey, + encryptBy: newPassword, + kdf: kdf, + kdfParams: kdfParams ?? KDF.defaultParameters + ) } catch { - main { - done(.failure(Error.keystoreExport(error))) - } + throw Error.walletImport(.keystoreError(error)) } } - + } + + func exportKeystore( + privateKey: PrivateKey, + encryptWalletBy password: String, + kdf: KDF = .default, + kdfParams: KDFParams = KDF.defaultParameters + ) async throws -> Keystore { + do { + return try await Keystore.from( + privateKey: privateKey, + encryptBy: password, + kdf: kdf, + kdfParams: kdfParams + ) + } catch { + throw Error.keystoreExport(error) + } } } diff --git a/Sources/Zesame/Services/ZilliqaService+PollTransaction.swift b/Sources/Zesame/Services/ZilliqaService+PollTransaction.swift index f58ddff7..7576840c 100644 --- a/Sources/Zesame/Services/ZilliqaService+PollTransaction.swift +++ b/Sources/Zesame/Services/ZilliqaService+PollTransaction.swift @@ -25,35 +25,30 @@ import Foundation public extension ZilliqaService { - func hasNetworkReachedConsensusYetForTransactionWith(id: String, polling: Polling, done: @escaping Done) { - func poll(retriesLeft: Int, delay delayInSeconds: Int) { + func hasNetworkReachedConsensusYetForTransactionWith(id: String, polling: Polling) async throws -> TransactionReceipt { + func poll(retriesLeft: Int, delay delayInSeconds: Int) async throws -> TransactionReceipt { // Stop recursion with failure when retry count reached zero guard retriesLeft > 0 else { - return done(.failure(Error.api(.timeout))) + throw Error.api(.timeout) } - let delay = DispatchTimeInterval.seconds(delayInSeconds) - - background(delay: delay) { [unowned self] in - self.getStatusOfTransaction(id: id) { - if case .success(let pollResponse) = $0, let receipt = TransactionReceipt(for: id, pollResponse: pollResponse) { - return done(.success(receipt)) - } - - // Recursivly call self - poll(retriesLeft: retriesLeft - 1, delay: polling.backoff.add(to: delayInSeconds)) - } + try await Task.sleep(nanoseconds: NSEC_PER_SEC * UInt64(delayInSeconds)) + let pollResponse = try await getStatusOfTransaction(id: id) + if let receipt = TransactionReceipt(for: id, pollResponse: pollResponse) { + return receipt } + // Recursivly call this method + return try await poll(retriesLeft: retriesLeft - 1, delay: polling.backoff.add(to: delayInSeconds)) } - + // Initiate recursive backing off polling - poll(retriesLeft: polling.count.rawValue, delay: polling.initialDelay.rawValue) + return try await poll(retriesLeft: polling.count.rawValue, delay: polling.initialDelay.rawValue) } } // MARK: - Private private extension ZilliqaService { - func getStatusOfTransaction(id: String, done: @escaping Done) { - return apiClient.send(method: .getTransaction(id), done: done) + func getStatusOfTransaction(id: String) async throws -> StatusOfTransactionResponse { + try await apiClient.send(method: .getTransaction(id)) } } diff --git a/Sources/Zesame/Services/ZilliqaService+Signing.swift b/Sources/Zesame/Services/ZilliqaService+Signing.swift index f17ab7e5..c8a11032 100644 --- a/Sources/Zesame/Services/ZilliqaService+Signing.swift +++ b/Sources/Zesame/Services/ZilliqaService+Signing.swift @@ -32,29 +32,23 @@ public extension ZilliqaService { for payment: Payment, keystore: Keystore, password: String, - network: Network, - done: @escaping Done - ) { - keystore.toKeypair(encryptedBy: password) { result in - switch result { - case .failure(let error): done(Result.failure(error)) - case .success(let keyPair): self.sendTransaction(for: payment, signWith: keyPair, network: network, done: done) - } - } + network: Network + ) async throws -> TransactionResponse { + let keyPair = try await keystore.toKeypair(encryptedBy: password) + return try await sendTransaction(for: payment, signWith: keyPair, network: network) } func sendTransaction( for payment: Payment, signWith keyPair: KeyPair, - network: Network, - done: @escaping Done - ) { + network: Network + ) async throws -> TransactionResponse { let transaction = sign( payment: payment, using: keyPair, network: network ) - send(transaction: transaction, done: done) + return try await send(transaction: transaction) } func sign( @@ -75,7 +69,7 @@ public extension ZilliqaService { } func sign(message: Message, using keyPair: KeyPair) -> Signature { - return Signer.sign(message, using: keyPair, personalizationDRBG: drbgPers) + Signer.sign(message, using: keyPair, personalizationDRBG: drbgPers) } } diff --git a/Sources/Zesame/Services/ZilliqaService.swift b/Sources/Zesame/Services/ZilliqaService.swift index 09776edd..6edc6d8d 100644 --- a/Sources/Zesame/Services/ZilliqaService.swift +++ b/Sources/Zesame/Services/ZilliqaService.swift @@ -25,70 +25,37 @@ import Foundation import EllipticCurveKit -import RxSwift -import CryptoSwift +import Combine public protocol ZilliqaService: AnyObject { var apiClient: APIClient { get } - - func getNetworkFromAPI(done: @escaping Done) - func getMinimumGasPrice(alsoUpdateLocallyCachedMinimum: Bool, done: @escaping Done) - - func verifyThat(encryptionPassword: String, canDecryptKeystore: Keystore, done: @escaping Done) - func createNewWallet(encryptionPassword: String, kdf: KDF, done: @escaping Done) - func restoreWallet(from restoration: KeyRestoration, done: @escaping Done) - func exportKeystore(privateKey: PrivateKey, encryptWalletBy password: String, kdf: KDF, done: @escaping Done) - - func getBalance(for address: LegacyAddress, done: @escaping Done) - func send(transaction: SignedTransaction, done: @escaping Done) -} - -public protocol ZilliqaServiceReactive { - - func getNetworkFromAPI() -> Observable - func getMinimumGasPrice(alsoUpdateLocallyCachedMinimum: Bool) -> Observable - func verifyThat(encryptionPassword: String, canDecryptKeystore: Keystore) -> Observable - func createNewWallet(encryptionPassword: String, kdf: KDF) -> Observable - func restoreWallet(from restoration: KeyRestoration) -> Observable - func exportKeystore(privateKey: PrivateKey, encryptWalletBy password: String) -> Observable - func extractKeyPairFrom(keystore: Keystore, encryptedBy password: String) -> Observable - - func getBalance(for address: LegacyAddress) -> Observable - func sendTransaction(for payment: Payment, keystore: Keystore, password: String, network: Network) -> Observable - func sendTransaction(for payment: Payment, signWith keyPair: KeyPair, network: Network) -> Observable - - func hasNetworkReachedConsensusYetForTransactionWith(id: String, polling: Polling) -> Observable + + func getNetworkFromAPI() async throws -> NetworkResponse + func getMinimumGasPrice(alsoUpdateLocallyCachedMinimum: Bool) async throws -> MinimumGasPriceResponse + func verifyThat(encryptionPassword: String, canDecryptKeystore: Keystore) async throws -> Bool + + func createNewKeystore(encryptionPassword: String, kdf: KDF, kdfParams: KDFParams?) async throws -> Keystore + func restoreKeystore(from restoration: KeyRestoration) async throws -> Keystore + + func exportKeystore(privateKey: PrivateKey, encryptWalletBy password: String, kdf: KDF, kdfParams: KDFParams) async throws -> Keystore + func extractKeyPairFrom(keystore: Keystore, encryptedBy password: String) async throws -> KeyPair + + func send(transaction: SignedTransaction) async throws -> TransactionResponse + func getBalance(for address: LegacyAddress) async throws -> BalanceResponse + func sendTransaction(for payment: Payment, keystore: Keystore, password: String, network: Network) async throws -> TransactionResponse + func sendTransaction(for payment: Payment, signWith keyPair: KeyPair, network: Network) async throws -> TransactionResponse + + func hasNetworkReachedConsensusYetForTransactionWith(id: String, polling: Polling) async throws -> TransactionReceipt } -public extension ZilliqaServiceReactive { - - func extractKeyPairFrom(wallet: Wallet, encryptedBy password: String) -> Observable { - return extractKeyPairFrom(keystore: wallet.keystore, encryptedBy: password) - } +public extension ZilliqaService { - func extractKeyPairFrom(keystore: Keystore, encryptedBy password: String) -> Observable { - return Observable.create { observer in - background { - keystore.toKeypair(encryptedBy: password) { - switch $0 { - case .success(let keyPair): - main { - observer.onNext(keyPair) - observer.onCompleted() - } - case .failure(let error): - main { - observer.onError(error) - } - } - } - } - return Disposables.create() - } + func extractKeyPairFrom(keystore: Keystore, encryptedBy password: String) async throws -> KeyPair { + try await keystore.toKeypair(encryptedBy: password) } - func hasNetworkReachedConsensusYetForTransactionWith(id: String) -> Observable { - return hasNetworkReachedConsensusYetForTransactionWith(id: id, polling: .twentyTimesLinearBackoff) + func hasNetworkReachedConsensusYetForTransactionWith(id: String) async throws -> TransactionReceipt { + try await hasNetworkReachedConsensusYetForTransactionWith(id: id, polling: .twentyTimesLinearBackoff) } } diff --git a/Tests/ZesameTests/AmountFromStringContainingWhitespaces.swift b/Tests/ZesameTests/AmountFromStringContainingWhitespaces.swift index a0ce1429..1ad97304 100644 --- a/Tests/ZesameTests/AmountFromStringContainingWhitespaces.swift +++ b/Tests/ZesameTests/AmountFromStringContainingWhitespaces.swift @@ -34,8 +34,8 @@ class AmountFromStringContainingWhitespaces: XCTestCase { XCTAssertEqual(Zil("1 234 567").qa, 1_234_567_000_000_000_000) } - func testZilAmountContainingWhitespaces() { - XCTAssertEqual(ZilAmount("1 234 567").qa, 1_234_567_000_000_000_000) + func testAmountContainingWhitespaces() { + XCTAssertEqual(Amount("1 234 567").qa, 1_234_567_000_000_000_000) } func testGasPriceContainingWhitespaces() { diff --git a/Tests/ZesameTests/BalanceResponseJSONTests.swift b/Tests/ZesameTests/BalanceResponseJSONTests.swift index 44548ebf..53fb9ccf 100644 --- a/Tests/ZesameTests/BalanceResponseJSONTests.swift +++ b/Tests/ZesameTests/BalanceResponseJSONTests.swift @@ -44,7 +44,7 @@ class BalanceResponseJSONTests: XCTestCase { func testEncoding() { do { - let model = BalanceResponse(balance: try ZilAmount(qa: "18446744073637511711"), nonce: 16) + let model = BalanceResponse(balance: try Amount(qa: "18446744073637511711"), nonce: 16) let jsonEncoder = JSONEncoder() jsonEncoder.outputFormatting = .prettyPrinted let json = try jsonEncoder.encode(model) diff --git a/Tests/ZesameTests/ExportKeystoreTests.swift b/Tests/ZesameTests/ExportKeystoreTests.swift index 501c2fcf..5527842c 100644 --- a/Tests/ZesameTests/ExportKeystoreTests.swift +++ b/Tests/ZesameTests/ExportKeystoreTests.swift @@ -28,39 +28,28 @@ import XCTest class ExportKeystoreTest: XCTestCase { - func testWalletImport() { - + func testWalletImport() async throws { + let service = DefaultZilliqaService(endpoint: .testnet) - let expectWalletImport = expectation(description: "importing wallet from keystore json") - do { - let keyRestoration = try KeyRestoration(keyStoreJSONString: keystoreWalletJSONString, encryptedBy: password) - service.restoreWallet(from: keyRestoration) { - switch $0 { - case .success(let importedWallet): - XCTAssertEqual(importedWallet.keystore.address.asString, "74c544a11795905C2c9808F9e78d8156159d32e4") - - case .failure(let error): XCTFail("Failed to export, error: \(error)") - } - expectWalletImport.fulfill() - } - waitForExpectations(timeout: 3, handler: nil) - } catch { - XCTFail("Unexpected error: \(error)") - } + let keyRestoration = try KeyRestoration(keyStoreJSONString: keystoreWalletJSONString, encryptedBy: password) + let importedKeystore = try await service.restoreKeystore(from: keyRestoration) + + XCTAssertEqual(importedKeystore.address.asString, "74c544a11795905C2c9808F9e78d8156159d32e4") } - - func testKeystoreDecoding() { - do { - let json = keystoreWalletJSONString.data(using: .utf8)! - let keystore = try JSONDecoder().decode(Keystore.self, from: json) - XCTAssertEqual(keystore.address.asString, "74c544a11795905C2c9808F9e78d8156159d32e4") - - keystore.decryptPrivateKey(password: password) { - XCTAssertEqual($0.asHex(), expectedPrivateKey.uppercased()) - } - } catch { - XCTFail("Unexpected error: \(error)") + + func testKeystoreDecoding() async throws { + let json = keystoreWalletJSONString.data(using: .utf8)! + let keystore = try JSONDecoder().decode(Keystore.self, from: json) + XCTAssertEqual(keystore.address.asString, "74c544a11795905C2c9808F9e78d8156159d32e4") + + guard let privateKey = await keystore.decryptPrivateKey(password: password) else { + XCTFail("Expected to be able to decrypt keystore.") + return } + XCTAssertEqual(privateKey.asHex(), expectedPrivateKey.uppercased()) + let zilliqaService = DefaultZilliqaService(network: .mainnet) + let keyPair = try await zilliqaService.extractKeyPairFrom(keystore: keystore, encryptedBy: password) + XCTAssertEqual(keyPair.privateKey, privateKey) } } diff --git a/Tests/ZesameTests/ExpressibleByAmountToStringTests.swift b/Tests/ZesameTests/ExpressibleByAmountToStringTests.swift index e4d6838b..a0cc372c 100644 --- a/Tests/ZesameTests/ExpressibleByAmountToStringTests.swift +++ b/Tests/ZesameTests/ExpressibleByAmountToStringTests.swift @@ -34,11 +34,11 @@ class ExpressibleByAmountToStringTests: XCTestCase { func testQa() { do { - let big = try ZilAmount(qa: "20999999999123567912432") - let small = try ZilAmount(qa: "510231481549") - let expected = try ZilAmount(qa: "20999999999633799393981") - let expectedPlus1 = try ZilAmount(qa: "20999999999633799393982") - let expectedMinus1 = try ZilAmount(qa: "20999999999633799393980") + let big = try Amount(qa: "20999999999123567912432") + let small = try Amount(qa: "510231481549") + let expected = try Amount(qa: "20999999999633799393981") + let expectedPlus1 = try Amount(qa: "20999999999633799393982") + let expectedMinus1 = try Amount(qa: "20999999999633799393980") XCTAssertEqual(try big + small, expected) XCTAssertLessThan(try big + small, expectedPlus1) XCTAssertGreaterThan(try big + small, expectedMinus1) @@ -48,18 +48,18 @@ class ExpressibleByAmountToStringTests: XCTestCase { } func testRounding() { - XCTAssertEqual(ZilAmount(0.1449).asString(in: .zil, roundingIfNeeded: .down, roundingNumberOfDigits: 3), "0\(decSep)144") - XCTAssertEqual(ZilAmount(0.1449).asString(in: .zil, roundingIfNeeded: .up, roundingNumberOfDigits: 3), "0\(decSep)145") + XCTAssertEqual(Amount(0.1449).asString(in: .zil, roundingIfNeeded: .down, roundingNumberOfDigits: 3), "0\(decSep)144") + XCTAssertEqual(Amount(0.1449).asString(in: .zil, roundingIfNeeded: .up, roundingNumberOfDigits: 3), "0\(decSep)145") } - func testSmallZilAmountAsZilString() { - XCTAssertEqual(ZilAmount(0.1).asString(in: .zil), "0\(decSep)1") - XCTAssertEqual(ZilAmount(0.49).asString(in: .zil), "0\(decSep)49") - XCTAssertEqual(ZilAmount(0.5).asString(in: .zil), "0\(decSep)5") - XCTAssertEqual(ZilAmount(0.51).asString(in: .zil), "0\(decSep)51") - XCTAssertEqual(ZilAmount(0).asString(in: .zil), "0") - XCTAssertEqual(ZilAmount(1).asString(in: .zil), "1") - XCTAssertEqual(ZilAmount(9).asString(in: .zil), "9") + func testSmallAmountAsZilString() { + XCTAssertEqual(Amount(0.1).asString(in: .zil), "0\(decSep)1") + XCTAssertEqual(Amount(0.49).asString(in: .zil), "0\(decSep)49") + XCTAssertEqual(Amount(0.5).asString(in: .zil), "0\(decSep)5") + XCTAssertEqual(Amount(0.51).asString(in: .zil), "0\(decSep)51") + XCTAssertEqual(Amount(0).asString(in: .zil), "0") + XCTAssertEqual(Amount(1).asString(in: .zil), "1") + XCTAssertEqual(Amount(9).asString(in: .zil), "9") } func testSmallLiAsLiString() { @@ -76,8 +76,8 @@ class ExpressibleByAmountToStringTests: XCTestCase { XCTAssertEqual(Li(9).asString(in: .li), "9") } - func testMazZilAmountAsZilString() { - XCTAssertEqual(ZilAmount(21_000_000_000).asString(in: .zil), "21000000000") + func testMazAmountAsZilString() { + XCTAssertEqual(Amount(21_000_000_000).asString(in: .zil), "21000000000") } func test10LiInQaAsString() { @@ -149,7 +149,7 @@ class ExpressibleByAmountToStringTests: XCTestCase { XCTAssertEqual(try Zil(zil: amountString).asString(in: .zil), amountString) } - func testDecimalStringZilAmount() { + func testDecimalStringAmount() { XCTAssertEqual(try Zil(zil: "0\(decSep)01").asString(in: .li), "10000") } @@ -223,11 +223,11 @@ class ExpressibleByAmountToStringTests: XCTestCase { XCTAssertNoThrow(try Zil(trimming: "1 0")) } - func testZilAmountFromDecimalStringWithLeadingZeroNoThrow() { + func testAmountFromDecimalStringWithLeadingZeroNoThrow() { XCTAssertNoThrow(try Zil(trimming: "1\(decSep)01")) } - func testZilAmountFromDecimalStringWithLeadingZeroToString() { + func testAmountFromDecimalStringWithLeadingZeroToString() { let zilString = "1\(decSep)01" XCTAssertEqual(try Zil(trimming: zilString).asString(in: .zil), zilString) } @@ -313,38 +313,38 @@ class ExpressibleByAmountToStringTests: XCTestCase { ) } - func testThatDecimalStringEndingWithDecimalSeparatorThrowsErrorZilAmount0() { + func testThatDecimalStringEndingWithDecimalSeparatorThrowsErrorAmount0() { XCTAssertThrowsSpecificError( try Zil(zil: "0\(decSep)"), AmountError.endsWithDecimalSeparator ) XCTAssertThrowsSpecificError( - try ZilAmount(zil: "0\(decSep)"), + try Amount(zil: "0\(decSep)"), AmountError.endsWithDecimalSeparator ) } - func testThatDecimalStringEndingWithDecimalSeparatorThrowsErrorZilAmount1() { + func testThatDecimalStringEndingWithDecimalSeparatorThrowsErrorAmount1() { XCTAssertThrowsSpecificError( try Zil(zil: "1\(decSep)"), AmountError.endsWithDecimalSeparator ) XCTAssertThrowsSpecificError( - try ZilAmount(zil: "1\(decSep)"), + try Amount(zil: "1\(decSep)"), AmountError.endsWithDecimalSeparator ) } - func testThatDecimalStringEndingWithDecimalSeparatorThrowsErrorLiFromZilAmount0() { + func testThatDecimalStringEndingWithDecimalSeparatorThrowsErrorLiFromAmount0() { XCTAssertThrowsSpecificError( try Li(zil: "0\(decSep)"), AmountError.endsWithDecimalSeparator ) } - func testThatDecimalStringEndingWithDecimalSeparatorThrowsErrorLiFromZilAmount1() { + func testThatDecimalStringEndingWithDecimalSeparatorThrowsErrorLiFromAmount1() { XCTAssertThrowsSpecificError( try Li(zil: "1\(decSep)"), AmountError.endsWithDecimalSeparator diff --git a/Tests/ZesameTests/KeyStoreImportExportTests.swift b/Tests/ZesameTests/KeyStoreImportExportTests.swift index 4ed323d4..b686d998 100644 --- a/Tests/ZesameTests/KeyStoreImportExportTests.swift +++ b/Tests/ZesameTests/KeyStoreImportExportTests.swift @@ -51,85 +51,63 @@ public extension KDFParams { extension Keystore { static func with( kdf: KDF, - kdfParams: KDFParams = .quickTestParameters, - done: @escaping (Keystore) -> Void) { - try! Keystore.from( + kdfParams: KDFParams = .quickTestParameters + ) async -> Keystore { + try! await Keystore.from( privateKey: privateKey, encryptBy: password, kdf: kdf, kdfParams: kdfParams - ) { - switch $0 { - case .failure(let error): - XCTFail("unexpected error: \(error)") - case .success(let keyStore): - done(keyStore) - } - } + ) } - func decryptPrivateKey(done: @escaping (PrivateKey) -> Void) { - decryptPrivateKey(password: password, done: done) + func decryptPrivateKey() async -> PrivateKey? { + await decryptPrivateKey(password: password) } - func decryptPrivateKey(password: String, done: @escaping (PrivateKey) -> Void) { - decryptPrivateKeyWith(password: password) { - switch $0 { - case .failure(let error): - XCTFail("unexpected error: \(error)") - case .success(let decryptedPrivateKey): - done(decryptedPrivateKey) - } + func decryptPrivateKey(password: String) async -> PrivateKey? { + do { + return try await decryptPrivateKeyWith(password: password) + } catch { + XCTFail("unexpected error: \(error)") + return nil } + } } class ScryptTests: XCTestCase { - - func testScrypt() { - Keystore.with(kdf: .scrypt) { keystore in - keystore.decryptPrivateKey { decryptedPrivateKey in - XCTAssertEqual(decryptedPrivateKey, privateKey) - } - } + + func testScrypt() async throws { + let keystore = await Keystore.with(kdf: .scrypt) + let decryptedPrivateKey = await keystore.decryptPrivateKey() + XCTAssertEqual(decryptedPrivateKey, privateKey) } - - func testPbkdf2() { - Keystore.with(kdf: .pbkdf2) { keystore in - keystore.decryptPrivateKey { decryptedPrivateKey in - XCTAssertEqual(decryptedPrivateKey, privateKey) - } - } + + func testPbkdf2() async throws { + let keystore = await Keystore.with(kdf: .pbkdf2) + let decryptedPrivateKey = await keystore.decryptPrivateKey() + XCTAssertEqual(decryptedPrivateKey, privateKey) } - + + typealias JSON = [String: Any] - func testNewWalletKeystore() { + + func testNewWalletKeystore() async throws { let privateKey = PrivateKey.generateNew() let password = "apabanan" - - let expectWalletImport = expectation(description: "keystore from private key") - try! Keystore.from(privateKey: privateKey, encryptBy: password, kdf: .scrypt) { - switch $0 { - case .failure(let error): XCTFail("unexpected error: \(error)") - case .success(let keystore): - XCTAssertEqual(keystore.crypto.keyDerivationFunctionParameters.saltHex.count, 64) - do { - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - let jsonData = try encoder.encode(keystore) - let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! JSON - let crypto = json["crypto"] as! JSON - let kdfparams = crypto["kdfparams"] as! JSON - let salt = kdfparams["salt"] as! String - XCTAssertNotNil(try? HexString(salt)) - } catch { - XCTFail("failed to encode") - } - expectWalletImport.fulfill() - } - } - - waitForExpectations(timeout: 3, handler: nil) + + let keystore = try! await Keystore.from(privateKey: privateKey, encryptBy: password, kdf: .scrypt) + XCTAssertEqual(keystore.crypto.keyDerivationFunctionParameters.saltHex.count, 64) + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let jsonData = try encoder.encode(keystore) + let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! JSON + let crypto = json["crypto"] as! JSON + let kdfparams = crypto["kdfparams"] as! JSON + let salt = kdfparams["salt"] as! String + XCTAssertNotNil(try? HexString(salt)) + } } diff --git a/Tests/ZesameTests/TransactionCostTests.swift b/Tests/ZesameTests/TransactionCostTests.swift index e695b7e4..bc37f354 100644 --- a/Tests/ZesameTests/TransactionCostTests.swift +++ b/Tests/ZesameTests/TransactionCostTests.swift @@ -32,7 +32,7 @@ class TransactionCostTests: XCTestCase { func testTotalAmount() { - let amount: ZilAmount = 21_000_000_000 + let amount: Amount = 21_000_000_000 XCTAssertEqual(amount.qa, "21000000000000000000000") } @@ -49,11 +49,11 @@ class TransactionCostTests: XCTestCase { var didThrowError = false do { let _ = try Payment.estimatedTotalCostOfTransaction(amount: 20_999_999_999, gasPrice: 1_000_000_000_001) - } catch let error as AmountError { + } catch let error as AmountError { didThrowError = true switch error { case .tooLarge(let max): - XCTAssertEqual(max, ZilAmount.max) + XCTAssertEqual(max, Amount.max) default: XCTFail() } } catch { @@ -66,11 +66,11 @@ class TransactionCostTests: XCTestCase { var didThrowError = false do { let _ = try Payment.estimatedTotalCostOfTransaction(amount: "20999999999", gasPrice: "1000000000001") - } catch let error as AmountError { + } catch let error as AmountError { didThrowError = true switch error { case .tooLarge(let max): - XCTAssertEqual(max, ZilAmount.max) + XCTAssertEqual(max, Amount.max) default: XCTFail() } } catch { diff --git a/Tests/ZesameTests/UnitConversionTests.swift b/Tests/ZesameTests/UnitConversionTests.swift index 17582c01..c236e153 100644 --- a/Tests/ZesameTests/UnitConversionTests.swift +++ b/Tests/ZesameTests/UnitConversionTests.swift @@ -103,13 +103,13 @@ class UnitConversionTests: XCTestCase { } func testAdditionOfUpperboundOverflow() { - let foo: ZilAmount = try! ZilAmount.max - 1 - let bar: ZilAmount = 2 + let foo: Amount = try! Amount.max - 1 + let bar: Amount = 2 do { let sum = try foo + bar XCTFail("Fail, should have thrown error, sum was: \(sum)") } catch { - XCTAssertTrue(error is AmountError) + XCTAssertTrue(error is AmountError) } } @@ -125,8 +125,8 @@ class UnitConversionTests: XCTestCase { ) } - func testZilAmountLiteral() { - let amount: ZilAmount = 15 + func testAmountLiteral() { + let amount: Amount = 15 XCTAssertEqual(amount, 15) } @@ -135,8 +135,8 @@ class UnitConversionTests: XCTestCase { XCTAssertEqual(GasPrice.min.zilString, "0\(decSep)1") } - func testZilAmountAndZil() { - let foo: ZilAmount = 123 + func testAmountAndZil() { + let foo: Amount = 123 let bar: Zil = 123 XCTAssertTrue(foo == bar) } @@ -211,15 +211,15 @@ class UnitConversionTests: XCTestCase { func testBoundString() { let qaString = "18446744073637511711" XCTAssertEqual( - try ZilAmount(zil: Zil(qa: try Qa(trimming: qaString))), - try ZilAmount(zil: try Zil(qa: qaString)) + try Amount(zil: Zil(qa: try Qa(trimming: qaString))), + try Amount(zil: try Zil(qa: qaString)) ) XCTAssertEqual( - try ZilAmount(zil: try Zil(qa: qaString)), - try ZilAmount(qa: qaString) + try Amount(zil: try Zil(qa: qaString)), + try Amount(qa: qaString) ) do { - let amount = try ZilAmount(qa: qaString) + let amount = try Amount(qa: qaString) XCTAssertEqual(amount.qaString, "18446744073637511711") } catch { XCTFail() @@ -237,11 +237,11 @@ class UnitConversionTests: XCTestCase { } func testStringZilMaxAmount() { - XCTAssertEqual(ZilAmount.max.zilString, "21000000000") + XCTAssertEqual(Amount.max.zilString, "21000000000") } - func testZilExceedingZilAmountMaxSinceZilIsUnbound() { - XCTAssertEqual(ZilAmount.max.asZil + 1, 21000000001) + func testZilExceedingAmountMaxSinceZilIsUnbound() { + XCTAssertEqual(Amount.max.asZil + 1, 21000000001) } func testNegativeAmountForZilSinceItIsUnbound() {