1
2
3use std::iter;
4use std::borrow::Cow;
5
6use bitcoin::{
7 Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
8 Weight, Witness,
9};
10use bitcoin::secp256k1::{Keypair, PublicKey};
11use bitcoin::sighash::{self, SighashCache, TapSighashType};
12use bitcoin_ext::{fee, KeypairExt, P2TR_DUST};
13
14use crate::SECP;
15
16pub const CONNECTOR_TX_CHAIN_VOUT: u32 = 0;
21pub const CONNECTOR_TX_CONNECTOR_VOUT: u32 = 1;
23
24const TX_WEIGHT: Weight = Weight::from_vb_unchecked(167);
26
27pub const INPUT_WEIGHT: Weight = Weight::from_wu(66);
29
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ConnectorChain {
37 len: usize,
39
40 spk: ScriptBuf,
42
43 utxo: OutPoint,
47}
48
49impl ConnectorChain {
50 pub fn total_weight(len: usize) -> Weight {
52 assert_ne!(len, 0);
53 (len - 1) as u64 * TX_WEIGHT
54 }
55
56 pub fn required_budget(len: usize) -> Amount {
59 assert_ne!(len, 0);
60
61 P2TR_DUST * len as u64
65 }
66
67 pub fn output_script(pubkey: PublicKey) -> ScriptBuf {
69 ScriptBuf::new_p2tr(&SECP, pubkey.x_only_public_key().0, None)
70 }
71
72 pub fn address(network: Network, pubkey: PublicKey) -> Address {
74 Address::from_script(&Self::output_script(pubkey), network).unwrap()
75 }
76
77 pub fn output(len: usize, pubkey: PublicKey) -> TxOut {
79 TxOut {
80 script_pubkey: Self::output_script(pubkey),
81 value: Self::required_budget(len),
82 }
83 }
84
85 pub fn new(len: usize, utxo: OutPoint, pubkey: PublicKey) -> ConnectorChain {
92 assert_ne!(len, 0);
93 let spk = Self::output_script(pubkey);
94
95 ConnectorChain { len, spk, utxo }
96 }
97
98 pub fn len(&self) -> usize {
99 self.len
100 }
101
102 fn tx(&self, prev: OutPoint, idx: usize) -> Transaction {
103 Transaction {
104 version: bitcoin::transaction::Version(3),
105 lock_time: bitcoin::absolute::LockTime::ZERO,
106 input: vec![TxIn {
107 previous_output: prev,
108 script_sig: ScriptBuf::new(),
109 sequence: Sequence::MAX,
110 witness: Witness::new(),
111 }],
112 output: vec![
113 TxOut {
116 script_pubkey: self.spk.to_owned(),
117 value: ConnectorChain::required_budget(self.len - idx - 1),
118 },
119 TxOut {
122 script_pubkey: self.spk.to_owned(),
123 value: P2TR_DUST,
124 },
125 fee::fee_anchor(),
127 ],
128 }
129 }
130
131 fn sign_tx(&self, tx: &mut Transaction, idx: usize, keypair: &Keypair) {
133 let prevout = TxOut {
134 script_pubkey: self.spk.to_owned(),
135 value: ConnectorChain::required_budget(self.len - idx),
136 };
137 let mut shc = SighashCache::new(&*tx);
138 let sighash = shc.taproot_key_spend_signature_hash(
139 0, &sighash::Prevouts::All(&[prevout]), TapSighashType::Default,
140 ).expect("sighash error");
141 let sig = SECP.sign_schnorr(&sighash.into(), &keypair);
142 tx.input[0].witness = Witness::from_slice(&[&sig[..]]);
143 }
144
145 pub fn iter_signed_txs(
149 &self,
150 sign_key: &Keypair,
151 ) -> Result<ConnectorTxIter<'_>, InvalidSigningKeyError> {
152 if self.spk == ConnectorChain::output_script(sign_key.public_key()) {
153 Ok(ConnectorTxIter {
154 chain: Cow::Borrowed(self),
155 sign_key: Some(sign_key.for_keyspend(&*SECP)),
156 prev: self.utxo,
157 idx: 0,
158 })
159 } else {
160 Err(InvalidSigningKeyError)
161 }
162 }
163
164 pub fn iter_unsigned_txs(&self) -> ConnectorTxIter<'_> {
166 ConnectorTxIter {
167 chain: Cow::Borrowed(self),
168 sign_key: None,
169 prev: self.utxo,
170 idx: 0,
171 }
172 }
173
174 pub fn connectors(&self) -> ConnectorIter<'_> {
176 ConnectorIter {
177 txs: self.iter_unsigned_txs(),
178 maybe_last: Some((self.utxo, None)),
179 }
180 }
181
182 pub fn connectors_signed(
186 &self,
187 sign_key: &Keypair,
188 ) -> Result<ConnectorIter<'_>, InvalidSigningKeyError> {
189 Ok(ConnectorIter {
190 txs: self.iter_signed_txs(sign_key)?,
191 maybe_last: Some((self.utxo, None)),
192 })
193 }
194}
195
196pub struct ConnectorTxIter<'a> {
201 chain: Cow<'a, ConnectorChain>,
202 sign_key: Option<Keypair>,
203
204 prev: OutPoint,
205 idx: usize,
206}
207
208impl<'a> ConnectorTxIter<'a> {
209 pub fn signing(&mut self, sign_key: Keypair) {
211 self.sign_key = Some(sign_key);
212 }
213
214 pub fn into_owned(self) -> ConnectorTxIter<'static> {
216 ConnectorTxIter {
217 chain: Cow::Owned(self.chain.into_owned()),
218 sign_key: self.sign_key,
219 prev: self.prev,
220 idx: self.idx,
221 }
222 }
223}
224
225impl<'a> iter::Iterator for ConnectorTxIter<'a> {
226 type Item = Transaction;
227
228 fn next(&mut self) -> Option<Self::Item> {
229 if self.idx >= self.chain.len - 1 {
230 return None;
231 }
232
233 let mut ret = self.chain.tx(self.prev, self.idx);
234 if let Some(ref keypair) = self.sign_key {
235 self.chain.sign_tx(&mut ret, self.idx, keypair);
236 }
237
238 self.idx += 1;
239 self.prev = OutPoint::new(ret.compute_txid(), CONNECTOR_TX_CHAIN_VOUT);
240 Some(ret)
241 }
242
243 fn size_hint(&self) -> (usize, Option<usize>) {
244 let len = (self.chain.len - 1).saturating_sub(self.idx);
245 (len, Some(len))
246 }
247}
248
249pub struct ConnectorIter<'a> {
254 txs: ConnectorTxIter<'a>,
255 maybe_last: Option<<Self as Iterator>::Item>,
262}
263
264impl<'a> ConnectorIter<'a> {
265 pub fn signing(&mut self, sign_key: Keypair) {
267 self.txs.signing(sign_key)
268 }
269
270 pub fn into_owned(self) -> ConnectorIter<'static> {
272 ConnectorIter {
273 txs: self.txs.into_owned(),
274 maybe_last: self.maybe_last,
275 }
276 }
277}
278
279impl<'a> iter::Iterator for ConnectorIter<'a> {
280 type Item = (OutPoint, Option<Transaction>);
281
282 fn next(&mut self) -> Option<Self::Item> {
283 if self.maybe_last.is_none() {
284 return None;
285 }
286
287 if let Some(tx) = self.txs.next() {
288 let txid = tx.compute_txid();
289 self.maybe_last = Some((OutPoint::new(txid, CONNECTOR_TX_CHAIN_VOUT), Some(tx.clone())));
290 Some((OutPoint::new(txid, CONNECTOR_TX_CONNECTOR_VOUT), Some(tx)))
291 } else {
292 Some(self.maybe_last.take().expect("broken"))
293 }
294 }
295
296 fn size_hint(&self) -> (usize, Option<usize>) {
297 let len = self.txs.size_hint().0 + 1;
298 (len, Some(len))
299 }
300}
301
302impl<'a> iter::ExactSizeIterator for ConnectorTxIter<'a> {}
303impl<'a> iter::FusedIterator for ConnectorTxIter<'a> {}
304
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
309#[error("signing key doesn't match connector chain")]
310pub struct InvalidSigningKeyError;
311
312
313#[cfg(test)]
314mod test {
315 use super::*;
316 use bitcoin::Txid;
317 use bitcoin::hashes::Hash;
318 use bitcoin_ext::TransactionExt;
319
320 #[test]
321 fn test_budget() {
322 let key = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
323 let utxo = OutPoint::new(Txid::all_zeros(), 3);
324
325 let chain = ConnectorChain::new(1, utxo, key.public_key());
326 assert_eq!(chain.connectors().count(), 1);
327 assert_eq!(chain.iter_unsigned_txs().count(), 0);
328 assert_eq!(chain.connectors().next().unwrap().0, utxo);
329
330 let chain = ConnectorChain::new(2, utxo, key.public_key());
331 assert_eq!(chain.connectors().count(), 2);
332 assert_eq!(chain.iter_unsigned_txs().count(), 1);
333 assert_eq!(chain.iter_signed_txs(&key).unwrap().count(), 1);
334 let tx = chain.iter_signed_txs(&key).unwrap().next().unwrap();
335 assert_eq!(TX_WEIGHT, tx.weight());
336 assert_eq!(tx.output_value(), ConnectorChain::required_budget(2));
337
338 let chain = ConnectorChain::new(3, utxo, key.public_key());
339 assert_eq!(chain.connectors().count(), 3);
340 assert_eq!(chain.iter_unsigned_txs().count(), 2);
341 let mut txs = chain.iter_signed_txs(&key).unwrap();
342 let tx = txs.next().unwrap();
343 assert_eq!(TX_WEIGHT, tx.weight());
344 assert_eq!(tx.output_value(), ConnectorChain::required_budget(3));
345 let tx = txs.next().unwrap();
346 assert_eq!(TX_WEIGHT, tx.weight());
347 assert_eq!(tx.output_value(), ConnectorChain::required_budget(2));
348 assert!(txs.next().is_none());
349
350 let chain = ConnectorChain::new(100, utxo, key.public_key());
351 assert_eq!(chain.connectors().count(), 100);
352 assert_eq!(chain.iter_unsigned_txs().count(), 99);
353 assert_eq!(chain.iter_signed_txs(&key).unwrap().count(), 99);
354 let tx = chain.iter_signed_txs(&key).unwrap().next().unwrap();
355 assert_eq!(TX_WEIGHT, tx.weight());
356 assert_eq!(tx.output_value(), ConnectorChain::required_budget(100));
357 for tx in chain.iter_signed_txs(&key).unwrap() {
358 assert_eq!(tx.weight(), TX_WEIGHT);
359 assert_eq!(tx.input[0].witness.size(), INPUT_WEIGHT.to_wu() as usize);
360 }
361 let weight = chain.iter_signed_txs(&key).unwrap().map(|t| t.weight()).sum::<Weight>();
362 assert_eq!(weight, ConnectorChain::total_weight(100));
363 chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, P2TR_DUST));
364 assert_eq!(P2TR_DUST, chain.iter_unsigned_txs().last().unwrap().output[0].value);
365 }
366
367 #[test]
368 fn test_signatures() {
369 let key = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
370 let utxo = OutPoint::new(Txid::all_zeros(), 3);
371 let spk = ConnectorChain::output_script(key.public_key());
372
373 let chain = ConnectorChain::new(10, utxo, key.public_key());
374 for (i, tx) in chain.iter_signed_txs(&key).unwrap().enumerate() {
375 let amount = ConnectorChain::required_budget(chain.len - i).to_sat();
376 let inputs = vec![bitcoinconsensus::Utxo {
377 script_pubkey: spk.as_bytes().as_ptr(),
378 script_pubkey_len: spk.len() as u32,
379 value: amount as i64,
380 }];
381 bitcoinconsensus::verify(
382 spk.as_bytes(),
383 amount,
384 &bitcoin::consensus::serialize(&tx),
385 Some(&inputs),
386 0,
387 ).expect("verification failed");
388 }
389 }
390}