Skip to content

Commit 238014a

Browse files
committed
More max_satisfaction_weight fixes
This commit 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.
1 parent d5615ac commit 238014a

File tree

10 files changed

+111
-70
lines changed

10 files changed

+111
-70
lines changed

embedded/src/main.rs

Lines changed: 1 addition & 1 deletion
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_satisfaction_weight().unwrap(), 284);
5757
// end miniscript test
5858

5959
// exit QEMU

examples/sign_multisig.rs

Lines changed: 3 additions & 3 deletions
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_satisfaction_weight().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

Lines changed: 4 additions & 4 deletions
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
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
106106
let max_sat_wt = real_desc.max_satisfaction_weight().unwrap();
107-
assert_eq!(max_sat_wt, 274);
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

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,16 @@ impl<Pk: MiniscriptKey> Bare<Pk> {
6666
Ok(())
6767
}
6868

69-
/// Computes an upper bound on the weight of a satisfying witness to the
70-
/// transaction.
69+
/// Computes an upper bound on the difference in weight between a
70+
/// non-satisfied `TxIn` (with empty `scriptSig` and `witness` fields) and a
71+
/// satisfied `TxIn`.
7172
///
72-
/// Assumes all ec-signatures are 73 bytes, including push opcode and
73-
/// sighash suffix. Includes the weight of the VarInts encoding the
74-
/// scriptSig and witness stack length.
75-
///
76-
/// # Errors
77-
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
73+
/// Assumes a ec-signature is 73 bytes (inclusive of sigHash and op_push/varint).
7874
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
79-
let scriptsig_len = self.ms.max_satisfaction_size()?;
80-
Ok(4 * (varint_len(scriptsig_len) + scriptsig_len))
75+
let scriptsig_size = self.ms.max_satisfaction_size()?;
76+
// scriptSig varint difference between non-satisfied (0) and satisfied
77+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
78+
Ok(4 * (scriptsig_varint_diff + scriptsig_size))
8179
}
8280
}
8381

@@ -211,14 +209,17 @@ impl<Pk: MiniscriptKey> Pkh<Pk> {
211209
self.pk
212210
}
213211

214-
/// Computes an upper bound on the weight of a satisfying witness to the
215-
/// transaction.
212+
/// Computes an upper bound on the difference in weight between a
213+
/// non-satisfied `TxIn` (with empty `scriptSig` and `witness` fields) and a
214+
/// satisfied `TxIn`.
216215
///
217-
/// Assumes all ec-signatures are 73 bytes, including push opcode and
218-
/// sighash suffix. Includes the weight of the VarInts encoding the
219-
/// scriptSig and witness stack length.
216+
/// Assumes a ec-signature is 73 bytes (inclusive of sigHash and op_push/varint).
220217
pub fn max_satisfaction_weight(&self) -> usize {
221-
4 * (1 + 73 + BareCtx::pk_len(&self.pk))
218+
// OP_72 + <sig(71)+sigHash(1)> + OP_33 + <pubkey>
219+
let scriptsig_size = 73 + BareCtx::pk_len(&self.pk);
220+
// scriptSig varint different between non-satisfied (0) and satisfied
221+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
222+
4 * (scriptsig_varint_diff + scriptsig_size)
222223
}
223224
}
224225

src/descriptor/mod.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -317,12 +317,22 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> {
317317
}
318318
}
319319

320-
/// Computes an upper bound on the weight of a satisfying witness to the
321-
/// transaction.
320+
/// Computes an upper bound on the difference in weight between a
321+
/// non-satisfied `TxIn` and a satisfied `TxIn`.
322322
///
323-
/// Assumes all ec-signatures are 73 bytes, including push opcode and
324-
/// sighash suffix. Includes the weight of the VarInts encoding the
325-
/// scriptSig and witness stack length.
323+
/// A non-satisfied `TxIn` contains the `scriptSigLen` varint recording the
324+
/// value 0 (which is 4WU) and also the `witnessStackLen` for transactions
325+
/// that have at least one witness spend, which will also record the value 0
326+
/// (which is 1WU).
327+
///
328+
/// Hence, "satisfaction weight" contains the additional varint weight of
329+
/// `scriptSigLen` and `witnessStackLen` (which occurs when the varint value
330+
/// increases over the 1byte threshold), and also the weights of `scriptSig`
331+
/// and the rest of the `witnessField` (which is the stack items and the
332+
/// stack-item-len varints of each item).
333+
///
334+
/// This assumes a ec-signatures is always 73 bytes, and the sighash prefix
335+
/// is always included (+1 byte).
326336
///
327337
/// # Errors
328338
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).

