Skip to content

Commit a95d3bf

Browse files
committed
psbt: untangle some logic in update_item_with_descriptor_helper
We have the function `update_item_with_descriptor_helper` which does a few things: it derives a descriptor (replacing all the xpubs with actual public keys) and updates the appropriate input or output map to map the derived keys to their keysources. It treats Tr outputs differently from other kinds of outputs, because the relevant maps are different. However, in doing so, it duplicates a bunch of work in ways that are hard to follow. Essentially, the algorithm does three things: (a) derives all the keys (and the descriptor), (b) optionally checks that the resulting scriptpubkey is what we expect, and (c) updates the maps. The existing code handles (a) separately for Tr and non-Tr descriptors. In the Tr case, we derive all the keys using Descriptor::<DescriptorPublicKey>::derived_descriptor which derives all the keys and throws away the conversion. Then separately it keeps around the un-derived descriptor, iterates through the keys, and populates the `tap_key_origins` map by re-computing the derivation. In the non-Tr case, we derive all the keys using the `KeySourceLookUp` object, which does exactly the same thing as `derived_descriptor` except that it stores its work in a BTreeMap, which is directly added to the PSBT's `item.bip32_derivation` field. This commit pulls out (a) into common code; it then copies all the data out of the key map into `item.tap_key_origins` along with an empty vector of tapleaves. It then goes through all the leaves, and for each key that appears in each leaf, appends that leaf's hash to the vector of tapleaves. This is still a little ineffecient but will be much cleaner after a later commit when we improve the Taproot SpendInfo structure. The original code dates to Lloyd's 2022 PR rust-bitcoin#339 which introduces logic to populate these maps. That algorithm underwent significant refactoring in response to review comments and I suspect that the duplicated logic went unnoticed after all the refactorings.
1 parent 0c43f97 commit a95d3bf

File tree

1 file changed

+67
-101
lines changed

1 file changed

+67
-101
lines changed

src/psbt/mod.rs

