Skip to content
Open
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
5 changes: 5 additions & 0 deletions bitcoind-tests/tests/test_desc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ fn test_descs(cl: &Client, testdata: &TestData) {
test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(3,X2,X3,X4,X5!)})").unwrap();
test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(4,X2,X3,X4,X5)})").unwrap();

test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),sortedmulti_a(1,X2,X3!,X4!,X5!)})").unwrap();
test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),sortedmulti_a(2,X2,X3,X4!,X5!)})").unwrap();
test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),sortedmulti_a(3,X2,X3,X4,X5!)})").unwrap();
test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),sortedmulti_a(4,X2,X3,X4,X5)})").unwrap();

// Test 7: Test script tree of depth 127 is valid, only X128 is known
test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),{pk(X2!),{pk(X3!),{pk(X4!),{pk(X5!),{pk(X6!),{pk(X7!),{pk(X8!),{pk(X9!),{pk(X10!),{pk(X11!),{pk(X12!),{pk(X13!),{pk(X14!),{pk(X15!),{pk(X16!),{pk(X17!),{pk(X18!),{pk(X19!),{pk(X20!),{pk(X21!),{pk(X22!),{pk(X23!),{pk(X24!),{pk(X25!),{pk(X26!),{pk(X27!),{pk(X28!),{pk(X29!),{pk(X30!),{pk(X31!),{pk(X32!),{pk(X33!),{pk(X34!),{pk(X35!),{pk(X36!),{pk(X37!),{pk(X38!),{pk(X39!),{pk(X40!),{pk(X41!),{pk(X42!),{pk(X43!),{pk(X44!),{pk(X45!),{pk(X46!),{pk(X47!),{pk(X48!),{pk(X49!),{pk(X50!),{pk(X51!),{pk(X52!),{pk(X53!),{pk(X54!),{pk(X55!),{pk(X56!),{pk(X57!),{pk(X58!),{pk(X59!),{pk(X60!),{pk(X61!),{pk(X62!),{pk(X63!),{pk(X64!),{pk(X65!),{pk(X66!),{pk(X67!),{pk(X68!),{pk(X69!),{pk(X70!),{pk(X71!),{pk(X72!),{pk(X73!),{pk(X74!),{pk(X75!),{pk(X76!),{pk(X77!),{pk(X78!),{pk(X79!),{pk(X80!),{pk(X81!),{pk(X82!),{pk(X83!),{pk(X84!),{pk(X85!),{pk(X86!),{pk(X87!),{pk(X88!),{pk(X89!),{pk(X90!),{pk(X91!),{pk(X92!),{pk(X93!),{pk(X94!),{pk(X95!),{pk(X96!),{pk(X97!),{pk(X98!),{pk(X99!),{pk(X100!),{pk(X101!),{pk(X102!),{pk(X103!),{pk(X104!),{pk(X105!),{pk(X106!),{pk(X107!),{pk(X108!),{pk(X109!),{pk(X110!),{pk(X111!),{pk(X112!),{pk(X113!),{pk(X114!),{pk(X115!),{pk(X116!),{pk(X117!),{pk(X118!),{pk(X119!),{pk(X120!),{pk(X121!),{pk(X122!),{pk(X123!),{pk(X124!),{pk(X125!),{pk(X126!),{pk(X127!),pk(X128)}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}})").unwrap();

Expand Down
34 changes: 34 additions & 0 deletions examples/xpub_descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ fn main() {

// P2WSH-P2SH and ranged xpubs.
let _ = p2sh_p2wsh(&secp);

// P2TR with xpubs in sortedmulti_a
let _ = p2tr_sortedmulti_a(&secp);
}

/// Parses a P2WSH descriptor, returns the associated address.
Expand Down Expand Up @@ -64,3 +67,34 @@ fn p2sh_p2wsh<C: Verification>(secp: &Secp256k1<C>) -> Address {
assert_eq!(address, expected);
address
}

/// Parses a P2TR sortedmulti_a descriptor, returns the associated address.
fn p2tr_sortedmulti_a<C: Verification>(secp: &Secp256k1<C>) -> Address {
let internal = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
// It does not matter what order the two xpubs go in, the same address will be generated.
let s1 = format!("tr({},sortedmulti_a(2,{}/1/0/*,{}/0/0/*))", internal, XPUB_1, XPUB_2);
let s2 = format!("tr({},sortedmulti_a(2,{}/0/0/*,{}/1/0/*))", internal, XPUB_2, XPUB_1);

let [address1, address2]: [Address; 2] = [s1, s2]
.into_iter()
.map(|s| {
Descriptor::<DescriptorPublicKey>::from_str(&s)
.unwrap()
.derived_descriptor(secp, 5)
.unwrap()
.address(Network::Bitcoin)
.unwrap()
})
.collect::<Vec<_>>()
.try_into()
.unwrap();

let expected =
Address::from_str("bc1ppfd3y5lxq4nf3tfstccz0t0hly3vmj93t7z46e52zlpt6dyf4hwqxaxnxc")
.unwrap()
.require_network(Network::Bitcoin)
.unwrap();
assert_eq!(address1, expected);
assert_eq!(address1, address2);
address1
}
11 changes: 1 addition & 10 deletions src/descriptor/sortedmulti.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
where
Pk: ToPublicKey,
{
let mut thresh = self.inner.clone();
// Sort pubkeys lexicographically according to BIP 67
thresh.data_mut().sort_by(|a, b| {
a.to_public_key()
.inner
.serialize()
.partial_cmp(&b.to_public_key().inner.serialize())
.unwrap()
});
Terminal::Multi(thresh)
Terminal::Multi(self.inner.to_sorted())
}

/// Encode as a Bitcoin script
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ where
None => return Some(Err(Error::UnexpectedStackEnd)),
}
}
Terminal::MultiA(ref thresh) => {
Terminal::MultiA(ref thresh) | Terminal::SortedMultiA(ref thresh) => {
if node_state.n_evaluated == thresh.n() {
if node_state.n_satisfied == thresh.k() {
self.stack.push(stack::Element::Satisfied);
Expand Down
9 changes: 6 additions & 3 deletions src/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript<Pk,
use Terminal::*;
match self.node {
PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..)
| Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary,
| Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..)
| SortedMultiA(..) => Tree::Nullary,
Alt(ref sub)
| Swap(ref sub)
| Check(ref sub)
Expand Down Expand Up @@ -57,7 +58,8 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Arc<Miniscript<
use Terminal::*;
match self.node {
PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..)
| Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary,
| Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..)
| SortedMultiA(..) => Tree::Nullary,
Alt(ref sub)
| Swap(ref sub)
| Check(ref sub)
Expand Down Expand Up @@ -87,7 +89,8 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Terminal<Pk, Ct
use Terminal::*;
match self {
PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..)
| Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary,
| Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..)
| SortedMultiA(..) => Tree::Nullary,
Alt(ref sub)
| Swap(ref sub)
| Check(ref sub)
Expand Down
18 changes: 14 additions & 4 deletions src/miniscript/astelem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,22 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
.push_int(thresh.n() as i64)
.push_opcode(opcodes::all::OP_CHECKMULTISIG)
}
Terminal::MultiA(ref thresh) => {
Terminal::MultiA(ref thresh) | Terminal::SortedMultiA(ref thresh) => {
debug_assert!(Ctx::sig_type() == SigType::Schnorr);
// keys must be atleast len 1 here, guaranteed by typing rules
builder = builder.push_ms_key::<_, Ctx>(&thresh.data()[0]);
let sorted;
let mut iter = if let Terminal::SortedMultiA(thresh) = self {
sorted = thresh.to_sorted_xonly();
sorted.iter()
} else {
thresh.iter()
};
builder =
builder.push_ms_key::<_, Ctx>(iter.next().expect(
"multi_a keys must be atleast len 1 here, guaranteed by typing rules",
));
builder = builder.push_opcode(opcodes::all::OP_CHECKSIG);
for pk in thresh.iter().skip(1) {

for pk in iter {
builder = builder.push_ms_key::<_, Ctx>(pk);
builder = builder.push_opcode(opcodes::all::OP_CHECKSIGADD);
}
Expand Down
14 changes: 10 additions & 4 deletions src/miniscript/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,9 @@ impl ScriptContext for Legacy {
}
Ok(())
}
Terminal::MultiA(..) => Err(ScriptContextError::MultiANotAllowed),
Terminal::MultiA(..) | Terminal::SortedMultiA(..) => {
Err(ScriptContextError::MultiANotAllowed)
}
_ => Ok(()),
};
// 2. After fragment and param check, validate the script size finally
Expand Down Expand Up @@ -494,7 +496,9 @@ impl ScriptContext for Segwitv0 {
}
Ok(())
}
Terminal::MultiA(..) => Err(ScriptContextError::MultiANotAllowed),
Terminal::MultiA(..) | Terminal::SortedMultiA(..) => {
Err(ScriptContextError::MultiANotAllowed)
}
_ => Ok(()),
};
// 2. After fragment and param check, validate the script size finally
Expand Down Expand Up @@ -599,7 +603,7 @@ impl ScriptContext for Tap {
// 1. Check the node first, throw an error on the language itself
let node_checked = match ms.node {
Terminal::PkK(ref pk) => Self::check_pk(pk),
Terminal::MultiA(ref thresh) => {
Terminal::MultiA(ref thresh) | Terminal::SortedMultiA(ref thresh) => {
for pk in thresh.iter() {
Self::check_pk(pk)?;
}
Expand Down Expand Up @@ -716,7 +720,9 @@ impl ScriptContext for BareCtx {
}
Ok(())
}
Terminal::MultiA(..) => Err(ScriptContextError::MultiANotAllowed),
Terminal::MultiA(..) | Terminal::SortedMultiA(..) => {
Err(ScriptContextError::MultiANotAllowed)
}
_ => Ok(()),
};
// 2. After fragment and param check, validate the script size finally
Expand Down
14 changes: 12 additions & 2 deletions src/miniscript/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ pub enum Terminal<Pk: MiniscriptKey, Ctx: ScriptContext> {
Multi(Threshold<Pk, MAX_PUBKEYS_PER_MULTISIG>),
/// `<key> CHECKSIG (<key> CHECKSIGADD)*(n-1) k NUMEQUAL`
MultiA(Threshold<Pk, MAX_PUBKEYS_IN_CHECKSIGADD>),
/// `<key> CHECKSIG (<key> CHECKSIGADD)*(n-1) k NUMEQUAL`
SortedMultiA(Threshold<Pk, MAX_PUBKEYS_IN_CHECKSIGADD>),
}

impl<Pk: MiniscriptKey, Ctx: ScriptContext> Clone for Terminal<Pk, Ctx> {
Expand Down Expand Up @@ -213,6 +215,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Clone for Terminal<Pk, Ctx> {
}
Terminal::Multi(ref thresh) => Terminal::Multi(thresh.clone()),
Terminal::MultiA(ref thresh) => Terminal::MultiA(thresh.clone()),
Terminal::SortedMultiA(ref thresh) => Terminal::SortedMultiA(thresh.clone()),
}
}
}
Expand All @@ -232,6 +235,9 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> PartialEq for Terminal<Pk, Ctx> {
(Terminal::Hash160(h1), Terminal::Hash160(h2)) if h1 != h2 => return false,
(Terminal::Multi(th1), Terminal::Multi(th2)) if th1 != th2 => return false,
(Terminal::MultiA(th1), Terminal::MultiA(th2)) if th1 != th2 => return false,
(Terminal::SortedMultiA(th1), Terminal::SortedMultiA(th2)) if th1 != th2 => {
return false
}
_ => {
if mem::discriminant(me) != mem::discriminant(you) {
return false;
Expand Down Expand Up @@ -264,7 +270,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> core::hash::Hash for Terminal<Pk, Ct
// The actual children will be hashed when we iterate
}
Terminal::Multi(th) => th.hash(hasher),
Terminal::MultiA(th) => th.hash(hasher),
Terminal::MultiA(th) | Terminal::SortedMultiA(th) => th.hash(hasher),
_ => {}
}
}
Expand Down Expand Up @@ -553,7 +559,11 @@ pub fn decode<Ctx: ScriptContext>(
);
keys.reverse();
let thresh = Threshold::new(k as usize, keys).map_err(Error::Threshold)?;
term.push(Miniscript::multi_a(thresh));
if thresh.is_sorted_xonly() {
term.push(Miniscript::sortedmulti_a(thresh));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second guessing this...do I need to check if keys.len() == k && thresh.is_sorted() and then that will decode as a sortedmulti_a?

} else {
term.push(Miniscript::multi_a(thresh));
}
},
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/miniscript/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for DisplayNode<'a, Pk,
Terminal::MultiA(ref thresh) => {
Tree::Nary(NaryChildren::Keys(thresh.k(), thresh.data()))
}
Terminal::SortedMultiA(thresh) => {
Tree::Nary(NaryChildren::Keys(thresh.k(), thresh.data()))
}
},
// Only nodes have children; the rest are terminals.
_ => Tree::Nullary,
Expand Down Expand Up @@ -272,6 +275,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
Terminal::Thresh(..) => "thresh",
Terminal::Multi(..) => "multi",
Terminal::MultiA(..) => "multi_a",
Terminal::SortedMultiA(..) => "sortedmulti_a",
}
}

Expand Down
1 change: 1 addition & 0 deletions src/miniscript/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
(Terminal::PkK(key), 0) | (Terminal::PkH(key), 0) => Some(key.clone()),
(Terminal::Multi(thresh), _) => thresh.data().get(n).cloned(),
(Terminal::MultiA(thresh), _) => thresh.data().get(n).cloned(),
(Terminal::SortedMultiA(thresh), _) => thresh.data().get(n).cloned(),
_ => None,
}
}
Expand Down
Loading
Loading