Skip to content

Commit ff107bd

Browse files
Introduce max_weight_to_satisfy
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]>
1 parent a7bfff4 commit ff107bd

File tree

10 files changed

+248
-12
lines changed

10 files changed

+248
-12
lines changed

embedded/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ fn main() -> ! {
5353
assert!(desc.sanity_check().is_ok());
5454

5555
// Estimate the satisfaction cost
56-
assert_eq!(desc.max_satisfaction_weight().unwrap(), 293);
56+
assert_eq!(desc.max_weight_to_satisfy().unwrap(), 288);
5757
// end miniscript test
5858

5959
// exit QEMU

examples/psbt_sign_finalize.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fn main() {
3333
);
3434
println!(
3535
"Weight for witness satisfaction cost {}",
36-
bridge_descriptor.max_satisfaction_weight().unwrap()
36+
bridge_descriptor.max_weight_to_satisfy().unwrap()
3737
);
3838

3939
let master_private_key_str = "cQhdvB3McbBJdx78VSSumqoHQiSXs75qwLptqwxSQBNBMDxafvaw";

examples/sign_multisig.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ fn main() {
3030
let descriptor = miniscript::Descriptor::<bitcoin::PublicKey>::from_str(&s).unwrap();
3131

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

3737
// Sometimes it is necessary to have additional information to get the
3838
// `bitcoin::PublicKey` from the `MiniscriptKey` which can be supplied by

examples/taproot.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ fn main() {
100100

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

109109
// Compute the bitcoin address and check if it matches
110110
let network = Network::Bitcoin;

src/descriptor/bare.rs

+42
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@ impl<Pk: MiniscriptKey> Bare<Pk> {
6666
Ok(())
6767
}
6868

69+
/// Computes an upper bound on the difference between a non-satisfied
70+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
71+
///
72+
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
73+
/// if you want to include only legacy inputs in your transaction,
74+
/// you should remove 1WU from each input's `max_weight_to_satisfy`
75+
/// for a more accurate estimate.
76+
///
77+
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
78+
/// sighash suffix.
79+
///
80+
/// # Errors
81+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
82+
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
83+
let scriptsig_size = self.ms.max_satisfaction_size()?;
84+
// scriptSig varint difference between non-satisfied (0) and satisfied
85+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
86+
Ok(4 * (scriptsig_varint_diff + scriptsig_size))
87+
}
88+
6989
/// Computes an upper bound on the weight of a satisfying witness to the
7090
/// transaction.
7191
///
@@ -75,6 +95,7 @@ impl<Pk: MiniscriptKey> Bare<Pk> {
7595
///
7696
/// # Errors
7797
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
98+
#[deprecated(note = "use max_weight_to_satisfy instead")]
7899
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
79100
let scriptsig_len = self.ms.max_satisfaction_size()?;
80101
Ok(4 * (varint_len(scriptsig_len) + scriptsig_len))
@@ -212,6 +233,27 @@ impl<Pk: MiniscriptKey> Pkh<Pk> {
212233
self.pk
213234
}
214235

236+
/// Computes an upper bound on the difference between a non-satisfied
237+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
238+
///
239+
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
240+
/// if you want to include only legacy inputs in your transaction,
241+
/// you should remove 1WU from each input's `max_weight_to_satisfy`
242+
/// for a more accurate estimate.
243+
///
244+
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
245+
/// sighash suffix.
246+
///
247+
/// # Errors
248+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
249+
pub fn max_weight_to_satisfy(&self) -> usize {
250+
// OP_72 + <sig(71)+sigHash(1)> + OP_33 + <pubkey>
251+
let scriptsig_size = 73 + BareCtx::pk_len(&self.pk);
252+
// scriptSig varint different between non-satisfied (0) and satisfied
253+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
254+
4 * (scriptsig_varint_diff + scriptsig_size)
255+
}
256+
215257
/// Computes an upper bound on the weight of a satisfying witness to the
216258
/// transaction.
217259
///

src/descriptor/mod.rs

+44
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,48 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
317317
}
318318
}
319319

320+
/// Computes an upper bound on the difference between a non-satisfied
321+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
322+
///
323+
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
324+
/// if you want to include only legacy inputs in your transaction,
325+
/// you should remove 1WU from each input's `max_weight_to_satisfy`
326+
/// for a more accurate estimate.
327+
///
328+
/// A non-satisfied `TxIn`'s segwit_weight includes the `scriptSigLen`
329+
/// varint recording the value 0 (which is 4WU) and also the
330+
/// `witnessStackLen`, which will also record the value 0 (which is 1WU).
331+
///
332+
/// Hence, "satisfaction weight" contains the additional varint weight of
333+
/// `scriptSigLen` and `witnessStackLen` (which occurs when the varint value
334+
/// increases over the 1byte threshold), and also the weights of `scriptSig`
335+
/// and the rest of the `witnessField` (which is the stack items and the
336+
/// stack-item-len varints of each item).
337+
///
338+
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
339+
/// if you want to include only legacy inputs in your transaction,
340+
/// you should remove 1WU from each input's `max_weight_to_satisfy`
341+
/// for a more accurate estimate.
342+
///
343+
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
344+
/// sighash suffix.
345+
/// Assumes all Schnorr signatures are 66 bytes, including push opcode and
346+
/// sighash suffix.
347+
///
348+
/// # Errors
349+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
350+
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
351+
let weight = match *self {
352+
Descriptor::Bare(ref bare) => bare.max_weight_to_satisfy()?,
353+
Descriptor::Pkh(ref pkh) => pkh.max_weight_to_satisfy(),
354+
Descriptor::Wpkh(ref wpkh) => wpkh.max_weight_to_satisfy(),
355+
Descriptor::Wsh(ref wsh) => wsh.max_weight_to_satisfy()?,
356+
Descriptor::Sh(ref sh) => sh.max_weight_to_satisfy()?,
357+
Descriptor::Tr(ref tr) => tr.max_weight_to_satisfy()?,
358+
};
359+
Ok(weight)
360+
}
361+
320362
/// Computes an upper bound on the weight of a satisfying witness to the
321363
/// transaction.
322364
///
@@ -326,6 +368,8 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
326368
///
327369
/// # Errors
328370
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
371+
#[deprecated(note = "use max_weight_to_satisfy instead")]
372+
#[allow(deprecated)]
329373
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
330374
let weight = match *self {
331375
Descriptor::Bare(ref bare) => bare.max_satisfaction_weight()?,

src/descriptor/segwitv0.rs

+42
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,34 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
8282
Ok(())
8383
}
8484

