provwallet-swift
is a Swift framework for building Provenance blockchain Swift clients
and transaction signing using Provenance Blockchain-compliant
(Hierarchical Deterministic Wallets) keys.
- Provenance compliant HD Wallet
- BIP44 coin type 505
- Mnemonic recovery phrase in BIP39
- BIP32 import and export
- Sign Provenance transactions
- gRPC client.
Add the ProvWallet
dependency to your CocoaPod pod file:
pod 'ProvWallet', '~> 0.0.5'
- Integrate with iOS/iCloud Keychain
- Utilize Apple CryptoKit over CryptoSwift if secp256k1 is ever available
This project uses CocoaPods for dependency management. Installation instructions can be found here.
Then, in the project directory run:
$ pod install
Next, open provwallet-swift.xcworkspace
and build as normal.
See https://github.com/apple/swift-protobuf
Install via Homebrew:
brew install protobuf
brew install swift-protobuf
The swift-protobuf
formulae does not install the protoc-gen-grpc-swift
plugin, only the protoc-gen-swift
plugin. Use CocoaPods to download the
Swift protoc-gen-grpc-swift
binary. The binary is part of the gRPC-Swift-Plugins
pod.
CocoaPods will put the files in ../Pods/gRPC-Swift-Plugins/
.
Copy ../Pods/gRPC-Swift-Plugins/bin/protoc-gen-grpc-swift
to protoc-gen-swiftgrpc
at a location on your PATH.
For example:
pod install
mkdir -p ~/proto/bin
cp ../Pods/gRPC-Swift-Plugins/bin/protoc-gen-grpc-swift ~/proto/bin/protoc-gen-swiftgrpc
export PATH=$PATH:~/proto/bin
Generate the protos:
cd Proto
rm -rf ../ProtoGen/*
protoc --swift_opt=FileNaming=PathToUnderscores --swift_out=../ProtoGen \
--swiftgrpc_opt=FileNaming=PathToUnderscores \
--swift_opt=Visibility=Public \
--swiftgrpc_out=Client=true,Server=false:../ProtoGen $(find . -iname "*.proto")
The ProvWallet.podspec
spec defines the CocoaPods pod specification for the
ProvWallet
CocoaPod. To build and deploy the provwallet-swift
to the CocoaPods
refer to the CocoaPods guide: https://guides.cocoapods.org/making/getting-setup-with-trunk.html
For example (from the root project directory):
pod spec lint ProvWallet.podspec
pod trunk register <your email address> '<your name>' --description='<your computer>'
pod trunk push ProvWallet.podspec
Refer to provwallet-swiftTests
for numerous HD Wallet test cases and examples.
let entropy = Data(hex: "000102030405060708090a0b0c0d0e0f")
let mnemonic = Mnemonic.create(entropy: entropy)
print(mnemonic)
// abandon amount liar amount expire adjust cage candy arch gather drum buyer
let seed = Mnemonic.createSeed(mnemonic: mnemonic)
print(seed.toHexString())
let mnemonic = Mnemonic.create()
let seed = Mnemonic.createSeed(mnemonic: mnemonic)
let privateKey = PrivateKey(seed: seed, coin: .bitcoin)
// BIP44 key derivation
// m/44'
let purpose = privateKey.derived(at: .hardened(44))
// m/44'/0'
let coinType = purpose.derived(at: .hardened(0))
// m/44'/0'/0'
let account = coinType.derived(at: .hardened(0))
// m/44'/0'/0'/0
let change = account.derived(at: .notHardened(0))
// m/44'/0'/0'/0/0
let firstPrivateKey = change.derived(at: .notHardened(0))
print(firstPrivateKey.publicKey.address)
let mnemonic = Mnemonic.create()
let seed = Mnemonic.createSeed(mnemonic: mnemonic)
let network: Network = .main(.bitcoin)
let wallet = Wallet(seed: seed, network: network)
let account = wallet.generateAccount()
print(account)
let wallet = try Wallet.init(bip32Serialized: "xprv9s21ZrQH143K2XojduRLQnU8D8K59KSBoMuQKGx8dW3NBitFDMkYGiJPwZdanjZonM7eXvcEbxwuGf3RdkCyyXjsbHSkwtLnJcsZ9US42Gd")
//yields m/44'/505'/0'/0/0
let account = wallet.generateAccount()
//yields m/44'/505'/0'/0/77'
let account77 = wallet.generateAccountHardened(at: 77)
let entropy = Data(hex: "000102030405060708090a0b0c0d0e0f")
let mnemonic = Mnemonic.create(entropy: entropy)
let seed = Mnemonic.createSeed(mnemonic: mnemonic)
let wallet = Wallet.init(seed: seed, coin: .mainnet)
let bip32 = try wallet.privateKey.serialize(publicKeyOnly: false)
print(bip32)
let entropy = Data(hex: "000102030405060708090a0b0c0d0e0f")
let mnemonic = Mnemonic.create(entropy: entropy)
let seed = Mnemonic.createSeed(mnemonic: mnemonic)
let wallet = Wallet.init(seed: seed, coin: .testnet)
//yields m/44'/1'/0'/0/0
let account = wallet.generateAccount()
let signature = try account.privateKey.sign(text: "HELLO")
print(signature.provenanceSignature.toHexString())
let hashData = SHA256.hash(data: "HELLO".data(using: .utf8)!).data
let validSignature = try Crypto.verify(signature, hash: hashData,
publicKey: account.publicKey.uncompressedPublicKey)
print(validSignature)
let wallet = try Wallet.init(bip32Serialized: "xprv9s21ZrQH143K2XojduRLQnU8D8K59KSBoMuQKGx8dW3NBitFDMkYGiJPwZdanjZonM7eXvcEbxwuGf3RdkCyyXjsbHSkwtLnJcsZ9US42Gd")
//yields m/44'/505'/0'/0/0
let account = wallet.generateAccount()
let signature = try account.privateKey.sign(text: "HELLO")
print(signature.provenanceSignature.toHexString())
Set up a gRPC channel pointing to a Provenance blockchain node, establish the signing key using Wallet, and submit a query:
import GRPC
import NIO
import SwiftProtobuf
import CryptoKit
let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)
let channel: ClientConnection! = ClientConnection.insecure(group: group)
.connect(host: "localhost", port: 9090)
let auth = AuthQuery(channel: channel) // Provenance Auth module query
//Set up signing key from mnemonic using Wallet
let seed = Mnemonic.createSeed(mnemonic: "...mnemonic phrase...")
let signingKey = PrivateKey(seed: seed, coin: .testnet).defaultKey()
let address = signingKey.publicKey.address
//Query base account from Auth module - returns a Promise so
//use a blocking .wait()
let baseAccount = try auth.baseAccount(address: address).wait()
//Alternative query a base account using Promise when
let promise = try auth.baseAccount(address: address)
promise.whenSuccess { baseAccount in
//...do something with baseAccount...
}
promise.whenFailure { error in
//...do something with error...
}
Set up a gRPC channel pointing to a Provenance blockchain node, establish the signing key using Wallet, estimate gas, and broadcast a transaction:
import GRPC
import NIO
import SwiftProtobuf
import CryptoKit
let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)
let channel: ClientConnection! = ClientConnection.insecure(group: group)
.connect(host: "localhost", port: 9090)
let auth = AuthQuery(channel: channel) // Provenance Auth module query
//Set up signing key from mnemonic using Wallet
let seed = Mnemonic.createSeed(mnemonic: "...mnemonic phrase...")
let signingKey = PrivateKey(seed: seed, coin: .testnet).defaultKey()
let address = signingKey.publicKey.address
//Query base account from Auth module - returns a Promise so
//use a blocking .wait()
let baseAccount = try auth.baseAccount(address: address).wait()
let tx = Tx(signingKey: signingKey, baseAccount: baseAccount, channel: channel)
//Estimate gas
let gasEstimate = try tx.estimateTx(messages: [bankSend]).wait()
let bankSend = try Google_Protobuf_Any.from(
message: Bank.buildMsgSend(
fromAddress: baseAccount.address,
toAddress: "tp1mpapyn7sgdrrmpx8ed7haprt8m0039gg0nyn8f",
amount: "77"))
let txPromise = try tx.broadcastTx(
gasEstimate: gasEstimate,
messages: [bankSend])
txPromise.whenSuccess { response in
//...do something with response...
}
txPromise.whenError { error in
//...do something with error...
}
Refer to the ProvenanceBlockchainClientTests.swift test file for examples.