clementine_core/rpc/parser/
operator.rs1use crate::{
2 citrea::CitreaClientT,
3 errors::BridgeError,
4 fetch_next_message_from_stream,
5 operator::Operator,
6 rpc::{
7 clementine::{
8 operator_params, DepositParams, DepositSignSession, OperatorConfig, OperatorParams,
9 Outpoint, SchnorrSig, WithdrawParams, XOnlyPublicKeyRpc,
10 },
11 error::{self, expected_msg_got_none},
12 },
13};
14use bitcoin::{
15 address::NetworkUnchecked, secp256k1::schnorr::Signature, taproot, Address, Amount, OutPoint,
16 ScriptBuf, TapSighashType, XOnlyPublicKey,
17};
18use bitvm::signatures::winternitz;
19use eyre::Context;
20use std::str::FromStr;
21use tonic::Status;
22
23impl<C> From<Operator<C>> for OperatorParams
24where
25 C: CitreaClientT,
26{
27 fn from(operator: Operator<C>) -> Self {
28 let operator_config = OperatorConfig {
29 collateral_funding_outpoint: Some(Outpoint {
30 txid: Some(operator.collateral_funding_outpoint.txid.into()),
31 vout: operator.collateral_funding_outpoint.vout,
32 }),
33 xonly_pk: operator.signer.xonly_public_key.to_string(),
34 wallet_reimburse_address: operator.reimburse_addr.to_string(),
35 };
36
37 OperatorParams {
38 response: Some(operator_params::Response::OperatorDetails(operator_config)),
39 }
40 }
41}
42
43impl From<winternitz::PublicKey> for OperatorParams {
44 fn from(winternitz_pubkey: winternitz::PublicKey) -> Self {
45 OperatorParams {
46 response: Some(operator_params::Response::WinternitzPubkeys(
47 winternitz_pubkey.into(),
48 )),
49 }
50 }
51}
52
53impl From<Signature> for OperatorParams {
54 fn from(sig: Signature) -> Self {
55 OperatorParams {
56 response: Some(operator_params::Response::UnspentKickoffSig(SchnorrSig {
57 schnorr_sig: sig.serialize().to_vec(),
58 })),
59 }
60 }
61}
62
63impl TryFrom<DepositSignSession> for DepositParams {
64 type Error = Status;
65
66 fn try_from(deposit_sign_session: DepositSignSession) -> Result<Self, Self::Error> {
67 match deposit_sign_session.deposit_params {
68 Some(deposit_params) => Ok(deposit_params),
69 None => Err(expected_msg_got_none("Deposit Params")()),
70 }
71 }
72}
73
74impl From<XOnlyPublicKey> for XOnlyPublicKeyRpc {
75 fn from(xonly_public_key: XOnlyPublicKey) -> Self {
76 XOnlyPublicKeyRpc {
77 xonly_public_key: xonly_public_key.serialize().to_vec(),
78 }
79 }
80}
81
82impl TryFrom<XOnlyPublicKeyRpc> for XOnlyPublicKey {
83 type Error = BridgeError;
84
85 fn try_from(xonly_public_key_rpc: XOnlyPublicKeyRpc) -> Result<Self, Self::Error> {
86 Ok(
87 XOnlyPublicKey::from_slice(&xonly_public_key_rpc.xonly_public_key)
88 .wrap_err("Failed to parse XOnlyPublicKey")?,
89 )
90 }
91}
92
93pub async fn parse_details(
104 stream: &mut tonic::Streaming<OperatorParams>,
105) -> Result<(OutPoint, XOnlyPublicKey, Address<NetworkUnchecked>), Status> {
106 let operator_param = fetch_next_message_from_stream!(stream, response)?;
107
108 let operator_config =
109 if let operator_params::Response::OperatorDetails(operator_config) = operator_param {
110 operator_config
111 } else {
112 return Err(expected_msg_got_none("OperatorDetails")());
113 };
114
115 let operator_xonly_pk = XOnlyPublicKey::from_str(&operator_config.xonly_pk)
116 .map_err(|_| Status::invalid_argument("Invalid operator xonly public key".to_string()))?;
117
118 let collateral_funding_outpoint = operator_config
119 .collateral_funding_outpoint
120 .ok_or(Status::invalid_argument(
121 "Collateral funding outpoint not provided".to_string(),
122 ))?
123 .try_into()?;
124
125 let wallet_reimburse_address = Address::from_str(&operator_config.wallet_reimburse_address)
126 .map_err(|e| {
127 Status::invalid_argument(format!("Failed to parse wallet reimburse address: {e:?}"))
128 })?;
129
130 Ok((
131 collateral_funding_outpoint,
132 operator_xonly_pk,
133 wallet_reimburse_address,
134 ))
135}
136
137pub async fn parse_winternitz_public_keys(
138 stream: &mut tonic::Streaming<OperatorParams>,
139) -> Result<winternitz::PublicKey, Status> {
140 let operator_param = fetch_next_message_from_stream!(stream, response)?;
141
142 if let operator_params::Response::WinternitzPubkeys(wpk) = operator_param {
143 Ok(wpk.try_into()?)
144 } else {
145 Err(expected_msg_got_none("WinternitzPubkeys")())
146 }
147}
148
149pub async fn parse_schnorr_sig(
150 stream: &mut tonic::Streaming<OperatorParams>,
151) -> Result<Signature, Status> {
152 let operator_param = fetch_next_message_from_stream!(stream, response)?;
153
154 if let operator_params::Response::UnspentKickoffSig(wpk) = operator_param {
155 Ok(wpk.try_into()?)
156 } else {
157 Err(expected_msg_got_none("UnspentKickoffSig")())
158 }
159}
160
161#[allow(clippy::result_large_err)]
162pub fn parse_withdrawal_sig_params(
163 params: WithdrawParams,
164) -> Result<(u32, taproot::Signature, OutPoint, ScriptBuf, Amount), Status> {
165 let mut input_signature =
166 taproot::Signature::from_slice(¶ms.input_signature).map_err(|e| {
167 Status::invalid_argument(format!("Can't convert input to taproot Signature - {e}"))
168 })?;
169
170 if input_signature.sighash_type == TapSighashType::Default {
175 tracing::warn!(
176 "Input signature for withdrawal {} has sighash type default, setting to SinglePlusAnyoneCanPay", params.withdrawal_id,
177 );
178 input_signature.sighash_type = TapSighashType::SinglePlusAnyoneCanPay;
179 }
180
181 if input_signature.sighash_type != TapSighashType::SinglePlusAnyoneCanPay {
183 return Err(Status::invalid_argument(format!(
184 "Input signature has wrong sighash type, SinglePlusAnyoneCanPay expected, got {}",
185 input_signature.sighash_type
186 )));
187 }
188
189 let input_outpoint: OutPoint = params
190 .input_outpoint
191 .ok_or_else(error::input_ended_prematurely)?
192 .try_into()?;
193
194 let users_intent_script_pubkey = ScriptBuf::from_bytes(params.output_script_pubkey);
195
196 Ok((
197 params.withdrawal_id,
198 input_signature,
199 input_outpoint,
200 users_intent_script_pubkey,
201 Amount::from_sat(params.output_amount),
202 ))
203}