Skip to content

Commit de8e418

Browse files
committed
tr: replace bitcoin::TaprootSpendInfo with TrSpendInfo, update psbt
See previous commit for details about this data structure. This commit stops using the rust-bitcoin `TaprootSpendInfo` in favor of our new `TrSpendInfo` structure. This one allows us to iterate over all the leaves of the Taptree, easily accessing their leaf hashes and control blocks in order, which simplifies satisfaction logic.
1 parent e72c313 commit de8e418

File tree

3 files changed

+23
-57
lines changed

3 files changed

+23
-57
lines changed

bitcoind-tests/tests/test_desc.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{error, fmt};
1010
use actual_rand as rand;
1111
use bitcoin::blockdata::witness::Witness;
1212
use bitcoin::hashes::{sha256d, Hash};
13+
use bitcoin::key::TapTweak as _;
1314
use bitcoin::psbt::Psbt;
1415
use bitcoin::sighash::SighashCache;
1516
use bitcoin::taproot::{LeafVersion, TapLeafHash};
@@ -168,16 +169,15 @@ pub fn test_desc_satisfy(
168169
if let Some(internal_keypair) = internal_keypair {
169170
// ---------------------- Tr key spend --------------------
170171
let internal_keypair = internal_keypair
171-
.add_xonly_tweak(&secp, &tr.spend_info().tap_tweak().to_scalar())
172-
.expect("Tweaking failed");
172+
.tap_tweak(&secp, tr.spend_info().merkle_root());
173173
let sighash_msg = sighash_cache
174174
.taproot_key_spend_signature_hash(0, &prevouts, sighash_type)
175175
.unwrap();
176176
let msg = secp256k1::Message::from_digest(sighash_msg.to_byte_array());
177177
let mut aux_rand = [0u8; 32];
178178
rand::thread_rng().fill_bytes(&mut aux_rand);
179179
let schnorr_sig =
180-
secp.sign_schnorr_with_aux_rand(&msg, &internal_keypair, &aux_rand);
180+
secp.sign_schnorr_with_aux_rand(&msg, &internal_keypair.to_inner(), &aux_rand);
181181
psbt.inputs[0].tap_key_sig =
182182
Some(taproot::Signature { signature: schnorr_sig, sighash_type });
183183
} else {

src/descriptor/tr/mod.rs

Lines changed: 16 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22

33
use core::{cmp, fmt, hash};
44

5-
#[cfg(not(test))] // https://github.com/rust-lang/rust/issues/121684
6-
use bitcoin::secp256k1;
7-
use bitcoin::taproot::{
8-
LeafVersion, TaprootBuilder, TaprootSpendInfo, TAPROOT_CONTROL_BASE_SIZE,
9-
TAPROOT_CONTROL_NODE_SIZE,
10-
};
5+
use bitcoin::taproot::{TAPROOT_CONTROL_BASE_SIZE, TAPROOT_CONTROL_NODE_SIZE};
116
use bitcoin::{opcodes, Address, Network, ScriptBuf, Weight};
127
use sync::Arc;
138

@@ -45,7 +40,7 @@ pub struct Tr<Pk: MiniscriptKey> {
4540
// The inner `Arc` here is because Rust does not allow us to return a reference
4641
// to the contents of the `Option` from inside a `MutexGuard`. There is no outer
4742
// `Arc` because when this structure is cloned, we create a whole new mutex.
48-
spend_info: Mutex<Option<Arc<TaprootSpendInfo>>>,
43+
spend_info: Mutex<Option<Arc<TrSpendInfo<Pk>>>>,
4944
}
5045

5146
impl<Pk: MiniscriptKey> Clone for Tr<Pk> {
@@ -120,46 +115,21 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
120115
}
121116
}
122117

123-
/// Compute the [`TaprootSpendInfo`] associated with this descriptor if spend data is `None`.
118+
/// Obtain the spending information for this [`Tr`].
124119
///
125-
/// If spend data is already computed (i.e it is not `None`), this does not recompute it.
120+
/// The first time this method is called, it computes the full Taproot Merkle tree of
121+
/// all branches as well as the output key which appears on-chain. This is fairly
122+
/// expensive since it requires hashing every branch and then doing an elliptic curve
123+
/// operation. The result is cached and reused on subsequent calls.
126124
///
127-
/// [`TaprootSpendInfo`] is only required for spending via the script paths.
128-
pub fn spend_info(&self) -> Arc<TaprootSpendInfo>
125+
/// This data is needed to compute the Taproot output, so this method is implicitly
126+
/// called through [`Self::script_pubkey`], [`Self::address`], etc. It is also needed
127+
/// to compute the hash needed to sign the output.
128+
pub fn spend_info(&self) -> TrSpendInfo<Pk>
129129
where
130130
Pk: ToPublicKey,
131131
{
132-
// If the value is already cache, read it
133-
// read only panics if the lock is poisoned (meaning other thread having a lock panicked)
134-
let read_lock = self.spend_info.lock().expect("Lock poisoned");
135-
if let Some(ref spend_info) = *read_lock {
136-
return Arc::clone(spend_info);
137-
}
138-
drop(read_lock);
139-
140-
// Get a new secp context
141-
// This would be cheap operation after static context support from upstream
142-
let secp = secp256k1::Secp256k1::verification_only();
143-
// Key spend path with no merkle root
144-
let data = if self.tree.is_none() {
145-
TaprootSpendInfo::new_key_spend(&secp, self.internal_key.to_x_only_pubkey(), None)
146-
} else {
147-
let mut builder = TaprootBuilder::new();
148-
for leaf in self.leaves() {
149-
let script = leaf.miniscript().encode();
150-
builder = builder
151-
.add_leaf(leaf.depth(), script)
152-
.expect("Computing spend data on a valid Tree should always succeed");
153-
}
154-
// Assert builder cannot error here because we have a well formed descriptor
155-
match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) {
156-
Ok(data) => data,
157-
Err(_) => unreachable!("We know the builder can be finalized"),
158-
}
159-
};
160-
let spend_info = Arc::new(data);
161-
*self.spend_info.lock().expect("Lock poisoned") = Some(Arc::clone(&spend_info));
162-
spend_info
132+
TrSpendInfo::from_tr(self)
163133
}
164134

