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 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 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
592impl<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
610impl 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}