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 })
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 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 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}