Skip to content

Commit

Permalink
pop: verify PoP in FP registration request (#57)
Browse files Browse the repository at this point in the history
Part of #7
  • Loading branch information
SebastianElvis authored Sep 9, 2024
1 parent 21e3fc1 commit 166c3aa
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ eots = { path = "./packages/eots" }
anyhow = "1.0.82"
bitcoin = "0.31.1"
bitvec = "1"
bech32 = "0.9.1"
blst = "0.3.11"
cosmos-sdk-proto = { version = "0.19.0", default-features = false, features = [
"cosmwasm",
Expand Down
2 changes: 2 additions & 0 deletions contracts/btc-staking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub enum ContractError {
SecP256K1Error(String), // TODO: inherit errors from k256
#[error("Unauthorized")]
Unauthorized,
#[error("Failed to verify the finality provider registration request: {0}")]
FinalityProviderVerificationError(String),
#[error("Finality provider already exists: {0}")]
FinalityProviderAlreadyExists(String),
#[error("No finality providers are registered in this Consumer")]
Expand Down
1 change: 1 addition & 0 deletions contracts/btc-staking/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod finality;
mod staking;
mod validation;

pub mod contract;
pub mod error;
Expand Down
8 changes: 6 additions & 2 deletions contracts/btc-staking/src/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ use crate::state::staking::{
FP_DELEGATIONS, FP_SET, TOTAL_POWER,
};
use crate::state::BTC_HEIGHT;
use crate::validation::verify_new_fp;
use babylon_apis::btc_staking_api::{
ActiveBtcDelegation, FinalityProvider, NewFinalityProvider, SlashedBtcDelegation,
UnbondedBtcDelegation,
};

use babylon_apis::Validate;
use babylon_bindings::BabylonMsg;

Expand Down Expand Up @@ -83,11 +85,13 @@ pub fn handle_new_fp(
}
// validate the finality provider data
new_fp.validate()?;

// verify the finality provider registration request
verify_new_fp(new_fp)?;

// get DB object
let fp = FinalityProvider::from(new_fp);

// TODO: Verify proof of possession

// save to DB
FPS.save(storage, &fp.btc_pk_hex, &fp)?;
// Set its voting power to zero
Expand Down
77 changes: 77 additions & 0 deletions contracts/btc-staking/src/validation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::error::ContractError;
use babylon_apis::btc_staking_api::{BTCSigType, NewFinalityProvider, ProofOfPossessionBtc};
use babylon_bitcoin::schnorr::verify_digest;
use cosmwasm_std::CanonicalAddr;
use k256::{
schnorr::{Signature, VerifyingKey},
sha2::{Digest, Sha256},
};

/// verify_pop verifies the proof of possession of the given address.
fn verify_pop(
btc_pk: &VerifyingKey,
address: CanonicalAddr,
pop: &ProofOfPossessionBtc,
) -> Result<(), ContractError> {
// get signed msg, i.e., the hash of the canonicalised address
let address_bytes = address.as_slice();
let msg_hash: [u8; 32] = Sha256::new_with_prefix(address_bytes).finalize().into();

// verify PoP
let btc_sig_type = BTCSigType::try_from(pop.btc_sig_type)
.map_err(|e| ContractError::FinalityProviderVerificationError(e.to_string()))?;
match btc_sig_type {
BTCSigType::BIP340 => {
let pop_sig = Signature::try_from(pop.btc_sig.as_slice())
.map_err(|e| ContractError::SecP256K1Error(e.to_string()))?;
verify_digest(btc_pk, &msg_hash, &pop_sig)
.map_err(|e| ContractError::SecP256K1Error(e.to_string()))?;
}
BTCSigType::BIP322 => {
// TODO: implement BIP322 verification
return Ok(());
}
BTCSigType::ECDSA => {
// TODO: implement ECDSA verification
return Ok(());
}
}

Ok(())
}

/// verify_new_fp verifies the new finality provider data (lite version)
#[cfg(not(feature = "full-validation"))]
pub fn verify_new_fp(_new_fp: &NewFinalityProvider) -> Result<(), ContractError> {
Ok(())
}

/// verify_new_fp verifies the new finality provider data (full validation version)
#[cfg(feature = "full-validation")]
pub fn verify_new_fp(new_fp: &NewFinalityProvider) -> Result<(), ContractError> {
// get FP's PK

use babylon_apis::new_canonical_addr;
let fp_pk_bytes = hex::decode(&new_fp.btc_pk_hex)
.map_err(|e| ContractError::SecP256K1Error(e.to_string()))?;
let fp_pk = VerifyingKey::from_bytes(&fp_pk_bytes)
.map_err(|e| ContractError::SecP256K1Error(e.to_string()))?;

// get canonicalised FP address
// TODO: parameterise `bbn` prefix
let addr = new_fp.addr.clone();
let address = new_canonical_addr(&addr, "bbn")?;

// get FP's PoP
let pop = new_fp
.pop
.clone()
.ok_or(ContractError::FinalityProviderVerificationError(
"proof of possession is missing".to_string(),
))?;

// verify PoP
verify_pop(&fp_pk, address, &pop)?;

Ok(())
}
2 changes: 1 addition & 1 deletion packages/apis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition.workspace = true
[dependencies]
babylon-bitcoin = { path = "../bitcoin" }
babylon-merkle = { path = "../merkle" }

bech32 = { workspace = true }
cosmwasm-std = { workspace = true }
cosmwasm-schema = { workspace = true }
hex = { workspace = true }
Expand Down
25 changes: 25 additions & 0 deletions packages/apis/src/btc_staking_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,31 @@ impl FinalityProviderDescription {
pub const MAX_DETAILS_LENGTH: usize = 280;
}

/// BTCSigType indicates the type of btc_sig in a pop
#[cw_serde]
pub enum BTCSigType {
/// BIP340 means the btc_sig will follow the BIP-340 encoding
BIP340 = 0,
/// BIP322 means the btc_sig will follow the BIP-322 encoding
BIP322 = 1,
/// ECDSA means the btc_sig will follow the ECDSA encoding
/// ref: https://github.com/okx/js-wallet-sdk/blob/a57c2acbe6ce917c0aa4e951d96c4e562ad58444/packages/coin-bitcoin/src/BtcWallet.ts#L331
ECDSA = 2,
}

impl TryFrom<i32> for BTCSigType {
type Error = String;

fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(BTCSigType::BIP340),
1 => Ok(BTCSigType::BIP322),
2 => Ok(BTCSigType::ECDSA),
_ => Err(format!("Invalid BTCSigType value: {}", value)),
}
}
}

/// ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1
/// secret key and a Bitcoin secp256k1 secret key are held by the same
/// person
Expand Down
2 changes: 2 additions & 0 deletions packages/apis/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use cosmwasm_std::StdError;
pub enum StakingApiError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Invalid address string: {0}")]
InvalidAddressString(String),
#[error("{0}")]
HexError(#[from] FromHexError),
#[error("Staking tx hash hex string is not {0} chars long")]
Expand Down
37 changes: 36 additions & 1 deletion packages/apis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ pub mod error;
pub mod finality_api;
mod validate;

use cosmwasm_std::{Addr, Binary, CustomQuery, QueryRequest, WasmQuery};
use bech32::{FromBase32, Variant};
use cosmwasm_std::{Addr, Binary, CanonicalAddr, CustomQuery, QueryRequest, WasmQuery};

pub fn encode_raw_query<T: Into<Binary>, Q: CustomQuery>(addr: &Addr, key: T) -> QueryRequest<Q> {
WasmQuery::Raw {
Expand All @@ -13,6 +14,40 @@ pub fn encode_raw_query<T: Into<Binary>, Q: CustomQuery>(addr: &Addr, key: T) ->
.into()
}

/// new_canonical_addr converts a bech32 address to a canonical address
/// ported from cosmwasm-std/testing/mock.rs
pub fn new_canonical_addr(addr: &str, prefix: &str) -> Result<CanonicalAddr, StakingApiError> {
// decode bech32 address
let decode_result = bech32::decode(addr);
if let Err(e) = decode_result {
return Err(StakingApiError::InvalidAddressString(e.to_string()));
}
let (decoded_prefix, decoded_data, variant) = decode_result.unwrap();
// check bech32 prefix
if decoded_prefix != prefix {
return Err(StakingApiError::InvalidAddressString(
"wrong bech32 prefix".to_string(),
));
}
// check bech32 variant
if variant == Variant::Bech32m {
return Err(StakingApiError::InvalidAddressString(
"wrong bech32 variant".to_string(),
));
}
// check bech32 data
let bytes = Vec::<u8>::from_base32(&decoded_data)
.map_err(|_| StakingApiError::InvalidAddressString("invalid bech32 data".to_string()))?;
if bytes.len() < 1 || bytes.len() > 255 {
return Err(StakingApiError::InvalidAddressString(
"Invalid canonical address length".to_string(),
));
}
// return canonical address
Ok(bytes.into())
}

pub type Bytes = Vec<u8>;

use error::StakingApiError;
pub use validate::Validate;

0 comments on commit 166c3aa

Please sign in to comment.