clementine_core/
deposit.rs

1//! This module defines the data structures related to Citrea deposits in the Clementine bridge.
2//!
3//! It includes structures for representing deposit data, actors involved (verifiers, watchtowers, operators),
4//! and security council configurations. The module also provides functionality for managing different types
5//! of deposits (base and replacement) and deriving the necessary scripts these deposits must have.
6
7use std::collections::HashSet;
8use std::sync::Arc;
9
10use crate::builder::script::{
11    BaseDepositScript, Multisig, ReplacementDepositScript, SpendableScript, TimelockScript,
12};
13use crate::builder::transaction::create_move_to_vault_txhandler;
14use crate::config::protocol::ProtocolParamset;
15use crate::errors::BridgeError;
16use crate::musig2::AggregateFromPublicKeys;
17use crate::operator::RoundIndex;
18use crate::utils::ScriptBufExt;
19use crate::EVMAddress;
20use bitcoin::address::NetworkUnchecked;
21use bitcoin::secp256k1::PublicKey;
22use bitcoin::{Address, OutPoint, Txid, XOnlyPublicKey};
23use eyre::Context;
24
25/// Data structure to represent a single kickoff utxo in an operators round tx.
26#[derive(
27    Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Ord, PartialOrd,
28)]
29pub struct KickoffData {
30    pub operator_xonly_pk: XOnlyPublicKey,
31    pub round_idx: RoundIndex,
32    pub kickoff_idx: u32,
33}
34
35/// Data structure to represent a deposit.
36/// - nofn_xonly_pk is cached to avoid recomputing it each time.
37/// - deposit includes the actual information about the deposit.
38/// - actors includes the public keys of the actors that will participate in the deposit.
39/// - security_council includes the public keys of the security council that can unlock the deposit to create a replacement deposit in case a bug is found in the bridge.
40#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq)]
41pub struct DepositData {
42    /// Cached nofn xonly public key used for deposit.
43    pub nofn_xonly_pk: Option<XOnlyPublicKey>,
44    pub deposit: DepositInfo,
45    pub actors: Actors,
46    pub security_council: SecurityCouncil,
47}
48
49impl PartialEq for DepositData {
50    fn eq(&self, other: &Self) -> bool {
51        // nofn_xonly_pk only depends on verifiers pk's so it can be ignored as verifiers are already compared
52        // for security council, order of keys matter as it will change the m of n multisig script,
53        // thus change the scriptpubkey of move to vault tx
54        self.security_council == other.security_council
55            && self.deposit.deposit_outpoint == other.deposit.deposit_outpoint
56            // for watchtowers/verifiers/operators, order doesn't matter, we compare sorted lists
57            // get() functions already return sorted lists
58            && self.get_operators() == other.get_operators()
59            && self.get_verifiers() == other.get_verifiers()
60            && self.get_watchtowers() == other.get_watchtowers()
61            && self.deposit.deposit_type == other.deposit.deposit_type
62    }
63}
64
65impl DepositData {
66    /// Returns the move to vault txid of the deposit.
67    pub fn get_move_txid(
68        &mut self,
69        paramset: &'static ProtocolParamset,
70    ) -> Result<Txid, BridgeError> {
71        Ok(*create_move_to_vault_txhandler(self, paramset)?.get_txid())
72    }
73}
74
75/// Data structure to represent the deposit outpoint and type.
76#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
77pub struct DepositInfo {
78    pub deposit_outpoint: OutPoint,
79    pub deposit_type: DepositType,
80}
81
82/// Type to represent the type of deposit, and related specific data for each type..
83#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
84pub enum DepositType {
85    BaseDeposit(BaseDepositData),
86    ReplacementDeposit(ReplacementDepositData),
87}
88
89impl DepositData {
90    /// Returns the outpoint of the deposit.
91    pub fn get_deposit_outpoint(&self) -> OutPoint {
92        self.deposit.deposit_outpoint
93    }
94    /// Returns the nofn xonly public key of the deposit. It is additionally cached to avoid recomputing it each time.
95    pub fn get_nofn_xonly_pk(&mut self) -> Result<XOnlyPublicKey, BridgeError> {
96        if let Some(pk) = self.nofn_xonly_pk {
97            return Ok(pk);
98        }
99        let verifiers = self.get_verifiers();
100        let nofn_xonly_pk = bitcoin::XOnlyPublicKey::from_musig2_pks(verifiers, None)?;
101        self.nofn_xonly_pk = Some(nofn_xonly_pk);
102        Ok(nofn_xonly_pk)
103    }
104    /// Returns the number of verifiers in the deposit.
105    pub fn get_num_verifiers(&self) -> usize {
106        self.actors.verifiers.len()
107    }
108    /// Returns the number of watchtowers in the deposit.
109    pub fn get_num_watchtowers(&self) -> usize {
110        self.get_num_verifiers() + self.actors.watchtowers.len()
111    }
112    /// Returns the index of a verifier in the deposit, in the sorted order of verifiers pk.
113    pub fn get_verifier_index(&self, public_key: &PublicKey) -> Result<usize, eyre::Report> {
114        self.get_verifiers()
115            .iter()
116            .position(|pk| pk == public_key)
117            .ok_or_else(|| eyre::eyre!("Verifier with public key {} not found", public_key))
118    }
119    /// Returns the index of a watchtower in the deposit, in the sorted order of watchtowers pk.
120    pub fn get_watchtower_index(&self, xonly_pk: &XOnlyPublicKey) -> Result<usize, eyre::Report> {
121        self.get_watchtowers()
122            .iter()
123            .position(|pk| pk == xonly_pk)
124            .ok_or_else(|| eyre::eyre!("Watchtower with xonly key {} not found", xonly_pk))
125    }
126    /// Returns the index of an operator in the deposit, in the sorted order of operators pk.
127    pub fn get_operator_index(&self, xonly_pk: XOnlyPublicKey) -> Result<usize, eyre::Report> {
128        self.get_operators()
129            .iter()
130            .position(|pk| pk == &xonly_pk)
131            .ok_or_else(|| eyre::eyre!("Operator with xonly key {} not found", xonly_pk))
132    }
133    /// Returns sorted verifiers, they are sorted so that their order is deterministic.
134    pub fn get_verifiers(&self) -> Vec<PublicKey> {
135        let mut verifiers = self.actors.verifiers.clone();
136        verifiers.sort();
137
138        verifiers
139    }
140    /// Returns sorted watchtowers, they are sorted so that their order is deterministic.
141    /// It is very important for watchtowers to be sorted, as this is the order the watchtower challenge utxo's will be
142    /// in the kickoff tx. So any change in order will change the kickoff txid's.
143    pub fn get_watchtowers(&self) -> Vec<XOnlyPublicKey> {
144        let mut watchtowers = self
145            .actors
146            .verifiers
147            .iter()
148            .map(|pk| pk.x_only_public_key().0)
149            .collect::<Vec<_>>();
150        watchtowers.extend(self.actors.watchtowers.iter());
151        watchtowers.sort();
152        watchtowers
153    }
154    /// Returns sorted operators, they are sorted so that their order is deterministic.
155    pub fn get_operators(&self) -> Vec<XOnlyPublicKey> {
156        let mut operators = self.actors.operators.clone();
157        operators.sort();
158        operators
159    }
160    /// Returns the number of operators in the deposit.
161    pub fn get_num_operators(&self) -> usize {
162        self.actors.operators.len()
163    }
164    /// Returns the scripts a taproot address of a deposit_outpoint must have to spend the deposit.
165    /// Deposits not having these scripts and corresponding taproot address should be rejected.
166    pub fn get_deposit_scripts(
167        &mut self,
168        paramset: &'static ProtocolParamset,
169    ) -> Result<Vec<Arc<dyn SpendableScript>>, BridgeError> {
170        let nofn_xonly_pk = self.get_nofn_xonly_pk()?;
171
172        match &mut self.deposit.deposit_type {
173            DepositType::BaseDeposit(original_deposit_data) => {
174                let deposit_script = Arc::new(BaseDepositScript::new(
175                    nofn_xonly_pk,
176                    original_deposit_data.evm_address,
177                ));
178
179                let recovery_script_pubkey = original_deposit_data
180                    .recovery_taproot_address
181                    .clone()
182                    .assume_checked()
183                    .script_pubkey();
184
185                let recovery_extracted_xonly_pk = recovery_script_pubkey
186                    .try_get_taproot_pk()
187                    .wrap_err("Recovery taproot address is not a valid taproot address")?;
188
189                let script_timelock = Arc::new(TimelockScript::new(
190                    Some(recovery_extracted_xonly_pk),
191                    paramset.user_takes_after,
192                ));
193
194                Ok(vec![deposit_script, script_timelock])
195            }
196            DepositType::ReplacementDeposit(replacement_deposit_data) => {
197                let deposit_script: Arc<dyn SpendableScript> =
198                    Arc::new(ReplacementDepositScript::new(
199                        nofn_xonly_pk,
200                        replacement_deposit_data.old_move_txid,
201                    ));
202                let security_council_script: Arc<dyn SpendableScript> = Arc::new(
203                    Multisig::from_security_council(self.security_council.clone()),
204                );
205
206                Ok(vec![deposit_script, security_council_script])
207            }
208        }
209    }
210
211    /// Checks if all verifiers are unique.
212    pub fn are_all_verifiers_unique(&self) -> bool {
213        let set: HashSet<_> = self.actors.verifiers.iter().collect();
214        set.len() == self.actors.verifiers.len()
215    }
216
217    /// Checks if all watchtowers are unique.
218    pub fn are_all_watchtowers_unique(&self) -> bool {
219        let set: HashSet<_> = self.get_watchtowers().into_iter().collect();
220        set.len() == self.get_num_watchtowers()
221    }
222
223    /// Checks if all operators are unique.
224    pub fn are_all_operators_unique(&self) -> bool {
225        let set: HashSet<_> = self.actors.operators.iter().collect();
226        set.len() == self.actors.operators.len()
227    }
228}
229
230/// Data structure to represent the actors public keys that participate in the deposit.
231#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
232pub struct Actors {
233    /// Public keys of verifiers that will participate in the deposit.
234    pub verifiers: Vec<PublicKey>,
235    /// X-only public keys of watchtowers that will participate in the deposit.
236    /// NOTE: verifiers are automatically considered watchtowers. This field is only for additional watchtowers.
237    pub watchtowers: Vec<XOnlyPublicKey>,
238    /// X-only public keys of operators that will participate in the deposit.
239    pub operators: Vec<XOnlyPublicKey>,
240}
241
242/// Data structure to represent the security council that can unlock the deposit using an m-of-n multisig to create a replacement deposit.
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct SecurityCouncil {
245    pub pks: Vec<XOnlyPublicKey>,
246    pub threshold: u32,
247}
248
249impl std::str::FromStr for SecurityCouncil {
250    type Err = eyre::Report;
251
252    fn from_str(s: &str) -> Result<Self, Self::Err> {
253        let mut parts = s.split(':');
254        let threshold_str = parts
255            .next()
256            .ok_or_else(|| eyre::eyre!("Missing threshold"))?;
257        let pks_str = parts
258            .next()
259            .ok_or_else(|| eyre::eyre!("Missing public keys"))?;
260
261        if parts.next().is_some() {
262            return Err(eyre::eyre!("Too many parts in security council string"));
263        }
264
265        let threshold = threshold_str
266            .parse::<u32>()
267            .map_err(|e| eyre::eyre!("Invalid threshold: {}", e))?;
268
269        let pks: Result<Vec<XOnlyPublicKey>, _> = pks_str
270            .split(',')
271            .map(|pk_str| {
272                let bytes = hex::decode(pk_str)
273                    .map_err(|e| eyre::eyre!("Invalid hex in public key: {}", e))?;
274                XOnlyPublicKey::from_slice(&bytes)
275                    .map_err(|e| eyre::eyre!("Invalid public key: {}", e))
276            })
277            .collect();
278
279        let pks = pks?;
280
281        if pks.is_empty() {
282            return Err(eyre::eyre!("No public keys provided"));
283        }
284
285        if threshold > pks.len() as u32 {
286            return Err(eyre::eyre!(
287                "Threshold cannot be greater than number of public keys"
288            ));
289        }
290
291        Ok(SecurityCouncil { pks, threshold })
292    }
293}
294
295impl serde::Serialize for SecurityCouncil {
296    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
297    where
298        S: serde::Serializer,
299    {
300        serializer.serialize_str(&self.to_string())
301    }
302}
303
304impl<'de> serde::Deserialize<'de> for SecurityCouncil {
305    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
306    where
307        D: serde::Deserializer<'de>,
308    {
309        let s = String::deserialize(deserializer)?;
310        s.parse().map_err(serde::de::Error::custom)
311    }
312}
313
314impl std::fmt::Display for SecurityCouncil {
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        write!(f, "{}:", self.threshold)?;
317        let pks_str = self
318            .pks
319            .iter()
320            .map(|pk| hex::encode(pk.serialize()))
321            .collect::<Vec<_>>()
322            .join(",");
323        write!(f, "{pks_str}")
324    }
325}
326
327/// Data structure to represent the data for a base deposit. These kinds of deposits are created by users.
328#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
329pub struct BaseDepositData {
330    /// User's EVM address.
331    pub evm_address: EVMAddress,
332    /// User's recovery taproot address.
333    pub recovery_taproot_address: bitcoin::Address<NetworkUnchecked>,
334}
335
336/// Data structure to represent the data for a replacement deposit. These kinds of deposits are created by the bridge, using
337/// security council to unlock the previous deposit and move the funds to create a new deposit. Verifiers will sign the new deposit again.
338#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
339pub struct ReplacementDepositData {
340    /// old move_to_vault txid that was replaced
341    pub old_move_txid: Txid,
342}
343
344/// Data structure to represent the data for an operator. These data is used in the tx creation so any deviation will change the tx's
345/// created by the bridge.
346#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
347pub struct OperatorData {
348    pub xonly_pk: XOnlyPublicKey,
349    pub reimburse_addr: Address,
350    pub collateral_funding_outpoint: OutPoint,
351}
352
353impl<'de> serde::Deserialize<'de> for OperatorData {
354    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
355    where
356        D: serde::Deserializer<'de>,
357    {
358        #[derive(serde::Deserialize)]
359        struct OperatorDataHelper {
360            xonly_pk: XOnlyPublicKey,
361            reimburse_addr: Address<NetworkUnchecked>,
362            collateral_funding_outpoint: OutPoint,
363        }
364
365        let helper = OperatorDataHelper::deserialize(deserializer)?;
366
367        Ok(OperatorData {
368            xonly_pk: helper.xonly_pk,
369            reimburse_addr: helper.reimburse_addr.assume_checked(),
370            collateral_funding_outpoint: helper.collateral_funding_outpoint,
371        })
372    }
373}