Skip to content

Commit

Permalink
[BTC]: Add Babylon Staking UTXO
Browse files Browse the repository at this point in the history
  • Loading branch information
satoshiotomakan committed Dec 13, 2024
1 parent c0d3556 commit dc9fec7
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 13 deletions.
32 changes: 32 additions & 0 deletions rust/chains/tw_bitcoin/src/babylon/claims/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,36 @@ impl StakingSpendInfo {
.or_tw_err(SigningErrorType::Error_internal)
.context("No merkle root of the Babylon Staking transaction spend info")
}

pub fn timelock_script(&self) -> &Script {
&self.timelock_script
}

pub fn unbonding_script(&self) -> &Script {
&self.unbonding_script
}

pub fn slashing_script(&self) -> &Script {
&self.slashing_script
}

pub fn timelock_control_block(&self) -> SigningResult<bitcoin::taproot::ControlBlock> {
self.control_block(&self.timelock_script)
}

pub fn unbonding_control_block(&self) -> SigningResult<bitcoin::taproot::ControlBlock> {
self.control_block(&self.unbonding_script)
}

pub fn slashing_control_block(&self) -> SigningResult<bitcoin::taproot::ControlBlock> {
self.control_block(&self.slashing_script)
}

fn control_block(&self, script: &Script) -> SigningResult<bitcoin::taproot::ControlBlock> {
let script = bitcoin::script::ScriptBuf::from_bytes(script.to_vec());
self.spend_info
.control_block(&(script, bitcoin::taproot::LeafVersion::TapScript))
.or_tw_err(SigningErrorType::Error_internal)
.context("'TaprootSpendInfo::control_block' is None")
}
}
6 changes: 6 additions & 0 deletions rust/chains/tw_bitcoin/src/babylon/tx_builder/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

pub mod output;
pub mod utxo;
55 changes: 55 additions & 0 deletions rust/chains/tw_bitcoin/src/babylon/tx_builder/utxo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use crate::babylon;
use crate::babylon::claims::UNSPENDABLE_KEY_PATH;
use tw_coin_entry::error::prelude::*;
use tw_keypair::schnorr;
use tw_utxo::transaction::standard_transaction::builder::UtxoBuilder;
use tw_utxo::transaction::standard_transaction::TransactionInput;
use tw_utxo::transaction::UtxoToSign;

/// An extension of the [`UtxoBuilder`] with Babylon BTC Staking outputs.
pub trait BabylonUtxoBuilder: Sized {
fn babylon_timelock_path(
self,
staker: &schnorr::PublicKey,
staking_locktime: u16,
finality_provider: &schnorr::PublicKey,
covenants: &[schnorr::PublicKey],
covenant_quorum: u32,
) -> SigningResult<(TransactionInput, UtxoToSign)>;
}

impl BabylonUtxoBuilder for UtxoBuilder {
fn babylon_timelock_path(
self,
staker: &schnorr::PublicKey,
staking_locktime: u16,
finality_provider: &schnorr::PublicKey,
covenants: &[schnorr::PublicKey],
covenant_quorum: u32,
) -> SigningResult<(TransactionInput, UtxoToSign)> {
let spend_info = babylon::claims::StakingSpendInfo::new(
staker,
staking_locktime,
finality_provider,
covenants,
covenant_quorum,
)?;

let control_block = spend_info.timelock_control_block()?.serialize();
let merkle_root = spend_info.merkle_root()?;
let timelock_payload = spend_info.timelock_script().clone();

self.p2tr_script_path(
// Babylon Staking or Unbonding output was created using an unspendable internal public key,
// that means taproot key spends is disabled.
&UNSPENDABLE_KEY_PATH,
timelock_payload,
control_block,
&merkle_root,
)
}
}
11 changes: 11 additions & 0 deletions rust/chains/tw_bitcoin/src/modules/tx_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
//
// Copyright © 2017 Trust Wallet.

use std::borrow::Cow;
use tw_coin_entry::error::prelude::*;
use tw_keypair::schnorr;
use tw_utxo::context::AddressPrefixes;

pub mod output_protobuf;
Expand All @@ -24,3 +27,11 @@ impl BitcoinChainInfo {
}
}
}

fn parse_schnorr_pk(bytes: &Cow<[u8]>) -> SigningResult<schnorr::PublicKey> {
schnorr::PublicKey::try_from(bytes.as_ref()).into_tw()
}

fn parse_schnorr_pks(pks: &[Cow<[u8]>]) -> SigningResult<Vec<schnorr::PublicKey>> {
pks.iter().map(parse_schnorr_pk).collect()
}
13 changes: 2 additions & 11 deletions rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
//
// Copyright © 2017 Trust Wallet.

