clementine_core/rpc/parser/
mod.rs

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)]
35/// Converts an integer type in to another integer type. This is needed because
36/// tonic defaults to wrong integer types for some parameters.
37pub 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/// Fetches the next message from a stream which is unwrapped and encapsulated
50/// by a [`Result`].
51///
52/// # Parameters
53///
54/// - stream: [`tonic::Streaming`] typed input stream
55/// - field: Input field ident (struct member) to look in the next message
56///
57/// # Returns
58///
59/// A [`Result`] containing the next message. Will return an [`Err`] variant if
60/// stream has exhausted.
61#[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/// Fetches next message from a stream.
71///
72/// # Parameters
73///
74/// - stream: [`tonic::Streaming`] typed input stream
75/// - field: Input field ident (struct member) to look in the next message
76///
77/// # Returns
78///
79/// An [`Option`] containing the next message. Will return a [`None`] variant if
80/// stream has exhausted.
81#[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        // Add reasonable size limit per key
199        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        // Add total memory limit check
207        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}