1
2use std::fmt;
3use std::collections::HashMap;
4use std::io::Write;
5use std::str::FromStr;
6
7use bitcoin::hashes::{sha256, Hash};
8use bitcoin::hex::DisplayHex;
9use bitcoin::secp256k1::PublicKey;
10use bitcoin::{key::Keypair, FeeRate, Transaction, Txid};
11use bitcoin::secp256k1::{self, schnorr, Message};
12
13use crate::{musig, OffboardRequest, SECP, SignedVtxoRequest, Vtxo, VtxoId};
14use crate::encode::ProtocolEncoding;
15use crate::tree::signed::VtxoTreeSpec;
16
17pub const MIN_ROUND_TX_OUTPUTS: usize = 2;
20
21pub const ROUND_TX_VTXO_TREE_VOUT: u32 = 0;
23pub const ROUND_TX_CONNECTOR_VOUT: u32 = 1;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct VtxoOwnershipChallenge([u8; 32]);
28
29impl VtxoOwnershipChallenge {
30 const CHALENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Ark round input ownership proof ";
31
32 pub fn new(value: [u8; 32]) -> Self {
33 Self(value)
34 }
35
36 pub fn generate() -> Self {
37 Self(rand::random())
38 }
39
40 pub fn inner(&self) -> [u8; 32] {
41 self.0
42 }
43
44 fn as_signable_message(&self, vtxo_id: VtxoId, vtxo_reqs: &[SignedVtxoRequest], offboard_reqs: &[OffboardRequest]) -> Message {
48 let mut engine = sha256::Hash::engine();
49 engine.write_all(Self::CHALENGE_MESSAGE_PREFIX).unwrap();
50 engine.write_all(&self.0).unwrap();
51 engine.write_all(&vtxo_id.to_bytes()).unwrap();
52
53 engine.write_all(&vtxo_reqs.len().to_be_bytes()).unwrap();
54 for req in vtxo_reqs {
55 engine.write_all(&req.vtxo.amount.to_sat().to_be_bytes()).unwrap();
56 req.vtxo.policy.encode(&mut engine).unwrap();
57 req.cosign_pubkey.encode(&mut engine).unwrap();
58 }
59
60 engine.write_all(&offboard_reqs.len().to_be_bytes()).unwrap();
61 for req in offboard_reqs {
62 req.to_txout().encode(&mut engine).unwrap();
63 }
64 let hash = sha256::Hash::from_engine(engine).to_byte_array();
65 Message::from_digest(hash)
66 }
67
68 pub fn sign_with(&self, vtxo_id: VtxoId, vtxo_reqs: &[SignedVtxoRequest], offboard_reqs: &[OffboardRequest], vtxo_keypair: Keypair) -> schnorr::Signature {
69 SECP.sign_schnorr(&self.as_signable_message(vtxo_id, vtxo_reqs, offboard_reqs), &vtxo_keypair)
70 }
71
72 pub fn verify_input_vtxo_sig(
73 &self,
74 vtxo: &Vtxo,
75 vtxo_reqs: &[SignedVtxoRequest],
76 offboard_reqs: &[OffboardRequest],
77 sig: &schnorr::Signature,
78 ) -> Result<(), secp256k1::Error> {
79 SECP.verify_schnorr(
80 sig,
81 &self.as_signable_message(vtxo.id(), vtxo_reqs, offboard_reqs),
82 &vtxo.user_pubkey().x_only_public_key().0,
83 )
84 }
85}
86
87#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
94pub struct RoundSeq(u64);
95
96impl RoundSeq {
97 pub const fn new(seq: u64) -> Self {
98 Self(seq)
99 }
100
101 pub fn increment(&mut self) {
102 self.0 += 1;
103 }
104
105 pub fn inner(&self) -> u64 {
106 self.0
107 }
108}
109
110impl From<u64> for RoundSeq {
111 fn from(v: u64) -> Self {
112 Self::new(v)
113 }
114}
115
116impl From<RoundSeq> for u64 {
117 fn from(v: RoundSeq) -> u64 {
118 v.0
119 }
120}
121
122impl fmt::Display for RoundSeq {
123 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
124}
125
126impl fmt::Debug for RoundSeq {
127 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
134pub struct RoundId(Txid);
135
136impl RoundId {
137 pub const fn new(txid: Txid) -> RoundId {
139 RoundId(txid)
140 }
141
142 pub fn from_slice(bytes: &[u8]) -> Result<RoundId, bitcoin::hashes::FromSliceError> {
143 Txid::from_slice(bytes).map(RoundId::new)
144 }
145
146 pub fn as_round_txid(&self) -> Txid {
147 self.0
148 }
149}
150
151impl From<Txid> for RoundId {
152 fn from(txid: Txid) -> RoundId {
153 RoundId::new(txid)
154 }
155}
156
157impl std::ops::Deref for RoundId {
158 type Target = Txid;
159 fn deref(&self) -> &Self::Target {
160 &self.0
161 }
162}
163
164impl fmt::Display for RoundId {
165 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
166 write!(f, "{}", self.0)
167 }
168}
169
170impl FromStr for RoundId {
171 type Err = bitcoin::hashes::hex::HexToArrayError;
172 fn from_str(s: &str) -> Result<Self, Self::Err> {
173 Txid::from_str(s).map(RoundId::new)
174 }
175}
176
177impl serde::Serialize for RoundId {
178 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
179 if s.is_human_readable() {
180 s.collect_str(self)
181 } else {
182 s.serialize_bytes(self.as_ref())
183 }
184 }
185}
186
187impl<'de> serde::Deserialize<'de> for RoundId {
188 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
189 struct Visitor;
190 impl<'de> serde::de::Visitor<'de> for Visitor {
191 type Value = RoundId;
192 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 write!(f, "a RoundId, which is a Txid")
194 }
195 fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
196 RoundId::from_slice(v).map_err(serde::de::Error::custom)
197 }
198 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
199 RoundId::from_str(v).map_err(serde::de::Error::custom)
200 }
201 }
202 if d.is_human_readable() {
203 d.deserialize_str(Visitor)
204 } else {
205 d.deserialize_bytes(Visitor)
206 }
207 }
208}
209
210
211#[derive(Debug, Clone)]
212pub struct RoundInfo {
213 pub round_seq: RoundSeq,
214 pub offboard_feerate: FeeRate,
215}
216
217#[derive(Debug, Clone)]
218pub struct RoundAttempt {
219 pub round_seq: RoundSeq,
220 pub attempt_seq: usize,
221 pub challenge: VtxoOwnershipChallenge,
222}
223
224#[derive(Debug, Clone)]
225pub struct VtxoProposal {
226 pub round_seq: RoundSeq,
227 pub attempt_seq: usize,
228 pub unsigned_round_tx: Transaction,
229 pub vtxos_spec: VtxoTreeSpec,
230 pub cosign_agg_nonces: Vec<musig::AggregatedNonce>,
231}
232
233#[derive(Debug, Clone)]
234pub struct RoundProposal {
235 pub round_seq: RoundSeq,
236 pub attempt_seq: usize,
237 pub cosign_sigs: Vec<schnorr::Signature>,
238 pub forfeit_nonces: HashMap<VtxoId, Vec<musig::PublicNonce>>,
239 pub connector_pubkey: PublicKey,
240}
241
242#[derive(Debug, Clone)]
243pub struct RoundFinished {
244 pub round_seq: RoundSeq,
245 pub attempt_seq: usize,
246 pub signed_round_tx: Transaction,
247}
248
249#[derive(Debug, Clone)]
250pub enum RoundEvent {
251 Start(RoundInfo),
252 Attempt(RoundAttempt),
253 VtxoProposal(VtxoProposal),
254 RoundProposal(RoundProposal),
255 Finished(RoundFinished),
256}
257
258impl RoundEvent {
259 pub fn round_seq(&self) -> RoundSeq {
260 match self {
261 Self::Start(e) => e.round_seq,
262 Self::Attempt(e) => e.round_seq,
263 Self::VtxoProposal(e) => e.round_seq,
264 Self::RoundProposal(e) => e.round_seq,
265 Self::Finished(e) => e.round_seq,
266 }
267 }
268}
269
270impl fmt::Display for RoundEvent {
272 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
273 match self {
274 Self::Start(RoundInfo { round_seq, offboard_feerate }) => {
275 f.debug_struct("Start")
276 .field("round_seq", round_seq)
277 .field("offboard_feerate", offboard_feerate)
278 .finish()
279 },
280 Self::Attempt(RoundAttempt { round_seq, attempt_seq, challenge }) => {
281 f.debug_struct("Attempt")
282 .field("round_seq", round_seq)
283 .field("attempt_seq", attempt_seq)
284 .field("challenge", &challenge.inner().as_hex())
285 .finish()
286 },
287 Self::VtxoProposal(VtxoProposal { round_seq, attempt_seq, unsigned_round_tx, .. }) => {
288 f.debug_struct("VtxoProposal")
289 .field("round_seq", round_seq)
290 .field("attempt_seq", attempt_seq)
291 .field("unsigned_round_txid", &unsigned_round_tx.compute_txid())
292 .finish()
293 },
294 Self::RoundProposal(RoundProposal { round_seq, attempt_seq, connector_pubkey, .. }) => {
295 f.debug_struct("RoundProposal")
296 .field("round_seq", round_seq)
297 .field("attempt_seq", attempt_seq)
298 .field("connector_pubkey", &connector_pubkey)
299 .finish()
300 },
301 Self::Finished(RoundFinished { round_seq, attempt_seq, signed_round_tx }) => {
302 f.debug_struct("Finished")
303 .field("round_seq", round_seq)
304 .field("attempt_seq", attempt_seq)
305 .field("signed_round_txid", &signed_round_tx.compute_txid())
306 .finish()
307 },
308 }
309 }
310}