Skip to content

Commit

Permalink
Introduce max_weight_to_satisfy
Browse files Browse the repository at this point in the history
This commit has two intentions:
1. Define `max_weight_to_satisfy` to be the difference between
   a "satisfied" TxIn's `segwit_weight` and an "unsatisfied" TxIn's
   `segwit_weight`
2. Deprecrate `max_satisfaction_weight`

Comments, tests and examples have been updated to reflect the above
intentions.

Co-authored-by: Daniela Brozzoni <[email protected]>
  • Loading branch information
evanlinjin and danielabrozzoni committed Dec 14, 2022
1 parent a7bfff4 commit ae5bdaf
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 12 deletions.
2 changes: 1 addition & 1 deletion embedded/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fn main() -> ! {
assert!(desc.sanity_check().is_ok());

// Estimate the satisfaction cost
assert_eq!(desc.max_satisfaction_weight().unwrap(), 293);
assert_eq!(desc.max_weight_to_satisfy().unwrap(), 288);
// end miniscript test

// exit QEMU
Expand Down
2 changes: 1 addition & 1 deletion examples/psbt_sign_finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn main() {
);
println!(
"Weight for witness satisfaction cost {}",
bridge_descriptor.max_satisfaction_weight().unwrap()
bridge_descriptor.max_weight_to_satisfy().unwrap()
);

let master_private_key_str = "cQhdvB3McbBJdx78VSSumqoHQiSXs75qwLptqwxSQBNBMDxafvaw";
Expand Down
6 changes: 3 additions & 3 deletions examples/sign_multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ fn main() {
let descriptor = miniscript::Descriptor::<bitcoin::PublicKey>::from_str(&s).unwrap();

// Check weight for witness satisfaction cost ahead of time.
// 4 (scriptSig length of 0) + 1 (witness stack size) + 106 (serialized witnessScript)
// + 73*2 (signature length + signatures + sighash bytes) + 1 (dummy byte) = 258
assert_eq!(descriptor.max_satisfaction_weight().unwrap(), 258);
// 106 (serialized witnessScript)
// + 73*2 (signature length + signatures + sighash bytes) + 1 (dummy byte) = 253
assert_eq!(descriptor.max_weight_to_satisfy().unwrap(), 253);

// Sometimes it is necessary to have additional information to get the
// `bitcoin::PublicKey` from the `MiniscriptKey` which can be supplied by
Expand Down
10 changes: 5 additions & 5 deletions examples/taproot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ fn main() {

// Max Satisfaction Weight for compilation, corresponding to the script-path spend
// `multi_a(2,PUBKEY_1,PUBKEY_2) at taptree depth 1, having
// Max Witness Size = scriptSig len + witnessStack len + varint(control_block_size) +
// control_block size + varint(script_size) + script_size + max_satisfaction_size
// = 4 + 1 + 1 + 65 + 1 + 70 + 132 = 274
let max_sat_wt = real_desc.max_satisfaction_weight().unwrap();
assert_eq!(max_sat_wt, 274);
// Max Witness Size = varint(control_block_size) + control_block size +
// varint(script_size) + script_size + max_satisfaction_size
// = 1 + 65 + 1 + 70 + 132 = 269
let max_sat_wt = real_desc.max_weight_to_satisfy().unwrap();
assert_eq!(max_sat_wt, 269);

// Compute the bitcoin address and check if it matches
let network = Network::Bitcoin;
Expand Down
42 changes: 42 additions & 0 deletions src/descriptor/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,26 @@ impl<Pk: MiniscriptKey> Bare<Pk> {
Ok(())
}

/// Computes an upper bound on the difference between a non-satisfied
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
///
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
/// if you want to include only legacy inputs in your transaction,
/// you should remove 1WU from each input's `max_weight_to_satisfy`
/// for a more accurate estimate.
///
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
/// sighash suffix.
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
let scriptsig_size = self.ms.max_satisfaction_size()?;
// scriptSig varint difference between non-satisfied (0) and satisfied
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
Ok(4 * (scriptsig_varint_diff + scriptsig_size))
}

/// Computes an upper bound on the weight of a satisfying witness to the
/// transaction.
///
Expand All @@ -75,6 +95,7 @@ impl<Pk: MiniscriptKey> Bare<Pk> {
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
#[deprecated(note = "use max_weight_to_satisfy instead")]
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
let scriptsig_len = self.ms.max_satisfaction_size()?;
Ok(4 * (varint_len(scriptsig_len) + scriptsig_len))
Expand Down Expand Up @@ -212,6 +233,27 @@ impl<Pk: MiniscriptKey> Pkh<Pk> {
self.pk
}

/// Computes an upper bound on the difference between a non-satisfied
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
///
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
/// if you want to include only legacy inputs in your transaction,
/// you should remove 1WU from each input's `max_weight_to_satisfy`
/// for a more accurate estimate.
///
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
/// sighash suffix.
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
pub fn max_weight_to_satisfy(&self) -> usize {
// OP_72 + <sig(71)+sigHash(1)> + OP_33 + <pubkey>
let scriptsig_size = 73 + BareCtx::pk_len(&self.pk);
// scriptSig varint different between non-satisfied (0) and satisfied
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
4 * (scriptsig_varint_diff + scriptsig_size)
}

/// Computes an upper bound on the weight of a satisfying witness to the
/// transaction.
///
Expand Down
55 changes: 55 additions & 0 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,59 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
}
}

/// Computes an upper bound on the difference between a non-satisfied
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
///
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
/// if you want to include only legacy inputs in your transaction,
/// you should remove 1WU from each input's `max_weight_to_satisfy`
/// for a more accurate estimate.
///
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
/// if you want to include only legacy inputs in your transaction,
/// you should remove 1WU from each input's `max_weight_to_satisfy`
/// for a more accurate estimate.
///
/// In other words, for segwit inputs or legacy inputs included in
/// segwit transactions, it holds:
/// ```ignore
/// for i in 0..transaction.input.len() {
/// assert_eq!(
/// descriptor_for_input[i].max_weight_to_satisfy(),
/// transaction.input[i].segwit_weight() - Txin::default().segwit_weight()
/// );
/// }
/// ```
///
/// Instead, for legacy transactions, it holds:
/// ```ignore
/// for i in 0..transaction.input.len() {
/// assert_eq!(
/// descriptor_for_input[i].max_weight_to_satisfy(),
/// transaction.input[i].legacy_weight() - Txin::default().legacy_weight()
/// );
/// }
/// ```
///
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
/// sighash suffix.
/// Assumes all Schnorr signatures are 66 bytes, including push opcode and
/// sighash suffix.
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
let weight = match *self {
Descriptor::Bare(ref bare) => bare.max_weight_to_satisfy()?,
Descriptor::Pkh(ref pkh) => pkh.max_weight_to_satisfy(),
Descriptor::Wpkh(ref wpkh) => wpkh.max_weight_to_satisfy(),
Descriptor::Wsh(ref wsh) => wsh.max_weight_to_satisfy()?,
Descriptor::Sh(ref sh) => sh.max_weight_to_satisfy()?,
Descriptor::Tr(ref tr) => tr.max_weight_to_satisfy()?,
};
Ok(weight)
}