Lines changed: 67 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,119 +1073,87 @@ fn update_item_with_descriptor_helper<F: PsbtFields>(
10731073
item: &mut F,
10741074
descriptor: &Descriptor<DefiniteDescriptorKey>,
10751075
check_script: Option<&Script>,
1076-
// the return value is a tuple here since the two internal calls to it require different info.
1077-
// One needs the derived descriptor and the other needs to know whether the script_pubkey check
1078-
// failed.
1076+
// We return an extra boolean here to indicate an error with `check_script`. We do this
1077+
// because the error is "morally" a UtxoUpdateError::MismatchedScriptPubkey, but some
1078+
// callers expect a `descriptor::ConversionError`, which cannot be produced from a
1079+
// `UtxoUpdateError`, and those callers can't get this error anyway because they pass
1080+
// `None` for `check_script`.
10791081
) -> Result<(Descriptor<bitcoin::PublicKey>, bool), descriptor::ConversionError> {
1080-
let secp = secp256k1::Secp256k1::verification_only();
1082+
let secp = Secp256k1::verification_only();
1083+
1084+
// 1. Derive the descriptor, recording each key derivation in a map from xpubs
1085+
// the keysource used to derive the key.
1086+
let mut bip32_derivation = KeySourceLookUp(BTreeMap::new(), secp);
1087+
let derived = descriptor
1088+
.translate_pk(&mut bip32_derivation)
1089+
.map_err(|e| e.expect_translator_err("No Outer Context errors in translations"))?;
1090+
1091+
// 2. If we have a specific scriptpubkey we are targeting, bail out.
1092+
if let Some(check_script) = check_script {
1093+
if check_script != &derived.script_pubkey() {
1094+
return Ok((derived, false));
1095+
}
1096+
}
10811097

1082-
let derived = if let Descriptor::Tr(_) = &descriptor {
1083-
let derived = descriptor.derived_descriptor(&secp)?;
1098+
// 3. Update the PSBT fields using the derived key map.
1099+
if let Descriptor::Tr(ref tr_derived) = &derived {
1100+
let spend_info = tr_derived.spend_info();
1101+
let KeySourceLookUp(xpub_map, _) = bip32_derivation;
10841102

1085-
if let Some(check_script) = check_script {
1086-
if check_script != &derived.script_pubkey() {
1087-
return Ok((derived, false));
1088-
}
1103+
*item.tap_internal_key() = Some(spend_info.internal_key());
1104+
for (derived_key, key_source) in xpub_map {
1105+
item.tap_key_origins()
1106+
.insert(derived_key.to_x_only_pubkey(), (vec![], key_source));
1107+
}
1108+
if let Some(merkle_root) = item.tap_merkle_root() {
1109+
*merkle_root = spend_info.merkle_root();
10891110
}
10901111

1091-
// NOTE: they will both always be Tr
1092-
if let (Descriptor::Tr(tr_derived), Descriptor::Tr(tr_xpk)) = (&derived, descriptor) {
1093-
let spend_info = tr_derived.spend_info();
1094-
let ik_derived = spend_info.internal_key();
1095-
let ik_xpk = tr_xpk.internal_key();
1096-
if let Some(merkle_root) = item.tap_merkle_root() {
1097-
*merkle_root = spend_info.merkle_root();
1112+
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);
1116+
builder = builder
1117+
.add_leaf(leaf_derived.depth(), leaf_script.0.clone())
1118+
.expect("Computing spend data on a valid tree should always succeed");
1119+
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");
1123+
tap_scripts.insert(control_block, leaf_script);
10981124
}
1099-
*item.tap_internal_key() = Some(ik_derived);
1100-
item.tap_key_origins().insert(
1101-
ik_derived,
1102-
(
1103-
vec![],
1104-
(
1105-
ik_xpk.master_fingerprint(),
1106-
ik_xpk
1107-
.full_derivation_path()
1108-
.ok_or(descriptor::ConversionError::MultiKey)?,
1109-
),
1110-
),
1111-
);
1112-
1113-
let mut builder = taproot::TaprootBuilder::new();
1114-
1115-
for (leaf_derived, leaf) in tr_derived.leaves().zip(tr_xpk.leaves()) {
1116-
debug_assert_eq!(leaf_derived.depth(), leaf.depth());
1117-
let leaf_script = (leaf_derived.compute_script(), leaf_derived.leaf_version());
1118-
let tapleaf_hash = TapLeafHash::from_script(&leaf_script.0, leaf_script.1);
1119-
builder = builder
1120-
.add_leaf(leaf_derived.depth(), leaf_script.0.clone())
1121-
.expect("Computing spend data on a valid tree should always succeed");
1122-
if let Some(tap_scripts) = item.tap_scripts() {
1123-
let control_block = spend_info
1124-
.control_block(&leaf_script)
1125-
.expect("Control block must exist in script map for every known leaf");
1126-
tap_scripts.insert(control_block, leaf_script);
1127-
}
11281125

1129-
for (pk_pkh_derived, pk_pkh_xpk) in leaf_derived
1130-
.miniscript()
1131-
.iter_pk()
1132-
.zip(leaf.miniscript().iter_pk())
1133-
{
1134-
let (xonly, xpk) = (pk_pkh_derived.to_x_only_pubkey(), pk_pkh_xpk);
1135-
1136-
let xpk_full_derivation_path = xpk
1137-
.full_derivation_path()
1138-
.ok_or(descriptor::ConversionError::MultiKey)?;
1139-
item.tap_key_origins()
1140-
.entry(xonly)
1141-
.and_modify(|(tapleaf_hashes, _)| {
1142-
if tapleaf_hashes.last() != Some(&tapleaf_hash) {
1143-
tapleaf_hashes.push(tapleaf_hash);
1144-
}
1145-
})
1146-
.or_insert_with(|| {
1147-
(
1148-
vec![tapleaf_hash],
1149-
(xpk.master_fingerprint(), xpk_full_derivation_path),
1150-
)
1151-
});
1126+
for leaf_pk in leaf_derived.miniscript().iter_pk() {
1127+
let tapleaf_hashes = &mut item
1128+
.tap_key_origins()
1129+
.get_mut(&leaf_pk.to_x_only_pubkey())
1130+
.expect("inserted all keys above")
1131+
.0;
1132+
if tapleaf_hashes.last() != Some(&tapleaf_hash) {
1133+
tapleaf_hashes.push(tapleaf_hash);
11521134
}
11531135
}
1136+
}
11541137

1155-
// Ensure there are no duplicated leaf hashes. This can happen if some of them were
1156-
// already present in the map when this function is called, since this only appends new
1157-
// data to the psbt without checking what's already present.
1158-
for (tapleaf_hashes, _) in item.tap_key_origins().values_mut() {
1159-
tapleaf_hashes.sort();
1160-
tapleaf_hashes.dedup();
1161-
}
1162-
1163-
match item.tap_tree() {
1164-
// Only set the tap_tree if the item supports it (it's an output) and the descriptor actually
1165-
// contains one, otherwise it'll just be empty
1166-
Some(tap_tree) if tr_derived.tap_tree().is_some() => {
1167-
*tap_tree = Some(
1168-
taproot::TapTree::try_from(builder)
1169-
.expect("The tree should always be valid"),
1170-
);
1171-
}
1172-
_ => {}
1173-
}
1138+
// Ensure there are no duplicated leaf hashes. This can happen if some of them were
1139+
// already present in the map when this function is called, since this only appends new
1140+
// data to the psbt without checking what's already present.
1141+
for (tapleaf_hashes, _) in item.tap_key_origins().values_mut() {
1142+
tapleaf_hashes.sort();
1143+
tapleaf_hashes.dedup();
11741144
}
11751145

1176-
derived
1177-
} else {
1178-
let mut bip32_derivation = KeySourceLookUp(BTreeMap::new(), Secp256k1::verification_only());
1179-
let derived = descriptor
1180-
.translate_pk(&mut bip32_derivation)
1181-
.map_err(|e| e.expect_translator_err("No Outer Context errors in translations"))?;
1182-
1183-
if let Some(check_script) = check_script {
1184-
if check_script != &derived.script_pubkey() {
1185-
return Ok((derived, false));
1146+
match item.tap_tree() {
1147+
// Only set the tap_tree if the item supports it (it's an output) and the descriptor actually
1148+
// contains one, otherwise it'll just be empty
1149+
Some(tap_tree) if tr_derived.tap_tree().is_some() => {
1150+
*tap_tree = Some(
1151+
taproot::TapTree::try_from(builder).expect("The tree should always be valid"),
1152+
);
11861153
}
1154+
_ => {}
11871155
}
1188-
1156+
} else {
11891157
item.bip32_derivation().append(&mut bip32_derivation.0);
11901158

11911159
match &derived {
@@ -1203,8 +1171,6 @@ fn update_item_with_descriptor_helper<F: PsbtFields>(
12031171
Descriptor::Wsh(wsh) => *item.witness_script() = Some(wsh.inner_script()),
12041172
Descriptor::Tr(_) => unreachable!("Tr is dealt with separately"),
12051173
}
1206-
1207-
derived
12081174
};
12091175

12101176
Ok((derived, true))

0 commit comments

Comments
 (0)