Generating a Bitcoin address
Overview
For a canister to receive Bitcoin payments, it must generate a Bitcoin address. In contrast to most other blockchains, Bitcoin doesn't use accounts. Instead, it uses a UTXO model. A UTXO is an unspent transaction output, and a Bitcoin transaction spends one or more UTXOs and creates new UTXOs. Each UTXO is associated with a Bitcoin address, which is derived from a public key or a script that defines the conditions under which the UTXO can be spent. A Bitcoin address is often used as a single use invoice instead of a persistant address to increase privacy.
Bitcoin address types
Bitcoin uses multiple address types:
Legacy addresses
These addresses start with a 1
and are called P2PKH
(Pay to Public Key Hash)
addresses. They encode the hash of an ECDSA public key.
There is also another type of legacy address that starts with a 3
called P2SH
(Pay to Script Hash) that encodes the hash of a
script. The script can define complex
conditions such as multisig or timelocks.
SegWit addresses
SegWit addresses are newer addresses following the
Bech32
format that start with bc1
. They are cheaper to spend than legacy addresses
and solve transaction malleability which is important for advanced use cases
like Partially Signed Bitcoin Transcations (PSBT) or the Lightning Network.
SegWit addresses can be of three types:
P2WPKH
(Pay to Witness Public Key Hash): A SegWit address that encodes the hash of an ECDSA public key.P2WSH
(Pay to Witness Script Hash): A SegWit address that encodes the hash of a script.P2TR
(Pay to Taproot): A SegWit address that can be unlocked by a Schnorr signature or a script.
Generating a Bitcoin address
As mentioned above, a Bitcoin address is derived from a public key or a script.
To generate a Bitcoin address that can only be spent by a specific canister or a
specific caller of a canister, you need to derive the address from a canister's
public key available via the
ecdsa_public_key
API.
The basic Bitcoin example demonstrates how to generate a P2PKH
address from a
canister's public key.
- Motoko
- Rust
/// Returns the P2PKH address of this canister at the given derivation path.
public func get_p2pkh_address(network : Network, key_name : Text, derivation_path : [[Nat8]]) : async BitcoinAddress {
// Fetch the public key of the given derivation path.
let public_key = await EcdsaApi.ecdsa_public_key(key_name, Array.map(derivation_path, Blob.fromArray));
// Compute the address.
public_key_to_p2pkh_address(network, Blob.toArray(public_key))
};
// Converts a public key to a P2PKH address.
func public_key_to_p2pkh_address(network : Network, public_key_bytes : [Nat8]) : BitcoinAddress {
let public_key = public_key_bytes_to_public_key(public_key_bytes);
// Compute the P2PKH address from our public key.
P2pkh.deriveAddress(Types.network_to_network_camel_case(network), Publickey.toSec1(public_key, true))
};
/// Returns the P2PKH address of this canister at the given derivation path.
pub async fn get_p2pkh_address(
network: BitcoinNetwork,
key_name: String,
derivation_path: Vec<Vec<u8>>,
) -> String {
// Fetch the public key of the given derivation path.
let public_key = ecdsa_api::ecdsa_public_key(key_name, derivation_path).await;
// Compute the address.
public_key_to_p2pkh_address(network, &public_key)
}
// Converts a public key to a P2PKH address.
fn public_key_to_p2pkh_address(network: BitcoinNetwork, public_key: &[u8]) -> String {
// SHA-256 & RIPEMD-160
let result = ripemd160(&sha256(public_key));
let prefix = match network {
BitcoinNetwork::Testnet | BitcoinNetwork::Regtest => 0x6f,
BitcoinNetwork::Mainnet => 0x00,
};
let mut data_with_prefix = vec![prefix];
data_with_prefix.extend(result);
let checksum = &sha256(&sha256(&data_with_prefix.clone()))[..4];
let mut full_address = data_with_prefix;
full_address.extend(checksum);
bs58::encode(full_address).into_string()
}
See more examples of addresses generated using rust-bitcoin
.
Learn more about Bitcoin addresses.
Learn more about the ecdsa_public_key
API.
Next steps
Learn how to create a Bitcoin transaction to spend the BTC received by the address.