From b5ce219c621378f7f6313aefb9a46011e4305e88 Mon Sep 17 00:00:00 2001 From: conduition Date: Tue, 21 Nov 2023 05:57:46 +0000 Subject: [PATCH 1/3] add weight method to InputWeightPrediction This method computes the weight an InputWeightPrediction would to a transaction, not including witness flag bytes. --- bitcoin/src/blockdata/transaction.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 68622ef1..f151c0bb 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -1202,7 +1202,7 @@ where |(count, partial_input_weight, inputs_with_witnesses), prediction| { ( count + 1, - partial_input_weight + prediction.script_size * 4 + prediction.witness_size, + partial_input_weight + prediction.weight().to_wu() as usize, inputs_with_witnesses + (prediction.witness_size > 0) as usize, ) }, @@ -1276,7 +1276,7 @@ pub const fn predict_weight_from_slices( let mut i = 0; while i < inputs.len() { let prediction = inputs[i]; - partial_input_weight += prediction.script_size * 4 + prediction.witness_size; + partial_input_weight += prediction.weight().to_wu() as usize; inputs_with_witnesses += (prediction.witness_size > 0) as usize; i += 1; } @@ -1440,6 +1440,12 @@ impl InputWeightPrediction { InputWeightPrediction { script_size, witness_size } } + + /// Tallies the total weight added to a transaction by an input with this weight prediction, + /// not counting potential witness flag bytes or the witness count varint. + pub const fn weight(&self) -> Weight { + Weight::from_wu_usize(self.script_size * 4 + self.witness_size) + } } #[cfg(test)] From 4514a80a23c721bb641cf10513ad139384093a1a Mon Sep 17 00:00:00 2001 From: conduition Date: Tue, 21 Nov 2023 06:04:38 +0000 Subject: [PATCH 2/3] Fix the InputWeightPrediction constants for DER signatures The P2WPKH_MAX constant assumed DER signatures in the witness have a max length of 73. However, their maximum length in practice is 72, because BIP62 forbids nodes from relaying transactions whose ECDSA signatures are not canonical (i.e. all sigs must have an s value of less than n/2). This means s is never encoded with a leading zero byte, and the signature as a whole never exceeds 72 bytes in total encoded length. The ground_p2wpkh function was already correct; only the constant needed to be corrected. --- bitcoin/src/blockdata/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index f151c0bb..654c7015 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -1321,7 +1321,7 @@ impl InputWeightPrediction { /// under-paying. See [`ground_p2wpkh`](Self::ground_p2wpkh) if you do use signature grinding. /// /// [signature grinding]: https://bitcoin.stackexchange.com/questions/111660/what-is-signature-grinding - pub const P2WPKH_MAX: Self = InputWeightPrediction::from_slice(0, &[73, 33]); + pub const P2WPKH_MAX: Self = InputWeightPrediction::from_slice(0, &[72, 33]); /// Input weight prediction corresponding to spending of a P2PKH output with the largest possible /// DER-encoded signature, and a compressed public key. From f41ebc2149f44857cb90b0e0adc957aefb2787d4 Mon Sep 17 00:00:00 2001 From: conduition Date: Tue, 21 Nov 2023 06:06:03 +0000 Subject: [PATCH 3/3] Add test for input weight predictions Sanity checks the InputWeightPrediction against a transaction which uses P2WPKH inputs. --- bitcoin/src/blockdata/transaction.rs | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 654c7015..d711f0ae 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -2153,6 +2153,56 @@ mod tests { assert_eq!(tx.total_sigop_cost(return_none), *expected_none); } } + + #[test] + fn weight_predictions() { + // TXID 3d3381f968e3a73841cba5e73bf47dcea9f25a9f7663c51c81f1db8229a309a0 + let tx_raw = hex!( + "01000000000103fc9aa70afba04da865f9821734b556cca9fb5710\ + fc1338b97fba811033f755e308000000000000000019b37457784d\ + d04936f011f733b8016c247a9ef08d40007a54a5159d1fc62ee216\ + 00000000000000004c4f2937c6ccf8256d9711a19df1ae62172297\ + 0bf46be925ff15f490efa1633d01000000000000000002c0e1e400\ + 0000000017a9146983f776902c1d1d0355ae0962cb7bc69e9afbde\ + 8706a1e600000000001600144257782711458506b89f255202d645\ + e25c41144702483045022100dcada0499865a49d0aab8cb113c5f8\ + 3fd5a97abc793f97f3f53aa4b9d1192ed702202094c7934666a30d\ + 6adb1cc9e3b6bc14d2ffebd3200f3908c40053ef2df640b5012103\ + 15434bb59b615a383ae87316e784fc11835bb97fab33fdd2578025\ + e9968d516e0247304402201d90b3197650569eba4bc0e0b1e2dca7\ + 7dfac7b80d4366f335b67e92e0546e4402203b4be1d443ad7e3a5e\ + a92aafbcdc027bf9ccf5fe68c0bc8f3ebb6ab806c5464c012103e0\ + 0d92b0fe60731a54fdbcc6920934159db8ffd69d55564579b69a22\ + ec5bb7530247304402205ab83b734df818e64d8b9e86a8a75f9d00\ + 5c0c6e1b988d045604853ab9ccbde002205a580235841df609d6bd\ + 67534bdcd301999b18e74e197e9e476cdef5fdcbf822012102ebb3\ + e8a4638ede4721fb98e44e3a3cd61fecfe744461b85e0b6a6a1017\ + 5d5aca00000000" + ); + + let tx = Transaction::consensus_decode::<&[u8]>(&mut tx_raw.as_ref()).unwrap(); + let input_weights = vec![ + InputWeightPrediction::P2WPKH_MAX, + InputWeightPrediction::ground_p2wpkh(1), + InputWeightPrediction::ground_p2wpkh(1), + ]; + // Outputs: [P2SH, P2WPKH] + + // Confirm the transaction's predicted weight matches its actual weight. + let predicted = predict_weight(input_weights, tx.script_pubkey_lens()); + let expected = tx.weight(); + assert_eq!(predicted, expected); + + // Confirm signature grinding input weight predictions are aligned with constants. + assert_eq!( + InputWeightPrediction::ground_p2wpkh(0).weight(), + InputWeightPrediction::P2WPKH_MAX.weight() + ); + assert_eq!( + InputWeightPrediction::ground_p2pkh_compressed(0).weight(), + InputWeightPrediction::P2PKH_COMPRESSED_MAX.weight() + ); + } } #[cfg(bench)]