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