Skip to content

Commit

Permalink
Merge #592: Add the planning module (updated PR)
Browse files Browse the repository at this point in the history
a358076 test: absolute/relative timelocks in satisfaction (Daniela Brozzoni)
cb9a769 tests: plan capabilities (Daniela Brozzoni)
d29c298 Add plan capabilities to miniscript (Alekos Filini)
fc20eb0 Fix test_cpp (sanket1729)
448fbd8 Add full_derivation_paths on DescriptorPublicKey (Daniela Brozzoni)
7ca9ba1 Add relative and absolute timelock in Satisfaction (Alekos Filini)

Pull request description:

  This PR builds on top of #481, fixing all the review comments.

  I didn't squash my last commits on purpose to make review easier, I can squash them before merging if preferred.

ACKs for top commit:
  apoelstra:
    ACK a358076
  sanket1729:
    ACK a358076

Tree-SHA512: 32e547eedaf56d7ddb9ab8069ab394b655f46f6eae7b971d521befc800abadb785335a84c977875b050bcb202517381aba0fb9d8f2d418cd59a1f87147491d67
  • Loading branch information
sanket1729 committed Sep 22, 2023
2 parents ff99a1f + a358076 commit 780dbc6
Show file tree
Hide file tree
Showing 13 changed files with 2,386 additions and 217 deletions.
18 changes: 8 additions & 10 deletions bitcoind-tests/tests/test_cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,24 @@ use std::path::Path;

use bitcoin::hashes::{sha256d, Hash};
use bitcoin::psbt::Psbt;
use bitcoin::secp256k1::{self, Secp256k1};
use bitcoin::{psbt, Amount, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid};
use bitcoin::{psbt, secp256k1, Amount, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid};
use bitcoind::bitcoincore_rpc::{json, Client, RpcApi};
use miniscript::bitcoin::absolute;
use miniscript::psbt::PsbtExt;
use miniscript::{bitcoin, Descriptor};
use miniscript::{bitcoin, DefiniteDescriptorKey, Descriptor};

mod setup;
use setup::test_util::{self, PubData, TestData};

// parse ~30 miniscripts from file
pub(crate) fn parse_miniscripts(
secp: &Secp256k1<secp256k1::All>,
pubdata: &PubData,
) -> Vec<Descriptor<bitcoin::PublicKey>> {
pub(crate) fn parse_miniscripts(pubdata: &PubData) -> Vec<Descriptor<DefiniteDescriptorKey>> {
// File must exist in current path before this produces output
let mut desc_vec = vec![];
// Consumes the iterator, returns an (Optional) String
for line in read_lines("tests/data/random_ms.txt") {
let ms = test_util::parse_insane_ms(&line.unwrap(), pubdata);
let wsh = Descriptor::new_wsh(ms).unwrap();
desc_vec.push(wsh.derived_descriptor(secp, 0).unwrap());
desc_vec.push(wsh.at_derivation_index(0).unwrap());
}
desc_vec
}
Expand Down Expand Up @@ -71,7 +67,7 @@ fn get_vout(cl: &Client, txid: Txid, value: u64) -> (OutPoint, TxOut) {

pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) {
let secp = secp256k1::Secp256k1::new();
let desc_vec = parse_miniscripts(&secp, &testdata.pubdata);
let desc_vec = parse_miniscripts(&testdata.pubdata);
let sks = &testdata.secretdata.sks;
let pks = &testdata.pubdata.pks;
// Generate some blocks
Expand Down Expand Up @@ -152,6 +148,7 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) {
input.witness_utxo = Some(witness_utxo);
input.witness_script = Some(desc.explicit_script().unwrap());
psbt.inputs.push(input);
psbt.update_input_with_descriptor(0, &desc).unwrap();
psbt.outputs.push(psbt::Output::default());
psbts.push(psbt);
}
Expand All @@ -160,7 +157,8 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) {
// Sign the transactions with all keys
// AKA the signer role of psbt
for i in 0..psbts.len() {
let ms = if let Descriptor::Wsh(wsh) = &desc_vec[i] {
let wsh_derived = desc_vec[i].derived_descriptor(&secp).unwrap();
let ms = if let Descriptor::Wsh(wsh) = &wsh_derived {
match wsh.as_inner() {
miniscript::descriptor::WshInner::Ms(ms) => ms,
_ => unreachable!(),
Expand Down
66 changes: 66 additions & 0 deletions src/descriptor/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ use bitcoin::script::{self, PushBytes};
use bitcoin::{Address, Network, ScriptBuf};

use super::checksum::{self, verify_checksum};
use crate::descriptor::DefiniteDescriptorKey;
use crate::expression::{self, FromTree};
use crate::miniscript::context::{ScriptContext, ScriptContextError};
use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness};
use crate::plan::AssetProvider;
use crate::policy::{semantic, Liftable};
use crate::prelude::*;
use crate::util::{varint_len, witness_to_scriptsig};
Expand Down Expand Up @@ -134,6 +137,30 @@ impl<Pk: MiniscriptKey + ToPublicKey> Bare<Pk> {
}
}

impl Bare<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.ms.build_template(provider)
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn plan_satisfaction_mall<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.ms.build_template_mall(provider)
}
}

impl<Pk: MiniscriptKey> fmt::Debug for Bare<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.ms)
Expand Down Expand Up @@ -311,6 +338,45 @@ impl<Pk: MiniscriptKey + ToPublicKey> Pkh<Pk> {
}
}

