1pub use crate::challenges::RoundAttemptChallenge;
2
3use std::fmt;
4use std::str::FromStr;
5
6use bitcoin::{Transaction, Txid};
7use bitcoin::hashes::Hash as _;
8use bitcoin::hex::DisplayHex;
9use bitcoin::secp256k1::schnorr;
10
11use crate::musig;
12use crate::tree::signed::VtxoTreeSpec;
13
14pub const MIN_ROUND_TX_OUTPUTS: usize = 2;
17
18pub const ROUND_TX_VTXO_TREE_VOUT: u32 = 0;
20
21#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
28pub struct RoundSeq(u64);
29
30impl RoundSeq {
31 pub const fn new(seq: u64) -> Self {
32 Self(seq)
33 }
34
35 pub fn increment(&mut self) {
36 self.0 += 1;
37 }
38
39 pub fn inner(&self) -> u64 {
40 self.0
41 }
42}
43
44impl From<u64> for RoundSeq {
45 fn from(v: u64) -> Self {
46 Self::new(v)
47 }
48}
49
50impl From<RoundSeq> for u64 {
51 fn from(v: RoundSeq) -> u64 {
52 v.0
53 }
54}
55
56impl fmt::Display for RoundSeq {
57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
58}
59
60impl fmt::Debug for RoundSeq {
61 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub struct RoundId(Txid);
69
70impl RoundId {
71 pub const fn new(txid: Txid) -> RoundId {
73 RoundId(txid)
74 }
75
76 pub fn from_slice(bytes: &[u8]) -> Result<RoundId, bitcoin::hashes::FromSliceError> {
77 Txid::from_slice(bytes).map(RoundId::new)
78 }
79
80 pub fn as_round_txid(&self) -> Txid {
81 self.0
82 }
83}
84
85impl From<Txid> for RoundId {
86 fn from(txid: Txid) -> RoundId {
87 RoundId::new(txid)
88 }
89}
90
91impl std::ops::Deref for RoundId {
92 type Target = Txid;
93 fn deref(&self) -> &Self::Target {
94 &self.0
95 }
96}
97
98impl fmt::Display for RoundId {
99 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100 write!(f, "{}", self.0)
101 }
102}
103
104impl FromStr for RoundId {
105 type Err = bitcoin::hashes::hex::HexToArrayError;
106 fn from_str(s: &str) -> Result<Self, Self::Err> {
107 Txid::from_str(s).map(RoundId::new)
108 }
109}
110
111impl serde::Serialize for RoundId {
112 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
113 if s.is_human_readable() {
114 s.collect_str(self)
115 } else {
116 s.serialize_bytes(self.as_ref())
117 }
118 }
119}
120
121impl<'de> serde::Deserialize<'de> for RoundId {
122 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
123 struct Visitor;
124 impl<'de> serde::de::Visitor<'de> for Visitor {
125 type Value = RoundId;
126 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 write!(f, "a RoundId, which is a Txid")
128 }
129 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
130 RoundId::from_slice(v).map_err(serde::de::Error::custom)
131 }
132 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
133 RoundId::from_str(v).map_err(serde::de::Error::custom)
134 }
135 }
136 if d.is_human_readable() {
137 d.deserialize_str(Visitor)
138 } else {
139 d.deserialize_bytes(Visitor)
140 }
141 }
142}
143
144#[derive(Debug, Clone)]
145pub struct RoundAttempt {
146 pub round_seq: RoundSeq,
147 pub attempt_seq: usize,
148 pub challenge: RoundAttemptChallenge,
149}
150
151#[derive(Debug, Clone)]
152pub struct VtxoProposal {
153 pub round_seq: RoundSeq,
154 pub attempt_seq: usize,
155 pub unsigned_round_tx: Transaction,
156 pub vtxos_spec: VtxoTreeSpec,
157 pub cosign_agg_nonces: Vec<musig::AggregatedNonce>,
158}
159
160#[derive(Debug, Clone)]
161pub struct RoundFinished {
162 pub round_seq: RoundSeq,
163 pub attempt_seq: usize,
164 pub cosign_sigs: Vec<schnorr::Signature>,
165 pub signed_round_tx: Transaction,
166}
167
168#[derive(Debug, Clone)]
169pub enum RoundEvent {
170 Attempt(RoundAttempt),
171 VtxoProposal(VtxoProposal),
172 Finished(RoundFinished),
173}
174
175impl RoundEvent {
176 pub fn kind(&self) -> &'static str {
178 match self {
179 Self::Attempt(_) => "RoundAttempt",
180 Self::VtxoProposal { .. } => "VtxoProposal",
181 Self::Finished { .. } => "Finished",
182 }
183 }
184
185 pub fn round_seq(&self) -> RoundSeq {
186 match self {
187 Self::Attempt(e) => e.round_seq,
188 Self::VtxoProposal(e) => e.round_seq,
189 Self::Finished(e) => e.round_seq,
190 }
191 }
192
193 pub fn attempt_seq(&self) -> usize {
194 match self {
195 Self::Attempt(e) => e.attempt_seq,
196 Self::VtxoProposal(e) => e.attempt_seq,
197 Self::Finished(e) => e.attempt_seq,
198 }
199 }
200}
201
202impl fmt::Display for RoundEvent {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 match self {
206 Self::Attempt(RoundAttempt { round_seq, attempt_seq, challenge }) => {
207 f.debug_struct("RoundAttempt")
208 .field("round_seq", round_seq)
209 .field("attempt_seq", attempt_seq)
210 .field("challenge", &challenge.inner().as_hex())
211 .finish()
212 },
213 Self::VtxoProposal(VtxoProposal { round_seq, attempt_seq, unsigned_round_tx, .. }) => {
214 f.debug_struct("VtxoProposal")
215 .field("round_seq", round_seq)
216 .field("attempt_seq", attempt_seq)
217 .field("unsigned_round_txid", &unsigned_round_tx.compute_txid())
218 .finish()
219 },
220 Self::Finished(RoundFinished {
221 round_seq, attempt_seq, signed_round_tx, ..
222 }) => {
223 f.debug_struct("Finished")
224 .field("round_seq", round_seq)
225 .field("attempt_seq", attempt_seq)
226 .field("signed_round_txid", &signed_round_tx.compute_txid())
227 .finish()
228 },
229 }
230 }
231}