src/descriptor/segwitv0.rs

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,16 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
8484
Ok(())
8585
}
8686

87-
/// Computes an upper bound on the weight of a satisfying witness to the
88-
/// transaction.
87+
/// Computes an upper bound on the difference in weight between a
88+
/// non-satisfied `TxIn` (with empty `scriptSig` and `witness` fields) and a
89+
/// satisfied `TxIn`.
8990
///
90-
/// Assumes all ec-signatures are 73 bytes, including push opcode and
91-
/// sighash suffix. Includes the weight of the VarInts encoding the
92-
/// scriptSig and witness stack length.
91+
/// Assumes a ec-signature is 73 bytes (inclusive of sigHash and op_push/varint).
9392
///
9493
/// # Errors
9594
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
9695
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
97-
let (script_size, max_sat_elems, max_sat_size) = match self.inner {
96+
let (redeem_script_size, max_sat_elems, max_sat_size) = match self.inner {
9897
WshInner::SortedMulti(ref smv) => (
9998
smv.script_size(),
10099
smv.max_satisfaction_witness_elements(),
@@ -106,11 +105,11 @@ impl<Pk: MiniscriptKey> Wsh<Pk> {
106105
ms.max_satisfaction_size()?,
107106
),
108107
};
109-
Ok(4 + // scriptSig length byte
110-
varint_len(script_size) +
111-
script_size +
112-
varint_len(max_sat_elems) +
113-
max_sat_size)
108+
// stack size varint difference between non-satisfied (0) and satisfied
109+
// `max_sat_elems` is inclusive of the "witness script" (redeem script)
110+
let stack_varint_diff = varint_len(max_sat_elems) - varint_len(0);
111+
112+
Ok(stack_varint_diff + varint_len(redeem_script_size) + redeem_script_size + max_sat_size)
114113
}
115114
}
116115

@@ -322,14 +321,17 @@ impl<Pk: MiniscriptKey> Wpkh<Pk> {
322321
}
323322
}
324323

325-
/// Computes an upper bound on the weight of a satisfying witness to the
326-
/// transaction.
324+
/// Computes an upper bound on the difference in weight between a
325+
/// non-satisfied `TxIn` (with empty `scriptSig` and `witness` fields) and a
326+
/// satisfied `TxIn`.
327327
///
328-
/// Assumes all ec-signatures are 73 bytes, including push opcode and
329-
/// sighash suffix. Includes the weight of the VarInts encoding the
330-
/// scriptSig and witness stack length.
328+
/// Assumes a ec-signature is 73 bytes (inclusive of sigHash and op_push/varint).
331329
pub fn max_satisfaction_weight(&self) -> usize {
332-
4 + 1 + 73 + Segwitv0::pk_len(&self.pk)
330+
// stack items: <varint(sig+sigHash)> <sig(71)+sigHash(1)> <varint(pubkey)> <pubkey>
331+
let stack_items_size = 73 + Segwitv0::pk_len(&self.pk);
332+
// stackLen varint difference between non-satisfied (0) and satisfied
333+
let stack_varint_diff = varint_len(2) - varint_len(0);
334+
stack_varint_diff + stack_items_size
333335
}
334336
}
335337

src/descriptor/sh.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -205,32 +205,45 @@ impl<Pk: MiniscriptKey> Sh<Pk> {
205205
}
206206
}
207207

