Skip to content

Commit 3f20a69

Browse files
committed
fix: add sorting to satisfy_helper for sortedmulti
This logic was done in sortedmulti_a but accidentally overlooked when refactoring sortedmulti. Wrote a test that proves two differently ordered sortedmulti descriptors produce the same witness.
1 parent ddc42c7 commit 3f20a69

2 files changed

Lines changed: 73 additions & 61 deletions

File tree

src/miniscript/satisfy.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1482,7 +1482,14 @@ impl<Pk: MiniscriptKey + ToPublicKey> Satisfaction<Placeholder<Pk>> {
14821482
// Collect all available signatures
14831483
let mut sig_count = 0;
14841484
let mut sigs = Vec::with_capacity(thresh.k());
1485-
for pk in thresh.data() {
1485+
let sorted;
1486+
let iter = if let Terminal::SortedMulti(ref thresh) = *term {
1487+
sorted = thresh.clone().into_sorted_bip67();
1488+
sorted.iter()
1489+
} else {
1490+
thresh.iter()
1491+
};
1492+
for pk in iter {
14861493
match Witness::signature::<_, Ctx>(stfr, pk, leaf_hash) {
14871494
Witness::Stack(sig) => {
14881495
sigs.push(sig);

src/plan.rs

Lines changed: 65 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ mod test {
734734
use std::str::FromStr;
735735

736736
use bitcoin::bip32::Xpub;
737+
use secp256k1::Secp256k1;
737738

738739
use super::*;
739740
use crate::*;
@@ -1160,88 +1161,92 @@ mod test {
11601161
assert_eq!(psbt_input.bip32_derivation.len(), 2, "Unexpected number of bip32_derivation");
11611162
}
11621163

1163-
#[test]
1164-
fn test_plan_satisfy_wsh() {
1165-
use std::collections::BTreeMap;
1166-
1167-
use bitcoin::secp256k1::{self, Secp256k1};
1168-
1164+
fn test_plan_satisfy(
1165+
desc_str_fn: fn(&[bitcoin::PublicKey]) -> String,
1166+
exp_witness_len: usize,
1167+
) -> Vec<Vec<u8>> {
11691168
let secp = Secp256k1::new();
11701169

1171-
let sk =
1172-
secp256k1::SecretKey::from_slice(&b"sally was a secret key, she said"[..]).unwrap();
1173-
let pk = bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk));
1170+
let (sks, pks): (Vec<_>, Vec<_>) = [
1171+
&b"sally was a secret key, she said"[..],
1172+
&b"polly was a secret key, she said"[..],
1173+
&b"bonny was a secret key, she said"[..],
1174+
]
1175+
.iter()
1176+
.map(|d| {
1177+
let sk = secp256k1::SecretKey::from_slice(d).unwrap();
1178+
let pk = bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk));
1179+
(sk, pk)
1180+
})
1181+
.unzip();
11741182

1175-
let desc =
1176-
Descriptor::<DefiniteDescriptorKey>::from_str(&format!("wsh(pk({}))", pk)).unwrap();
1183+
let desc = Descriptor::<DefiniteDescriptorKey>::from_str(&desc_str_fn(&pks)).unwrap();
11771184

1178-
let sighash =
1179-
secp256k1::Message::from_digest_slice(&b"michael was a message, amusingly"[..])
1180-
.expect("32 bytes");
1181-
let ecdsa_sig = bitcoin::ecdsa::Signature {
1182-
signature: secp.sign_ecdsa(&sighash, &sk),
1183-
sighash_type: bitcoin::sighash::EcdsaSighashType::All,
1184-
};
1185+
let sigs = sks
1186+
.iter()
1187+
.map(|sk| {
1188+
let sighash =
1189+
secp256k1::Message::from_digest_slice(&b"michael was a message, amusingly"[..])
1190+
.expect("32 bytes");
1191+
bitcoin::ecdsa::Signature {
1192+
signature: secp.sign_ecdsa(&sighash, sk),
1193+
sighash_type: bitcoin::sighash::EcdsaSighashType::All,
1194+
}
1195+
})
1196+
.collect::<Vec<_>>();
11851197

11861198
// This witness script should exist in the witness stack returned by `Plan::satisfy`.
1187-
let exp_witness_script = desc.explicit_script().expect("wsh has explicit script");
1199+
let exp_witness_script = desc.explicit_script().expect("has explicit script");
1200+
let exp_script_sig = desc.unsigned_script_sig();
11881201

11891202
let mut satisfier = BTreeMap::<DefiniteDescriptorKey, bitcoin::ecdsa::Signature>::new();
1190-
satisfier.insert(DefiniteDescriptorKey::from_str(&pk.to_string()).unwrap(), ecdsa_sig);
1203+
let mut assets = Assets::new();
1204+
for (i, pk) in pks.iter().enumerate() {
1205+
satisfier.insert(DefiniteDescriptorKey::from_str(&pk.to_string()).unwrap(), sigs[i]);
1206+
assets = assets.add(DescriptorPublicKey::from_str(&pk.to_string()).unwrap());
1207+
}
11911208

1192-
let assets = Assets::new().add(DescriptorPublicKey::from_str(&pk.to_string()).unwrap());
11931209
let plan = desc.plan(&assets).expect("plan should succeed");
11941210

11951211
let (witness, script_sig) = plan.satisfy(&satisfier).expect("satisfy should succeed");
1212+
assert_eq!(witness.last().unwrap(), &exp_witness_script.into_bytes());
1213+
assert_eq!(script_sig, exp_script_sig);
1214+
assert_eq!(witness.len(), exp_witness_len);
11961215

1216+
witness
1217+
}
1218+
1219+
#[test]
1220+
fn test_plan_satisfy_wsh() {
11971221
// For native P2WSH:
11981222
// - script_sig should be empty
11991223
// - witness should contain [signature, witness_script]
1200-
assert_eq!(script_sig, ScriptBuf::new());
1201-
assert_eq!(witness.len(), 2);
1202-
assert_eq!(witness.last().unwrap(), &exp_witness_script.into_bytes());
1224+
test_plan_satisfy(|pks| format!("wsh(pk({}))", pks[0]), 2);
12031225
}
12041226

12051227
#[test]
12061228
fn test_plan_satisfy_sh_wsh() {
1207-
use std::collections::BTreeMap;
1208-
1209-
use bitcoin::secp256k1::{self, Secp256k1};
1210-
1211-
let secp = Secp256k1::new();
1212-
let sk =
1213-
secp256k1::SecretKey::from_slice(&b"sally was a secret key, she said"[..]).unwrap();
1214-
let pk = bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk));
1215-
1216-
let desc =
1217-
Descriptor::<DefiniteDescriptorKey>::from_str(&format!("sh(wsh(pk({})))", pk)).unwrap();
1218-
1219-
let sighash =
1220-
secp256k1::Message::from_digest_slice(&b"michael was a message, amusingly"[..])
1221-
.expect("32 bytes");
1222-
let ecdsa_sig = bitcoin::ecdsa::Signature {
1223-
signature: secp.sign_ecdsa(&sighash, &sk),
1224-
sighash_type: bitcoin::sighash::EcdsaSighashType::All,
1225-
};
1226-
1227-
// Get expected values before plan() consumes the descriptor.
1228-
let exp_witness_script = desc.explicit_script().expect("sh-wsh has explicit script");
1229-
let exp_script_sig = desc.unsigned_script_sig();
1230-
1231-
let mut satisfier: BTreeMap<DefiniteDescriptorKey, bitcoin::ecdsa::Signature> =
1232-
BTreeMap::new();
1233-
satisfier.insert(DefiniteDescriptorKey::from_str(&pk.to_string()).unwrap(), ecdsa_sig);
1234-
1235-
let assets = Assets::new().add(DescriptorPublicKey::from_str(&pk.to_string()).unwrap());
1236-
let plan = desc.plan(&assets).expect("plan should succeed");
1237-
1238-
let (witness, script_sig) = plan.satisfy(&satisfier).expect("satisfy should succeed");
1239-
12401229
// For P2SH-P2WSH:
12411230
// - script_sig should be the unsigned_script_sig (pushes the P2WSH redeemScript)
12421231
// - witness should contain [signature, witness_script]
1243-
assert_eq!(script_sig, exp_script_sig);
1244-
assert_eq!(witness.len(), 2);
1245-
assert_eq!(witness.last().unwrap(), &exp_witness_script.into_bytes());
1232+
test_plan_satisfy(|pks| format!("sh(wsh(pk({})))", pks[0]), 2);
1233+
}
1234+
1235+
#[test]
1236+
fn test_plan_satisfy_sortedmulti() {
1237+
// For native P2WSH with sortedmulti:
1238+
// - script_sig should be empty
1239+
// - witness should contain [sig1, sig2, sig3, witness_script]
1240+
// - witness should be the same no matter the order of the keys
1241+
assert_eq!(
1242+
test_plan_satisfy(
1243+
|pks| format!("wsh(sortedmulti(2,{},{},{}))", pks[0], pks[1], pks[2]),
1244+
4
1245+
),
1246+
test_plan_satisfy(
1247+
|pks| format!("wsh(sortedmulti(2,{},{},{}))", pks[1], pks[2], pks[0]),
1248+
4
1249+
)
1250+
);
12461251
}
12471252
}

0 commit comments

Comments
 (0)