use crate::babylon::tx_builder::BabylonOutputBuilder;
use crate::modules::tx_builder::BitcoinChainInfo;
use std::borrow::Cow;
use crate::babylon::tx_builder::output::BabylonOutputBuilder;
use crate::modules::tx_builder::{parse_schnorr_pk, parse_schnorr_pks, BitcoinChainInfo};
use std::marker::PhantomData;
use std::str::FromStr;
use tw_coin_entry::error::prelude::*;
Expand Down Expand Up @@ -277,11 +276,3 @@ impl<'a, Context: UtxoContext> OutputProtobuf<'a, Context> {
.with_context(|| format!("Expected exactly {N} bytes public key hash"))
}
}

fn parse_schnorr_pk(bytes: &Cow<[u8]>) -> SigningResult<schnorr::PublicKey> {
schnorr::PublicKey::try_from(bytes.as_ref()).into_tw()
}

fn parse_schnorr_pks(pks: &[Cow<[u8]>]) -> SigningResult<Vec<schnorr::PublicKey>> {
pks.iter().map(parse_schnorr_pk).collect()
}
33 changes: 31 additions & 2 deletions rust/chains/tw_bitcoin/src/modules/tx_builder/utxo_protobuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//
// Copyright © 2017 Trust Wallet.

use crate::babylon::tx_builder::utxo::BabylonUtxoBuilder;
use crate::modules::tx_builder::public_keys::PublicKeys;
use crate::modules::tx_builder::script_parser::{StandardScript, StandardScriptParser};
use crate::modules::tx_builder::BitcoinChainInfo;
use crate::modules::tx_builder::{parse_schnorr_pk, parse_schnorr_pks, BitcoinChainInfo};
use std::marker::PhantomData;
use std::str::FromStr;
use tw_coin_entry::error::prelude::*;
Expand Down Expand Up @@ -56,9 +57,12 @@ impl<'a, Context: UtxoContext> UtxoProtobuf<'a, Context> {
BuilderType::p2tr_key_path(ref key_path) => self.p2tr_key_path(key_path),
// BuilderType::p2tr_script_path(ref script) => self.p2tr_script_path(script),
BuilderType::brc20_inscribe(ref inscription) => self.brc20_inscribe(inscription),
BuilderType::babylon_timelock_path(ref timelock) => {
self.babylon_timelock_path(timelock)
},
BuilderType::None => SigningError::err(SigningErrorType::Error_invalid_params)
.context("No Input Builder type provided"),
_ => todo!()
_ => todo!(),
},
ScriptType::script_data(ref script) => self.custom_script(script.to_vec()),
ScriptType::receiver_address(ref address) => self.recipient_address(address),
Expand Down Expand Up @@ -131,6 +135,31 @@ impl<'a, Context: UtxoContext> UtxoProtobuf<'a, Context> {
)
}

pub fn babylon_timelock_path(
&self,
timelock: &Proto::mod_Input::BabylonStakingTimelockPath,
) -> SigningResult<(TransactionInput, UtxoToSign)> {
let staker =
parse_schnorr_pk(&timelock.staker_public_key).context("Invalid stakerPublicKey")?;
let staking_locktime: u16 = timelock
.staking_time
.try_into()
.tw_err(|_| SigningErrorType::Error_invalid_params)
.context("stakingTime cannot be greater than 65535")?;
let finality_provider = parse_schnorr_pk(&timelock.finality_provider_public_key)
.context("Invalid finalityProviderPublicKeys")?;
let covenant_committees = parse_schnorr_pks(&timelock.covenant_committee_public_keys)
.context("Invalid covenantCommitteePublicKeys")?;

self.prepare_builder()?.babylon_timelock_path(
&staker,
staking_locktime,
&finality_provider,
&covenant_committees,
timelock.covenant_committee_quorum,
)
}

pub fn custom_script(
&self,
script_data: Data,
Expand Down
8 changes: 8 additions & 0 deletions src/proto/BitcoinV2.proto
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,17 @@ message Input {
message BabylonStakingTimelockPath {
// User's public key.
bytes staker_public_key = 1;
// Finality provider's public key chosen by the user.
bytes finality_provider_public_key = 2;
// Staking Output's lock time.
// Must be expired to be spent.
uint32 staking_time = 3;
// Retrieved from global_parameters.covenant_pks.
// Babylon nodes that can approve Unbonding tx or Slash the staked position when acting bad.
repeated bytes covenant_committee_public_keys = 4;
// Retrieved from global_parameters.covenant_quorum.
// Specifies the quorum required by the covenant committee for unbonding transactions to be confirmed.
uint32 covenant_committee_quorum = 5;
}

// Unbonding path can be used to on-demand unlock `Staking Output` before the timelock expires.
Expand Down

0 comments on commit dc9fec7

Please sign in to comment.