clementine_core/rpc/
ecdsa_verification_sig.rs

1//! # ECDSA Verification Signature
2//!
3//! This module contains the ECDSA verification signature for the Clementine protocol.
4//! It is for additional verification that the request for optimistic payout and withdrawal is coming from the aggregator, which
5//! is the owner of the address in operator/verifiers config.
6//!
7//! The address who signed the signature is retrieved by calculating the EIP-712 hash of the withdrawal params.
8//! The address is then compared to the address in the config.
9//!
10
11use alloy::primitives::PrimitiveSignature;
12use alloy::sol_types::Eip712Domain;
13use bitcoin::hashes::Hash;
14use bitcoin::{taproot, OutPoint};
15use bitcoin::{Amount, ScriptBuf};
16use eyre::{Context, Result};
17
18use crate::errors::BridgeError;
19
20alloy_sol_types::sol! {
21    #[derive(Debug)]
22    struct OptimisticPayoutMessage {
23        uint32 withdrawal_id;
24        bytes input_signature;
25        bytes32 input_outpoint_txid;
26        uint32 input_outpoint_vout;
27        bytes output_script_pubkey;
28        uint64 output_amount;
29    }
30
31    #[derive(Debug)]
32    struct OperatorWithdrawalMessage  {
33        uint32 withdrawal_id;
34        bytes input_signature;
35        bytes32 input_outpoint_txid;
36        uint32 input_outpoint_vout;
37        bytes output_script_pubkey;
38        uint64 output_amount;
39    }
40}
41
42pub static CLEMENTINE_EIP712_DOMAIN: Eip712Domain = alloy_sol_types::eip712_domain! {
43    name: "ClementineVerification",
44    version: "1",
45};
46
47pub trait WithdrawalMessage {
48    fn new(
49        deposit_id: u32,
50        input_signature: taproot::Signature,
51        input_outpoint: OutPoint,
52        output_script_pubkey: ScriptBuf,
53        output_amount: Amount,
54    ) -> Self;
55}
56
57impl WithdrawalMessage for OptimisticPayoutMessage {
58    fn new(
59        deposit_id: u32,
60        input_signature: taproot::Signature,
61        input_outpoint: OutPoint,
62        output_script_pubkey: ScriptBuf,
63        output_amount: Amount,
64    ) -> Self {
65        OptimisticPayoutMessage {
66            withdrawal_id: deposit_id,
67            input_signature: input_signature.serialize().to_vec().into(),
68            input_outpoint_txid: input_outpoint.txid.to_byte_array().into(),
69            input_outpoint_vout: input_outpoint.vout,
70            output_script_pubkey: output_script_pubkey.as_bytes().to_vec().into(),
71            output_amount: output_amount.to_sat(),
72        }
73    }
74}
75
76impl WithdrawalMessage for OperatorWithdrawalMessage {
77    fn new(
78        deposit_id: u32,
79        input_signature: taproot::Signature,
80        input_outpoint: OutPoint,
81        output_script_pubkey: ScriptBuf,
82        output_amount: Amount,
83    ) -> Self {
84        OperatorWithdrawalMessage {
85            withdrawal_id: deposit_id,
86            input_signature: input_signature.serialize().to_vec().into(),
87            input_outpoint_txid: input_outpoint.txid.to_byte_array().into(),
88            input_outpoint_vout: input_outpoint.vout,
89            output_script_pubkey: output_script_pubkey.as_bytes().to_vec().into(),
90            output_amount: output_amount.to_sat(),
91        }
92    }
93}
94
95/// Recover the address from the signature
96/// EIP712 hash is calculated from optimistic payout params
97/// Signature is the signature of the eip712 hash
98///
99/// Parameters:
100/// - deposit_id: The id of the deposit
101/// - input_signature: The signature of the withdrawal input
102/// - input_outpoint: The outpoint of the withdrawal input
103/// - output_script_pubkey: The script pubkey of the withdrawal output
104/// - output_amount: The amount of the withdrawal output
105/// - signature: The signature of the eip712 hash of the withdrawal params
106///
107/// Returns:
108/// - The address recovered from the signature
109pub fn recover_address_from_ecdsa_signature<M: WithdrawalMessage + alloy_sol_types::SolStruct>(
110    deposit_id: u32,
111    input_signature: taproot::Signature,
112    input_outpoint: OutPoint,
113    output_script_pubkey: ScriptBuf,
114    output_amount: Amount,
115    signature: PrimitiveSignature,
116) -> Result<alloy::primitives::Address, BridgeError> {
117    let params = M::new(
118        deposit_id,
119        input_signature,
120        input_outpoint,
121        output_script_pubkey,
122        output_amount,
123    );
124
125    let eip712_hash = params.eip712_signing_hash(&CLEMENTINE_EIP712_DOMAIN);
126
127    let address = signature
128        .recover_address_from_prehash(&eip712_hash)
129        .wrap_err("Invalid signature")?;
130    Ok(address)
131}