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; const FEE: Amount = Amount::from_sat(1_000); const DUMMY_CHANGE_AMOUNT: Amount = Amount::from_sat(100_000);
fn main() -> anyhow::Result<()> {
let alice = Alice::new();
let bob = Bob::new();
let pk_a = alice.public_key();
let pk_b = bob.public_key();
let (previous_output_a, change_address_a, change_value_a) = alice.contribute_to_multisig();
let previous_output_b = bob.contribute_to_multisig();
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(),
};
let change = TxOut { value: change_value_a, script_pubkey: change_address_a.script_pubkey() };
let witness_script = multisig_witness_script(&pk_a, &pk_b);
let address = Address::p2wsh(&witness_script, MAINNET);
let value = SPEND_AMOUNT * 2 - FEE;
let multi = TxOut { value, script_pubkey: address.script_pubkey() };
let tx = Transaction {
version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, input: vec![input_0, input_1],
output: vec![multi, change],
};
let mut psbt = v0::Psbt::from_unsigned_tx(tx)?;
psbt.inputs[0].witness_utxo = Some(alice.input_utxo());
psbt.inputs[1].witness_utxo = Some(bob.input_utxo());
let signed_by_a = alice.sign(psbt.clone())?;
let _ = bob.sign(signed_by_a)?;
Ok(())
}
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()
}
pub struct Alice(Entity);
impl Alice {
pub fn new() -> Self { Self(Entity::new_random()) }
pub fn public_key(&self) -> bitcoin::PublicKey { self.0.public_key() }
pub fn contribute_to_multisig(&self) -> (OutPoint, Address, Amount) {
let out = OutPoint { txid: Txid::all_zeros(), vout: 0 };
let compressed =
CompressedPublicKey::try_from(self.public_key()).expect("uncompressed key");
let address = Address::p2wpkh(&compressed, Network::Bitcoin);
let amount = DUMMY_CHANGE_AMOUNT;
(out, address, amount)
}
pub fn input_utxo(&self) -> TxOut {
let script_pubkey =
ScriptBuf::new_p2wpkh(&self.public_key().wpubkey_hash().expect("uncompressed key"));
TxOut { value: DUMMY_UTXO_AMOUNT, script_pubkey }
}
pub fn sign(&self, psbt: Psbt) -> anyhow::Result<Psbt> { self.0.sign_ecdsa(psbt) }
}
impl Default for Alice {
fn default() -> Self { Self::new() }
}
pub struct Bob(Entity);
impl Bob {
pub fn new() -> Self { Self(Entity::new_random()) }
pub fn public_key(&self) -> bitcoin::PublicKey { self.0.public_key() }
pub fn contribute_to_multisig(&self) -> OutPoint {
OutPoint { txid: Txid::all_zeros(), vout: 1 }
}
pub fn input_utxo(&self) -> TxOut {
let script_pubkey =
ScriptBuf::new_p2wpkh(&self.public_key().wpubkey_hash().expect("uncompressed key"));
TxOut { value: DUMMY_UTXO_AMOUNT, script_pubkey }
}
pub fn sign(&self, psbt: Psbt) -> anyhow::Result<Psbt> { self.0.sign_ecdsa(psbt) }
}
impl Default for Bob {
fn default() -> Self { Self::new() }
}
pub struct Entity {
sk: secp256k1::SecretKey,
pk: secp256k1::PublicKey,
}
impl Entity {
pub fn new_random() -> Self {
let (sk, pk) = random_keys();
Entity { sk, pk }
}
fn private_key(&self) -> bitcoin::PrivateKey { bitcoin::PrivateKey::new(self.sk, MAINNET) }
pub fn public_key(&self) -> bitcoin::PublicKey { bitcoin::PublicKey::new(self.pk) }
pub fn sign_ecdsa(&self, mut psbt: Psbt) -> anyhow::Result<Psbt> {
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)
}
}
fn random_keys() -> (secp256k1::SecretKey, secp256k1::PublicKey) {
let sk = secp256k1::SecretKey::new(&mut rand::thread_rng());
let pk = sk.public_key(SECP256K1);
(sk, pk)
}