Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for OP_DROP opcode #1

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/descriptor/checksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ impl<'f, 'a> Formatter<'f, 'a> {
}
}

impl<'f, 'a> fmt::Write for Formatter<'f, 'a> {
impl fmt::Write for Formatter<'_, '_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.fmt.write_str(s)?;
self.eng.input(s).map_err(|_| fmt::Error)
Expand Down
147 changes: 126 additions & 21 deletions src/descriptor/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,20 +227,13 @@ impl DescriptorXKey<bip32::Xpriv> {
let xpub = bip32::Xpub::from_priv(secp, &xprv);

let origin = match &self.origin {
Some((fingerprint, path)) => Some((
*fingerprint,
path.into_iter()
.chain(hardened_path.iter())
.cloned()
.collect(),
)),
None => {
if hardened_path.is_empty() {
None
} else {
Some((self.xkey.fingerprint(secp), hardened_path.into()))
}
Some((fingerprint, path)) => {
Some((*fingerprint, path.into_iter().chain(hardened_path).copied().collect()))
}
None if !hardened_path.is_empty() => {
Some((self.xkey.fingerprint(secp), hardened_path.into()))
}
None => None,
};

Ok(DescriptorXKey {
Expand All @@ -252,6 +245,85 @@ impl DescriptorXKey<bip32::Xpriv> {
}
}

impl DescriptorMultiXKey<bip32::Xpriv> {
/// Returns the public version of this multi-key, applying all the hardened derivation steps that
/// are shared among all derivation paths before turning it into a public key.
///
/// Errors if there are hardened derivation steps that are not shared among all paths.
fn to_public<C: Signing>(
&self,
secp: &Secp256k1<C>,
) -> Result<DescriptorMultiXKey<bip32::Xpub>, DescriptorKeyParseError> {
let deriv_paths = self.derivation_paths.paths();

let shared_prefix: Vec<_> = deriv_paths[0]
.into_iter()
.enumerate()
.take_while(|(index, child_num)| {
deriv_paths[1..].iter().all(|other_path| {
other_path.len() > *index && other_path[*index] == **child_num
})
})
.map(|(_, child_num)| *child_num)
.collect();

let suffixes: Vec<Vec<_>> = deriv_paths
.iter()
.map(|path| {
path.into_iter()
.skip(shared_prefix.len())
.map(|child_num| {
if child_num.is_normal() {
Ok(*child_num)
} else {
Err(DescriptorKeyParseError("Can't make a multi-xpriv with hardened derivation steps that are not shared among all paths into a public key."))
}
})
.collect()
})
.collect::<Result<_, _>>()?;

let unhardened = shared_prefix
.iter()
.rev()
.take_while(|c| c.is_normal())
.count();
let last_hardened_idx = shared_prefix.len() - unhardened;
let hardened_path = &shared_prefix[..last_hardened_idx];
let unhardened_path = &shared_prefix[last_hardened_idx..];

let xprv = self
.xkey
.derive_priv(secp, &hardened_path)
.map_err(|_| DescriptorKeyParseError("Unable to derive the hardened steps"))?;
let xpub = bip32::Xpub::from_priv(secp, &xprv);

let origin = match &self.origin {
Some((fingerprint, path)) => {
Some((*fingerprint, path.into_iter().chain(hardened_path).copied().collect()))
}
None if !hardened_path.is_empty() => {
Some((self.xkey.fingerprint(secp), hardened_path.into()))
}
None => None,
};
let new_deriv_paths = suffixes
.into_iter()
.map(|suffix| {
let path = unhardened_path.iter().copied().chain(suffix);
path.collect::<Vec<_>>().into()
})
.collect();

Ok(DescriptorMultiXKey {
origin,
xkey: xpub,
derivation_paths: DerivPaths::new(new_deriv_paths).expect("not empty"),
wildcard: self.wildcard,
})
}
}

/// Descriptor Key parsing errors
// FIXME: replace with error enums
#[derive(Debug, PartialEq, Clone, Copy)]
Expand Down Expand Up @@ -309,20 +381,17 @@ impl DescriptorSecretKey {
/// If the key is an "XPrv", the hardened derivation steps will be applied
/// before converting it to a public key.
///
/// It will return an error if the key is a "multi-xpriv", as we wouldn't
/// always be able to apply hardened derivation steps if there are multiple
/// paths.
/// It will return an error if the key is a "multi-xpriv" that includes
/// hardened derivation steps not shared for all paths.
pub fn to_public<C: Signing>(
&self,
secp: &Secp256k1<C>,
) -> Result<DescriptorPublicKey, DescriptorKeyParseError> {
let pk = match self {
DescriptorSecretKey::Single(prv) => DescriptorPublicKey::Single(prv.to_public(secp)),
DescriptorSecretKey::XPrv(xprv) => DescriptorPublicKey::XPub(xprv.to_public(secp)?),
DescriptorSecretKey::MultiXPrv(_) => {
return Err(DescriptorKeyParseError(
"Can't make an extended private key with multiple paths into a public key.",
))
DescriptorSecretKey::MultiXPrv(xprv) => {
DescriptorPublicKey::MultiXPub(xprv.to_public(secp)?)
}
};

Expand Down Expand Up @@ -627,6 +696,7 @@ impl DescriptorPublicKey {
/// # Errors
///
/// - If `index` is hardened.
/// - If the key contains multi-path derivations
pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
let definite = match self {
DescriptorPublicKey::Single(_) => self,
Expand Down Expand Up @@ -702,7 +772,7 @@ impl FromStr for DescriptorSecretKey {
if key_part.len() <= 52 {
let sk = bitcoin::PrivateKey::from_str(key_part)
.map_err(|_| DescriptorKeyParseError("Error while parsing a WIF private key"))?;
Ok(DescriptorSecretKey::Single(SinglePriv { key: sk, origin: None }))
Ok(DescriptorSecretKey::Single(SinglePriv { key: sk, origin }))
} else {
let (xpriv, derivation_paths, wildcard) = parse_xkey_deriv::<bip32::Xpriv>(key_part)?;
if derivation_paths.len() > 1 {
Expand Down Expand Up @@ -1488,6 +1558,41 @@ mod test {
DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1;>").unwrap_err();
}

#[test]
fn test_multixprv_to_public() {
let secp = secp256k1::Secp256k1::signing_only();

// Works if all hardended derivation steps are part of the shared path
let xprv = get_multipath_xprv("[01020304/5]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1'/2'/3/<4;5>/6");
let xpub = DescriptorPublicKey::MultiXPub(xprv.to_public(&secp).unwrap()); // wrap in a DescriptorPublicKey to have Display
assert_eq!(xpub.to_string(), "[01020304/5/1'/2']tpubDBTRkEMEFkUbk3WTz6CFSULyswkTPpPr38AWibf5TVkB5GxuBxbSbmdFGr3jmswwemknyYxAGoX7BJnKfyPy4WXaHmcrxZhfzFwoUFvFtm5/3/<4;5>/6");

// Fails if they're part of the multi-path specifier or following it
get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3';4'>/5").to_public(&secp).unwrap_err();
get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3;4>/5/6'").to_public(&secp).unwrap_err();
}

#[test]
fn test_parse_wif() {
let secret_key = "[0dd03d09/0'/1/2']5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
.parse()
.unwrap();
if let DescriptorSecretKey::Single(single) = secret_key {
assert_eq!(
single.key.inner,
"0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D"
.parse()
.unwrap()
);
assert_eq!(
single.origin,
Some(("0dd03d09".parse().unwrap(), "m/0'/1/2'".parse().unwrap()))
);
} else {
panic!("expected a DescriptorSecretKey::Single");
}
}

#[test]
#[cfg(feature = "serde")]
fn test_descriptor_public_key_serde() {
Expand Down
44 changes: 40 additions & 4 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,40 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
Ok(Descriptor::Tr(Tr::new(key, script)?))
}

/// For a Taproot descriptor, returns the internal key.
pub fn internal_key(&self) -> Option<&Pk> {
if let Descriptor::Tr(ref tr) = self {
Some(tr.internal_key())
} else {
None
}
}

/// For a Taproot descriptor, returns the [`TapTree`] describing the Taproot tree.
///
/// To obtain the individual leaves of the tree, call [`TapTree::iter`] on the
/// returned value.
pub fn tap_tree(&self) -> Option<&TapTree<Pk>> {
if let Descriptor::Tr(ref tr) = self {
tr.tap_tree().as_ref()
} else {
None
}
}

/// For a Taproot descriptor, returns an iterator over the scripts in the Taptree.
///
/// If the descriptor is not a Taproot descriptor, **or** if the descriptor is a
/// Taproot descriptor containing only a keyspend, returns an empty iterator.
pub fn tap_tree_iter(&self) -> tr::TapTreeIter<Pk> {
if let Descriptor::Tr(ref tr) = self {
if let Some(ref tree) = tr.tap_tree() {
return tree.iter();
}
}
tr::TapTreeIter::empty()
}

/// Get the [DescriptorType] of [Descriptor]
pub fn desc_type(&self) -> DescriptorType {
match *self {
Expand Down Expand Up @@ -596,6 +630,7 @@ impl Descriptor<DescriptorPublicKey> {
///
/// # Errors
/// - If index ≥ 2^31
/// - If the descriptor contains multi-path derivations
pub fn at_derivation_index(
&self,
index: u32,
Expand Down Expand Up @@ -650,7 +685,8 @@ impl Descriptor<DescriptorPublicKey> {
///
/// # Errors
///
/// This function will return an error if hardened derivation is attempted.
/// This function will return an error for multi-path descriptors
/// or if hardened derivation is attempted,
pub fn derived_descriptor<C: secp256k1::Verification>(
&self,
secp: &secp256k1::Secp256k1<C>,
Expand Down Expand Up @@ -696,7 +732,7 @@ impl Descriptor<DescriptorPublicKey> {

struct KeyMapWrapper<'a, C: secp256k1::Signing>(KeyMap, &'a secp256k1::Secp256k1<C>);

impl<'a, C: secp256k1::Signing> Translator<String> for KeyMapWrapper<'a, C> {
impl<C: secp256k1::Signing> Translator<String> for KeyMapWrapper<'_, C> {
type TargetPk = DescriptorPublicKey;
type Error = Error;

Expand Down Expand Up @@ -744,7 +780,7 @@ impl Descriptor<DescriptorPublicKey> {
pub fn to_string_with_secret(&self, key_map: &KeyMap) -> String {
struct KeyMapLookUp<'a>(&'a KeyMap);

impl<'a> Translator<DescriptorPublicKey> for KeyMapLookUp<'a> {
impl Translator<DescriptorPublicKey> for KeyMapLookUp<'_> {
type TargetPk = String;
type Error = core::convert::Infallible;

Expand Down Expand Up @@ -907,7 +943,7 @@ impl Descriptor<DefiniteDescriptorKey> {
) -> Result<Descriptor<bitcoin::PublicKey>, ConversionError> {
struct Derivator<'a, C: secp256k1::Verification>(&'a secp256k1::Secp256k1<C>);

impl<'a, C: secp256k1::Verification> Translator<DefiniteDescriptorKey> for Derivator<'a, C> {
impl<C: secp256k1::Verification> Translator<DefiniteDescriptorKey> for Derivator<'_, C> {
type TargetPk = bitcoin::PublicKey;
type Error = ConversionError;

Expand Down
5 changes: 5 additions & 0 deletions src/descriptor/tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,11 @@ pub struct TapTreeIter<'a, Pk: MiniscriptKey> {
stack: Vec<(u8, &'a TapTree<Pk>)>,
}

impl<Pk: MiniscriptKey> TapTreeIter<'_, Pk> {
/// Helper function to return an empty iterator from Descriptor::tap_tree_iter.
pub(super) fn empty() -> Self { Self { stack: vec![] } }
}

impl<'a, Pk> Iterator for TapTreeIter<'a, Pk>
where
Pk: MiniscriptKey + 'a,
Expand Down
8 changes: 6 additions & 2 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,13 +682,17 @@ where
Terminal::DupIf(ref _sub) if node_state.n_evaluated == 1 => {
self.stack.push(stack::Element::Satisfied);
}
Terminal::ZeroNotEqual(ref sub) | Terminal::Verify(ref sub)
Terminal::ZeroNotEqual(ref sub)
| Terminal::Verify(ref sub)
| Terminal::Drop(ref sub)
if node_state.n_evaluated == 0 =>
{
self.push_evaluation_state(node_state.node, 1, 0);
self.push_evaluation_state(sub, 0, 0);
}
Terminal::Verify(ref _sub) if node_state.n_evaluated == 1 => {
Terminal::Verify(ref _sub) | Terminal::Drop(ref _sub)
if node_state.n_evaluated == 1 =>
{
match self.stack.pop() {
Some(stack::Element::Satisfied) => (),
Some(_) => return Some(Err(Error::VerifyFailed)),
Expand Down
3 changes: 3 additions & 0 deletions src/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript<Pk,
| Check(ref sub)
| DupIf(ref sub)
| Verify(ref sub)
| Drop(ref sub)
| NonZero(ref sub)
| ZeroNotEqual(ref sub) => Tree::Unary(sub),
AndV(ref left, ref right)
Expand Down Expand Up @@ -63,6 +64,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Arc<Miniscript<
| Check(ref sub)
| DupIf(ref sub)
| Verify(ref sub)
| Drop(ref sub)
| NonZero(ref sub)
| ZeroNotEqual(ref sub) => Tree::Unary(sub),
AndV(ref left, ref right)
Expand Down Expand Up @@ -93,6 +95,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Terminal<Pk, Ct
| Check(ref sub)
| DupIf(ref sub)
| Verify(ref sub)
| Drop(ref sub)
| NonZero(ref sub)
| ZeroNotEqual(ref sub) => Tree::Unary(sub.as_inner()),
AndV(ref left, ref right)
Expand Down
Loading
Loading