Skip to main content

bitcoin_ext/
bitcoin.rs

1
2use std::borrow::Borrow;
3use std::collections::BTreeMap;
4
5use bitcoin::{
6	taproot, Amount, Denomination, FeeRate, OutPoint, ScriptBuf, TapNodeHash, Transaction, TxOut, Weight
7};
8use bitcoin::taproot::ControlBlock;
9use bitcoin::secp256k1::{self, Keypair, Secp256k1};
10
11use crate::{fee, P2PKH_DUST, P2SH_DUST, P2TR_DUST, P2WPKH_DUST, P2WSH_DUST};
12
13/// Extension trait for [Keypair].
14pub trait KeypairExt: Borrow<Keypair> {
15	/// Adapt this key pair to be used in a key-spend using the given MAST root
16	fn for_keyspend(
17		&self,
18		secp: &Secp256k1<impl secp256k1::Verification>,
19		tap_merkle_root: Option<TapNodeHash>,
20	) -> Keypair {
21		let tweak = taproot::TapTweakHash::from_key_and_tweak(
22			self.borrow().x_only_public_key().0,
23			tap_merkle_root,
24		);
25		self.borrow().add_xonly_tweak(secp, &tweak.to_scalar()).expect("hashed values")
26	}
27
28	/// Adapt this key pair to be used in a key-spend-only taproot
29	fn for_keyspend_only(&self, secp: &Secp256k1<impl secp256k1::Verification>) -> Keypair {
30		self.for_keyspend(secp, None)
31	}
32}
33impl KeypairExt for Keypair {}
34
35
36/// Extension trait for [TxOut].
37pub trait TxOutExt: Borrow<TxOut> {
38	/// Check whether this output is a p2a fee anchor.
39	fn is_p2a_fee_anchor(&self) -> bool {
40		self.borrow().script_pubkey == *fee::P2A_SCRIPT
41	}
42
43	/// Basic standardness check. Might be too strict.
44	fn is_standard(&self) -> bool {
45		let out = self.borrow();
46
47		let dust_limit = if out.script_pubkey.is_p2pkh() {
48			P2PKH_DUST
49		} else if out.script_pubkey.is_p2sh() {
50			P2SH_DUST
51		} else if out.script_pubkey.is_p2wpkh() {
52			P2WPKH_DUST
53		} else if out.script_pubkey.is_p2wsh() {
54			P2WSH_DUST
55		} else if out.script_pubkey.is_p2tr() {
56			P2TR_DUST
57		} else if out.script_pubkey.is_op_return() {
58			return out.script_pubkey.len() <= 83;
59		} else {
60			return false;
61		};
62
63		out.value >= dust_limit
64	}
65}
66impl TxOutExt for TxOut {}
67
68
69/// Extension trait for [Transaction].
70pub trait TransactionExt: Borrow<Transaction> {
71	/// Check if this tx has a fee anchor output and return the outpoint of it.
72	///
73	/// Only the first fee anchor is returned.
74	fn fee_anchor(&self) -> Option<(OutPoint, &TxOut)> {
75		for (i, out) in self.borrow().output.iter().enumerate() {
76			if out.is_p2a_fee_anchor() {
77				let point = OutPoint::new(self.borrow().compute_txid(), i as u32);
78				return Some((point, out));
79			}
80		}
81		None
82	}
83
84	/// Returns total output value of the transaction.
85	fn output_value(&self) -> Amount {
86		self.borrow().output.iter().map(|o| o.value).sum()
87	}
88
89	/// Returns an iterator over all input and output UTXOs related to this tx.
90	#[cfg(feature = "all-related-utxos")]
91	fn all_related_utxos(&self) -> impl Iterator<Item = OutPoint> {
92		let tx = self.borrow();
93		let inputs = tx.input.iter().map(|i| i.previous_output);
94		let txid = tx.compute_txid();
95		let outputs = (0..tx.output.len()).map(move |idx| OutPoint::new(txid, idx as u32));
96		inputs.chain(outputs)
97	}
98}
99impl TransactionExt for Transaction {}
100
101
102/// An extension trait for [taproot::TaprootSpendInfo].
103pub trait TaprootSpendInfoExt: Borrow<taproot::TaprootSpendInfo> {
104	/// The p2tr output scriptPubkey for this taproot.
105	fn script_pubkey(&self) -> ScriptBuf {
106		ScriptBuf::new_p2tr_tweaked(self.borrow().output_key())
107	}
108
109	/// Return the existing tapscripts in the format that PSBT expects.
110	fn psbt_tap_scripts(&self) -> BTreeMap<ControlBlock, (ScriptBuf, taproot::LeafVersion)> {
111		let s = self.borrow();
112		s.script_map().keys().map(|pair| {
113			let cb = s.control_block(pair).unwrap();
114			let (script, leaf_version) = pair;
115			(cb, (script.clone(), *leaf_version))
116		}).collect()
117	}
118}
119impl TaprootSpendInfoExt for taproot::TaprootSpendInfo {}
120
121/// Extension trait for [Amount].
122pub trait AmountExt: Borrow<Amount> {
123	fn to_msat(&self) -> u64 {
124		self.borrow().to_sat() * 1_000
125	}
126
127	/// Convert an amount from msat, rounding up.
128	fn from_msat_ceil(value: u64) -> Amount {
129		Amount::from_sat((value + 999) / 1_000)
130	}
131
132	/// Convert an amount from msat, rounding down.
133	fn from_msat_floor(value: u64) -> Amount {
134		Amount::from_sat(value / 1_000)
135	}
136}
137impl AmountExt for Amount {}
138
139
140/// Extension trait for [FeeRate].
141pub trait FeeRateExt: Borrow<FeeRate> {
142	fn from_amount_per_kvb_ceil(amount_vkb: Amount) -> FeeRate {
143		FeeRate::from_sat_per_kvb_ceil(amount_vkb.to_sat())
144	}
145
146	fn from_amount_and_weight_ceil(fee: Amount, weight: Weight) -> Option<FeeRate> {
147		if weight == Weight::ZERO {
148			return None;
149		}
150
151		// Compute the fee rate as amount_sat * 1000 / fee_rate_wu
152		let amount_time_thousand = u64::checked_mul(fee.to_sat(), 1_000)?;
153		let sat_kwu = u64::div_ceil(amount_time_thousand, weight.to_wu());
154		Some(FeeRate::from_sat_per_kwu(sat_kwu))
155	}
156
157	fn from_sat_per_kvb_ceil(sat_kvb: u64) -> FeeRate {
158		// Adding 3 to sat_kvb ensures we always round up when performing integer division.
159		FeeRate::from_sat_per_kwu((sat_kvb + 3) / 4)
160	}
161
162	fn from_sat_per_vb_decimal_checked_ceil(sat_vb: f64) -> Option<FeeRate> {
163		let fee = (sat_vb * 250.0).ceil();
164		if fee.is_finite() && fee >= 0.0 && fee <= u64::MAX as f64 {
165			Some(FeeRate::from_sat_per_kwu(fee as u64))
166		} else {
167			None
168		}
169	}
170
171	fn to_btc_per_kvb(&self) -> String {
172		Amount::from_sat(self.to_sat_per_kvb()).to_string_in(Denomination::Bitcoin)
173	}
174
175	fn to_sat_per_kvb(&self) -> u64 {
176		self.borrow().to_sat_per_kwu() * 4
177	}
178}
179impl FeeRateExt for FeeRate {}
180
181
182#[cfg(test)]
183mod test {
184	use super::*;
185
186	#[test]
187	fn amount_from_msat() {
188		assert_eq!(Amount::from_msat_ceil(3000), Amount::from_sat(3));
189		assert_eq!(Amount::from_msat_ceil(3001), Amount::from_sat(4));
190		assert_eq!(Amount::from_msat_ceil(3999), Amount::from_sat(4));
191
192		assert_eq!(Amount::from_msat_floor(3000), Amount::from_sat(3));
193		assert_eq!(Amount::from_msat_floor(3001), Amount::from_sat(3));
194		assert_eq!(Amount::from_msat_floor(3999), Amount::from_sat(3));
195	}
196
197	#[test]
198	fn fee_rate_from_amount_per_kvb() {
199		assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(1_000)),
200			FeeRate::from_sat_per_kwu(250)
201		);
202		assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(7_372)),
203			FeeRate::from_sat_per_kwu(1_843)
204		);
205		assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(238)),
206			FeeRate::from_sat_per_kwu(60) // 59.5 rounded up
207		);
208		assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(15_775)),
209			FeeRate::from_sat_per_kwu(3_944) // 3943.75 rounded up
210		);
211		assert_eq!(FeeRate::from_amount_per_kvb_ceil(Amount::from_sat(10_125)),
212			FeeRate::from_sat_per_kwu(2_532) // 2531.25 rounded up
213		);
214	}
215
216	#[test]
217	fn fee_rate_from_amount_and_weight() {
218		assert_eq!(FeeRate::from_amount_and_weight_ceil(
219				Amount::from_sat(1_000), Weight::from_wu(0),
220			),
221			None // Divide by zero avoided
222		);
223		assert_eq!(FeeRate::from_amount_and_weight_ceil(
224				Amount::from_sat(u64::MAX / 2), Weight::from_wu(1),
225			),
226			None // Overflow isn't allowed
227		);
228		assert_eq!(FeeRate::from_amount_and_weight_ceil(
229				Amount::from_sat(0), Weight::from_wu(1_000),
230			),
231			Some(FeeRate::ZERO)
232		);
233		assert_eq!(FeeRate::from_amount_and_weight_ceil(
234				Amount::from_sat(500), Weight::from_wu(250)
235			),
236			Some(FeeRate::from_sat_per_kwu(2_000))
237		);
238		assert_eq!(FeeRate::from_amount_and_weight_ceil(
239				Amount::from_sat(100), Weight::from_wu(1000)
240			),
241			Some(FeeRate::from_sat_per_kwu(100))
242		);
243		assert_eq!(FeeRate::from_amount_and_weight_ceil(
244				Amount::from_sat(10_000), Weight::from_wu(327)
245			),
246			Some(FeeRate::from_sat_per_kwu(30_582)) // 30,581.03 rounded up
247		);
248		assert_eq!(FeeRate::from_amount_and_weight_ceil(
249				Amount::from_sat(10_000), Weight::from_wu(256)
250			),
251			Some(FeeRate::from_sat_per_kwu(39_063)) // 39,062.5 rounded up
252		);
253		assert_eq!(FeeRate::from_amount_and_weight_ceil(
254				Amount::from_sat(10_000), Weight::from_wu(2_588)
255			),
256			Some(FeeRate::from_sat_per_kwu(3_864)) // 3,863.98 rounded up
257		);
258	}
259
260	#[test]
261	fn fee_rate_from_sat_per_kvb() {
262		assert_eq!(FeeRate::from_sat_per_kvb_ceil(1_000),
263			FeeRate::from_sat_per_kwu(250)
264		);
265		assert_eq!(FeeRate::from_sat_per_kvb_ceil(7_372),
266			FeeRate::from_sat_per_kwu(1_843)
267		);
268		assert_eq!(FeeRate::from_sat_per_kvb_ceil(238),
269			FeeRate::from_sat_per_kwu(60) // 59.5 rounded up
270		);
271		assert_eq!(FeeRate::from_sat_per_kvb_ceil(15_775),
272			FeeRate::from_sat_per_kwu(3_944) // 3943.75 rounded up
273		);
274		assert_eq!(FeeRate::from_sat_per_kvb_ceil(10_125),
275			FeeRate::from_sat_per_kwu(2_532) // 2531.25 rounded up
276		);
277	}
278
279	#[test]
280	fn fee_rate_from_sat_per_vb_decimal_checked() {
281		assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(-1.0), None);
282		assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(-15_4921.0), None);
283
284		assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(1.0),
285			Some(FeeRate::from_sat_per_kwu(250))
286		);
287		assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(7.372),
288			Some(FeeRate::from_sat_per_kwu(1_843))
289		);
290		assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(0.238),
291			Some(FeeRate::from_sat_per_kwu(60)) // 59.5 rounded up
292		);
293		assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(15.775),
294			Some(FeeRate::from_sat_per_kwu(3_944)) // 3943.75 rounded up
295		);
296		assert_eq!(FeeRate::from_sat_per_vb_decimal_checked_ceil(10.12452),
297			Some(FeeRate::from_sat_per_kwu(2_532)) // 2531.13 rounded up
298		);
299	}
300
301	#[test]
302	fn fee_rate_to_btc_per_kvb() {
303		assert_eq!(FeeRate::from_sat_per_kwu(250).to_btc_per_kvb(), "0.00001");
304		assert_eq!(FeeRate::from_sat_per_kwu(1_843).to_btc_per_kvb(), "0.00007372");
305		assert_eq!(FeeRate::from_sat_per_kwu(60).to_btc_per_kvb(), "0.0000024");
306		assert_eq!(FeeRate::from_sat_per_kwu(3_944).to_btc_per_kvb(), "0.00015776");
307		assert_eq!(FeeRate::from_sat_per_kwu(2_532).to_btc_per_kvb(), "0.00010128");
308	}
309
310	#[test]
311	fn fee_rate_to_sat_per_kvb() {
312		assert_eq!(FeeRate::from_sat_per_kwu(250).to_sat_per_kvb(), 1_000);
313		assert_eq!(FeeRate::from_sat_per_kwu(1_843).to_sat_per_kvb(), 7_372);
314		assert_eq!(FeeRate::from_sat_per_kwu(60).to_sat_per_kvb(), 240);
315		assert_eq!(FeeRate::from_sat_per_kwu(3_944).to_sat_per_kvb(), 15_776);
316		assert_eq!(FeeRate::from_sat_per_kwu(2_532).to_sat_per_kvb(), 10_128);
317	}
318}