Skip to content

Commit c9b0499

Browse files
committed
Merge #921: fix: add sorting to satisfy_helper for sortedmulti
3f20a69 fix: add sorting to satisfy_helper for sortedmulti (Trevor Arjeski) Pull request description: 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. Found by Andrew in #915 (comment) ACKs for top commit: apoelstra: ACK 3f20a69; successfully ran local tests Tree-SHA512: f81642afeb8415aec564260d352176cf479f316fae1e07f2b9d455449d4749224c0bb05b51f7836718d43cdfe59387062596d4282c9b208bb1b2ae651e19da79
2 parents ac73b83 + 3f20a69 commit c9b0499

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
@@ -728,6 +728,7 @@ mod test {
728728
use std::str::FromStr;
729729

730730
use bitcoin::bip32::Xpub;
731+
use secp256k1::Secp256k1;
731732

732733
use super::*;
733734
use crate::*;
@@ -1154,88 +1155,92 @@ mod test {
11541155
assert_eq!(psbt_input.bip32_derivation.len(), 2, "Unexpected number of bip32_derivation");
11551156
}
11561157

1157-
#[test]
1158-
fn test_plan_satisfy_wsh() {
1159-
use std::collections::BTreeMap;
1160-
1161-
use bitcoin::secp256k1::{self, Secp256k1};
1162-
1158+
fn test_plan_satisfy(
1159+
desc_str_fn: fn(&[bitcoin::PublicKey]) -> String,
1160+
exp_witness_len: usize,
1161+
) -> Vec<Vec<u8>> {
11631162
let secp = Secp256k1::new();
11641163

1165-
let sk =
1166-
secp256k1::SecretKey::from_slice(&b"sally was a secret key, she said"[..]).unwrap();
1167-
let pk = bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk));
1164+
let (sks, pks): (Vec<_>, Vec<_>) = [
1165+
&b"sally was a secret key, she said"[..],
1166+
&b"polly was a secret key, she said"[..],
1167+
&b"bonny was a secret key, she said"[..],
1168+
]
1169+
.iter()
1170+
.map(|d| {
1171+
let sk = secp256k1::SecretKey::from_slice(d).unwrap();
1172+
let pk = bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk));
1173+
(sk, pk)
1174+
})
1175+
.unzip();
11681176

1169-
let desc =
1170-
Descriptor::<DefiniteDescriptorKey>::from_str(&format!("wsh(pk({}))", pk)).unwrap();
1177+
let desc = Descriptor::<DefiniteDescriptorKey>::from_str(&desc_str_fn(&pks)).unwrap();
11711178

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

11801192
// This witness script should exist in the witness stack returned by `Plan::satisfy`.
1181-
let exp_witness_script = desc.explicit_script().expect("wsh has explicit script");
1193+
let exp_witness_script = desc.explicit_script().expect("has explicit script");
1194+
let exp_script_sig = desc.unsigned_script_sig();
11821195

11831196
let mut satisfier = BTreeMap::<DefiniteDescriptorKey, bitcoin::ecdsa::Signature>::new();
1184-
satisfier.insert(DefiniteDescriptorKey::from_str(&pk.to_string()).unwrap(), ecdsa_sig);
1197+
let mut assets = Assets::new();
1198+
for (i, pk) in pks.iter().enumerate() {
1199+
satisfier.insert(DefiniteDescriptorKey::from_str(&pk.to_string()).unwrap(), sigs[i]);
1200+
assets = assets.add(DescriptorPublicKey::from_str(&pk.to_string()).unwrap());
1201+
}
11851202

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

11891205
let (witness, script_sig) = plan.satisfy(&satisfier).expect("satisfy should succeed");
1206+
assert_eq!(witness.last().unwrap(), &exp_witness_script.into_bytes());
1207+
assert_eq!(script_sig, exp_script_sig);
1208+
assert_eq!(witness.len(), exp_witness_len);
11901209

1210+
witness
1211+
}
1212+
1213+
#[test]
1214+
fn test_plan_satisfy_wsh() {
11911215
// For native P2WSH:
11921216
// - script_sig should be empty
11931217
// - witness should contain [signature, witness_script]
1194-
assert_eq!(script_sig, ScriptBuf::new());
1195-
assert_eq!(witness.len(), 2);
1196-
assert_eq!(witness.last().unwrap(), &exp_witness_script.into_bytes());
1218+
test_plan_satisfy(|pks| format!("wsh(pk({}))", pks[0]), 2);
11971219
}
11981220

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

0 commit comments

Comments
 (0)