Rust Taproot Transactions
This uses a bech32m encoded address and sends the amount from the UTXO to another bech32m address (- fees)
main.rs
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);
}
constants.rs
pub const NETWORK: bitcoin::Network = bitcoin::Network::Regtest;
pub const TIP: u64 = 2000;
pub const SEED: &str = "1d454c6ab705f999d97e6465300a79a9595fb5ae1186ae20e33e12bea606c094";
pub const LOG: bool = true;
regtest_call.rs
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");
}
p2tr_keys
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;
}
Last updated