Skip to content

Commit

Permalink
Integrate Rust bindings as library (#11)
Browse files Browse the repository at this point in the history
Add Rust binding library `ConcordiumWalletCrypto` which implements the functions currently exposed in the `ConcordiumHdWallet` in the JavaScript SDK. The functions are made available from the SDK via `ConcordiumHdWallet` as well. The unit tests have been ported from the other SDK also.

The bindings are generated by [UniFFI](https://mozilla.github.io/uniffi-rs/Overview.html) via [`cargo-swift`](https://github.com/antoniusnaumann/cargo-swift/). That tool was mainly helpful for setting up the structure of the project, it probably isn't really required for actually building the lib. As it's ability to compile seems a little flaky and because we might want to build fewer variants for the purpose of CI, it seems reasonable that we'd replace this dependency with a simple script that does the same thing.

Resolves https://concordium.atlassian.net/browse/CBW-1552.

References:
- JS SDK: https://github.com/Concordium/concordium-node-sdk-js/blob/main/packages/sdk/src/wasm/HdWallet.ts
- Unit tests ported: https://github.com/Concordium/concordium-node-sdk-js/blob/main/packages/sdk/test/ci/HdWallet.test.ts#L186
  • Loading branch information
bisgardo authored Jan 25, 2024
1 parent 0a5af87 commit 9dca818
Show file tree
Hide file tree
Showing 18 changed files with 2,697 additions and 18 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/build+test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
pull_request:

env:
rust_version: "1.72"
swift_version: "5.9"

jobs:
Expand All @@ -16,9 +17,19 @@ jobs:
- name: Setup Swift
uses: swift-actions/setup-swift@v1
with:
swift-version: "${{ env.swift_version }}"
swift-version: "${{env.swift_version}}"
- name: Setup Rust
run: |
rustup default "${{env.rust_version}}"
rustup target install aarch64-apple-darwin x86_64-apple-darwin
cargo install cargo-swift
- name: Check out sources
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build rust bindings (macOS only)
run: cargo swift package --name=ConcordiumWalletCrypto --platforms=macos --release
working-directory: ./lib/crypto
- name: Check formatting
run: swift package plugin --allow-writing-to-package-directory swiftformat --lint
- name: Build project
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "concordium-grpc-api"]
path = concordium-grpc-api
url = https://github.com/Concordium/concordium-grpc-api.git
[submodule "lib/crypto/concordium-base"]
path = lib/crypto/concordium-base
url = https://github.com/Concordium/concordium-base.git
10 changes: 5 additions & 5 deletions ExampleWallet/ExampleWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
A10E85322B56A6110063B079 /* SeedPhraseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10E85312B56A6110063B079 /* SeedPhraseView.swift */; };
A16C9F8F2B5D2CAF0009AD77 /* SeedPhraseValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16C9F8E2B5D2CAF0009AD77 /* SeedPhraseValidator.swift */; };
A16C9FBC2B5D370C0009AD77 /* SeedPhraseValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16C9FBB2B5D370C0009AD77 /* SeedPhraseValidatorTests.swift */; };
CD0E4E142B6116CA00E62FF1 /* ConcordiumSwiftSdk in Frameworks */ = {isa = PBXBuildFile; productRef = CD0E4E132B6116CA00E62FF1 /* ConcordiumSwiftSdk */; };
A17E071D2B57E8DE00F7BFFC /* IssueIdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A17E071C2B57E8DE00F7BFFC /* IssueIdentityView.swift */; };
A17E07202B57EE8E00F7BFFC /* BlueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A17E071F2B57EE8E00F7BFFC /* BlueButton.swift */; };
A17E07222B57F12400F7BFFC /* BlueTextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A17E07212B57F12400F7BFFC /* BlueTextStyle.swift */; };
Expand All @@ -20,6 +19,7 @@
A1CD79D82B556FD200F1CAA5 /* ExampleWalletApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1CD79D72B556FD200F1CAA5 /* ExampleWalletApp.swift */; };
A1CD79DC2B556FD300F1CAA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1CD79DB2B556FD300F1CAA5 /* Assets.xcassets */; };
A1CD79DF2B556FD300F1CAA5 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1CD79DE2B556FD300F1CAA5 /* Preview Assets.xcassets */; };
CD0E4E142B6116CA00E62FF1 /* ConcordiumSwiftSdk in Frameworks */ = {isa = PBXBuildFile; productRef = CD0E4E132B6116CA00E62FF1 /* ConcordiumSwiftSdk */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -541,15 +541,15 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
CD0E4E132B6116CA00E62FF1 /* ConcordiumSwiftSdk */ = {
isa = XCSwiftPackageProductDependency;
productName = ConcordiumSwiftSdk;
};
A1A6B3A02B5A893100EC1313 /* MnemonicSwift */ = {
isa = XCSwiftPackageProductDependency;
package = A1A6B39F2B5A893100EC1313 /* XCRemoteSwiftPackageReference "MnemonicSwift" */;
productName = MnemonicSwift;
};
CD0E4E132B6116CA00E62FF1 /* ConcordiumSwiftSdk */ = {
isa = XCSwiftPackageProductDependency;
productName = ConcordiumSwiftSdk;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = A1CD79CC2B556FD200F1CAA5 /* Project object */;
Expand Down
10 changes: 5 additions & 5 deletions ExampleWallet/ExampleWallet/Views/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ struct MainView: View {
NavigationView {
VStack {
getNavigationLinkTo(destination: SeedPhraseView(), withLabel: "Seed phrase view")

BlueButton("Identity recovery view") {}

BlueButton("Identity view") {}

BlueButton("Account view") {}

Spacer()
}
}
}

private func getNavigationLinkTo<Destination: View>(destination: Destination, withLabel label: String) -> some View {
NavigationLink(destination: destination) {
Text(label).modifier(BlueTextStyle())
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ fmt:
.PHONY: generate-grpc
generate-grpc:
protoc --proto_path=./concordium-grpc-api --grpc-swift_opt='Client=true,Server=false' --grpc-swift_out=./Sources/ConcordiumSwiftSdk/Generated/Grpc --swift_out=./Sources/ConcordiumSwiftSdk/Generated/Grpc ./concordium-grpc-api/v2/concordium/*.proto

.PHONY: build-crypto
build-crypto:
cd ./lib/crypto && ./build.sh
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ let package = Package(
.package(url: "https://github.com/anquii/Base58Check.git", from: "1.0.0"),
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.15.0"),
.package(url: "https://github.com/nicklockwood/SwiftFormat.git", exact: "0.53.0"),
.package(path: "./lib/crypto/ConcordiumWalletCrypto"),
],
targets: [
.target(
name: "ConcordiumSwiftSdk",
dependencies: [
"Base58Check",
"ConcordiumWalletCrypto",
.product(name: "GRPC", package: "grpc-swift"),
"SwiftFormat",
]
Expand Down
54 changes: 47 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,90 @@
**STATUS: EARLY DEVELOPMENT**

An SDK for applications written in the [Swift Programming Language](https://www.swift.org/) to
interact with the Concordium Blockchain.
interact with the [Concordium Blockchain](https://concordium.com).

The main purpose of the SDK is to facilitate development of mobile wallet apps for iOS devices.

Once the project is ready for production, it will replace the existing
[`ConcordiumWalletCrypto`](https://github.com/Concordium/concordium-wallet-crypto-swift) library
which is currently used in the [iOS reference wallet](https://github.com/Concordium/concordium-reference-wallet-ios/).

### Supported platforms

- iOS 15+
- macOS 10.15+

## Usage

*No tags have been added to "publish" a build yet. The following is unverified and is only going to work once v1.0 has been published.*
*No tags have been added to "publish" a build yet. The following is only going to work once v1.0 has been published.
To use it in the current, unfinished state, replace `"1.0"` with `"main"`.*

The SDK is available as a SwiftPM package hosted on GitHub as this repository.
The SDK is available as a [SwiftPM package](https://developer.apple.com/documentation/xcode/swift-packages)
hosted on GitHub as this repository.
To include it as a dependency, add the following

```swift
.package(url: "https://github.com/Concordium/concordium-swift-sdk.git", from: "1.0"),
.package(url: "https://github.com/Concordium/concordium-swift-sdk.git", from: "1.0")
```

and adding

```swift
.product(name: "ConcordiumSwiftSDK", package: "concordium-swift-sdk"),
.product(name: "ConcordiumSwiftSDK", package: "concordium-swift-sdk")
```

to the `dependencies` list of the appropriate `target`.

## Development

### Build Rust bindings

Concordium specific cryptographic functions are implemented in Rust and shared between all kinds of Concordium products.
This SDK includes a thin wrapper for providing bindings to the Rust library
[`wallet_library`](https://github.com/Concordium/concordium-base/tree/main/rust-src/wallet_library)
which exposes functions specifically relevant for wallets.

These bindings are located in `./lib/crypto` and built using [`cargo-swift`](https://github.com/antoniusnaumann/cargo-swift/)
into a [XCFramework](https://developer.apple.com/documentation/xcode/distributing-binary-frameworks-as-swift-packages).
The SDK pulls in this framework from a local path, so the bindings have to built manually before the SDK can be used.

Building is only a matter of installing `cargo-swift` and invoking a Make target:

```shell
cargo install cargo-swift
make build-crypto
```

This will place the target framework at `./lib/crypto/ConcordiumWalletCrypto`.

### Build and test SDK

With the Rust bindings in place, the SDK is built and tests executed using `swift test`.
It's not necessary to build the project in order to use it in other projects:
Just declare a dependency as explained in [usage](#usage).
The SDK will get compiled as part of the build process of the executable.

TODO: This means that we'll either have to add steps in `Package.swift` for automatically building the binaries (if possible)
or push them to some specific location
(like we did with [`concordium-wallet-crypto-swift`](https://github.com/Concordium/concordium-wallet-crypto-swift)).
This could be a GitHub release/package or S3.

### Source code formatting

The source code is formatted according to the default rules of [`SwiftFormat`](https://github.com/nicklockwood/SwiftFormat).

The CI workflow [`Build and test`](https://github.com/Concordium/concordium-swift-sdk/blob/main/.github/workflows/build%2Btest.yml)
checks that the code base is correctly formated before PRs are merged.

The formatter has been integrated as a [Swift Package Manger plugin](https://github.com/nicklockwood/SwiftFormat#swift-package-manager-plugin).
The formatter has been integrated as a
[Swift Package Manger plugin](https://github.com/nicklockwood/SwiftFormat#swift-package-manager-plugin).
It's possible to run the tool in a variety of ways (see the previous link for all options).
The easiest option is to run it on the command line via

```shell
make fmt
```

It may also be [invoked directly from XCode](https://github.com/nicklockwood/SwiftFormat#trigger-plugin-from-xcode) by right-clicking on package root (i.e. `concordium-swift-sdk`) in the Project Navigator pane.
It may also be [invoked directly from XCode](https://github.com/nicklockwood/SwiftFormat#trigger-plugin-from-xcode)
by right-clicking on package root (i.e. `concordium-swift-sdk`) in the Project Navigator pane.
The tool is then listed under "SwiftFormat" as "SwiftFormatPlugin" in the context menu for formatting the entire project.
114 changes: 114 additions & 0 deletions Sources/ConcordiumSwiftSdk/ConcordiumHdWallet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import Foundation

import ConcordiumWalletCrypto

enum ConcordiumNetwork: String {
case mainnet = "Mainnet"
case testnet = "Testnet"
}

class ConcordiumHdWallet {
let seedHex: String
let network: ConcordiumNetwork

init(seedHex: String, network: ConcordiumNetwork) {
self.seedHex = seedHex
self.network = network
}

func getAccountSigningKey(identityProviderIndex: UInt32, identityIndex: UInt32, credentialCounter: UInt32) throws -> String {
try ConcordiumWalletCrypto.getAccountSigningKey(
seedHex: seedHex,
network: network.rawValue,
identityProviderIndex: identityProviderIndex,
identityIndex: identityIndex,
credentialCounter: credentialCounter
)
}

func getAccountPublicKey(identityProviderIndex: UInt32, identityIndex: UInt32, credentialCounter: UInt32) throws -> String {
try ConcordiumWalletCrypto.getAccountPublicKey(
seedHex: seedHex,
network: network.rawValue,
identityProviderIndex: identityProviderIndex,
identityIndex: identityIndex,
credentialCounter: credentialCounter
)
}

func getCredentialId(identityProviderIndex: UInt32, identityIndex: UInt32, credentialCounter: UInt8, commitmentKey: String) throws -> String {
try ConcordiumWalletCrypto.getCredentialId(
seedHex: seedHex,
network: network.rawValue,
identityProviderIndex: identityProviderIndex,
identityIndex: identityIndex,
credentialCounter: credentialCounter,
commitmentKey: commitmentKey
)
}

func getPrfKey(identityProviderIndex: UInt32, identityIndex: UInt32) throws -> String {
try ConcordiumWalletCrypto.getPrfKey(
seedHex: seedHex,
network: network.rawValue,
identityProviderIndex: identityProviderIndex,
identityIndex: identityIndex
)
}

func getIdCredSec(identityProviderIndex: UInt32, identityIndex: UInt32) throws -> String {
try ConcordiumWalletCrypto.getIdCredSec(
seedHex: seedHex,
network: network.rawValue,
identityProviderIndex: identityProviderIndex,
identityIndex: identityIndex
)
}

func getSignatureBlindingRandomness(identityProviderIndex: UInt32, identityIndex: UInt32) throws -> String {
try ConcordiumWalletCrypto.getSignatureBlindingRandomness(
seedHex: seedHex,
network: network.rawValue,
identityProviderIndex: identityProviderIndex,
identityIndex: identityIndex
)
}

func getAttributeCommitmentRandomness(identityProviderIndex: UInt32, identityIndex: UInt32, credentialCounter: UInt32, attribute: UInt8) throws -> String {
try ConcordiumWalletCrypto.getAttributeCommitmentRandomness(
seedHex: seedHex,
network: network.rawValue,
identityProviderIndex: identityProviderIndex,
identityIndex: identityIndex,
credentialCounter: credentialCounter,
attribute: attribute
)
}

func getVerifiableCredentialSigningKey(issuerIndex: UInt64, issuerSubindex: UInt64, verifiableCredentialIndex: UInt32) throws -> String {
try ConcordiumWalletCrypto.getVerifiableCredentialSigningKey(
seedHex: seedHex,
network: network.rawValue,
issuerIndex: issuerIndex,
issuerSubindex: issuerSubindex,
verifiableCredentialIndex: verifiableCredentialIndex
)
}

func getVerifiableCredentialPublicKey(issuerIndex: UInt64, issuerSubindex: UInt64, verifiableCredentialIndex: UInt32) throws -> String {
try ConcordiumWalletCrypto.getVerifiableCredentialPublicKey(
seedHex: seedHex,
network: network.rawValue,
issuerIndex: issuerIndex,
issuerSubindex: issuerSubindex,
verifiableCredentialIndex: verifiableCredentialIndex
)
}

func getVerifiableCredentialBackupEncryptionKey() throws -> String {
try ConcordiumWalletCrypto.getVerifiableCredentialBackupEncryptionKey(
seedHex: seedHex,
network: network.rawValue
)
}
}
1 change: 1 addition & 0 deletions Sources/ConcordiumSwiftSdk/ConcordiumNodeClient.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ConcordiumWalletCrypto
import Foundation
import GRPC
import NIOCore
Expand Down
Loading

0 comments on commit 9dca818

Please sign in to comment.