Skip to content

Commit c08f6dc

Browse files
committed
Merge #476: Another attempt at max_satisfaction_weight fixes
7296f8e Introduce `max_weight_to_satisfy` (志宇) Pull request description: Replaces #474, refer to #474 (comment) This PR has two intentions: 1. Redefine `max_satisfaction_weight` to be the difference in `TxIn` weight between "satisfied" and "unsatisfied" states. In an "unsatisfied" state, we still need to include the `scriptSigLen` varint, as well as the `witnessStackLen` (for txs with at least one segwit spend). 2. Attempt further fixes to improve accuracy of `max_satisfaction_weight`. Comments, tests and examples have been updated to reflect the above intentions. ### Notes for reviewers The new definition of `max_satisfaction_weight` can be seen in this comment: https://github.com/rust-bitcoin/rust-miniscript/blob/08cff39fa862ff957c7ff96d17a0011dd6446f87/src/descriptor/mod.rs#L320-L339 ACKs for top commit: sanket1729: ACK 7296f8e Tree-SHA512: ecae8ff742198289b598aefde83ad66a4b7c7cb67da0625b4d84271df510331408c3da6fc8796fc234ce095af08a2f34a5beb5a10549d7ce20a9878b3ab6fd47
2 parents 0ee470d + 7296f8e commit c08f6dc

File tree

10 files changed

+256
-12
lines changed

10 files changed

+256
-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
@@ -56,6 +56,26 @@ impl<Pk: MiniscriptKey> Bare<Pk> {
5656
Ok(())
5757
}
5858

59+
/// Computes an upper bound on the difference between a non-satisfied
60+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
61+
///
62+
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
63+
/// if you want to include only legacy inputs in your transaction,
64+
/// you should remove 1WU from each input's `max_weight_to_satisfy`
65+
/// for a more accurate estimate.
66+
///
67+
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
68+
/// sighash suffix.
69+
///
70+
/// # Errors
71+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
72+
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
73+
let scriptsig_size = self.ms.max_satisfaction_size()?;
74+
// scriptSig varint difference between non-satisfied (0) and satisfied
75+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
76+
Ok(4 * (scriptsig_varint_diff + scriptsig_size))
77+
}
78+
5979
/// Computes an upper bound on the weight of a satisfying witness to the
6080
/// transaction.
6181
///
@@ -65,6 +85,7 @@ impl<Pk: MiniscriptKey> Bare<Pk> {
6585
///
6686
/// # Errors
6787
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
88+
#[deprecated(note = "use max_weight_to_satisfy instead")]
6889
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
6990
let scriptsig_len = self.ms.max_satisfaction_size()?;
7091
Ok(4 * (varint_len(scriptsig_len) + scriptsig_len))
@@ -202,6 +223,27 @@ impl<Pk: MiniscriptKey> Pkh<Pk> {
202223
self.pk
203224
}
204225

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

src/descriptor/mod.rs

+52
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,56 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
307307
}
308308
}
309309

310+
/// Computes an upper bound on the difference between a non-satisfied
311+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
312+
///
313+
/// Since this method uses `segwit_weight` instead of `legacy_weight`,
314+
/// if you want to include only legacy inputs in your transaction,
315+
/// you should remove 1WU from each input's `max_weight_to_satisfy`
316+
/// for a more accurate estimate.
317+
///
318+
/// In other words, for segwit inputs or legacy inputs included in
319+
/// segwit transactions, the following will hold for each input if
320+
/// that input was satisfied with the largest possible witness:
321+
/// ```ignore
322+
/// for i in 0..transaction.input.len() {
323+
/// assert_eq!(
324+
/// descriptor_for_input[i].max_weight_to_satisfy(),
325+
/// transaction.input[i].segwit_weight() - Txin::default().segwit_weight()
326+
/// );
327+
/// }
328+
/// ```
329+
///
330+
/// Instead, for legacy transactions, the following will hold for each input
331+
/// if that input was satisfied with the largest possible witness:
332+
/// ```ignore
333+
/// for i in 0..transaction.input.len() {
334+
/// assert_eq!(
335+
/// descriptor_for_input[i].max_weight_to_satisfy(),
336+
/// transaction.input[i].legacy_weight() - Txin::default().legacy_weight()
337+
/// );
338+
/// }
339+
/// ```
340+
///
341+
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
342+
/// sighash suffix.
343+
/// Assumes all Schnorr signatures are 66 bytes, including push opcode and
344+
/// sighash suffix.
345+
///
346+
/// # Errors
347+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
348+
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
349+
let weight = match *self {
350+
Descriptor::Bare(ref bare) => bare.max_weight_to_satisfy()?,
351+
Descriptor::Pkh(ref pkh) => pkh.max_weight_to_satisfy(),
352+
Descriptor::Wpkh(ref wpkh) => wpkh.max_weight_to_satisfy(),
353+
Descriptor::Wsh(ref wsh) => wsh.max_weight_to_satisfy()?,
354+
Descriptor::Sh(ref sh) => sh.max_weight_to_satisfy()?,
355+
Descriptor::Tr(ref tr) => tr.max_weight_to_satisfy()?,
356+
};
357+
Ok(weight)
358+
}
359+
310360
/// Computes an upper bound on the weight of a satisfying witness to the
311361
/// transaction.
312362
///
@@ -316,6 +366,8 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
316366
///
317367
/// # Errors
318368
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
369+
#[deprecated(note = "use max_weight_to_satisfy instead")]
370+
#[allow(deprecated)]
319371
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
320372
let weight = match *self {
321373
Descriptor::Bare(ref bare) => bare.max_satisfaction_weight()?,

src/descriptor/segwitv0.rs

+42
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,34 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
7272
Ok(())
7373
}
7474

