Rust Taproot Transactions

This uses a bech32m encoded address and sends the amount from the UTXO to another bech32m address (- fees)
main.rs
1
use std::env;
2
3
use simple_wallet::p2tr_key::p2tr;
4
use structure::{constants::SEED, input_data::regtest_call::RegtestCall};
5
6
pub mod simple_wallet;
7
pub mod structure;
8
9
fn main() {
10
env::set_var("RUST_BACKTRACE", "full");
11
12
let client = RegtestCall::init(
13
&vec!["bcrt1prnpxwf9tpjm4jll4ts72s2xscq66qxep6w9hf6sqnvwe9t4gvqasklfhyj"],
14
"polar12_wallet",
15
150,
16
);
17
18
p2tr(Some(SEED), client);
19
}
constants.rs
1
pub const NETWORK: bitcoin::Network = bitcoin::Network::Regtest;
2
pub const TIP: u64 = 2000;
3
pub const SEED: &str = "1d454c6ab705f999d97e6465300a79a9595fb5ae1186ae20e33e12bea606c094";
4
pub const LOG: bool = true;
regtest_call.rs
1
use std::{collections::BTreeMap, str::FromStr};
2
3
use bitcoin::{Address, BlockHash, OutPoint, Script, Transaction, TxIn, Txid, Witness};
4
use bitcoincore_rpc::{
5
bitcoincore_rpc_json::{ImportMultiResult, LoadWalletResult},
6
jsonrpc::serde_json::{json, Map, Value},
7
Client, RpcApi,
8
};
9
10
use super::RpcCall;
11
12
pub struct RegtestCall {
13
amount: u64,
14
tx_in: Vec<TxIn>,
15
previous_tx: Vec<Transaction>,
16
address_list: Vec<Address>,
17
client: Client,
18
}
19
20
impl RpcCall for RegtestCall {
21
fn contract_source(&self) -> Vec<Transaction> {
22
return self.previous_tx.clone();
23
}
24
25
fn prev_input(&self) -> Vec<TxIn> {
26
return self.tx_in.clone();
27
}
28
29
fn script_get_balance(&self) -> u64 {
30
return self.amount.clone();
31
}
32
33
fn fee(&self) -> u64 {
34
return 100000;
35
}
36
fn broadcasts_transacton(&self, tx: &Transaction) {
37
let tx_id = RegtestCall::get_client().send_raw_transaction(tx).unwrap();
38
println!("transaction send transaction id is: {}", tx_id)
39
}
40
}
41
42
impl<'a> RegtestCall {
43
pub fn get_client() -> Client {
44
return Client::new(
45
"http://127.0.0.1:18447",
46
bitcoincore_rpc::Auth::UserPass("polaruser".to_string(), "polarpass".to_owned()),
47
)
48
.unwrap();
49
}
50
51
pub fn init(address_list: &Vec<&str>, wallet_name: &str, mine: u8) -> Self {
52
let client = RegtestCall::get_client();
53
54
address_list.iter().for_each(|address| {
55
let mut addr = "addr(".to_owned();
56
addr.push_str(&address);
57
addr.push_str(")");
58
59
client
60
.get_descriptor_info(&addr)
61
.map(|desc| {
62
let descriptor = desc.descriptor;
63
println!("assigned a descriptor {} ", descriptor);
64
create_wallet(&client, wallet_name, mine, &descriptor)
65
})
66
.unwrap();
67
});
68
return RegtestCall::from_string(address_list);
69
}
70
71
pub fn generatetodescriptor(
72
client: &Client,
73
block_num: u64,
74
address: &Address,
75
) -> Vec<BlockHash> {
76
return client.generate_to_address(block_num, address).unwrap();
77
}
78
79
pub fn transaction_broadcast(&self, tx: &Transaction) -> Txid {
80
let tx_id = RegtestCall::get_client().send_raw_transaction(tx).unwrap();
81
println!("transaction id: {}", &tx_id);
82
return tx_id;
83
}
84
85
pub fn from_string(address_list: &'a Vec<&str>) -> RegtestCall {
86
return RegtestCall::from_address(
87
address_list
88
.iter()
89
.map(|addr| Address::from_str(addr).unwrap())
90
.collect::<Vec<Address>>(),
91
);
92
}
93
94
pub fn update(&self) -> Self {
95
let tx_in = RegtestCall::get_txin(&self.client, &self.address_list).to_vec();
96
let previous_tx = RegtestCall::get_previous_tx(&self.client, &tx_in);
97
98
let amt = RegtestCall::get_amount(&previous_tx, &self.address_list);
99
return RegtestCall {
100
amount: amt,
101
tx_in,
102
previous_tx,
103
address_list: self.address_list.clone(),
104
client: RegtestCall::get_client(),
105
};
106
}
107
108
fn get_txin(client: &Client, address_list: &Vec<Address>) -> Vec<TxIn> {
109
return client
110
.list_unspent(
111
None,
112
None,
113
Some(&address_list.clone().iter().collect::<Vec<&Address>>()),
114
None,
115
None,
116
)
117
.unwrap()
118
.iter()
119
.map(|entry| {
120
return TxIn {
121
previous_output: OutPoint::new(entry.txid, entry.vout),
122
script_sig: Script::new(),
123
sequence: bitcoin::Sequence(0xFFFFFFFF),
124
witness: Witness::default(),
125
};
126
})
127
.collect::<Vec<TxIn>>();
128
}
129
130
fn get_previous_tx(client: &Client, tx_in: &Vec<TxIn>) -> Vec<Transaction> {
131
return tx_in
132
.iter()
133
.map(|tx_id| {
134
let result = client
135
.get_transaction(&tx_id.previous_output.txid, Some(true))
136
.unwrap()
137
.transaction()
138
.unwrap();
139
return result;
140
})
141
.collect::<Vec<Transaction>>();
142
}
143
144
fn get_amount(previous_tx: &Vec<Transaction>, address_list: &Vec<Address>) -> u64 {
145
return previous_tx
146
.iter()
147
.map(|tx| {
148
tx.output
149
.iter()
150
.filter(|p| {
151
address_list
152
.clone()
153
.iter()
154
.map(|addr| addr.script_pubkey())
155
.collect::<Vec<Script>>()
156
.contains(&p.script_pubkey)
157
})
158
.map(|output_tx| output_tx.value)
159
.sum::<u64>()
160
})
161
.sum::<u64>();
162
}
163
164
pub fn from_address(address_list: Vec<Address>) -> Self {
165
let client = RegtestCall::get_client();
166
let tx_in = RegtestCall::get_txin(&client, &address_list).to_vec();
167
let previous_tx = RegtestCall::get_previous_tx(&client, &tx_in);
168
let amt = RegtestCall::get_amount(&previous_tx, &address_list);
169
return RegtestCall {
170
amount: amt,
171
tx_in,
172
previous_tx,
173
address_list,
174
client,
175
};
176
}
177
}
178
179
fn create_wallet(client: &Client, wallet_name: &str, mine: u8, desc: &String) {
180
if client
181
.list_wallets()
182
.unwrap()
183
.contains(&wallet_name.to_owned())
184
{
185
importdescriptors(client, desc, mine);
186
return;
187
}
188
client
189
.create_wallet(wallet_name, Some(true), Some(true), None, Some(false))
190
.map(|load_wallet| {
191
println!("wallet {} created successfully ", load_wallet.name);
192
193
if let Some(msg) = load_wallet.warning {
194
println!("Warning! {}", msg)
195
}
196
197
println!("wallet {} created successfully ", load_wallet.name);
198
199
importdescriptors(client, desc, mine);
200
})
201
.unwrap();
202
}
203
204
fn importdescriptors(client: &Client, desc: &String, mine: u8) {
205
let mut params = Map::new();
206
params.insert("desc".to_owned(), Value::String(desc.to_string()));
207
params.insert("timestamp".to_owned(), Value::String("now".to_owned()));
208
209
client
210
.call::<Vec<ImportMultiResult>>(
211
"importdescriptors",
212
&[Value::Array([Value::Object(params)].to_vec())],
213
)
214
.map(|import| {
215
import.iter().for_each(|result| {
216
if result.success {
217
println!("descriptor successfully imported");
218
}
219
result.error.iter().for_each(|err| {
220
panic!("error importing wallet {:#?}", err);
221
})
222
});
223
224
mine_to_descriptors(client, mine, desc);
225
})
226
.unwrap()
227
}
228
229
fn mine_to_descriptors(client: &Client, mine: u8, desc: &String) {
230
client
231
.call::<Vec<BlockHash>>("generatetodescriptor", &[json!(mine), json!(desc)])
232
.unwrap();
233
println!("successfully mined blocks");
234
}
p2tr_keys
1
use std::str::FromStr;
2
3
use bitcoin::{
4
psbt::{Input, PartiallySignedTransaction, Prevouts},
5
schnorr::TapTweak,
6
secp256k1::{All, Message, Scalar, Secp256k1, SecretKey},
7
util::{
8
bip32::{ExtendedPrivKey, ExtendedPubKey},
9
sighash::SighashCache,
10
},
11
Address, KeyPair, PackedLockTime, SchnorrSig, Transaction, TxOut,
12
};
13
use miniscript::psbt::PsbtExt;
14
15
use crate::structure::{
16
constants::NETWORK,
17
input_data::{regtest_call::RegtestCall, RpcCall},
18
};
19
20
pub fn p2tr(secret_string: Option<&str>, client: impl RpcCall) {
21
let secp = Secp256k1::new();
22
let secret = match secret_string {
23
Some(sec_str) => SecretKey::from_str(&sec_str).unwrap(),
24
None => {
25
let scalar = Scalar::random();
26
let secret_key = SecretKey::from_slice(&scalar.to_be_bytes()).unwrap();
27
println!("secret_key: {}", secret_key.display_secret());
28
secret_key
29
}
30
};
31
32
let key_pair = KeyPair::from_secret_key(&secp, &secret);
33
34
let (x_only, _) = key_pair.x_only_public_key();
35
36
let address = Address::p2tr(&secp, x_only, None, NETWORK);
37
38
println!("address {}", address.to_string());
39
40
let ext_pub = ExtendedPubKey::from_priv(
41
&secp,
42
&ExtendedPrivKey::new_master(NETWORK, &secret.secret_bytes()).unwrap(),
43
);
44
45
println!("xpub {}", ext_pub.to_string());
46
47
if (secret_string.is_none()) {
48
return;
49
}
50
51
let tx_in_list = client.prev_input();
52
53
let transaction_list = client.contract_source();
54
55
let prevouts = transaction_list
56
.iter()
57
.flat_map(|tx| tx.output.clone())
58
.filter(|p| address.script_pubkey().eq(&p.script_pubkey))
59
.collect::<Vec<TxOut>>();
60
61
let total: u64 = prevouts.iter().map(|tx_out| tx_out.value).sum();
62
63
let out_put = create_output(total, &client);
64
65
let unsigned_tx = Transaction {
66
version: 2,
67
lock_time: PackedLockTime(0),
68
input: tx_in_list,
69
output: out_put,
70
};
71
72
let mut psbt = PartiallySignedTransaction::from_unsigned_tx(unsigned_tx.clone()).unwrap();
73
psbt.inputs = sign_all_unsigned_tx(&secp, &prevouts, &unsigned_tx, &key_pair);
74
75
let tx = psbt.finalize(&secp).unwrap().extract_tx();
76
77
client.broadcasts_transacton(&tx);
78
}
79
80
fn create_output<'a>(total: u64, client: &'a impl RpcCall) -> Vec<TxOut> {
81
let out_put = vec![TxOut {
82
value: total - client.fee(),
83
script_pubkey: Address::from_str(
84
"bcrt1prnpxwf9tpjm4jll4ts72s2xscq66qxep6w9hf6sqnvwe9t4gvqasklfhyj",
85
)
86
.unwrap()
87
.script_pubkey(),
88
}];
89
out_put
90
}
91
92
fn sign_all_unsigned_tx(
93
secp: &Secp256k1<All>,
94
prevouts: &Vec<TxOut>,
95
unsigned_tx: &Transaction,
96
key_pair: &KeyPair,
97
) -> Vec<Input> {
98
return prevouts
99
.iter()
100
.enumerate()
101
.map(|(index, tx_out)| {
102
sign_tx(secp, index, unsigned_tx, &prevouts, key_pair, tx_out).clone()
103
})
104
.collect();
105
}
106
107
fn sign_tx(
108
secp: &Secp256k1<All>,
109
index: usize,
110
unsigned_tx: &Transaction,
111
prevouts: &Vec<TxOut>,
112
key_pair: &KeyPair,
113
tx_out: &TxOut,
114
) -> Input {
115
let sighash = SighashCache::new(&mut unsigned_tx.clone())
116
.taproot_key_spend_signature_hash(
117
index,
118
&Prevouts::All(&prevouts),
119
bitcoin::SchnorrSighashType::AllPlusAnyoneCanPay,
120
)
121
.unwrap();
122
123
let message = Message::from_slice(&sighash).unwrap();
124
125
let tweaked_key_pair = key_pair.tap_tweak(&secp, None);
126
127
let sig = secp.sign_schnorr(&message, &tweaked_key_pair.to_inner());
128
129
secp.verify_schnorr(
130
&sig,
131
&message,
132
&tweaked_key_pair.to_inner().x_only_public_key().0,
133
)
134
.unwrap();
135
136
let schnorr_sig = SchnorrSig {
137
sig,
138
hash_ty: bitcoin::SchnorrSighashType::AllPlusAnyoneCanPay,
139
};
140
141
let mut input = Input::default();
142
143
input.witness_script = Some(tx_out.script_pubkey.clone());
144
145
input.tap_key_sig = Some(schnorr_sig);
146
147
input.witness_utxo = Some(tx_out.clone());
148
149
return input;
150
}