85+
/// Computes an upper bound on the difference between a non-satisfied
86+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
87+
///
88+
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
89+
/// sighash suffix.
90+
///
91+
/// # Errors
92+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
93+
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
94+
let (redeem_script_size, max_sat_elems, max_sat_size) = match self.inner {
95+
WshInner::SortedMulti(ref smv) => (
96+
smv.script_size(),
97+
smv.max_satisfaction_witness_elements(),
98+
smv.max_satisfaction_size(),
99+
),
100+
WshInner::Ms(ref ms) => (
101+
ms.script_size(),
102+
ms.max_satisfaction_witness_elements()?,
103+
ms.max_satisfaction_size()?,
104+
),
105+
};
106+
// stack size varint difference between non-satisfied (0) and satisfied
107+
// `max_sat_elems` is inclusive of the "witness script" (redeem script)
108+
let stack_varint_diff = varint_len(max_sat_elems) - varint_len(0);
109+
110+
Ok(stack_varint_diff + varint_len(redeem_script_size) + redeem_script_size + max_sat_size)
111+
}
112+
85113
/// Computes an upper bound on the weight of a satisfying witness to the
86114
/// transaction.
87115
///
@@ -91,6 +119,7 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
91119
///
92120
/// # Errors
93121
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
122+
#[deprecated(note = "use max_weight_to_satisfy instead")]
94123
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
95124
let (script_size, max_sat_elems, max_sat_size) = match self.inner {
96125
WshInner::SortedMulti(ref smv) => (
@@ -325,6 +354,19 @@ impl<Pk: MiniscriptKey> Wpkh<Pk> {
325354
}
326355
}
327356

357+
/// Computes an upper bound on the difference between a non-satisfied
358+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
359+
///
360+
/// Assumes all ec-signatures are 73 bytes, including push opcode and
361+
/// sighash suffix.
362+
pub fn max_weight_to_satisfy(&self) -> usize {
363+
// stack items: <varint(sig+sigHash)> <sig(71)+sigHash(1)> <varint(pubkey)> <pubkey>
364+
let stack_items_size = 73 + Segwitv0::pk_len(&self.pk);
365+
// stackLen varint difference between non-satisfied (0) and satisfied
366+
let stack_varint_diff = varint_len(2) - varint_len(0);
367+
stack_varint_diff + stack_items_size
368+
}
369+
328370
/// Computes an upper bound on the weight of a satisfying witness to the
329371
/// transaction.
330372
///

src/descriptor/sh.rs

+52-1
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,66 @@ impl<Pk: MiniscriptKey> Sh<Pk> {
206206
}
207207
}
208208

