babylon_apis/
btc_staking_api.rs

1use std::str::FromStr;
2
3/// BTC staking messages / API
4/// The definitions here follow the same structure as the equivalent IBC protobuf message types,
5/// defined in `packages/proto/src/gen/babylon.btcstaking.v1.rs`
6use cosmwasm_schema::cw_serde;
7use cosmwasm_std::{Binary, Decimal, Uint128};
8
9/// Hash size in bytes
10pub const HASH_SIZE: usize = 32;
11
12#[cw_serde]
13/// btc_staking execution handlers
14pub enum ExecuteMsg {
15    /// Change the admin
16    UpdateAdmin { admin: Option<String> },
17    /// Set the BTC light client addr and BTC finality addr.
18    /// Only admin or the babylon contract can set this
19    UpdateContractAddresses {
20        btc_light_client: String,
21        finality: String,
22    },
23    /// BTC Staking operations
24    BtcStaking {
25        new_fp: Vec<NewFinalityProvider>,
26        active_del: Vec<ActiveBtcDelegation>,
27        slashed_del: Vec<SlashedBtcDelegation>,
28        unbonded_del: Vec<UnbondedBtcDelegation>,
29    },
30    /// Slash finality provider staking power.
31    /// Used by the babylon-contract only.
32    /// The Babylon contract will call this message to set the finality provider's staking power to
33    /// zero when the finality provider is found to be malicious by the finality contract.
34    Slash { fp_btc_pk_hex: String },
35    /// Message sent by the finality contract, to distribute rewards to delegators.
36    DistributeRewards {
37        /// List of finality providers and their rewards.
38        fp_distribution: Vec<RewardInfo>,
39    },
40    /// Message sent by anyone on behalf of the staker, to withdraw rewards from BTC staking via the given FP.
41    WithdrawRewards {
42        /// Both the address to claim and receive the rewards.
43        /// It's a Babylon address. If rewards are to be sent to a Consumer address, the
44        /// staker's equivalent address in that chain will be computed and used.
45        staker_addr: String,
46        fp_pubkey_hex: String,
47    },
48}
49
50#[cw_serde]
51pub struct RewardInfo {
52    pub fp_pubkey_hex: String,
53    pub reward: Uint128,
54}
55
56#[cw_serde]
57pub struct NewFinalityProvider {
58    /// Description terms for the finality provider.
59    pub description: Option<FinalityProviderDescription>,
60    /// Commission rate of the finality provider.
61    pub commission: Decimal,
62    /// Bech32 address identifier of the finality provider.
63    pub addr: String,
64    /// Bitcoin secp256k1 PK of this finality provider
65    /// the PK follows encoding in BIP-340 spec in hex format
66    pub btc_pk_hex: String,
67    /// Proof of possession of the babylon_pk and btc_pk
68    pub pop: Option<ProofOfPossessionBtc>,
69    /// ID of the consumer that the finality provider is operating on.
70    pub consumer_id: String,
71}
72
73impl TryFrom<&babylon_proto::babylon::btcstaking::v1::NewFinalityProvider> for NewFinalityProvider {
74    type Error = String;
75
76    fn try_from(
77        fp: &babylon_proto::babylon::btcstaking::v1::NewFinalityProvider,
78    ) -> Result<Self, Self::Error> {
79        Ok(NewFinalityProvider {
80            description: fp
81                .description
82                .as_ref()
83                .map(|d| FinalityProviderDescription {
84                    moniker: d.moniker.clone(),
85                    identity: d.identity.clone(),
86                    website: d.website.clone(),
87                    security_contact: d.security_contact.clone(),
88                    details: d.details.clone(),
89                }),
90            commission: Decimal::from_str(&fp.commission)
91                .map_err(|e| format!("Failed to parse commission: {}", e))?,
92            addr: fp.addr.clone(),
93            btc_pk_hex: fp.btc_pk_hex.clone(),
94            pop: fp.pop.as_ref().map(|pop| ProofOfPossessionBtc {
95                btc_sig_type: pop.btc_sig_type,
96                btc_sig: pop.btc_sig.to_vec().into(),
97            }),
98            consumer_id: fp.consumer_id.clone(),
99        })
100    }
101}
102
103#[cw_serde]
104pub struct FinalityProvider {
105    /// Description terms for the finality provider.
106    pub description: Option<FinalityProviderDescription>,
107    /// Commission rate of the finality provider.
108    pub commission: Decimal,
109    /// Bech32 address identifier of the finality provider.
110    pub addr: String,
111    /// Bitcoin secp256k1 PK of this finality provider
112    /// the PK follows encoding in BIP-340 spec in hex format
113    pub btc_pk_hex: String,
114    /// Proof of possession of the babylon_pk and btc_pk.
115    pub pop: Option<ProofOfPossessionBtc>,
116    /// Height on which the finality provider is slashed.
117    pub slashed_height: u64,
118    /// BTC height on which the finality provider is slashed.
119    pub slashed_btc_height: u32,
120    /// ID of the consumer that the finality provider is operating on.
121    pub consumer_id: String,
122}
123
124impl From<&NewFinalityProvider> for FinalityProvider {
125    fn from(new_fp: &NewFinalityProvider) -> Self {
126        FinalityProvider {
127            description: new_fp.description.clone(),
128            commission: new_fp.commission,
129            addr: new_fp.addr.clone(),
130            btc_pk_hex: new_fp.btc_pk_hex.clone(),
131            pop: new_fp.pop.clone(),
132            slashed_height: 0,
133            slashed_btc_height: 0,
134            consumer_id: new_fp.consumer_id.clone(),
135        }
136    }
137}
138
139#[cw_serde]
140pub struct FinalityProviderDescription {
141    /// Name of the finality provider.
142    pub moniker: String,
143    /// Identity of the finality provider.
144    pub identity: String,
145    /// Website of the finality provider.
146    pub website: String,
147    /// Security contact of the finality provider.
148    pub security_contact: String,
149    /// Details of the finality provider.
150    pub details: String,
151}
152
153impl FinalityProviderDescription {
154    /// Description field lengths
155    pub const MAX_MONIKER_LENGTH: usize = 70;
156    pub const MAX_IDENTITY_LENGTH: usize = 3000;
157    pub const MAX_WEBSITE_LENGTH: usize = 140;
158    pub const MAX_SECURITY_CONTACT_LENGTH: usize = 140;
159    pub const MAX_DETAILS_LENGTH: usize = 280;
160}
161
162/// Indicates the type of btc_sig in a pop
163#[cw_serde]
164pub enum BTCSigType {
165    /// BIP340 means the btc_sig will follow the BIP-340 encoding
166    BIP340 = 0,
167    /// BIP322 means the btc_sig will follow the BIP-322 encoding
168    BIP322 = 1,
169    /// ECDSA means the btc_sig will follow the ECDSA encoding
170    /// ref: https://github.com/okx/js-wallet-sdk/blob/a57c2acbe6ce917c0aa4e951d96c4e562ad58444/packages/coin-bitcoin/src/BtcWallet.ts#L331
171    ECDSA = 2,
172}
173
174impl TryFrom<i32> for BTCSigType {
175    type Error = String;
176
177    fn try_from(value: i32) -> Result<Self, Self::Error> {
178        match value {
179            0 => Ok(BTCSigType::BIP340),
180            1 => Ok(BTCSigType::BIP322),
181            2 => Ok(BTCSigType::ECDSA),
182            _ => Err(format!("Invalid BTCSigType value: {}", value)),
183        }
184    }
185}
186
187/// Proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are
188/// held by the same person.
189#[cw_serde]
190pub struct ProofOfPossessionBtc {
191    /// Type of `btc_sig` in the pop.
192    pub btc_sig_type: i32,
193    /// Signature generated via sign(sk_btc, babylon_sig).
194    ///
195    /// The signature follows encoding in either BIP-340 spec or BIP-322 spec.
196    pub btc_sig: Binary,
197}
198
199/// Represents the status of a delegation.
200/// The state transition path is PENDING -> ACTIVE -> UNBONDED with two possibilities:
201///     1. The typical path when time-lock of staking transaction expires.
202///     2. The path when staker requests an early undelegation through a BtcStaking
203///     UnbondedBtcDelegation message.
204#[cw_serde]
205pub enum BTCDelegationStatus {
206    /// A delegation waiting for covenant signatures to become active
207    PENDING = 0,
208    /// A delegation that has voting power
209    ACTIVE = 1,
210    /// A delegation that no longer has voting power:
211    /// - Either reaching the end of staking transaction time-lock.
212    /// - Or by receiving an unbonding tx with signatures from staker and covenant committee
213    UNBONDED = 2,
214    /// ANY is any of the status above
215    ANY = 3,
216}
217
218/// Message sent when a BTC delegation newly receives covenant signatures and thus becomes active.
219#[cw_serde]
220pub struct ActiveBtcDelegation {
221    /// Address to receive rewards from BTC delegation.
222    pub staker_addr: String,
223    /// Bitcoin secp256k1 PK of the BTC delegator.
224    /// The PK follows encoding in BIP-340 spec in hex format
225    pub btc_pk_hex: String,
226    /// List of BIP-340 PKs of the finality providers that
227    /// this BTC delegation delegates to
228    pub fp_btc_pk_list: Vec<String>,
229    /// Start BTC height of the BTC delegation.
230    /// It is the start BTC height of the time-lock
231    pub start_height: u32,
232    /// End height of the BTC delegation.
233    /// It is the end BTC height of the time-lock - w
234    pub end_height: u32,
235    /// Total BTC stakes in this delegation, quantified in satoshi
236    pub total_sat: u64,
237    /// Staking tx.
238    pub staking_tx: Binary,
239    /// Slashing tx.
240    pub slashing_tx: Binary,
241    /// Signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk) as string hex.
242    /// It will be a part of the witness for the staking tx output.
243    pub delegator_slashing_sig: Binary,
244    /// List of adaptor signatures on the slashing tx by each covenant member.
245    /// It will be a part of the witness for the staking tx output.
246    pub covenant_sigs: Vec<CovenantAdaptorSignatures>,
247    /// Index of the staking output in the staking tx
248    pub staking_output_idx: u32,
249    /// Used in unbonding output time-lock path and in slashing transactions change outputs
250    pub unbonding_time: u32,
251    /// Undelegation info of this delegation.
252    pub undelegation_info: BtcUndelegationInfo,
253    /// Params version used to validate the delegation
254    pub params_version: u32,
255}
256
257impl TryFrom<&babylon_proto::babylon::btcstaking::v1::ActiveBtcDelegation> for ActiveBtcDelegation {
258    type Error = String;
259
260    fn try_from(
261        d: &babylon_proto::babylon::btcstaking::v1::ActiveBtcDelegation,
262    ) -> Result<Self, Self::Error> {
263        let delegator_unbonding_info = if let Some(info) = d
264            .undelegation_info
265            .clone()
266            .unwrap()
267            .delegator_unbonding_info
268        {
269            Some(DelegatorUnbondingInfo {
270                spend_stake_tx: Binary::new(info.spend_stake_tx.to_vec()),
271            })
272        } else {
273            None
274        };
275
276        Ok(ActiveBtcDelegation {
277            staker_addr: d.staker_addr.clone(),
278            btc_pk_hex: d.btc_pk_hex.clone(),
279            fp_btc_pk_list: d.fp_btc_pk_list.clone(),
280            start_height: d.start_height,
281            end_height: d.end_height,
282            total_sat: d.total_sat,
283            staking_tx: d.staking_tx.to_vec().into(),
284            slashing_tx: d.slashing_tx.to_vec().into(),
285            delegator_slashing_sig: d.delegator_slashing_sig.to_vec().into(),
286            covenant_sigs: d
287                .covenant_sigs
288                .iter()
289                .map(|s| CovenantAdaptorSignatures {
290                    cov_pk: s.cov_pk.to_vec().into(),
291                    adaptor_sigs: s.adaptor_sigs.iter().map(|a| a.to_vec().into()).collect(),
292                })
293                .collect(),
294            staking_output_idx: d.staking_output_idx,
295            unbonding_time: d.unbonding_time,
296            undelegation_info: d
297                .undelegation_info
298                .as_ref()
299                .map(|ui| BtcUndelegationInfo {
300                    unbonding_tx: ui.unbonding_tx.to_vec().into(),
301                    delegator_unbonding_info,
302                    covenant_unbonding_sig_list: ui
303                        .covenant_unbonding_sig_list
304                        .iter()
305                        .map(|s| SignatureInfo {
306                            pk: s.pk.to_vec().into(),
307                            sig: s.sig.to_vec().into(),
308                        })
309                        .collect(),
310                    slashing_tx: ui.slashing_tx.to_vec().into(),
311                    delegator_slashing_sig: ui.delegator_slashing_sig.to_vec().into(),
312                    covenant_slashing_sigs: ui
313                        .covenant_slashing_sigs
314                        .iter()
315                        .map(|s| CovenantAdaptorSignatures {
316                            cov_pk: s.cov_pk.to_vec().into(),
317                            adaptor_sigs: s
318                                .adaptor_sigs
319                                .iter()
320                                .map(|a| a.to_vec().into())
321                                .collect(),
322                        })
323                        .collect(),
324                })
325                .ok_or("undelegation info not set")?,
326            params_version: d.params_version,
327        })
328    }
329}
330
331/// Represents a list adaptor signatures signed by the
332/// covenant with different finality provider's public keys as encryption keys
333#[cw_serde]
334pub struct CovenantAdaptorSignatures {
335    /// Public key of the covenant emulator, used as the public key of the adaptor signature
336    pub cov_pk: Binary,
337    /// List of adaptor signatures, each encrypted by a restaked BTC finality provider's public key
338    pub adaptor_sigs: Vec<Binary>,
339}
340
341/// Provides all necessary info about the undelegation.
342#[cw_serde]
343pub struct BtcUndelegationInfo {
344    /// Transaction which will transfer the funds from staking
345    /// output to unbonding output. Unbonding output will usually have lower timelock
346    /// than staking output.
347    pub unbonding_tx: Binary,
348    /// Unbonding slashing tx
349    pub slashing_tx: Binary,
350    /// Signature on the slashing tx
351    /// by the delegator (i.e. SK corresponding to btc_pk).
352    /// It will be a part of the witness for the unbonding tx output.
353    pub delegator_slashing_sig: Binary,
354    /// List of adaptor signatures on the
355    /// unbonding slashing tx by each covenant member
356    /// It will be a part of the witness for the staking tx output.
357    pub covenant_slashing_sigs: Vec<CovenantAdaptorSignatures>,
358    /// List of signatures on the unbonding tx
359    /// by covenant members
360    pub covenant_unbonding_sig_list: Vec<SignatureInfo>,
361    /// Information about transaction which spent
362    /// the staking output
363    pub delegator_unbonding_info: Option<DelegatorUnbondingInfo>,
364}
365
366#[cw_serde]
367pub struct DelegatorUnbondingInfo {
368    pub spend_stake_tx: Binary,
369}
370
371/// A BIP-340 signature together with its signer's BIP-340 PK.
372#[cw_serde]
373pub struct SignatureInfo {
374    pub pk: Binary,
375    pub sig: Binary,
376}
377
378/// A packet sent from Babylon to the Consumer chain about a slashed BTC
379/// delegation re-staked to >=1 of the Consumer chain's finality providers
380#[cw_serde]
381pub struct SlashedBtcDelegation {
382    /// Staking tx hash of the BTC delegation. It uniquely identifies a BTC delegation
383    pub staking_tx_hash: String,
384    /// Extracted BTC SK of the finality provider on this Consumer chain.
385    pub recovered_fp_btc_sk: String,
386}
387
388/// Sent from Babylon to the Consumer chain upon an early unbonded BTC
389/// delegation.
390#[cw_serde]
391pub struct UnbondedBtcDelegation {
392    /// Staking tx hash of the BTC delegation. It uniquely identifies a BTC delegation
393    pub staking_tx_hash: String,
394    /// Signature on the unbonding tx signed by the BTC delegator
395    /// It proves that the BTC delegator wants to unbond
396    pub unbonding_tx_sig: Binary,
397}
398
399#[cw_serde]
400pub enum SudoMsg {
401    /// The SDK should call SudoMsg::BeginBlock{} once per block (in BeginBlock).
402    /// It allows the staking module to index the BTC height, and update the power
403    /// distribution of Finality Providers.
404    BeginBlock {
405        hash_hex: String,
406        app_hash_hex: String,
407    },
408}