Merge #148: descriptors: add a method to get the size of a spending input

eebc96c4d53f0f6a86d5ddf4267b3d4a45aa7e3f descriptors: add a method to get the size of a spending input (Antoine Poinsot)

Pull request description:

ACKs for top commit:
  edouardparis:
    utACK eebc96c4d53f0f6a86d5ddf4267b3d4a45aa7e3f

Tree-SHA512: 6d94d7eccfed1d828633f7942072c7b044339c1df7add1df209bba6409131aa138f8cf83a36691df88af237943dcc45c3b1e050745143ab663a6c9c13c83176a
This commit is contained in:
edouard 2022-12-05 11:15:07 +01:00
commit 38e342c8cd
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F

View File

@ -17,6 +17,15 @@ use std::{collections::BTreeMap, convert::TryFrom, error, fmt, str, sync};
use serde::{Deserialize, Serialize};
const WITNESS_FACTOR: usize = 4;
// Convert a size in weight units to a size in virtual bytes, rounding up.
fn wu_to_vb(vb: usize) -> usize {
(vb + WITNESS_FACTOR - 1)
.checked_div(WITNESS_FACTOR)
.expect("Non 0")
}
#[derive(Debug)]
pub enum DescCreationError {
InsaneTimelock(u32),
@ -504,6 +513,20 @@ impl InheritanceDescriptor {
),
)
}
/// Get the maximum size in WU of a satisfaction for this descriptor.
pub fn max_sat_weight(&self) -> usize {
self.0
.max_satisfaction_weight()
.expect("Cannot fail for P2WSH")
}
/// Get the maximum size in virtual bytes of the whole input in a transaction spending
/// a coin with this Script.
pub fn spender_input_size(&self) -> usize {
// txid + vout + nSequence + empty scriptSig + witness
32 + 4 + 4 + 1 + wu_to_vb(self.max_sat_weight())
}
}
/// Map of a raw public key to the xpub used to derive it and its derivation path
@ -620,5 +643,47 @@ mod tests {
assert_eq!(desc.timelock_value(), 0xffff);
}
#[test]
fn inheritance_descriptor_sat_size() {
let secp = secp256k1::Secp256k1::verification_only();
let desc = MultipathDescriptor::from_str("wsh(or_d(pk([92162c45]tpubD6NzVbkrYhZ4WzTf9SsD6h7AH7oQEippXK2KP8qvhMMqFoNeN5YFVi7vRyeRSDGtgd2bPyMxUNmHui8t5yCgszxPPxMafu1VVzDpg9aruYW/<0;1>/*),and_v(v:pkh(tpubD6NzVbkrYhZ4Wdgu2yfdmrce5g4fiH1ZLmKhewsnNKupbi4sxjH1ZVAorkBLWSkhsjhg8kiq8C4BrBjMy3SjAKDyDdbuvUa1ToAHbiR98js/<0;1>/*),older(2))))#uact7s3g").unwrap();
let receive_desc = desc.receive_descriptor();
let change_desc = desc.change_descriptor();
// Receive and change are the same descriptor.
assert_eq!(receive_desc.max_sat_weight(), change_desc.max_sat_weight());
assert_eq!(
receive_desc.spender_input_size(),
change_desc.spender_input_size()
);
// Derived or not the expected maximum satisfaction size should be the same for
// the same descriptor.
assert_eq!(
receive_desc.derive(999.into(), &secp).max_sat_weight(),
change_desc.derive(999.into(), &secp).max_sat_weight()
);
// Maximum input size is (txid + vout + scriptsig + nSequence + max_sat).
// Where max_sat is:
// - Push the witness stack size
// - Push the script
// - Push an empty vector for using the recovery path
// - Push the recovery key
// - Push a signature for the recovery key
// NOTE: The specific value is asserted because this was tested against a regtest
// transaction.
let stack = vec![vec![0; 68], vec![0; 0], vec![0; 33], vec![0; 72]];
let witness_size = bitcoin::VarInt(stack.len() as u64).len()
+ stack
.iter()
.map(|item| bitcoin::VarInt(stack.len() as u64).len() + item.len())
.sum::<usize>();
assert_eq!(
receive_desc.spender_input_size(),
32 + 4 + 1 + 4 + wu_to_vb(witness_size),
);
}
// TODO: test error conditions of deserialization.
}