1use super::clementine::{
2 self, DepositParams, FeeType, Outpoint, RawSignedTx, RbfSigningInfoRpc, SchnorrSig,
3 TransactionRequest, WinternitzPubkey,
4};
5use super::error;
6use crate::builder::transaction::sign::TransactionRequestData;
7use crate::builder::transaction::TransactionType;
8use crate::constants::{MAX_BYTES_PER_WINTERNITZ_KEY, MAX_WINTERNITZ_DIGITS_PER_KEY};
9use crate::deposit::{
10 Actors, BaseDepositData, DepositData, DepositInfo, DepositType, ReplacementDepositData,
11 SecurityCouncil,
12};
13use crate::errors::BridgeError;
14use crate::operator::RoundIndex;
15use crate::rpc::clementine::{SignedTxWithType, SignedTxsWithType};
16use crate::utils::{FeePayingType, RbfSigningInfo};
17use bitcoin::hashes::{sha256d, FromSliceError, Hash};
18use bitcoin::secp256k1::schnorr::Signature;
19use bitcoin::{OutPoint, TapNodeHash, Transaction, Txid, XOnlyPublicKey};
20use bitvm::signatures::winternitz;
21use eyre::Context;
22use std::fmt::{Debug, Display};
23use std::num::TryFromIntError;
24use tonic::Status;
25
26pub mod operator;
27pub mod verifier;
28
29#[derive(Debug, Clone, thiserror::Error)]
30pub enum ParserError {
31 #[error("RPC function field {0} is required")]
33 RPCRequiredParam(&'static str),
34 #[error("RPC function parameter {0} is malformed")]
35 RPCParamMalformed(String),
36 #[error("RPC function parameter {0} is oversized: {1}")]
37 RPCParamOversized(String, usize),
38}
39
40impl From<ParserError> for tonic::Status {
41 fn from(value: ParserError) -> Self {
42 match value {
43 ParserError::RPCRequiredParam(field) => {
44 Status::invalid_argument(format!("RPC function field {field} is required."))
45 }
46 ParserError::RPCParamMalformed(field) => {
47 Status::invalid_argument(format!("RPC function parameter {field} is malformed."))
48 }
49 ParserError::RPCParamOversized(field, size) => Status::invalid_argument(format!(
50 "RPC function parameter {field} is oversized: {size}",
51 )),
52 }
53 }
54}
55
56#[allow(dead_code)]
57#[allow(clippy::result_large_err)]
58pub fn convert_int_to_another<SOURCE, TARGET>(
61 field_name: &str,
62 value: SOURCE,
63 try_from: fn(SOURCE) -> Result<TARGET, TryFromIntError>,
64) -> Result<TARGET, Status>
65where
66 SOURCE: Copy + Debug + Display,
67{
68 try_from(value)
69 .map_err(|e| error::invalid_argument(field_name, "Given number is out of bounds")(e))
70}
71
72#[macro_export]
85macro_rules! fetch_next_message_from_stream {
86 ($stream:expr, $field:ident) => {
87 $crate::fetch_next_optional_message_from_stream!($stream, $field).ok_or(
88 $crate::rpc::error::expected_msg_got_none(stringify!($field))(),
89 )
90 };
91}
92
93#[macro_export]
105macro_rules! fetch_next_optional_message_from_stream {
106 ($stream:expr, $field:ident) => {
107 $stream.message().await?.and_then(|msg| msg.$field)
108 };
109}
110
111impl From<RbfSigningInfo> for RbfSigningInfoRpc {
112 fn from(value: RbfSigningInfo) -> Self {
113 RbfSigningInfoRpc {
114 merkle_root: value
115 .tweak_merkle_root
116 .map_or(vec![], |root| root.to_byte_array().to_vec()),
117 vout: value.vout,
118 }
119 }
120}
121
122impl TryFrom<RbfSigningInfoRpc> for RbfSigningInfo {
123 type Error = BridgeError;
124
125 fn try_from(value: RbfSigningInfoRpc) -> Result<Self, Self::Error> {
126 Ok(RbfSigningInfo {
127 tweak_merkle_root: if value.merkle_root.is_empty() {
128 None
129 } else {
130 Some(
131 TapNodeHash::from_slice(&value.merkle_root).wrap_err(eyre::eyre!(
132 "Failed to convert merkle root bytes from rpc to TapNodeHash"
133 ))?,
134 )
135 },
136 vout: value.vout,
137 #[cfg(test)]
138 annex: None,
139 #[cfg(test)]
140 additional_taproot_output_count: None,
141 })
142 }
143}
144
145impl TryFrom<Outpoint> for OutPoint {
146 type Error = BridgeError;
147
148 fn try_from(value: Outpoint) -> Result<Self, Self::Error> {
149 let hash = match Hash::from_slice(
150 &value
151 .txid
152 .ok_or(eyre::eyre!("Can't convert empty txid"))?
153 .txid,
154 ) {
155 Ok(h) => h,
156 Err(e) => return Err(BridgeError::FromSliceError(e)),
157 };
158
159 Ok(OutPoint {
160 txid: Txid::from_raw_hash(hash),
161 vout: value.vout,
162 })
163 }
164}
165impl From<OutPoint> for Outpoint {
166 fn from(value: OutPoint) -> Self {
167 Outpoint {
168 txid: Some(value.txid.into()),
169 vout: value.vout,
170 }
171 }
172}
173
174impl TryFrom<WinternitzPubkey> for winternitz::PublicKey {
175 type Error = BridgeError;
176
177 fn try_from(value: WinternitzPubkey) -> Result<Self, Self::Error> {
178 let inner = value.digit_pubkey;
179
180 if inner.len() > MAX_WINTERNITZ_DIGITS_PER_KEY {
182 return Err(BridgeError::Parser(ParserError::RPCParamOversized(
183 "digit_pubkey".to_string(),
184 inner.len(),
185 )));
186 }
187
188 let total_bytes = inner.len() * 20;
190 if total_bytes > MAX_BYTES_PER_WINTERNITZ_KEY {
191 return Err(BridgeError::Parser(ParserError::RPCParamOversized(
192 "digit_pubkey".to_string(),
193 inner.len(),
194 )));
195 }
196
197 inner
198 .into_iter()
199 .enumerate()
200 .map(|(i, inner_vec)| {
201 inner_vec
202 .try_into()
203 .map_err(|e: Vec<_>| eyre::eyre!("Incorrect length {:?}, expected 20", e.len()))
204 .wrap_err_with(|| ParserError::RPCParamMalformed(format!("digit_pubkey.[{i}]")))
205 })
206 .collect::<Result<Vec<[u8; 20]>, eyre::Report>>()
207 .map_err(Into::into)
208 }
209}
210
211impl From<FeePayingType> for FeeType {
212 fn from(value: FeePayingType) -> Self {
213 match value {
214 FeePayingType::CPFP => FeeType::Cpfp,
215 FeePayingType::RBF => FeeType::Rbf,
216 FeePayingType::NoFunding => FeeType::NoFunding,
217 }
218 }
219}
220
221impl TryFrom<FeeType> for FeePayingType {
222 type Error = Status;
223
224 fn try_from(value: FeeType) -> Result<Self, Self::Error> {
225 match value {
226 FeeType::Cpfp => Ok(FeePayingType::CPFP),
227 FeeType::Rbf => Ok(FeePayingType::RBF),
228 FeeType::NoFunding => Ok(FeePayingType::NoFunding),
229 _ => Err(Status::invalid_argument("Invalid FeeType variant")),
230 }
231 }
232}
233
234impl TryFrom<SchnorrSig> for Signature {
235 type Error = BridgeError;
236
237 fn try_from(value: SchnorrSig) -> Result<Self, Self::Error> {
238 Signature::from_slice(&value.schnorr_sig)
239 .wrap_err("Failed to parse schnorr signature")
240 .wrap_err_with(|| ParserError::RPCParamMalformed("schnorr_sig".to_string()))
241 .map_err(Into::into)
242 }
243}
244impl From<winternitz::PublicKey> for WinternitzPubkey {
245 fn from(value: winternitz::PublicKey) -> Self {
246 {
247 let digit_pubkey = value.into_iter().map(|inner| inner.to_vec()).collect();
248
249 WinternitzPubkey { digit_pubkey }
250 }
251 }
252}
253
254impl From<DepositInfo> for clementine::Deposit {
255 fn from(value: DepositInfo) -> Self {
256 clementine::Deposit {
257 deposit_outpoint: Some(value.deposit_outpoint.into()),
258 deposit_data: Some(value.deposit_type.into()),
259 }
260 }
261}
262
263impl TryFrom<clementine::Deposit> for DepositInfo {
264 type Error = Status;
265
266 fn try_from(value: clementine::Deposit) -> Result<Self, Self::Error> {
267 let deposit_outpoint: OutPoint = value
268 .deposit_outpoint
269 .ok_or_else(|| Status::invalid_argument("No deposit outpoint received"))?
270 .try_into()?;
271
272 let deposit_type = value
273 .deposit_data
274 .ok_or_else(|| Status::invalid_argument("No deposit data received"))?
275 .try_into()?;
276
277 Ok(DepositInfo {
278 deposit_outpoint,
279 deposit_type,
280 })
281 }
282}
283
284impl From<DepositData> for DepositParams {
285 fn from(value: DepositData) -> Self {
286 let actors: clementine::Actors = value.actors.into();
287 let security_council: clementine::SecurityCouncil = value.security_council.into();
288 let deposit: clementine::Deposit = value.deposit.into();
289
290 DepositParams {
291 deposit: Some(deposit),
292 actors: Some(actors),
293 security_council: Some(security_council),
294 }
295 }
296}
297
298impl TryFrom<DepositParams> for DepositData {
299 type Error = Status;
300
301 fn try_from(value: DepositParams) -> Result<Self, Self::Error> {
302 let deposit: DepositInfo = value
303 .deposit
304 .ok_or(Status::invalid_argument("No deposit received"))?
305 .try_into()?;
306 let actors: Actors = value
307 .actors
308 .ok_or(Status::invalid_argument("No actors received"))?
309 .try_into()?;
310
311 let security_council: SecurityCouncil = value
312 .security_council
313 .ok_or(Status::invalid_argument("No security council received"))?
314 .try_into()?;
315
316 Ok(DepositData {
317 nofn_xonly_pk: None,
318 deposit,
319 actors,
320 security_council,
321 })
322 }
323}
324
325impl TryFrom<clementine::deposit::DepositData> for DepositType {
326 type Error = Status;
327
328 fn try_from(value: clementine::deposit::DepositData) -> Result<Self, Self::Error> {
329 match value {
330 clementine::deposit::DepositData::BaseDeposit(data) => {
331 Ok(DepositType::BaseDeposit(BaseDepositData {
332 evm_address: data.evm_address.try_into().map_err(|e| {
333 Status::invalid_argument(format!(
334 "Failed to convert evm_address to EVMAddress: {e}",
335 ))
336 })?,
337 recovery_taproot_address: data
338 .recovery_taproot_address
339 .parse::<bitcoin::Address<_>>()
340 .map_err(|e| Status::internal(e.to_string()))?,
341 }))
342 }
343 clementine::deposit::DepositData::ReplacementDeposit(data) => {
344 Ok(DepositType::ReplacementDeposit(ReplacementDepositData {
345 old_move_txid: data
346 .old_move_txid
347 .ok_or(Status::invalid_argument("No move_txid received"))?
348 .try_into().map_err(|e| {
349 Status::invalid_argument(format!(
350 "Failed to convert replacement deposit move_txid to bitcoin::Txid: {e}",
351 ))
352 })?,
353 }))
354 }
355 }
356 }
357}
358
359impl From<DepositType> for clementine::deposit::DepositData {
360 fn from(value: DepositType) -> Self {
361 match value {
362 DepositType::BaseDeposit(data) => {
363 clementine::deposit::DepositData::BaseDeposit(clementine::BaseDeposit {
364 evm_address: data.evm_address.0.to_vec(),
365 recovery_taproot_address: data
366 .recovery_taproot_address
367 .assume_checked()
368 .to_string(),
369 })
370 }
371 DepositType::ReplacementDeposit(data) => {
372 clementine::deposit::DepositData::ReplacementDeposit(
373 clementine::ReplacementDeposit {
374 old_move_txid: Some(data.old_move_txid.into()),
375 },
376 )
377 }
378 }
379 }
380}
381
382impl TryFrom<clementine::XOnlyPublicKeys> for Vec<XOnlyPublicKey> {
383 type Error = Status;
384
385 fn try_from(value: clementine::XOnlyPublicKeys) -> Result<Self, Self::Error> {
386 value
387 .xonly_public_keys
388 .iter()
389 .map(|pk| {
390 XOnlyPublicKey::from_slice(pk).map_err(|e| {
391 Status::invalid_argument(format!("Failed to parse xonly public key: {e}"))
392 })
393 })
394 .collect::<Result<Vec<_>, _>>()
395 }
396}
397
398impl From<Vec<XOnlyPublicKey>> for clementine::XOnlyPublicKeys {
399 fn from(value: Vec<XOnlyPublicKey>) -> Self {
400 clementine::XOnlyPublicKeys {
401 xonly_public_keys: value.iter().map(|pk| pk.serialize().to_vec()).collect(),
402 }
403 }
404}
405
406impl TryFrom<clementine::Actors> for Actors {
407 type Error = Status;
408
409 fn try_from(value: clementine::Actors) -> Result<Self, Self::Error> {
410 let verifiers = value
411 .verifiers
412 .ok_or(Status::invalid_argument("No verifiers received"))?
413 .try_into()?;
414 let watchtowers = value
415 .watchtowers
416 .ok_or(Status::invalid_argument("No watchtowers received"))?
417 .try_into()?;
418 let operators = value
419 .operators
420 .ok_or(Status::invalid_argument("No operators received"))?
421 .try_into()?;
422
423 Ok(Actors {
424 verifiers,
425 watchtowers,
426 operators,
427 })
428 }
429}
430
431impl From<Actors> for clementine::Actors {
432 fn from(value: Actors) -> Self {
433 clementine::Actors {
434 verifiers: Some(value.verifiers.into()),
435 watchtowers: Some(value.watchtowers.into()),
436 operators: Some(value.operators.into()),
437 }
438 }
439}
440
441impl From<SecurityCouncil> for clementine::SecurityCouncil {
442 fn from(value: SecurityCouncil) -> Self {
443 clementine::SecurityCouncil {
444 pks: value
445 .pks
446 .into_iter()
447 .map(|pk| pk.serialize().to_vec())
448 .collect(),
449 threshold: value.threshold,
450 }
451 }
452}
453
454impl TryFrom<clementine::SecurityCouncil> for SecurityCouncil {
455 type Error = Status;
456
457 fn try_from(value: clementine::SecurityCouncil) -> Result<Self, Self::Error> {
458 let pks = value
459 .pks
460 .into_iter()
461 .map(|pk| {
462 XOnlyPublicKey::from_slice(&pk).map_err(|e| {
463 Status::invalid_argument(format!("Failed to parse xonly public key: {e}"))
464 })
465 })
466 .collect::<Result<Vec<_>, _>>()?;
467
468 Ok(SecurityCouncil {
469 pks,
470 threshold: value.threshold,
471 })
472 }
473}
474
475impl TryFrom<RawSignedTx> for bitcoin::Transaction {
476 type Error = Status;
477
478 fn try_from(value: RawSignedTx) -> Result<Self, Self::Error> {
479 bitcoin::consensus::encode::deserialize(&value.raw_tx)
480 .map_err(|e| Status::invalid_argument(format!("Failed to parse raw signed tx: {e}")))
481 }
482}
483
484impl From<&bitcoin::Transaction> for RawSignedTx {
485 fn from(value: &bitcoin::Transaction) -> Self {
486 RawSignedTx {
487 raw_tx: bitcoin::consensus::encode::serialize(value),
488 }
489 }
490}
491
492impl From<Txid> for clementine::Txid {
493 fn from(value: Txid) -> Self {
494 clementine::Txid {
495 txid: value.to_byte_array().to_vec(),
496 }
497 }
498}
499impl TryFrom<clementine::Txid> for Txid {
500 type Error = FromSliceError;
501
502 fn try_from(value: clementine::Txid) -> Result<Self, Self::Error> {
503 Ok(Txid::from_raw_hash(sha256d::Hash::from_slice(&value.txid)?))
504 }
505}
506
507#[allow(clippy::result_large_err)]
508impl TryFrom<TransactionRequest> for TransactionRequestData {
509 type Error = Status;
510
511 fn try_from(request: TransactionRequest) -> Result<Self, Self::Error> {
512 let deposit_outpoint: OutPoint = request
513 .deposit_outpoint
514 .ok_or(Status::invalid_argument("No deposit params received"))?
515 .try_into()?;
516
517 let kickoff_id = request
518 .kickoff_id
519 .ok_or(Status::invalid_argument("No kickoff params received"))?;
520
521 Ok(TransactionRequestData {
522 deposit_outpoint,
523 kickoff_data: kickoff_id.try_into()?,
524 })
525 }
526}
527
528impl From<TransactionRequestData> for TransactionRequest {
529 fn from(value: TransactionRequestData) -> Self {
530 TransactionRequest {
531 deposit_outpoint: Some(value.deposit_outpoint.into()),
532 kickoff_id: Some(value.kickoff_data.into()),
533 }
534 }
535}
536
537impl TryFrom<clementine::KickoffId> for crate::deposit::KickoffData {
538 type Error = Status;
539
540 fn try_from(value: clementine::KickoffId) -> Result<Self, Self::Error> {
541 let operator_xonly_pk =
542 XOnlyPublicKey::from_slice(&value.operator_xonly_pk).map_err(|e| {
543 Status::invalid_argument(format!("Failed to parse operator_xonly_pk: {e}"))
544 })?;
545
546 Ok(crate::deposit::KickoffData {
547 operator_xonly_pk,
548 round_idx: RoundIndex::from_index(value.round_idx as usize),
549 kickoff_idx: value.kickoff_idx,
550 })
551 }
552}
553
554impl From<crate::deposit::KickoffData> for clementine::KickoffId {
555 fn from(value: crate::deposit::KickoffData) -> Self {
556 clementine::KickoffId {
557 operator_xonly_pk: value.operator_xonly_pk.serialize().to_vec(),
558 round_idx: value.round_idx.to_index() as u32,
559 kickoff_idx: value.kickoff_idx,
560 }
561 }
562}
563
564impl From<Vec<(TransactionType, Transaction)>> for SignedTxsWithType {
565 fn from(value: Vec<(TransactionType, Transaction)>) -> Self {
566 SignedTxsWithType {
567 signed_txs: value
568 .into_iter()
569 .map(|(tx_type, signed_tx)| SignedTxWithType {
570 transaction_type: Some(tx_type.into()),
571 raw_tx: bitcoin::consensus::serialize(&signed_tx),
572 })
573 .collect(),
574 }
575 }
576}
577
578impl TryFrom<SignedTxWithType> for (TransactionType, Transaction) {
579 type Error = Status;
580
581 fn try_from(value: SignedTxWithType) -> Result<Self, Self::Error> {
582 Ok((
583 value
584 .transaction_type
585 .ok_or(Status::invalid_argument("No transaction type received"))?
586 .try_into()
587 .map_err(|e| {
588 Status::invalid_argument(format!("Failed to parse transaction type: {e}"))
589 })?,
590 bitcoin::consensus::encode::deserialize(&value.raw_tx).map_err(|e| {
591 Status::invalid_argument(format!("Failed to parse raw signed tx: {e}"))
592 })?,
593 ))
594 }
595}
596
597impl TryFrom<clementine::SignedTxsWithType> for Vec<(TransactionType, Transaction)> {
598 type Error = Status;
599
600 fn try_from(value: clementine::SignedTxsWithType) -> Result<Self, Self::Error> {
601 value
602 .signed_txs
603 .into_iter()
604 .map(|signed_tx| signed_tx.try_into())
605 .collect::<Result<Vec<_>, _>>()
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use crate::rpc::clementine::{self, Outpoint, WinternitzPubkey};
612 use bitcoin::{hashes::Hash, OutPoint, Txid};
613 use bitvm::signatures::winternitz;
614
615 #[test]
616 fn from_bitcoin_outpoint_to_proto_outpoint() {
617 let og_outpoint = OutPoint {
618 txid: Txid::from_raw_hash(Hash::from_slice(&[0x1F; 32]).unwrap()),
619 vout: 0x45,
620 };
621
622 let proto_outpoint: Outpoint = og_outpoint.into();
623 let bitcoin_outpoint: OutPoint = proto_outpoint.try_into().unwrap();
624 assert_eq!(og_outpoint, bitcoin_outpoint);
625
626 let proto_outpoint = Outpoint {
627 txid: Some(clementine::Txid {
628 txid: vec![0x1F; 32],
629 }),
630 vout: 0x45,
631 };
632 let bitcoin_outpoint: OutPoint = proto_outpoint.try_into().unwrap();
633 assert_eq!(og_outpoint, bitcoin_outpoint);
634 }
635
636 #[test]
637 fn from_proto_outpoint_to_bitcoin_outpoint() {
638 let og_outpoint = Outpoint {
639 txid: Some(clementine::Txid {
640 txid: vec![0x1F; 32],
641 }),
642 vout: 0x45,
643 };
644
645 let bitcoin_outpoint: OutPoint = og_outpoint.clone().try_into().unwrap();
646 let proto_outpoint: Outpoint = bitcoin_outpoint.into();
647 assert_eq!(og_outpoint, proto_outpoint);
648
649 let bitcoin_outpoint = OutPoint {
650 txid: Txid::from_raw_hash(Hash::from_slice(&[0x1F; 32]).unwrap()),
651 vout: 0x45,
652 };
653 let proto_outpoint: Outpoint = bitcoin_outpoint.into();
654 assert_eq!(og_outpoint, proto_outpoint);
655 }
656
657 #[test]
658 fn from_proto_winternitz_public_key_to_bitvm() {
659 let og_wpk = vec![[0x45u8; 20]];
660
661 let rpc_wpk: WinternitzPubkey = og_wpk.clone().into();
662 let rpc_converted_wpk: winternitz::PublicKey =
663 rpc_wpk.try_into().expect("encoded wpk has to be valid");
664 assert_eq!(og_wpk, rpc_converted_wpk);
665 }
666
667 #[test]
668 fn from_txid_to_proto_txid() {
669 let og_txid = Txid::from_raw_hash(Hash::from_slice(&[0x1F; 32]).unwrap());
670
671 let rpc_txid: clementine::Txid = og_txid.into();
672 let rpc_converted_txid: Txid = rpc_txid.try_into().unwrap();
673 assert_eq!(og_txid, rpc_converted_txid);
674 }
675}