ark/
connectors.rs

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
16/// The output index of the connector chain continuation in the connector tx.
17///
18/// In the last item of the chain, it is a connector output along with
19/// output at index 1.
20pub const CONNECTOR_TX_CHAIN_VOUT: u32 = 0;
21/// The output index of the connector output in the connector tx.
22pub const CONNECTOR_TX_CONNECTOR_VOUT: u32 = 1;
23
24/// The weight of each connector tx.
25const TX_WEIGHT: Weight = Weight::from_vb_unchecked(167);
26
27/// The witness weight of a connector input.
28pub const INPUT_WEIGHT: Weight = Weight::from_wu(66);
29
30
31/// A chain of connector outputs.
32///
33/// Each connector is a p2tr keyspend output for the provided key.
34/// Each connector has the p2tr dust value.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ConnectorChain {
37	/// The total number of connectors in the connector chain.
38	len: usize,
39
40	/// The scriptPubkey used by all connector outputs.
41	spk: ScriptBuf,
42
43	/// The prevout from where the chain starts.
44	///
45	/// This should be an output of the round transaction.
46	utxo: OutPoint,
47}
48
49impl ConnectorChain {
50	/// The total size in vbytes of the connector tree.
51	pub fn total_weight(len: usize) -> Weight {
52		assert_ne!(len, 0);
53		(len - 1) as u64 * TX_WEIGHT
54	}
55
56	/// The budget needed for a chain of length `len` to pay for
57	/// one dust for the connector output per tx
58	pub fn required_budget(len: usize) -> Amount {
59		assert_ne!(len, 0);
60
61		// Each tx of the chain will hold one output to continue the
62		// chain + one output for the connector + one fee anchor output
63		// So the required budget is 1 dust per connector
64		P2TR_DUST * len as u64
65	}
66
67	/// Create the scriptPubkey to create a connector chain using the given publick key.
68	pub fn output_script(pubkey: PublicKey) -> ScriptBuf {
69		ScriptBuf::new_p2tr(&SECP, pubkey.x_only_public_key().0, None)
70	}
71
72	/// Create the address to create a connector chain using the given publick key.
73	pub fn address(network: Network, pubkey: PublicKey) -> Address {
74		Address::from_script(&Self::output_script(pubkey), network).unwrap()
75	}
76
77	/// Create a connector output.
78	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	/// Create a new connector tree.
86	///
87	/// Before calling this method, a utxo should be created with a scriptPubkey
88	/// as specified by [ConnectorChain::output_script] or [ConnectorChain::address].
89	/// The amount in this output is expected to be exaclty equal to
90	/// [ConnectorChain::required_budget] for the given length.
91	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				// this is the continuation of the chain
114				// (or a connector output if the last tx)
115				TxOut {
116					script_pubkey: self.spk.to_owned(),
117					value: ConnectorChain::required_budget(self.len - idx - 1),
118				},
119				// this is the connector output
120				// (or the second one if the last tx)
121				TxOut {
122					script_pubkey: self.spk.to_owned(),
123					value: P2TR_DUST,
124				},
125				// this is the fee anchor output
126				fee::fee_anchor(),
127			],
128		}
129	}
130
131	/// NB we expect the output key here, not the internal key
132	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	/// Iterator over the signed transactions in this chain.
146	///
147	/// We expect the internal key here, not the output key.
148	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	/// Iterator over the transactions in this chain.
165	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	/// Iterator over the connector outpoints and unsigned txs in this chain.
175	pub fn connectors(&self) -> ConnectorIter<'_> {
176		ConnectorIter {
177			txs: self.iter_unsigned_txs(),
178			maybe_last: Some((self.utxo, None)),
179		}
180	}
181
182	/// Iterator over the connector outpoints and signed txs in this chain.
183	///
184	/// We expect the internal key here, not the output key.
185	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
196/// An iterator over transactions in a [ConnectorChain].
197///
198/// See [ConnectorChain::iter_unsigned_txs] and
199/// [ConnectorChain::iter_signed_txs] for more info.
200pub 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	/// Upgrade this iterator to a signing iterator.
210	pub fn signing(&mut self, sign_key: Keypair) {
211		self.sign_key = Some(sign_key);
212	}
213
214	/// Convert into owned iterator.
215	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
249/// An iterator over the connectors in a [ConnectorChain].
250///
251/// See [ConnectorChain::connectors] and [ConnectorChain::connectors_signed]
252/// for more info.
253pub struct ConnectorIter<'a> {
254	txs: ConnectorTxIter<'a>,
255	// On all intermediate txs, only the second output is a connector and
256	// the first output continues the chain. On the very last tx, both
257	// outputs are connectors. We will keep this variable updated to contain
258	// the first output of the last tx we say, so that we can return it once
259	// the tx iterator does no longer yield new txs (hence we reached the
260	// last tx).
261	maybe_last: Option<<Self as Iterator>::Item>,
262}
263
264impl<'a> ConnectorIter<'a> {
265	/// Upgrade this iterator to a signing iterator.
266	pub fn signing(&mut self, sign_key: Keypair) {
267		self.txs.signing(sign_key)
268	}
269
270	/// Convert into owned iterator.
271	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/// The signing key passed into [ConnectorChain::iter_signed_txs] or
307/// [ConnectorChain::connectors_signed] is incorrect.
308#[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 bitcoin::Txid;
316	use bitcoin::hashes::Hash;
317	use bitcoin_ext::TransactionExt;
318	use crate::test::verify_tx;
319	use super::*;
320
321	#[test]
322	fn test_budget() {
323		let key = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
324		let utxo = OutPoint::new(Txid::all_zeros(), 3);
325
326		let chain = ConnectorChain::new(1, utxo, key.public_key());
327		assert_eq!(chain.connectors().count(), 1);
328		assert_eq!(chain.iter_unsigned_txs().count(), 0);
329		assert_eq!(chain.connectors().next().unwrap().0, utxo);
330
331		let chain = ConnectorChain::new(2, utxo, key.public_key());
332		assert_eq!(chain.connectors().count(), 2);
333		assert_eq!(chain.iter_unsigned_txs().count(), 1);
334		assert_eq!(chain.iter_signed_txs(&key).unwrap().count(), 1);
335		let tx = chain.iter_signed_txs(&key).unwrap().next().unwrap();
336		assert_eq!(TX_WEIGHT, tx.weight());
337		assert_eq!(tx.output_value(), ConnectorChain::required_budget(2));
338
339		let chain = ConnectorChain::new(3, utxo, key.public_key());
340		assert_eq!(chain.connectors().count(), 3);
341		assert_eq!(chain.iter_unsigned_txs().count(), 2);
342		let mut txs = chain.iter_signed_txs(&key).unwrap();
343		let tx = txs.next().unwrap();
344		assert_eq!(TX_WEIGHT, tx.weight());
345		assert_eq!(tx.output_value(), ConnectorChain::required_budget(3));
346		let tx = txs.next().unwrap();
347		assert_eq!(TX_WEIGHT, tx.weight());
348		assert_eq!(tx.output_value(), ConnectorChain::required_budget(2));
349		assert!(txs.next().is_none());
350
351		let chain = ConnectorChain::new(100, utxo, key.public_key());
352		assert_eq!(chain.connectors().count(), 100);
353		assert_eq!(chain.iter_unsigned_txs().count(), 99);
354		assert_eq!(chain.iter_signed_txs(&key).unwrap().count(), 99);
355		let tx = chain.iter_signed_txs(&key).unwrap().next().unwrap();
356		assert_eq!(TX_WEIGHT, tx.weight());
357		assert_eq!(tx.output_value(), ConnectorChain::required_budget(100));
358		for tx in chain.iter_signed_txs(&key).unwrap() {
359			assert_eq!(tx.weight(), TX_WEIGHT);
360			assert_eq!(tx.input[0].witness.size(), INPUT_WEIGHT.to_wu() as usize);
361		}
362		let weight = chain.iter_signed_txs(&key).unwrap().map(|t| t.weight()).sum::<Weight>();
363		assert_eq!(weight, ConnectorChain::total_weight(100));
364		chain.iter_unsigned_txs().for_each(|t| assert_eq!(t.output[1].value, P2TR_DUST));
365		assert_eq!(P2TR_DUST, chain.iter_unsigned_txs().last().unwrap().output[0].value);
366	}
367
368	#[test]
369	fn test_signatures() {
370		let key = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
371		let utxo = OutPoint::new(Txid::all_zeros(), 3);
372		let spk = ConnectorChain::output_script(key.public_key());
373
374		let chain = ConnectorChain::new(10, utxo, key.public_key());
375		for (i, tx) in chain.iter_signed_txs(&key).unwrap().enumerate() {
376			let amount = ConnectorChain::required_budget(chain.len - i);
377			let input = TxOut {
378				script_pubkey: spk.clone(),
379				value: amount,
380			};
381			verify_tx(&[input], 0, &tx).expect(&format!("invalid connector tx idx {}", i));
382		}
383	}
384}