/// Computes an upper bound on the weight of a satisfying witness to the
/// transaction.
///
Expand All @@ -326,6 +379,8 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
#[deprecated(note = "use max_weight_to_satisfy instead")]
#[allow(deprecated)]
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
let weight = match *self {
Descriptor::Bare(ref bare) => bare.max_satisfaction_weight()?,
Expand Down
42 changes: 42 additions & 0 deletions src/descriptor/segwitv0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,34 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
Ok(())
}

/// Computes an upper bound on the difference between a non-satisfied
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
///
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
/// sighash suffix.
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
let (redeem_script_size, max_sat_elems, max_sat_size) = match self.inner {
WshInner::SortedMulti(ref smv) => (
smv.script_size(),
smv.max_satisfaction_witness_elements(),
smv.max_satisfaction_size(),
),
WshInner::Ms(ref ms) => (
ms.script_size(),
ms.max_satisfaction_witness_elements()?,
ms.max_satisfaction_size()?,
),
};
// stack size varint difference between non-satisfied (0) and satisfied
// `max_sat_elems` is inclusive of the "witness script" (redeem script)
let stack_varint_diff = varint_len(max_sat_elems) - varint_len(0);

Ok(stack_varint_diff + varint_len(redeem_script_size) + redeem_script_size + max_sat_size)
}

/// Computes an upper bound on the weight of a satisfying witness to the
/// transaction.
///
Expand All @@ -91,6 +119,7 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
#[deprecated(note = "use max_weight_to_satisfy instead")]
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
let (script_size, max_sat_elems, max_sat_size) = match self.inner {
WshInner::SortedMulti(ref smv) => (
Expand Down Expand Up @@ -325,6 +354,19 @@ impl<Pk: MiniscriptKey> Wpkh<Pk> {
}
}

/// Computes an upper bound on the difference between a non-satisfied
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
///
/// Assumes all ec-signatures are 73 bytes, including push opcode and
/// sighash suffix.
pub fn max_weight_to_satisfy(&self) -> usize {
// stack items: <varint(sig+sigHash)> <sig(71)+sigHash(1)> <varint(pubkey)> <pubkey>
let stack_items_size = 73 + Segwitv0::pk_len(&self.pk);
// stackLen varint difference between non-satisfied (0) and satisfied
let stack_varint_diff = varint_len(2) - varint_len(0);
stack_varint_diff + stack_items_size
}

/// Computes an upper bound on the weight of a satisfying witness to the
/// transaction.
///
Expand Down
53 changes: 52 additions & 1 deletion src/descriptor/sh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,66 @@ impl<Pk: MiniscriptKey> Sh<Pk> {
}
}

/// Computes an upper bound on the difference between a non-satisfied
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
///
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
/// if you want to include only legacy inputs in your transaction,
/// you should remove 1WU from each input's `max_weight_to_satisfy`
/// for a more accurate estimate.
///
/// Assumes all ec-signatures are 73 bytes, including push opcode and
/// sighash suffix.
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
let (scriptsig_size, witness_size) = match self.inner {
// add weighted script sig, len byte stays the same
ShInner::Wsh(ref wsh) => {
// scriptSig: OP_34 <OP_0 OP_32 <32-byte-hash>>
let scriptsig_size = 1 + 1 + 1 + 32;
let witness_size = wsh.max_weight_to_satisfy()?;
(scriptsig_size, witness_size)
}
ShInner::SortedMulti(ref smv) => {
let ss = smv.script_size();
let ps = push_opcode_size(ss);
let scriptsig_size = ps + ss + smv.max_satisfaction_size();
(scriptsig_size, 0)
}
// add weighted script sig, len byte stays the same
ShInner::Wpkh(ref wpkh) => {
// scriptSig: OP_22 <OP_0 OP_20 <20-byte-hash>>
let scriptsig_size = 1 + 1 + 1 + 20;
let witness_size = wpkh.max_weight_to_satisfy();
(scriptsig_size, witness_size)
}
ShInner::Ms(ref ms) => {
let ss = ms.script_size();
let ps = push_opcode_size(ss);
let scriptsig_size = ps + ss + ms.max_satisfaction_size()?;
(scriptsig_size, 0)
}
};

// scriptSigLen varint difference between non-satisfied (0) and satisfied
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);

Ok(4 * (scriptsig_varint_diff + scriptsig_size) + witness_size)
}

/// Computes an upper bound on the weight of a satisfying witness to the
/// transaction.
///
/// Assumes all ec-signatures are 73 bytes, including push opcode and
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
/// sighash suffix. Includes the weight of the VarInts encoding the
/// scriptSig and witness stack length.
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
#[deprecated(note = "use max_weight_to_satisfy instead")]
#[allow(deprecated)]
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
Ok(match self.inner {
// add weighted script sig, len byte stays the same
Expand Down
50 changes: 50 additions & 0 deletions src/descriptor/tr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,55 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
Ok(())
}

/// Computes an upper bound on the difference between a non-satisfied
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
///
/// Assumes all Schnorr signatures are 66 bytes, including push opcode and
/// sighash suffix.
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
let tree = match self.taptree() {
None => {
// key spend path
// item: varint(sig+sigHash) + <sig(64)+sigHash(1)>
let item_sig_size = 1 + 65;
// 1 stack item
let stack_varint_diff = varint_len(1) - varint_len(0);

return Ok(stack_varint_diff + item_sig_size);
}
// script path spend..
Some(tree) => tree,
};

tree.iter()
.filter_map(|(depth, ms)| {
let script_size = ms.script_size();
let max_sat_elems = ms.max_satisfaction_witness_elements().ok()?;
let max_sat_size = ms.max_satisfaction_size().ok()?;
let control_block_size = control_block_len(depth);

// stack varint difference (+1 for ctrl block, witness script already included)
let stack_varint_diff = varint_len(max_sat_elems + 1) - varint_len(0);

Some(
stack_varint_diff +
// size of elements to satisfy script
max_sat_size +
// second to last element: script
varint_len(script_size) +
script_size +
// last element: control block
varint_len(control_block_size) +
control_block_size,
)
})
.max()
.ok_or(Error::ImpossibleSatisfaction)
}

/// Computes an upper bound on the weight of a satisfying witness to the
/// transaction.
///
Expand All @@ -255,6 +304,7 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
///
/// # Errors
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
#[deprecated(note = "use max_weight_to_satisfy instead")]
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
let tree = match self.taptree() {
// key spend path:
Expand Down
9 changes: 8 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,14 @@
//! assert!(desc.sanity_check().is_ok());
//!
//! // Estimate the satisfaction cost
//! assert_eq!(desc.max_satisfaction_weight().unwrap(), 293);
//! // scriptSig:
//! // * OP_PUSH34 <OP_0 OP_32 <32-byte-hash>>
//! // = (1 + 1 + 1 + 32) * 4 = 140 WU
//! // redeemScript: varint <OP_33 <pk1> OP_CHECKSIG OP_IFDUP OP_NOTIF OP_33 <pk2> OP_CHECKSIG OP_ENDIF>
//! // = 1 + (1 + 33 + 1 + 1 + 1 + 1 + 33 + 1 + 1) = 74 WU
//! // stackItem[Sig]: varint <sig+sighash> = 1 + 73 = 74 WU
//! // Expected satisfaction weight: 140 + 74 + 74 = 288
//! assert_eq!(desc.max_weight_to_satisfy().unwrap(), 288);
//! ```
//!
Expand Down

0 comments on commit ae5bdaf

Please sign in to comment.