From: Alekos Filini Date: Thu, 2 Dec 2021 15:38:47 +0000 (+0100) Subject: Add Taproot blog post part 2 X-Git-Url: http://internal-gitweb-vhost/script/%22https:/database/scripts/enum.BloomFlags.html?a=commitdiff_plain;h=92dc5f88bdd1227eafcda6096488956966c94e45;p=bitcoindevkit.org Add Taproot blog post part 2 --- diff --git a/docs/_blog/first_bdk_taproot_tx_part_2.md b/docs/_blog/first_bdk_taproot_tx_part_2.md new file mode 100644 index 0000000000..8af5b5209f --- /dev/null +++ b/docs/_blog/first_bdk_taproot_tx_part_2.md @@ -0,0 +1,911 @@ +--- +title: "The first BDK Taproot TX: a look at the code (Part 2)" +description: "A quick overview of the changes made to bdk, rust-miniscript and rust-bitcoin to make a Taproot transaction" +authors: + - Alekos Filini +date: "2021-11-25" +tags: ["BDK", "taproot", "miniscript"] +permalink: "/blog/2021/11/first-bdk-taproot-tx-look-at-the-code-part-2" +--- + +This is the second part of a two-part blog series in which I talk through the changes made to BDK to make a Taproot transaction. If you haven't read it yet, check out [Part 1]. + +While in the first part I managed to show full raw commits, in this case I will only focus on the relevant changes, otherwise the post would get very long. You can always find the full diff here, if you are interested +in that. + +## Shortcuts + +As mentioned previously, the main goal of this journey for me was to find out what it really takes to support Taproot in BDK. The code shown here wasn't written to be readable and/or maintainable, so +some shortcuts were taken, in particular: + +- No support for BIP32 extended keys: this is probably very quick to add, but in the first "proof of concept" I decided to only work with WIF keys for simplicity +- No support for `SIGHASH_DEFAULT`: this would require some minor changes to a few traits in BDK that still use the "legacy" `SigHashType` enum from rust-bitcoin + +## Utilities + +Let's start with some utilities: + +```rust +pub fn ecdsa_to_schnorr(pk: &ecdsa::PublicKey) -> schnorr::PublicKey { + schnorr::PublicKey::from_slice(&pk.to_bytes()[1..]).expect("Key conversion failure") +} + +pub fn compute_merkle_root( + leaf_hash: &taproot::TapLeafHash, + control_block: &taproot::ControlBlock, +) -> taproot::TapBranchHash { + taproot::TapBranchHash::from_inner( + control_block + .merkle_branch + .as_inner() + .iter() + .fold( + taproot::NodeInfo::new_hidden( + sha256::Hash::from_slice(leaf_hash.as_inner()).expect("Invalid TapLeafHash"), + ), + |acc, branch| { + taproot::NodeInfo::combine(acc, taproot::NodeInfo::new_hidden(*branch)) + .expect("Invalid tree") + }, + ) + .hash() + .into_inner(), + ) +} +``` + +The first function "converts" an ECDSA key to a Schnorr key by dropping the first byte that encodes the key parity, since Schnorr keys are "x-only". + +The second one constructs the merkle root of a taptree given a leaf hash and the corresponding control block. + +## Wrap Fallible Methods + +Many of the methods exposed by a `Descriptor` struct used to be infallible: for instance, it was always possible to "encode" a descriptor into a Bitcoin script by calling the `script_pubkey()` method. + +Unfortunately, taproot descriptors need some more metadata to do that: they can be computed by calling the `spend_info()` method, and they will be cached inside the descriptor, but since it's not guaranteed by the +compiler that the method will be called before trying to encode it, the infallible methods had to be changed to return a `Result`, so that they can fail if the spend info is not present. + +In BDK we call the `spend_info()` method right after "deriving" the descriptor, so it's guaranteed that we will never encounter that error: for this reason, we wrap those methods and call `expect()` on them, to keep +the original code mostly unchanged. + +Here we call `spend_info()` right after deriving the descriptor, if it's a `Tr` variant: + +```diff +@@ -136,10 +133,16 @@ impl AsDerived for Descriptor { + index: u32, + secp: &'s SecpCtx, + ) -> Descriptor> { +- self.derive(index).translate_pk_infallible( ++ let mut derived = self.derive(index).translate_pk_infallible( + |key| DerivedDescriptorKey::new(key.clone(), secp), + |key| DerivedDescriptorKey::new(key.clone(), secp), +- ) ++ ); ++ ++ if let Descriptor::Tr(tr) = &mut derived { ++ tr.spend_info(secp); ++ } ++ ++ derived + } +``` + +And here we wrap the `script_pubkey()` method and call `expect()` on it. Note that we only implement it on `DerivedDescriptor`, because it's not guaranteed that "extended descriptors" will have the cached metadata inside. + +```rust +pub(crate) trait DerivedDescriptorSafeOps { + /// The [`Descriptor::script_pubkey`] method can fail on `Tr` descriptors that don't have the + /// `spend_info` inside. Since we generate those upon derivation, it's guaranteed that the + /// method will not fail on `DerivedDescriptor`s. + fn script_pubkey_derived(&self) -> Script; +} + +impl<'s> DerivedDescriptorSafeOps for Descriptor> { + fn script_pubkey_derived(&self) -> Script { + self.script_pubkey() + .expect("`spend_info` is always present in `DerivedDescriptor`s") + } +} +``` + +## Descriptor Metadata + +In BDK we have a few traits that in a way "unify" the interface of a descriptor: things like the `redeem_script` of an input has to be computed differently depending on the type of descriptor. The traits we define +are implemented on the `DerivedDescriptor` or `ExtendedDescriptor` structs and allow us to quickly get what we need without having to check the descriptor type manually. + +Internally, they are essentially large `match`es that return different things depending on the descriptor variant. Due to some renaming that had been done recently in `miniscript` (not necessarily related to taproot) +we have to update them: + +```diff +@@ -337,6 +339,7 @@ pub(crate) trait DerivedDescriptorMeta { + + pub(crate) trait DescriptorMeta { + fn is_witness(&self) -> bool; ++ fn is_tap(&self) -> bool; + fn get_extended_keys(&self) -> Result>, DescriptorError>; + fn derive_from_hd_keypaths<'s>( + &self, +@@ -358,23 +361,29 @@ pub(crate) trait DescriptorScripts { + + impl<'s> DescriptorScripts for DerivedDescriptor<'s> { + fn psbt_redeem_script(&self) -> Option