BDK-CLI BASICS: 2-of-3 Multi-Signature Testnet Transaction (Tutorial)
Bitcoin Dev Kit 2-of-3 Multi-Signature Descriptor Wallet using BDK using Command-Line
Overview of the tutorial
- The purpose of this tutorial is to continue learning
bdk-cli
as our tool manage a 2 of 3 multi-signature wallet. - Generate a receive address with a spending Policy of 2 out of 3 escrow aka multi-signature.
- Intro to more complex but standard policies to create custom encumberances aka custom spending conditions for transactions.
Installation Pre-requisites
1. Install Rust and Cargo :```
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
2. Installl sqlite3
#sqlite is a database
# Linux machine
$ apt install libsqlite3-dev
# macOS machine
$ brew sqlite
3. Install jq
# jq is a tool used for data parsing
# linux machine
$ apt install jq
# macOS machine
$ brew install jq
Note: If you need remove the cache wallet data at any time use this is the command.
bdk-cli
installation:
$ cargo install --features "electrum,compiler" --version 0.6.0 bdk-cli
Step 1: Generate the XPRVs (Extended-Keys) and Save to environment variables
Create three private keys and each in their own environment variable
$ export XPRV_00=$(bdk-cli key generate | jq -r '.xprv')
$ export XPRV_01=$(bdk-cli key generate | jq -r '.xprv')
$ export XPRV_02=$(bdk-cli key generate | jq -r '.xprv')
1a: Verify XPRV environment variables are Active
$ env | grep XPRV
Step 2: Generate XPUBs (Extended Public Keys) & Save to environment variables
Generate the three individual Public Keys aka XPUBs using our Private key and descriptor path.
$ export XPUB_00=$(bdk-cli key derive --xprv $XPRV_00 --path "m/84'/1'/0'/0" | jq -r ".xpub")
$ export XPUB_01=$(bdk-cli key derive --xprv $XPRV_01 --path "m/84'/1'/0'/0" | jq -r ".xpub")
$ export XPUB_02=$(bdk-cli key derive --xprv $XPRV_02 --path "m/84'/1'/0'/0" | jq -r ".xpub")
2a: Verify XPUB environment variables
$ env | grep XPUB
Step 3: Create Single-Wallet Descriptors
Create the wallet Descriptor for each wallet
$ export DESCRIPTOR_00="$XPRV_00/84h/1h/0h/0/*"
$ export DESCRIPTOR_01="$XPRV_01/84h/1h/0h/0/*"
$ export DESCRIPTOR_02="$XPRV_02/84h/1h/0h/0/*"
Step 4: Create Multi-Sig-Descriptor Wallets
This is how you create the 2-of-3 multi-sig output descriptor. You will need (one PrivateKey and two Xpubs) It consists of using the
compiler
function to parsepolicy
tomini-script
.
- When creating the descriptor the order matters so be aware of that when following tutorial if you are for any reason changing the order of the policy.
Multi-Sig-Wallet 0
- $
export MULTI_DESCRIPTOR_00=$(bdk-cli compile "thresh(2,pk($DESCRIPTOR_00),pk($XPUB_01),pk($XPUB_02))" | jq -r '.descriptor')
Multi-Sig-Wallet 1
- $
export MULTI_DESCRIPTOR_01=$(bdk-cli compile "thresh(2,pk($XPUB_00),pk($DESCRIPTOR_01),pk($XPUB_02))" | jq -r '.descriptor')
Multi-Sig-Wallet 2
- $
export MULTI_DESCRIPTOR_02=$(bdk-cli compile "thresh(2,pk($XPUB_00),pk($XPUB_01),pk($DESCRIPTOR_02))" | jq -r '.descriptor')
What is Miniscript? + Resources
More details Miniscript, Policy:
Minsc is a high-level scripting language
Notes :
multi-sig 2 of 3 policy gets compiled to miniscript
# policy
thresh(2,pk(XPRV_A),pk(XPUB_B),pk(XPUB_C))
# miniscript
wsh(multi(2,XPRV_KEY,PUBKEY_B,XPUB_C))
4a: Verify Multi-Sig-Descriptor environment variables are active
$ env | grep MULTI
Step 5: Generate Receive Address by using Multi-Sig-Descriptor Wallets
$ bdk-cli wallet --wallet wallet_name_msd00 --descriptor $MULTI_DESCRIPTOR_00 get_new_address
$ bdk-cli wallet --wallet wallet_name_msd01 --descriptor $MULTI_DESCRIPTOR_01 get_new_address
$ bdk-cli wallet --wallet wallet_name_msd02 --descriptor $MULTI_DESCRIPTOR_02 get_new_address
Did you generate the same address for all three? Good! Else, something might be incorrect.
Step 6: Send Testnet Bitcoin to the newly created receive-address
Bitcoin Testnet Faucet link:1 Bitcoin Testnet Faucet link:2
Step 7: Sync one of the Multi-Sig Wallets
$ ` bdk-cli wallet –wallet wallet_name_msd00 –descriptor $MULTI_DESCRIPTOR_00 sync`
Step 8: Check Balance Multi-Sig Wallets
$ ` bdk-cli wallet –wallet wallet_name_msd00 –descriptor $MULTI_DESCRIPTOR_00 get_balance`
- Every wallet has access to sync and view balance.
Step 9: Check Multi-Sig Policies on Descriptor Wallet
$ bdk-cli wallet --wallet wallet_name_msd00 --descriptor $MULTI_DESCRIPTOR_00 policies
:::info :+1: The output below confirms the command was successful. ::::
{
"external": {
"contribution": {
"conditions": {
"0": [
{}
]
},
"items": [
0
],
"m": 2,
"n": 3,
"sorted": false,
"type": "PARTIAL"
},
"id": "seaxtqqn",
"keys": [
{
"fingerprint": "7cdf2d46"
},
{
"fingerprint": "fc7870cd"
},
{
"fingerprint": "26b03333"
}
],
"satisfaction": {
"items": [],
"m": 2,
"n": 3,
"sorted": false,
"type": "PARTIAL"
},
"threshold": 2,
"type": "MULTISIG"
},
"internal": null
}
### SpendingPolicyRequired for complex descriptors
--external_policy "{\"seaxtqqn\": [0,1]}"
<-rootnode-><children #0 and #1 of root node>
Save the “id”: We will need to use this ‘‘id’’ later.
- More info on external here create_tx > external policies
Step 10: Create a Transaction (PSBT)
- 1st Create a PSBT using the first wallet
- 2nd Sign the PSBT with the first wallet
- 3rd Sign PSBT with the second wallet
- Broadcast PSBT
Create TXN and Export UNSIGNED_PSBT to environment variable
$ export UNSIGNED_PSBT=$(bdk-cli wallet --wallet wallet_name_msd00 --descriptor $MULTI_DESCRIPTOR_00 create_tx --send_all --to mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt:0 --external_policy "{\"CHANGE_ID_HERE\": [0,1]}" | jq -r '.psbt')
Verify UNSIGNED_PSBT environment variable
$ env | grep UNSIGNED
Step 11: SIGN the Transaction
1st Wallet Signs the transaction
$ bdk-cli wallet --wallet wallet_name_msd00 --descriptor $MULTI_DESCRIPTOR_00 sign --psbt $UNSIGNED_PSBT
$ export ONESIG_PSBT=$(bdk-cli wallet --wallet wallet_name_msd00 --descriptor $MULTI_DESCRIPTOR_00 sign --psbt $UNSIGNED_PSBT | jq -r '.psbt')
$env | grep ONESIG
{
"is_finalized": false,
"psbt": "cHNidP8BAFUBAAAAAdYCtva/7Rkt+fgFu3mxAdaPh4uTbgBL3HmYZgcEKWygAAAAAAD/////AQqGAQAAAAAAGXapFDRKD0jKFQ7CuQOBdmC5tosTpnAmiKwAAAAAAAEA6gIAAAAAAQFLyGFJFK884DGBM1WgskRZ6gKp/7oZ+Z30u0+wF3pZYAEAAAAA/v///wKghgEAAAAAACIAINHcOQLE6GpJ3J+FOzn/be+HApxW8sZtGqfA3TBW+NYX91hoOAAAAAAWABTPQDZx2wYYIn+ug2pZBmWBn0Tu/gJHMEQCIHu6GmRMDgPZyTx+klFMA9VujR3qDA/Y08kSkRvOaChjAiBAtExtGAYLuQ/DDJzCqLlNZ1bMB3MV+nxsLfTdI9YcYwEhA0b8lz+kt0xHfR/tjUKOc2Nt2L61pDd5vJ/lsKi8pw9MmFUjAAEBK6CGAQAAAAAAIgAg0dw5AsToakncn4U7Of9t74cCnFbyxm0ap8DdMFb41hciAgIjUCIdnyr6rDtuNhVNt4ZBDcvYLawfoJbzbPyxc/WNDUgwRQIhAJdILr7G3UzYylyr2fA13MFsz/jG4+iZlKeEkX79d082AiA99UF0/uFyXBVNUmuGaxdHL7wlhzqfbgGLMREN0z/O6QEBBWlSIQIjUCIdnyr6rDtuNhVNt4ZBDcvYLawfoJbzbPyxc/WNDSEDzsDXexRPSxeXiLJoS0i2fQlOoOGHmo+Dhaeaq3oHV6YhAjGKA2Dqg+QeMICBAifYslQF2WrehLEQ0iEOpp/+eQ0NU64iBgIjUCIdnyr6rDtuNhVNt4ZBDcvYLawfoJbzbPyxc/WNDRh83y1GVAAAgAEAAIAAAACAAAAAAAAAAAAiBgIxigNg6oPkHjCAgQIn2LJUBdlq3oSxENIhDqaf/nkNDRgmsDMzVAAAgAEAAIAAAACAAAAAAAAAAAAiBgPOwNd7FE9LF5eIsmhLSLZ9CU6g4Yeaj4OFp5qregdXphj8eHDNVAAAgAEAAIAAAACAAAAAAAAAAAAAAA=="
}
2nd Wallet Signs the transaction
$ bdk-cli wallet --wallet wallet_name_msd01 --descriptor $MULTI_DESCRIPTOR_01 sign --psbt $ONESIG_PSBT
$ export SECONDSIG_PSBT=$(bdk-cli wallet --wallet wallet_name_msd01 --descriptor $MULTI_DESCRIPTOR_01 sign --psbt $ONESIG_PSBT | jq -r '.psbt')
$env | grep SECONDSIG
{
"is_finalized": true,
"psbt": "cHNidP8BAFUBAAAAAdYCtva/7Rkt+fgFu3mxAdaPh4uTbgBL3HmYZgcEKWygAAAAAAD/////AQqGAQAAAAAAGXapFDRKD0jKFQ7CuQOBdmC5tosTpnAmiKwAAAAAAAEA6gIAAAAAAQFLyGFJFK884DGBM1WgskRZ6gKp/7oZ+Z30u0+wF3pZYAEAAAAA/v///wKghgEAAAAAACIAINHcOQLE6GpJ3J+FOzn/be+HApxW8sZtGqfA3TBW+NYX91hoOAAAAAAWABTPQDZx2wYYIn+ug2pZBmWBn0Tu/gJHMEQCIHu6GmRMDgPZyTx+klFMA9VujR3qDA/Y08kSkRvOaChjAiBAtExtGAYLuQ/DDJzCqLlNZ1bMB3MV+nxsLfTdI9YcYwEhA0b8lz+kt0xHfR/tjUKOc2Nt2L61pDd5vJ/lsKi8pw9MmFUjAAEBK6CGAQAAAAAAIgAg0dw5AsToakncn4U7Of9t74cCnFbyxm0ap8DdMFb41hciAgIjUCIdnyr6rDtuNhVNt4ZBDcvYLawfoJbzbPyxc/WNDUgwRQIhAJdILr7G3UzYylyr2fA13MFsz/jG4+iZlKeEkX79d082AiA99UF0/uFyXBVNUmuGaxdHL7wlhzqfbgGLMREN0z/O6QEiAgPOwNd7FE9LF5eIsmhLSLZ9CU6g4Yeaj4OFp5qregdXpkgwRQIhAO2aRERcublhAzToshkZRMg2I8GaE7mM2ECr0vYyuscmAiB5KK4ETlvrLqL0QbcRbGqrSwIa9lVuOqP3f5qCnGRMaQEBBWlSIQIjUCIdnyr6rDtuNhVNt4ZBDcvYLawfoJbzbPyxc/WNDSEDzsDXexRPSxeXiLJoS0i2fQlOoOGHmo+Dhaeaq3oHV6YhAjGKA2Dqg+QeMICBAifYslQF2WrehLEQ0iEOpp/+eQ0NU64iBgIjUCIdnyr6rDtuNhVNt4ZBDcvYLawfoJbzbPyxc/WNDRh83y1GVAAAgAEAAIAAAACAAAAAAAAAAAAiBgIxigNg6oPkHjCAgQIn2LJUBdlq3oSxENIhDqaf/nkNDRgmsDMzVAAAgAEAAIAAAACAAAAAAAAAAAAiBgPOwNd7FE9LF5eIsmhLSLZ9CU6g4Yeaj4OFp5qregdXphj8eHDNVAAAgAEAAIAAAACAAAAAAAAAAAABBwABCP3+AAQASDBFAiEAl0guvsbdTNjKXKvZ8DXcwWzP+Mbj6JmUp4SRfv13TzYCID31QXT+4XJcFU1Sa4ZrF0cvvCWHOp9uAYsxEQ3TP87pAUgwRQIhAO2aRERcublhAzToshkZRMg2I8GaE7mM2ECr0vYyuscmAiB5KK4ETlvrLqL0QbcRbGqrSwIa9lVuOqP3f5qCnGRMaQFpUiECI1AiHZ8q+qw7bjYVTbeGQQ3L2C2sH6CW82z8sXP1jQ0hA87A13sUT0sXl4iyaEtItn0JTqDhh5qPg4Wnmqt6B1emIQIxigNg6oPkHjCAgQIn2LJUBdlq3oSxENIhDqaf/nkNDVOuAAA="
}
Step 12: Broadcast Transaction
$ bdk-cli wallet --wallet wallet_name_msd01 --descriptor $MULTI_DESCRIPTOR_01 broadcast --psbt $SECONDSIG_PSBT
{
"txid": "61da2451874a483aa8d1d0787c7680d157639f284840de8885098cac43f6cc2f"
}
Verify Transaction
- Verify transcation in the memory pool on testnet Mempool-testnet!