bitcoin_ext/
bitcoin.rs

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