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, VtxoId, VtxoRequest, SECP};
9use crate::encode::ProtocolEncoding;
10use crate::lightning::PaymentHash;
11use crate::offboard::OffboardRequest;
12use crate::Vtxo;
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<G>(
65 &self,
66 vtxo: &Vtxo<G>,
67 vtxo_reqs: &[SignedVtxoRequest],
68 sig: &schnorr::Signature,
69 ) -> Result<(), secp256k1::Error> {
70
71 let msg = self.as_signable_message(vtxo.id(), vtxo_reqs);
72 SECP.verify_schnorr(sig, &msg, &vtxo.user_pubkey().x_only_public_key().0)
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct NonInteractiveRoundParticipationChallenge;
78
79impl NonInteractiveRoundParticipationChallenge {
80 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"hArk round join ownership proof ";
81
82 fn signable_message(
85 vtxo_id: VtxoId,
86 vtxo_reqs: &[VtxoRequest],
87 ) -> Message {
88 let mut engine = sha256::Hash::engine();
89 engine.write_all(Self::CHALLENGE_MESSAGE_PREFIX).unwrap();
90 engine.write_all(&vtxo_id.to_bytes()).unwrap();
91
92 engine.write_all(&vtxo_reqs.len().to_be_bytes()).unwrap();
93 for req in vtxo_reqs {
94 engine.write_all(&req.amount.to_sat().to_be_bytes()).unwrap();
95 req.policy.encode(&mut engine).unwrap();
96 }
97
98 let hash = sha256::Hash::from_engine(engine).to_byte_array();
99 Message::from_digest(hash)
100 }
101
102 pub fn sign_with(
103 vtxo_id: VtxoId,
104 vtxo_reqs: &[VtxoRequest],
105 vtxo_keypair: &Keypair,
106 ) -> schnorr::Signature {
107 let msg = Self::signable_message(vtxo_id, vtxo_reqs);
108 SECP.sign_schnorr_with_aux_rand(&msg, &vtxo_keypair, &rand::random())
109 }
110
111 pub fn verify_input_vtxo_sig<G>(
112 vtxo: &Vtxo<G>,
113 vtxo_reqs: &[VtxoRequest],
114 sig: &schnorr::Signature,
115 ) -> Result<(), secp256k1::Error> {
116
117 let msg = Self::signable_message(vtxo.id(), vtxo_reqs);
118 SECP.verify_schnorr( sig, &msg, &vtxo.user_pubkey().x_only_public_key().0)
119 }
120}
121
122#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
128pub struct LightningReceiveChallenge(PaymentHash);
129
130impl LightningReceiveChallenge {
131 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Lightning receive VTXO challenge";
132
133 pub fn new(value: PaymentHash) -> Self {
134 Self(value)
135 }
136
137 fn as_signable_message(&self, vtxo_id: VtxoId) -> Message {
141 let mut engine = sha256::Hash::engine();
142 engine.write_all(Self::CHALLENGE_MESSAGE_PREFIX).unwrap();
143 engine.write_all(&self.0.to_byte_array()).unwrap();
144 engine.write_all(&vtxo_id.to_bytes()).unwrap();
145
146 let hash = sha256::Hash::from_engine(engine).to_byte_array();
147 Message::from_digest(hash)
148 }
149
150 pub fn sign_with(
151 &self,
152 vtxo_id: VtxoId,
153 vtxo_keypair: &Keypair,
154 ) -> schnorr::Signature {
155 SECP.sign_schnorr_with_aux_rand(
156 &Self::as_signable_message(self, vtxo_id),
157 &vtxo_keypair,
158 &rand::random()
159 )
160 }
161
162 pub fn verify_input_vtxo_sig<G>(
163 &self,
164 vtxo: &Vtxo<G>,
165 sig: &schnorr::Signature,
166 ) -> Result<(), secp256k1::Error> {
167
168 SECP.verify_schnorr(
169 sig,
170 &Self::as_signable_message(self, vtxo.id()),
171 &vtxo.user_pubkey().x_only_public_key().0,
172 )
173 }
174}
175
176#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
190pub struct VtxoStatusChallenge;
191
192impl VtxoStatusChallenge {
193 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Ark VTXO status query challenge ";
194
195 pub fn new() -> Self {
196 Self
197 }
198
199 fn as_signable_message(&self, vtxo_id: VtxoId) -> Message {
200 let mut engine = sha256::Hash::engine();
201 engine.write_all(Self::CHALLENGE_MESSAGE_PREFIX).unwrap();
202 engine.write_all(&vtxo_id.to_bytes()).unwrap();
203
204 let hash = sha256::Hash::from_engine(engine).to_byte_array();
205 Message::from_digest(hash)
206 }
207
208 pub fn sign_with(
209 &self,
210 vtxo_id: VtxoId,
211 vtxo_keypair: &Keypair,
212 ) -> schnorr::Signature {
213 SECP.sign_schnorr_with_aux_rand(
214 &Self::as_signable_message(self, vtxo_id),
215 &vtxo_keypair,
216 &rand::random(),
217 )
218 }
219
220 pub fn verify_input_vtxo_sig<G>(
221 &self,
222 vtxo: &Vtxo<G>,
223 sig: &schnorr::Signature,
224 ) -> Result<(), secp256k1::Error> {
225
226 SECP.verify_schnorr(
227 sig,
228 &Self::as_signable_message(self, vtxo.id()),
229 &vtxo.user_pubkey().x_only_public_key().0,
230 )
231 }
232}
233
234#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
238pub struct OffboardRequestChallenge {
239 message: Message,
240}
241
242impl OffboardRequestChallenge {
243 const CHALLENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Ark offboard request challenge ";
244
245 pub fn new(
246 req: &OffboardRequest,
247 inputs: impl Iterator<Item = VtxoId> + ExactSizeIterator,
248 ) -> Self {
249 let mut eng = sha256::Hash::engine();
250 eng.input(Self::CHALLENGE_MESSAGE_PREFIX);
251 req.to_txout().encode(&mut eng).unwrap();
252 eng.emit_u32(inputs.len() as u32).unwrap();
253 for vtxo in inputs {
254 eng.input(&vtxo.to_bytes());
255 }
256 Self {
257 message: Message::from_digest(sha256::Hash::from_engine(eng).to_byte_array()),
258 }
259 }
260
261 pub fn sign_with(
262 &self,
263 vtxo_keypair: &Keypair,
264 ) -> schnorr::Signature {
265 SECP.sign_schnorr_with_aux_rand(
266 &self.message,
267 vtxo_keypair,
268 &rand::random(),
269 )
270 }
271
272 pub fn verify_input_vtxo_sig<G>(
273 &self,
274 vtxo: &Vtxo<G>,
275 sig: &schnorr::Signature,
276 ) -> Result<(), secp256k1::Error> {
277
278 SECP.verify_schnorr(
279 sig,
280 &self.message,
281 &vtxo.user_pubkey().x_only_public_key().0,
282 )
283 }
284}