209+
/// Computes an upper bound on the difference between a non-satisfied
210+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
211+
///
212+
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
213+
/// if you want to include only legacy inputs in your transaction,
214+
/// you should remove 1WU from each input's `max_weight_to_satisfy`
215+
/// for a more accurate estimate.
216+
///
217+
/// Assumes all ec-signatures are 73 bytes, including push opcode and
218+
/// sighash suffix.
219+
///
220+
/// # Errors
221+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
222+
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
223+
let (scriptsig_size, witness_size) = match self.inner {
224+
// add weighted script sig, len byte stays the same
225+
ShInner::Wsh(ref wsh) => {
226+
// scriptSig: OP_34 <OP_0 OP_32 <32-byte-hash>>
227+
let scriptsig_size = 1 + 1 + 1 + 32;
228+
let witness_size = wsh.max_weight_to_satisfy()?;
229+
(scriptsig_size, witness_size)
230+
}
231+
ShInner::SortedMulti(ref smv) => {
232+
let ss = smv.script_size();
233+
let ps = push_opcode_size(ss);
234+
let scriptsig_size = ps + ss + smv.max_satisfaction_size();
235+
(scriptsig_size, 0)
236+
}
237+
// add weighted script sig, len byte stays the same
238+
ShInner::Wpkh(ref wpkh) => {
239+
// scriptSig: OP_22 <OP_0 OP_20 <20-byte-hash>>
240+
let scriptsig_size = 1 + 1 + 1 + 20;
241+
let witness_size = wpkh.max_weight_to_satisfy();
242+
(scriptsig_size, witness_size)
243+
}
244+
ShInner::Ms(ref ms) => {
245+
let ss = ms.script_size();
246+
let ps = push_opcode_size(ss);
247+
let scriptsig_size = ps + ss + ms.max_satisfaction_size()?;
248+
(scriptsig_size, 0)
249+
}
250+
};
251+
252+
// scriptSigLen varint difference between non-satisfied (0) and satisfied
253+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
254+
255+
Ok(4 * (scriptsig_varint_diff + scriptsig_size) + witness_size)
256+
}
257+
209258
/// Computes an upper bound on the weight of a satisfying witness to the
210259
/// transaction.
211260
///
212-
/// Assumes all ec-signatures are 73 bytes, including push opcode and
261+
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
213262
/// sighash suffix. Includes the weight of the VarInts encoding the
214263
/// scriptSig and witness stack length.
215264
///
216265
/// # Errors
217266
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
267+
#[deprecated(note = "use max_weight_to_satisfy instead")]
268+
#[allow(deprecated)]
218269
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
219270
Ok(match self.inner {
220271
// add weighted script sig, len byte stays the same

src/descriptor/tr.rs

+50
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,55 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
246246
Ok(())
247247
}
248248

249+
/// Computes an upper bound on the difference between a non-satisfied
250+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
251+
///
252+
/// Assumes all Schnorr signatures are 66 bytes, including push opcode and
253+
/// sighash suffix.
254+
///
255+
/// # Errors
256+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
257+
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
258+
let tree = match self.taptree() {
259+
None => {
260+
// key spend path
261+
// item: varint(sig+sigHash) + <sig(64)+sigHash(1)>
262+
let item_sig_size = 1 + 65;
263+
// 1 stack item
264+
let stack_varint_diff = varint_len(1) - varint_len(0);
265+
266+
return Ok(stack_varint_diff + item_sig_size);
267+
}
268+
// script path spend..
269+
Some(tree) => tree,
270+
};
271+
272+
tree.iter()
273+
.filter_map(|(depth, ms)| {
274+
let script_size = ms.script_size();
275+
let max_sat_elems = ms.max_satisfaction_witness_elements().ok()?;
276+
let max_sat_size = ms.max_satisfaction_size().ok()?;
277+
let control_block_size = control_block_len(depth);
278+
279+
// stack varint difference (+1 for ctrl block, witness script already included)
280+
let stack_varint_diff = varint_len(max_sat_elems + 1) - varint_len(0);
281+
282+
Some(
283+
stack_varint_diff +
284+
// size of elements to satisfy script
285+
max_sat_size +
286+
// second to last element: script
287+
varint_len(script_size) +
288+
script_size +
289+
// last element: control block
290+
varint_len(control_block_size) +
291+
control_block_size,
292+
)
293+
})
294+
.max()
295+
.ok_or(Error::ImpossibleSatisfaction)
296+
}
297+
249298
/// Computes an upper bound on the weight of a satisfying witness to the
250299
/// transaction.
251300
///
@@ -255,6 +304,7 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
255304
///
256305
/// # Errors
257306
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
307+
#[deprecated(note = "use max_weight_to_satisfy instead")]
258308
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
259309
let tree = match self.taptree() {
260310
// key spend path:

src/lib.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,14 @@
7474
//! assert!(desc.sanity_check().is_ok());
7575
//!
7676
//! // Estimate the satisfaction cost
77-
//! assert_eq!(desc.max_satisfaction_weight().unwrap(), 293);
77+
//! // scriptSig:
78+
//! // * OP_PUSH34 <OP_0 OP_32 <32-byte-hash>>
79+
//! // = (1 + 1 + 1 + 32) * 4 = 140 WU
80+
//! // redeemScript: varint <OP_33 <pk1> OP_CHECKSIG OP_IFDUP OP_NOTIF OP_33 <pk2> OP_CHECKSIG OP_ENDIF>
81+
//! // = 1 + (1 + 33 + 1 + 1 + 1 + 1 + 33 + 1 + 1) = 74 WU
82+
//! // stackItem[Sig]: varint <sig+sighash> = 1 + 73 = 74 WU
83+
//! // Expected satisfaction weight: 140 + 74 + 74 = 288
84+
//! assert_eq!(desc.max_weight_to_satisfy().unwrap(), 288);
7885
//! ```
7986
//!
8087

0 commit comments

Comments
 (0)