ark/
rounds.rs

1pub use crate::challenges::RoundAttemptChallenge;
2
3use std::fmt;
4use std::collections::HashMap;
5use std::str::FromStr;
6
7use bitcoin::{Transaction, Txid};
8use bitcoin::hashes::Hash as _;
9use bitcoin::hex::DisplayHex;
10use bitcoin::secp256k1::{schnorr, PublicKey};
11
12use crate::{musig, VtxoId};
13use crate::tree::signed::VtxoTreeSpec;
14
15/// A round tx must have at least vtxo tree and connector chain outputs.
16/// Can also have several offboard outputs on next indices.
17pub const MIN_ROUND_TX_OUTPUTS: usize = 2;
18
19/// The output index of the vtxo tree root in the round tx.
20pub const ROUND_TX_VTXO_TREE_VOUT: u32 = 0;
21/// The output index of the connector chain  start in the round tx.
22pub const ROUND_TX_CONNECTOR_VOUT: u32 = 1;
23
24/// Unique identifier for a round.
25///
26/// It is a simple sequence number.
27///
28/// It is used to identify a round before we have a round tx.
29/// [RoundId] should be used as soon as we have a round tx.
30#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
31pub struct RoundSeq(u64);
32
33impl RoundSeq {
34	pub const fn new(seq: u64) -> Self {
35		Self(seq)
36	}
37
38	pub fn increment(&mut self) {
39		self.0 += 1;
40	}
41
42	pub fn inner(&self) -> u64 {
43		self.0
44	}
45}
46
47impl From<u64> for RoundSeq {
48	fn from(v: u64) -> Self {
49	    Self::new(v)
50	}
51}
52
53impl From<RoundSeq> for u64 {
54	fn from(v: RoundSeq) -> u64 {
55	    v.0
56	}
57}
58
59impl fmt::Display for RoundSeq {
60	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
61}
62
63impl fmt::Debug for RoundSeq {
64	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
65}
66
67/// Identifier for a past round.
68///
69/// It is the txid of the round tx.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub struct RoundId(Txid);
72
73impl RoundId {
74	/// Create a new [RoundId] from the round tx's [Txid].
75	pub const fn new(txid: Txid) -> RoundId {
76		RoundId(txid)
77	}
78
79	pub fn from_slice(bytes: &[u8]) -> Result<RoundId, bitcoin::hashes::FromSliceError> {
80		Txid::from_slice(bytes).map(RoundId::new)
81	}
82
83	pub fn as_round_txid(&self) -> Txid {
84		self.0
85	}
86}
87
88impl From<Txid> for RoundId {
89	fn from(txid: Txid) -> RoundId {
90		RoundId::new(txid)
91	}
92}
93
94impl std::ops::Deref for RoundId {
95	type Target = Txid;
96	fn deref(&self) -> &Self::Target {
97		&self.0
98	}
99}
100
101impl fmt::Display for RoundId {
102	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103		write!(f, "{}", self.0)
104	}
105}
106
107impl FromStr for RoundId {
108	type Err = bitcoin::hashes::hex::HexToArrayError;
109	fn from_str(s: &str) -> Result<Self, Self::Err> {
110		Txid::from_str(s).map(RoundId::new)
111	}
112}
113
114impl serde::Serialize for RoundId {
115	fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
116		if s.is_human_readable() {
117			s.collect_str(self)
118		} else {
119			s.serialize_bytes(self.as_ref())
120		}
121	}
122}
123
124impl<'de> serde::Deserialize<'de> for RoundId {
125	fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
126		struct Visitor;
127		impl<'de> serde::de::Visitor<'de> for Visitor {
128			type Value = RoundId;
129			fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130				write!(f, "a RoundId, which is a Txid")
131			}
132			fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
133				RoundId::from_slice(v).map_err(serde::de::Error::custom)
134			}
135			fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
136				RoundId::from_str(v).map_err(serde::de::Error::custom)
137			}
138		}
139		if d.is_human_readable() {
140			d.deserialize_str(Visitor)
141		} else {
142			d.deserialize_bytes(Visitor)
143		}
144	}
145}
146
147#[derive(Debug, Clone)]
148pub struct RoundAttempt {
149	pub round_seq: RoundSeq,
150	pub attempt_seq: usize,
151	pub challenge: RoundAttemptChallenge,
152}
153
154#[derive(Debug, Clone)]
155pub struct VtxoProposal {
156	pub round_seq: RoundSeq,
157	pub attempt_seq: usize,
158	pub unsigned_round_tx: Transaction,
159	pub vtxos_spec: VtxoTreeSpec,
160	pub cosign_agg_nonces: Vec<musig::AggregatedNonce>,
161}
162
163#[derive(Debug, Clone)]
164pub struct RoundProposal {
165	pub round_seq: RoundSeq,
166	pub attempt_seq: usize,
167	pub cosign_sigs: Vec<schnorr::Signature>,
168	pub forfeit_nonces: HashMap<VtxoId, Vec<musig::PublicNonce>>,
169	pub connector_pubkey: PublicKey,
170}
171
172#[derive(Debug, Clone)]
173pub struct RoundFinished {
174	pub round_seq: RoundSeq,
175	pub attempt_seq: usize,
176	pub signed_round_tx: Transaction,
177}
178
179#[derive(Debug, Clone)]
180pub enum RoundEvent {
181	Attempt(RoundAttempt),
182	VtxoProposal(VtxoProposal),
183	RoundProposal(RoundProposal),
184	Finished(RoundFinished),
185}
186
187impl RoundEvent {
188	/// String representation of the kind of event
189	pub fn kind(&self) -> &'static str {
190		match self {
191			Self::Attempt(_) => "RoundAttempt",
192			Self::VtxoProposal { .. } => "VtxoProposal",
193			Self::RoundProposal { .. } => "RoundProposal",
194			Self::Finished { .. } => "Finished",
195		}
196	}
197
198	pub fn round_seq(&self) -> RoundSeq {
199		match self {
200			Self::Attempt(e) => e.round_seq,
201			Self::VtxoProposal(e) => e.round_seq,
202			Self::RoundProposal(e) => e.round_seq,
203			Self::Finished(e) => e.round_seq,
204		}
205	}
206
207	pub fn attempt_seq(&self) -> usize {
208		match self {
209			Self::Attempt(e) => e.attempt_seq,
210			Self::VtxoProposal(e) => e.attempt_seq,
211			Self::RoundProposal(e) => e.attempt_seq,
212			Self::Finished(e) => e.attempt_seq,
213		}
214	}
215}
216
217/// A more concise way to display [RoundEvent].
218impl fmt::Display for RoundEvent {
219	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
220		match self {
221			Self::Attempt(RoundAttempt { round_seq, attempt_seq, challenge }) => {
222				f.debug_struct("RoundAttempt")
223					.field("round_seq", round_seq)
224					.field("attempt_seq", attempt_seq)
225					.field("challenge", &challenge.inner().as_hex())
226					.finish()
227			},
228			Self::VtxoProposal(VtxoProposal { round_seq, attempt_seq, unsigned_round_tx, .. }) => {
229				f.debug_struct("VtxoProposal")
230					.field("round_seq", round_seq)
231					.field("attempt_seq", attempt_seq)
232					.field("unsigned_round_txid", &unsigned_round_tx.compute_txid())
233					.finish()
234			},
235			Self::RoundProposal(RoundProposal { round_seq, attempt_seq, connector_pubkey, .. }) => {
236				f.debug_struct("RoundProposal")
237					.field("round_seq", round_seq)
238					.field("attempt_seq", attempt_seq)
239					.field("connector_pubkey", &connector_pubkey)
240					.finish()
241			},
242			Self::Finished(RoundFinished { round_seq, attempt_seq, signed_round_tx }) => {
243				f.debug_struct("Finished")
244					.field("round_seq", round_seq)
245					.field("attempt_seq", attempt_seq)
246					.field("signed_round_txid", &signed_round_tx.compute_txid())
247					.finish()
248			},
249		}
250	}
251}