208-
/// Computes an upper bound on the weight of a satisfying witness to the
209-
/// transaction.
208+
/// Computes an upper bound on the difference in weight between a
209+
/// non-satisfied `TxIn` (with empty `scriptSig` and `witness` fields) and a
210+
/// satisfied `TxIn`.
210211
///
211-
/// Assumes all ec-signatures are 73 bytes, including push opcode and
212-
/// sighash suffix. Includes the weight of the VarInts encoding the
213-
/// scriptSig and witness stack length.
212+
/// Assumes a ec-signature is 73 bytes and the sigHash suffix is always
213+
/// included (+1 byte).
214214
///
215215
/// # Errors
216216
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
217217
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
218218
Ok(match self.inner {
219219
// add weighted script sig, len byte stays the same
220-
ShInner::Wsh(ref wsh) => 4 * 35 + wsh.max_satisfaction_weight()?,
220+
ShInner::Wsh(ref wsh) => {
221+
// scriptSig: OP_0 OP_32 <32-byte-hash>
222+
let scriptsig_size = 1 + 1 + 32;
223+
// scriptSigLen varint difference between non-satisfied (0) and satisfied
224+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
225+
4 * (scriptsig_varint_diff + scriptsig_size) + wsh.max_satisfaction_weight()?
226+
}
221227
ShInner::SortedMulti(ref smv) => {
222228
let ss = smv.script_size();
223229
let ps = push_opcode_size(ss);
224-
let scriptsig_len = ps + ss + smv.max_satisfaction_size();
225-
4 * (varint_len(scriptsig_len) + scriptsig_len)
230+
let scriptsig_size = ps + ss + smv.max_satisfaction_size();
231+
let scirptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
232+
4 * (scirptsig_varint_diff + scriptsig_size)
226233
}
227234
// add weighted script sig, len byte stays the same
228-
ShInner::Wpkh(ref wpkh) => 4 * 23 + wpkh.max_satisfaction_weight(),
235+
ShInner::Wpkh(ref wpkh) => {
236+
// scriptSig: OP_0 OP_20 <20-byte-hash>
237+
let scriptsig_size = 1 + 1 + 20;
238+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
239+
4 * (scriptsig_varint_diff + scriptsig_size) + wpkh.max_satisfaction_weight()
240+
}
229241
ShInner::Ms(ref ms) => {
230242
let ss = ms.script_size();
231243
let ps = push_opcode_size(ss);
232-
let scriptsig_len = ps + ss + ms.max_satisfaction_size()?;
233-
4 * (varint_len(scriptsig_len) + scriptsig_len)
244+
let scriptsig_size = ps + ss + ms.max_satisfaction_size()?;
245+
let scriptsig_varint_diff = varint_len(scriptsig_size) - varint_len(0);
246+
4 * (scriptsig_varint_diff + scriptsig_size)
234247
}
235248
})
236249
}

src/descriptor/tr.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -254,20 +254,26 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
254254
Ok(())
255255
}
256256

257-
/// Computes an upper bound on the weight of a satisfying witness to the
258-
/// transaction.
257+
/// Computes an upper bound on the difference in weight between a
258+
/// non-satisfied `TxIn` (with empty `scriptSig` and `witness` fields) and a
259+
/// satisfied `TxIn`.
259260
///
260-
/// Assumes all ec-signatures are 73 bytes, including push opcode and
261-
/// sighash suffix. Includes the weight of the VarInts encoding the
262-
/// scriptSig and witness stack length.
261+
/// Assumes a ec-signature is 73 bytes and the sigHash suffix is always
262+
/// included (+1 byte).
263263
///
264264
/// # Errors
265265
/// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)).
266266
pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
267267
let tree = match self.taptree() {
268-
// key spend path:
269-
// scriptSigLen(4) + stackLen(1) + stack[Sig]Len(1) + stack[Sig](65)
270-
None => return Ok(4 + 1 + 1 + 65),
268+
None => {
269+
// key spend path
270+
// item: varint(sig+sigHash) + <sig(74)+sigHash(1)>
271+
let item_sig_size = 1 + 65;
272+
// 1 stack item
273+
let stack_varint_diff = varint_len(1) - varint_len(0);
274+
275+
return Ok(stack_varint_diff + item_sig_size);
276+
}
271277
// script path spend..
272278
Some(tree) => tree,
273279
};
@@ -278,11 +284,12 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
278284
let max_sat_elems = ms.max_satisfaction_witness_elements().ok()?;
279285
let max_sat_size = ms.max_satisfaction_size().ok()?;
280286
let control_block_size = control_block_len(depth);
287+
288+
// stack varint difference (+1 for ctrl block, witness script already included)
289+
let stack_varint_diff = varint_len(max_sat_elems + 1) - varint_len(0);
290+
281291
Some(
282-
// scriptSig len byte
283-
4 +
284-
// witness field stack len (+2 for control block & script)
285-
varint_len(max_sat_elems + 2) +
292+
stack_varint_diff +
286293
// size of elements to satisfy script
287294
max_sat_size +
288295
// second to last element: script

src/lib.rs

Lines changed: 8 additions & 1 deletion
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_0 OP_32 <32-byte-hash>
79+
//! // = (1 + 1 + 32) * 4 = 136 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 + 72+1 = 74 WU (TODO: Figure out why sig is 72bytes here)
83+
//! // Expected satisfaction weight: 136 + 74 + 74 = 284
84+
//! assert_eq!(desc.max_satisfaction_weight().unwrap(), 284);
7885
//! ```
7986
//!
8087

src/miniscript/context.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ where
222222

223223
/// Depending on script context, the size of a satifaction witness may slightly differ.
224224
fn max_satisfaction_size<Pk: MiniscriptKey>(ms: &Miniscript<Pk, Self>) -> Option<usize>;
225+
225226
/// Depending on script Context, some of the Terminals might not
226227
/// be valid under the current consensus rules.
227228
/// Or some of the script resource limits may have been exceeded.

0 commit comments

Comments
 (0)