Skip to content
Closed
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
99 changes: 87 additions & 12 deletions src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::descriptor::{self, Descriptor, DescriptorType, KeyMap};
use crate::miniscript::hash256;
use crate::miniscript::satisfy::{Placeholder, Satisfier, SchnorrSigType};
use crate::prelude::*;
use crate::util::witness_size;
use crate::util::{varint_len, witness_size};
use crate::{DefiniteDescriptorKey, DescriptorPublicKey, Error, MiniscriptKey, ToPublicKey};

/// Trait describing a present/missing lookup table for constructing witness templates
Expand Down Expand Up @@ -792,10 +792,11 @@ mod test {
let desc = format!("wsh(t:or_c(pk({}),v:pkh({})))", keys[0], keys[1]);

// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig)
// + 1 (witness script len) + 63 (witness script)
let tests = vec![
(vec![], vec![], None, None, None),
(vec![0], vec![], None, None, Some(4 + 1 + 73)),
(vec![0, 1], vec![], None, None, Some(4 + 1 + 73)),
(vec![0], vec![], None, None, Some(4 + 1 + 73 + 1 + 63)),
(vec![0, 1], vec![], None, None, Some(4 + 1 + 73 + 1 + 63)),
];

test_inner(&desc, keys, hashes, tests);
Expand All @@ -817,10 +818,11 @@ mod test {
let desc = format!("wsh(and_v(v:pk({}),pk({})))", keys[0], keys[1]);

// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) * 2
// + 1 (witness script len) + 70 (witness script)
let tests = vec![
(vec![], vec![], None, None, None),
(vec![0], vec![], None, None, None),
(vec![0, 1], vec![], None, None, Some(4 + 1 + 73 * 2)),
(vec![0, 1], vec![], None, None, Some(4 + 1 + 73 * 2 + 1 + 70)),
];

test_inner(&desc, keys, hashes, tests);
Expand Down Expand Up @@ -853,7 +855,8 @@ mod test {
(vec![], vec![], None, None, None),
(vec![0, 1], vec![], None, None, None),
// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) * 3 + 1 (dummy push)
(vec![0, 1, 3], vec![], None, None, Some(4 + 1 + 73 * 3 + 1)),
// + 1 (witness script len) + 139 (witness script)
(vec![0, 1, 3], vec![], None, None, Some(4 + 1 + 73 * 3 + 1 + 1 + 139)),
];

test_inner(&desc, keys, hashes, tests);
Expand Down Expand Up @@ -887,24 +890,28 @@ mod test {
),
(vec![0], vec![], None, None, None),
// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) + 1 (OP_0) + 1 (OP_ZERO)
// + 1 (witness script len) + 85 (witness script)
(
vec![0],
vec![],
Some(Sequence(1000).to_relative_lock_time().unwrap()),
None,
Some(80),
Some(166),
),
// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) * 2 + 2 (OP_PUSHBYTE_1 0x01)
(vec![0, 1], vec![], None, None, Some(153)),
// + 1 (witness script len) + 85 (witness script)
(vec![0, 1], vec![], None, None, Some(239)),
// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) + 1 (OP_0) + 1 (OP_ZERO)
// + 1 (witness script len) + 85 (witness script)
(
vec![0, 1],
vec![],
Some(Sequence(1000).to_relative_lock_time().unwrap()),
None,
Some(80),
Some(166),
),
// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) * 2 + 2 (OP_PUSHBYTE_1 0x01)
// + 1 (witness script len) + 85 (witness script)
(
vec![0, 1],
vec![],
Expand All @@ -914,7 +921,7 @@ mod test {
.unwrap(),
),
None,
Some(153),
Some(239),
), // incompatible timelock
];

Expand All @@ -924,20 +931,22 @@ mod test {

let tests = vec![
// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) + 1 (OP_0) + 1 (OP_ZERO)
// + 1 (witness script len) + 85 (witness script)
(
vec![0],
vec![],
None,
Some(absolute::LockTime::from_height(1000).unwrap()),
Some(80),
Some(166),
),
// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) * 2 + 2 (OP_PUSHBYTE_1 0x01)
// + 1 (witness script len) + 85 (witness script)
(
vec![0, 1],
vec![],
None,
Some(absolute::LockTime::from_time(500_001_000).unwrap()),
Some(153),
Some(239),
), // incompatible timelock
];

Expand Down Expand Up @@ -1081,7 +1090,8 @@ mod test {
(vec![], vec![0], None, None, None),
// Key + hash
// expected weight: 4 (scriptSig len) + 1 (witness len) + 73 (sig) + 1 (OP_PUSH) + 32 (preimage)
(vec![0], vec![0], None, None, Some(111)),
// + 1 (witness script len) + 62 (witness script)
(vec![0], vec![0], None, None, Some(174)),
];

test_inner(&desc, keys, hashes, tests);
Expand Down Expand Up @@ -1260,4 +1270,69 @@ mod test {

assert!(desc.into_plan(&assets).is_err());
}

/// A [`Satisfier`] that knows hash preimages only.
///
/// A plan sizes a signature at its 73-byte maximum while a real one varies
/// in length, so a spend path that carries no signature is the one whose
/// witness has a size a plan can be held to byte for byte.
struct Preimages(BTreeMap<sha256::Hash, [u8; 32]>);

impl Satisfier<DefiniteDescriptorKey> for Preimages {
fn lookup_sha256(&self, h: &sha256::Hash) -> Option<[u8; 32]> { self.0.get(h).copied() }
}

#[test]
fn sizes_match_satisfaction() {
use bitcoin::hashes::Hash as _;

let preimages = [[1u8; 32], [2u8; 32]];
let hashes = preimages.map(|p| sha256::Hash::hash(&p));
let satisfier = Preimages(hashes.iter().copied().zip(preimages).collect());
let assets = Assets::new().add(hashes[0]).add(hashes[1]);

// Sigless, so it has to go around the sanity checks of `from_str`.
let ms = Miniscript::<DefiniteDescriptorKey, Segwitv0>::from_str_insane(&format!(
"and_v(v:sha256({}),sha256({}))",
hashes[0], hashes[1]
))
.unwrap();

for desc in [
Descriptor::new_wsh(ms.clone()).unwrap(),
Descriptor::new_sh_wsh(ms).unwrap(),
] {
let max_weight = desc.max_weight_to_satisfy().unwrap().to_wu() as usize;
let plan = desc.into_plan(&assets).unwrap();
let (witness, script_sig) = plan.satisfy(&satisfier).unwrap();

// The witness as it goes on the wire: an element count followed by
// length-prefixed elements, the witness script among them.
let witness = bitcoin::Witness::from_slice(&witness);
assert_eq!(plan.witness_size(), bitcoin::consensus::serialize(&witness).len());
assert_eq!(plan.scriptsig_size(), varint_len(script_sig.len()) + script_sig.len());

// `max_weight_to_satisfy` is the weight that satisfying adds to an
// input, so it leaves out the scriptSig length byte (4 WU) and the
// witness count byte (1 WU) that an unsatisfied input carries too.
// The plan reports the absolute size, so it is larger by exactly those.
assert_eq!(plan.satisfaction_weight(), max_weight + 4 + 1);
}

// sh(wpkh) needs a signature, whose real size varies, so only what a plan
// can be held to exactly is checked: the scriptSig it returns, and the
// relation to `max_weight_to_satisfy`, which sizes a signature the way the
// plan does.
let key = DescriptorPublicKey::from_str(
"02c2fd50ceae468857bb7eb32ae9cd4083e6c7e42fbbec179d81134b3e3830586c",
)
.unwrap();
let desc =
Descriptor::<DefiniteDescriptorKey>::from_str(&format!("sh(wpkh({}))", key)).unwrap();
let max_weight = desc.max_weight_to_satisfy().unwrap().to_wu() as usize;
let script_sig = desc.unsigned_script_sig();
let plan = desc.into_plan(&Assets::new().add(key)).unwrap();
assert_eq!(plan.scriptsig_size(), varint_len(script_sig.len()) + script_sig.len());
assert_eq!(plan.satisfaction_weight(), max_weight + 4 + 1);
}
}
Loading