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}