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        })
113    }
114}
115
116impl TryFrom<Outpoint> for OutPoint {
117    type Error = BridgeError;
118
119    fn try_from(value: Outpoint) -> Result<Self, Self::Error> {
120        let hash = match Hash::from_slice(
121            &value
122                .txid
123                .ok_or(eyre::eyre!("Can't convert empty txid"))?
124                .txid,
125        ) {
126            Ok(h) => h,
127            Err(e) => return Err(BridgeError::FromSliceError(e)),
128        };
129
130        Ok(OutPoint {
131            txid: Txid::from_raw_hash(hash),
132            vout: value.vout,
133        })
134    }
135}
136impl From<OutPoint> for Outpoint {
137    fn from(value: OutPoint) -> Self {
138        Outpoint {
139            txid: Some(value.txid.into()),
140            vout: value.vout,
141        }
142    }
143}
144
145impl TryFrom<WinternitzPubkey> for winternitz::PublicKey {
146    type Error = BridgeError;
147
148    fn try_from(value: WinternitzPubkey) -> Result<Self, Self::Error> {
149        let inner = value.digit_pubkey;
150
151        // Add reasonable size limit per key
152        if inner.len() > MAX_WINTERNITZ_DIGITS_PER_KEY {
153            return Err(BridgeError::Parser(ParserError::RPCParamOversized(
154                "digit_pubkey".to_string(),
155                inner.len(),
156            )));
157        }
158
159        // Add total memory limit check
160        let total_bytes = inner.len() * 20;
161        if total_bytes > MAX_BYTES_PER_WINTERNITZ_KEY {
162            return Err(BridgeError::Parser(ParserError::RPCParamOversized(
163                "digit_pubkey".to_string(),
164                inner.len(),
165            )));
166        }
167
168        inner
169            .into_iter()
170            .enumerate()
171            .map(|(i, inner_vec)| {
172                inner_vec
173                    .try_into()
174                    .map_err(|e: Vec<_>| eyre::eyre!("Incorrect length {:?}, expected 20", e.len()))
175                    .wrap_err_with(|| ParserError::RPCParamMalformed(format!("digit_pubkey.[{i}]")))
176            })
177            .collect::<Result<Vec<[u8; 20]>, eyre::Report>>()
178            .map_err(Into::into)
179    }
180}
181
182impl From<FeePayingType> for FeeType {
183    fn from(value: FeePayingType) -> Self {
184        match value {
185            FeePayingType::CPFP => FeeType::Cpfp,
186            FeePayingType::RBF => FeeType::Rbf,
187            FeePayingType::NoFunding => FeeType::NoFunding,
188        }
189    }
190}
191
192impl TryFrom<FeeType> for FeePayingType {
193    type Error = Status;
194
195    fn try_from(value: FeeType) -> Result<Self, Self::Error> {
196        match value {
197            FeeType::Cpfp => Ok(FeePayingType::CPFP),
198            FeeType::Rbf => Ok(FeePayingType::RBF),
199            FeeType::NoFunding => Ok(FeePayingType::NoFunding),
200            _ => Err(Status::invalid_argument("Invalid FeeType variant")),
201        }
202    }
203}
204
205impl TryFrom<SchnorrSig> for Signature {
206    type Error = BridgeError;
207
208    fn try_from(value: SchnorrSig) -> Result<Self, Self::Error> {
209        Signature::from_slice(&value.schnorr_sig)
210            .wrap_err("Failed to parse schnorr signature")
211            .wrap_err_with(|| ParserError::RPCParamMalformed("schnorr_sig".to_string()))
212            .map_err(Into::into)
213    }
214}
215impl From<winternitz::PublicKey> for WinternitzPubkey {
216    fn from(value: winternitz::PublicKey) -> Self {
217        {
218            let digit_pubkey = value.into_iter().map(|inner| inner.to_vec()).collect();
219
220            WinternitzPubkey { digit_pubkey }
221        }
222    }
223}
224
225impl From<DepositInfo> for clementine::Deposit {
226    fn from(value: DepositInfo) -> Self {
227        clementine::Deposit {
228            deposit_outpoint: Some(value.deposit_outpoint.into()),
229            deposit_data: Some(value.deposit_type.into()),
230        }
231    }
232}
233
234impl TryFrom<clementine::Deposit> for DepositInfo {
235    type Error = Status;
236
237    fn try_from(value: clementine::Deposit) -> Result<Self, Self::Error> {
238        let deposit_outpoint: OutPoint = value
239            .deposit_outpoint
240            .ok_or_else(|| Status::invalid_argument("No deposit outpoint received"))?
241            .try_into()?;
242
243        let deposit_type = value
244            .deposit_data
245            .ok_or_else(|| Status::invalid_argument("No deposit data received"))?
246            .try_into()?;
247
248        Ok(DepositInfo {
249            deposit_outpoint,
250            deposit_type,
251        })
252    }
253}
254
255impl From<DepositData> for DepositParams {
256    fn from(value: DepositData) -> Self {
257        let actors: clementine::Actors = value.actors.into();
258        let security_council: clementine::SecurityCouncil = value.security_council.into();
259        let deposit: clementine::Deposit = value.deposit.into();
260
261        DepositParams {
262            deposit: Some(deposit),
263            actors: Some(actors),
264            security_council: Some(security_council),
265        }
266    }
267}
268
269impl TryFrom<DepositParams> for DepositData {
270    type Error = Status;
271
272    fn try_from(value: DepositParams) -> Result<Self, Self::Error> {
273        let deposit: DepositInfo = value
274            .deposit
275            .ok_or(Status::invalid_argument("No deposit received"))?
276            .try_into()?;
277        let actors: Actors = value
278            .actors
279            .ok_or(Status::invalid_argument("No actors received"))?
280            .try_into()?;
281
282        let security_council: SecurityCouncil = value
283            .security_council
284            .ok_or(Status::invalid_argument("No security council received"))?
285            .try_into()?;
286
287        Ok(DepositData {
288            nofn_xonly_pk: None,
289            deposit,
290            actors,
291            security_council,
292        })
293    }
294}
295
296impl TryFrom<clementine::deposit::DepositData> for DepositType {
297    type Error = Status;
298
299    fn try_from(value: clementine::deposit::DepositData) -> Result<Self, Self::Error> {
300        match value {
301            clementine::deposit::DepositData::BaseDeposit(data) => {
302                Ok(DepositType::BaseDeposit(BaseDepositData {
303                    evm_address: data.evm_address.try_into().map_err(|e| {
304                        Status::invalid_argument(format!(
305                            "Failed to convert evm_address to EVMAddress: {e}",
306                        ))
307                    })?,
308                    recovery_taproot_address: data
309                        .recovery_taproot_address
310                        .parse::<bitcoin::Address<_>>()
311                        .map_err(|e| Status::internal(e.to_string()))?,
312                }))
313            }
314            clementine::deposit::DepositData::ReplacementDeposit(data) => {
315                Ok(DepositType::ReplacementDeposit(ReplacementDepositData {
316                    old_move_txid: data
317                        .old_move_txid
318                        .ok_or(Status::invalid_argument("No move_txid received"))?
319                        .try_into().map_err(|e| {
320                            Status::invalid_argument(format!(
321                                "Failed to convert replacement deposit move_txid to bitcoin::Txid: {e}",
322                            ))
323                        })?,
324                }))
325            }
326        }
327    }
328}
329
330impl From<DepositType> for clementine::deposit::DepositData {
331    fn from(value: DepositType) -> Self {
332        match value {
333            DepositType::BaseDeposit(data) => {
334                clementine::deposit::DepositData::BaseDeposit(clementine::BaseDeposit {
335                    evm_address: data.evm_address.0.to_vec(),
336                    recovery_taproot_address: data
337                        .recovery_taproot_address
338                        .assume_checked()
339                        .to_string(),
340                })
341            }
342            DepositType::ReplacementDeposit(data) => {
343                clementine::deposit::DepositData::ReplacementDeposit(
344                    clementine::ReplacementDeposit {
345                        old_move_txid: Some(data.old_move_txid.into()),
346                    },
347                )
348            }
349        }
350    }
351}
352
353impl TryFrom<clementine::XOnlyPublicKeys> for Vec<XOnlyPublicKey> {
354    type Error = Status;
355
356    fn try_from(value: clementine::XOnlyPublicKeys) -> Result<Self, Self::Error> {
357        value
358            .xonly_public_keys
359            .iter()
360            .map(|pk| {
361                XOnlyPublicKey::from_slice(pk).map_err(|e| {
362                    Status::invalid_argument(format!("Failed to parse xonly public key: {e}"))
363                })
364            })
365            .collect::<Result<Vec<_>, _>>()
366    }
367}
368
369impl From<Vec<XOnlyPublicKey>> for clementine::XOnlyPublicKeys {
370    fn from(value: Vec<XOnlyPublicKey>) -> Self {
371        clementine::XOnlyPublicKeys {
372            xonly_public_keys: value.iter().map(|pk| pk.serialize().to_vec()).collect(),
373        }
374    }
375}
376
377impl TryFrom<clementine::Actors> for Actors {
378    type Error = Status;
379
380    fn try_from(value: clementine::Actors) -> Result<Self, Self::Error> {
381        let verifiers = value
382            .verifiers
383            .ok_or(Status::invalid_argument("No verifiers received"))?
384            .try_into()?;
385        let watchtowers = value
386            .watchtowers
387            .ok_or(Status::invalid_argument("No watchtowers received"))?
388            .try_into()?;
389        let operators = value
390            .operators
391            .ok_or(Status::invalid_argument("No operators received"))?
392            .try_into()?;
393
394        Ok(Actors {
395            verifiers,
396            watchtowers,
397            operators,
398        })
399    }
400}
401
402impl From<Actors> for clementine::Actors {
403    fn from(value: Actors) -> Self {
404        clementine::Actors {
405            verifiers: Some(value.verifiers.into()),
406            watchtowers: Some(value.watchtowers.into()),
407            operators: Some(value.operators.into()),
408        }
409    }
410}
411
412impl From<SecurityCouncil> for clementine::SecurityCouncil {
413    fn from(value: SecurityCouncil) -> Self {
414        clementine::SecurityCouncil {
415            pks: value
416                .pks
417                .into_iter()
418                .map(|pk| pk.serialize().to_vec())
419                .collect(),
420            threshold: value.threshold,
421        }
422    }
423}
424
425impl TryFrom<clementine::SecurityCouncil> for SecurityCouncil {
426    type Error = Status;
427
428    fn try_from(value: clementine::SecurityCouncil) -> Result<Self, Self::Error> {
429        let pks = value
430            .pks
431            .into_iter()
432            .map(|pk| {
433                XOnlyPublicKey::from_slice(&pk).map_err(|e| {
434                    Status::invalid_argument(format!("Failed to parse xonly public key: {e}"))
435                })
436            })
437            .collect::<Result<Vec<_>, _>>()?;
438
439        Ok(SecurityCouncil {
440            pks,
441            threshold: value.threshold,
442        })
443    }
444}
445
446impl TryFrom<RawSignedTx> for bitcoin::Transaction {
447    type Error = Status;
448
449    fn try_from(value: RawSignedTx) -> Result<Self, Self::Error> {
450        bitcoin::consensus::encode::deserialize(&value.raw_tx)
451            .map_err(|e| Status::invalid_argument(format!("Failed to parse raw signed tx: {e}")))
452    }
453}
454
455impl From<&bitcoin::Transaction> for RawSignedTx {
456    fn from(value: &bitcoin::Transaction) -> Self {
457        RawSignedTx {
458            raw_tx: bitcoin::consensus::encode::serialize(value),
459        }
460    }
461}
462
463impl From<Txid> for clementine::Txid {
464    fn from(value: Txid) -> Self {
465        clementine::Txid {
466            txid: value.to_byte_array().to_vec(),
467        }
468    }
469}
470impl TryFrom<clementine::Txid> for Txid {
471    type Error = FromSliceError;
472
473    fn try_from(value: clementine::Txid) -> Result<Self, Self::Error> {
474        Ok(Txid::from_raw_hash(sha256d::Hash::from_slice(&value.txid)?))
475    }
476}
477
478#[allow(clippy::result_large_err)]
479impl TryFrom<TransactionRequest> for TransactionRequestData {
480    type Error = Status;
481
482    fn try_from(request: TransactionRequest) -> Result<Self, Self::Error> {
483        let deposit_outpoint: OutPoint = request
484            .deposit_outpoint
485            .ok_or(Status::invalid_argument("No deposit params received"))?
486            .try_into()?;
487
488        let kickoff_id = request
489            .kickoff_id
490            .ok_or(Status::invalid_argument("No kickoff params received"))?;
491
492        Ok(TransactionRequestData {
493            deposit_outpoint,
494            kickoff_data: kickoff_id.try_into()?,
495        })
496    }
497}
498
499impl From<TransactionRequestData> for TransactionRequest {
500    fn from(value: TransactionRequestData) -> Self {
501        TransactionRequest {
502            deposit_outpoint: Some(value.deposit_outpoint.into()),
503            kickoff_id: Some(value.kickoff_data.into()),
504        }
505    }
506}
507
508impl TryFrom<clementine::KickoffId> for crate::deposit::KickoffData {
509    type Error = Status;
510
511    fn try_from(value: clementine::KickoffId) -> Result<Self, Self::Error> {
512        let operator_xonly_pk =
513            XOnlyPublicKey::from_slice(&value.operator_xonly_pk).map_err(|e| {
514                Status::invalid_argument(format!("Failed to parse operator_xonly_pk: {e}"))
515            })?;
516
517        Ok(crate::deposit::KickoffData {
518            operator_xonly_pk,
519            round_idx: RoundIndex::from_index(value.round_idx as usize),
520            kickoff_idx: value.kickoff_idx,
521        })
522    }
523}
524
525impl From<crate::deposit::KickoffData> for clementine::KickoffId {
526    fn from(value: crate::deposit::KickoffData) -> Self {
527        clementine::KickoffId {
528            operator_xonly_pk: value.operator_xonly_pk.serialize().to_vec(),
529            round_idx: value.round_idx.to_index() as u32,
530            kickoff_idx: value.kickoff_idx,
531        }
532    }
533}
534
535impl From<Vec<(TransactionType, Transaction)>> for SignedTxsWithType {
536    fn from(value: Vec<(TransactionType, Transaction)>) -> Self {
537        SignedTxsWithType {
538            signed_txs: value
539                .into_iter()
540                .map(|(tx_type, signed_tx)| SignedTxWithType {
541                    transaction_type: Some(tx_type.into()),
542                    raw_tx: bitcoin::consensus::serialize(&signed_tx),
543                })
544                .collect(),
545        }
546    }
547}
548
549impl TryFrom<SignedTxWithType> for (TransactionType, Transaction) {
550    type Error = Status;
551
552    fn try_from(value: SignedTxWithType) -> Result<Self, Self::Error> {
553        Ok((
554            value
555                .transaction_type
556                .ok_or(Status::invalid_argument("No transaction type received"))?
557                .try_into()
558                .map_err(|e| {
559                    Status::invalid_argument(format!("Failed to parse transaction type: {e}"))
560                })?,
561            bitcoin::consensus::encode::deserialize(&value.raw_tx).map_err(|e| {
562                Status::invalid_argument(format!("Failed to parse raw signed tx: {e}"))
563            })?,
564        ))
565    }
566}
567
568impl TryFrom<clementine::SignedTxsWithType> for Vec<(TransactionType, Transaction)> {
569    type Error = Status;
570
571    fn try_from(value: clementine::SignedTxsWithType) -> Result<Self, Self::Error> {
572        value
573            .signed_txs
574            .into_iter()
575            .map(|signed_tx| signed_tx.try_into())
576            .collect::<Result<Vec<_>, _>>()
577    }
578}
579
580#[cfg(test)]
581mod tests {
582    use crate::rpc::clementine::{self, Outpoint, WinternitzPubkey};
583    use bitcoin::{hashes::Hash, OutPoint, Txid};
584    use bitvm::signatures::winternitz;
585
586    #[test]
587    fn from_bitcoin_outpoint_to_proto_outpoint() {
588        let og_outpoint = OutPoint {
589            txid: Txid::from_raw_hash(Hash::from_slice(&[0x1F; 32]).unwrap()),
590            vout: 0x45,
591        };
592
593        let proto_outpoint: Outpoint = og_outpoint.into();
594        let bitcoin_outpoint: OutPoint = proto_outpoint.try_into().unwrap();
595        assert_eq!(og_outpoint, bitcoin_outpoint);
596
597        let proto_outpoint = Outpoint {
598            txid: Some(clementine::Txid {
599                txid: vec![0x1F; 32],
600            }),
601            vout: 0x45,
602        };
603        let bitcoin_outpoint: OutPoint = proto_outpoint.try_into().unwrap();
604        assert_eq!(og_outpoint, bitcoin_outpoint);
605    }
606
607    #[test]
608    fn from_proto_outpoint_to_bitcoin_outpoint() {
609        let og_outpoint = Outpoint {
610            txid: Some(clementine::Txid {
611                txid: vec![0x1F; 32],
612            }),
613            vout: 0x45,
614        };
615
616        let bitcoin_outpoint: OutPoint = og_outpoint.clone().try_into().unwrap();
617        let proto_outpoint: Outpoint = bitcoin_outpoint.into();
618        assert_eq!(og_outpoint, proto_outpoint);
619
620        let bitcoin_outpoint = OutPoint {
621            txid: Txid::from_raw_hash(Hash::from_slice(&[0x1F; 32]).unwrap()),
622            vout: 0x45,
623        };
624        let proto_outpoint: Outpoint = bitcoin_outpoint.into();
625        assert_eq!(og_outpoint, proto_outpoint);
626    }
627
628    #[test]
629    fn from_proto_winternitz_public_key_to_bitvm() {
630        let og_wpk = vec![[0x45u8; 20]];
631
632        let rpc_wpk: WinternitzPubkey = og_wpk.clone().into();
633        let rpc_converted_wpk: winternitz::PublicKey =
634            rpc_wpk.try_into().expect("encoded wpk has to be valid");
635        assert_eq!(og_wpk, rpc_converted_wpk);
636    }
637
638    #[test]
639    fn from_txid_to_proto_txid() {
640        let og_txid = Txid::from_raw_hash(Hash::from_slice(&[0x1F; 32]).unwrap());
641
642        let rpc_txid: clementine::Txid = og_txid.into();
643        let rpc_converted_txid: Txid = rpc_txid.try_into().unwrap();
644        assert_eq!(og_txid, rpc_converted_txid);
645    }
646}