clementine_core/rpc/parser/
mod.rs1use 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)]
33pub 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#[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#[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 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 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 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}