From 1cca276c3874e8831bb368e9b6080b6681c12388 Mon Sep 17 00:00:00 2001 From: Zhou Fang Date: Mon, 6 Apr 2026 15:29:43 -0700 Subject: [PATCH 1/2] feat!: make `t` and `allowed_delta` configurable --- crates/e2e-tests/src/e2e_flow.rs | 27 ++++++++++++++ crates/hashi/src/lib.rs | 11 +++--- crates/hashi/src/mpc/mpc_except_signing.rs | 31 +++++++++------- .../hashi/src/mpc/mpc_except_signing_tests.rs | 37 +++++++++++++++---- crates/hashi/src/onchain/mod.rs | 8 ++++ crates/hashi/src/onchain/types.rs | 22 +++++++++++ crates/internal-tools/src/key_recovery.rs | 8 +++- packages/hashi/sources/core/mpc_config.move | 36 ++++++++++++++++++ .../core/proposal/types/update_config.move | 3 +- packages/hashi/sources/hashi.move | 1 + 10 files changed, 154 insertions(+), 30 deletions(-) create mode 100644 packages/hashi/sources/core/mpc_config.move diff --git a/crates/e2e-tests/src/e2e_flow.rs b/crates/e2e-tests/src/e2e_flow.rs index 722ae0c4d..41791b010 100644 --- a/crates/e2e-tests/src/e2e_flow.rs +++ b/crates/e2e-tests/src/e2e_flow.rs @@ -1317,6 +1317,33 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_mpc_config_defaults_match_rust() -> Result<()> { + init_test_logging(); + + let networks = TestNetworksBuilder::new().with_nodes(1).build().await?; + networks.hashi_network.nodes()[0] + .wait_for_mpc_key(Duration::from_secs(60)) + .await?; + + use hashi::onchain::types::DEFAULT_MPC_ALLOWED_DELTA; + use hashi::onchain::types::DEFAULT_MPC_THRESHOLD_BASIS_POINTS; + + let hashi = networks.hashi_network.nodes()[0].hashi(); + let threshold_bps = hashi.onchain_state().mpc_threshold_basis_points(); + let allowed_delta = hashi.onchain_state().mpc_allowed_delta(); + + assert_eq!( + threshold_bps, DEFAULT_MPC_THRESHOLD_BASIS_POINTS, + "on-chain mpc_threshold_basis_points ({threshold_bps}) != Rust default ({DEFAULT_MPC_THRESHOLD_BASIS_POINTS})" + ); + assert_eq!( + allowed_delta, DEFAULT_MPC_ALLOWED_DELTA, + "on-chain mpc_allowed_delta ({allowed_delta}) != Rust default ({DEFAULT_MPC_ALLOWED_DELTA})" + ); + Ok(()) + } + /// Verify that a withdrawal can spend a change output whose producing /// transaction is mined on Bitcoin but not yet confirmed on Sui. The /// actual Bitcoin confirmation count must be queried from the node diff --git a/crates/hashi/src/lib.rs b/crates/hashi/src/lib.rs index 79d53388d..8db089cd5 100644 --- a/crates/hashi/src/lib.rs +++ b/crates/hashi/src/lib.rs @@ -28,9 +28,6 @@ pub mod tls; pub mod utxo_pool; pub mod withdrawals; -/// The allowed delta for weight reduction in basis points (800 means 8%). -/// This matches Sui's `random_beacon_reduction_allowed_delta` configuration. -const WEIGHT_REDUCTION_ALLOWED_DELTA: u16 = 800; // TODO: Tune based on production workload. const BATCH_SIZE_PER_WEIGHT: u16 = 10; @@ -220,7 +217,10 @@ impl Hashi { protocol_type: mpc::types::ProtocolType, ) -> anyhow::Result { let state = self.onchain_state().state(); - let committee_set = &state.hashi().committees; + let hashi = state.hashi(); + let committee_set = &hashi.committees; + let threshold_basis_points = hashi.config.mpc_threshold_basis_points(); + let allowed_delta = hashi.config.mpc_allowed_delta(); let session_id = mpc::SessionId::new(self.config.sui_chain_id(), epoch, &protocol_type); let encryption_key = self.config.encryption_private_key()?; self.db @@ -261,7 +261,8 @@ impl Hashi { encryption_key, signing_key, store, - WEIGHT_REDUCTION_ALLOWED_DELTA, + threshold_basis_points, + allowed_delta, chain_id, self.config.test_weight_divisor, batch_size_per_weight, diff --git a/crates/hashi/src/mpc/mpc_except_signing.rs b/crates/hashi/src/mpc/mpc_except_signing.rs index e939b4a6e..02743f881 100644 --- a/crates/hashi/src/mpc/mpc_except_signing.rs +++ b/crates/hashi/src/mpc/mpc_except_signing.rs @@ -74,6 +74,7 @@ const ERR_PUBLISH_CERT_FAILED: &str = "Failed to publish certificate"; const EXPECT_THRESHOLD_VALIDATED: &str = "Threshold already validated"; const EXPECT_THRESHOLD_MET: &str = "Already checked earlier that threshold is met"; const EXPECT_SERIALIZATION_SUCCESS: &str = "Serialization should always succeed"; +const MAX_BASIS_POINTS: u32 = 10000; // DKG protocol // 1) A dealer sends out a message to all parties containing the encrypted shares and the public keys of the nonces. @@ -123,6 +124,7 @@ impl MpcManager { encryption_key: PrivateKey, signing_key: Bls12381PrivateKey, public_message_store: Box, + threshold_basis_points: u16, allowed_delta: u16, chain_id: &str, weight_divisor: Option, @@ -144,8 +146,12 @@ impl MpcManager { .get(&epoch) .ok_or_else(|| MpcError::InvalidConfig(format!("no committee for epoch {epoch}")))? .clone(); - // TODO: Pass t and f as arguments instead of computing them - let (nodes, threshold) = build_reduced_nodes(&committee, allowed_delta, weight_divisor)?; + let (nodes, threshold) = build_reduced_nodes( + &committee, + threshold_basis_points, + allowed_delta, + weight_divisor, + )?; let total_weight = nodes.total_weight(); let max_faulty = ((total_weight - threshold) / 2).min(threshold - 1); let dkg_config = MpcConfig::new(epoch, nodes, threshold, max_faulty)?; @@ -199,8 +205,12 @@ impl MpcManager { }; let (previous_nodes, previous_threshold) = match previous_committee.as_ref() { Some(prev_committee) => { - let (nodes, threshold) = - build_reduced_nodes(prev_committee, allowed_delta, weight_divisor)?; + let (nodes, threshold) = build_reduced_nodes( + prev_committee, + threshold_basis_points, + allowed_delta, + weight_divisor, + )?; (Some(nodes), Some(threshold)) } None => (None, None), @@ -3505,17 +3515,9 @@ fn compute_messages_hash(messages: &Messages) -> MessageHash { MessageHash::from(Blake2b256::digest(&bytes).digest) } -fn compute_bft_threshold(total_weight: u16) -> MpcResult { - if total_weight == 0 { - return Err(MpcError::InvalidConfig( - "committee has zero total weight".into(), - )); - } - Ok((total_weight - 1) / 3 + 1) -} - fn build_reduced_nodes( committee: &Committee, + threshold_basis_points: u16, allowed_delta: u16, test_weight_divisor: u16, ) -> MpcResult<(Nodes, u16)> { @@ -3530,7 +3532,8 @@ fn build_reduced_nodes( }) .collect(); let total_weight: u16 = nodes_vec.iter().map(|n| n.weight).sum(); - let threshold = compute_bft_threshold(total_weight)?; + let threshold = + (total_weight as u32 * threshold_basis_points as u32).div_ceil(MAX_BASIS_POINTS) as u16; Nodes::new_reduced(nodes_vec, threshold, allowed_delta, 1) .map_err(|e| MpcError::CryptoError(e.to_string())) } diff --git a/crates/hashi/src/mpc/mpc_except_signing_tests.rs b/crates/hashi/src/mpc/mpc_except_signing_tests.rs index c4361e527..fc4e553af 100644 --- a/crates/hashi/src/mpc/mpc_except_signing_tests.rs +++ b/crates/hashi/src/mpc/mpc_except_signing_tests.rs @@ -24,6 +24,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +const TEST_THRESHOLD_BASIS_POINTS: u16 = 3333; /// Use 0 for allowed_delta in tests to disable weight reduction. const TEST_ALLOWED_DELTA: u16 = 0; /// Use 1 for test_weight_divisor in unit tests (they already use small weights). @@ -284,6 +285,7 @@ impl TestSetup { self.encryption_keys[validator_index].clone(), self.signing_keys[validator_index].clone(), store, + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, TEST_CHAIN_ID, None, @@ -897,6 +899,7 @@ fn test_mpc_manager_new_from_committee_set() { encryption_key, signing_key, Box::new(MockPublicMessagesStore), + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, TEST_CHAIN_ID, None, @@ -960,6 +963,7 @@ fn test_mpc_manager_new_fails_if_no_committee_for_epoch() { encryption_keys[0].clone(), signing_keys[0].clone(), Box::new(MockPublicMessagesStore), + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, "test", None, @@ -983,7 +987,8 @@ fn test_mpc_manager_new_with_weighted_committee() { let manager = setup.create_manager(0); - // With total_weight=15: max_faulty = (15-1)/3 = 4, threshold = 5 + // With total_weight=15, threshold=ceil(15*3333/10000)=ceil(4.9995)=5 + // max_faulty = min((15-5)/2, 5-1) = min(5, 4) = 4 assert_eq!(manager.mpc_config.threshold, 5); assert_eq!(manager.mpc_config.max_faulty, 4); } @@ -1262,7 +1267,7 @@ fn test_complete_dkg_success() { let mut rng = rand::thread_rng(); // Use different weights: [3, 2, 4, 1, 2] (total = 12) - // threshold = (12 - 1) / 3 + 1 = 4 + // threshold = ceil(12 * 3333 / 10000) = 4 let weights = [3, 2, 4, 1, 2]; let setup = TestSetup::with_weights(&weights); @@ -4756,8 +4761,8 @@ struct RotationTestSetup { impl RotationTestSetup { /// Creates a rotation test setup with weighted validators and completed DKG. - /// Uses weights [3, 2, 4, 1, 2] (total = 12, threshold = 4). - /// Dealers are validators 0, 1, 4 (total weight = 7 >= threshold). + /// Uses weights [3, 2, 4, 1, 2] (total = 12, threshold = ceil(12*3333/10000) = 4). + /// Dealers are validators 0, 1, 4 (total weight = 7 >= threshold + max_faulty = 7). fn new() -> Self { let mut rng = rand::thread_rng(); let weights = [3, 2, 4, 1, 2]; @@ -4816,8 +4821,13 @@ impl RotationTestSetup { /// This matches `run_as_party` behavior during live DKG. fn threshold_dealer_addresses(&self) -> Vec
{ let committee = self.setup.committee(); - let (nodes, threshold) = - build_reduced_nodes(committee, TEST_ALLOWED_DELTA, TEST_WEIGHT_DIVISOR).unwrap(); + let (nodes, threshold) = build_reduced_nodes( + committee, + TEST_THRESHOLD_BASIS_POINTS, + TEST_ALLOWED_DELTA, + TEST_WEIGHT_DIVISOR, + ) + .unwrap(); let mut result = Vec::new(); let mut weight_sum = 0u16; for addr in self.certificates.keys() { @@ -4837,8 +4847,13 @@ impl RotationTestSetup { fn prepare_for_rotation(&self, manager: &mut MpcManager) { let previous_committee = self.setup.committee_set.previous_committee().cloned(); if let Some(ref prev) = previous_committee { - let (nodes, threshold) = - build_reduced_nodes(prev, TEST_ALLOWED_DELTA, TEST_WEIGHT_DIVISOR).unwrap(); + let (nodes, threshold) = build_reduced_nodes( + prev, + TEST_THRESHOLD_BASIS_POINTS, + TEST_ALLOWED_DELTA, + TEST_WEIGHT_DIVISOR, + ) + .unwrap(); manager.previous_nodes = Some(nodes); manager.previous_threshold = Some(threshold); } @@ -5830,6 +5845,7 @@ async fn test_prepare_previous_output_for_new_member() { new_member_encryption_key, new_member_signing_key, Box::new(InMemoryPublicMessagesStore::new()), + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, TEST_CHAIN_ID, None, @@ -6965,6 +6981,7 @@ fn test_reconstruct_from_dkg_certificates_with_shifted_party_ids() { rotation_setup.setup.encryption_keys[shifted_member_index].clone(), rotation_setup.setup.signing_keys[shifted_member_index].clone(), Box::new(store), + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, TEST_CHAIN_ID, None, @@ -7127,6 +7144,7 @@ fn test_reconstruct_from_dkg_certificates_stops_at_threshold() { setup.encryption_keys[target_index].clone(), setup.signing_keys[target_index].clone(), Box::new(store), + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, TEST_CHAIN_ID, None, @@ -7207,6 +7225,7 @@ fn test_reconstruct_from_rotation_certificates_with_shifted_party_ids() { rotation_setup.setup.encryption_keys[dealer_idx].clone(), rotation_setup.setup.signing_keys[dealer_idx].clone(), Box::new(InMemoryPublicMessagesStore::new()), + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, TEST_CHAIN_ID, None, @@ -7238,6 +7257,7 @@ fn test_reconstruct_from_rotation_certificates_with_shifted_party_ids() { rotation_setup.setup.encryption_keys[other_idx].clone(), rotation_setup.setup.signing_keys[other_idx].clone(), Box::new(InMemoryPublicMessagesStore::new()), + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, TEST_CHAIN_ID, None, @@ -7325,6 +7345,7 @@ fn test_reconstruct_from_rotation_certificates_with_shifted_party_ids() { rotation_setup.setup.encryption_keys[shifted_member_index].clone(), rotation_setup.setup.signing_keys[shifted_member_index].clone(), Box::new(store), + TEST_THRESHOLD_BASIS_POINTS, TEST_ALLOWED_DELTA, TEST_CHAIN_ID, None, diff --git a/crates/hashi/src/onchain/mod.rs b/crates/hashi/src/onchain/mod.rs index fb5ccab78..3166ee818 100644 --- a/crates/hashi/src/onchain/mod.rs +++ b/crates/hashi/src/onchain/mod.rs @@ -394,6 +394,14 @@ impl OnchainState { self.state().hashi().config.bitcoin_confirmation_threshold() } + pub fn mpc_threshold_basis_points(&self) -> u16 { + self.state().hashi().config.mpc_threshold_basis_points() + } + + pub fn mpc_allowed_delta(&self) -> u16 { + self.state().hashi().config.mpc_allowed_delta() + } + pub fn bridge_service_client( &self, validator: &Address, diff --git a/crates/hashi/src/onchain/types.rs b/crates/hashi/src/onchain/types.rs index 0cc4d3140..be477661c 100644 --- a/crates/hashi/src/onchain/types.rs +++ b/crates/hashi/src/onchain/types.rs @@ -454,6 +454,10 @@ pub struct Config { // This constant mirrors the value in btc_config.move and must be kept in sync. const DUST_RELAY_MIN_VALUE: u64 = 546; +// These mirror the defaults in mpc_config.move and must be kept in sync. +pub const DEFAULT_MPC_THRESHOLD_BASIS_POINTS: u16 = 3333; +pub const DEFAULT_MPC_ALLOWED_DELTA: u16 = 800; + impl Config { /// Minimum deposit amount, mirroring the floor logic in btc_config.move. pub fn bitcoin_deposit_minimum(&self) -> u64 { @@ -495,6 +499,24 @@ impl Config { _ => 6, } } + + pub fn mpc_threshold_basis_points(&self) -> u16 { + match self.config.get("mpc_threshold_basis_points") { + Some(ConfigValue::U64(v)) => { + u16::try_from(*v).expect("mpc_threshold_basis_points exceeds u16::MAX") + } + _ => DEFAULT_MPC_THRESHOLD_BASIS_POINTS, + } + } + + pub fn mpc_allowed_delta(&self) -> u16 { + match self.config.get("mpc_allowed_delta") { + Some(ConfigValue::U64(v)) => { + u16::try_from(*v).expect("mpc_allowed_delta exceeds u16::MAX") + } + _ => DEFAULT_MPC_ALLOWED_DELTA, + } + } } #[derive(Debug)] diff --git a/crates/internal-tools/src/key_recovery.rs b/crates/internal-tools/src/key_recovery.rs index a697065df..09c845196 100644 --- a/crates/internal-tools/src/key_recovery.rs +++ b/crates/internal-tools/src/key_recovery.rs @@ -113,14 +113,18 @@ pub async fn run(args: Args, onchain_state: &OnchainState, chain_id: &str) -> an let session_id = SessionId::new(chain_id, reconstruction_epoch, &ProtocolType::KeyRotation); let mut manager = { let state = onchain_state.state(); + let hashi = state.hashi(); + let threshold_basis_points = hashi.config.mpc_threshold_basis_points(); + let allowed_delta = hashi.config.mpc_allowed_delta(); MpcManager::new( validator_address, - &state.hashi().committees, + &hashi.committees, session_id, encryption_key, dummy_signing_key.clone(), Box::new(store), - 800, // allowed_delta (same as devnet) + threshold_basis_points, + allowed_delta, chain_id, None, // weight_divisor 0, // batch_size_per_weight (unused for reconstruction) diff --git a/packages/hashi/sources/core/mpc_config.move b/packages/hashi/sources/core/mpc_config.move new file mode 100644 index 000000000..d2c86f310 --- /dev/null +++ b/packages/hashi/sources/core/mpc_config.move @@ -0,0 +1,36 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module hashi::mpc_config; + +use hashi::{config::Config, config_value}; + +const DEFAULT_THRESHOLD_BASIS_POINTS: u64 = 3333; + +const MAX_BPS: u64 = 10000; + +/// Default allowed delta for weight reduction. +const DEFAULT_ALLOWED_DELTA: u64 = 800; + +#[allow(implicit_const_copy)] +public(package) fun is_valid_config_entry( + key: &std::string::String, + value: &config_value::Value, +): bool { + let k = key.as_bytes(); + if (k == &b"mpc_threshold_basis_points") { + value.is_u64() && (*value).as_u64() > 0 && (*value).as_u64() <= MAX_BPS + } else if (k == &b"mpc_allowed_delta") { + value.is_u64() && (*value).as_u64() <= MAX_BPS + } else { + false + } +} + +public(package) fun init_defaults(config: &mut Config) { + config.upsert( + b"mpc_threshold_basis_points", + config_value::new_u64(DEFAULT_THRESHOLD_BASIS_POINTS), + ); + config.upsert(b"mpc_allowed_delta", config_value::new_u64(DEFAULT_ALLOWED_DELTA)); +} diff --git a/packages/hashi/sources/core/proposal/types/update_config.move b/packages/hashi/sources/core/proposal/types/update_config.move index 1282ced58..8f54145c4 100644 --- a/packages/hashi/sources/core/proposal/types/update_config.move +++ b/packages/hashi/sources/core/proposal/types/update_config.move @@ -33,7 +33,8 @@ public fun execute(hashi: &mut Hashi, proposal_id: ID, clock: &Clock) { let UpdateConfig { key, value } = proposal::execute(hashi, proposal_id, clock); assert!( config::is_valid_core_config_entry(&key, &value) - || hashi::btc_config::is_valid_config_entry(&key, &value), + || hashi::btc_config::is_valid_config_entry(&key, &value) + || hashi::mpc_config::is_valid_config_entry(&key, &value), EInvalidConfigEntry, ); let bytes = *key.as_bytes(); diff --git a/packages/hashi/sources/hashi.move b/packages/hashi/sources/hashi.move index 612dca75b..4393635c5 100644 --- a/packages/hashi/sources/hashi.move +++ b/packages/hashi/sources/hashi.move @@ -44,6 +44,7 @@ fun init(ctx: &mut TxContext) { config: { let mut config = hashi::config::create(); hashi::btc_config::init_defaults(&mut config); + hashi::mpc_config::init_defaults(&mut config); config }, treasury: hashi::treasury::create(ctx), From 4f79ce95a727fb8daaf8d8990eb6a7867bde0a16 Mon Sep 17 00:00:00 2001 From: Zhou Fang Date: Wed, 8 Apr 2026 10:28:21 -0700 Subject: [PATCH 2/2] refactor: rename `allowed_delta` to be `weight_reduction_allowed_delta` --- crates/e2e-tests/src/e2e_flow.rs | 9 ++++--- crates/hashi/src/lib.rs | 4 +-- crates/hashi/src/mpc/mpc_except_signing.rs | 10 +++---- .../hashi/src/mpc/mpc_except_signing_tests.rs | 26 +++++++++---------- crates/hashi/src/onchain/mod.rs | 7 +++-- crates/hashi/src/onchain/types.rs | 10 +++---- crates/internal-tools/src/key_recovery.rs | 4 +-- packages/hashi/sources/core/mpc_config.move | 10 ++++--- 8 files changed, 43 insertions(+), 37 deletions(-) diff --git a/crates/e2e-tests/src/e2e_flow.rs b/crates/e2e-tests/src/e2e_flow.rs index 41791b010..a777fbb6b 100644 --- a/crates/e2e-tests/src/e2e_flow.rs +++ b/crates/e2e-tests/src/e2e_flow.rs @@ -1326,20 +1326,21 @@ mod tests { .wait_for_mpc_key(Duration::from_secs(60)) .await?; - use hashi::onchain::types::DEFAULT_MPC_ALLOWED_DELTA; use hashi::onchain::types::DEFAULT_MPC_THRESHOLD_BASIS_POINTS; + use hashi::onchain::types::DEFAULT_MPC_WEIGHT_REDUCTION_ALLOWED_DELTA; let hashi = networks.hashi_network.nodes()[0].hashi(); let threshold_bps = hashi.onchain_state().mpc_threshold_basis_points(); - let allowed_delta = hashi.onchain_state().mpc_allowed_delta(); + let weight_reduction_allowed_delta = + hashi.onchain_state().mpc_weight_reduction_allowed_delta(); assert_eq!( threshold_bps, DEFAULT_MPC_THRESHOLD_BASIS_POINTS, "on-chain mpc_threshold_basis_points ({threshold_bps}) != Rust default ({DEFAULT_MPC_THRESHOLD_BASIS_POINTS})" ); assert_eq!( - allowed_delta, DEFAULT_MPC_ALLOWED_DELTA, - "on-chain mpc_allowed_delta ({allowed_delta}) != Rust default ({DEFAULT_MPC_ALLOWED_DELTA})" + weight_reduction_allowed_delta, DEFAULT_MPC_WEIGHT_REDUCTION_ALLOWED_DELTA, + "on-chain mpc_weight_reduction_allowed_delta ({weight_reduction_allowed_delta}) != Rust default ({DEFAULT_MPC_WEIGHT_REDUCTION_ALLOWED_DELTA})" ); Ok(()) } diff --git a/crates/hashi/src/lib.rs b/crates/hashi/src/lib.rs index 8db089cd5..522e25db2 100644 --- a/crates/hashi/src/lib.rs +++ b/crates/hashi/src/lib.rs @@ -220,7 +220,7 @@ impl Hashi { let hashi = state.hashi(); let committee_set = &hashi.committees; let threshold_basis_points = hashi.config.mpc_threshold_basis_points(); - let allowed_delta = hashi.config.mpc_allowed_delta(); + let weight_reduction_allowed_delta = hashi.config.mpc_weight_reduction_allowed_delta(); let session_id = mpc::SessionId::new(self.config.sui_chain_id(), epoch, &protocol_type); let encryption_key = self.config.encryption_private_key()?; self.db @@ -262,7 +262,7 @@ impl Hashi { signing_key, store, threshold_basis_points, - allowed_delta, + weight_reduction_allowed_delta, chain_id, self.config.test_weight_divisor, batch_size_per_weight, diff --git a/crates/hashi/src/mpc/mpc_except_signing.rs b/crates/hashi/src/mpc/mpc_except_signing.rs index 02743f881..391934a86 100644 --- a/crates/hashi/src/mpc/mpc_except_signing.rs +++ b/crates/hashi/src/mpc/mpc_except_signing.rs @@ -125,7 +125,7 @@ impl MpcManager { signing_key: Bls12381PrivateKey, public_message_store: Box, threshold_basis_points: u16, - allowed_delta: u16, + weight_reduction_allowed_delta: u16, chain_id: &str, weight_divisor: Option, batch_size_per_weight: u16, @@ -149,7 +149,7 @@ impl MpcManager { let (nodes, threshold) = build_reduced_nodes( &committee, threshold_basis_points, - allowed_delta, + weight_reduction_allowed_delta, weight_divisor, )?; let total_weight = nodes.total_weight(); @@ -208,7 +208,7 @@ impl MpcManager { let (nodes, threshold) = build_reduced_nodes( prev_committee, threshold_basis_points, - allowed_delta, + weight_reduction_allowed_delta, weight_divisor, )?; (Some(nodes), Some(threshold)) @@ -3518,7 +3518,7 @@ fn compute_messages_hash(messages: &Messages) -> MessageHash { fn build_reduced_nodes( committee: &Committee, threshold_basis_points: u16, - allowed_delta: u16, + weight_reduction_allowed_delta: u16, test_weight_divisor: u16, ) -> MpcResult<(Nodes, u16)> { let nodes_vec: Vec> = committee @@ -3534,7 +3534,7 @@ fn build_reduced_nodes( let total_weight: u16 = nodes_vec.iter().map(|n| n.weight).sum(); let threshold = (total_weight as u32 * threshold_basis_points as u32).div_ceil(MAX_BASIS_POINTS) as u16; - Nodes::new_reduced(nodes_vec, threshold, allowed_delta, 1) + Nodes::new_reduced(nodes_vec, threshold, weight_reduction_allowed_delta, 1) .map_err(|e| MpcError::CryptoError(e.to_string())) } diff --git a/crates/hashi/src/mpc/mpc_except_signing_tests.rs b/crates/hashi/src/mpc/mpc_except_signing_tests.rs index fc4e553af..b62a86ce3 100644 --- a/crates/hashi/src/mpc/mpc_except_signing_tests.rs +++ b/crates/hashi/src/mpc/mpc_except_signing_tests.rs @@ -25,8 +25,8 @@ use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; const TEST_THRESHOLD_BASIS_POINTS: u16 = 3333; -/// Use 0 for allowed_delta in tests to disable weight reduction. -const TEST_ALLOWED_DELTA: u16 = 0; +/// Use 0 for weight_reduction_allowed_delta in tests to disable weight reduction. +const TEST_WEIGHT_REDUCTION_ALLOWED_DELTA: u16 = 0; /// Use 1 for test_weight_divisor in unit tests (they already use small weights). const TEST_WEIGHT_DIVISOR: u16 = 1; const TEST_CHAIN_ID: &str = "testchain"; @@ -286,7 +286,7 @@ impl TestSetup { self.signing_keys[validator_index].clone(), store, TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_CHAIN_ID, None, TEST_BATCH_SIZE_PER_WEIGHT, @@ -900,7 +900,7 @@ fn test_mpc_manager_new_from_committee_set() { signing_key, Box::new(MockPublicMessagesStore), TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_CHAIN_ID, None, TEST_BATCH_SIZE_PER_WEIGHT, @@ -964,7 +964,7 @@ fn test_mpc_manager_new_fails_if_no_committee_for_epoch() { signing_keys[0].clone(), Box::new(MockPublicMessagesStore), TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, "test", None, TEST_BATCH_SIZE_PER_WEIGHT, @@ -4824,7 +4824,7 @@ impl RotationTestSetup { let (nodes, threshold) = build_reduced_nodes( committee, TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_WEIGHT_DIVISOR, ) .unwrap(); @@ -4850,7 +4850,7 @@ impl RotationTestSetup { let (nodes, threshold) = build_reduced_nodes( prev, TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_WEIGHT_DIVISOR, ) .unwrap(); @@ -5846,7 +5846,7 @@ async fn test_prepare_previous_output_for_new_member() { new_member_signing_key, Box::new(InMemoryPublicMessagesStore::new()), TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_CHAIN_ID, None, TEST_BATCH_SIZE_PER_WEIGHT, @@ -6982,7 +6982,7 @@ fn test_reconstruct_from_dkg_certificates_with_shifted_party_ids() { rotation_setup.setup.signing_keys[shifted_member_index].clone(), Box::new(store), TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_CHAIN_ID, None, TEST_BATCH_SIZE_PER_WEIGHT, @@ -7145,7 +7145,7 @@ fn test_reconstruct_from_dkg_certificates_stops_at_threshold() { setup.signing_keys[target_index].clone(), Box::new(store), TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_CHAIN_ID, None, TEST_BATCH_SIZE_PER_WEIGHT, @@ -7226,7 +7226,7 @@ fn test_reconstruct_from_rotation_certificates_with_shifted_party_ids() { rotation_setup.setup.signing_keys[dealer_idx].clone(), Box::new(InMemoryPublicMessagesStore::new()), TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_CHAIN_ID, None, TEST_BATCH_SIZE_PER_WEIGHT, @@ -7258,7 +7258,7 @@ fn test_reconstruct_from_rotation_certificates_with_shifted_party_ids() { rotation_setup.setup.signing_keys[other_idx].clone(), Box::new(InMemoryPublicMessagesStore::new()), TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_CHAIN_ID, None, TEST_BATCH_SIZE_PER_WEIGHT, @@ -7346,7 +7346,7 @@ fn test_reconstruct_from_rotation_certificates_with_shifted_party_ids() { rotation_setup.setup.signing_keys[shifted_member_index].clone(), Box::new(store), TEST_THRESHOLD_BASIS_POINTS, - TEST_ALLOWED_DELTA, + TEST_WEIGHT_REDUCTION_ALLOWED_DELTA, TEST_CHAIN_ID, None, TEST_BATCH_SIZE_PER_WEIGHT, diff --git a/crates/hashi/src/onchain/mod.rs b/crates/hashi/src/onchain/mod.rs index 3166ee818..18a4a25b3 100644 --- a/crates/hashi/src/onchain/mod.rs +++ b/crates/hashi/src/onchain/mod.rs @@ -398,8 +398,11 @@ impl OnchainState { self.state().hashi().config.mpc_threshold_basis_points() } - pub fn mpc_allowed_delta(&self) -> u16 { - self.state().hashi().config.mpc_allowed_delta() + pub fn mpc_weight_reduction_allowed_delta(&self) -> u16 { + self.state() + .hashi() + .config + .mpc_weight_reduction_allowed_delta() } pub fn bridge_service_client( diff --git a/crates/hashi/src/onchain/types.rs b/crates/hashi/src/onchain/types.rs index be477661c..4ce7d1ddd 100644 --- a/crates/hashi/src/onchain/types.rs +++ b/crates/hashi/src/onchain/types.rs @@ -456,7 +456,7 @@ const DUST_RELAY_MIN_VALUE: u64 = 546; // These mirror the defaults in mpc_config.move and must be kept in sync. pub const DEFAULT_MPC_THRESHOLD_BASIS_POINTS: u16 = 3333; -pub const DEFAULT_MPC_ALLOWED_DELTA: u16 = 800; +pub const DEFAULT_MPC_WEIGHT_REDUCTION_ALLOWED_DELTA: u16 = 800; impl Config { /// Minimum deposit amount, mirroring the floor logic in btc_config.move. @@ -509,12 +509,12 @@ impl Config { } } - pub fn mpc_allowed_delta(&self) -> u16 { - match self.config.get("mpc_allowed_delta") { + pub fn mpc_weight_reduction_allowed_delta(&self) -> u16 { + match self.config.get("mpc_weight_reduction_allowed_delta") { Some(ConfigValue::U64(v)) => { - u16::try_from(*v).expect("mpc_allowed_delta exceeds u16::MAX") + u16::try_from(*v).expect("mpc_weight_reduction_allowed_delta exceeds u16::MAX") } - _ => DEFAULT_MPC_ALLOWED_DELTA, + _ => DEFAULT_MPC_WEIGHT_REDUCTION_ALLOWED_DELTA, } } } diff --git a/crates/internal-tools/src/key_recovery.rs b/crates/internal-tools/src/key_recovery.rs index 09c845196..ae869b651 100644 --- a/crates/internal-tools/src/key_recovery.rs +++ b/crates/internal-tools/src/key_recovery.rs @@ -115,7 +115,7 @@ pub async fn run(args: Args, onchain_state: &OnchainState, chain_id: &str) -> an let state = onchain_state.state(); let hashi = state.hashi(); let threshold_basis_points = hashi.config.mpc_threshold_basis_points(); - let allowed_delta = hashi.config.mpc_allowed_delta(); + let weight_reduction_allowed_delta = hashi.config.mpc_weight_reduction_allowed_delta(); MpcManager::new( validator_address, &hashi.committees, @@ -124,7 +124,7 @@ pub async fn run(args: Args, onchain_state: &OnchainState, chain_id: &str) -> an dummy_signing_key.clone(), Box::new(store), threshold_basis_points, - allowed_delta, + weight_reduction_allowed_delta, chain_id, None, // weight_divisor 0, // batch_size_per_weight (unused for reconstruction) diff --git a/packages/hashi/sources/core/mpc_config.move b/packages/hashi/sources/core/mpc_config.move index d2c86f310..480e8d0ef 100644 --- a/packages/hashi/sources/core/mpc_config.move +++ b/packages/hashi/sources/core/mpc_config.move @@ -9,8 +9,7 @@ const DEFAULT_THRESHOLD_BASIS_POINTS: u64 = 3333; const MAX_BPS: u64 = 10000; -/// Default allowed delta for weight reduction. -const DEFAULT_ALLOWED_DELTA: u64 = 800; +const DEFAULT_WEIGHT_REDUCTION_ALLOWED_DELTA: u64 = 800; #[allow(implicit_const_copy)] public(package) fun is_valid_config_entry( @@ -20,7 +19,7 @@ public(package) fun is_valid_config_entry( let k = key.as_bytes(); if (k == &b"mpc_threshold_basis_points") { value.is_u64() && (*value).as_u64() > 0 && (*value).as_u64() <= MAX_BPS - } else if (k == &b"mpc_allowed_delta") { + } else if (k == &b"mpc_weight_reduction_allowed_delta") { value.is_u64() && (*value).as_u64() <= MAX_BPS } else { false @@ -32,5 +31,8 @@ public(package) fun init_defaults(config: &mut Config) { b"mpc_threshold_basis_points", config_value::new_u64(DEFAULT_THRESHOLD_BASIS_POINTS), ); - config.upsert(b"mpc_allowed_delta", config_value::new_u64(DEFAULT_ALLOWED_DELTA)); + config.upsert( + b"mpc_weight_reduction_allowed_delta", + config_value::new_u64(DEFAULT_WEIGHT_REDUCTION_ALLOWED_DELTA), + ); }