clementine_core/rpc/parser/
mod.rs

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