2022-04-04 06:50:08 +00:00
//! Implements an example PSBT workflow.
//!
//! The workflow we simulate is that of a setup using a watch-only online wallet (contains only
//! public keys) and a cold-storage signing wallet (contains the private keys).
//!
//! You can verify the workflow using `bitcoind` and `bitcoin-cli`.
//!
//! ## Example Setup
//!
//! 1. Start Bitcoin Core in Regtest mode, for example:
//!
//! `bitcoind -regtest -server -daemon -fallbackfee=0.0002 -rpcuser=admin -rpcpassword=pass -rpcallowip=127.0.0.1/0 -rpcbind=127.0.0.1 -blockfilterindex=1 -peerblockfilters=1`
//!
//! 2. Define a shell alias to `bitcoin-cli`, for example:
//!
//! `alias bt=bitcoin-cli -rpcuser=admin -rpcpassword=pass -rpcport=18443`
//!
//! 3. Create (or load) a default wallet, for example:
//!
//! `bt createwallet <wallet-name>`
//!
//! 4. Mine some blocks, for example:
//!
//! `bt generatetoaddress 110 $(bt getnewaddress)`
//!
//! 5. Get the details for a UTXO to fund the PSBT with:
//!
//! `bt listunspent`
//!
2022-07-20 01:32:54 +00:00
use std ::collections ::BTreeMap ;
2022-04-04 06:50:08 +00:00
use std ::fmt ;
use std ::str ::FromStr ;
2023-08-21 22:03:55 +00:00
use bitcoin ::bip32 ::{ ChildNumber , DerivationPath , Fingerprint , IntoDerivationPath , Xpriv , Xpub } ;
2022-11-15 23:22:16 +00:00
use bitcoin ::consensus ::encode ;
use bitcoin ::locktime ::absolute ;
2022-08-05 03:23:03 +00:00
use bitcoin ::psbt ::{ self , Input , Psbt , PsbtSighashType } ;
2022-11-15 23:22:16 +00:00
use bitcoin ::secp256k1 ::{ Secp256k1 , Signing , Verification } ;
2022-07-20 01:32:54 +00:00
use bitcoin ::{
2023-12-17 00:59:05 +00:00
transaction , Address , Amount , CompressedPublicKey , Network , OutPoint , ScriptBuf , Sequence ,
Transaction , TxIn , TxOut , Witness ,
2022-07-20 01:32:54 +00:00
} ;
2022-04-04 06:50:08 +00:00
type Result < T > = std ::result ::Result < T , Error > ;
// Get this from the output of `bt dumpwallet <file>`.
const EXTENDED_MASTER_PRIVATE_KEY : & str = " tprv8ZgxMBicQKsPeSHZFZWT8zxie2dXWcwemnTkf4grVzMvP2UABUxqbPTCHzZ4ztwhBghpfFw27sJqEgW6y1ZTZcfvCUdtXE1L6qMF7TBdbqQ " ;
// Set these with valid data from output of step 5 above. Please note, input utxo must be a p2wpkh.
const INPUT_UTXO_TXID : & str = " 295f06639cde6039bf0c3dbf4827f0e3f2b2c2b476408e2f9af731a8d7a9c7fb " ;
const INPUT_UTXO_VOUT : u32 = 0 ;
const INPUT_UTXO_SCRIPT_PUBKEY : & str = " 00149891eeb8891b3e80a2a1ade180f143add23bf5de " ;
const INPUT_UTXO_VALUE : & str = " 50 BTC " ;
// Get this from the desciptor,
// "wpkh([97f17dca/0'/0'/0']02749483607dafb30c66bd93ece4474be65745ce538c2d70e8e246f17e7a4e0c0c)#m9n56cx0".
2024-02-05 16:29:31 +00:00
const INPUT_UTXO_DERIVATION_PATH : & str = " 0h/0h/0h " ;
2022-04-04 06:50:08 +00:00
// Grab an address to receive on: `bt generatenewaddress` (obviously contrived but works as an example).
const RECEIVE_ADDRESS : & str = " bcrt1qcmnpjjjw78yhyjrxtql6lk7pzpujs3h244p7ae " ; // The address to receive the coins we send.
// These should be correct if the UTXO above should is for 50 BTC.
const OUTPUT_AMOUNT_BTC : & str = " 1 BTC " ;
const CHANGE_AMOUNT_BTC : & str = " 48.99999 BTC " ; // 1000 sat transaction fee.
const NETWORK : Network = Network ::Regtest ;
fn main ( ) -> Result < ( ) > {
let secp = Secp256k1 ::new ( ) ;
2022-07-20 01:32:54 +00:00
let ( offline , fingerprint , account_0_xpub , input_xpub ) =
ColdStorage ::new ( & secp , EXTENDED_MASTER_PRIVATE_KEY ) ? ;
2022-04-04 06:50:08 +00:00
let online = WatchOnly ::new ( account_0_xpub , input_xpub , fingerprint ) ;
let created = online . create_psbt ( & secp ) ? ;
let updated = online . update_psbt ( created ) ? ;
let signed = offline . sign_psbt ( & secp , updated ) ? ;
let finalized = online . finalize_psbt ( signed ) ? ;
// You can use `bt sendrawtransaction` to broadcast the extracted transaction.
2023-09-11 23:30:49 +00:00
let tx = finalized . extract_tx_unchecked_fee_rate ( ) ;
2022-04-04 06:50:08 +00:00
tx . verify ( | _ | Some ( previous_output ( ) ) ) . expect ( " failed to verify transaction " ) ;
let hex = encode ::serialize_hex ( & tx ) ;
println! ( " You should now be able to broadcast the following transaction: \n \n {} " , hex ) ;
Ok ( ( ) )
}
// We cache the pubkeys for convenience because it requires a scep context to convert the private key.
/// An example of an offline signer i.e., a cold-storage device.
struct ColdStorage {
/// The master extended private key.
2023-08-21 22:03:55 +00:00
master_xpriv : Xpriv ,
2022-04-04 06:50:08 +00:00
/// The master extended public key.
2023-08-21 22:03:55 +00:00
master_xpub : Xpub ,
2022-04-04 06:50:08 +00:00
}
/// The data exported from an offline wallet to enable creation of a watch-only online wallet.
/// (wallet, fingerprint, account_0_xpub, input_utxo_xpub)
2023-08-21 22:03:55 +00:00
type ExportData = ( ColdStorage , Fingerprint , Xpub , Xpub ) ;
2022-04-04 06:50:08 +00:00
impl ColdStorage {
/// Constructs a new `ColdStorage` signer.
///
/// # Returns
///
/// The newly created signer along with the data needed to configure a watch-only wallet.
2022-07-20 01:32:54 +00:00
fn new < C : Signing > ( secp : & Secp256k1 < C > , xpriv : & str ) -> Result < ExportData > {
2023-08-21 22:03:55 +00:00
let master_xpriv = Xpriv ::from_str ( xpriv ) ? ;
let master_xpub = Xpub ::from_priv ( secp , & master_xpriv ) ;
2022-04-04 06:50:08 +00:00
// Hardened children require secret data to derive.
2024-02-05 16:29:31 +00:00
let path = " 84h/0h/0h " . into_derivation_path ( ) ? ;
2022-04-04 06:50:08 +00:00
let account_0_xpriv = master_xpriv . derive_priv ( secp , & path ) ? ;
2023-08-21 22:03:55 +00:00
let account_0_xpub = Xpub ::from_priv ( secp , & account_0_xpriv ) ;
2022-04-04 06:50:08 +00:00
let path = INPUT_UTXO_DERIVATION_PATH . into_derivation_path ( ) ? ;
let input_xpriv = master_xpriv . derive_priv ( secp , & path ) ? ;
2023-08-21 22:03:55 +00:00
let input_xpub = Xpub ::from_priv ( secp , & input_xpriv ) ;
2022-04-04 06:50:08 +00:00
2022-07-20 01:32:54 +00:00
let wallet = ColdStorage { master_xpriv , master_xpub } ;
2022-04-04 06:50:08 +00:00
let fingerprint = wallet . master_fingerprint ( ) ;
Ok ( ( wallet , fingerprint , account_0_xpub , input_xpub ) )
}
/// Returns the fingerprint for the master extended public key.
2022-07-20 01:32:54 +00:00
fn master_fingerprint ( & self ) -> Fingerprint { self . master_xpub . fingerprint ( ) }
2022-04-04 06:50:08 +00:00
/// Signs `psbt` with this signer.
2024-02-01 07:37:26 +00:00
fn sign_psbt < C : Signing + Verification > ( & self , secp : & Secp256k1 < C > , mut psbt : Psbt ) -> Result < Psbt > {
2022-07-21 01:43:03 +00:00
match psbt . sign ( & self . master_xpriv , secp ) {
Ok ( keys ) = > assert_eq! ( keys . len ( ) , 1 ) ,
Err ( ( _ , e ) ) = > {
let e = e . get ( & 0 ) . expect ( " at least one error " ) ;
return Err ( e . clone ( ) . into ( ) ) ;
2022-07-20 01:32:54 +00:00
}
2022-07-21 01:43:03 +00:00
} ;
Ok ( psbt )
2022-04-04 06:50:08 +00:00
}
}
/// An example of an watch-only online wallet.
struct WatchOnly {
/// The xpub for account 0 derived from derivation path "m/84h/0h/0h".
2023-08-21 22:03:55 +00:00
account_0_xpub : Xpub ,
2022-04-04 06:50:08 +00:00
/// The xpub derived from `INPUT_UTXO_DERIVATION_PATH`.
2023-08-21 22:03:55 +00:00
input_xpub : Xpub ,
2022-04-04 06:50:08 +00:00
/// The master extended pubkey fingerprint.
master_fingerprint : Fingerprint ,
}
impl WatchOnly {
/// Constructs a new watch-only wallet.
///
/// A watch-only wallet would typically be online and connected to the Bitcoin network. We
/// 'import' into the wallet the `account_0_xpub` and `master_fingerprint`.
///
/// The reason for importing the `input_xpub` is so one can use bitcoind to grab a valid input
/// to verify the workflow presented in this file.
2023-08-21 22:03:55 +00:00
fn new ( account_0_xpub : Xpub , input_xpub : Xpub , master_fingerprint : Fingerprint ) -> Self {
2022-04-04 06:50:08 +00:00
WatchOnly { account_0_xpub , input_xpub , master_fingerprint }
}
/// Creates the PSBT, in BIP174 parlance this is the 'Creater'.
fn create_psbt < C : Verification > ( & self , secp : & Secp256k1 < C > ) -> Result < Psbt > {
2022-11-02 22:36:37 +00:00
let to_address = Address ::from_str ( RECEIVE_ADDRESS ) ? . require_network ( Network ::Regtest ) ? ;
2022-04-04 06:50:08 +00:00
let to_amount = Amount ::from_str ( OUTPUT_AMOUNT_BTC ) ? ;
let ( _ , change_address , _ ) = self . change_address ( secp ) ? ;
let change_amount = Amount ::from_str ( CHANGE_AMOUNT_BTC ) ? ;
let tx = Transaction {
2023-08-18 01:17:39 +00:00
version : transaction ::Version ::TWO ,
2022-10-19 16:17:39 +00:00
lock_time : absolute ::LockTime ::ZERO ,
2022-07-20 01:32:54 +00:00
input : vec ! [ TxIn {
2022-11-02 22:36:37 +00:00
previous_output : OutPoint { txid : INPUT_UTXO_TXID . parse ( ) ? , vout : INPUT_UTXO_VOUT } ,
2022-07-30 12:22:18 +00:00
script_sig : ScriptBuf ::new ( ) ,
2022-07-20 01:32:54 +00:00
sequence : Sequence ::MAX , // Disable LockTime and RBF.
witness : Witness ::default ( ) ,
} ] ,
2022-04-04 06:50:08 +00:00
output : vec ! [
2023-04-24 14:47:55 +00:00
TxOut { value : to_amount , script_pubkey : to_address . script_pubkey ( ) } ,
TxOut { value : change_amount , script_pubkey : change_address . script_pubkey ( ) } ,
2022-04-04 06:50:08 +00:00
] ,
} ;
let psbt = Psbt ::from_unsigned_tx ( tx ) ? ;
Ok ( psbt )
}
/// Updates the PSBT, in BIP174 parlance this is the 'Updater'.
fn update_psbt ( & self , mut psbt : Psbt ) -> Result < Psbt > {
let mut input = Input { witness_utxo : Some ( previous_output ( ) ) , .. Default ::default ( ) } ;
let pk = self . input_xpub . to_pub ( ) ;
2023-12-11 20:10:32 +00:00
let wpkh = pk . wpubkey_hash ( ) ;
2022-04-04 06:50:08 +00:00
2023-08-14 00:51:14 +00:00
let redeem_script = ScriptBuf ::new_p2wpkh ( & wpkh ) ;
2022-04-04 06:50:08 +00:00
input . redeem_script = Some ( redeem_script ) ;
let fingerprint = self . master_fingerprint ;
let path = input_derivation_path ( ) ? ;
let mut map = BTreeMap ::new ( ) ;
2023-12-11 20:10:32 +00:00
map . insert ( pk . 0 , ( fingerprint , path ) ) ;
2022-04-04 06:50:08 +00:00
input . bip32_derivation = map ;
2022-07-21 01:43:03 +00:00
let ty = PsbtSighashType ::from_str ( " SIGHASH_ALL " ) ? ;
2022-04-04 06:50:08 +00:00
input . sighash_type = Some ( ty ) ;
psbt . inputs = vec! [ input ] ;
Ok ( psbt )
}
/// Finalizes the PSBT, in BIP174 parlance this is the 'Finalizer'.
2022-10-25 19:48:14 +00:00
/// This is just an example. For a production-ready PSBT Finalizer, use [rust-miniscript](https://docs.rs/miniscript/latest/miniscript/psbt/trait.PsbtExt.html#tymethod.finalize)
2022-04-04 06:50:08 +00:00
fn finalize_psbt ( & self , mut psbt : Psbt ) -> Result < Psbt > {
if psbt . inputs . is_empty ( ) {
2022-07-21 01:43:03 +00:00
return Err ( psbt ::SignError ::MissingInputUtxo . into ( ) ) ;
2022-04-04 06:50:08 +00:00
}
let sigs : Vec < _ > = psbt . inputs [ 0 ] . partial_sigs . values ( ) . collect ( ) ;
let mut script_witness : Witness = Witness ::new ( ) ;
2022-07-25 11:44:28 +00:00
script_witness . push ( & sigs [ 0 ] . to_vec ( ) ) ;
script_witness . push ( self . input_xpub . to_pub ( ) . to_bytes ( ) ) ;
2022-04-04 06:50:08 +00:00
psbt . inputs [ 0 ] . final_script_witness = Some ( script_witness ) ;
// Clear all the data fields as per the spec.
psbt . inputs [ 0 ] . partial_sigs = BTreeMap ::new ( ) ;
psbt . inputs [ 0 ] . sighash_type = None ;
psbt . inputs [ 0 ] . redeem_script = None ;
psbt . inputs [ 0 ] . witness_script = None ;
psbt . inputs [ 0 ] . bip32_derivation = BTreeMap ::new ( ) ;
Ok ( psbt )
}
/// Returns data for the first change address (standard BIP84 derivation path
/// "m/84h/0h/0h/1/0"). A real wallet would have access to the chain so could determine if an
/// address has been used or not. We ignore this detail and just re-use the first change address
/// without loss of generality.
2022-07-20 01:32:54 +00:00
fn change_address < C : Verification > (
& self ,
secp : & Secp256k1 < C > ,
2023-12-11 20:10:32 +00:00
) -> Result < ( CompressedPublicKey , Address , DerivationPath ) > {
2023-02-21 17:22:44 +00:00
let path = [ ChildNumber ::from_normal_idx ( 1 ) ? , ChildNumber ::from_normal_idx ( 0 ) ? ] ;
2022-04-04 06:50:08 +00:00
let derived = self . account_0_xpub . derive_pub ( secp , & path ) ? ;
let pk = derived . to_pub ( ) ;
2023-12-11 20:10:32 +00:00
let addr = Address ::p2wpkh ( & pk , NETWORK ) ;
2022-04-04 06:50:08 +00:00
let path = path . into_derivation_path ( ) ? ;
Ok ( ( pk , addr , path ) )
}
}
fn input_derivation_path ( ) -> Result < DerivationPath > {
let path = INPUT_UTXO_DERIVATION_PATH . into_derivation_path ( ) ? ;
Ok ( path )
}
fn previous_output ( ) -> TxOut {
2022-07-30 12:22:18 +00:00
let script_pubkey = ScriptBuf ::from_hex ( INPUT_UTXO_SCRIPT_PUBKEY )
2022-07-20 01:32:54 +00:00
. expect ( " failed to parse input utxo scriptPubkey " ) ;
2022-04-04 06:50:08 +00:00
let amount = Amount ::from_str ( INPUT_UTXO_VALUE ) . expect ( " failed to parse input utxo value " ) ;
2023-04-24 14:47:55 +00:00
TxOut { value : amount , script_pubkey }
2022-04-04 06:50:08 +00:00
}
2022-07-21 01:43:03 +00:00
struct Error ( Box < dyn std ::error ::Error > ) ;
2022-04-04 06:50:08 +00:00
2022-07-21 01:43:03 +00:00
impl < T : std ::error ::Error + 'static > From < T > for Error {
fn from ( e : T ) -> Self { Error ( Box ::new ( e ) ) }
2022-04-04 06:50:08 +00:00
}
2022-07-21 01:43:03 +00:00
impl fmt ::Debug for Error {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result { fmt ::Debug ::fmt ( & self . 0 , f ) }
2022-04-04 06:50:08 +00:00
}