Usage Guide

We present here an overview of the basic wallet operations. Language-specific samples are provided in step-by-step guides.

The covered basic operations are:

  • Wallet management

    • Creating a new multi-coin wallet

    • Importing a multi-coin wallet

  • Address derivation (receiving)

    • Generating the default address for a coin

    • Generating an address using a custom derivation path (expert)

  • Transaction signing (e.g. for sending)

For the examples we use Bitcoin, Ethereum and Binance Coin as sample coins/blockchains.

Note: Star Tower Core does not cover communication with blockchain networks (nodes): address derivation is covered, but address balance retrieval not; transaction signing is covered, but broadcasting transactions to the network not.

In this guide we use small code examples from a Swift sample application, but the focus is on the explanations.

Wallet Management

Multi-Coin Wallet

The Multi-Coin Wallet is a structure allowing accounts for many coins, all controlled by a single recovery phrase. It is a standard HD Wallet (Hierarchically Derived), employing the standard derivation schemes, interoperable with many other wallets: BIP39 for recovery phrase, BIP44/BIP84 for account derivation.

Creating a New Multi-Coin Wallet

When a new wallet is created, a new seed (and thus recovery phrase) is chosen at random. After creation, the user has to be informed and guided to backup the recovery phrase.

The random generation employs secure random generation, as available on the device.

Copy

let wallet = HDWallet(strength: 128, passphrase: "")

Importing a Multi-Coin Wallet

A previously created wallet can be imported using the recovery phrase. Typical usecases for import are:

  • re-importing a wallet later, into a later installation, or

  • importing into another device, or

  • importing into another wallet app.

If the wallet was created with a passphrase, it is also required.

Copy

let wallet = HDWallet(mnemonic: "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", passphrase: "")

Account Address Derivation

Each coin needs a different account, with matching address. Addresses are derived from the multi-coin wallet. Derivation is based on a derivation path, which is unique for each coin, but can have other parameters as well. Each coin has a default derivation path, such as "m/84'/0'/0'/0/0" for Bitcoin and "m/44'/60'/0'/0/0" for Ethereum.

Generating the Default Address for a Coin

The simplest is to get the default address for a coin -- this requires no further inputs. The address is generated using the default derivation path of the coin.

For example, the default BTC address, derived for the wallet with the mnemonic shown above, with the default BTC derivation path (m/84'/0'/0'/0/0) is: bc1qpsp72plnsqe6e2dvtsetxtww2cz36ztmfxghpd. For Ethereum, this is 0xA3Dcd899C0f3832DFDFed9479a9d828c6A4EB2A7.

Here is the sample code fort obtaining the default address for different coins:

Copy

let addressBTC = wallet.getAddressForCoin(coin: .bitcoin)
let addressETH = wallet.getAddressForCoin(coin: .ethereum)
let addressBNB = wallet.getAddressForCoin(coin: .binance)

Generating an Address Using a Custom Derivation Path (Expert)

It is also possible to derive addresses using custom derivation paths. This can be done in two steps: first a derived private key is obtained, then an address from it.

Warning: use this only if you are well aware of the semantics of the derivation path used!

Security Warning: if secrets such as private keys are handled by the wallet, even if for a short time, handle with care! Avoid any risk of leakage of secrets!

Copy

let key = wallet.getKey(derivationPath: "m/44\'/60\'/1\'/0/0")   // m/44'/60'/1'/0/0
let address = CoinType.ethereum.deriveAddress(privateKey: key)

For example, a second Ethereum address can be derived using the custom derivation path ”m/44'/60’/1’/0/0” (note the 1 in the third position), yielding address 0x68eF4e5660620976a5968c7d7925753D3Cc40809.

Transaction Signing

In general, when creating a new blockchain transaction, a wallet has to:

  1. Put together a transaction with relevant fields (source, target, amount, etc.)

  2. Sign the transaction, using the account private key. This is done by Star Tower Core.

  3. Send to a node for broadcasting to the blockchain network.

The exact fields needed for a transaction are different for each blockchain. In Star Tower Core, signing input and output parameters are typically represented in a protobuf message (internally needed for serialization for passing through different language runtimes).

A generic, coin-independent signer also exists (AnySigner), but its usage is recommended only in browser-based applications.

Bitcoin Transaction Signing

Bitcoin is the first UTXO (Unspent Transaction Output) based cryptocurrency / blockchain, if you haven't read the documentation about Bitcoin, we highly recommend you to read developer glossary and raw transaction format, these will help you understand how to sign a Bitcoin transaction. Wallet Core supports Bitcoin, Bitcoin Cash, Zcash, Decred and a few forks.

The most important models in Swift are BitcoinSigningInput and BitcoinUnspentTransaction

BitcoinSigningInput

BitcoinUnspentTransaction

Here is the Swift sample code for signing a real world Bitcoin Cash transaction

Copy

let utxoTxId = Data(hexString: "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2")! // latest utxo for sender, "txid" field from blockbook utxo api: https://github.com/trezor/blockbook/blob/master/docs/api.md#get-utxo
let privateKey = PrivateKey(data: Data(hexString: "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384")!)!
let address = CoinType.bitcoinCash.deriveAddress(privateKey: privateKey)

let utxo = BitcoinUnspentTransaction.with {
    $0.outPoint.hash = Data(utxoTxId.reversed()) // reverse of UTXO tx id, Bitcoin internal expects network byte order
    $0.outPoint.index = 2                        // outpoint index of this this UTXO, "vout" field from blockbook utxo api
    $0.outPoint.sequence = UINT32_MAX
    $0.amount = 5151                             // value of this UTXO, "value" field from blockbook utxo api
    $0.script = BitcoinScript.lockScriptForAddress(address: address, coin: .bitcoinCash).data // Build lock script from address or public key hash
}

let input = BitcoinSigningInput.with {
    $0.hashType = BitcoinScript.hashTypeForCoin(coinType: .bitcoinCash)
    $0.amount = 600
    $0.byteFee = 1
    $0.toAddress = "1Bp9U1ogV3A14FMvKbRJms7ctyso4Z4Tcx"
    $0.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU" // can be same sender address
    $0.utxo = [utxo]
    $0.privateKey = [privateKey.data]
}

let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .bitcoinCash)
guard output.error.isEmpty else { return }
// encoded transaction to broadcast
print(output.encoded)