75+
/// Computes an upper bound on the difference between a non-satisfied
76+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
77+
///
78+
/// Assumes all ECDSA signatures are 73 bytes, including push opcode and
79+
/// sighash suffix.
80+
///
81+
/// # Errors
82+
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
83+
pub fn max_weight_to_satisfy(&self) -> Result<usize, Error> {
84+
let (redeem_script_size, max_sat_elems, max_sat_size) = match self.inner {
85+
WshInner::SortedMulti(ref smv) => (
86+
smv.script_size(),
87+
smv.max_satisfaction_witness_elements(),
88+
smv.max_satisfaction_size(),
89+
),
90+
WshInner::Ms(ref ms) => (
91+
ms.script_size(),
92+
ms.max_satisfaction_witness_elements()?,
93+
ms.max_satisfaction_size()?,
94+
),
95+
};
96+
// stack size varint difference between non-satisfied (0) and satisfied
97+
// `max_sat_elems` is inclusive of the "witness script" (redeem script)
98+
let stack_varint_diff = varint_len(max_sat_elems) - varint_len(0);
99+
100+
Ok(stack_varint_diff + varint_len(redeem_script_size) + redeem_script_size + max_sat_size)
101+
}
102+
75103
/// Computes an upper bound on the weight of a satisfying witness to the
76104
/// transaction.
77105
///
@@ -81,6 +109,7 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
81109
///
82110
/// # Errors
83111
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
112+
#[deprecated(note = "use max_weight_to_satisfy instead")]
84113
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
85114
let (script_size, max_sat_elems, max_sat_size) = match self.inner {
86115
WshInner::SortedMulti(ref smv) => (
@@ -315,6 +344,19 @@ impl<Pk: MiniscriptKey> Wpkh<Pk> {
315344
}
316345
}
317346

347+
/// Computes an upper bound on the difference between a non-satisfied
348+
/// `TxIn`'s `segwit_weight` and a satisfied `TxIn`'s `segwit_weight`
349+
///
350+
/// Assumes all ec-signatures are 73 bytes, including push opcode and
351+
/// sighash suffix.
352+
pub fn max_weight_to_satisfy(&self) -> usize {
353+
// stack items: <varint(sig+sigHash)> <sig(71)+sigHash(1)> <varint(pubkey)> <pubkey>
354+
let stack_items_size = 73 + Segwitv0::pk_len(&self.pk);
355+
// stackLen varint difference between non-satisfied (0) and satisfied
356+
let stack_varint_diff = varint_len(2) - varint_len(0);
357+
stack_varint_diff + stack_items_size
358+
}
359+
318360
/// Computes an upper bound on the weight of a satisfying witness to the
319361
/// transaction.
320362
///

src/descriptor/sh.rs

+52-1
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,66 @@ impl<Pk: MiniscriptKey> Sh<Pk> {
196196
}
197197
}
198198

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

src/descriptor/tr.rs

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

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

src/lib.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,14 @@
6262
//! assert!(desc.sanity_check().is_ok());
6363
//!
6464
//! // Estimate the satisfaction cost.
65-
//! assert_eq!(desc.max_satisfaction_weight().unwrap(), 293);
65+
//! // scriptSig: OP_PUSH34 <OP_0 OP_32 <32-byte-hash>>
66+
//! // = (1 + 1 + 1 + 32) * 4 = 140 WU
67+
//! // redeemScript: varint <OP_33 <pk1> OP_CHECKSIG OP_IFDUP OP_NOTIF OP_33 <pk2> OP_CHECKSIG OP_ENDIF>
68+
//! // = 1 + (1 + 33 + 1 + 1 + 1 + 1 + 33 + 1 + 1) = 74 WU
69+
//! // stackItem[Sig]: varint <sig+sighash>
70+
//! // = 1 + 73 = 74 WU
71+
//! // Expected satisfaction weight: 140 + 74 + 74 = 288
72+
//! assert_eq!(desc.max_weight_to_satisfy().unwrap(), 288);
6673
//! ```
6774
//!
6875

0 commit comments

Comments
 (0)