Skip to content

Commit 4c9adf9

Browse files
authored
Merge pull request #85 from coblox/witness-stack-order-wrong
Miniscript produces witness stacks with wrong order of signatures for `and` instructions
2 parents 3610c1d + 824fed2 commit 4c9adf9

File tree

2 files changed

+59
-3
lines changed

2 files changed

+59
-3
lines changed

src/descriptor/mod.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ mod tests {
555555
use bitcoin::hashes::{hash160, sha256};
556556
use bitcoin::{self, secp256k1, PublicKey};
557557
use miniscript::satisfy::BitcoinSig;
558+
use std::collections::HashMap;
558559
use std::str::FromStr;
559560
use {Descriptor, DummyKey, Miniscript, Satisfier};
560561

@@ -947,4 +948,59 @@ mod tests {
947948
"unexpected «no arguments given»"
948949
)
949950
}
951+
952+
#[test]
953+
fn witness_stack_for_andv_is_arranged_in_correct_order() {
954+
// arrange
955+
let a = bitcoin::PublicKey::from_str(
956+
"02937402303919b3a2ee5edd5009f4236f069bf75667b8e6ecf8e5464e20116a0e",
957+
)
958+
.unwrap();
959+
let sig_a = secp256k1::Signature::from_str("3045022100a7acc3719e9559a59d60d7b2837f9842df30e7edcd754e63227e6168cec72c5d022066c2feba4671c3d99ea75d9976b4da6c86968dbf3bab47b1061e7a1966b1778c").unwrap();
960+
961+
let b = bitcoin::PublicKey::from_str(
962+
"02eb64639a17f7334bb5a1a3aad857d6fec65faef439db3de72f85c88bc2906ad3",
963+
)
964+
.unwrap();
965+
let sig_b = secp256k1::Signature::from_str("3044022075b7b65a7e6cd386132c5883c9db15f9a849a0f32bc680e9986398879a57c276022056d94d12255a4424f51c700ac75122cb354895c9f2f88f0cbb47ba05c9c589ba").unwrap();
966+
967+
let descriptor = Descriptor::<bitcoin::PublicKey>::from_str(&format!(
968+
"wsh(and_v(v:pk({A}),pk({B})))",
969+
A = a,
970+
B = b
971+
))
972+
.unwrap();
973+
974+
let mut txin = bitcoin::TxIn {
975+
previous_output: bitcoin::OutPoint::default(),
976+
script_sig: bitcoin::Script::new(),
977+
sequence: 0,
978+
witness: vec![],
979+
};
980+
let satisfier = {
981+
let mut satisfier = HashMap::with_capacity(2);
982+
983+
satisfier.insert(a, (sig_a.clone(), ::bitcoin::SigHashType::All));
984+
satisfier.insert(b, (sig_b.clone(), ::bitcoin::SigHashType::All));
985+
986+
satisfier
987+
};
988+
989+
// act
990+
descriptor.satisfy(&mut txin, &satisfier).unwrap();
991+
992+
// assert
993+
let witness0 = &txin.witness[0];
994+
let witness1 = &txin.witness[1];
995+
996+
let sig0 = secp256k1::Signature::from_der(&witness0[..witness0.len() - 1]).unwrap();
997+
let sig1 = secp256k1::Signature::from_der(&witness1[..witness1.len() - 1]).unwrap();
998+
999+
// why are we asserting this way?
1000+
// The witness stack is evaluated from top to bottom. Given an `and` instruction, the left arm of the and is going to evaluate first,
1001+
// meaning the next witness element (on a three element stack, that is the middle one) needs to be the signature for the left side of the `and`.
1002+
// The left side of the `and` performs a CHECKSIG against public key `a` so `sig1` needs to be `sig_a` and `sig0` needs to be `sig_b`.
1003+
assert_eq!(sig1, sig_a);
1004+
assert_eq!(sig0, sig_b);
1005+
}
9501006
}

src/miniscript/satisfy.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ impl Satisfaction {
580580
let l_sat = Self::satisfy(&l.node, stfr);
581581
let r_sat = Self::satisfy(&r.node, stfr);
582582
Satisfaction {
583-
stack: Witness::combine(l_sat.stack, r_sat.stack),
583+
stack: Witness::combine(r_sat.stack, l_sat.stack),
584584
has_sig: l_sat.has_sig || r_sat.has_sig,
585585
}
586586
}
@@ -592,11 +592,11 @@ impl Satisfaction {
592592

593593
Self::minimum(
594594
Satisfaction {
595-
stack: Witness::combine(a_sat.stack, b_sat.stack),
595+
stack: Witness::combine(b_sat.stack, a_sat.stack),
596596
has_sig: a_sat.has_sig || b_sat.has_sig,
597597
},
598598
Satisfaction {
599-
stack: Witness::combine(a_nsat.stack, c_sat.stack),
599+
stack: Witness::combine(c_sat.stack, a_nsat.stack),
600600
has_sig: a_nsat.has_sig || c_sat.has_sig,
601601
},
602602
)

0 commit comments

Comments
 (0)