1
2
3use std::iter;
4use std::borrow::Cow;
5
6use bitcoin::{
7 Address, Amount, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Weight, Witness
8};
9use bitcoin::secp256k1::{Keypair, PublicKey};
10use bitcoin::sighash::{self, SighashCache, TapSighashType};
11use bitcoin_ext::{fee, KeypairExt, P2TR_DUST};
12
13use crate::SECP;
14
15pub const CONNECTOR_TX_CHAIN_VOUT: u32 = 0;
20pub const CONNECTOR_TX_CONNECTOR_VOUT: u32 = 1;
22
23const TX_WEIGHT: Weight = Weight::from_vb_unchecked(167);
25
26pub const INPUT_WEIGHT: Weight = Weight::from_wu(66);
28
29
30pub fn construct_multi_connector_tx(
32 prev: OutPoint,
33 nb_outputs: usize,
34 connector_spk: &Script,
35) -> Transaction {
36 assert_ne!(nb_outputs, 0);
37 Transaction {
38 version: bitcoin::transaction::Version(3),
39 lock_time: bitcoin::absolute::LockTime::ZERO,
40 input: vec![TxIn {
41 previous_output: prev,
42 script_sig: ScriptBuf::new(),
43 sequence: Sequence::MAX,
44 witness: Witness::new(),
45 }],
46 output: (0..nb_outputs)
47 .map(|_| TxOut { script_pubkey: connector_spk.to_owned(), value: P2TR_DUST })
48 .chain([fee::fee_anchor()])
49 .collect(),
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ConnectorChain {
59 len: usize,
61
62 spk: ScriptBuf,
64
65 utxo: OutPoint,
69}
70
71impl ConnectorChain {
72 pub fn total_weight(len: usize) -> Weight {
74 assert_ne!(len, 0);
75 #[allow(clippy::arithmetic_side_effects)]
77 let w = (len - 1) as u64 * TX_WEIGHT;
78 w
79 }
80
81 pub fn required_budget(len: usize) -> Amount {
84 assert_ne!(len, 0);
85
86 P2TR_DUST * len as u64
90 }
91
92 pub fn output_script(pubkey: PublicKey) -> ScriptBuf {
94 ScriptBuf::new_p2tr(&SECP, pubkey.x_only_public_key().0, None)
95 }
96
97 pub fn address(network: Network, pubkey: PublicKey) -> Address {
99 Address::from_script(&Self::output_script(pubkey), network).unwrap()
100 }
101
102 pub fn output(len: usize, pubkey: PublicKey) -> TxOut {
104 TxOut {
105 script_pubkey: Self::output_script(pubkey),
106 value: Self::required_budget(len),
107 }
108 }
109
110 pub fn new(len: usize, utxo: OutPoint, pubkey: PublicKey) -> ConnectorChain {
117 assert_ne!(len, 0);
118 let spk = Self::output_script(pubkey);
119
120 ConnectorChain { len, spk, utxo }
121 }
122
123 pub fn len(&self) -> usize {
124 self.len
125 }
126
127 #[allow(clippy::arithmetic_side_effects)]
129 fn tx(&self, prev: OutPoint, idx: usize) -> Transaction {
130 Transaction {
131 version: bitcoin::transaction::Version(3),
132 lock_time: bitcoin::absolute::LockTime::ZERO,
133 input: vec![TxIn {
134 previous_output: prev,
135 script_sig: ScriptBuf::new(),
136 sequence: Sequence::MAX,
137 witness: Witness::new(),
138 }],
139 output: vec![
140 TxOut {
143 script_pubkey: self.spk.to_owned(),
144 value: ConnectorChain::required_budget(self.len - idx - 1),
145 },
146 TxOut {
149 script_pubkey: self.spk.to_owned(),
150 value: P2TR_DUST,
151 },
152 fee::fee_anchor(),
154 ],
155 }
156 }
157
158 #[allow(clippy::arithmetic_side_effects)]
161 fn sign_tx(&self, tx: &mut Transaction, idx: usize, keypair: &Keypair) {
162 let prevout = TxOut {
163 script_pubkey: self.spk.to_owned(),
164 value: ConnectorChain::required_budget(self.len - idx),
165 };
166 let mut shc = SighashCache::new(&*tx);
167 let sighash = shc.taproot_key_spend_signature_hash(
168 0, &sighash::Prevouts::All(&[prevout]), TapSighashType::Default,
169 ).expect("sighash error");
170 let sig = SECP.sign_schnorr_with_aux_rand(&sighash.into(), &keypair, &rand::random());
171 tx.input[0].witness = Witness::from_slice(&[&sig[..]]);
172 }
173
174 pub fn iter_signed_txs(
178 &self,
179 sign_key: &Keypair,
180 ) -> Result<ConnectorTxIter<'_>, InvalidSigningKeyError> {
181 if self.spk == ConnectorChain::output_script(sign_key.public_key()) {
182 Ok(ConnectorTxIter {
183 chain: Cow::Borrowed(self),
184 sign_key: Some(sign_key.for_keyspend_only(&*SECP)),
185 prev: self.utxo,
186 idx: 0,
187 })
188 } else {
189 Err(InvalidSigningKeyError)
190 }
191 }
192
193 pub fn iter_unsigned_txs(&self) -> ConnectorTxIter<'_> {
195 ConnectorTxIter {
196 chain: Cow::Borrowed(self),
197 sign_key: None,
198 prev: self.utxo,
199 idx: 0,
200 }
201 }
202
203 pub fn connectors(&self) -> ConnectorIter<'_> {
205 ConnectorIter {
206 txs: self.iter_unsigned_txs(),
207 maybe_last: Some((self.utxo, None)),
208 }
209 }
210
211 pub fn connectors_signed(
215 &self,
216 sign_key: &Keypair,
217 ) -> Result<ConnectorIter<'_>, InvalidSigningKeyError> {
218 Ok(ConnectorIter {
219 txs: self.iter_signed_txs(sign_key)?,
220 maybe_last: Some((self.utxo, None)),
221 })
222 }
223}
224
225pub struct ConnectorTxIter<'a> {
230 chain: Cow<'a, ConnectorChain>,
231 sign_key: Option<Keypair>,
232
233 prev: OutPoint,
234 idx: usize,
235}
236
237impl<'a> ConnectorTxIter<'a> {
238 pub fn signing(&mut self, sign_key: Keypair) {
240 self.sign_key = Some(sign_key);
241 }
242
243 pub fn into_owned(self) -> ConnectorTxIter<'static> {
245 ConnectorTxIter {
246 chain: Cow::Owned(self.chain.into_owned()),
247 sign_key: self.sign_key,
248 prev: self.prev,
249 idx: self.idx,
250 }
251 }
252}
253
254impl<'a> iter::Iterator for ConnectorTxIter<'a> {
255 type Item = Transaction;
256
257 #[allow(clippy::arithmetic_side_effects)]
259 fn next(&mut self) -> Option<Self::Item> {
260 if self.idx >= self.chain.len - 1 {
261 return None;
262 }
263
264 let mut ret = self.chain.tx(self.prev, self.idx);
265 if let Some(ref keypair) = self.sign_key {
266 self.chain.sign_tx(&mut ret, self.idx, keypair);
267 }
268
269 self.idx += 1;
270 self.prev = OutPoint::new(ret.compute_txid(), CONNECTOR_TX_CHAIN_VOUT);
271 Some(ret)
272 }
273
274 fn size_hint(&self) -> (usize, Option<usize>) {
275 let len = self.chain.len.saturating_sub(1).saturating_sub(self.idx);
276 (len, Some(len))
277 }
278}
279
280pub struct ConnectorIter<'a> {
285 txs: ConnectorTxIter<'a>,
286 maybe_last: Option<<Self as Iterator>::Item>,
293}
294
295impl<'a> ConnectorIter<'a> {
296 pub fn signing(&mut self, sign_key: Keypair) {
298 self.txs.signing(sign_key)
299 }
300
301 pub fn into_owned(self) -> ConnectorIter<'static> {
303 ConnectorIter {
304 txs: self.txs.into_owned(),
305 maybe_last: self.maybe_last,
306 }
307 }
308}
309
310impl<'a> iter::Iterator for ConnectorIter<'a> {
311 type Item = (OutPoint, Option<Transaction>);
312
313 fn next(&mut self) -> Option<Self::Item> {
314 if self.maybe_last.is_none() {
315 return None;
316 }
317
318 if let Some(tx) = self.txs.next() {
319 let txid = tx.compute_txid();
320 self.maybe_last = Some((OutPoint::new(txid, CONNECTOR_TX_CHAIN_VOUT), Some(tx.clone())));
321 Some((OutPoint::new(txid, CONNECTOR_TX_CONNECTOR_VOUT), Some(tx)))
322 } else {
323 Some(self.maybe_last.take().expect("broken"))
324 }
325 }
326
327 fn size_hint(&self) -> (usize, Option<usize>) {
328 let len = self.txs.size_hint().0.saturating_add(1);
329 (len, Some(len))
330 }
331}
332
333impl<'a> iter::ExactSizeIterator for ConnectorTxIter<'a> {}
334impl<'a> iter::FusedIterator for ConnectorTxIter<'a> {}
335
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
340#[error("signing key doesn't match connector chain")]
341pub struct InvalidSigningKeyError;
342
343
344#[cfg(test)]
345mod test {
346 use bitcoin::Txid;
347 use bitcoin::hashes::Hash;
348 use bitcoin_ext::TransactionExt;
349 use crate::test_util::verify_tx;
350 use super::*;
351
352 #[test]
353 fn test_budget() {
354 let key = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
355 let utxo = OutPoint::new(Txid::all_zeros(), 3);
356
357 let chain = ConnectorChain::new(1, utxo, key.public_key());
358 assert_eq!(chain.connectors().count(), 1);
359 assert_eq!(chain.iter_unsigned_txs().count(), 0);
360 assert_eq!(chain.connectors().next().unwrap().0, utxo);
361
362 let chain = ConnectorChain::new(2, utxo, key.public_key());
363 assert_eq!(chain.connectors().count(), 2);
364 assert_eq!(chain.iter_unsigned_txs().count(), 1);
365 assert_eq!(chain.iter_signed_txs(&key).unwrap().count(), 1);
366 let tx = chain.iter_signed_txs(&key).unwrap().next().unwrap();
367 assert_eq!(TX_WEIGHT, tx.weight());
368 assert_eq!(tx.output_value(), ConnectorChain::required_budget(2));
369
370 let chain = ConnectorChain::new(3, utxo, key.public_key());
371 assert_eq!(chain.connectors().count(), 3);
372 assert_eq!(chain.iter_unsigned_txs().count(), 2);
373 let mut txs = chain.iter_signed_txs(&key).unwrap();
374 let tx = txs.next().unwrap();
375 assert_eq!(TX_WEIGHT, tx.weight());
376 assert_eq!(tx.output_value(), ConnectorChain::required_budget(3));
377 let tx = txs.next().unwrap();
378 assert_eq!(TX_WEIGHT, tx.weight());
379 assert_eq!(tx.output_value(), ConnectorChain::required_budget(2));
380 assert!(txs.next().is_none());
381
382 let chain = ConnectorChain::new(100, utxo, key.public_key());
383 assert_eq!(chain.connectors().count(), 100);
384 assert_eq!(chain.iter_unsigned_txs().count(), 99);
385 assert_eq!(chain.iter_signed_txs(&key).unwrap().count(), 99);
386 let tx = chain.iter_signed_txs(&key).unwrap().next().unwrap();
387 assert_eq!(TX_WEIGHT, tx.weight());
388 assert_eq!(tx.output_value(), ConnectorChain::required_budget(100));
389 for tx in chain.iter_signed_txs(&key).unwrap() {
390 assert_eq!(tx.weight(), TX_WEIGHT);
391 assert_eq!(tx.input[0].witness.size(), INPUT_WEIGHT.to_wu() as usize);
392 }
393 let weight = chain.iter_signed_txs(&key).unwrap().map(|t| t.weight()).sum::<Weight>();
394 assert_eq!(weight, ConnectorChain::total_weight(100));
395 chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, P2TR_DUST));
396 assert_eq!(P2TR_DUST, chain.iter_unsigned_txs().last().unwrap().output[0].value);
397 }
398
399 #[test]
400 fn test_signatures() {
401 let key = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
402 let utxo = OutPoint::new(Txid::all_zeros(), 3);
403 let spk = ConnectorChain::output_script(key.public_key());
404
405 let chain = ConnectorChain::new(10, utxo, key.public_key());
406 for (i, tx) in chain.iter_signed_txs(&key).unwrap().enumerate() {
407 let amount = ConnectorChain::required_budget(chain.len - i);
408 let input = TxOut {
409 script_pubkey: spk.clone(),
410 value: amount,
411 };
412 verify_tx(&[input], 0, &tx).expect(&format!("invalid connector tx idx {}", i));
413 }
414 }
415}