Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Structured concurrency #54

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
22 changes: 2 additions & 20 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
}
},
{
Expand All @@ -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",
Expand Down
12 changes: 5 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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(
Expand Down
59 changes: 28 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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<Wallet>
func exportKeystore(from wallet: Wallet, encryptWalletBy passphrase: String) -> Observable<Keystore>
func importWalletFrom(keyStore: Keystore, encryptedBy passphrase: String) -> Observable<Wallet>

func getBalance(for address: Address) -> Observable<BalanceResponse>
func sendTransaction(for payment: Payment, signWith keyPair: KeyPair) -> Observable<TransactionIdentifier>
}
```

# Explorer
Expand All @@ -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
1 change: 0 additions & 1 deletion Sources/Zesame/Cryptography/DerivedKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import Foundation
import EllipticCurveKit
import CryptoSwift

public struct DerivedKey {
public let data: Data
Expand Down
4 changes: 1 addition & 3 deletions Sources/Zesame/Extensions/CryptoSwift+Scrypt+KDFParams.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
//

import Foundation
import CryptoSwift

public typealias Scrypt = CryptoSwift.Scrypt
@_exported import class CryptoSwift.Scrypt

public extension Scrypt {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Zesame/Models/Manual/KeyRestoration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import Foundation

public struct ZilAmount: ExpressibleByAmount, Upperbound, Lowerbound {
public struct Amount: ExpressibleByAmount, Upperbound, Lowerbound, Hashable {

public typealias Magnitude = Zil.Magnitude

Expand All @@ -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)
}
}
18 changes: 9 additions & 9 deletions Sources/Zesame/Models/Manual/Payment/Payment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -57,7 +57,7 @@ public extension Payment {

static func withMinimumGasLimit(
to recipient: LegacyAddress,
amount: ZilAmount,
amount: Amount,
gasPrice: GasPrice,
nonce: Nonce = 0
) -> Self {
Expand All @@ -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
}

Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Foundation
public enum Address:
AddressChecksummedConvertible,
StringConvertible,
Equatable,
Hashable,
ExpressibleByStringLiteral
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import Foundation
public struct Bech32Address:
AddressChecksummedConvertible,
StringConvertible,
Equatable,
Hashable,
ExpressibleByStringLiteral
{

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extension CharacterSet {
}
}

public struct HexString {
public struct HexString: Hashable {

public let value: String
init(_ value: String) throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 12 additions & 11 deletions Sources/Zesame/Models/Manual/Wallet/Keystore/AnyKeyDeriving.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
//

import Foundation
import CryptoSwift

public struct AnyKeyDeriving: KeyDeriving {
private let kdf: KDF
Expand All @@ -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<DerivedKey, Swift.Error>(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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading