Skip to content

Commit e5f50ab

Browse files
committed
Merge #815: [Taproot API Project] replace TaprootSpendInfo with new miniscript-specific structure
3d12b42 fuzz: update taptree regression test to also check control blocks (Andrew Poelstra) c1d4305 tr: add a whole bunch of fixed vector unit tests for TrSpendInfo (Andrew Poelstra) 8bc9400 tr: add conversion from TrSpendInfo to bitcoin::TapTree (Andrew Poelstra) 5fe2d25 tr: replace `bitcoin::TaprootSpendInfo` with `TrSpendInfo`, update psbt (Andrew Poelstra) 795cd4f tr: introduce new `TrSpendInfo` structure which holds a full TapTree (Andrew Poelstra) 27a30e6 psbt: untangle some logic in `update_item_with_descriptor_helper` (Andrew Poelstra) 00495a5 descriptor: add unit test from sanket (Andrew Poelstra) Pull request description: In Miniscript, to compute control blocks, estimate satisfaction costs, or otherwise iterate through all the leaves of a Taptree, we use the `bitcoin::TaprootSpendInfo` structure to maintain a map of all leaves. This map is inappropriate for Miniscript (it may not be appropriate for *anyone* actually..) for a few reasons: * It is a map from Tapleaves' encoding as Script to data about the Tapleaves; but in Miniscript the Script encoding isn't primary and isn't even available for non-`ToPublicKey` keys * This map structure means that if duplicate leaves exist then only one of the dupes will be accessible. * The map structure is also really inefficient; it stores the entire merkle path for each leaf even those these paths significantly overlap, leading to O(n log n) space instead of O(n). * Furthermore, we don't need *any* map because we only ever iterate through the entire tree. We fix all these issues by introducing a new `TrSpendInfo` struct which stores the entire Merkle tree in a flat representation and can produce an iterator over all the leaves. The iterator item can be used to access the Script, the Miniscript, the leaf version, and the control block for each leaf, while the `TrSpendInfo` structure itself can be used to access the internal and external keys. In other words, this one structure efficiently implements APIs for everything that rust-miniscript needs. This completes the Taproot API overhaul project. After this I'll go back to fixing error types, eliminating recursive structures, or overhauling the validation parameters, whichever one seems most tractable from the current state of `master`. ACKs for top commit: sanket1729: ACK 3d12b42 Tree-SHA512: 40fa71ebf22b104304ddbd9b3bf9efdd5d509c071a9461c80eb5f669ecdfd4dd7174cb5fcd2af30f32e45aecd370e077a49fd771eb71edf3f6fe4949c9b80d7d
2 parents 9a9f9b1 + 3d12b42 commit e5f50ab

File tree

7 files changed

+781
-160
lines changed

7 files changed

+781
-160
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 {

fuzz/fuzz_targets/regression_taptree.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::{HashMap, HashSet};
2+
13
use descriptor_fuzz::FuzzPk;
24
use honggfuzz::fuzz;
35
use miniscript::descriptor::Tr;
@@ -27,6 +29,22 @@ fn do_test(data: &[u8]) {
2729
new_si.output_key(),
2830
"merkle root mismatch (left is old, new is right)",
2931
);
32+
33+
// Map every leaf script to a set of all the control blocks
34+
let mut new_cbs = HashMap::new();
35+
for leaf in new_si.leaves() {
36+
new_cbs
37+
.entry(leaf.script())
38+
.or_insert(HashSet::new())
39+
.insert(leaf.control_block().clone());
40+
}
41+
// ...the old code will only ever yield one of them and it's not easy to predict which one
42+
for leaf in new_si.leaves() {
43+
let old_cb = old_si
44+
.control_block(&(leaf.script().into(), leaf.leaf_version()))
45+
.unwrap();
46+
assert!(new_cbs[leaf.script()].contains(&old_cb));
47+
}
3048
}
3149
}
3250
}

src/descriptor/mod.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ pub use self::iter::PkIter;
4444
pub use self::segwitv0::{Wpkh, Wsh, WshInner};
4545
pub use self::sh::{Sh, ShInner};
4646
pub use self::sortedmulti::SortedMultiVec;
47-
pub use self::tr::{TapTree, TapTreeDepthError, TapTreeIter, TapTreeIterItem, Tr};
47+
pub use self::tr::{
48+
TapTree, TapTreeDepthError, TapTreeIter, TapTreeIterItem, Tr, TrSpendInfo, TrSpendInfoIter,
49+
TrSpendInfoIterItem,
50+
};
4851

4952
pub mod checksum;
5053
mod key;
@@ -1547,17 +1550,20 @@ mod tests {
15471550
"tr({},{{pk({}),{{pk({}),or_d(pk({}),pkh({}))}}}})",
15481551
p1, p2, p3, p4, p5
15491552
))
1550-
.unwrap()
1551-
.to_string();
1553+
.unwrap();
15521554

15531555
// p5.to_pubkeyhash() = 516ca378e588a7ed71336147e2a72848b20aca1a
15541556
assert_eq!(
1555-
descriptor,
1557+
descriptor.to_string(),
15561558
format!(
15571559
"tr({},{{pk({}),{{pk({}),or_d(pk({}),pkh({}))}}}})#tvu28c0s",
15581560
p1, p2, p3, p4, p5
15591561
)
1560-
)
1562+
);
1563+
assert_eq!(
1564+
descriptor.spend_info().merkle_root().unwrap().to_string(),
1565+
"e1597abcb76f7cbc0792cf04a9c2d4f39caed1ede0afef772064126f28c69b09"
1566+
);
15611567
}
15621568

15631569
#[test]

src/descriptor/tr/mod.rs

Lines changed: 18 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

@@ -26,8 +21,10 @@ use crate::{
2621
Threshold, ToPublicKey, TranslateErr, Translator,
2722
};
2823

24+
mod spend_info;
2925
mod taptree;
3026

27+
pub use self::spend_info::{TrSpendInfo, TrSpendInfoIter, TrSpendInfoIterItem};
3128
pub use self::taptree::{TapTree, TapTreeDepthError, TapTreeIter, TapTreeIterItem};
3229

3330
/// A taproot descriptor
@@ -43,7 +40,7 @@ pub struct Tr<Pk: MiniscriptKey> {
4340
// The inner `Arc` here is because Rust does not allow us to return a reference
4441
// to the contents of the `Option` from inside a `MutexGuard`. There is no outer
4542
// `Arc` because when this structure is cloned, we create a whole new mutex.
46-
spend_info: Mutex<Option<Arc<TaprootSpendInfo>>>,
43+
spend_info: Mutex<Option<Arc<TrSpendInfo<Pk>>>>,
4744
}
4845

4946
impl<Pk: MiniscriptKey> Clone for Tr<Pk> {
@@ -118,46 +115,21 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
118115
}
119116
}
120117

121-
/// Compute the [`TaprootSpendInfo`] associated with this descriptor if spend data is `None`.
118+
/// Obtain the spending information for this [`Tr`].
122119
///
123-
/// 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.
124124
///
125-
/// [`TaprootSpendInfo`] is only required for spending via the script paths.
126-
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>
127129
where
128130
Pk: ToPublicKey,
129131
{
130-
// If the value is already cache, read it
131-
// read only panics if the lock is poisoned (meaning other thread having a lock panicked)
132-
let read_lock = self.spend_info.lock().expect("Lock poisoned");
133-
if let Some(ref spend_info) = *read_lock {
134-
return Arc::clone(spend_info);
135-
}
136-
drop(read_lock);
137-
138-
// Get a new secp context
139-
// This would be cheap operation after static context support from upstream
140-
let secp = secp256k1::Secp256k1::verification_only();
141-
// Key spend path with no merkle root
142-
let data = if self.tree.is_none() {
143-
TaprootSpendInfo::new_key_spend(&secp, self.internal_key.to_x_only_pubkey(), None)
144-
} else {
145-
let mut builder = TaprootBuilder::new();
146-
for leaf in self.leaves() {
147-
let script = leaf.miniscript().encode();
148-
builder = builder
149-
.add_leaf(leaf.depth(), script)
150-
.expect("Computing spend data on a valid Tree should always succeed");
151-
}
152-
// Assert builder cannot error here because we have a well formed descriptor
153-
match builder.finalize(&secp, self.internal_key.to_x_only_pubkey()) {
154-
Ok(data) => data,
155-
Err(_) => unreachable!("We know the builder can be finalized"),
156-
}
157-
};
158-
let spend_info = Arc::new(data);
159-
*self.spend_info.lock().expect("Lock poisoned") = Some(Arc::clone(&spend_info));
160-
spend_info
132+
TrSpendInfo::from_tr(self)
161133
}
162134

163135
/// Checks whether the descriptor is safe.
@@ -506,7 +478,7 @@ where
506478
absolute_timelock: None,
507479
};
508480
let mut min_wit_len = None;
509-
for leaf in desc.leaves() {
481+
for leaf in spend_info.leaves() {
510482
let mut satisfaction = if allow_mall {
511483
match leaf.miniscript().build_template(provider) {
512484
s @ Satisfaction { stack: Witness::Stack(_), .. } => s,
@@ -523,12 +495,10 @@ where
523495
_ => unreachable!(),
524496
};
525497

526-
let leaf_script = (leaf.compute_script(), LeafVersion::TapScript);
527-
let control_block = spend_info
528-
.control_block(&leaf_script)
529-
.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();
530500

531-
wit.push(Placeholder::TapScript(leaf_script.0));
501+
wit.push(Placeholder::TapScript(script));
532502
wit.push(Placeholder::TapControlBlock(control_block));
533503

534504
let wit_size = witness_size(wit);

0 commit comments

Comments
 (0)