impl Pkh<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
let stack = if provider.provider_lookup_ecdsa_sig(&self.pk) {
let stack = vec![
Placeholder::EcdsaSigPk(self.pk.clone()),
Placeholder::Pubkey(self.pk.clone(), BareCtx::pk_len(&self.pk)),
];
Witness::Stack(stack)
} else {
Witness::Unavailable
};

Satisfaction {
stack,
has_sig: true,
relative_timelock: None,
absolute_timelock: None,
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn plan_satisfaction_mall<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.plan_satisfaction(provider)
}
}

impl<Pk: MiniscriptKey> fmt::Debug for Pkh<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "pkh({:?})", self.pk)
Expand Down
41 changes: 41 additions & 0 deletions src/descriptor/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,33 @@ impl DescriptorPublicKey {
}
}

/// Returns a vector containing the full derivation paths from the master key.
/// The vector will contain just one element for single keys, and multiple elements
/// for multipath extended keys.
///
/// For wildcard keys this will return the path up to the wildcard, so you
/// can get full paths by appending one additional derivation step, according
/// to the wildcard type (hardened or normal).
pub fn full_derivation_paths(&self) -> Vec<bip32::DerivationPath> {
match self {
DescriptorPublicKey::MultiXPub(xpub) => {
let origin_path = if let Some((_, ref path)) = xpub.origin {
path.clone()
} else {
bip32::DerivationPath::from(vec![])
};
xpub.derivation_paths
.paths()
.into_iter()
.map(|p| origin_path.extend(p))
.collect()
}
_ => vec![self
.full_derivation_path()
.expect("Must be Some for non-multipath keys")],
}
}

/// Whether or not the key has a wildcard
#[deprecated(note = "use has_wildcard instead")]
pub fn is_deriveable(&self) -> bool {
Expand Down Expand Up @@ -1075,6 +1102,12 @@ impl DefiniteDescriptorKey {
self.0.full_derivation_path()
}

/// Full paths from the master key. The vector will contain just one path for single
/// keys, and multiple ones for multipath extended keys
pub fn full_derivation_paths(&self) -> Vec<bip32::DerivationPath> {
self.0.full_derivation_paths()
}

/// Reference to the underlying `DescriptorPublicKey`
pub fn as_descriptor_public_key(&self) -> &DescriptorPublicKey {
&self.0
Expand Down Expand Up @@ -1476,6 +1509,14 @@ mod test {
let desc_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/<0';1>/8h/*'").unwrap();
assert!(desc_key.full_derivation_path().is_none());
assert!(desc_key.is_multipath());
// But you can get all the derivation paths
assert_eq!(
desc_key.full_derivation_paths(),
vec![
bip32::DerivationPath::from_str("m/0'/1'/9478'/0'/8'").unwrap(),
bip32::DerivationPath::from_str("m/0'/1'/9478'/1/8'").unwrap(),
],
);
assert_eq!(desc_key.into_single_keys(), vec![DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/0'/8h/*'").unwrap(), DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/1/8h/*'").unwrap()]);

// All the same but with extended private keys instead of xpubs.
Expand Down
65 changes: 62 additions & 3 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use sync::Arc;

use self::checksum::verify_checksum;
use crate::miniscript::decode::Terminal;
use crate::miniscript::{Legacy, Miniscript, Segwitv0};
use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0};
use crate::plan::{AssetProvider, Plan};
use crate::prelude::*;
use crate::{
expression, hash256, BareCtx, Error, ForEachKey, MiniscriptKey, Satisfier, ToPublicKey,
Expand Down Expand Up @@ -474,7 +475,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction(satisfier),
Descriptor::Wsh(ref wsh) => wsh.get_satisfaction(satisfier),
Descriptor::Sh(ref sh) => sh.get_satisfaction(satisfier),
Descriptor::Tr(ref tr) => tr.get_satisfaction(satisfier),
Descriptor::Tr(ref tr) => tr.get_satisfaction(&satisfier),
}
}

Expand All @@ -491,7 +492,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction_mall(satisfier),
Descriptor::Wsh(ref wsh) => wsh.get_satisfaction_mall(satisfier),
Descriptor::Sh(ref sh) => sh.get_satisfaction_mall(satisfier),
Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(satisfier),
Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(&satisfier),
}
}

