1
2use std::borrow::Borrow;
3use std::convert::TryFrom;
4use std::time::Duration;
5
6use bitcoin::hashes::sha256;
7use bitcoin::secp256k1::{schnorr, PublicKey};
8use bitcoin::{self, Amount, FeeRate, OutPoint, ScriptBuf, Transaction, Txid};
9
10use ark::{musig, ProtocolEncoding, SignedVtxoRequest, Vtxo, VtxoId, VtxoPolicy, VtxoRequest};
11use ark::vtxo::policy::check_block_delta;
12use ark::arkoor::{ArkoorCosignRequest, ArkoorCosignResponse, ArkoorDestination};
13use ark::arkoor::package::{ArkoorPackageCosignRequest, ArkoorPackageCosignResponse};
14use ark::attestations::{
15 ArkoorCosignAttestation, DelegatedRoundParticipationAttestation, LightningReceiveAttestation, OffboardRequestAttestation, RoundAttemptAttestation, VtxoStatusAttestation
16};
17use ark::board::BoardCosignResponse;
18use ark::fees::PpmFeeRate;
19use ark::forfeit::HashLockedForfeitBundle;
20use ark::lightning::{PaymentHash, Preimage};
21use ark::mailbox::BlindedMailboxIdentifier;
22use ark::offboard::OffboardRequest;
23use ark::rounds::{Challenge, RoundId};
24use ark::tree::signed::{LeafVtxoCosignRequest, LeafVtxoCosignResponse, VtxoTreeSpec};
25use ark::vtxo::{Bare, Full, VtxoRef};
26
27use crate::protos;
28
29
30#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
31#[error("rpc conversion error: {msg}")]
32pub struct ConvertError {
33 pub msg: &'static str,
34}
35
36impl From<&'static str> for ConvertError {
37 fn from(msg: &'static str) -> ConvertError {
38 ConvertError { msg }
39 }
40}
41
42impl From<ConvertError> for tonic::Status {
43 fn from(e: ConvertError) -> Self {
44 tonic::Status::invalid_argument(e.msg)
45 }
46}
47
48pub trait TryFromBytes: Sized {
50 fn from_bytes<T: AsRef<[u8]>>(b: T) -> Result<Self, ConvertError>;
51}
52
53macro_rules! impl_try_from_byte_array {
54 ($ty:path, $exp:expr) => {
55 impl TryFromBytes for $ty {
56 fn from_bytes<T: AsRef<[u8]>>(b: T) -> Result<Self, ConvertError> {
57 #[allow(unused)]
58 use bitcoin::hashes::Hash;
59
60 let array = TryFrom::try_from(b.as_ref())
61 .map_err(|_| concat!("invalid ", $exp))?;
62 Ok(<$ty>::from_byte_array(array))
63 }
64 }
65 };
66}
67impl_try_from_byte_array!(PaymentHash, "lightning payment hash");
68impl_try_from_byte_array!(Preimage, "lightning payment preimage");
69impl_try_from_byte_array!(sha256::Hash, "SHA-256 hash");
70impl_try_from_byte_array!(Txid, "transaction id");
71
72macro_rules! impl_try_from_byte_array_result {
73 ($ty:path, $exp:expr) => {
74 impl TryFromBytes for $ty {
75 fn from_bytes<T: AsRef<[u8]>>(b: T) -> Result<Self, ConvertError> {
76 Ok(TryFrom::try_from(b.as_ref()).ok()
77 .and_then(|b| <$ty>::from_byte_array(b).ok())
78 .ok_or(concat!("invalid ", $exp))?)
79 }
80 }
81 };
82}
83impl_try_from_byte_array_result!(musig::PublicNonce, "public musig nonce");
84impl_try_from_byte_array_result!(musig::PartialSignature, "partial musig signature");
85impl_try_from_byte_array_result!(musig::AggregatedNonce, "aggregated musig nonce");
86
87macro_rules! impl_try_from_byte_slice {
88 ($ty:path, $exp:expr) => {
89 impl TryFromBytes for $ty {
90 fn from_bytes<T: AsRef<[u8]>>(b: T) -> Result<Self, ConvertError> {
91 #[allow(unused)] use bitcoin::hashes::Hash;
93
94 Ok(<$ty>::from_slice(b.as_ref()).map_err(|_| concat!("invalid ", $exp))?)
95 }
96 }
97 };
98}
99impl_try_from_byte_slice!(PublicKey, "public key");
100impl_try_from_byte_slice!(schnorr::Signature, "Schnorr signature");
101impl_try_from_byte_slice!(VtxoId, "VTXO ID");
102impl_try_from_byte_slice!(RoundId, "VTXO ID");
103
104macro_rules! impl_try_from_bytes_protocol {
105 ($ty:path, $exp:expr) => {
106 impl TryFromBytes for $ty {
107 fn from_bytes<T: AsRef<[u8]>>(b: T) -> Result<Self, ConvertError> {
108 Ok(ProtocolEncoding::deserialize(b.as_ref())
109 .map_err(|_| concat!("invalid ", $exp))?)
110 }
111 }
112 };
113}
114impl_try_from_bytes_protocol!(OutPoint, "outpoint");
115impl_try_from_bytes_protocol!(Vtxo<Bare>, "bare VTXO");
116impl_try_from_bytes_protocol!(Vtxo<Full>, "full VTXO (with genesis)");
117impl_try_from_bytes_protocol!(VtxoPolicy, "VTXO policy");
118impl_try_from_bytes_protocol!(BlindedMailboxIdentifier, "a blinded VTXO mailbox identifier");
119impl_try_from_bytes_protocol!(HashLockedForfeitBundle, "hArk forfeit bundle");
120impl_try_from_bytes_protocol!(RoundAttemptAttestation, "round attempt attestation");
121impl_try_from_bytes_protocol!(DelegatedRoundParticipationAttestation,
122 "delegated round participation attestation"
123);
124impl_try_from_bytes_protocol!(LightningReceiveAttestation, "lightning receive attestation");
125impl_try_from_bytes_protocol!(VtxoStatusAttestation, "VTXO status attestation");
126impl_try_from_bytes_protocol!(OffboardRequestAttestation, "offboard request attestation");
127
128macro_rules! impl_try_from_bytes_bitcoin {
129 ($ty:path, $exp:expr) => {
130 impl TryFromBytes for $ty {
131 fn from_bytes<T: AsRef<[u8]>>(b: T) -> Result<Self, ConvertError> {
132 Ok(bitcoin::consensus::encode::deserialize(b.as_ref())
133 .map_err(|_| concat!("invalid ", $exp))?)
134 }
135 }
136 };
137}
138impl_try_from_bytes_bitcoin!(Transaction, "bitcoin transaction");
139
140
141impl From<ark::ArkInfo> for protos::ArkInfo {
142 #[allow(deprecated)] fn from(v: ark::ArkInfo) -> Self {
144 protos::ArkInfo {
145 network: v.network.to_string(),
146 server_pubkey: v.server_pubkey.serialize().to_vec(),
147 mailbox_pubkey: v.mailbox_pubkey.serialize().to_vec(),
148 round_interval_secs: v.round_interval.as_secs() as u32,
149 nb_round_nonces: v.nb_round_nonces as u32,
150 vtxo_exit_delta: v.vtxo_exit_delta as u32,
151 vtxo_expiry_delta: v.vtxo_expiry_delta as u32,
152 htlc_send_expiry_delta: v.htlc_send_expiry_delta as u32,
153 htlc_expiry_delta: v.htlc_expiry_delta as u32,
154 max_vtxo_amount: v.max_vtxo_amount.map(|v| v.to_sat()),
155 required_board_confirmations: v.required_board_confirmations as u32,
156 max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta as u32,
157 min_board_amount: v.min_board_amount.to_sat(),
158 offboard_feerate_sat_vkb: v.offboard_feerate.to_sat_per_kwu() * 4,
159 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
160 fees: Some(v.fees.into()),
161 max_vtxo_exit_depth: v.max_vtxo_exit_depth as u32,
162 }
163 }
164}
165
166impl TryFrom<protos::ArkInfo> for ark::ArkInfo {
167 type Error = ConvertError;
168 #[allow(deprecated)] fn try_from(v: protos::ArkInfo) -> Result<Self, Self::Error> {
170 Ok(ark::ArkInfo {
171 network: v.network.parse().map_err(|_| "invalid network")?,
172 server_pubkey: PublicKey::from_slice(&v.server_pubkey)
173 .map_err(|_| "invalid server pubkey")?,
174 mailbox_pubkey: PublicKey::from_slice(&v.mailbox_pubkey)
175 .map_err(|_| "invalid mailbox pubkey")?,
176 round_interval: Duration::from_secs(v.round_interval_secs as u64),
177 nb_round_nonces: v.nb_round_nonces as usize,
178 vtxo_exit_delta: check_block_delta(v.vtxo_exit_delta)
179 .map_err(|_| "invalid vtxo_exit_delta")?,
180 vtxo_expiry_delta: check_block_delta(v.vtxo_expiry_delta)
181 .map_err(|_| "invalid vtxo_expiry_delta")?,
182 htlc_send_expiry_delta: check_block_delta(v.htlc_send_expiry_delta)
183 .map_err(|_| "invalid htlc_send_expiry_delta")?,
184 htlc_expiry_delta: check_block_delta(v.htlc_expiry_delta)
185 .map_err(|_| "invalid htlc_expiry_delta")?,
186 max_vtxo_amount: v.max_vtxo_amount.map(|v| Amount::from_sat(v)),
187 required_board_confirmations: v.required_board_confirmations as usize,
188 max_user_invoice_cltv_delta: check_block_delta(v.max_user_invoice_cltv_delta)
189 .map_err(|_| "invalid max_user_invoice_cltv_delta")?,
190 min_board_amount: Amount::from_sat(v.min_board_amount),
191 offboard_feerate: FeeRate::from_sat_per_kwu(v.offboard_feerate_sat_vkb / 4),
192 ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
193 fees: v.fees.ok_or("missing fees")?.try_into()?,
194 max_vtxo_exit_depth: v.max_vtxo_exit_depth.try_into()
195 .map_err(|_| "invalid max_vtxo_exit_depth")?,
196 })
197 }
198}
199
200impl From<ark::fees::PpmExpiryFeeEntry> for protos::PpmExpiryFeeEntry {
201 fn from(v: ark::fees::PpmExpiryFeeEntry) -> Self {
202 protos::PpmExpiryFeeEntry {
203 expiry_blocks_threshold: v.expiry_blocks_threshold,
204 ppm: v.ppm.0,
205 }
206 }
207}
208
209impl From<protos::PpmExpiryFeeEntry> for ark::fees::PpmExpiryFeeEntry {
210 fn from(v: protos::PpmExpiryFeeEntry) -> Self {
211 ark::fees::PpmExpiryFeeEntry {
212 expiry_blocks_threshold: v.expiry_blocks_threshold,
213 ppm: PpmFeeRate(v.ppm),
214 }
215 }
216}
217
218impl From<ark::fees::BoardFees> for protos::BoardFees {
219 fn from(v: ark::fees::BoardFees) -> Self {
220 protos::BoardFees {
221 min_fee_sat: v.min_fee.to_sat(),
222 base_fee_sat: v.base_fee.to_sat(),
223 ppm: v.ppm.0,
224 }
225 }
226}
227
228impl From<protos::BoardFees> for ark::fees::BoardFees {
229 fn from(v: protos::BoardFees) -> Self {
230 ark::fees::BoardFees {
231 min_fee: Amount::from_sat(v.min_fee_sat),
232 base_fee: Amount::from_sat(v.base_fee_sat),
233 ppm: PpmFeeRate(v.ppm),
234 }
235 }
236}
237
238impl From<ark::fees::OffboardFees> for protos::OffboardFees {
239 fn from(v: ark::fees::OffboardFees) -> Self {
240 protos::OffboardFees {
241 base_fee_sat: v.base_fee.to_sat(),
242 fixed_additional_vb: v.fixed_additional_vb,
243 ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
244 }
245 }
246}
247
248impl From<protos::OffboardFees> for ark::fees::OffboardFees {
249 fn from(v: protos::OffboardFees) -> Self {
250 ark::fees::OffboardFees {
251 base_fee: Amount::from_sat(v.base_fee_sat),
252 fixed_additional_vb: v.fixed_additional_vb,
253 ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
254 }
255 }
256}
257
258impl From<ark::fees::RefreshFees> for protos::RefreshFees {
259 fn from(v: ark::fees::RefreshFees) -> Self {
260 protos::RefreshFees {
261 base_fee_sat: v.base_fee.to_sat(),
262 ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
263 }
264 }
265}
266
267impl From<protos::RefreshFees> for ark::fees::RefreshFees {
268 fn from(v: protos::RefreshFees) -> Self {
269 ark::fees::RefreshFees {
270 base_fee: Amount::from_sat(v.base_fee_sat),
271 ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
272 }
273 }
274}
275
276impl From<ark::fees::LightningReceiveFees> for protos::LightningReceiveFees {
277 fn from(v: ark::fees::LightningReceiveFees) -> Self {
278 protos::LightningReceiveFees {
279 base_fee_sat: v.base_fee.to_sat(),
280 ppm: v.ppm.0,
281 }
282 }
283}
284
285impl From<protos::LightningReceiveFees> for ark::fees::LightningReceiveFees {
286 fn from(v: protos::LightningReceiveFees) -> Self {
287 ark::fees::LightningReceiveFees {
288 base_fee: Amount::from_sat(v.base_fee_sat),
289 ppm: PpmFeeRate(v.ppm),
290 }
291 }
292}
293
294impl From<ark::fees::LightningSendFees> for protos::LightningSendFees {
295 fn from(v: ark::fees::LightningSendFees) -> Self {
296 protos::LightningSendFees {
297 min_fee_sat: v.min_fee.to_sat(),
298 base_fee_sat: v.base_fee.to_sat(),
299 ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
300 }
301 }
302}
303
304impl From<protos::LightningSendFees> for ark::fees::LightningSendFees {
305 fn from(v: protos::LightningSendFees) -> Self {
306 ark::fees::LightningSendFees {
307 min_fee: Amount::from_sat(v.min_fee_sat),
308 base_fee: Amount::from_sat(v.base_fee_sat),
309 ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
310 }
311 }
312}
313
314impl From<ark::fees::FeeSchedule> for protos::FeeSchedule {
315 fn from(v: ark::fees::FeeSchedule) -> Self {
316 protos::FeeSchedule {
317 board: Some(v.board.into()),
318 offboard: Some(v.offboard.into()),
319 refresh: Some(v.refresh.into()),
320 lightning_receive: Some(v.lightning_receive.into()),
321 lightning_send: Some(v.lightning_send.into()),
322 }
323 }
324}
325
326impl TryFrom<protos::FeeSchedule> for ark::fees::FeeSchedule {
327 type Error = ConvertError;
328 fn try_from(v: protos::FeeSchedule) -> Result<Self, Self::Error> {
329 Ok(ark::fees::FeeSchedule {
330 board: v.board.ok_or("missing board fees")?.into(),
331 offboard: v.offboard.ok_or("missing offboard fees")?.into(),
332 refresh: v.refresh.ok_or("missing refresh fees")?.into(),
333 lightning_receive: v.lightning_receive.ok_or("missing lightning receive fees")?.into(),
334 lightning_send: v.lightning_send.ok_or("missing lightning send fees")?.into(),
335 })
336 }
337}
338
339impl<'a> From<&'a ark::rounds::RoundEvent> for protos::RoundEvent {
340 fn from(e: &'a ark::rounds::RoundEvent) -> Self {
341 protos::RoundEvent {
342 event: Some(match e {
343 ark::rounds::RoundEvent::Attempt(ark::rounds::RoundAttempt {
344 round_seq, attempt_seq, challenge,
345 }) => {
346 protos::round_event::Event::Attempt(protos::RoundAttempt {
347 round_seq: (*round_seq).into(),
348 attempt_seq: *attempt_seq as u64,
349 round_attempt_challenge: challenge.inner().to_vec(),
350 })
351 },
352 ark::rounds::RoundEvent::VtxoProposal(ark::rounds::VtxoProposal {
353 round_seq, attempt_seq, vtxos_spec, unsigned_round_tx, cosign_agg_nonces,
354 }) => {
355 protos::round_event::Event::VtxoProposal(protos::VtxoProposal {
356 round_seq: (*round_seq).into(),
357 attempt_seq: *attempt_seq as u64,
358 vtxos_spec: vtxos_spec.serialize(),
359 unsigned_round_tx: bitcoin::consensus::serialize(&unsigned_round_tx),
360 vtxos_agg_nonces: cosign_agg_nonces.into_iter()
361 .map(|n| n.serialize().to_vec())
362 .collect(),
363 })
364 },
365 ark::rounds::RoundEvent::Finished(ark::rounds::RoundFinished {
366 round_seq, attempt_seq, cosign_sigs, signed_round_tx,
367 }) => {
368 protos::round_event::Event::Finished(protos::RoundFinished {
369 round_seq: (*round_seq).into(),
370 attempt_seq: *attempt_seq as u64,
371 vtxo_cosign_signatures: cosign_sigs.into_iter()
372 .map(|s| s.serialize().to_vec()).collect(),
373 signed_round_tx: bitcoin::consensus::serialize(&signed_round_tx),
374 })
375 },
376 ark::rounds::RoundEvent::Failed(ark::rounds::RoundFailed {
377 round_seq,
378 }) => {
379 protos::round_event::Event::Failed(protos::RoundFailed {
380 round_seq: (*round_seq).into(),
381 })
382 },
383 })
384 }
385 }
386}
387
388impl TryFrom<protos::RoundEvent> for ark::rounds::RoundEvent {
389 type Error = ConvertError;
390
391 fn try_from(m: protos::RoundEvent) -> Result<ark::rounds::RoundEvent, Self::Error> {
392 Ok(match m.event.ok_or("unknown round event")? {
393 protos::round_event::Event::Attempt(m) => {
394 ark::rounds::RoundEvent::Attempt(ark::rounds::RoundAttempt {
395 round_seq: m.round_seq.into(),
396 attempt_seq: m.attempt_seq as usize,
397 challenge: Challenge::new(
398 m.round_attempt_challenge.try_into().map_err(|_| "invalid challenge")?
399 ),
400 })
401 },
402 protos::round_event::Event::VtxoProposal(m) => {
403 ark::rounds::RoundEvent::VtxoProposal(ark::rounds::VtxoProposal {
404 round_seq: m.round_seq.into(),
405 attempt_seq: m.attempt_seq as usize,
406 unsigned_round_tx: bitcoin::consensus::deserialize(&m.unsigned_round_tx)
407 .map_err(|_| "invalid unsigned_round_tx")?,
408 vtxos_spec: VtxoTreeSpec::deserialize(&m.vtxos_spec)
409 .map_err(|_| "invalid vtxos_spec")?,
410 cosign_agg_nonces: m.vtxos_agg_nonces.into_iter().map(|n| {
411 musig::AggregatedNonce::from_bytes(&n)
412 }).collect::<Result<_, _>>()?,
413 })
414 },
415 protos::round_event::Event::Finished(m) => {
416 ark::rounds::RoundEvent::Finished(ark::rounds::RoundFinished {
417 round_seq: m.round_seq.into(),
418 attempt_seq: m.attempt_seq as usize,
419 cosign_sigs: m.vtxo_cosign_signatures.into_iter().map(|s| {
420 schnorr::Signature::from_slice(&s)
421 .map_err(|_| "invalid vtxo_cosign_signatures")
422 }).collect::<Result<_, _>>()?,
423 signed_round_tx: bitcoin::consensus::deserialize(&m.signed_round_tx)
424 .map_err(|_| "invalid signed_round_tx")?,
425 })
426 },
427 protos::round_event::Event::Failed(m) => {
428 ark::rounds::RoundEvent::Failed(ark::rounds::RoundFailed {
429 round_seq: m.round_seq.into(),
430 })
431 },
432 })
433 }
434}
435
436impl From<crate::WalletStatus> for protos::WalletStatus {
437 fn from(s: crate::WalletStatus) -> Self {
438 protos::WalletStatus {
439 address: s.address.assume_checked().to_string(),
440 total_balance: s.total_balance.to_sat(),
441 trusted_balance: s.trusted_balance.to_sat(),
442 untrusted_balance: s.untrusted_balance.to_sat(),
443 confirmed_utxos: s.confirmed_utxos.iter().map(|u| u.to_string()).collect(),
444 unconfirmed_utxos: s.unconfirmed_utxos.iter().map(|u| u.to_string()).collect(),
445 }
446 }
447}
448
449impl TryFrom<protos::WalletStatus> for crate::WalletStatus {
450 type Error = ConvertError;
451 fn try_from(s: protos::WalletStatus) -> Result<Self, Self::Error> {
452 Ok(crate::WalletStatus {
453 address: s.address.parse().map_err(|_| "invalid address")?,
454 total_balance: Amount::from_sat(s.total_balance),
455 trusted_balance: Amount::from_sat(s.trusted_balance),
456 untrusted_balance: Amount::from_sat(s.untrusted_balance),
457 confirmed_utxos: s.confirmed_utxos.iter().map(|u| {
458 u.parse().map_err(|_| "invalid outpoint")
459 }).collect::<Result<_, _>>()?,
460 unconfirmed_utxos: s.unconfirmed_utxos.iter().map(|u| {
461 u.parse().map_err(|_| "invalid outpoint")
462 }).collect::<Result<_, _>>()?,
463 })
464 }
465}
466
467
468impl<'a> From<&'a VtxoRequest> for protos::VtxoRequest {
469 fn from(v: &'a VtxoRequest) -> Self {
470 protos::VtxoRequest {
471 amount: v.amount.to_sat(),
472 policy: v.policy.serialize(),
473 }
474 }
475}
476
477impl TryFrom<protos::VtxoRequest> for VtxoRequest {
478 type Error = ConvertError;
479 fn try_from(v: protos::VtxoRequest) -> Result<Self, Self::Error> {
480 Ok(Self {
481 amount: Amount::from_sat(v.amount),
482 policy: VtxoPolicy::deserialize(&v.policy).map_err(|_| "invalid policy")?,
483 })
484 }
485}
486
487impl TryFrom<protos::ArkoorDestination> for ArkoorDestination {
488 type Error = ConvertError;
489 fn try_from(v: protos::ArkoorDestination) -> Result<Self, Self::Error> {
490 Ok(Self {
491 total_amount: Amount::from_sat(v.total_amount),
492 policy: VtxoPolicy::deserialize(&v.policy).map_err(|_| "invalid policy")?,
493 })
494 }
495}
496
497impl From<ArkoorDestination> for protos::ArkoorDestination {
498 fn from(v: ArkoorDestination) -> Self {
499 Self {
500 total_amount: v.total_amount.to_sat(),
501 policy: v.policy.serialize(),
502 }
503 }
504}
505
506impl From<SignedVtxoRequest> for protos::SignedVtxoRequest {
507 fn from(v: SignedVtxoRequest) -> Self {
508 protos::SignedVtxoRequest {
509 vtxo: Some(protos::VtxoRequest {
510 amount: v.vtxo.amount.to_sat(),
511 policy: v.vtxo.policy.serialize(),
512 }),
513 cosign_pubkey: v.cosign_pubkey.serialize().to_vec(),
514 public_nonces: v.nonces.iter().map(|n| n.serialize().to_vec()).collect(),
515 }
516 }
517}
518
519impl TryFrom<protos::SignedVtxoRequest> for SignedVtxoRequest {
520 type Error = ConvertError;
521 fn try_from(v: protos::SignedVtxoRequest) -> Result<Self, Self::Error> {
522 let vtxo = v.vtxo.ok_or("vtxo field missing")?;
523 Ok(SignedVtxoRequest {
524 vtxo: VtxoRequest {
525 amount: Amount::from_sat(vtxo.amount),
526 policy: VtxoPolicy::from_bytes(&vtxo.policy)?,
527 },
528 cosign_pubkey: PublicKey::from_bytes(&v.cosign_pubkey)?,
529 nonces: v.public_nonces.into_iter()
530 .map(|n| musig::PublicNonce::from_bytes(n))
531 .collect::<Result<_, _>>()?,
532 })
533 }
534}
535
536impl From<BoardCosignResponse> for protos::BoardCosignResponse {
537 fn from(v: BoardCosignResponse) -> Self {
538 Self {
539 pub_nonce: v.pub_nonce.serialize().to_vec(),
540 partial_sig: v.partial_signature.serialize().to_vec(),
541 }
542 }
543}
544
545impl TryFrom<protos::BoardCosignResponse> for BoardCosignResponse {
546 type Error = ConvertError;
547 fn try_from(v: protos::BoardCosignResponse) -> Result<Self, Self::Error> {
548 Ok(Self {
549 pub_nonce: musig::PublicNonce::from_bytes(&v.pub_nonce)?,
550 partial_signature: musig::PartialSignature::from_bytes(&v.partial_sig)?,
551 })
552 }
553}
554
555impl From<ark::integration::TokenType> for protos::intman::TokenType {
556 fn from(value: ark::integration::TokenType) -> Self {
557 match value {
558 ark::integration::TokenType::SingleUseBoard => protos::intman::TokenType::SingleUseBoard,
559 }
560 }
561}
562
563impl From<protos::intman::TokenType> for ark::integration::TokenType {
564 fn from(value: protos::intman::TokenType) -> Self {
565 match value {
566 protos::intman::TokenType::SingleUseBoard => ark::integration::TokenType::SingleUseBoard,
567 }
568 }
569}
570
571impl From<protos::intman::TokenStatus> for ark::integration::TokenStatus {
572 fn from(value: protos::intman::TokenStatus) -> Self {
573 match value {
574 protos::intman::TokenStatus::Unused => ark::integration::TokenStatus::Unused,
575 protos::intman::TokenStatus::Used => ark::integration::TokenStatus::Used,
576 protos::intman::TokenStatus::Abused => ark::integration::TokenStatus::Abused,
577 protos::intman::TokenStatus::Disabled => ark::integration::TokenStatus::Disabled,
578 protos::intman::TokenStatus::Expired => ark::integration::TokenStatus::Unused,
580 }
581 }
582}
583
584impl From<ark::integration::TokenStatus> for protos::intman::TokenStatus {
585 fn from(value: ark::integration::TokenStatus) -> Self {
586 match value {
587 ark::integration::TokenStatus::Unused => protos::intman::TokenStatus::Unused,
588 ark::integration::TokenStatus::Used => protos::intman::TokenStatus::Used,
589 ark::integration::TokenStatus::Abused => protos::intman::TokenStatus::Abused,
590 ark::integration::TokenStatus::Disabled => protos::intman::TokenStatus::Disabled,
591 }
592 }
593}
594
595impl<V: VtxoRef> From<ArkoorCosignRequest<V>> for protos::ArkoorCosignRequest {
597 fn from(v: ArkoorCosignRequest<V>) -> Self {
598 Self {
599 input_vtxo_id: v.input.vtxo_id().serialize(),
600 user_pub_nonces: v.user_pub_nonces.into_iter()
601 .map(|n| n.serialize().to_vec())
602 .collect::<Vec<_>>(),
603 outputs: v.outputs.into_iter().map(|output| output.into()).collect::<Vec<_>>(),
604 isolated_outputs: v.isolated_outputs.into_iter()
605 .map(|output| output.into())
606 .collect::<Vec<_>>(),
607 use_checkpoint: v.use_checkpoint,
608 attestation: v.attestation.serialize().to_vec(),
609 }
610 }
611}
612
613impl TryFrom<protos::ArkoorCosignRequest> for ArkoorCosignRequest<VtxoId> {
615 type Error = ConvertError;
616 fn try_from(v: protos::ArkoorCosignRequest) -> Result<Self, Self::Error> {
617 let req = Self::new_with_attestation(
618 v.user_pub_nonces.into_iter()
619 .map(|n| musig::PublicNonce::from_bytes(&n))
620 .collect::<Result<Vec<_>, _>>()?,
621 VtxoId::from_bytes(&v.input_vtxo_id)?,
622 v.outputs.into_iter()
623 .map(|output| ArkoorDestination::try_from(output))
624 .collect::<Result<Vec<_>, _>>()?,
625 v.isolated_outputs.into_iter()
626 .map(|output| ArkoorDestination::try_from(output))
627 .collect::<Result<Vec<_>, _>>()?,
628 v.use_checkpoint,
629 ArkoorCosignAttestation::deserialize(&v.attestation)
630 .map_err(|_| "Failed to parse attestation")?,
631 );
632 Ok(req)
633 }
634}
635
636impl<V: VtxoRef> From<ArkoorPackageCosignRequest<V>> for protos::ArkoorPackageCosignRequest {
637 fn from(v: ArkoorPackageCosignRequest<V>) -> Self {
638 Self {
639 parts: v.requests.into_iter().map(|p| p.into()).collect(),
640 }
641 }
642}
643
644impl<'a> TryFrom<protos::ArkoorPackageCosignRequest> for ArkoorPackageCosignRequest<VtxoId> {
645 type Error = ConvertError;
646
647 fn try_from(v: protos::ArkoorPackageCosignRequest) -> Result<Self, Self::Error> {
648 Ok(Self {
649 requests: v.parts.into_iter().map(|p| p.try_into()).collect::<Result<Vec<_>, _>>()?,
650 })
651 }
652}
653
654impl<'a> TryFrom<protos::LightningPayHtlcCosignRequest> for ArkoorPackageCosignRequest<VtxoId> {
655 type Error = ConvertError;
656
657 fn try_from(v: protos::LightningPayHtlcCosignRequest) -> Result<Self, Self::Error> {
658 Ok(Self {
659 requests: v.parts.into_iter().map(|p| p.try_into()).collect::<Result<Vec<_>, _>>()?,
660 })
661 }
662}
663
664impl From<ArkoorCosignResponse> for protos::ArkoorCosignResponse {
665 fn from(v: ArkoorCosignResponse) -> Self {
666 Self {
667 server_pub_nonces: v.server_pub_nonces.into_iter().map(|p| p.serialize().to_vec()).collect::<Vec<_>>(),
668 server_partial_sigs: v.server_partial_sigs.into_iter().map(|p| p.serialize().to_vec()).collect::<Vec<_>>(),
669 }
670 }
671}
672
673impl TryFrom<protos::ArkoorCosignResponse> for ArkoorCosignResponse {
674 type Error = ConvertError;
675 fn try_from(v: protos::ArkoorCosignResponse) -> Result<Self, Self::Error> {
676 Ok(Self {
677 server_pub_nonces: v.server_pub_nonces.into_iter().map(|n| musig::PublicNonce::from_bytes(&n)).collect::<Result<Vec<_>, _>>()?,
678 server_partial_sigs: v.server_partial_sigs.into_iter().map(|n| musig::PartialSignature::from_bytes(&n)).collect::<Result<Vec<_>, _>>()?,
679 })
680 }
681}
682
683impl From<ArkoorPackageCosignResponse> for protos::ArkoorPackageCosignResponse {
684 fn from(v: ArkoorPackageCosignResponse) -> Self {
685 Self {
686 parts: v.responses.into_iter().map(|p| p.into()).collect::<Vec<_>>(),
687 }
688 }
689}
690
691impl TryFrom<protos::ArkoorPackageCosignResponse> for ArkoorPackageCosignResponse {
692 type Error = ConvertError;
693 fn try_from(v: protos::ArkoorPackageCosignResponse) -> Result<Self, Self::Error> {
694 Ok(Self {
695 responses: v.parts.into_iter().map(|p| p.try_into()).collect::<Result<Vec<_>, _>>()?,
696 })
697 }
698}
699
700impl From<LeafVtxoCosignRequest> for protos::LeafVtxoCosignRequest {
701 fn from(v: LeafVtxoCosignRequest) -> Self {
702 protos::LeafVtxoCosignRequest {
703 vtxo_id: v.vtxo_id.to_bytes().to_vec(),
704 public_nonce: v.pub_nonce.serialize().to_vec(),
705 }
706 }
707}
708
709impl From<LeafVtxoCosignResponse> for protos::LeafVtxoCosignResponse {
710 fn from(v: LeafVtxoCosignResponse) -> Self {
711 protos::LeafVtxoCosignResponse {
712 public_nonce: v.public_nonce.serialize().to_vec(),
713 partial_signature: v.partial_signature.serialize().to_vec(),
714 }
715 }
716}
717
718impl TryFrom<protos::LeafVtxoCosignResponse> for LeafVtxoCosignResponse {
719 type Error = ConvertError;
720
721 fn try_from(v: protos::LeafVtxoCosignResponse) -> Result<Self, Self::Error> {
722 Ok(Self {
723 public_nonce: TryFromBytes::from_bytes(v.public_nonce)?,
724 partial_signature: TryFromBytes::from_bytes(v.partial_signature)?,
725 })
726 }
727}
728
729impl<V: Borrow<OffboardRequest>> From<V> for protos::OffboardRequest {
730 fn from(v: V) -> Self {
731 let v = v.borrow();
732 protos::OffboardRequest {
733 offboard_spk: v.script_pubkey.to_bytes(),
734 net_amount_sat: v.net_amount.to_sat(),
735 deduct_fees_from_gross_amount: v.deduct_fees_from_gross_amount,
736 fee_rate_kwu: v.fee_rate.to_sat_per_kwu(),
737 }
738 }
739}
740
741impl TryFrom<protos::OffboardRequest> for OffboardRequest {
742 type Error = ConvertError;
743
744 fn try_from(v: protos::OffboardRequest) -> Result<Self, Self::Error> {
745 Ok(Self {
746 script_pubkey: ScriptBuf::from_bytes(v.offboard_spk),
747 net_amount: Amount::from_sat(v.net_amount_sat),
748 deduct_fees_from_gross_amount: v.deduct_fees_from_gross_amount,
749 fee_rate: FeeRate::from_sat_per_kwu(v.fee_rate_kwu),
750 })
751 }
752}
753
754#[cfg(test)]
755mod test {
756 use std::str::FromStr;
757 use bitcoin::hex::FromHex;
758 use super::*;
759
760 #[test]
761 fn test_preimage_bytes() {
762 let h = "ef2cb05d04819ddb2b9d960c7e0e295ea48ffb429712dc8f30aa48dfcc20c97e";
763 let b = Vec::<u8>::from_hex(h).unwrap();
764
765 let preimage = Preimage::from_str(h).unwrap();
766 assert_eq!(preimage, Preimage::from_bytes(&b).unwrap());
767 assert_eq!(preimage, Preimage::from_slice(&b).unwrap());
768 assert_eq!(preimage, Preimage::from_slice(&preimage.to_vec()).unwrap());
769 assert_eq!(preimage, Preimage::from_bytes(&preimage.to_vec()).unwrap());
770 }
771
772 fn baseline_ark_info_proto() -> protos::ArkInfo {
776 let pk = PublicKey::from_str(
777 "02dfa52f6690299d2d6a08323083e290597b56fee125063e5f4e2957731639c42c",
778 ).unwrap();
779 protos::ArkInfo {
780 network: "regtest".into(),
781 server_pubkey: pk.serialize().to_vec(),
782 mailbox_pubkey: pk.serialize().to_vec(),
783 round_interval_secs: 60,
784 nb_round_nonces: 1,
785 vtxo_exit_delta: 12,
786 vtxo_expiry_delta: 100,
787 htlc_send_expiry_delta: 100,
788 htlc_expiry_delta: 6,
789 max_vtxo_amount: None,
790 required_board_confirmations: 1,
791 max_user_invoice_cltv_delta: 50,
792 min_board_amount: 0,
793 ln_receive_anti_dos_required: false,
794 #[allow(deprecated)]
795 offboard_feerate_sat_vkb: 1000,
796 fees: None,
797 max_vtxo_exit_depth: 5,
798 }
799 }
800
801 #[test]
802 fn ark_info_rejects_oversized_vtxo_exit_delta() {
803 let mut proto = baseline_ark_info_proto();
804 proto.vtxo_exit_delta = ark::vtxo::policy::MAX_BLOCK_DELTA as u32 + 1;
805 assert!(ark::ArkInfo::try_from(proto).is_err());
806 }
807
808 #[test]
809 fn ark_info_rejects_oversized_htlc_expiry_delta() {
810 let mut proto = baseline_ark_info_proto();
811 proto.htlc_expiry_delta = ark::vtxo::policy::MAX_BLOCK_DELTA as u32 + 1;
812 assert!(ark::ArkInfo::try_from(proto).is_err());
813 }
814
815 #[test]
816 fn ark_info_rejects_oversized_max_user_invoice_cltv_delta() {
817 let mut proto = baseline_ark_info_proto();
818 proto.max_user_invoice_cltv_delta = ark::vtxo::policy::MAX_BLOCK_DELTA as u32 + 1;
819 assert!(ark::ArkInfo::try_from(proto).is_err());
820 }
821
822 #[test]
823 fn ark_info_rejects_u32_max_delta() {
824 let mut proto = baseline_ark_info_proto();
827 proto.htlc_expiry_delta = u32::MAX;
828 assert!(ark::ArkInfo::try_from(proto).is_err());
829 }
830}