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