psbt-v2 0.2.0

Partially Signed Bitcoin Transaction, v0 and v2
Documentation
//! PSBT v0 2 of 2 multisig example.
//!
//! An example of using PSBT v0 to create a 2 of 2 multisig by spending two native segwit v0 inputs
//! to a native segwit v0 output (the multisig output).
//!
//! We sign invalid inputs, this code is not run against Bitcoin Core so everything here should be
//! taken as NOT PROVEN CORRECT.

use std::collections::BTreeMap;

use psbt_v2::bitcoin::hashes::Hash as _;
use psbt_v2::bitcoin::locktime::absolute;
use psbt_v2::bitcoin::opcodes::all::OP_CHECKMULTISIG;
use psbt_v2::bitcoin::secp256k1::{self, rand, SECP256K1};
use psbt_v2::bitcoin::{
    script, transaction, Address, Amount, CompressedPublicKey, Network, OutPoint, PublicKey,
    ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
};
use psbt_v2::v0::{self, Psbt};

pub const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000);
pub const SPEND_AMOUNT: Amount = Amount::from_sat(20_000_000);

const MAINNET: Network = Network::Bitcoin; // Bitcoin mainnet network.
const FEE: Amount = Amount::from_sat(1_000); // Usually this would be calculated.
const DUMMY_CHANGE_AMOUNT: Amount = Amount::from_sat(100_000);

fn main() -> anyhow::Result<()> {
    // Mimic two people, Alice and Bob, who wish to create a 2-of-2 multisig output together.
    let alice = Alice::new();
    let bob = Bob::new();

    // Each person provides their pubkey.
    let pk_a = alice.public_key();
    let pk_b = bob.public_key();

    // Each party will be contributing 20,000,000 sats to the mulitsig output, as such each party
    // provides an unspent input to create the multisig output (and any change details if needed).

    // Alice has a UTXO that is too big, she needs change.
    let (previous_output_a, change_address_a, change_value_a) = alice.contribute_to_multisig();

    // Bob has a UTXO the right size so no change needed.
    let previous_output_b = bob.contribute_to_multisig();

    // In PSBT v0 the creator is responsible for creating the transaction.

    // Build the inputs using information provide by each party.
    let input_0 = TxIn {
        previous_output: previous_output_a,
        script_sig: ScriptBuf::default(),
        sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
        witness: Witness::default(),
    };
    let input_1 = TxIn {
        previous_output: previous_output_b,
        script_sig: ScriptBuf::default(),
        sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
        witness: Witness::default(),
    };

    // Build Alice's change output.
    let change = TxOut { value: change_value_a, script_pubkey: change_address_a.script_pubkey() };

    // Create the witness script, receive address, and the locking script.
    let witness_script = multisig_witness_script(&pk_a, &pk_b);
    let address = Address::p2wsh(&witness_script, MAINNET);
    let value = SPEND_AMOUNT * 2 - FEE;
    // The spend output is locked by the witness script.
    let multi = TxOut { value, script_pubkey: address.script_pubkey() };

    // And create the transaction.
    let tx = Transaction {
        version: transaction::Version::TWO,  // Post BIP-68.
        lock_time: absolute::LockTime::ZERO, // Ignore the locktime.
        input: vec![input_0, input_1],
        output: vec![multi, change],
    };

    // Now the creator can create the PSBT.
    let mut psbt = v0::Psbt::from_unsigned_tx(tx)?;

    // Update the PSBT with the inputs described by `previous_output_a` and `previous_output_b`
    // above, here we get them from Alice and Bob, typically the update would have access to chain
    // data and would get them from there.
    psbt.inputs[0].witness_utxo = Some(alice.input_utxo());
    psbt.inputs[1].witness_utxo = Some(bob.input_utxo());

    // Since we are spending 2 p2wpkh inputs there are no other updates needed.

    // Each party signs a copy of the PSBT.
    let signed_by_a = alice.sign(psbt.clone())?;
    let _ = bob.sign(signed_by_a)?;

    // At this stage we would usually finalize with miniscript and extract the transaction.

    Ok(())
}

/// Creates a 2-of-2 multisig script locking to a and b's keys.
fn multisig_witness_script(a: &PublicKey, b: &PublicKey) -> ScriptBuf {
    script::Builder::new()
        .push_int(2)
        .push_key(a)
        .push_key(b)
        .push_int(2)
        .push_opcode(OP_CHECKMULTISIG)
        .into_script()
}

/// Party 1 in a 2-of-2 multisig.
pub struct Alice(Entity);

impl Alice {
    /// Creates a new actor with random keys.
    pub fn new() -> Self { Self(Entity::new_random()) }

