Skip to main content

server_rpc/
convert.rs

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
47/// Trait to convert some types from byte slices.
48pub 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)] // used for the impls of hash types
91				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			// Setting to `Unused` since this status is an alias for `Unused` + expired.
595			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
611// Serialization of a CosignRequest
612impl<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
629// Deserialization of CosignRequest
630impl 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}