Rust Taproot Transactions
This uses a bech32m encoded address and sends the amount from the UTXO to another bech32m address (- fees)
use std::env;
use simple_wallet::p2tr_key::p2tr;use structure::{constants::SEED, input_data::regtest_call::RegtestCall};
pub mod simple_wallet;pub mod structure;
fn main() { env::set_var("RUST_BACKTRACE", "full");
let client = RegtestCall::init( &vec!["bcrt1prnpxwf9tpjm4jll4ts72s2xscq66qxep6w9hf6sqnvwe9t4gvqasklfhyj"], "polar12_wallet", 150, );
p2tr(Some(SEED), client);}pub const NETWORK: bitcoin::Network = bitcoin::Network::Regtest;pub const TIP: u64 = 2000;pub const SEED: &str = "1d454c6ab705f999d97e6465300a79a9595fb5ae1186ae20e33e12bea606c094";pub const LOG: bool = true;use std::{collections::BTreeMap, str::FromStr};
use bitcoin::{Address, BlockHash, OutPoint, Script, Transaction, TxIn, Txid, Witness};use bitcoincore_rpc::{ bitcoincore_rpc_json::{ImportMultiResult, LoadWalletResult}, jsonrpc::serde_json::{json, Map, Value}, Client, RpcApi,};
use super::RpcCall;
pub struct RegtestCall { amount: u64, tx_in: Vec<TxIn>, previous_tx: Vec<Transaction>, address_list: Vec<Address>, client: Client,}
impl RpcCall for RegtestCall { fn contract_source(&self) -> Vec<Transaction> { return self.previous_tx.clone(); }
fn prev_input(&self) -> Vec<TxIn> { return self.tx_in.clone(); }
fn script_get_balance(&self) -> u64 { return self.amount.clone(); }
fn fee(&self) -> u64 { return 100000; } fn broadcasts_transacton(&self, tx: &Transaction) { let tx_id = RegtestCall::get_client().send_raw_transaction(tx).unwrap(); println!("transaction send transaction id is: {}", tx_id) }}
impl<'a> RegtestCall { pub fn get_client() -> Client { return Client::new( "http://127.0.0.1:18447", bitcoincore_rpc::Auth::UserPass("polaruser".to_string(), "polarpass".to_owned()), ) .unwrap(); }
pub fn init(address_list: &Vec<&str>, wallet_name: &str, mine: u8) -> Self { let client = RegtestCall::get_client();
address_list.iter().for_each(|address| { let mut addr = "addr(".to_owned(); addr.push_str(&address); addr.push_str(")");
client .get_descriptor_info(&addr) .map(|desc| { let descriptor = desc.descriptor; println!("assigned a descriptor {} ", descriptor); create_wallet(&client, wallet_name, mine, &descriptor) }) .unwrap(); }); return RegtestCall::from_string(address_list); }
pub fn generatetodescriptor( client: &Client, block_num: u64, address: &Address, ) -> Vec<BlockHash> { return client.generate_to_address(block_num, address).unwrap(); }
pub fn transaction_broadcast(&self, tx: &Transaction) -> Txid { let tx_id = RegtestCall::get_client().send_raw_transaction(tx).unwrap(); println!("transaction id: {}", &tx_id); return tx_id; }
pub fn from_string(address_list: &'a Vec<&str>) -> RegtestCall { return RegtestCall::from_address( address_list .iter() .map(|addr| Address::from_str(addr).unwrap()) .collect::<Vec<Address>>(), ); }
pub fn update(&self) -> Self { let tx_in = RegtestCall::get_txin(&self.client, &self.address_list).to_vec(); let previous_tx = RegtestCall::get_previous_tx(&self.client, &tx_in);
let amt = RegtestCall::get_amount(&previous_tx, &self.address_list); return RegtestCall { amount: amt, tx_in, previous_tx, address_list: self.address_list.clone(), client: RegtestCall::get_client(), }; }
fn get_txin(client: &Client, address_list: &Vec<Address>) -> Vec<TxIn> { return client .list_unspent( None, None, Some(&address_list.clone().iter().collect::<Vec<&Address>>()), None, None, ) .unwrap() .iter() .map(|entry| { return TxIn { previous_output: OutPoint::new(entry.txid, entry.vout), script_sig: Script::new(), sequence: bitcoin::Sequence(0xFFFFFFFF), witness: Witness::default(), }; }) .collect::<Vec<TxIn>>(); }
fn get_previous_tx(client: &Client, tx_in: &Vec<TxIn>) -> Vec<Transaction> { return tx_in .iter() .map(|tx_id| { let result = client .get_transaction(&tx_id.previous_output.txid, Some(true)) .unwrap() .transaction() .unwrap(); return result; }) .collect::<Vec<Transaction>>(); }
fn get_amount(previous_tx: &Vec<Transaction>, address_list: &Vec<Address>) -> u64 { return previous_tx .iter() .map(|tx| { tx.output .iter() .filter(|p| { address_list .clone() .iter() .map(|addr| addr.script_pubkey()) .collect::<Vec<Script>>() .contains(&p.script_pubkey) }) .map(|output_tx| output_tx.value) .sum::<u64>() }) .sum::<u64>(); }
pub fn from_address(address_list: Vec<Address>) -> Self { let client = RegtestCall::get_client(); let tx_in = RegtestCall::get_txin(&client, &address_list).to_vec(); let previous_tx = RegtestCall::get_previous_tx(&client, &tx_in); let amt = RegtestCall::get_amount(&previous_tx, &address_list); return RegtestCall { amount: amt, tx_in, previous_tx, address_list, client, }; }}
fn create_wallet(client: &Client, wallet_name: &str, mine: u8, desc: &String) { if client .list_wallets() .unwrap() .contains(&wallet_name.to_owned()) { importdescriptors(client, desc, mine); return; } client .create_wallet(wallet_name, Some(true), Some(true), None, Some(false)) .map(|load_wallet| { println!("wallet {} created successfully ", load_wallet.name);
if let Some(msg) = load_wallet.warning { println!("Warning! {}", msg) }
println!("wallet {} created successfully ", load_wallet.name);
importdescriptors(client, desc, mine); }) .unwrap();}
fn importdescriptors(client: &Client, desc: &String, mine: u8) { let mut params = Map::new(); params.insert("desc".to_owned(), Value::String(desc.to_string())); params.insert("timestamp".to_owned(), Value::String("now".to_owned()));
client .call::<Vec<ImportMultiResult>>( "importdescriptors", &[Value::Array([Value::Object(params)].to_vec())], ) .map(|import| { import.iter().for_each(|result| { if result.success { println!("descriptor successfully imported"); } result.error.iter().for_each(|err| { panic!("error importing wallet {:#?}", err); }) });
mine_to_descriptors(client, mine, desc); }) .unwrap()}
fn mine_to_descriptors(client: &Client, mine: u8, desc: &String) { client .call::<Vec<BlockHash>>("generatetodescriptor", &[json!(mine), json!(desc)]) .unwrap(); println!("successfully mined blocks");}use std::str::FromStr;
use bitcoin::{ psbt::{Input, PartiallySignedTransaction, Prevouts}, schnorr::TapTweak, secp256k1::{All, Message, Scalar, Secp256k1, SecretKey}, util::{ bip32::{ExtendedPrivKey, ExtendedPubKey}, sighash::SighashCache, }, Address, KeyPair, PackedLockTime, SchnorrSig, Transaction, TxOut,};use miniscript::psbt::PsbtExt;
use crate::structure::{ constants::NETWORK, input_data::{regtest_call::RegtestCall, RpcCall},};
pub fn p2tr(secret_string: Option<&str>, client: impl RpcCall) { let secp = Secp256k1::new(); let secret = match secret_string { Some(sec_str) => SecretKey::from_str(&sec_str).unwrap(), None => { let scalar = Scalar::random(); let secret_key = SecretKey::from_slice(&scalar.to_be_bytes()).unwrap(); println!("secret_key: {}", secret_key.display_secret()); secret_key } };
let key_pair = KeyPair::from_secret_key(&secp, &secret);
let (x_only, _) = key_pair.x_only_public_key();
let address = Address::p2tr(&secp, x_only, None, NETWORK);
println!("address {}", address.to_string());
let ext_pub = ExtendedPubKey::from_priv( &secp, &ExtendedPrivKey::new_master(NETWORK, &secret.secret_bytes()).unwrap(), );
println!("xpub {}", ext_pub.to_string());
if (secret_string.is_none()) { return; }
let tx_in_list = client.prev_input();
let transaction_list = client.contract_source();
let prevouts = transaction_list .iter() .flat_map(|tx| tx.output.clone()) .filter(|p| address.script_pubkey().eq(&p.script_pubkey)) .collect::<Vec<TxOut>>();
let total: u64 = prevouts.iter().map(|tx_out| tx_out.value).sum();
let out_put = create_output(total, &client);
let unsigned_tx = Transaction { version: 2, lock_time: PackedLockTime(0), input: tx_in_list, output: out_put, };
let mut psbt = PartiallySignedTransaction::from_unsigned_tx(unsigned_tx.clone()).unwrap(); psbt.inputs = sign_all_unsigned_tx(&secp, &prevouts, &unsigned_tx, &key_pair);
let tx = psbt.finalize(&secp).unwrap().extract_tx();
client.broadcasts_transacton(&tx);}
fn create_output<'a>(total: u64, client: &'a impl RpcCall) -> Vec<TxOut> { let out_put = vec![TxOut { value: total - client.fee(), script_pubkey: Address::from_str( "bcrt1prnpxwf9tpjm4jll4ts72s2xscq66qxep6w9hf6sqnvwe9t4gvqasklfhyj", ) .unwrap() .script_pubkey(), }]; out_put}
fn sign_all_unsigned_tx( secp: &Secp256k1<All>, prevouts: &Vec<TxOut>, unsigned_tx: &Transaction, key_pair: &KeyPair,) -> Vec<Input> { return prevouts .iter() .enumerate() .map(|(index, tx_out)| { sign_tx(secp, index, unsigned_tx, &prevouts, key_pair, tx_out).clone() }) .collect();}
fn sign_tx( secp: &Secp256k1<All>, index: usize, unsigned_tx: &Transaction, prevouts: &Vec<TxOut>, key_pair: &KeyPair, tx_out: &TxOut,) -> Input { let sighash = SighashCache::new(&mut unsigned_tx.clone()) .taproot_key_spend_signature_hash( index, &Prevouts::All(&prevouts), bitcoin::SchnorrSighashType::AllPlusAnyoneCanPay, ) .unwrap();
let message = Message::from_slice(&sighash).unwrap();
let tweaked_key_pair = key_pair.tap_tweak(&secp, None);
let sig = secp.sign_schnorr(&message, &tweaked_key_pair.to_inner());
secp.verify_schnorr( &sig, &message, &tweaked_key_pair.to_inner().x_only_public_key().0, ) .unwrap();
let schnorr_sig = SchnorrSig { sig, hash_ty: bitcoin::SchnorrSighashType::AllPlusAnyoneCanPay, };
let mut input = Input::default();
input.witness_script = Some(tx_out.script_pubkey.clone());
input.tap_key_sig = Some(schnorr_sig);
input.witness_utxo = Some(tx_out.clone());
return input;}