1use std::io::Write as _;
2
3use bitcoin::consensus::WriteExt;
4use bitcoin::hashes::{sha256, Hash, HashEngine};
5use bitcoin::key::Keypair;
6use bitcoin::secp256k1::{self, schnorr, Message};
7
8use crate::{SignedVtxoRequest, Vtxo, VtxoId, VtxoRequest, SECP};
9use crate::encode::ProtocolEncoding;
10use crate::lightning::PaymentHash;
11use crate::offboard::OffboardRequest;
12
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct RoundAttemptChallenge([u8; 32]);
16
17impl RoundAttemptChallenge {
18 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Ark round input ownership proof ";
19
20 pub fn new(value: [u8; 32]) -> Self {
21 Self(value)
22 }
23
24 pub fn generate() -> Self {
25 Self(rand::random())
26 }
27
28 pub fn inner(&self) -> [u8; 32] {
29 self.0
30 }
31
32 fn as_signable_message(
34 &self,
35 vtxo_id: VtxoId,
36 vtxo_reqs: &[SignedVtxoRequest],
37 ) -> Message {
38 let mut engine = sha256::Hash::engine();
39 engine.write_all(Self::CHALLENGE_MESSAGE_PREFIX).unwrap();
40 engine.write_all(&self.0).unwrap();
41 engine.write_all(&vtxo_id.to_bytes()).unwrap();
42
43 engine.write_all(&vtxo_reqs.len().to_be_bytes()).unwrap();
44 for req in vtxo_reqs {
45 engine.write_all(&req.vtxo.amount.to_sat().to_be_bytes()).unwrap();
46 req.vtxo.policy.encode(&mut engine).unwrap();
47 req.cosign_pubkey.encode(&mut engine).unwrap();
48 }
49
50 let hash = sha256::Hash::from_engine(engine).to_byte_array();
51 Message::from_digest(hash)
52 }
53
54 pub fn sign_with(
55 &self,
56 vtxo_id: VtxoId,
57 vtxo_reqs: &[SignedVtxoRequest],
58 vtxo_keypair: &Keypair,
59 ) -> schnorr::Signature {
60 let msg = self.as_signable_message(vtxo_id, vtxo_reqs);
61 SECP.sign_schnorr_with_aux_rand(&msg, &vtxo_keypair, &rand::random())
62 }
63
64 pub fn verify_input_vtxo_sig(
65 &self,
66 vtxo: &Vtxo,
67 vtxo_reqs: &[SignedVtxoRequest],
68 sig: &schnorr::Signature,
69 ) -> Result<(), secp256k1::Error> {
70 let msg = self.as_signable_message(vtxo.id(), vtxo_reqs);
71 SECP.verify_schnorr( sig, &msg, &vtxo.user_pubkey().x_only_public_key().0)
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct NonInteractiveRoundParticipationChallenge;
77
78impl NonInteractiveRoundParticipationChallenge {
79 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"hArk round join ownership proof ";
80
81 fn signable_message(
84 vtxo_id: VtxoId,
85 vtxo_reqs: &[VtxoRequest],
86 ) -> Message {
87 let mut engine = sha256::Hash::engine();
88 engine.write_all(Self::CHALLENGE_MESSAGE_PREFIX).unwrap();
89 engine.write_all(&vtxo_id.to_bytes()).unwrap();
90
91 engine.write_all(&vtxo_reqs.len().to_be_bytes()).unwrap();
92 for req in vtxo_reqs {
93 engine.write_all(&req.amount.to_sat().to_be_bytes()).unwrap();
94 req.policy.encode(&mut engine).unwrap();
95 }
96
97 let hash = sha256::Hash::from_engine(engine).to_byte_array();
98 Message::from_digest(hash)
99 }
100
101 pub fn sign_with(
102 vtxo_id: VtxoId,
103 vtxo_reqs: &[VtxoRequest],
104 vtxo_keypair: &Keypair,
105 ) -> schnorr::Signature {
106 let msg = Self::signable_message(vtxo_id, vtxo_reqs);
107 SECP.sign_schnorr_with_aux_rand(&msg, &vtxo_keypair, &rand::random())
108 }
109
110 pub fn verify_input_vtxo_sig(
111 vtxo: &Vtxo,
112 vtxo_reqs: &[VtxoRequest],
113 sig: &schnorr::Signature,
114 ) -> Result<(), secp256k1::Error> {
115 let msg = Self::signable_message(vtxo.id(), vtxo_reqs);
116 SECP.verify_schnorr( sig, &msg, &vtxo.user_pubkey().x_only_public_key().0)
117 }
118}
119
120#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
126pub struct LightningReceiveChallenge(PaymentHash);
127
128impl LightningReceiveChallenge {
129 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Lightning receive VTXO challenge";
130
131 pub fn new(value: PaymentHash) -> Self {
132 Self(value)
133 }
134
135 fn as_signable_message(&self, vtxo_id: VtxoId) -> Message {
139 let mut engine = sha256::Hash::engine();
140 engine.write_all(Self::CHALLENGE_MESSAGE_PREFIX).unwrap();
141 engine.write_all(&self.0.to_byte_array()).unwrap();
142 engine.write_all(&vtxo_id.to_bytes()).unwrap();
143
144 let hash = sha256::Hash::from_engine(engine).to_byte_array();
145 Message::from_digest(hash)
146 }
147
148 pub fn sign_with(
149 &self,
150 vtxo_id: VtxoId,
151 vtxo_keypair: &Keypair,
152 ) -> schnorr::Signature {
153 SECP.sign_schnorr_with_aux_rand(
154 &Self::as_signable_message(self, vtxo_id),
155 &vtxo_keypair,
156 &rand::random()
157 )
158 }
159
160 pub fn verify_input_vtxo_sig(
161 &self,
162 vtxo: &Vtxo,
163 sig: &schnorr::Signature,
164 ) -> Result<(), secp256k1::Error> {
165 SECP.verify_schnorr(
166 sig,
167 &Self::as_signable_message(self, vtxo.id()),
168 &vtxo.user_pubkey().x_only_public_key().0,
169 )
170 }
171}
172
173#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
187pub struct VtxoStatusChallenge;
188
189impl VtxoStatusChallenge {
190 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Ark VTXO status query challenge ";
191
192 pub fn new() -> Self {
193 Self
194 }
195
196 fn as_signable_message(&self, vtxo_id: VtxoId) -> Message {
197 let mut engine = sha256::Hash::engine();
198 engine.write_all(Self::CHALLENGE_MESSAGE_PREFIX).unwrap();
199 engine.write_all(&vtxo_id.to_bytes()).unwrap();
200
201 let hash = sha256::Hash::from_engine(engine).to_byte_array();
202 Message::from_digest(hash)
203 }
204
205 pub fn sign_with(
206 &self,
207 vtxo_id: VtxoId,
208 vtxo_keypair: &Keypair,
209 ) -> schnorr::Signature {
210 SECP.sign_schnorr_with_aux_rand(
211 &Self::as_signable_message(self, vtxo_id),
212 &vtxo_keypair,
213 &rand::random(),
214 )
215 }
216
217 pub fn verify_input_vtxo_sig(
218 &self,
219 vtxo: &Vtxo,
220 sig: &schnorr::Signature,
221 ) -> Result<(), secp256k1::Error> {
222 SECP.verify_schnorr(
223 sig,
224 &Self::as_signable_message(self, vtxo.id()),
225 &vtxo.user_pubkey().x_only_public_key().0,
226 )
227 }
228}
229
230#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
234pub struct OffboardRequestChallenge {
235 message: Message,
236}
237
238impl OffboardRequestChallenge {
239 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Ark offboard request challenge ";
240
241 pub fn new(
242 request: &OffboardRequest,
243 inputs: impl Iterator<Item = VtxoId> + ExactSizeIterator,
244 ) -> Self {
245 let mut eng = sha256::Hash::engine();
246 eng.input(Self::CHALLENGE_MESSAGE_PREFIX);
247 request.to_txout().encode(&mut eng).unwrap();
248 eng.emit_u32(inputs.len() as u32).unwrap();
249 for vtxo in inputs {
250 eng.input(&vtxo.to_bytes());
251 }
252 Self {
253 message: Message::from_digest(sha256::Hash::from_engine(eng).to_byte_array()),
254 }
255 }
256
257 pub fn sign_with(
258 &self,
259 vtxo_keypair: &Keypair,
260 ) -> schnorr::Signature {
261 SECP.sign_schnorr_with_aux_rand(
262 &self.message,
263 vtxo_keypair,
264 &rand::random(),
265 )
266 }
267
268 pub fn verify_input_vtxo_sig(
269 &self,
270 vtxo: &Vtxo,
271 sig: &schnorr::Signature,
272 ) -> Result<(), secp256k1::Error> {
273 SECP.verify_schnorr(
274 sig,
275 &self.message,
276 &vtxo.user_pubkey().x_only_public_key().0,
277 )
278 }
279}