Expand All @@ -509,6 +510,64 @@ impl<Pk: MiniscriptKey + ToPublicKey> Descriptor<Pk> {
}
}

impl Descriptor<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
///
/// If the assets aren't sufficient for generating a Plan, the descriptor is returned
pub fn plan<P>(self, provider: &P) -> Result<Plan, Self>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
let satisfaction = match self {
Descriptor::Bare(ref bare) => bare.plan_satisfaction(provider),
Descriptor::Pkh(ref pkh) => pkh.plan_satisfaction(provider),
Descriptor::Wpkh(ref wpkh) => wpkh.plan_satisfaction(provider),
Descriptor::Wsh(ref wsh) => wsh.plan_satisfaction(provider),
Descriptor::Sh(ref sh) => sh.plan_satisfaction(provider),
Descriptor::Tr(ref tr) => tr.plan_satisfaction(provider),
};

if let satisfy::Witness::Stack(stack) = satisfaction.stack {
Ok(Plan {
descriptor: self,
template: stack,
absolute_timelock: satisfaction.absolute_timelock.map(Into::into),
relative_timelock: satisfaction.relative_timelock,
})
} else {
Err(self)
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
///
/// If the assets aren't sufficient for generating a Plan, the descriptor is returned
pub fn plan_mall<P>(self, provider: &P) -> Result<Plan, Self>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
let satisfaction = match self {
Descriptor::Bare(ref bare) => bare.plan_satisfaction_mall(provider),
Descriptor::Pkh(ref pkh) => pkh.plan_satisfaction_mall(provider),
Descriptor::Wpkh(ref wpkh) => wpkh.plan_satisfaction_mall(provider),
Descriptor::Wsh(ref wsh) => wsh.plan_satisfaction_mall(provider),
Descriptor::Sh(ref sh) => sh.plan_satisfaction_mall(provider),
Descriptor::Tr(ref tr) => tr.plan_satisfaction_mall(provider),
};

if let satisfy::Witness::Stack(stack) = satisfaction.stack {
Ok(Plan {
descriptor: self,
template: stack,
absolute_timelock: satisfaction.absolute_timelock.map(Into::into),
relative_timelock: satisfaction.relative_timelock,
})
} else {
Err(self)
}
}
}

impl<P, Q> TranslatePk<P, Q> for Descriptor<P>
where
P: MiniscriptKey,
Expand Down
72 changes: 72 additions & 0 deletions src/descriptor/segwitv0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ use bitcoin::{Address, Network, ScriptBuf};

use super::checksum::{self, verify_checksum};
use super::SortedMultiVec;
use crate::descriptor::DefiniteDescriptorKey;
use crate::expression::{self, FromTree};
use crate::miniscript::context::{ScriptContext, ScriptContextError};
use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness};
use crate::plan::AssetProvider;
use crate::policy::{semantic, Liftable};
use crate::prelude::*;
use crate::util::varint_len;
Expand Down Expand Up @@ -191,6 +194,36 @@ impl<Pk: MiniscriptKey + ToPublicKey> Wsh<Pk> {
}
}

impl Wsh<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
WshInner::SortedMulti(sm) => sm.build_template(provider),
WshInner::Ms(ms) => ms.build_template(provider),
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn plan_satisfaction_mall<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
match &self.inner {
WshInner::SortedMulti(sm) => sm.build_template(provider),
WshInner::Ms(ms) => ms.build_template_mall(provider),
}
}
}

/// Wsh Inner
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum WshInner<Pk: MiniscriptKey> {
Expand Down Expand Up @@ -418,6 +451,45 @@ impl<Pk: MiniscriptKey + ToPublicKey> Wpkh<Pk> {
}
}

impl Wpkh<DefiniteDescriptorKey> {
/// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction
pub fn plan_satisfaction<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
let stack = if provider.provider_lookup_ecdsa_sig(&self.pk) {
let stack = vec![
Placeholder::EcdsaSigPk(self.pk.clone()),
Placeholder::Pubkey(self.pk.clone(), Segwitv0::pk_len(&self.pk)),
];
Witness::Stack(stack)
} else {
Witness::Unavailable
};

Satisfaction {
stack,
has_sig: true,
relative_timelock: None,
absolute_timelock: None,
}
}

/// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction
pub fn plan_satisfaction_mall<P>(
&self,
provider: &P,
) -> Satisfaction<Placeholder<DefiniteDescriptorKey>>
where
P: AssetProvider<DefiniteDescriptorKey>,
{
self.plan_satisfaction(provider)
}
}

impl<Pk: MiniscriptKey> fmt::Debug for Wpkh<Pk> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "wpkh({:?})", self.pk)
Expand Down
Loading

0 comments on commit 780dbc6

Please sign in to comment.