It's worth to note that you can also calcuate fee and change manually (by using a BitcoinTransactionPlan struct) Below is another real world Zcash transparent transaction demonstrate this

Copy

let utxos = [
    BitcoinUnspentTransaction.with {
        $0.outPoint.hash = Data(hexString: "53685b8809efc50dd7d5cb0906b307a1b8aa5157baa5fc1bd6fe2d0344dd193a")!
        $0.outPoint.index = 0
        $0.outPoint.sequence = UINT32_MAX
        $0.amount = 494000
        $0.script = Data(hexString: "76a914f84c7f4dd3c3dc311676444fdead6e6d290d50e388ac")!
    }
]

let input = BitcoinSigningInput.with {
    $0.hashType = BitcoinSigHashType.all.rawValue
    $0.amount = 488000
    $0.toAddress = "t1QahNjDdibyE4EdYkawUSKBBcVTSqv64CS"
    $0.coinType = CoinType.zcash.rawValue
    $0.privateKey = [Data(hexString: "a9684f5bebd0e1208aae2e02bc9e9163bd1965ad23d8538644e1df8b99b99559")!]
    $0.plan = BitcoinTransactionPlan.with {
        $0.amount = 488000
        $0.fee = 6000
        $0.change = 0
        // Sapling branch id
        $0.branchID = Data(hexString: "0xbb09b876")!
        $0.utxos = utxos
    }
}

let output: BitcoinSigningOutput = AnySigner.sign(input: input, coin: .zcash)

// encoded transaction to broadcast
print(output.encoded)

Besides orignal Bitcoin RPC, there are many other APIs / block explorer can get UTXO and broadcast raw transaction, like: insight api, trezor blockbook, blockchain com, blockchair api.

Ethereum Transaction Signing

A simple Ethereum send transaction needs the following fields:

Several parameters, like the current nonce and gasPrice values can be obtained from Ethereum node RPC calls (see https://github.com/ethereum/wiki/wiki/JSON-RPC, e.g., eth_gasPrice).

Code example to fill in the signer input parameters:

Copy

let input = EthereumSigningInput.with {
    $0.chainID = Data(hexString: "01")!
    $0.gasPrice = Data(hexString: "d693a400")! // decimal 3600000000
    $0.gasLimit = Data(hexString: "5208")! // decimal 21000
    $0.toAddress = "0xC37054b3b48C3317082E7ba872d7753D13da4986"
    $0.transaction = EthereumTransaction.with {
       $0.transfer = EthereumTransaction.Transfer.with {
           $0.amount = Data(hexString: "0348bca5a16000")!
       }
    }
    $0.privateKey = wallet.getKeyForCoin(coin: .ethereum).data
}

Then Signer is invoked, and the signed and encoded output retrieved:

Copy

let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum)
print(" data:   ", output.encoded.hexString)

For more details on Ethereum transactions, check the Ethereum documentation. A few resources are here:

  • https://medium.com/@codetractio/inside-an-ethereum-transaction-fa94ffca912f

  • https://kauri.io/article/7e79b6932f8a41a4bcbbd194fd2fcc3a/v2/ethereum-101-part-4-accounts-transactions-and-messages

  • https://github.com/ethereumbook/ethereumbook/blob/develop/06transactions.asciidoc

Binance Chain (BNB) Transaction Signing

Binance Chain is built upon cosmos-sdk, instead of Message, transaction in Binance Chain is called Order, Binance.proto shows all the orders that Star Tower Core currently supports.

To sign a order, you need to use BinanceSigningInput:

A Swift sample code send order is shown below:

Copy

let privateKey = PrivateKey(data: Data(hexString: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")!)!
let publicKey = privateKey.getPublicKeySecp256k1(compressed: true)

let token = BinanceSendOrder.Token.with {
    $0.denom = "BNB" // BNB or BEP2 token symbol
    $0.amount = 1    // Amount, 1 BNB
}

// A.k.a from / sender
let orderInput = BinanceSendOrder.Input.with {
    $0.address = CosmosAddress(hrp: .binance, publicKey: publicKey)!.keyHash
    $0.coins = [token]
}

// A.k.a to / recipient
let orderOutput = BinanceSendOrder.Output.with {
    $0.address = CosmosAddress(string: "bnb1hlly02l6ahjsgxw9wlcswnlwdhg4xhx38yxpd5")!.keyHash
    $0.coins = [token]
}

let input = BinanceSigningInput.with {
    $0.chainID = "Binance-Chain-Nile" // Testnet Chain id
    $0.accountNumber = 0              // On chain account number
    $0.sequence = 0                   // Sequence number
    $0.source = 0                     // BEP10 source id
    $0.privateKey = privateKey.data
    $0.memo = ""
    $0.sendOrder = BinanceSendOrder.with {
        $0.inputs = [orderInput]
        $0.outputs = [orderOutput]
    }
}

let output: BinanceSigningOutput = AnySigner.sign(input: input, coin: .binance)
// encoded order to broadcast
print(output.encoded)

For more details please check the Binance Chain documentation:

  • https://docs.binance.org/encoding.html

  • https://docs.binance.org/api-reference/dex-api/paths.html#http-api

Consult the complete sample applications for more details.

Last updated