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			max_vtxo_exit_depth: v.max_vtxo_exit_depth as u32,
160		}
161	}
162}
163
164impl TryFrom<protos::ArkInfo> for ark::ArkInfo {
165	type Error = ConvertError;
166	fn try_from(v: protos::ArkInfo) -> Result<Self, Self::Error> {
167		Ok(ark::ArkInfo {
168			network: v.network.parse().map_err(|_| "invalid network")?,
169			server_pubkey: PublicKey::from_slice(&v.server_pubkey)
170				.map_err(|_| "invalid server pubkey")?,
171			mailbox_pubkey: PublicKey::from_slice(&v.mailbox_pubkey)
172				.map_err(|_| "invalid mailbox pubkey")?,
173			round_interval: Duration::from_secs(v.round_interval_secs as u64),
174			nb_round_nonces: v.nb_round_nonces as usize,
175			vtxo_exit_delta: v.vtxo_exit_delta.try_into()
176				.map_err(|_| "invalid vtxo exit delta")?,
177			vtxo_expiry_delta: v.vtxo_expiry_delta.try_into()
178				.map_err(|_| "invalid vtxo expiry delta")?,
179			htlc_send_expiry_delta: v.htlc_send_expiry_delta.try_into()
180				.map_err(|_| "invalid htlc send expiry delta")?,
181			htlc_expiry_delta: v.htlc_expiry_delta.try_into()
182				.map_err(|_| "invalid htlc expiry delta")?,
183			max_vtxo_amount: v.max_vtxo_amount.map(|v| Amount::from_sat(v)),
184			required_board_confirmations: v.required_board_confirmations as usize,
185			max_user_invoice_cltv_delta: v.max_user_invoice_cltv_delta.try_into()
186				.map_err(|_| "invalid max user invoice cltv delta")?,
187			min_board_amount: Amount::from_sat(v.min_board_amount),
188			offboard_feerate: FeeRate::from_sat_per_kwu(v.offboard_feerate_sat_vkb / 4),
189			ln_receive_anti_dos_required: v.ln_receive_anti_dos_required,
190			fees: v.fees.ok_or("missing fees")?.try_into()?,
191			max_vtxo_exit_depth: v.max_vtxo_exit_depth.try_into()
192				.map_err(|_| "invalid max_vtxo_exit_depth")?,
193		})
194	}
195}
196
197impl From<ark::fees::PpmExpiryFeeEntry> for protos::PpmExpiryFeeEntry {
198	fn from(v: ark::fees::PpmExpiryFeeEntry) -> Self {
199		protos::PpmExpiryFeeEntry {
200			expiry_blocks_threshold: v.expiry_blocks_threshold,
201			ppm: v.ppm.0,
202		}
203	}
204}
205
206impl From<protos::PpmExpiryFeeEntry> for ark::fees::PpmExpiryFeeEntry {
207	fn from(v: protos::PpmExpiryFeeEntry) -> Self {
208		ark::fees::PpmExpiryFeeEntry {
209			expiry_blocks_threshold: v.expiry_blocks_threshold,
210			ppm: PpmFeeRate(v.ppm),
211		}
212	}
213}
214
215impl From<ark::fees::BoardFees> for protos::BoardFees {
216	fn from(v: ark::fees::BoardFees) -> Self {
217		protos::BoardFees {
218			min_fee_sat: v.min_fee.to_sat(),
219			base_fee_sat: v.base_fee.to_sat(),
220			ppm: v.ppm.0,
221		}
222	}
223}
224
225impl From<protos::BoardFees> for ark::fees::BoardFees {
226	fn from(v: protos::BoardFees) -> Self {
227		ark::fees::BoardFees {
228			min_fee: Amount::from_sat(v.min_fee_sat),
229			base_fee: Amount::from_sat(v.base_fee_sat),
230			ppm: PpmFeeRate(v.ppm),
231		}
232	}
233}
234
235impl From<ark::fees::OffboardFees> for protos::OffboardFees {
236	fn from(v: ark::fees::OffboardFees) -> Self {
237		protos::OffboardFees {
238			base_fee_sat: v.base_fee.to_sat(),
239			fixed_additional_vb: v.fixed_additional_vb,
240			ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
241		}
242	}
243}
244
245impl From<protos::OffboardFees> for ark::fees::OffboardFees {
246	fn from(v: protos::OffboardFees) -> Self {
247		ark::fees::OffboardFees {
248			base_fee: Amount::from_sat(v.base_fee_sat),
249			fixed_additional_vb: v.fixed_additional_vb,
250			ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
251		}
252	}
253}
254
255impl From<ark::fees::RefreshFees> for protos::RefreshFees {
256	fn from(v: ark::fees::RefreshFees) -> Self {
257		protos::RefreshFees {
258			base_fee_sat: v.base_fee.to_sat(),
259			ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
260		}
261	}
262}
263
264impl From<protos::RefreshFees> for ark::fees::RefreshFees {
265	fn from(v: protos::RefreshFees) -> Self {
266		ark::fees::RefreshFees {
267			base_fee: Amount::from_sat(v.base_fee_sat),
268			ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
269		}
270	}
271}
272
273impl From<ark::fees::LightningReceiveFees> for protos::LightningReceiveFees {
274	fn from(v: ark::fees::LightningReceiveFees) -> Self {
275		protos::LightningReceiveFees {
276			base_fee_sat: v.base_fee.to_sat(),
277			ppm: v.ppm.0,
278		}
279	}
280}
281
282impl From<protos::LightningReceiveFees> for ark::fees::LightningReceiveFees {
283	fn from(v: protos::LightningReceiveFees) -> Self {
284		ark::fees::LightningReceiveFees {
285			base_fee: Amount::from_sat(v.base_fee_sat),
286			ppm: PpmFeeRate(v.ppm),
287		}
288	}
289}
290
291impl From<ark::fees::LightningSendFees> for protos::LightningSendFees {
292	fn from(v: ark::fees::LightningSendFees) -> Self {
293		protos::LightningSendFees {
294			min_fee_sat: v.min_fee.to_sat(),
295			base_fee_sat: v.base_fee.to_sat(),
296			ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
297		}
298	}
299}
300
301impl From<protos::LightningSendFees> for ark::fees::LightningSendFees {
302	fn from(v: protos::LightningSendFees) -> Self {
303		ark::fees::LightningSendFees {
304			min_fee: Amount::from_sat(v.min_fee_sat),
305			base_fee: Amount::from_sat(v.base_fee_sat),
306			ppm_expiry_table: v.ppm_expiry_table.into_iter().map(Into::into).collect(),
307		}
308	}
309}
310
311impl From<ark::fees::FeeSchedule> for protos::FeeSchedule {
312	fn from(v: ark::fees::FeeSchedule) -> Self {
313		protos::FeeSchedule {
314			board: Some(v.board.into()),
315			offboard: Some(v.offboard.into()),
316			refresh: Some(v.refresh.into()),
317			lightning_receive: Some(v.lightning_receive.into()),
318			lightning_send: Some(v.lightning_send.into()),
319		}
320	}
321}
322
323impl TryFrom<protos::FeeSchedule> for ark::fees::FeeSchedule {
324	type Error = ConvertError;
325	fn try_from(v: protos::FeeSchedule) -> Result<Self, Self::Error> {
326		Ok(ark::fees::FeeSchedule {
327			board: v.board.ok_or("missing board fees")?.into(),
328			offboard: v.offboard.ok_or("missing offboard fees")?.into(),
329			refresh: v.refresh.ok_or("missing refresh fees")?.into(),
330			lightning_receive: v.lightning_receive.ok_or("missing lightning receive fees")?.into(),
331			lightning_send: v.lightning_send.ok_or("missing lightning send fees")?.into(),
332		})
333	}
334}
335
336impl<'a> From<&'a ark::rounds::RoundEvent> for protos::RoundEvent {
337	fn from(e: &'a ark::rounds::RoundEvent) -> Self {
338		protos::RoundEvent {
339			event: Some(match e {
340				ark::rounds::RoundEvent::Attempt(ark::rounds::RoundAttempt {
341					round_seq, attempt_seq, challenge,
342				}) => {
343					protos::round_event::Event::Attempt(protos::RoundAttempt {
344						round_seq: (*round_seq).into(),
345						attempt_seq: *attempt_seq as u64,
346						round_attempt_challenge: challenge.inner().to_vec(),
347					})
348				},
349				ark::rounds::RoundEvent::VtxoProposal(ark::rounds::VtxoProposal {
350					round_seq, attempt_seq, vtxos_spec, unsigned_round_tx, cosign_agg_nonces,
351				}) => {
352					protos::round_event::Event::VtxoProposal(protos::VtxoProposal {
353						round_seq: (*round_seq).into(),
354						attempt_seq: *attempt_seq as u64,
355						vtxos_spec: vtxos_spec.serialize(),
356						unsigned_round_tx: bitcoin::consensus::serialize(&unsigned_round_tx),
357						vtxos_agg_nonces: cosign_agg_nonces.into_iter()
358							.map(|n| n.serialize().to_vec())
359							.collect(),
360					})
361				},
362				ark::rounds::RoundEvent::Finished(ark::rounds::RoundFinished {
363					round_seq, attempt_seq, cosign_sigs, signed_round_tx,
364				}) => {
365					protos::round_event::Event::Finished(protos::RoundFinished {
366						round_seq: (*round_seq).into(),
367						attempt_seq: *attempt_seq as u64,
368						vtxo_cosign_signatures: cosign_sigs.into_iter()
369							.map(|s| s.serialize().to_vec()).collect(),
370						signed_round_tx: bitcoin::consensus::serialize(&signed_round_tx),
371					})
372				},
373				ark::rounds::RoundEvent::Failed(ark::rounds::RoundFailed {
374					round_seq,
375				}) => {
376					protos::round_event::Event::Failed(protos::RoundFailed {
377						round_seq: (*round_seq).into(),
378					})
379				},
380			})
381		}
382	}
383}
384
385impl TryFrom<protos::RoundEvent> for ark::rounds::RoundEvent {
386	type Error = ConvertError;
387
388	fn try_from(m: protos::RoundEvent) -> Result<ark::rounds::RoundEvent, Self::Error> {
389		Ok(match m.event.ok_or("unknown round event")? {
390			protos::round_event::Event::Attempt(m) => {
391				ark::rounds::RoundEvent::Attempt(ark::rounds::RoundAttempt {
392					round_seq: m.round_seq.into(),
393					attempt_seq: m.attempt_seq as usize,
394					challenge: Challenge::new(
395						m.round_attempt_challenge.try_into().map_err(|_| "invalid challenge")?
396					),
397				})
398			},
399			protos::round_event::Event::VtxoProposal(m) => {
400				ark::rounds::RoundEvent::VtxoProposal(ark::rounds::VtxoProposal {
401					round_seq: m.round_seq.into(),
402					attempt_seq: m.attempt_seq as usize,
403					unsigned_round_tx: bitcoin::consensus::deserialize(&m.unsigned_round_tx)
404						.map_err(|_| "invalid unsigned_round_tx")?,
405					vtxos_spec: VtxoTreeSpec::deserialize(&m.vtxos_spec)
406						.map_err(|_| "invalid vtxos_spec")?,
407					cosign_agg_nonces: m.vtxos_agg_nonces.into_iter().map(|n| {
408						musig::AggregatedNonce::from_bytes(&n)
409					}).collect::<Result<_, _>>()?,
410				})
411			},
412			protos::round_event::Event::Finished(m) => {
413				ark::rounds::RoundEvent::Finished(ark::rounds::RoundFinished {
414					round_seq: m.round_seq.into(),
415					attempt_seq: m.attempt_seq as usize,
416					cosign_sigs: m.vtxo_cosign_signatures.into_iter().map(|s| {
417						schnorr::Signature::from_slice(&s)
418							.map_err(|_| "invalid vtxo_cosign_signatures")
419					}).collect::<Result<_, _>>()?,
420					signed_round_tx: bitcoin::consensus::deserialize(&m.signed_round_tx)
421						.map_err(|_| "invalid signed_round_tx")?,
422				})
423			},
424			protos::round_event::Event::Failed(m) => {
425				ark::rounds::RoundEvent::Failed(ark::rounds::RoundFailed {
426					round_seq: m.round_seq.into(),
427				})
428			},
429		})
430	}
431}
432
433impl From<crate::WalletStatus> for protos::WalletStatus {
434	fn from(s: crate::WalletStatus) -> Self {
435		protos::WalletStatus {
436			address: s.address.assume_checked().to_string(),
437			total_balance: s.total_balance.to_sat(),
438			trusted_balance: s.trusted_balance.to_sat(),
439			untrusted_balance: s.untrusted_balance.to_sat(),
440			confirmed_utxos: s.confirmed_utxos.iter().map(|u| u.to_string()).collect(),
441			unconfirmed_utxos: s.unconfirmed_utxos.iter().map(|u| u.to_string()).collect(),
442		}
443	}
444}
445
446impl TryFrom<protos::WalletStatus> for crate::WalletStatus {
447	type Error = ConvertError;
448	fn try_from(s: protos::WalletStatus) -> Result<Self, Self::Error> {
449		Ok(crate::WalletStatus {
450			address: s.address.parse().map_err(|_| "invalid address")?,
451			total_balance: Amount::from_sat(s.total_balance),
452			trusted_balance: Amount::from_sat(s.trusted_balance),
453			untrusted_balance: Amount::from_sat(s.untrusted_balance),
454			confirmed_utxos: s.confirmed_utxos.iter().map(|u| {
455				u.parse().map_err(|_| "invalid outpoint")
456			}).collect::<Result<_, _>>()?,
457			unconfirmed_utxos: s.unconfirmed_utxos.iter().map(|u| {
458				u.parse().map_err(|_| "invalid outpoint")
459			}).collect::<Result<_, _>>()?,
460		})
461	}
462}
463
464
465impl<'a> From<&'a VtxoRequest> for protos::VtxoRequest {
466	fn from(v: &'a VtxoRequest) -> Self {
467		protos::VtxoRequest {
468			amount: v.amount.to_sat(),
469			policy: v.policy.serialize(),
470		}
471	}
472}
473
474impl TryFrom<protos::VtxoRequest> for VtxoRequest {
475	type Error = ConvertError;
476	fn try_from(v: protos::VtxoRequest) -> Result<Self, Self::Error> {
477		Ok(Self {
478			amount: Amount::from_sat(v.amount),
479			policy: VtxoPolicy::deserialize(&v.policy).map_err(|_| "invalid policy")?,
480		})
481	}
482}
483
484impl TryFrom<protos::ArkoorDestination> for ArkoorDestination {
485	type Error = ConvertError;
486	fn try_from(v: protos::ArkoorDestination) -> Result<Self, Self::Error> {
487		Ok(Self {
488			total_amount: Amount::from_sat(v.total_amount),
489			policy: VtxoPolicy::deserialize(&v.policy).map_err(|_| "invalid policy")?,
490		})
491	}
492}
493
494impl From<ArkoorDestination> for protos::ArkoorDestination {
495	fn from(v: ArkoorDestination) -> Self {
496		Self {
497			total_amount: v.total_amount.to_sat(),
498			policy: v.policy.serialize(),
499		}
500	}
501}
502
503impl From<SignedVtxoRequest> for protos::SignedVtxoRequest {
504	fn from(v: SignedVtxoRequest) -> Self {
505		protos::SignedVtxoRequest {
506			vtxo: Some(protos::VtxoRequest {
507				amount: v.vtxo.amount.to_sat(),
508				policy: v.vtxo.policy.serialize(),
509			}),
510			cosign_pubkey: v.cosign_pubkey.serialize().to_vec(),
511			public_nonces: v.nonces.iter().map(|n| n.serialize().to_vec()).collect(),
512		}
513	}
514}
515
516impl TryFrom<protos::SignedVtxoRequest> for SignedVtxoRequest {
517	type Error = ConvertError;
518	fn try_from(v: protos::SignedVtxoRequest) -> Result<Self, Self::Error> {
519		let vtxo = v.vtxo.ok_or("vtxo field missing")?;
520		Ok(SignedVtxoRequest {
521			vtxo: VtxoRequest {
522				amount: Amount::from_sat(vtxo.amount),
523				policy: VtxoPolicy::from_bytes(&vtxo.policy)?,
524			},
525			cosign_pubkey: PublicKey::from_bytes(&v.cosign_pubkey)?,
526			nonces: v.public_nonces.into_iter()
527				.map(|n| musig::PublicNonce::from_bytes(n))
528				.collect::<Result<_, _>>()?,
529		})
530	}
531}
532
533impl From<BoardCosignResponse> for protos::BoardCosignResponse {
534	fn from(v: BoardCosignResponse) -> Self {
535		Self {
536			pub_nonce: v.pub_nonce.serialize().to_vec(),
537			partial_sig: v.partial_signature.serialize().to_vec(),
538		}
539	}
540}
541
542impl TryFrom<protos::BoardCosignResponse> for BoardCosignResponse {
543	type Error = ConvertError;
544	fn try_from(v: protos::BoardCosignResponse) -> Result<Self, Self::Error> {
545		Ok(Self {
546			pub_nonce: musig::PublicNonce::from_bytes(&v.pub_nonce)?,
547			partial_signature: musig::PartialSignature::from_bytes(&v.partial_sig)?,
548		})
549	}
550}
551
552impl From<ark::integration::TokenType> for protos::intman::TokenType {
553	fn from(value: ark::integration::TokenType) -> Self {
554		match value {
555			ark::integration::TokenType::SingleUseBoard => protos::intman::TokenType::SingleUseBoard,
556		}
557	}
558}
559
560impl From<protos::intman::TokenType> for ark::integration::TokenType {
561	fn from(value: protos::intman::TokenType) -> Self {
562		match value {
563			protos::intman::TokenType::SingleUseBoard => ark::integration::TokenType::SingleUseBoard,
564		}
565	}
566}
567
568impl From<protos::intman::TokenStatus> for ark::integration::TokenStatus {
569	fn from(value: protos::intman::TokenStatus) -> Self {
570		match value {
571			protos::intman::TokenStatus::Unused => ark::integration::TokenStatus::Unused,
572			protos::intman::TokenStatus::Used => ark::integration::TokenStatus::Used,
573			protos::intman::TokenStatus::Abused => ark::integration::TokenStatus::Abused,
574			protos::intman::TokenStatus::Disabled => ark::integration::TokenStatus::Disabled,
575			// Setting to `Unused` since this status is an alias for `Unused` + expired.
576			protos::intman::TokenStatus::Expired => ark::integration::TokenStatus::Unused,
577		}
578	}
579}
580
581impl From<ark::integration::TokenStatus> for protos::intman::TokenStatus {
582	fn from(value: ark::integration::TokenStatus) -> Self {
583		match value {
584			ark::integration::TokenStatus::Unused => protos::intman::TokenStatus::Unused,
585			ark::integration::TokenStatus::Used => protos::intman::TokenStatus::Used,
586			ark::integration::TokenStatus::Abused => protos::intman::TokenStatus::Abused,
587			ark::integration::TokenStatus::Disabled => protos::intman::TokenStatus::Disabled,
588		}
589	}
590}
591
592// Serialization of a CosignRequest
593impl<V: VtxoRef> From<ArkoorCosignRequest<V>> for protos::ArkoorCosignRequest {
594	fn from(v: ArkoorCosignRequest<V>) -> Self {
595		Self {
596			input_vtxo_id: v.input.vtxo_id().serialize(),
597			user_pub_nonces: v.user_pub_nonces.into_iter()
598				.map(|n| n.serialize().to_vec())
599				.collect::<Vec<_>>(),
600			outputs: v.outputs.into_iter().map(|output| output.into()).collect::<Vec<_>>(),
601			isolated_outputs: v.isolated_outputs.into_iter()
602				.map(|output| output.into())
603				.collect::<Vec<_>>(),
604			use_checkpoint: v.use_checkpoint,
605			attestation: v.attestation.serialize().to_vec(),
606		}
607	}
608}
609
610// Deserialization of CosignRequest
611impl TryFrom<protos::ArkoorCosignRequest> for ArkoorCosignRequest<VtxoId> {
612	type Error = ConvertError;
613	fn try_from(v: protos::ArkoorCosignRequest) -> Result<Self, Self::Error> {
614		let req = Self::new_with_attestation(
615			v.user_pub_nonces.into_iter()
616				.map(|n| musig::PublicNonce::from_bytes(&n))
617				.collect::<Result<Vec<_>, _>>()?,
618			VtxoId::from_bytes(&v.input_vtxo_id)?,
619			v.outputs.into_iter()
620				.map(|output| ArkoorDestination::try_from(output))
621				.collect::<Result<Vec<_>, _>>()?,
622			v.isolated_outputs.into_iter()
623				.map(|output| ArkoorDestination::try_from(output))
624				.collect::<Result<Vec<_>, _>>()?,
625			v.use_checkpoint,
626			ArkoorCosignAttestation::deserialize(&v.attestation)
627				.map_err(|_| "Failed to parse attestation")?,
628		);
629		Ok(req)
630	}
631}
632
633impl<V: VtxoRef> From<ArkoorPackageCosignRequest<V>> for protos::ArkoorPackageCosignRequest {
634	fn from(v: ArkoorPackageCosignRequest<V>) -> Self {
635		Self {
636			parts: v.requests.into_iter().map(|p| p.into()).collect(),
637		}
638	}
639}
640
641impl<'a> TryFrom<protos::ArkoorPackageCosignRequest> for ArkoorPackageCosignRequest<VtxoId> {
642	type Error = ConvertError;
643
644	fn try_from(v: protos::ArkoorPackageCosignRequest) -> Result<Self, Self::Error> {
645		Ok(Self {
646			requests: v.parts.into_iter().map(|p| p.try_into()).collect::<Result<Vec<_>, _>>()?,
647		})
648	}
649}
650
651impl<'a> TryFrom<protos::LightningPayHtlcCosignRequest> for ArkoorPackageCosignRequest<VtxoId> {
652	type Error = ConvertError;
653
654	fn try_from(v: protos::LightningPayHtlcCosignRequest) -> Result<Self, Self::Error> {
655		Ok(Self {
656			requests: v.parts.into_iter().map(|p| p.try_into()).collect::<Result<Vec<_>, _>>()?,
657		})
658	}
659}
660
661impl From<ArkoorCosignResponse> for protos::ArkoorCosignResponse {
662	fn from(v: ArkoorCosignResponse) -> Self {
663		Self {
664			server_pub_nonces: v.server_pub_nonces.into_iter().map(|p| p.serialize().to_vec()).collect::<Vec<_>>(),
665			server_partial_sigs: v.server_partial_sigs.into_iter().map(|p| p.serialize().to_vec()).collect::<Vec<_>>(),
666		}
667	}
668}
669
670impl TryFrom<protos::ArkoorCosignResponse> for ArkoorCosignResponse {
671	type Error = ConvertError;
672	fn try_from(v: protos::ArkoorCosignResponse) -> Result<Self, Self::Error> {
673		Ok(Self {
674			server_pub_nonces: v.server_pub_nonces.into_iter().map(|n| musig::PublicNonce::from_bytes(&n)).collect::<Result<Vec<_>, _>>()?,
675			server_partial_sigs: v.server_partial_sigs.into_iter().map(|n| musig::PartialSignature::from_bytes(&n)).collect::<Result<Vec<_>, _>>()?,
676		})
677	}
678}
679
680impl From<ArkoorPackageCosignResponse> for protos::ArkoorPackageCosignResponse {
681	fn from(v: ArkoorPackageCosignResponse) -> Self {
682		Self {
683			parts: v.responses.into_iter().map(|p| p.into()).collect::<Vec<_>>(),
684		}
685	}
686}
687
688impl TryFrom<protos::ArkoorPackageCosignResponse> for ArkoorPackageCosignResponse {
689	type Error = ConvertError;
690	fn try_from(v: protos::ArkoorPackageCosignResponse) -> Result<Self, Self::Error> {
691		Ok(Self {
692			responses: v.parts.into_iter().map(|p| p.try_into()).collect::<Result<Vec<_>, _>>()?,
693		})
694	}
695}
696
697impl From<LeafVtxoCosignRequest> for protos::LeafVtxoCosignRequest {
698	fn from(v: LeafVtxoCosignRequest) -> Self {
699		protos::LeafVtxoCosignRequest {
700			vtxo_id: v.vtxo_id.to_bytes().to_vec(),
701			public_nonce: v.pub_nonce.serialize().to_vec(),
702		}
703	}
704}
705
706impl From<LeafVtxoCosignResponse> for protos::LeafVtxoCosignResponse {
707	fn from(v: LeafVtxoCosignResponse) -> Self {
708		protos::LeafVtxoCosignResponse {
709			public_nonce: v.public_nonce.serialize().to_vec(),
710			partial_signature: v.partial_signature.serialize().to_vec(),
711		}
712	}
713}
714
715impl TryFrom<protos::LeafVtxoCosignResponse> for LeafVtxoCosignResponse {
716	type Error = ConvertError;
717
718	fn try_from(v: protos::LeafVtxoCosignResponse) -> Result<Self, Self::Error> {
719		Ok(Self {
720			public_nonce: TryFromBytes::from_bytes(v.public_nonce)?,
721			partial_signature: TryFromBytes::from_bytes(v.partial_signature)?,
722		})
723	}
724}
725
726impl<V: Borrow<OffboardRequest>> From<V> for protos::OffboardRequest {
727	fn from(v: V) -> Self {
728		let v = v.borrow();
729	    protos::OffboardRequest {
730			offboard_spk: v.script_pubkey.to_bytes(),
731			net_amount_sat: v.net_amount.to_sat(),
732			deduct_fees_from_gross_amount: v.deduct_fees_from_gross_amount,
733			fee_rate_kwu: v.fee_rate.to_sat_per_kwu(),
734		}
735	}
736}
737
738impl TryFrom<protos::OffboardRequest> for OffboardRequest {
739	type Error = ConvertError;
740
741	fn try_from(v: protos::OffboardRequest) -> Result<Self, Self::Error> {
742		Ok(Self {
743			script_pubkey: ScriptBuf::from_bytes(v.offboard_spk),
744			net_amount: Amount::from_sat(v.net_amount_sat),
745			deduct_fees_from_gross_amount: v.deduct_fees_from_gross_amount,
746			fee_rate: FeeRate::from_sat_per_kwu(v.fee_rate_kwu),
747		})
748	}
749}
750
751#[cfg(test)]
752mod test {
753	use std::str::FromStr;
754	use bitcoin::hex::FromHex;
755	use super::*;
756
757	#[test]
758	fn test_preimage_bytes() {
759		let h = "ef2cb05d04819ddb2b9d960c7e0e295ea48ffb429712dc8f30aa48dfcc20c97e";
760		let b = Vec::<u8>::from_hex(h).unwrap();
761
762		let preimage = Preimage::from_str(h).unwrap();
763		assert_eq!(preimage, Preimage::from_bytes(&b).unwrap());
764		assert_eq!(preimage, Preimage::from_slice(&b).unwrap());
765		assert_eq!(preimage, Preimage::from_slice(&preimage.to_vec()).unwrap());
766		assert_eq!(preimage, Preimage::from_bytes(&preimage.to_vec()).unwrap());
767	}
768}