    /// Returns the public key for this entity.
    pub fn public_key(&self) -> bitcoin::PublicKey { self.0.public_key() }

    /// Alice provides an input to be used to create the multisig and the details required to get
    /// some change back (change address and amount).
    pub fn contribute_to_multisig(&self) -> (OutPoint, Address, Amount) {
        // An obviously invalid output, we just use all zeros then use the `vout` to differentiate
        // Alice's output from Bob's.
        let out = OutPoint { txid: Txid::all_zeros(), vout: 0 };

        // The usual caveat about reusing addresses applies here, this is just an example.
        let compressed =
            CompressedPublicKey::try_from(self.public_key()).expect("uncompressed key");
        let address = Address::p2wpkh(&compressed, Network::Bitcoin);

        // This is a made up value, it is supposed to represent the outpoints value minus the value
        // contributed to the multisig.
        let amount = DUMMY_CHANGE_AMOUNT;

        (out, address, amount)
    }

    /// Provides the actual UTXO that Alice is contributing, this would usually come from the chain.
    pub fn input_utxo(&self) -> TxOut {
        // A dummy script_pubkey representing a UTXO that is locked to a pubkey that Alice controls.
        let script_pubkey =
            ScriptBuf::new_p2wpkh(&self.public_key().wpubkey_hash().expect("uncompressed key"));
        TxOut { value: DUMMY_UTXO_AMOUNT, script_pubkey }
    }

    /// Signs `psbt`.
    pub fn sign(&self, psbt: Psbt) -> anyhow::Result<Psbt> { self.0.sign_ecdsa(psbt) }
}

impl Default for Alice {
    fn default() -> Self { Self::new() }
}

/// Party 2 in a 2-of-2 multisig.
pub struct Bob(Entity);

impl Bob {
    /// Creates a new actor with random keys.
    pub fn new() -> Self { Self(Entity::new_random()) }

    /// Returns the public key for this entity.
    pub fn public_key(&self) -> bitcoin::PublicKey { self.0.public_key() }

    /// Bob provides an input to be used to create the multisig, its the right size so no change.
    pub fn contribute_to_multisig(&self) -> OutPoint {
        // An obviously invalid output, we just use all zeros then use the `vout` to differentiate
        // Alice's output from Bob's.
        OutPoint { txid: Txid::all_zeros(), vout: 1 }
    }

    /// Provides the actual UTXO that Alice is contributing, this would usually come from the chain.
    pub fn input_utxo(&self) -> TxOut {
        // A dummy script_pubkey representing a UTXO that is locked to a pubkey that Bob controls.
        let script_pubkey =
            ScriptBuf::new_p2wpkh(&self.public_key().wpubkey_hash().expect("uncompressed key"));
        TxOut { value: DUMMY_UTXO_AMOUNT, script_pubkey }
    }

    /// Signs `psbt`.
    pub fn sign(&self, psbt: Psbt) -> anyhow::Result<Psbt> { self.0.sign_ecdsa(psbt) }
}

impl Default for Bob {
    fn default() -> Self { Self::new() }
}

/// An entity that can take on one of the PSBT roles.
pub struct Entity {
    sk: secp256k1::SecretKey,
    pk: secp256k1::PublicKey,
}

impl Entity {
    /// Creates a new entity with random keys.
    pub fn new_random() -> Self {
        let (sk, pk) = random_keys();
        Entity { sk, pk }
    }

    /// Returns the private key for this entity.
    fn private_key(&self) -> bitcoin::PrivateKey { bitcoin::PrivateKey::new(self.sk, MAINNET) }

    /// Returns the public key for this entity.
    ///
    /// All examples use segwit so this key is serialize in compressed form.
    pub fn public_key(&self) -> bitcoin::PublicKey { bitcoin::PublicKey::new(self.pk) }

    /// Signs any ECDSA inputs for which we have keys.
    pub fn sign_ecdsa(&self, mut psbt: Psbt) -> anyhow::Result<Psbt> {
        // TODO: Should this be called internally in the `v0` module?
        psbt.signer_checks()?;

        let sk = self.private_key();
        let pk = self.public_key();

        let mut keys = BTreeMap::new();
        keys.insert(pk, sk);
        psbt.sign(&keys, SECP256K1).expect("failed to sign psbt");

        Ok(psbt)
    }
}

/// Creates a set of random secp256k1 keys.
///
/// In a real application these would come from actual secrets.
fn random_keys() -> (secp256k1::SecretKey, secp256k1::PublicKey) {
    let sk = secp256k1::SecretKey::new(&mut rand::thread_rng());
    let pk = sk.public_key(SECP256K1);
    (sk, pk)
}