Skip to content

Commit

Permalink
policies: refactor script derivation
Browse files Browse the repository at this point in the history
Instead of deriving the witness script at a keypath, we instead derive
a descriptor instead.

The reason for this is that witness_script only makes sense for
`wsh(...)` desciptors. When we add `tr(...)` Taproot descriptors, we
will need the Taproot output key instead. So a `witness_script()`
method directly on the policy does not make sense as it does not work
for both variants.
  • Loading branch information
benma committed Jun 13, 2024
1 parent 50b05b6 commit 1f0993a
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 36 deletions.
8 changes: 4 additions & 4 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ impl Payload {
policy: &super::policies::ParsedPolicy,
keypath: &[u32],
) -> Result<Self, Error> {
let witness_script = policy.witness_script_at_keypath(keypath)?;
match &policy.descriptor {
super::policies::Descriptor::Wsh { .. } => Ok(Payload {
data: Sha256::digest(witness_script).to_vec(),
let derived_descriptor = policy.derive_at_keypath(keypath)?;
match derived_descriptor {
super::policies::Descriptor::Wsh(wsh) => Ok(Payload {
data: Sha256::digest(wsh.witness_script()).to_vec(),
output_type: BtcOutputType::P2wsh,
}),
}
Expand Down
82 changes: 51 additions & 31 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,23 +215,32 @@ impl<'a> miniscript::Translator<String, bitcoin::PublicKey, Error>

/// See `ParsedPolicy`.
#[derive(Debug)]
pub struct Wsh {
miniscript_expr: miniscript::Miniscript<String, miniscript::Segwitv0>,
pub struct Wsh<T: miniscript::MiniscriptKey> {
miniscript_expr: miniscript::Miniscript<T, miniscript::Segwitv0>,
}

impl Wsh<bitcoin::PublicKey> {
/// Return the witness script this concrete wsh() descriptor.
pub fn witness_script(&self) -> Vec<u8> {
self.miniscript_expr.encode().as_bytes().to_vec()
}
}

/// See `ParsedPolicy`.
#[derive(Debug)]
pub enum Descriptor {
pub enum Descriptor<T: miniscript::MiniscriptKey> {
// `wsh(...)` policies
Wsh(Wsh),
Wsh(Wsh<T>),
// `tr(...)` Taproot etc. in the future.
}

/// Result of `parse()`.
#[derive(Debug)]
pub struct ParsedPolicy<'a> {
policy: &'a Policy,
pub descriptor: Descriptor,
// String for pubkeys so we can parse and process the placeholder wallet policy keys like
// `@0/**` etc.
pub descriptor: Descriptor<String>,
}

impl<'a> ParsedPolicy<'a> {
Expand Down Expand Up @@ -294,43 +303,52 @@ impl<'a> ParsedPolicy<'a> {
Ok(())
}

/// Derive the witness script of the policy derived at a receive or change path.
/// If is_change is false, the witness script for the receive address is derived.
/// If is_change is true, the witness script for the change address is derived.
/// Derive the descriptor of the policy at a receive or change path.
/// This turns key placeholders into actual pubkeys.
/// If is_change is false, the descriptor for the receive address is derived.
/// If is_change is true, the descriptor for the change address is derived.
/// Example: wsh(and_v(v:pk(@0/**),pk(@1/<20;21>/*))) derived using `is_change=false, address_index=5` derives
/// wsh(and_v(v:pk(@0/0/5),pk(@1/20/5))).
/// The same derived using `is_change=true` derives: wsh(and_v(v:pk(@0/1/5),pk(@1/21/5)))
pub fn witness_script(&self, is_change: bool, address_index: u32) -> Result<Vec<u8>, Error> {
pub fn derive(
&self,
is_change: bool,
address_index: u32,
) -> Result<Descriptor<bitcoin::PublicKey>, Error> {
let mut translator = WalletPolicyPkTranslator {
keys: self.policy.keys.as_ref(),
is_change,
address_index,
};
match &self.descriptor {
Descriptor::Wsh(Wsh { miniscript_expr }) => {
let mut translator = WalletPolicyPkTranslator {
keys: self.policy.keys.as_ref(),
is_change,
address_index,
};
let miniscript_expr = match miniscript_expr.translate_pk(&mut translator) {
Ok(m) => m,
Err(miniscript::TranslateErr::TranslatorErr(e)) => return Err(e),
Err(miniscript::TranslateErr::OuterError(_)) => return Err(Error::Generic),
};
Ok(miniscript_expr.encode().as_bytes().to_vec())
Ok(Descriptor::Wsh(Wsh { miniscript_expr }))
}
}
}

/// Derive the witness script of the policy derived at the given full keypath.
/// Derive the descriptor of the policy derived at the given full keypath.
/// This turns key placeholders into actual pubkeys.
/// Example: wsh(and_v(v:pk(@0/<10;11>/*),pk(@1/<20;21>/*))) with our key [fp/48'/1'/0'/3']xpub...]
/// derived using keypath m/48'/1'/0'/3'/11/5 derives:
/// wsh(and_v(v:pk(@0/11/5),pk(@1/21/5))).
pub fn witness_script_at_keypath(&self, keypath: &[u32]) -> Result<Vec<u8>, Error> {
pub fn derive_at_keypath(
&self,
keypath: &[u32],
) -> Result<Descriptor<bitcoin::PublicKey>, Error> {
match &self.descriptor {
Descriptor::Wsh(Wsh { miniscript_expr }) => {
let (is_change, address_index) = get_change_and_address_index(
miniscript_expr.iter_pk(),
&self.policy.keys,
keypath,
)?;
self.witness_script(is_change, address_index)
self.derive(is_change, address_index)
}
}
}
Expand Down Expand Up @@ -899,7 +917,7 @@ mod tests {
}

#[test]
fn test_witness_script() {
fn test_wsh_witness_script() {
mock_unlocked_using_mnemonic(
"sudden tenant fault inject concert weather maid people chunk youth stumble grit",
"",
Expand All @@ -913,20 +931,22 @@ mod tests {
let address_index = 5;

let witness_script = |pol: &str, keys: &[pb::KeyOriginInfo], is_change: bool| {
hex::encode(
parse(&make_policy(pol, keys))
.unwrap()
.witness_script(is_change, address_index)
.unwrap(),
)
let derived = parse(&make_policy(pol, keys))
.unwrap()
.derive(is_change, address_index)
.unwrap();
match derived {
Descriptor::Wsh(wsh) => hex::encode(wsh.witness_script()),
}
};
let witness_script_at_keypath = |pol: &str, keys: &[pb::KeyOriginInfo], keypath: &[u32]| {
hex::encode(
parse(&make_policy(pol, keys))
.unwrap()
.witness_script_at_keypath(keypath)
.unwrap(),
)
let derived = parse(&make_policy(pol, keys))
.unwrap()
.derive_at_keypath(keypath)
.unwrap();
match derived {
Descriptor::Wsh(wsh) => hex::encode(wsh.witness_script()),
}
};

// pk(key) => <key> OP_CHECKSIG
Expand Down
4 changes: 3 additions & 1 deletion src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,9 @@ fn sighash_script(
ValidatedScriptConfigWithKeypath {
config: ValidatedScriptConfig::Policy(policy),
..
} => policy.witness_script_at_keypath(keypath),
} => match policy.derive_at_keypath(keypath)? {
super::policies::Descriptor::Wsh(wsh) => Ok(wsh.witness_script()),
},
}
}

Expand Down

0 comments on commit 1f0993a

Please sign in to comment.