165135
/// Checks whether the descriptor is safe.
@@ -508,7 +478,7 @@ where
508478
absolute_timelock: None,
509479
};
510480
let mut min_wit_len = None;
511-
for leaf in desc.leaves() {
481+
for leaf in spend_info.leaves() {
512482
let mut satisfaction = if allow_mall {
513483
match leaf.miniscript().build_template(provider) {
514484
s @ Satisfaction { stack: Witness::Stack(_), .. } => s,
@@ -525,12 +495,10 @@ where
525495
_ => unreachable!(),
526496
};
527497

528-
let leaf_script = (leaf.compute_script(), LeafVersion::TapScript);
529-
let control_block = spend_info
530-
.control_block(&leaf_script)
531-
.expect("Control block must exist in script map for every known leaf");
498+
let script = ScriptBuf::from(leaf.script());
499+
let control_block = leaf.control_block().clone();
532500

533-
wit.push(Placeholder::TapScript(leaf_script.0));
501+
wit.push(Placeholder::TapScript(script));
534502
wit.push(Placeholder::TapControlBlock(control_block));
535503

536504
let wit_size = witness_size(wit);

src/psbt/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,16 +1110,14 @@ fn update_item_with_descriptor_helper<F: PsbtFields>(
11101110
}
11111111

11121112
let mut builder = taproot::TaprootBuilder::new();
1113-
for leaf_derived in tr_derived.leaves() {
1114-
let leaf_script = (leaf_derived.compute_script(), leaf_derived.leaf_version());
1115-
let tapleaf_hash = TapLeafHash::from_script(&leaf_script.0, leaf_script.1);
1113+
for leaf_derived in spend_info.leaves() {
1114+
let leaf_script = (ScriptBuf::from(leaf_derived.script()), leaf_derived.leaf_version());
1115+
let tapleaf_hash = leaf_derived.leaf_hash();
11161116
builder = builder
11171117
.add_leaf(leaf_derived.depth(), leaf_script.0.clone())
11181118
.expect("Computing spend data on a valid tree should always succeed");
11191119
if let Some(tap_scripts) = item.tap_scripts() {
1120-
let control_block = spend_info
1121-
.control_block(&leaf_script)
1122-
.expect("Control block must exist in script map for every known leaf");
1120+
let control_block = leaf_derived.control_block().clone();
11231121
tap_scripts.insert(control_block, leaf_script);
11241122
}
11251123

0 commit comments

Comments
 (0)