From 6b53cfc9c78ddb516007ca2bc3c93395d7387ba7 Mon Sep 17 00:00:00 2001 From: zhoujunma Date: Wed, 24 Jan 2024 09:02:08 -0800 Subject: [PATCH 01/11] dkg manager state transition (#11612) * more dkg skeleton * clean up * clean up * load dkg sk * dkg trait and transcript aggregation logic * renames * todo details * renames * test-only marks * dkg manager state transition * block metadata ext + related type refactoring (#11647) * block metadata ext * update golden files * state computer working with block metadata ext (#11651) * state computer working with block metadata ext * update doc * rename --------- Co-authored-by: Daniel Xiang <66756900+danielxiangzl@users.noreply.github.com> --------- Co-authored-by: danielxiangzl Co-authored-by: Daniel Xiang <66756900+danielxiangzl@users.noreply.github.com> * avoid unwrap * pr comments * update * goldenfiles * update api formats * lint --------- Co-authored-by: danielxiangzl Co-authored-by: Daniel Xiang <66756900+danielxiangzl@users.noreply.github.com> --- aptos-move/aptos-vm/src/validator_txns/dkg.rs | 8 +- aptos-move/vm-genesis/src/lib.rs | 1 + .../src/pipeline/ordering_state_computer.rs | 2 +- crates/validator-transaction-pool/src/lib.rs | 16 + dkg/src/{dkg_manager => }/agg_trx_producer.rs | 14 +- dkg/src/dkg_manager/mod.rs | 316 ++++++++++++++++-- dkg/src/dkg_manager/tests.rs | 163 +++++++++ dkg/src/epoch_manager.rs | 56 +++- dkg/src/lib.rs | 2 + dkg/src/network.rs | 20 +- dkg/src/transcript_aggregation/mod.rs | 18 +- dkg/src/transcript_aggregation/tests.rs | 14 +- dkg/src/types.rs | 26 +- .../generate-format/tests/staged/api.yaml | 10 +- .../generate-format/tests/staged/aptos.yaml | 10 +- .../tests/staged/consensus.yaml | 10 +- types/src/contract_event.rs | 19 ++ types/src/dkg/mod.rs | 4 +- types/src/validator_txn.rs | 4 +- 19 files changed, 623 insertions(+), 90 deletions(-) rename dkg/src/{dkg_manager => }/agg_trx_producer.rs (86%) create mode 100644 dkg/src/dkg_manager/tests.rs diff --git a/aptos-move/aptos-vm/src/validator_txns/dkg.rs b/aptos-move/aptos-vm/src/validator_txns/dkg.rs index d9aa722e5db086..0df0e7daff18a6 100644 --- a/aptos-move/aptos-vm/src/validator_txns/dkg.rs +++ b/aptos-move/aptos-vm/src/validator_txns/dkg.rs @@ -12,7 +12,7 @@ use crate::{ AptosVM, }; use aptos_types::{ - dkg::{DKGNode, DKGState, DKGTrait, DefaultDKG}, + dkg::{DKGState, DKGTrait, DKGTranscript, DefaultDKG}, fee_statement::FeeStatement, move_utils::as_move_value::AsMoveValue, on_chain_config::OnChainConfig, @@ -49,9 +49,9 @@ impl AptosVM { resolver: &impl AptosMoveResolver, log_context: &AdapterLogSchema, session_id: SessionId, - dkg_node: DKGNode, + dkg_transcript: DKGTranscript, ) -> Result<(VMStatus, VMOutput), VMStatus> { - match self.process_dkg_result_inner(resolver, log_context, session_id, dkg_node) { + match self.process_dkg_result_inner(resolver, log_context, session_id, dkg_transcript) { Ok((vm_status, vm_output)) => Ok((vm_status, vm_output)), Err(Expected(failure)) => { // Pretend we are inside Move, and expected failures are like Move aborts. @@ -69,7 +69,7 @@ impl AptosVM { resolver: &impl AptosMoveResolver, log_context: &AdapterLogSchema, session_id: SessionId, - dkg_node: DKGNode, + dkg_node: DKGTranscript, ) -> Result<(VMStatus, VMOutput), ExecutionFailure> { let dkg_state = OnChainConfig::fetch_config(resolver) .ok_or_else(|| Expected(MissingResourceDKGState))?; diff --git a/aptos-move/vm-genesis/src/lib.rs b/aptos-move/vm-genesis/src/lib.rs index 0c0de4cfa2511c..aa1fb788262926 100644 --- a/aptos-move/vm-genesis/src/lib.rs +++ b/aptos-move/vm-genesis/src/lib.rs @@ -443,6 +443,7 @@ pub fn default_features() -> Vec { FeatureFlag::BN254_STRUCTURES, FeatureFlag::COMMISSION_CHANGE_DELEGATION_POOL, FeatureFlag::WEBAUTHN_SIGNATURE, + // FeatureFlag::RECONFIGURE_WITH_DKG, //TODO: re-enable once randomness is ready. FeatureFlag::ZK_ID_SIGNATURE, FeatureFlag::OPEN_ID_SIGNATURE, ] diff --git a/consensus/src/pipeline/ordering_state_computer.rs b/consensus/src/pipeline/ordering_state_computer.rs index 07c51bbd0c4427..9f6d6b230af79d 100644 --- a/consensus/src/pipeline/ordering_state_computer.rs +++ b/consensus/src/pipeline/ordering_state_computer.rs @@ -208,7 +208,7 @@ impl StateComputer for DagStateSyncComputer { _transaction_shuffler: Arc, _block_executor_onchain_config: BlockExecutorConfigFromOnchain, _transaction_deduper: Arc, - _: bool, + _randomness_enabled: bool, ) { unimplemented!("method not supported"); } diff --git a/crates/validator-transaction-pool/src/lib.rs b/crates/validator-transaction-pool/src/lib.rs index aeec944714fb1c..dbe9d3065a17b3 100644 --- a/crates/validator-transaction-pool/src/lib.rs +++ b/crates/validator-transaction-pool/src/lib.rs @@ -6,6 +6,7 @@ use aptos_infallible::Mutex; use aptos_types::validator_txn::{Topic, ValidatorTransaction}; use std::{ collections::{BTreeMap, HashMap, HashSet}, + fmt::{Debug, Formatter}, sync::Arc, time::Instant, }; @@ -14,6 +15,12 @@ pub enum TransactionFilter { PendingTxnHashSet(HashSet), } +impl TransactionFilter { + pub fn no_op() -> Self { + Self::PendingTxnHashSet(HashSet::new()) + } +} + impl TransactionFilter { pub fn should_exclude(&self, txn: &ValidatorTransaction) -> bool { match self { @@ -107,11 +114,20 @@ pub struct PoolStateInner { /// If this is dropped, `txn` will be deleted from the pool (if it has not been). /// /// This allows the pool to be emptied on epoch boundaries. +#[derive(Clone)] pub struct TxnGuard { pool: Arc>, seq_num: u64, } +impl Debug for TxnGuard { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TxnGuard") + .field("seq_num", &self.seq_num) + .finish() + } +} + impl PoolStateInner { fn try_delete(&mut self, seq_num: u64) { if let Some(item) = self.txn_queue.remove(&seq_num) { diff --git a/dkg/src/dkg_manager/agg_trx_producer.rs b/dkg/src/agg_trx_producer.rs similarity index 86% rename from dkg/src/dkg_manager/agg_trx_producer.rs rename to dkg/src/agg_trx_producer.rs index 5716d1af755fe9..7d76caab5c294d 100644 --- a/dkg/src/dkg_manager/agg_trx_producer.rs +++ b/dkg/src/agg_trx_producer.rs @@ -1,7 +1,7 @@ // Copyright © Aptos Foundation use crate::{ - transcript_aggregation::TranscriptAggregationState, types::DKGNodeRequest, DKGMessage, + transcript_aggregation::TranscriptAggregationState, types::DKGTranscriptRequest, DKGMessage, }; use aptos_channels::aptos_channel::Sender; use aptos_reliable_broadcast::ReliableBroadcast; @@ -15,7 +15,7 @@ use tokio_retry::strategy::ExponentialBackoff; /// Once invoked by `DKGManager` to `start_produce`, /// it starts producing an aggregated transcript and returns an abort handle. /// Once an aggregated transcript is available, it is sent back via channel `agg_trx_tx`. -pub trait AggTranscriptProducer: Send + Sync { +pub trait TAggTranscriptProducer: Send + Sync { fn start_produce( &self, epoch_state: Arc, @@ -25,11 +25,11 @@ pub trait AggTranscriptProducer: Send + Sync { } /// The real implementation of `AggTranscriptProducer` that broadcasts a `NodeRequest`, collects and verifies nodes from network. -pub struct RealAggTranscriptProducer { +pub struct AggTranscriptProducer { reliable_broadcast: Arc>, } -impl RealAggTranscriptProducer { +impl AggTranscriptProducer { pub fn new(reliable_broadcast: ReliableBroadcast) -> Self { Self { reliable_broadcast: Arc::new(reliable_broadcast), @@ -37,7 +37,7 @@ impl RealAggTranscriptProducer { } } -impl AggTranscriptProducer for RealAggTranscriptProducer { +impl TAggTranscriptProducer for AggTranscriptProducer { fn start_produce( &self, epoch_state: Arc, @@ -45,7 +45,7 @@ impl AggTranscriptProducer for RealAggTranscriptPr agg_trx_tx: Option>, ) -> AbortHandle { let rb = self.reliable_broadcast.clone(); - let req = DKGNodeRequest::new(epoch_state.epoch); + let req = DKGTranscriptRequest::new(epoch_state.epoch); let agg_state = Arc::new(TranscriptAggregationState::::new(params, epoch_state)); let task = async move { let agg_trx = rb.broadcast(req, agg_state).await; @@ -63,7 +63,7 @@ impl AggTranscriptProducer for RealAggTranscriptPr pub struct DummyAggTranscriptProducer {} #[cfg(test)] -impl AggTranscriptProducer for DummyAggTranscriptProducer { +impl TAggTranscriptProducer for DummyAggTranscriptProducer { fn start_produce( &self, _epoch_state: Arc, diff --git a/dkg/src/dkg_manager/mod.rs b/dkg/src/dkg_manager/mod.rs index 8f6bced29744af..091e385ea4c413 100644 --- a/dkg/src/dkg_manager/mod.rs +++ b/dkg/src/dkg_manager/mod.rs @@ -1,66 +1,338 @@ // Copyright © Aptos Foundation -use crate::{dkg_manager::agg_trx_producer::AggTranscriptProducer, network::IncomingRpcRequest}; -use aptos_channels::aptos_channel; +use crate::{agg_trx_producer::TAggTranscriptProducer, network::IncomingRpcRequest, DKGMessage}; +use anyhow::{anyhow, bail, ensure, Result}; +use aptos_channels::{aptos_channel, message_queues::QueueStyle}; +use aptos_crypto::Uniform; +use aptos_logger::error; use aptos_types::{ - dkg::{DKGNode, DKGSessionState, DKGStartEvent, DKGTrait}, + dkg::{ + DKGSessionMetadata, DKGSessionState, DKGStartEvent, DKGTrait, DKGTranscript, + DKGTranscriptMetadata, + }, epoch_state::EpochState, + validator_txn::{Topic, ValidatorTransaction}, }; -use aptos_validator_transaction_pool::VTxnPoolState; +use aptos_validator_transaction_pool::{TxnGuard, VTxnPoolState}; use futures_channel::oneshot; -use futures_util::{FutureExt, StreamExt}; +use futures_util::{future::AbortHandle, FutureExt, StreamExt}; use move_core_types::account_address::AccountAddress; +use rand::thread_rng; use std::sync::Arc; -pub mod agg_trx_producer; +#[allow(dead_code)] +#[derive(Clone, Debug)] +enum InnerState { + NotStarted, + InProgress { + start_time_us: u64, + public_params: DKG::PublicParams, + my_transcript: DKGTranscript, + abort_handle: AbortHandle, + }, + Finished { + vtxn_guard: TxnGuard, + start_time_us: u64, + my_transcript: DKGTranscript, + pull_confirmed: bool, + }, +} + +impl InnerState { + fn variant_name(&self) -> &str { + match self { + InnerState::NotStarted => "NotStarted", + InnerState::InProgress { .. } => "InProgress", + InnerState::Finished { .. } => "Finished", + } + } + + #[cfg(test)] + pub fn my_node_cloned(&self) -> DKGTranscript { + match self { + InnerState::NotStarted => panic!("my_node unavailable"), + InnerState::InProgress { + my_transcript: my_node, + .. + } + | InnerState::Finished { + my_transcript: my_node, + .. + } => my_node.clone(), + } + } +} + +impl Default for InnerState { + fn default() -> Self { + Self::NotStarted + } +} #[allow(dead_code)] pub struct DKGManager { dealer_sk: Arc, + my_index: usize, my_addr: AccountAddress, epoch_state: Arc, + vtxn_pool: VTxnPoolState, - agg_trx_producer: Arc>, - agg_trx_tx: Option>, - //TODO: inner state + agg_trx_producer: Arc>, + agg_trx_tx: Option>, + + // When we put vtxn in the pool, we also put a copy of this so later pool can notify us. + pull_notification_tx: aptos_channel::Sender<(), Arc>, + pull_notification_rx: aptos_channel::Receiver<(), Arc>, + + // Control states. + stopped: bool, + state: InnerState, } -#[allow(clippy::never_loop)] impl DKGManager { pub fn new( dealer_sk: Arc, + my_index: usize, my_addr: AccountAddress, epoch_state: Arc, - agg_trx_producer: Arc>, + agg_trx_producer: Arc>, vtxn_pool: VTxnPoolState, ) -> Self { + let (pull_notification_tx, pull_notification_rx) = + aptos_channel::new(QueueStyle::KLAST, 1, None); Self { dealer_sk, + my_index, my_addr, epoch_state, vtxn_pool, agg_trx_tx: None, + pull_notification_tx, + pull_notification_rx, agg_trx_producer, + stopped: false, + state: InnerState::NotStarted, } } pub async fn run( - self, - _in_progress_session: Option, - _dkg_start_event_rx: aptos_channel::Receiver<(), DKGStartEvent>, - _rpc_msg_rx: aptos_channel::Receiver<(), (AccountAddress, IncomingRpcRequest)>, + mut self, + in_progress_session: Option, + dkg_start_event_rx: oneshot::Receiver, + mut rpc_msg_rx: aptos_channel::Receiver< + AccountAddress, + (AccountAddress, IncomingRpcRequest), + >, close_rx: oneshot::Receiver>, ) { + if let Some(session_state) = in_progress_session { + let DKGSessionState { + metadata, + start_time_us, + .. + } = session_state; + self.setup_deal_broadcast(start_time_us, &metadata) + .await + .expect("setup_deal_broadcast() should be infallible"); + } + + let (agg_trx_tx, mut agg_trx_rx) = aptos_channel::new(QueueStyle::KLAST, 1, None); + self.agg_trx_tx = Some(agg_trx_tx); + + let mut dkg_start_event_rx = dkg_start_event_rx.into_stream(); let mut close_rx = close_rx.into_stream(); - loop { - tokio::select! { - //TODO: handle other events + while !self.stopped { + let handling_result = tokio::select! { + dkg_start_event = dkg_start_event_rx.select_next_some() => { + self.process_dkg_start_event(dkg_start_event.ok()).await + }, + (_sender, msg) = rpc_msg_rx.select_next_some() => { + self.process_peer_rpc_msg(msg).await + }, + agg_node = agg_trx_rx.select_next_some() => { + self.process_aggregated_transcript(agg_node).await + }, + dkg_txn = self.pull_notification_rx.select_next_some() => { + self.process_dkg_txn_pulled_notification(dkg_txn).await + }, close_req = close_rx.select_next_some() => { - if let Ok(ack_sender) = close_req { - ack_sender.send(()).unwrap(); - } - break; + self.process_close_cmd(close_req.ok()) } + }; + + if let Err(e) = handling_result { + error!("{}", e); + } + } + } + + /// On a CLOSE command from epoch manager, do clean-up. + fn process_close_cmd(&mut self, ack_tx: Option>) -> Result<()> { + self.stopped = true; + + if let InnerState::InProgress { abort_handle, .. } = &self.state { + abort_handle.abort(); + } + + if let Some(tx) = ack_tx { + let _ = tx.send(()); + } + + Ok(()) + } + + /// On a vtxn pull notification, record metric. + async fn process_dkg_txn_pulled_notification( + &mut self, + _txn: Arc, + ) -> Result<()> { + if let InnerState::Finished { pull_confirmed, .. } = &mut self.state { + if !*pull_confirmed { + // TODO(zjma): metric DKG_AGG_NODE_PROPOSED } + *pull_confirmed = true; } + Ok(()) + } + + /// Calculate DKG config. Deal a transcript. Start broadcasting the transcript. + /// Called when a DKG start event is received, or when the node is restarting. + /// + /// NOTE: the dealt DKG transcript does not have to be persisted: + /// it is ok for a validator to equivocate on its DKG transcript, as long as the transcript is valid. + async fn setup_deal_broadcast( + &mut self, + start_time_us: u64, + dkg_session_metadata: &DKGSessionMetadata, + ) -> Result<()> { + self.state = match &self.state { + InnerState::NotStarted => { + let public_params = DKG::new_public_params(dkg_session_metadata); + let mut rng = thread_rng(); + let input_secret = if cfg!(feature = "smoke-test") { + DKG::generate_predictable_input_secret_for_testing(self.dealer_sk.as_ref()) + } else { + DKG::InputSecret::generate(&mut rng) + }; + + let trx = DKG::generate_transcript( + &mut rng, + &public_params, + &input_secret, + self.my_index as u64, + &self.dealer_sk, + ); + + let dkg_transcript = DKGTranscript::new( + self.epoch_state.epoch, + self.my_addr, + bcs::to_bytes(&trx).map_err(|e| { + anyhow!("setup_deal_broadcast failed with trx serialization error: {e}") + })?, + ); + + // TODO(zjma): DKG_NODE_READY metric + + let abort_handle = self.agg_trx_producer.start_produce( + self.epoch_state.clone(), + public_params.clone(), + self.agg_trx_tx.clone(), + ); + + // Switch to the next stage. + InnerState::InProgress { + start_time_us, + public_params, + my_transcript: dkg_transcript, + abort_handle, + } + }, + _ => unreachable!(), // `setup_deal_broadcast` is called only when DKG state is `NotStarted`. + }; + + Ok(()) + } + + /// On a locally aggregated transcript, put it into the validator txn pool and update inner states. + async fn process_aggregated_transcript(&mut self, agg_trx: DKG::Transcript) -> Result<()> { + self.state = match std::mem::take(&mut self.state) { + InnerState::InProgress { + start_time_us, + my_transcript: my_node, + .. + } => { + // TODO(zjma): metric DKG_AGG_NODE_READY + let txn = ValidatorTransaction::DKGResult(DKGTranscript { + metadata: DKGTranscriptMetadata { + epoch: self.epoch_state.epoch, + author: self.my_addr, + }, + transcript_bytes: bcs::to_bytes(&agg_trx).map_err(|e|anyhow!("process_aggregated_transcript failed with trx serialization error: {e}"))?, + }); + let vtxn_guard = self.vtxn_pool.put( + Topic::DKG, + Arc::new(txn), + Some(self.pull_notification_tx.clone()), + ); + InnerState::Finished { + vtxn_guard, + start_time_us, + my_transcript: my_node, + pull_confirmed: false, + } + }, + _ => bail!("process agg trx failed with invalid inner state"), + }; + Ok(()) + } + + /// On a DKG start event, execute DKG. + async fn process_dkg_start_event(&mut self, maybe_event: Option) -> Result<()> { + if let Some(event) = maybe_event { + let DKGStartEvent { + session_metadata, + start_time_us, + } = event; + ensure!(self.epoch_state.epoch == session_metadata.dealer_epoch); + self.setup_deal_broadcast(start_time_us, &session_metadata) + .await?; + } + Ok(()) + } + + /// Process an RPC request from DKG peers. + async fn process_peer_rpc_msg(&mut self, req: IncomingRpcRequest) -> Result<()> { + let IncomingRpcRequest { + msg, + mut response_sender, + .. + } = req; + ensure!(msg.epoch() == self.epoch_state.epoch); + let response = match (&self.state, &msg) { + ( + InnerState::Finished { + my_transcript: my_node, + .. + }, + DKGMessage::NodeRequest(_), + ) + | ( + InnerState::InProgress { + my_transcript: my_node, + .. + }, + DKGMessage::NodeRequest(_), + ) => Ok(DKGMessage::NodeResponse(my_node.clone())), + _ => Err(anyhow!( + "msg {:?} unexpected in state {:?}", + msg.name(), + self.state.variant_name() + )), + }; + + response_sender.send(response); + Ok(()) } } + +#[cfg(test)] +mod tests; diff --git a/dkg/src/dkg_manager/tests.rs b/dkg/src/dkg_manager/tests.rs new file mode 100644 index 00000000000000..8e48a6227df41b --- /dev/null +++ b/dkg/src/dkg_manager/tests.rs @@ -0,0 +1,163 @@ +// Copyright © Aptos Foundation + +use crate::{ + agg_trx_producer::DummyAggTranscriptProducer, + dkg_manager::{DKGManager, InnerState}, + network::{DummyRpcResponseSender, IncomingRpcRequest}, + types::DKGTranscriptRequest, + DKGMessage, +}; +use aptos_crypto::{ + bls12381::{PrivateKey, PublicKey}, + Uniform, +}; +use aptos_infallible::RwLock; +use aptos_types::{ + dkg::{ + dummy_dkg::DummyDKG, DKGSessionMetadata, DKGStartEvent, DKGTrait, DKGTranscript, + DKGTranscriptMetadata, + }, + epoch_state::EpochState, + validator_txn::ValidatorTransaction, + validator_verifier::{ + ValidatorConsensusInfo, ValidatorConsensusInfoMoveStruct, ValidatorVerifier, + }, +}; +use aptos_validator_transaction_pool::{TransactionFilter, VTxnPoolState}; +use move_core_types::account_address::AccountAddress; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +#[tokio::test] +async fn test_dkg_state_transition() { + // Setup a validator set of 4 validators. + let private_keys: Vec> = (0..4) + .map(|_| Arc::new(PrivateKey::generate_for_testing())) + .collect(); + let public_keys: Vec = private_keys + .iter() + .map(|sk| PublicKey::from(sk.as_ref())) + .collect(); + let addrs: Vec = (0..4).map(|_| AccountAddress::random()).collect(); + let voting_powers: Vec = vec![1, 1, 1, 1]; + let vtxn_pool_handle = VTxnPoolState::default(); + let validator_consensus_infos: Vec = (0..4) + .map(|i| ValidatorConsensusInfo::new(addrs[i], public_keys[i].clone(), voting_powers[i])) + .collect(); + let validator_consensus_info_move_structs = validator_consensus_infos + .clone() + .into_iter() + .map(ValidatorConsensusInfoMoveStruct::from) + .collect::>(); + let epoch_state = EpochState { + epoch: 999, + verifier: ValidatorVerifier::new(validator_consensus_infos.clone()), + }; + let agg_node_producer = DummyAggTranscriptProducer {}; + let mut dkg_manager: DKGManager = DKGManager::new( + private_keys[0].clone(), + 0, + addrs[0], + Arc::new(epoch_state), + Arc::new(agg_node_producer), + vtxn_pool_handle.clone(), + ); + + // Initial state should be `NotStarted`. + assert!(matches!(&dkg_manager.state, InnerState::NotStarted)); + + let rpc_response_collector = Arc::new(RwLock::new(vec![])); + + // In state `NotStarted`, DKGManager should reply to RPC request with errors. + let rpc_node_request = new_rpc_node_request(999, addrs[3], rpc_response_collector.clone()); + let handle_result = dkg_manager.process_peer_rpc_msg(rpc_node_request).await; + assert!(handle_result.is_ok()); + let last_invocations = std::mem::take(&mut *rpc_response_collector.write()); + assert!(last_invocations.len() == 1 && last_invocations[0].is_err()); + assert!(matches!(&dkg_manager.state, InnerState::NotStarted)); + + // In state `NotStarted`, DKGManager should accept `DKGStartEvent`: + // it should record start time, compute its own node, and enter state `InProgress`. + let handle_result = dkg_manager + .process_dkg_start_event(Some(DKGStartEvent { + session_metadata: DKGSessionMetadata { + dealer_epoch: 999, + dealer_validator_set: validator_consensus_info_move_structs.clone(), + target_validator_set: validator_consensus_info_move_structs.clone(), + }, + start_time_us: 1700000000000000, + })) + .await; + assert!(handle_result.is_ok()); + assert!( + matches!(&dkg_manager.state, InnerState::InProgress { start_time_us, my_transcript, .. } if *start_time_us == 1700000000000000 && my_transcript.metadata == DKGTranscriptMetadata{ epoch: 999, author: addrs[0]}) + ); + + // In state `InProgress`, DKGManager should respond to `DKGNodeRequest` with its own node. + let rpc_node_request = new_rpc_node_request(999, addrs[3], rpc_response_collector.clone()); + let handle_result = dkg_manager.process_peer_rpc_msg(rpc_node_request).await; + assert!(handle_result.is_ok()); + let last_responses = std::mem::take(&mut *rpc_response_collector.write()) + .into_iter() + .map(anyhow::Result::unwrap) + .collect::>(); + assert_eq!( + vec![DKGMessage::NodeResponse(dkg_manager.state.my_node_cloned())], + last_responses + ); + assert!(matches!(&dkg_manager.state, InnerState::InProgress { .. })); + + // In state `InProgress`, DKGManager should accept `DKGAggNode`: + // it should update validator txn pool, and enter state `Finished`. + let agg_trx = ::Transcript::default(); + let handle_result = dkg_manager + .process_aggregated_transcript(agg_trx.clone()) + .await; + assert!(handle_result.is_ok()); + let available_vtxns = vtxn_pool_handle.pull( + Instant::now() + Duration::from_secs(10), + 999, + 2048, + TransactionFilter::no_op(), + ); + assert_eq!( + vec![ValidatorTransaction::DKGResult(DKGTranscript { + metadata: DKGTranscriptMetadata { + epoch: 999, + author: addrs[0], + }, + transcript_bytes: bcs::to_bytes(&agg_trx).unwrap(), + })], + available_vtxns + ); + assert!(matches!(&dkg_manager.state, InnerState::Finished { .. })); + + // In state `Finished`, DKGManager should still respond to `DKGNodeRequest` with its own node. + let rpc_node_request = new_rpc_node_request(999, addrs[3], rpc_response_collector.clone()); + let handle_result = dkg_manager.process_peer_rpc_msg(rpc_node_request).await; + assert!(handle_result.is_ok()); + let last_responses = std::mem::take(&mut *rpc_response_collector.write()) + .into_iter() + .map(anyhow::Result::unwrap) + .collect::>(); + assert_eq!( + vec![DKGMessage::NodeResponse(dkg_manager.state.my_node_cloned())], + last_responses + ); + assert!(matches!(&dkg_manager.state, InnerState::Finished { .. })); +} + +#[cfg(test)] +fn new_rpc_node_request( + epoch: u64, + sender: AccountAddress, + response_collector: Arc>>>, +) -> IncomingRpcRequest { + IncomingRpcRequest { + msg: DKGMessage::NodeRequest(DKGTranscriptRequest::new(epoch)), + sender, + response_sender: Box::new(DummyRpcResponseSender::new(response_collector)), + } +} diff --git a/dkg/src/epoch_manager.rs b/dkg/src/epoch_manager.rs index d4b064924c0c1d..f5b210820ed2e9 100644 --- a/dkg/src/epoch_manager.rs +++ b/dkg/src/epoch_manager.rs @@ -1,7 +1,8 @@ // Copyright © Aptos Foundation use crate::{ - dkg_manager::{agg_trx_producer::RealAggTranscriptProducer, DKGManager}, + agg_trx_producer::AggTranscriptProducer, + dkg_manager::DKGManager, network::{IncomingRpcRequest, NetworkReceivers, NetworkSender}, network_interface::DKGNetworkClient, DKGMessage, @@ -13,7 +14,7 @@ use aptos_event_notifications::{ EventNotification, EventNotificationListener, ReconfigNotification, ReconfigNotificationListener, }; -use aptos_logger::error; +use aptos_logger::{debug, error}; use aptos_network::{application::interface::NetworkClient, protocols::network::Event}; use aptos_reliable_broadcast::ReliableBroadcast; use aptos_types::{ @@ -30,7 +31,6 @@ use futures_channel::oneshot; use std::{sync::Arc, time::Duration}; use tokio_retry::strategy::ExponentialBackoff; -#[allow(dead_code)] pub struct EpochManager { dkg_dealer_sk: Arc<::DealerPrivateKey>, // Some useful metadata @@ -42,9 +42,10 @@ pub struct EpochManager { dkg_start_events: EventNotificationListener, // Msgs to DKG manager - dkg_rpc_msg_tx: Option>, + dkg_rpc_msg_tx: + Option>, dkg_manager_close_tx: Option>>, - dkg_start_event_tx: Option>, + dkg_start_event_tx: Option>, vtxn_pool: VTxnPoolState, // Network utils @@ -79,15 +80,32 @@ impl EpochManager

{ fn process_rpc_request( &mut self, - _peer_id: AccountAddress, - _dkg_request: IncomingRpcRequest, + peer_id: AccountAddress, + dkg_request: IncomingRpcRequest, ) -> Result<()> { - //TODO + if Some(dkg_request.msg.epoch()) == self.epoch_state.as_ref().map(|s| s.epoch) { + // Forward to DKGManager if it is alive. + if let Some(tx) = &self.dkg_rpc_msg_tx { + let _ = tx.push(peer_id, (peer_id, dkg_request)); + } + } Ok(()) } - fn on_dkg_start_notification(&mut self, _notification: EventNotification) -> Result<()> { - //TODO + fn on_dkg_start_notification(&mut self, notification: EventNotification) -> Result<()> { + if let Some(tx) = self.dkg_start_event_tx.take() { + let EventNotification { + subscribed_events, .. + } = notification; + for event in subscribed_events { + if let Ok(dkg_start_event) = DKGStartEvent::try_from(&event) { + let _ = tx.send(dkg_start_event); + return Ok(()); + } else { + debug!("[DKG] on_dkg_start_notification: failed in converting a contract event to a dkg start event!"); + } + } + } Ok(()) } @@ -132,10 +150,18 @@ impl EpochManager

{ verifier: (&validator_set).into(), }); self.epoch_state = Some(epoch_state.clone()); + let my_index = epoch_state + .verifier + .address_to_validator_index() + .get(&self.my_addr) + .copied(); let features = payload.get::().unwrap_or_default(); - if features.is_enabled(FeatureFlag::RECONFIGURE_WITH_DKG) { + if let (true, Some(my_index)) = ( + features.is_enabled(FeatureFlag::RECONFIGURE_WITH_DKG), + my_index, + ) { let DKGState { in_progress: in_progress_session, .. @@ -150,14 +176,13 @@ impl EpochManager

{ Duration::from_millis(1000), BoundedExecutor::new(8, tokio::runtime::Handle::current()), ); - let agg_trx_producer = RealAggTranscriptProducer::new(rb); + let agg_trx_producer = AggTranscriptProducer::new(rb); - let (dkg_start_event_tx, dkg_start_event_rx) = - aptos_channel::new(QueueStyle::KLAST, 1, None); + let (dkg_start_event_tx, dkg_start_event_rx) = oneshot::channel(); self.dkg_start_event_tx = Some(dkg_start_event_tx); let (dkg_rpc_msg_tx, dkg_rpc_msg_rx) = aptos_channel::new::< - (), + AccountAddress, (AccountAddress, IncomingRpcRequest), >(QueueStyle::FIFO, 100, None); self.dkg_rpc_msg_tx = Some(dkg_rpc_msg_tx); @@ -166,6 +191,7 @@ impl EpochManager

{ let dkg_manager = DKGManager::::new( self.dkg_dealer_sk.clone(), + my_index, self.my_addr, epoch_state, Arc::new(agg_trx_producer), diff --git a/dkg/src/lib.rs b/dkg/src/lib.rs index 0bbea2d334c7cc..9e62b71c8e35ce 100644 --- a/dkg/src/lib.rs +++ b/dkg/src/lib.rs @@ -49,3 +49,5 @@ pub fn start_dkg_runtime( runtime.spawn(dkg_epoch_manager.start(network_receiver)); runtime } + +pub mod agg_trx_producer; diff --git a/dkg/src/network.rs b/dkg/src/network.rs index d7379581aa0298..be292bda57029d 100644 --- a/dkg/src/network.rs +++ b/dkg/src/network.rs @@ -7,6 +7,7 @@ use crate::{ use anyhow::bail; use aptos_channels::{aptos_channel, message_queues::QueueStyle}; use aptos_config::network_id::NetworkId; +use aptos_infallible::RwLock; use aptos_logger::warn; use aptos_network::{ application::interface::{NetworkClient, NetworkServiceEvents}, @@ -22,7 +23,7 @@ use futures::{ }; use futures_channel::oneshot; use move_core_types::account_address::AccountAddress; -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use tokio::time::timeout; pub struct IncomingRpcRequest { @@ -181,7 +182,20 @@ impl RpcResponseSender for RealRpcResponseSender { } } -#[cfg(test)] pub struct DummyRpcResponseSender { - //TODO + pub rpc_response_collector: Arc>>>, +} + +impl DummyRpcResponseSender { + pub fn new(rpc_response_collector: Arc>>>) -> Self { + Self { + rpc_response_collector, + } + } +} + +impl RpcResponseSender for DummyRpcResponseSender { + fn send(&mut self, response: anyhow::Result) { + self.rpc_response_collector.write().push(response); + } } diff --git a/dkg/src/transcript_aggregation/mod.rs b/dkg/src/transcript_aggregation/mod.rs index e88bac361372d7..15a970277f39f3 100644 --- a/dkg/src/transcript_aggregation/mod.rs +++ b/dkg/src/transcript_aggregation/mod.rs @@ -1,12 +1,12 @@ // Copyright © Aptos Foundation -use crate::{types::DKGNodeRequest, DKGMessage}; +use crate::{types::DKGTranscriptRequest, DKGMessage}; use anyhow::ensure; use aptos_consensus_types::common::Author; use aptos_infallible::Mutex; use aptos_reliable_broadcast::BroadcastStatus; use aptos_types::{ - dkg::{DKGNode, DKGTrait}, + dkg::{DKGTrait, DKGTranscript}, epoch_state::EpochState, }; use move_core_types::account_address::AccountAddress; @@ -45,14 +45,18 @@ impl TranscriptAggregationState { impl BroadcastStatus for Arc> { type Aggregated = S::Transcript; - type Message = DKGNodeRequest; - type Response = DKGNode; + type Message = DKGTranscriptRequest; + type Response = DKGTranscript; - fn add(&self, sender: Author, dkg_node: DKGNode) -> anyhow::Result> { - let DKGNode { + fn add( + &self, + sender: Author, + dkg_transcript: DKGTranscript, + ) -> anyhow::Result> { + let DKGTranscript { metadata, transcript_bytes, - } = dkg_node; + } = dkg_transcript; ensure!( metadata.epoch == self.epoch_state.epoch, "adding dkg node failed with invalid node epoch", diff --git a/dkg/src/transcript_aggregation/tests.rs b/dkg/src/transcript_aggregation/tests.rs index 53e11684ea1a6e..eeb2e34dcfb50a 100644 --- a/dkg/src/transcript_aggregation/tests.rs +++ b/dkg/src/transcript_aggregation/tests.rs @@ -6,7 +6,7 @@ use aptos_reliable_broadcast::BroadcastStatus; use aptos_types::{ dkg::{ dummy_dkg::{DummyDKG, DummyDKGTranscript}, - DKGNode, DKGSessionMetadata, DKGTrait, DKGTranscriptMetadata, + DKGSessionMetadata, DKGTrait, DKGTranscript, DKGTranscriptMetadata, }, epoch_state::EpochState, validator_verifier::{ @@ -54,7 +54,7 @@ fn test_transcript_aggregation_state() { let good_trx_bytes = bcs::to_bytes(&good_transcript).unwrap(); // Node with incorrect epoch should be rejected. - let result = trx_agg_state.add(addrs[0], DKGNode { + let result = trx_agg_state.add(addrs[0], DKGTranscript { metadata: DKGTranscriptMetadata { epoch: 998, author: addrs[0], @@ -64,7 +64,7 @@ fn test_transcript_aggregation_state() { assert!(result.is_err()); // Node authored by X but sent by Y should be rejected. - let result = trx_agg_state.add(addrs[1], DKGNode { + let result = trx_agg_state.add(addrs[1], DKGTranscript { metadata: DKGTranscriptMetadata { epoch: 999, author: addrs[0], @@ -76,7 +76,7 @@ fn test_transcript_aggregation_state() { // Node with invalid transcript should be rejected. let mut bad_trx_bytes = good_trx_bytes.clone(); bad_trx_bytes[0] = 0xAB; - let result = trx_agg_state.add(addrs[2], DKGNode { + let result = trx_agg_state.add(addrs[2], DKGTranscript { metadata: DKGTranscriptMetadata { epoch: 999, author: addrs[2], @@ -86,7 +86,7 @@ fn test_transcript_aggregation_state() { assert!(result.is_err()); // Good node should be accepted. - let result = trx_agg_state.add(addrs[3], DKGNode { + let result = trx_agg_state.add(addrs[3], DKGTranscript { metadata: DKGTranscriptMetadata { epoch: 999, author: addrs[3], @@ -96,7 +96,7 @@ fn test_transcript_aggregation_state() { assert!(matches!(result, Ok(None))); // Node from contributed author should be ignored. - let result = trx_agg_state.add(addrs[3], DKGNode { + let result = trx_agg_state.add(addrs[3], DKGTranscript { metadata: DKGTranscriptMetadata { epoch: 999, author: addrs[3], @@ -106,7 +106,7 @@ fn test_transcript_aggregation_state() { assert!(matches!(result, Ok(None))); // Aggregated trx should be returned if after adding a node, the threshold is exceeded. - let result = trx_agg_state.add(addrs[4], DKGNode { + let result = trx_agg_state.add(addrs[4], DKGTranscript { metadata: DKGTranscriptMetadata { epoch: 999, author: addrs[4], diff --git a/dkg/src/types.rs b/dkg/src/types.rs index 1dc8a8ed957701..29172e48e05ad3 100644 --- a/dkg/src/types.rs +++ b/dkg/src/types.rs @@ -4,16 +4,16 @@ use aptos_crypto_derive::CryptoHasher; use aptos_enum_conversion_derive::EnumConversion; use aptos_reliable_broadcast::RBMessage; -pub use aptos_types::dkg::DKGNode; +pub use aptos_types::dkg::DKGTranscript; use serde::{Deserialize, Serialize}; /// Once DKG starts, a validator should send this message to peers in order to collect DKG transcripts from peers. #[derive(Clone, Serialize, Deserialize, CryptoHasher, Debug, PartialEq)] -pub struct DKGNodeRequest { +pub struct DKGTranscriptRequest { dealer_epoch: u64, } -impl DKGNodeRequest { +impl DKGTranscriptRequest { pub fn new(epoch: u64) -> Self { Self { dealer_epoch: epoch, @@ -24,8 +24,24 @@ impl DKGNodeRequest { /// The DKG network message. #[derive(Clone, Serialize, Deserialize, Debug, EnumConversion, PartialEq)] pub enum DKGMessage { - NodeRequest(DKGNodeRequest), - NodeResponse(DKGNode), + NodeRequest(DKGTranscriptRequest), + NodeResponse(DKGTranscript), +} + +impl DKGMessage { + pub fn epoch(&self) -> u64 { + match self { + DKGMessage::NodeRequest(request) => request.dealer_epoch, + DKGMessage::NodeResponse(response) => response.metadata.epoch, + } + } + + pub fn name(&self) -> &str { + match self { + DKGMessage::NodeRequest(_) => "DKGTranscriptRequest", + DKGMessage::NodeResponse(_) => "DKGTranscriptResponse", + } + } } impl RBMessage for DKGMessage {} diff --git a/testsuite/generate-format/tests/staged/api.yaml b/testsuite/generate-format/tests/staged/api.yaml index 36e51069fc400f..3829a0d6205e52 100644 --- a/testsuite/generate-format/tests/staged/api.yaml +++ b/testsuite/generate-format/tests/staged/api.yaml @@ -178,7 +178,7 @@ ContractEventV2: - type_tag: TYPENAME: TypeTag - event_data: BYTES -DKGNode: +DKGTranscript: STRUCT: - metadata: TYPENAME: DKGTranscriptMetadata @@ -669,13 +669,13 @@ TypeTag: ValidatorTransaction: ENUM: 0: - DKGResult: - NEWTYPE: - TYPENAME: DKGNode - 1: DummyTopic1: NEWTYPE: TYPENAME: DummyValidatorTransaction + 1: + DKGResult: + NEWTYPE: + TYPENAME: DKGTranscript 2: DummyTopic2: NEWTYPE: diff --git a/testsuite/generate-format/tests/staged/aptos.yaml b/testsuite/generate-format/tests/staged/aptos.yaml index bf608083d6ed71..e32c495d755ded 100644 --- a/testsuite/generate-format/tests/staged/aptos.yaml +++ b/testsuite/generate-format/tests/staged/aptos.yaml @@ -158,7 +158,7 @@ ContractEventV2: - type_tag: TYPENAME: TypeTag - event_data: BYTES -DKGNode: +DKGTranscript: STRUCT: - metadata: TYPENAME: DKGTranscriptMetadata @@ -554,13 +554,13 @@ TypeTag: ValidatorTransaction: ENUM: 0: - DKGResult: - NEWTYPE: - TYPENAME: DKGNode - 1: DummyTopic1: NEWTYPE: TYPENAME: DummyValidatorTransaction + 1: + DKGResult: + NEWTYPE: + TYPENAME: DKGTranscript 2: DummyTopic2: NEWTYPE: diff --git a/testsuite/generate-format/tests/staged/consensus.yaml b/testsuite/generate-format/tests/staged/consensus.yaml index 6a66e383b7df61..2b600dacee1dbb 100644 --- a/testsuite/generate-format/tests/staged/consensus.yaml +++ b/testsuite/generate-format/tests/staged/consensus.yaml @@ -408,7 +408,7 @@ DAGNetworkMessage: STRUCT: - epoch: U64 - data: BYTES -DKGNode: +DKGTranscript: STRUCT: - metadata: TYPENAME: DKGTranscriptMetadata @@ -945,13 +945,13 @@ ValidatorConsensusInfo: ValidatorTransaction: ENUM: 0: - DKGResult: - NEWTYPE: - TYPENAME: DKGNode - 1: DummyTopic1: NEWTYPE: TYPENAME: DummyValidatorTransaction + 1: + DKGResult: + NEWTYPE: + TYPENAME: DKGTranscript 2: DummyTopic2: NEWTYPE: diff --git a/types/src/contract_event.rs b/types/src/contract_event.rs index 29ed4db149fbb9..c3f1d9addc4722 100644 --- a/types/src/contract_event.rs +++ b/types/src/contract_event.rs @@ -4,6 +4,7 @@ use crate::{ account_config::{DepositEvent, NewBlockEvent, NewEpochEvent, WithdrawEvent}, + dkg::DKGStartEvent, event::EventKey, on_chain_config::new_epoch_event_key, transaction::Version, @@ -293,6 +294,24 @@ impl From<(u64, NewEpochEvent)> for ContractEvent { } } +impl TryFrom<&ContractEvent> for DKGStartEvent { + type Error = Error; + + fn try_from(event: &ContractEvent) -> Result { + match event { + ContractEvent::V1(_) => { + bail!("conversion to dkg start event failed with wrong contract event version"); + }, + ContractEvent::V2(event) => { + if event.type_tag != TypeTag::Struct(Box::new(Self::struct_tag())) { + bail!("conversion to dkg start event failed with wrong type tag") + } + bcs::from_bytes(&event.event_data).map_err(Into::into) + }, + } + } +} + impl TryFrom<&ContractEvent> for NewEpochEvent { type Error = Error; diff --git a/types/src/dkg/mod.rs b/types/src/dkg/mod.rs index 96390f0e4c1699..9df95eebd8a9c7 100644 --- a/types/src/dkg/mod.rs +++ b/types/src/dkg/mod.rs @@ -34,13 +34,13 @@ impl MoveStructType for DKGStartEvent { /// DKG transcript and its metadata. #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub struct DKGNode { +pub struct DKGTranscript { pub metadata: DKGTranscriptMetadata, #[serde(with = "serde_bytes")] pub transcript_bytes: Vec, } -impl DKGNode { +impl DKGTranscript { pub fn new(epoch: u64, author: AccountAddress, transcript_bytes: Vec) -> Self { Self { metadata: DKGTranscriptMetadata { epoch, author }, diff --git a/types/src/validator_txn.rs b/types/src/validator_txn.rs index 350f6132efe80a..7960ae0da78671 100644 --- a/types/src/validator_txn.rs +++ b/types/src/validator_txn.rs @@ -1,15 +1,15 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::{dkg::DKGNode, jwks}; +use crate::{dkg::DKGTranscript, jwks}; use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, CryptoHasher, BCSCryptoHash)] pub enum ValidatorTransaction { - DKGResult(DKGNode), DummyTopic1(DummyValidatorTransaction), + DKGResult(DKGTranscript), DummyTopic2(DummyValidatorTransaction), } From e87269ee4ec5fb1e5eb8e956ef8a6818e4e86f9e Mon Sep 17 00:00:00 2001 From: Vineeth Kashyap Date: Wed, 24 Jan 2024 12:43:15 -0500 Subject: [PATCH 02/11] [compiler-v2] Invalid friend declaration warnings (#11733) --- .../friend_decl_out_of_account_addr.exp | 7 ++ .../friend_decl_out_of_account_addr.move | 9 ++ .../checking/friends/friend_decl_self.exp | 13 +++ .../checking/friends/friend_decl_self.move | 11 +++ .../friends/friend_decl_self_with_use.exp | 7 ++ .../friends/friend_decl_self_with_use.move | 6 ++ .../friends/friend_decl_unbound_module.exp | 7 ++ .../friends/friend_decl_unbound_module.move | 5 + .../friends/friend_different_addresses.exp | 7 ++ .../friends/friend_different_addresses.move | 8 ++ third_party/move/move-model/src/lib.rs | 93 ++++++++++++++++--- 11 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_out_of_account_addr.exp create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_out_of_account_addr.move create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self.exp create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self.move create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self_with_use.exp create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self_with_use.move create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_unbound_module.exp create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_unbound_module.move create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_different_addresses.exp create mode 100644 third_party/move/move-compiler-v2/tests/checking/friends/friend_different_addresses.move diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_out_of_account_addr.exp b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_out_of_account_addr.exp new file mode 100644 index 00000000000000..831f3471b144e1 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_out_of_account_addr.exp @@ -0,0 +1,7 @@ + +Diagnostics: +error: friend modules of `0x3::M` must have the same address, but the declared friend module `0x2::M` has a different address + ┌─ tests/checking/friends/friend_decl_out_of_account_addr.move:7:5 + │ +7 │ friend 0x2::M; + │ ^^^^^^^^^^^^^^ diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_out_of_account_addr.move b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_out_of_account_addr.move new file mode 100644 index 00000000000000..aa204f9ed8541b --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_out_of_account_addr.move @@ -0,0 +1,9 @@ +address 0x2 { +module M {} +} + +address 0x3 { +module M { + friend 0x2::M; +} +} diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self.exp b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self.exp new file mode 100644 index 00000000000000..77cfbea9e111a7 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self.exp @@ -0,0 +1,13 @@ + +Diagnostics: +error: cannot declare module `0x42::M` as a friend of itself + ┌─ tests/checking/friends/friend_decl_self.move:3:5 + │ +3 │ friend Self; + │ ^^^^^^^^^^^^ + +error: cannot declare module `0x43::M` as a friend of itself + ┌─ tests/checking/friends/friend_decl_self.move:9:5 + │ +9 │ friend 0x43::M; + │ ^^^^^^^^^^^^^^^ diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self.move b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self.move new file mode 100644 index 00000000000000..ddd9bb39c2a9df --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self.move @@ -0,0 +1,11 @@ +address 0x42 { +module M { + friend Self; +} +} + +address 0x43 { +module M { + friend 0x43::M; +} +} diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self_with_use.exp b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self_with_use.exp new file mode 100644 index 00000000000000..23df7913cd8ca6 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self_with_use.exp @@ -0,0 +1,7 @@ + +Diagnostics: +error: cannot declare module `0xc0ffee::o` as a friend of itself + ┌─ tests/checking/friends/friend_decl_self_with_use.move:3:5 + │ +3 │ friend o; + │ ^^^^^^^^^ diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self_with_use.move b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self_with_use.move new file mode 100644 index 00000000000000..dc166af8cc1342 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_self_with_use.move @@ -0,0 +1,6 @@ +module 0xc0ffee::o { + use 0xc0ffee::o; + friend o; + + public fun test() {} +} diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_unbound_module.exp b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_unbound_module.exp new file mode 100644 index 00000000000000..0af57a71debbd1 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_unbound_module.exp @@ -0,0 +1,7 @@ + +Diagnostics: +error: unbound module `0x42::Nonexistent` in friend declaration + ┌─ tests/checking/friends/friend_decl_unbound_module.move:3:5 + │ +3 │ friend 0x42::Nonexistent; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_unbound_module.move b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_unbound_module.move new file mode 100644 index 00000000000000..13eaa7b7607abe --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_decl_unbound_module.move @@ -0,0 +1,5 @@ +address 0x42 { +module M { + friend 0x42::Nonexistent; +} +} diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_different_addresses.exp b/third_party/move/move-compiler-v2/tests/checking/friends/friend_different_addresses.exp new file mode 100644 index 00000000000000..fc642f970becda --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_different_addresses.exp @@ -0,0 +1,7 @@ + +Diagnostics: +error: friend modules of `0xc0ffee::m` must have the same address, but the declared friend module `0xdeadbeef::n` has a different address + ┌─ tests/checking/friends/friend_different_addresses.move:2:5 + │ +2 │ friend 0xdeadbeef::n; + │ ^^^^^^^^^^^^^^^^^^^^^ diff --git a/third_party/move/move-compiler-v2/tests/checking/friends/friend_different_addresses.move b/third_party/move/move-compiler-v2/tests/checking/friends/friend_different_addresses.move new file mode 100644 index 00000000000000..da22435acb39f6 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/friends/friend_different_addresses.move @@ -0,0 +1,8 @@ +module 0xc0ffee::m { + friend 0xdeadbeef::n; + public fun test() {} +} + +module 0xdeadbeef::n { + public fun test() {} +} diff --git a/third_party/move/move-model/src/lib.rs b/third_party/move/move-model/src/lib.rs index d2699ae43e04cd..ad37b73f6c2789 100644 --- a/third_party/move/move-model/src/lib.rs +++ b/third_party/move/move-model/src/lib.rs @@ -385,30 +385,99 @@ fn run_move_checker(env: &mut GlobalEnv, program: E::Program) { let module_def = expansion_script_to_module(script_def); module_translator.translate(loc, module_def, None); } + // Perform any remaining friend-declaration checks and update friend module id information. + check_and_update_friend_info(builder); + // Compute information derived from AST (currently callgraph) + for module in env.module_data.iter_mut() { + for fun_data in module.function_data.values_mut() { + fun_data.called_funs = Some( + fun_data + .def + .as_ref() + .map(|e| e.called_funs()) + .unwrap_or_default(), + ) + } + } +} +/// Checks if any friend declarations are invalid because: +/// - they are self-friend declarations +/// - they are out-of-address friend declarations +/// - they refer to unbound modules +/// If so, report errors. +/// Also, update friend module id information: this function assumes all modules have been assigned ids. +/// +/// Note: we assume (a) friend declarations creating cyclic dependencies (cycle size > 1), +/// (b) duplicate friend declarations +/// have been reported already. Currently, these checks happen in the expansion phase. +fn check_and_update_friend_info(mut builder: ModelBuilder) { let module_table = std::mem::take(&mut builder.module_table); + let env = builder.env; + // To save (loc, name) info about self friend decls. + let mut self_friends = vec![]; + // To save (loc, friend_module_name, current_module_name) info about out-of-address friend decls. + let mut out_of_address_friends = vec![]; + // To save (loc, friend_module_name) info about unbound modules in friend decls. + let mut unbound_friend_modules = vec![]; // Patch up information about friend module ids, as all the modules have ids by now. for module in env.module_data.iter_mut() { let mut friend_modules = BTreeSet::new(); for friend_decl in module.friend_decls.iter_mut() { + // Save information of out-of-address friend decls to report error later. + if friend_decl.module_name.addr() != module.name.addr() { + out_of_address_friends.push(( + friend_decl.loc.clone(), + friend_decl.module_name.clone(), + module.name.clone(), + )); + } if let Some(friend_mod_id) = module_table.get(&friend_decl.module_name) { friend_decl.module_id = Some(*friend_mod_id); friend_modules.insert(*friend_mod_id); - } // else: unresolved friend module, which should be reported elsewhere. + // Save information of self-friend decls to report error later. + if module.id == *friend_mod_id { + self_friends.push((friend_decl.loc.clone(), friend_decl.module_name.clone())); + } + } else { + // Save information of unbound modules in friend decls to report error later. + unbound_friend_modules + .push((friend_decl.loc.clone(), friend_decl.module_name.clone())); + } } module.friend_modules = friend_modules; } - // Compute information derived from AST (currently callgraph) - for module in env.module_data.iter_mut() { - for fun_data in module.function_data.values_mut() { - fun_data.called_funs = Some( - fun_data - .def - .as_ref() - .map(|e| e.called_funs()) - .unwrap_or_default(), - ) - } + // Report self-friend errors. + for (loc, module_name) in self_friends { + env.error( + &loc, + &format!( + "cannot declare module `{}` as a friend of itself", + module_name.display_full(env) + ), + ); + } + // Report out-of-address friend errors. + for (loc, friend_mod_name, cur_mod_name) in out_of_address_friends { + env.error( + &loc, + &format!( + "friend modules of `{}` must have the same address, \ + but the declared friend module `{}` has a different address", + cur_mod_name.display_full(env), + friend_mod_name.display_full(env), + ), + ); + } + // Report unbound friend errors. + for (loc, friend_mod_name) in unbound_friend_modules { + env.error( + &loc, + &format!( + "unbound module `{}` in friend declaration", + friend_mod_name.display_full(env) + ), + ); } } From 32a7ee18ae07ac0863294187fa59e2ca1fbf8b9d Mon Sep 17 00:00:00 2001 From: Vineeth Kashyap Date: Wed, 24 Jan 2024 13:25:05 -0500 Subject: [PATCH 03/11] [compiler-v2] Implement uninitialized use checker. (#11729) --- third_party/move/move-compiler-v2/src/lib.rs | 4 +- .../move/move-compiler-v2/src/pipeline/mod.rs | 3 + .../src/pipeline/uninitialized_use_checker.rs | 386 ++++++++++++++++++ .../move/move-compiler-v2/tests/testsuite.rs | 12 +- .../uninit-use-checker/assign_both_branch.exp | 52 +++ .../assign_both_branch.move | 12 + .../assign_in_one_if_branch.exp | 93 +++++ .../assign_in_one_if_branch.move | 11 + .../assign_wrong_if_branch.exp | 85 ++++ .../assign_wrong_if_branch.move | 9 + .../assign_wrong_if_branch_no_else.exp | 85 ++++ .../assign_wrong_if_branch_no_else.move | 9 + .../tests/uninit-use-checker/borrow_if.exp | 105 +++++ .../tests/uninit-use-checker/borrow_if.move | 12 + .../else_assigns_if_doesnt.exp | 100 +++++ .../else_assigns_if_doesnt.move | 15 + .../eq_unassigned_local.exp | 44 ++ .../eq_unassigned_local.move | 7 + .../if_assigns_else_doesnt.exp | 100 +++++ .../if_assigns_else_doesnt.move | 15 + .../uninit-use-checker/if_assigns_no_else.exp | 85 ++++ .../if_assigns_no_else.move | 9 + .../uninit-use-checker/move_before_assign.exp | 34 ++ .../move_before_assign.move | 8 + .../tests/uninit-use-checker/no_error.exp | 59 +++ .../tests/uninit-use-checker/no_error.move | 8 + .../uninit-use-checker/use_before_assign.exp | 29 ++ .../uninit-use-checker/use_before_assign.move | 8 + .../use_before_assign_if.exp | 165 ++++++++ .../use_before_assign_if.move | 20 + .../use_before_assign_if_else.exp | 180 ++++++++ .../use_before_assign_if_else.move | 20 + .../use_before_assign_loop.exp | 269 ++++++++++++ .../use_before_assign_loop.move | 23 ++ .../use_before_assign_simple.exp | 145 +++++++ .../use_before_assign_simple.move | 28 ++ .../use_before_assign_while.exp | 339 +++++++++++++++ .../use_before_assign_while.move | 22 + .../use_twice_before_assign.exp | 34 ++ .../use_twice_before_assign.move | 6 + .../uninit-use-checker/uses_before_assign.exp | 42 ++ .../uses_before_assign.move | 7 + .../bytecode/src/stackless_bytecode.rs | 7 + 43 files changed, 2704 insertions(+), 2 deletions(-) create mode 100644 third_party/move/move-compiler-v2/src/pipeline/uninitialized_use_checker.rs create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_both_branch.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_both_branch.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_in_one_if_branch.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_in_one_if_branch.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch_no_else.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch_no_else.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/borrow_if.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/borrow_if.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/else_assigns_if_doesnt.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/else_assigns_if_doesnt.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/eq_unassigned_local.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/eq_unassigned_local.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_else_doesnt.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_else_doesnt.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_no_else.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_no_else.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/move_before_assign.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/move_before_assign.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/no_error.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/no_error.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if_else.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if_else.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_loop.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_loop.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_simple.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_simple.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_while.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_while.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_twice_before_assign.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/use_twice_before_assign.move create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/uses_before_assign.exp create mode 100644 third_party/move/move-compiler-v2/tests/uninit-use-checker/uses_before_assign.move diff --git a/third_party/move/move-compiler-v2/src/lib.rs b/third_party/move/move-compiler-v2/src/lib.rs index 27bf63d4d3bbd1..e1ac8711643543 100644 --- a/third_party/move/move-compiler-v2/src/lib.rs +++ b/third_party/move/move-compiler-v2/src/lib.rs @@ -14,7 +14,8 @@ use crate::pipeline::{ ability_checker::AbilityChecker, avail_copies_analysis::AvailCopiesAnalysisProcessor, copy_propagation::CopyPropagation, dead_store_elimination::DeadStoreElimination, explicit_drop::ExplicitDrop, livevar_analysis_processor::LiveVarAnalysisProcessor, - reference_safety_processor::ReferenceSafetyProcessor, visibility_checker::VisibilityChecker, + reference_safety_processor::ReferenceSafetyProcessor, + uninitialized_use_checker::UninitializedUseChecker, visibility_checker::VisibilityChecker, }; use anyhow::bail; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor}; @@ -176,6 +177,7 @@ pub fn bytecode_pipeline(env: &GlobalEnv) -> FunctionTargetPipeline { let safety_on = !options.experiment_on(Experiment::NO_SAFETY); let mut pipeline = FunctionTargetPipeline::default(); if safety_on { + pipeline.add_processor(Box::new(UninitializedUseChecker {})); pipeline.add_processor(Box::new(VisibilityChecker())); } pipeline.add_processor(Box::new(LiveVarAnalysisProcessor { diff --git a/third_party/move/move-compiler-v2/src/pipeline/mod.rs b/third_party/move/move-compiler-v2/src/pipeline/mod.rs index 6381ff261d8f89..54f25dcc13e1f0 100644 --- a/third_party/move/move-compiler-v2/src/pipeline/mod.rs +++ b/third_party/move/move-compiler-v2/src/pipeline/mod.rs @@ -6,6 +6,7 @@ use crate::pipeline::{ avail_copies_analysis::AvailCopiesAnalysisProcessor, livevar_analysis_processor::LiveVarAnalysisProcessor, reference_safety_processor::ReferenceSafetyProcessor, + uninitialized_use_checker::UninitializedUseChecker, }; use move_stackless_bytecode::function_target::FunctionTarget; @@ -16,6 +17,7 @@ pub mod dead_store_elimination; pub mod explicit_drop; pub mod livevar_analysis_processor; pub mod reference_safety_processor; +pub mod uninitialized_use_checker; pub mod visibility_checker; /// Function to register all annotation formatters in the pipeline. Those are used @@ -25,4 +27,5 @@ pub fn register_formatters(target: &FunctionTarget) { LiveVarAnalysisProcessor::register_formatters(target); ReferenceSafetyProcessor::register_formatters(target); AvailCopiesAnalysisProcessor::register_formatters(target); + UninitializedUseChecker::register_formatters(target); } diff --git a/third_party/move/move-compiler-v2/src/pipeline/uninitialized_use_checker.rs b/third_party/move/move-compiler-v2/src/pipeline/uninitialized_use_checker.rs new file mode 100644 index 00000000000000..cbf3d7aa3348b2 --- /dev/null +++ b/third_party/move/move-compiler-v2/src/pipeline/uninitialized_use_checker.rs @@ -0,0 +1,386 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +//! Implements a checker which verifies that all locals are initialized before any use. +//! This intra-procedural checker does not require any other analysis to be run before. +//! As a side effect of running this checker, function targets are annotated with +//! the initialized state (yes, no, maybe) of all locals at each reachable program point. +//! +//! There are two parts to this checker: +//! * `InitializedStateAnalysis` which computes the initialized state of all locals at each +//! program point via a forward dataflow analysis. +//! * `UninitializedUseChecker` which checks that all locals are initialized before use. + +use im::Vector; +use move_binary_format::file_format::CodeOffset; +use move_model::{ast::TempIndex, model::FunctionEnv}; +use move_stackless_bytecode::{ + dataflow_analysis::{DataflowAnalysis, TransferFunctions}, + dataflow_domains::{AbstractDomain, JoinResult}, + function_target::{FunctionData, FunctionTarget}, + function_target_pipeline::{FunctionTargetProcessor, FunctionTargetsHolder}, + stackless_bytecode::Bytecode, + stackless_control_flow_graph::StacklessControlFlowGraph, +}; +use std::collections::BTreeMap; + +/// State of initialization of a local at a given program point. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Initialized { + No, // definitely not initialized + Maybe, // maybe initialized + Yes, // definitely initialized +} + +impl AbstractDomain for Initialized { + /// Implements `join` for the initialized state lattice: + /// ```diagram + /// +-------+ + /// | Maybe | + /// +-------+ + /// / \ + /// / \ + /// +-----+/ \+----+ + /// | Yes | | No | + /// +-----+\ /+----+ + /// \ / + /// \ / + /// +--------+ + /// | bottom | + /// +--------+ + /// ``` + /// Note that bottom is not explicitly represented in the enum `Initialized`. + /// Instead, it is implicit: it represents the initialized state for locals at unreachable program points. + fn join(&mut self, other: &Self) -> JoinResult { + if *self == *other { + return JoinResult::Unchanged; + } + if *self != Initialized::Maybe { + *self = Initialized::Maybe; + JoinResult::Changed + } else { + JoinResult::Unchanged + } + } +} + +/// Initialization state of all the locals at a program point. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InitializedState(Vector); + +impl InitializedState { + /// Create a new initialized state with: + /// * all param locals set to `Yes` + /// * all other locals set to `No`. + /// Note: `num_locals` is the total number of locals, including params. + pub fn new(num_params: usize, num_locals: usize) -> Self { + if num_locals < num_params { + panic!("ICE: num_locals must be >= num_params"); + } + Self(Vector::from_iter( + std::iter::repeat(Initialized::Yes) + .take(num_params) + .chain(std::iter::repeat(Initialized::No).take(num_locals - num_params)), + )) + } + + /// Mark `local` as initialized. + fn mark_as_initialized(&mut self, local: usize) { + self.0.set(local, Initialized::Yes); + } + + /// Get the initialization state of `local`. + /// Panics if `local` does not exist in this state. + fn get_initialized_state(&self, local: usize) -> Initialized { + self.0.get(local).expect("local must exist").clone() + } +} + +impl AbstractDomain for InitializedState { + /// `join` of two initialized states is the point-wise join for each local. + /// The result is `JoinResult::Changed` if any of the local joins causes a change. + fn join(&mut self, other: &Self) -> JoinResult { + let mut result = JoinResult::Unchanged; + // Do a shallow check if the two states are the same. + if self.0.ptr_eq(&other.0) { + return result; + } + // Otherwise, join each element. + for (l, r) in self.0.iter_mut().zip(other.0.iter()) { + result = result.combine(l.join(r)); + } + result + } +} + +/// Initialized state of all locals, both before and after various program points. +#[derive(Clone)] +struct InitializedStateAnnotation( + BTreeMap< + CodeOffset, // program point + ( + /*before*/ InitializedState, + /*after*/ InitializedState, + ), + >, +); + +impl InitializedStateAnnotation { + /// Get the initialized state of `local` just before the instruction at `offset`, if available. + fn get_initialized_state(&self, local: TempIndex, offset: CodeOffset) -> Option { + self.0 + .get(&offset) + .map(|(before, _)| before.get_initialized_state(local)) + } +} + +/// Analysis to compute the initialized state of all locals at each reachable program point. +/// This is an intra-procedural forward dataflow analysis. +pub struct InitializedStateAnalysis { + num_params: usize, // number of parameters in the analyzed function + num_locals: usize, // number of locals in the analyzed function, including parameters +} + +impl InitializedStateAnalysis { + /// Create a new instance of the initialized state analysis for a function. + /// The function's `num_params` and `num_locals` are provided. + /// + /// Note: `num_locals` is the total number of locals, including params. + /// Thus, `num_locals` must be >= `num_params`. + pub fn new(num_params: usize, num_locals: usize) -> Self { + Self { + num_params, + num_locals, + } + } + + /// Analyze the given function and return the initialized state of all locals before and + /// after each reachable program point. + fn analyze(&self, func_target: &FunctionTarget) -> InitializedStateAnnotation { + let code = func_target.get_bytecode(); + let cfg = StacklessControlFlowGraph::new_forward(code); + let block_state_map = self.analyze_function( + InitializedState::new(self.num_params, self.num_locals), + code, + &cfg, + ); + let per_bytecode_state = + self.state_per_instruction(block_state_map, code, &cfg, |before, after| { + (before.clone(), after.clone()) + }); + InitializedStateAnnotation(per_bytecode_state) + } +} + +impl TransferFunctions for InitializedStateAnalysis { + type State = InitializedState; + + // This is a forward analysis. + const BACKWARD: bool = false; + + fn execute(&self, state: &mut Self::State, instr: &Bytecode, _offset: CodeOffset) { + // Once you write to a local, it is considered initialized. + instr.dests().iter().for_each(|dst| { + state.mark_as_initialized(*dst); + }); + } +} + +impl DataflowAnalysis for InitializedStateAnalysis {} + +/// Checker which verifies that all locals are definitely initialized before use. +/// Violations are reported as errors. +pub struct UninitializedUseChecker {} + +impl UninitializedUseChecker { + /// Check whether all locals are definitely initialized before use in the function `target`. + /// Information about initialized state is provided in `annotation`. + /// Violations are reported as errors in the `target`'s global environment. + fn perform_checks(&self, target: &FunctionTarget, annotation: &InitializedStateAnnotation) { + for (offset, bc) in target.get_bytecode().iter().enumerate() { + if bc.is_spec_only() { + // We don't check spec-only instructions here. + continue; + } + bc.sources().iter().for_each(|src| { + if let Some(state @ (Initialized::Maybe | Initialized::No)) = + annotation.get_initialized_state(*src, offset as CodeOffset) + { + target.global_env().error( + &target.get_bytecode_loc(bc.get_attr_id()), + &format!( + "use of {}unassigned {}", + match state { + Initialized::Maybe => "possibly ", + _ => "", + }, + target.get_local_name_for_error_message(*src) + ), + ); + } + }); + } + } + + /// Registers initialized state annotation formatter at the given function target. + /// Helps with testing and debugging. + pub fn register_formatters(target: &FunctionTarget) { + target.register_annotation_formatter(Box::new(format_initialized_state_annotation)); + } +} + +impl FunctionTargetProcessor for UninitializedUseChecker { + fn process( + &self, + _targets: &mut FunctionTargetsHolder, + func_env: &FunctionEnv, + mut data: FunctionData, + _scc_opt: Option<&[FunctionEnv]>, + ) -> FunctionData { + if func_env.is_native() { + // We don't have to look inside native functions. + return data; + } + let target = FunctionTarget::new(func_env, &data); + let analysis = + InitializedStateAnalysis::new(target.get_parameter_count(), target.get_local_count()); + let annotation = analysis.analyze(&target); + self.perform_checks(&target, &annotation); + data.annotations.set(annotation, true); // for testing. + data + } + + fn name(&self) -> String { + "uninitialized_use_checker".to_string() + } +} + +// ==================================================================== +// Formatting functionality for initialized state annotation. + +/// Format the initialized state annotation for a given function target. +pub fn format_initialized_state_annotation( + target: &FunctionTarget, + code_offset: CodeOffset, +) -> Option { + let InitializedStateAnnotation(map) = target + .get_annotations() + .get::()?; + let (before, after) = map.get(&code_offset)?; + let mut s = String::new(); + s.push_str("before: "); + s.push_str(&format_initialized_state(before, target)); + s.push_str(", after: "); + s.push_str(&format_initialized_state(after, target)); + Some(s) +} + +/// Format a vector of `locals`. +/// `header` is added as a prefix. +/// `target` is used to get the name of each local symbol. +fn format_vector_of_locals( + header: &str, + locals: Vec, + target: &FunctionTarget, +) -> String { + let mut s = String::new(); + s.push_str(&format!("{{ {}: ", header)); + s.push_str( + &locals + .into_iter() + .map(|tmp| { + let name = target.get_local_raw_name(tmp); + name.display(target.symbol_pool()).to_string() + }) + .collect::>() + .join(", "), + ); + s.push_str(" }"); + s +} + +/// Format the initialized state for a given function `target`. +fn format_initialized_state(state: &InitializedState, target: &FunctionTarget) -> String { + let mut s = String::new(); + let mut nos = vec![]; + let mut maybes = vec![]; + for (i, v) in state.0.iter().enumerate() { + match v { + Initialized::No => nos.push(i), + Initialized::Maybe => maybes.push(i), + Initialized::Yes => {}, + } + } + let mut all_initialized = true; + if !nos.is_empty() { + s.push_str(&format_vector_of_locals("no", nos, target)); + all_initialized = false; + } + if !maybes.is_empty() { + s.push_str(&format_vector_of_locals("maybe", maybes, target)); + all_initialized = false; + } + if all_initialized { + s.push_str("all initialized"); + } + s +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_initialized_join() { + let mut state = Initialized::No; + assert_eq!(state.join(&Initialized::No), JoinResult::Unchanged); + assert_eq!(state, Initialized::No); + + state = Initialized::No; + assert_eq!(state.join(&Initialized::Maybe), JoinResult::Changed); + assert_eq!(state, Initialized::Maybe); + + state = Initialized::No; + assert_eq!(state.join(&Initialized::Yes), JoinResult::Changed); + assert_eq!(state, Initialized::Maybe); + + state = Initialized::Maybe; + assert_eq!(state.join(&Initialized::No), JoinResult::Unchanged); + assert_eq!(state, Initialized::Maybe); + + state = Initialized::Maybe; + assert_eq!(state.join(&Initialized::Maybe), JoinResult::Unchanged); + assert_eq!(state, Initialized::Maybe); + + state = Initialized::Maybe; + assert_eq!(state.join(&Initialized::Yes), JoinResult::Unchanged); + assert_eq!(state, Initialized::Maybe); + + state = Initialized::Yes; + assert_eq!(state.join(&Initialized::No), JoinResult::Changed); + assert_eq!(state, Initialized::Maybe); + + state = Initialized::Yes; + assert_eq!(state.join(&Initialized::Maybe), JoinResult::Changed); + assert_eq!(state, Initialized::Maybe); + + state = Initialized::Yes; + assert_eq!(state.join(&Initialized::Yes), JoinResult::Unchanged); + assert_eq!(state, Initialized::Yes); + } + + #[test] + fn test_initialized_state_join() { + let mut state = InitializedState::new(1, 2); + let mut other = InitializedState::new(1, 2); + assert_eq!(state.join(&other), JoinResult::Unchanged); + assert_eq!(state, InitializedState::new(1, 2)); + + state.mark_as_initialized(1); + assert_eq!(state.join(&other), JoinResult::Changed); + + state = InitializedState::new(1, 2); + other.mark_as_initialized(1); + assert_eq!(state.join(&other), JoinResult::Changed); + } +} diff --git a/third_party/move/move-compiler-v2/tests/testsuite.rs b/third_party/move/move-compiler-v2/tests/testsuite.rs index f4359d6aa4ce45..cd3273db5b5f8d 100644 --- a/third_party/move/move-compiler-v2/tests/testsuite.rs +++ b/third_party/move/move-compiler-v2/tests/testsuite.rs @@ -13,7 +13,7 @@ use move_compiler_v2::{ copy_propagation::CopyPropagation, dead_store_elimination::DeadStoreElimination, explicit_drop::ExplicitDrop, livevar_analysis_processor::LiveVarAnalysisProcessor, reference_safety_processor::ReferenceSafetyProcessor, - visibility_checker::VisibilityChecker, + uninitialized_use_checker::UninitializedUseChecker, visibility_checker::VisibilityChecker, }, run_file_format_gen, Options, }; @@ -261,6 +261,16 @@ impl TestConfig { // Only dump with annotations after these pipeline stages. dump_for_only_some_stages: Some(vec![0, 1, 3]), } + } else if path.contains("/uninit-use-checker/") { + pipeline.add_processor(Box::new(UninitializedUseChecker {})); + Self { + type_check_only: false, + dump_ast: false, + pipeline, + generate_file_format: false, + dump_annotated_targets: true, + dump_for_only_some_stages: None, + } } else { panic!( "unexpected test path `{}`, cannot derive configuration", diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_both_branch.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_both_branch.exp new file mode 100644 index 00000000000000..fa135c53f0cc48 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_both_branch.exp @@ -0,0 +1,52 @@ +============ initial bytecode ================ + +[variant baseline] +fun m::test($t0: bool): u64 { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + 0: if ($t0) goto 1 else goto 5 + 1: label L0 + 2: $t3 := 1 + 3: $t2 := infer($t3) + 4: goto 8 + 5: label L1 + 6: $t4 := 2 + 7: $t2 := infer($t4) + 8: label L2 + 9: $t1 := infer($t2) + 10: return $t1 +} + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun m::test($t0: bool): u64 { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t2, $t3, $t4 } + 0: if ($t0) goto 1 else goto 5 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t2, $t3, $t4 } + 1: label L0 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t2, $t4 } + 2: $t3 := 1 + # before: { no: $t1, $t2, $t4 }, after: { no: $t1, $t4 } + 3: $t2 := infer($t3) + # before: { no: $t1, $t4 }, after: { no: $t1, $t4 } + 4: goto 8 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t2, $t3, $t4 } + 5: label L1 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t2, $t3 } + 6: $t4 := 2 + # before: { no: $t1, $t2, $t3 }, after: { no: $t1, $t3 } + 7: $t2 := infer($t4) + # before: { no: $t1 }{ maybe: $t3, $t4 }, after: { no: $t1 }{ maybe: $t3, $t4 } + 8: label L2 + # before: { no: $t1 }{ maybe: $t3, $t4 }, after: { maybe: $t3, $t4 } + 9: $t1 := infer($t2) + # before: { maybe: $t3, $t4 }, after: { maybe: $t3, $t4 } + 10: return $t1 +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_both_branch.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_both_branch.move new file mode 100644 index 00000000000000..3c87a36ab84a18 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_both_branch.move @@ -0,0 +1,12 @@ +module 0xc0ffee::m { + fun test(cond: bool): u64 { + let x: u64; + if (cond) { + x = 1; + } else { + x = 2; + }; + x + } + +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_in_one_if_branch.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_in_one_if_branch.exp new file mode 100644 index 00000000000000..e90261b57460c2 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_in_one_if_branch.exp @@ -0,0 +1,93 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: bool + var $t3: u64 + var $t4: bool + var $t5: u64 + var $t6: bool + 0: $t2 := true + 1: if ($t2) goto 2 else goto 6 + 2: label L0 + 3: $t3 := 5 + 4: $t0 := infer($t3) + 5: goto 7 + 6: label L1 + 7: label L2 + 8: $t4 := true + 9: if ($t4) goto 10 else goto 14 + 10: label L3 + 11: $t5 := 5 + 12: $t1 := infer($t5) + 13: goto 15 + 14: label L4 + 15: label L5 + 16: $t6 := ==($t0, $t1) + 17: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/assign_in_one_if_branch.move:7:5 + │ +7 │ x == y; + │ ^^^^^^ + +error: use of possibly unassigned local `y` + ┌─ tests/uninit-use-checker/assign_in_one_if_branch.move:7:5 + │ +7 │ x == y; + │ ^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: bool + var $t3: u64 + var $t4: bool + var $t5: u64 + var $t6: bool + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5, $t6 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6 } + 0: $t2 := true + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6 } + 1: if ($t2) goto 2 else goto 6 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6 } + 2: label L0 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6 }, after: { no: $t0, $t1, $t4, $t5, $t6 } + 3: $t3 := 5 + # before: { no: $t0, $t1, $t4, $t5, $t6 }, after: { no: $t1, $t4, $t5, $t6 } + 4: $t0 := infer($t3) + # before: { no: $t1, $t4, $t5, $t6 }, after: { no: $t1, $t4, $t5, $t6 } + 5: goto 7 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6 } + 6: label L1 + # before: { no: $t1, $t4, $t5, $t6 }{ maybe: $t0, $t3 }, after: { no: $t1, $t4, $t5, $t6 }{ maybe: $t0, $t3 } + 7: label L2 + # before: { no: $t1, $t4, $t5, $t6 }{ maybe: $t0, $t3 }, after: { no: $t1, $t5, $t6 }{ maybe: $t0, $t3 } + 8: $t4 := true + # before: { no: $t1, $t5, $t6 }{ maybe: $t0, $t3 }, after: { no: $t1, $t5, $t6 }{ maybe: $t0, $t3 } + 9: if ($t4) goto 10 else goto 14 + # before: { no: $t1, $t5, $t6 }{ maybe: $t0, $t3 }, after: { no: $t1, $t5, $t6 }{ maybe: $t0, $t3 } + 10: label L3 + # before: { no: $t1, $t5, $t6 }{ maybe: $t0, $t3 }, after: { no: $t1, $t6 }{ maybe: $t0, $t3 } + 11: $t5 := 5 + # before: { no: $t1, $t6 }{ maybe: $t0, $t3 }, after: { no: $t6 }{ maybe: $t0, $t3 } + 12: $t1 := infer($t5) + # before: { no: $t6 }{ maybe: $t0, $t3 }, after: { no: $t6 }{ maybe: $t0, $t3 } + 13: goto 15 + # before: { no: $t1, $t5, $t6 }{ maybe: $t0, $t3 }, after: { no: $t1, $t5, $t6 }{ maybe: $t0, $t3 } + 14: label L4 + # before: { no: $t6 }{ maybe: $t0, $t1, $t3, $t5 }, after: { no: $t6 }{ maybe: $t0, $t1, $t3, $t5 } + 15: label L5 + # before: { no: $t6 }{ maybe: $t0, $t1, $t3, $t5 }, after: { maybe: $t0, $t1, $t3, $t5 } + 16: $t6 := ==($t0, $t1) + # before: { maybe: $t0, $t1, $t3, $t5 }, after: { maybe: $t0, $t1, $t3, $t5 } + 17: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_in_one_if_branch.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_in_one_if_branch.move new file mode 100644 index 00000000000000..17afcfc0c42125 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_in_one_if_branch.move @@ -0,0 +1,11 @@ +script { +fun main() { + let x; + let y; + if (true) x = 5 else (); + if (true) y = 5; + x == y; +} +} + +// check: COPYLOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch.exp new file mode 100644 index 00000000000000..dbfa258d75ce5f --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch.exp @@ -0,0 +1,85 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: bool + var $t2: u64 + var $t3: bool + var $t4: u64 + var $t5: u64 + 0: $t1 := true + 1: if ($t1) goto 2 else goto 4 + 2: label L0 + 3: goto 7 + 4: label L1 + 5: $t2 := 100 + 6: $t0 := infer($t2) + 7: label L2 + 8: $t4 := 100 + 9: $t3 := ==($t0, $t4) + 10: if ($t3) goto 11 else goto 13 + 11: label L3 + 12: goto 16 + 13: label L4 + 14: $t5 := 42 + 15: abort($t5) + 16: label L5 + 17: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/assign_wrong_if_branch.move:5:13 + │ +5 │ assert!(x == 100, 42); + │ ^^^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: bool + var $t2: u64 + var $t3: bool + var $t4: u64 + var $t5: u64 + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 0: $t1 := true + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 1: if ($t1) goto 2 else goto 4 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 2: label L0 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 3: goto 7 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 4: label L1 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t3, $t4, $t5 } + 5: $t2 := 100 + # before: { no: $t0, $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 6: $t0 := infer($t2) + # before: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 }, after: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 } + 7: label L2 + # before: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 }, after: { no: $t3, $t5 }{ maybe: $t0, $t2 } + 8: $t4 := 100 + # before: { no: $t3, $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 9: $t3 := ==($t0, $t4) + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 10: if ($t3) goto 11 else goto 13 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 11: label L3 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 12: goto 16 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 13: label L4 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { maybe: $t0, $t2 } + 14: $t5 := 42 + # before: { maybe: $t0, $t2 }, after: { maybe: $t0, $t2 } + 15: abort($t5) + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 16: label L5 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 17: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch.move new file mode 100644 index 00000000000000..5ba1a27494bbb7 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch.move @@ -0,0 +1,9 @@ +script { +fun main() { + let x: u64; + if (true) () else x = 100; + assert!(x == 100, 42); +} +} + +// check: COPYLOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch_no_else.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch_no_else.exp new file mode 100644 index 00000000000000..721613aa100d22 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch_no_else.exp @@ -0,0 +1,85 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: bool + var $t2: u64 + var $t3: bool + var $t4: u64 + var $t5: u64 + 0: $t1 := false + 1: if ($t1) goto 2 else goto 6 + 2: label L0 + 3: $t2 := 100 + 4: $t0 := infer($t2) + 5: goto 7 + 6: label L1 + 7: label L2 + 8: $t4 := 100 + 9: $t3 := ==($t0, $t4) + 10: if ($t3) goto 11 else goto 13 + 11: label L3 + 12: goto 16 + 13: label L4 + 14: $t5 := 42 + 15: abort($t5) + 16: label L5 + 17: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/assign_wrong_if_branch_no_else.move:5:13 + │ +5 │ assert!(x == 100, 42); + │ ^^^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: bool + var $t2: u64 + var $t3: bool + var $t4: u64 + var $t5: u64 + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 0: $t1 := false + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 1: if ($t1) goto 2 else goto 6 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 2: label L0 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t3, $t4, $t5 } + 3: $t2 := 100 + # before: { no: $t0, $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 4: $t0 := infer($t2) + # before: { no: $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 5: goto 7 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 6: label L1 + # before: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 }, after: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 } + 7: label L2 + # before: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 }, after: { no: $t3, $t5 }{ maybe: $t0, $t2 } + 8: $t4 := 100 + # before: { no: $t3, $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 9: $t3 := ==($t0, $t4) + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 10: if ($t3) goto 11 else goto 13 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 11: label L3 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 12: goto 16 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 13: label L4 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { maybe: $t0, $t2 } + 14: $t5 := 42 + # before: { maybe: $t0, $t2 }, after: { maybe: $t0, $t2 } + 15: abort($t5) + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 16: label L5 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 17: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch_no_else.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch_no_else.move new file mode 100644 index 00000000000000..60b5aae60bcc7d --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/assign_wrong_if_branch_no_else.move @@ -0,0 +1,9 @@ +script { +fun main() { + let x: u64; + if (false) x = 100; + assert!(x == 100, 42); +} +} + +// check: COPYLOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/borrow_if.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/borrow_if.exp new file mode 100644 index 00000000000000..212f7cf4d3a8c8 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/borrow_if.exp @@ -0,0 +1,105 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: &u64 + var $t3: bool + var $t4: &u64 + var $t5: bool + var $t6: u64 + var $t7: &u64 + var $t8: u64 + var $t9: u64 + 0: $t1 := 5 + 1: $t0 := infer($t1) + 2: $t3 := true + 3: if ($t3) goto 4 else goto 8 + 4: label L0 + 5: $t4 := borrow_local($t0) + 6: $t2 := infer($t4) + 7: goto 9 + 8: label L1 + 9: label L2 + 10: $t7 := move($t2) + 11: $t6 := read_ref($t7) + 12: $t8 := 5 + 13: $t5 := ==($t6, $t8) + 14: if ($t5) goto 15 else goto 17 + 15: label L3 + 16: goto 20 + 17: label L4 + 18: $t9 := 42 + 19: abort($t9) + 20: label L5 + 21: return () +} + + +Diagnostics: +error: use of possibly unassigned local `ref` + ┌─ tests/uninit-use-checker/borrow_if.move:8:14 + │ +8 │ assert!(*move ref == 5, 42); + │ ^^^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: &u64 + var $t3: bool + var $t4: &u64 + var $t5: bool + var $t6: u64 + var $t7: &u64 + var $t8: u64 + var $t9: u64 + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9 }, after: { no: $t0, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9 } + 0: $t1 := 5 + # before: { no: $t0, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9 }, after: { no: $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9 } + 1: $t0 := infer($t1) + # before: { no: $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9 }, after: { no: $t2, $t4, $t5, $t6, $t7, $t8, $t9 } + 2: $t3 := true + # before: { no: $t2, $t4, $t5, $t6, $t7, $t8, $t9 }, after: { no: $t2, $t4, $t5, $t6, $t7, $t8, $t9 } + 3: if ($t3) goto 4 else goto 8 + # before: { no: $t2, $t4, $t5, $t6, $t7, $t8, $t9 }, after: { no: $t2, $t4, $t5, $t6, $t7, $t8, $t9 } + 4: label L0 + # before: { no: $t2, $t4, $t5, $t6, $t7, $t8, $t9 }, after: { no: $t2, $t5, $t6, $t7, $t8, $t9 } + 5: $t4 := borrow_local($t0) + # before: { no: $t2, $t5, $t6, $t7, $t8, $t9 }, after: { no: $t5, $t6, $t7, $t8, $t9 } + 6: $t2 := infer($t4) + # before: { no: $t5, $t6, $t7, $t8, $t9 }, after: { no: $t5, $t6, $t7, $t8, $t9 } + 7: goto 9 + # before: { no: $t2, $t4, $t5, $t6, $t7, $t8, $t9 }, after: { no: $t2, $t4, $t5, $t6, $t7, $t8, $t9 } + 8: label L1 + # before: { no: $t5, $t6, $t7, $t8, $t9 }{ maybe: $t2, $t4 }, after: { no: $t5, $t6, $t7, $t8, $t9 }{ maybe: $t2, $t4 } + 9: label L2 + # before: { no: $t5, $t6, $t7, $t8, $t9 }{ maybe: $t2, $t4 }, after: { no: $t5, $t6, $t8, $t9 }{ maybe: $t2, $t4 } + 10: $t7 := move($t2) + # before: { no: $t5, $t6, $t8, $t9 }{ maybe: $t2, $t4 }, after: { no: $t5, $t8, $t9 }{ maybe: $t2, $t4 } + 11: $t6 := read_ref($t7) + # before: { no: $t5, $t8, $t9 }{ maybe: $t2, $t4 }, after: { no: $t5, $t9 }{ maybe: $t2, $t4 } + 12: $t8 := 5 + # before: { no: $t5, $t9 }{ maybe: $t2, $t4 }, after: { no: $t9 }{ maybe: $t2, $t4 } + 13: $t5 := ==($t6, $t8) + # before: { no: $t9 }{ maybe: $t2, $t4 }, after: { no: $t9 }{ maybe: $t2, $t4 } + 14: if ($t5) goto 15 else goto 17 + # before: { no: $t9 }{ maybe: $t2, $t4 }, after: { no: $t9 }{ maybe: $t2, $t4 } + 15: label L3 + # before: { no: $t9 }{ maybe: $t2, $t4 }, after: { no: $t9 }{ maybe: $t2, $t4 } + 16: goto 20 + # before: { no: $t9 }{ maybe: $t2, $t4 }, after: { no: $t9 }{ maybe: $t2, $t4 } + 17: label L4 + # before: { no: $t9 }{ maybe: $t2, $t4 }, after: { maybe: $t2, $t4 } + 18: $t9 := 42 + # before: { maybe: $t2, $t4 }, after: { maybe: $t2, $t4 } + 19: abort($t9) + # before: { no: $t9 }{ maybe: $t2, $t4 }, after: { no: $t9 }{ maybe: $t2, $t4 } + 20: label L5 + # before: { no: $t9 }{ maybe: $t2, $t4 }, after: { no: $t9 }{ maybe: $t2, $t4 } + 21: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/borrow_if.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/borrow_if.move new file mode 100644 index 00000000000000..b7c2395645b73f --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/borrow_if.move @@ -0,0 +1,12 @@ +script { +fun main() { + let x = 5; + let ref; + if (true) { + ref = &x; + }; + assert!(*move ref == 5, 42); +} +} + +// check: MOVELOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/else_assigns_if_doesnt.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/else_assigns_if_doesnt.exp new file mode 100644 index 00000000000000..785585e3019861 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/else_assigns_if_doesnt.exp @@ -0,0 +1,100 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: bool + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: bool + var $t7: u64 + var $t8: u64 + 0: $t2 := true + 1: if ($t2) goto 2 else goto 6 + 2: label L0 + 3: $t3 := 0 + 4: $t1 := infer($t3) + 5: goto 10 + 6: label L1 + 7: $t4 := 42 + 8: $t0 := infer($t4) + 9: $t5 := infer($t0) + 10: label L2 + 11: $t7 := 0 + 12: $t6 := ==($t1, $t7) + 13: if ($t6) goto 14 else goto 16 + 14: label L3 + 15: goto 19 + 16: label L4 + 17: $t8 := 42 + 18: abort($t8) + 19: label L5 + 20: return () +} + + +Diagnostics: +error: use of possibly unassigned local `y` + ┌─ tests/uninit-use-checker/else_assigns_if_doesnt.move:11:13 + │ +11 │ assert!(y == 0, 42); + │ ^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: bool + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: bool + var $t7: u64 + var $t8: u64 + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 } + 0: $t2 := true + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 } + 1: if ($t2) goto 2 else goto 6 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 } + 2: label L0 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t4, $t5, $t6, $t7, $t8 } + 3: $t3 := 0 + # before: { no: $t0, $t1, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t4, $t5, $t6, $t7, $t8 } + 4: $t1 := infer($t3) + # before: { no: $t0, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t4, $t5, $t6, $t7, $t8 } + 5: goto 10 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 } + 6: label L1 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t5, $t6, $t7, $t8 } + 7: $t4 := 42 + # before: { no: $t0, $t1, $t3, $t5, $t6, $t7, $t8 }, after: { no: $t1, $t3, $t5, $t6, $t7, $t8 } + 8: $t0 := infer($t4) + # before: { no: $t1, $t3, $t5, $t6, $t7, $t8 }, after: { no: $t1, $t3, $t6, $t7, $t8 } + 9: $t5 := infer($t0) + # before: { no: $t6, $t7, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t6, $t7, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 10: label L2 + # before: { no: $t6, $t7, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t6, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 11: $t7 := 0 + # before: { no: $t6, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 12: $t6 := ==($t1, $t7) + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 13: if ($t6) goto 14 else goto 16 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 14: label L3 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 15: goto 19 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 16: label L4 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { maybe: $t0, $t1, $t3, $t4, $t5 } + 17: $t8 := 42 + # before: { maybe: $t0, $t1, $t3, $t4, $t5 }, after: { maybe: $t0, $t1, $t3, $t4, $t5 } + 18: abort($t8) + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 19: label L5 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 20: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/else_assigns_if_doesnt.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/else_assigns_if_doesnt.move new file mode 100644 index 00000000000000..819096298e8762 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/else_assigns_if_doesnt.move @@ -0,0 +1,15 @@ +script { +fun main() { + let x; + let y; + if (true) { + y = 0; + } else { + x = 42; + x; + }; + assert!(y == 0, 42); +} +} + +// check: COPYLOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/eq_unassigned_local.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/eq_unassigned_local.exp new file mode 100644 index 00000000000000..29e7a6a8994e87 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/eq_unassigned_local.exp @@ -0,0 +1,44 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: &u64 + var $t3: bool + var $t4: &u64 + 0: $t1 := 5 + 1: $t0 := infer($t1) + 2: $t4 := borrow_local($t0) + 3: $t3 := ==($t2, $t4) + 4: return () +} + + +Diagnostics: +error: use of unassigned local `ref` + ┌─ tests/uninit-use-checker/eq_unassigned_local.move:5:9 + │ +5 │ ref == &x; + │ ^^^^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: &u64 + var $t3: bool + var $t4: &u64 + # before: { no: $t0, $t1, $t2, $t3, $t4 }, after: { no: $t0, $t2, $t3, $t4 } + 0: $t1 := 5 + # before: { no: $t0, $t2, $t3, $t4 }, after: { no: $t2, $t3, $t4 } + 1: $t0 := infer($t1) + # before: { no: $t2, $t3, $t4 }, after: { no: $t2, $t3 } + 2: $t4 := borrow_local($t0) + # before: { no: $t2, $t3 }, after: { no: $t2 } + 3: $t3 := ==($t2, $t4) + # before: { no: $t2 }, after: { no: $t2 } + 4: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/eq_unassigned_local.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/eq_unassigned_local.move new file mode 100644 index 00000000000000..aabb6e2e56d629 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/eq_unassigned_local.move @@ -0,0 +1,7 @@ +script { + fun main() { + let x = 5; + let ref; + ref == &x; + } +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_else_doesnt.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_else_doesnt.exp new file mode 100644 index 00000000000000..64d79c71a31937 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_else_doesnt.exp @@ -0,0 +1,100 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: bool + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: bool + var $t7: u64 + var $t8: u64 + 0: $t2 := true + 1: if ($t2) goto 2 else goto 6 + 2: label L0 + 3: $t3 := 42 + 4: $t0 := infer($t3) + 5: goto 10 + 6: label L1 + 7: $t4 := 0 + 8: $t1 := infer($t4) + 9: $t5 := infer($t1) + 10: label L2 + 11: $t7 := 42 + 12: $t6 := ==($t0, $t7) + 13: if ($t6) goto 14 else goto 16 + 14: label L3 + 15: goto 19 + 16: label L4 + 17: $t8 := 42 + 18: abort($t8) + 19: label L5 + 20: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/if_assigns_else_doesnt.move:11:13 + │ +11 │ assert!(x == 42, 42); + │ ^^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: bool + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: bool + var $t7: u64 + var $t8: u64 + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 } + 0: $t2 := true + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 } + 1: if ($t2) goto 2 else goto 6 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 } + 2: label L0 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t4, $t5, $t6, $t7, $t8 } + 3: $t3 := 42 + # before: { no: $t0, $t1, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t1, $t4, $t5, $t6, $t7, $t8 } + 4: $t0 := infer($t3) + # before: { no: $t1, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t1, $t4, $t5, $t6, $t7, $t8 } + 5: goto 10 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 } + 6: label L1 + # before: { no: $t0, $t1, $t3, $t4, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t1, $t3, $t5, $t6, $t7, $t8 } + 7: $t4 := 0 + # before: { no: $t0, $t1, $t3, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t3, $t5, $t6, $t7, $t8 } + 8: $t1 := infer($t4) + # before: { no: $t0, $t3, $t5, $t6, $t7, $t8 }, after: { no: $t0, $t3, $t6, $t7, $t8 } + 9: $t5 := infer($t1) + # before: { no: $t6, $t7, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t6, $t7, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 10: label L2 + # before: { no: $t6, $t7, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t6, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 11: $t7 := 42 + # before: { no: $t6, $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 12: $t6 := ==($t0, $t7) + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 13: if ($t6) goto 14 else goto 16 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 14: label L3 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 15: goto 19 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 16: label L4 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { maybe: $t0, $t1, $t3, $t4, $t5 } + 17: $t8 := 42 + # before: { maybe: $t0, $t1, $t3, $t4, $t5 }, after: { maybe: $t0, $t1, $t3, $t4, $t5 } + 18: abort($t8) + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 19: label L5 + # before: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t8 }{ maybe: $t0, $t1, $t3, $t4, $t5 } + 20: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_else_doesnt.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_else_doesnt.move new file mode 100644 index 00000000000000..532e51accb97f9 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_else_doesnt.move @@ -0,0 +1,15 @@ +script { +fun main() { + let x; + let y; + if (true) { + x = 42; + } else { + y = 0; + y; + }; + assert!(x == 42, 42); +} +} + +// check: COPYLOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_no_else.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_no_else.exp new file mode 100644 index 00000000000000..693ad135e5ecd2 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_no_else.exp @@ -0,0 +1,85 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: bool + var $t2: u64 + var $t3: bool + var $t4: u64 + var $t5: u64 + 0: $t1 := true + 1: if ($t1) goto 2 else goto 6 + 2: label L0 + 3: $t2 := 42 + 4: $t0 := infer($t2) + 5: goto 7 + 6: label L1 + 7: label L2 + 8: $t4 := 42 + 9: $t3 := ==($t0, $t4) + 10: if ($t3) goto 11 else goto 13 + 11: label L3 + 12: goto 16 + 13: label L4 + 14: $t5 := 42 + 15: abort($t5) + 16: label L5 + 17: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/if_assigns_no_else.move:5:13 + │ +5 │ assert!(x == 42, 42); + │ ^^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: bool + var $t2: u64 + var $t3: bool + var $t4: u64 + var $t5: u64 + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 0: $t1 := true + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 1: if ($t1) goto 2 else goto 6 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 2: label L0 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t3, $t4, $t5 } + 3: $t2 := 42 + # before: { no: $t0, $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 4: $t0 := infer($t2) + # before: { no: $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 5: goto 7 + # before: { no: $t0, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t2, $t3, $t4, $t5 } + 6: label L1 + # before: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 }, after: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 } + 7: label L2 + # before: { no: $t3, $t4, $t5 }{ maybe: $t0, $t2 }, after: { no: $t3, $t5 }{ maybe: $t0, $t2 } + 8: $t4 := 42 + # before: { no: $t3, $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 9: $t3 := ==($t0, $t4) + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 10: if ($t3) goto 11 else goto 13 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 11: label L3 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 12: goto 16 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 13: label L4 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { maybe: $t0, $t2 } + 14: $t5 := 42 + # before: { maybe: $t0, $t2 }, after: { maybe: $t0, $t2 } + 15: abort($t5) + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 16: label L5 + # before: { no: $t5 }{ maybe: $t0, $t2 }, after: { no: $t5 }{ maybe: $t0, $t2 } + 17: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_no_else.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_no_else.move new file mode 100644 index 00000000000000..ba8c29c6e0ea57 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/if_assigns_no_else.move @@ -0,0 +1,9 @@ +script { +fun main() { + let x; + if (true) x = 42; + assert!(x == 42, 42); +} +} + +// check: COPYLOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/move_before_assign.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/move_before_assign.exp new file mode 100644 index 00000000000000..8edad599324bfb --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/move_before_assign.exp @@ -0,0 +1,34 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + 0: $t2 := move($t0) + 1: $t1 := infer($t2) + 2: return () +} + + +Diagnostics: +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/move_before_assign.move:4:13 + │ +4 │ let y = move x; + │ ^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + # before: { no: $t0, $t1, $t2 }, after: { no: $t0, $t1 } + 0: $t2 := move($t0) + # before: { no: $t0, $t1 }, after: { no: $t0 } + 1: $t1 := infer($t2) + # before: { no: $t0 }, after: { no: $t0 } + 2: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/move_before_assign.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/move_before_assign.move new file mode 100644 index 00000000000000..5af97478ca9e03 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/move_before_assign.move @@ -0,0 +1,8 @@ +script { +fun main() { + let x: u64; + let y = move x; +} +} + +// check: MOVELOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/no_error.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/no_error.exp new file mode 100644 index 00000000000000..0163dd1a2badba --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/no_error.exp @@ -0,0 +1,59 @@ +============ initial bytecode ================ + +[variant baseline] +fun m::foo($t0: u64, $t1: u64): u64 { + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + var $t7: u64 + var $t8: u64 + var $t9: u64 + var $t10: u64 + 0: $t4 := +($t0, $t1) + 1: $t3 := infer($t4) + 2: $t7 := 1 + 3: $t6 := +($t3, $t7) + 4: $t5 := infer($t6) + 5: $t10 := 1 + 6: $t9 := +($t5, $t10) + 7: $t8 := infer($t9) + 8: $t2 := infer($t8) + 9: return $t2 +} + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun m::foo($t0: u64, $t1: u64): u64 { + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + var $t7: u64 + var $t8: u64 + var $t9: u64 + var $t10: u64 + # before: { no: $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10 }, after: { no: $t2, $t3, $t5, $t6, $t7, $t8, $t9, $t10 } + 0: $t4 := +($t0, $t1) + # before: { no: $t2, $t3, $t5, $t6, $t7, $t8, $t9, $t10 }, after: { no: $t2, $t5, $t6, $t7, $t8, $t9, $t10 } + 1: $t3 := infer($t4) + # before: { no: $t2, $t5, $t6, $t7, $t8, $t9, $t10 }, after: { no: $t2, $t5, $t6, $t8, $t9, $t10 } + 2: $t7 := 1 + # before: { no: $t2, $t5, $t6, $t8, $t9, $t10 }, after: { no: $t2, $t5, $t8, $t9, $t10 } + 3: $t6 := +($t3, $t7) + # before: { no: $t2, $t5, $t8, $t9, $t10 }, after: { no: $t2, $t8, $t9, $t10 } + 4: $t5 := infer($t6) + # before: { no: $t2, $t8, $t9, $t10 }, after: { no: $t2, $t8, $t9 } + 5: $t10 := 1 + # before: { no: $t2, $t8, $t9 }, after: { no: $t2, $t8 } + 6: $t9 := +($t5, $t10) + # before: { no: $t2, $t8 }, after: { no: $t2 } + 7: $t8 := infer($t9) + # before: { no: $t2 }, after: all initialized + 8: $t2 := infer($t8) + # before: all initialized, after: all initialized + 9: return $t2 +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/no_error.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/no_error.move new file mode 100644 index 00000000000000..4ed39ff07e7a27 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/no_error.move @@ -0,0 +1,8 @@ +module 0xc0::m { + fun foo(p: u64, q: u64): u64 { + let x = p + q; + let y = x + 1; + let z = y + 1; + z + } +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign.exp new file mode 100644 index 00000000000000..9ff8f4af896660 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign.exp @@ -0,0 +1,29 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + 0: $t1 := infer($t0) + 1: return () +} + + +Diagnostics: +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign.move:4:9 + │ +4 │ let y = x; + │ ^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + # before: { no: $t0, $t1 }, after: { no: $t0 } + 0: $t1 := infer($t0) + # before: { no: $t0 }, after: { no: $t0 } + 1: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign.move new file mode 100644 index 00000000000000..0baa595e4be513 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign.move @@ -0,0 +1,8 @@ +script { +fun main() { + let x: u64; + let y = x; +} +} + +// check: COPYLOC_UNAVAILABLE_ERROR diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if.exp new file mode 100644 index 00000000000000..0e05f2d0422d58 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if.exp @@ -0,0 +1,165 @@ +============ initial bytecode ================ + +[variant baseline] +fun M::tborrow($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: &u64 + 0: if ($t0) goto 1 else goto 5 + 1: label L0 + 2: $t2 := 0 + 3: $t1 := infer($t2) + 4: goto 6 + 5: label L1 + 6: label L2 + 7: $t3 := borrow_local($t1) + 8: return () +} + + +[variant baseline] +fun M::tcopy($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + 0: if ($t0) goto 1 else goto 5 + 1: label L0 + 2: $t2 := 0 + 3: $t1 := infer($t2) + 4: goto 6 + 5: label L1 + 6: label L2 + 7: $t4 := 1 + 8: $t3 := +($t1, $t4) + 9: return () +} + + +[variant baseline] +fun M::tmove($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + 0: if ($t0) goto 1 else goto 5 + 1: label L0 + 2: $t2 := 0 + 3: $t1 := infer($t2) + 4: goto 6 + 5: label L1 + 6: label L2 + 7: $t4 := move($t1) + 8: $t5 := 1 + 9: $t3 := +($t4, $t5) + 10: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_if.move:5:17 + │ +5 │ let _ = move x + 1; + │ ^^^^^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_if.move:11:17 + │ +11 │ let _ = x + 1; + │ ^^^^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_if.move:17:17 + │ +17 │ let _ = &x; + │ ^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun M::tborrow($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: &u64 + # before: { no: $t1, $t2, $t3 }, after: { no: $t1, $t2, $t3 } + 0: if ($t0) goto 1 else goto 5 + # before: { no: $t1, $t2, $t3 }, after: { no: $t1, $t2, $t3 } + 1: label L0 + # before: { no: $t1, $t2, $t3 }, after: { no: $t1, $t3 } + 2: $t2 := 0 + # before: { no: $t1, $t3 }, after: { no: $t3 } + 3: $t1 := infer($t2) + # before: { no: $t3 }, after: { no: $t3 } + 4: goto 6 + # before: { no: $t1, $t2, $t3 }, after: { no: $t1, $t2, $t3 } + 5: label L1 + # before: { no: $t3 }{ maybe: $t1, $t2 }, after: { no: $t3 }{ maybe: $t1, $t2 } + 6: label L2 + # before: { no: $t3 }{ maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 7: $t3 := borrow_local($t1) + # before: { maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 8: return () +} + + +[variant baseline] +fun M::tcopy($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t2, $t3, $t4 } + 0: if ($t0) goto 1 else goto 5 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t2, $t3, $t4 } + 1: label L0 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t3, $t4 } + 2: $t2 := 0 + # before: { no: $t1, $t3, $t4 }, after: { no: $t3, $t4 } + 3: $t1 := infer($t2) + # before: { no: $t3, $t4 }, after: { no: $t3, $t4 } + 4: goto 6 + # before: { no: $t1, $t2, $t3, $t4 }, after: { no: $t1, $t2, $t3, $t4 } + 5: label L1 + # before: { no: $t3, $t4 }{ maybe: $t1, $t2 }, after: { no: $t3, $t4 }{ maybe: $t1, $t2 } + 6: label L2 + # before: { no: $t3, $t4 }{ maybe: $t1, $t2 }, after: { no: $t3 }{ maybe: $t1, $t2 } + 7: $t4 := 1 + # before: { no: $t3 }{ maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 8: $t3 := +($t1, $t4) + # before: { maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 9: return () +} + + +[variant baseline] +fun M::tmove($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 0: if ($t0) goto 1 else goto 5 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 1: label L0 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t3, $t4, $t5 } + 2: $t2 := 0 + # before: { no: $t1, $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 3: $t1 := infer($t2) + # before: { no: $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 4: goto 6 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 5: label L1 + # before: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 } + 6: label L2 + # before: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3, $t5 }{ maybe: $t1, $t2 } + 7: $t4 := move($t1) + # before: { no: $t3, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3 }{ maybe: $t1, $t2 } + 8: $t5 := 1 + # before: { no: $t3 }{ maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 9: $t3 := +($t4, $t5) + # before: { maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 10: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if.move new file mode 100644 index 00000000000000..8952f5f7bd8e3e --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if.move @@ -0,0 +1,20 @@ +module 0x8675309::M { + fun tmove(cond: bool) { + let x: u64; + if (cond) { x = 0 }; + let _ = move x + 1; + } + + fun tcopy(cond: bool) { + let x: u64; + if (cond) { x = 0 }; + let _ = x + 1; + } + + fun tborrow(cond: bool) { + let x: u64; + if (cond) { x = 0 }; + let _ = &x; + } + +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if_else.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if_else.exp new file mode 100644 index 00000000000000..3e562b05030dfd --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if_else.exp @@ -0,0 +1,180 @@ +============ initial bytecode ================ + +[variant baseline] +fun M::tborrow($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + 0: if ($t0) goto 1 else goto 3 + 1: label L0 + 2: goto 6 + 3: label L1 + 4: $t2 := 0 + 5: $t1 := infer($t2) + 6: label L2 + 7: $t4 := move($t1) + 8: $t5 := 1 + 9: $t3 := +($t4, $t5) + 10: return () +} + + +[variant baseline] +fun M::tcopy($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + 0: if ($t0) goto 1 else goto 3 + 1: label L0 + 2: goto 6 + 3: label L1 + 4: $t2 := 0 + 5: $t1 := infer($t2) + 6: label L2 + 7: $t4 := move($t1) + 8: $t5 := 1 + 9: $t3 := +($t4, $t5) + 10: return () +} + + +[variant baseline] +fun M::tmove($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + 0: if ($t0) goto 1 else goto 3 + 1: label L0 + 2: goto 6 + 3: label L1 + 4: $t2 := 0 + 5: $t1 := infer($t2) + 6: label L2 + 7: $t4 := move($t1) + 8: $t5 := 1 + 9: $t3 := +($t4, $t5) + 10: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_if_else.move:5:17 + │ +5 │ let _ = move x + 1; + │ ^^^^^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_if_else.move:11:17 + │ +11 │ let _ = move x + 1; + │ ^^^^^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_if_else.move:17:17 + │ +17 │ let _ = move x + 1; + │ ^^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun M::tborrow($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 0: if ($t0) goto 1 else goto 3 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 1: label L0 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 2: goto 6 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 3: label L1 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t3, $t4, $t5 } + 4: $t2 := 0 + # before: { no: $t1, $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 5: $t1 := infer($t2) + # before: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 } + 6: label L2 + # before: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3, $t5 }{ maybe: $t1, $t2 } + 7: $t4 := move($t1) + # before: { no: $t3, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3 }{ maybe: $t1, $t2 } + 8: $t5 := 1 + # before: { no: $t3 }{ maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 9: $t3 := +($t4, $t5) + # before: { maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 10: return () +} + + +[variant baseline] +fun M::tcopy($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 0: if ($t0) goto 1 else goto 3 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 1: label L0 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 2: goto 6 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 3: label L1 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t3, $t4, $t5 } + 4: $t2 := 0 + # before: { no: $t1, $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 5: $t1 := infer($t2) + # before: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 } + 6: label L2 + # before: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3, $t5 }{ maybe: $t1, $t2 } + 7: $t4 := move($t1) + # before: { no: $t3, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3 }{ maybe: $t1, $t2 } + 8: $t5 := 1 + # before: { no: $t3 }{ maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 9: $t3 := +($t4, $t5) + # before: { maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 10: return () +} + + +[variant baseline] +fun M::tmove($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 0: if ($t0) goto 1 else goto 3 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 1: label L0 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 2: goto 6 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 3: label L1 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t3, $t4, $t5 } + 4: $t2 := 0 + # before: { no: $t1, $t3, $t4, $t5 }, after: { no: $t3, $t4, $t5 } + 5: $t1 := infer($t2) + # before: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 } + 6: label L2 + # before: { no: $t3, $t4, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3, $t5 }{ maybe: $t1, $t2 } + 7: $t4 := move($t1) + # before: { no: $t3, $t5 }{ maybe: $t1, $t2 }, after: { no: $t3 }{ maybe: $t1, $t2 } + 8: $t5 := 1 + # before: { no: $t3 }{ maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 9: $t3 := +($t4, $t5) + # before: { maybe: $t1, $t2 }, after: { maybe: $t1, $t2 } + 10: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if_else.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if_else.move new file mode 100644 index 00000000000000..f0eba9c1eee53c --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_if_else.move @@ -0,0 +1,20 @@ +module 0x8675309::M { + fun tmove(cond: bool) { + let x: u64; + if (cond) { } else { x = 0 }; + let _ = move x + 1; + } + + fun tcopy(cond: bool) { + let x: u64; + if (cond) { } else { x = 0 }; + let _ = move x + 1; + } + + fun tborrow(cond: bool) { + let x: u64; + if (cond) { } else { x = 0 }; + let _ = move x + 1; + } + +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_loop.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_loop.exp new file mode 100644 index 00000000000000..ba1a6af6e9dbfd --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_loop.exp @@ -0,0 +1,269 @@ +============ initial bytecode ================ + +[variant baseline] +fun M::tborrow1() { + var $t0: u64 + var $t1: &u64 + var $t2: &u64 + var $t3: &u64 + var $t4: u64 + 0: label L0 + 1: $t2 := borrow_local($t0) + 2: $t1 := infer($t2) + 3: $t3 := move($t1) + 4: $t4 := 0 + 5: $t0 := infer($t4) + 6: goto 0 + 7: label L1 + 8: return () +} + + +[variant baseline] +fun M::tborrow2($t0: bool) { + var $t1: u64 + var $t2: &u64 + var $t3: &u64 + var $t4: &u64 + var $t5: u64 + var $t6: u64 + 0: label L0 + 1: $t3 := borrow_local($t1) + 2: $t2 := infer($t3) + 3: $t4 := move($t2) + 4: if ($t0) goto 5 else goto 9 + 5: label L2 + 6: $t5 := 0 + 7: $t1 := infer($t5) + 8: goto 10 + 9: label L3 + 10: label L4 + 11: goto 13 + 12: goto 0 + 13: label L1 + 14: $t6 := infer($t1) + 15: return () +} + + +[variant baseline] +fun M::tcopy($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + 0: label L0 + 1: $t4 := 1 + 2: $t3 := +($t1, $t4) + 3: $t2 := infer($t3) + 4: if ($t0) goto 5 else goto 8 + 5: label L2 + 6: goto 0 + 7: goto 9 + 8: label L3 + 9: label L4 + 10: $t5 := 0 + 11: $t1 := infer($t5) + 12: $t6 := infer($t2) + 13: goto 0 + 14: label L1 + 15: return () +} + + +[variant baseline] +fun M::tmove() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + 0: label L0 + 1: $t3 := move($t0) + 2: $t4 := 1 + 3: $t2 := +($t3, $t4) + 4: $t1 := infer($t2) + 5: $t5 := 0 + 6: $t0 := infer($t5) + 7: $t6 := infer($t1) + 8: goto 0 + 9: label L1 + 10: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_loop.move:4:24 + │ +4 │ loop { let y = move x + 1; x = 0; y; } + │ ^^^^^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_loop.move:9:24 + │ +9 │ loop { let y = x + 1; if (cond) { continue }; x = 0; y; } + │ ^^^^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_loop.move:14:24 + │ +14 │ loop { let y = &x; _ = move y; x = 0 } + │ ^^ + +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_loop.move:19:24 + │ +19 │ loop { let y = &x; _ = move y; if (cond) { x = 0 }; break }; + │ ^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_loop.move:20:9 + │ +20 │ x; + │ ^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun M::tborrow1() { + var $t0: u64 + var $t1: &u64 + var $t2: &u64 + var $t3: &u64 + var $t4: u64 + # before: { maybe: $t0, $t1, $t2, $t3, $t4 }, after: { maybe: $t0, $t1, $t2, $t3, $t4 } + 0: label L0 + # before: { maybe: $t0, $t1, $t2, $t3, $t4 }, after: { maybe: $t0, $t1, $t3, $t4 } + 1: $t2 := borrow_local($t0) + # before: { maybe: $t0, $t1, $t3, $t4 }, after: { maybe: $t0, $t3, $t4 } + 2: $t1 := infer($t2) + # before: { maybe: $t0, $t3, $t4 }, after: { maybe: $t0, $t4 } + 3: $t3 := move($t1) + # before: { maybe: $t0, $t4 }, after: { maybe: $t0 } + 4: $t4 := 0 + # before: { maybe: $t0 }, after: all initialized + 5: $t0 := infer($t4) + # before: all initialized, after: all initialized + 6: goto 0 + 7: label L1 + 8: return () +} + + +[variant baseline] +fun M::tborrow2($t0: bool) { + var $t1: u64 + var $t2: &u64 + var $t3: &u64 + var $t4: &u64 + var $t5: u64 + var $t6: u64 + # before: { no: $t1, $t2, $t3, $t4, $t5, $t6 }, after: { no: $t1, $t2, $t3, $t4, $t5, $t6 } + 0: label L0 + # before: { no: $t1, $t2, $t3, $t4, $t5, $t6 }, after: { no: $t1, $t2, $t4, $t5, $t6 } + 1: $t3 := borrow_local($t1) + # before: { no: $t1, $t2, $t4, $t5, $t6 }, after: { no: $t1, $t4, $t5, $t6 } + 2: $t2 := infer($t3) + # before: { no: $t1, $t4, $t5, $t6 }, after: { no: $t1, $t5, $t6 } + 3: $t4 := move($t2) + # before: { no: $t1, $t5, $t6 }, after: { no: $t1, $t5, $t6 } + 4: if ($t0) goto 5 else goto 9 + # before: { no: $t1, $t5, $t6 }, after: { no: $t1, $t5, $t6 } + 5: label L2 + # before: { no: $t1, $t5, $t6 }, after: { no: $t1, $t6 } + 6: $t5 := 0 + # before: { no: $t1, $t6 }, after: { no: $t6 } + 7: $t1 := infer($t5) + # before: { no: $t6 }, after: { no: $t6 } + 8: goto 10 + # before: { no: $t1, $t5, $t6 }, after: { no: $t1, $t5, $t6 } + 9: label L3 + # before: { no: $t6 }{ maybe: $t1, $t5 }, after: { no: $t6 }{ maybe: $t1, $t5 } + 10: label L4 + # before: { no: $t6 }{ maybe: $t1, $t5 }, after: { no: $t6 }{ maybe: $t1, $t5 } + 11: goto 13 + 12: goto 0 + # before: { no: $t6 }{ maybe: $t1, $t5 }, after: { no: $t6 }{ maybe: $t1, $t5 } + 13: label L1 + # before: { no: $t6 }{ maybe: $t1, $t5 }, after: { maybe: $t1, $t5 } + 14: $t6 := infer($t1) + # before: { maybe: $t1, $t5 }, after: { maybe: $t1, $t5 } + 15: return () +} + + +[variant baseline] +fun M::tcopy($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6 } + 0: label L0 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6 }, after: { maybe: $t1, $t2, $t3, $t5, $t6 } + 1: $t4 := 1 + # before: { maybe: $t1, $t2, $t3, $t5, $t6 }, after: { maybe: $t1, $t2, $t5, $t6 } + 2: $t3 := +($t1, $t4) + # before: { maybe: $t1, $t2, $t5, $t6 }, after: { maybe: $t1, $t5, $t6 } + 3: $t2 := infer($t3) + # before: { maybe: $t1, $t5, $t6 }, after: { maybe: $t1, $t5, $t6 } + 4: if ($t0) goto 5 else goto 8 + # before: { maybe: $t1, $t5, $t6 }, after: { maybe: $t1, $t5, $t6 } + 5: label L2 + # before: { maybe: $t1, $t5, $t6 }, after: { maybe: $t1, $t5, $t6 } + 6: goto 0 + 7: goto 9 + # before: { maybe: $t1, $t5, $t6 }, after: { maybe: $t1, $t5, $t6 } + 8: label L3 + # before: { maybe: $t1, $t5, $t6 }, after: { maybe: $t1, $t5, $t6 } + 9: label L4 + # before: { maybe: $t1, $t5, $t6 }, after: { maybe: $t1, $t6 } + 10: $t5 := 0 + # before: { maybe: $t1, $t6 }, after: { maybe: $t6 } + 11: $t1 := infer($t5) + # before: { maybe: $t6 }, after: all initialized + 12: $t6 := infer($t2) + # before: all initialized, after: all initialized + 13: goto 0 + 14: label L1 + 15: return () +} + + +[variant baseline] +fun M::tmove() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + # before: { maybe: $t0, $t1, $t2, $t3, $t4, $t5, $t6 }, after: { maybe: $t0, $t1, $t2, $t3, $t4, $t5, $t6 } + 0: label L0 + # before: { maybe: $t0, $t1, $t2, $t3, $t4, $t5, $t6 }, after: { maybe: $t0, $t1, $t2, $t4, $t5, $t6 } + 1: $t3 := move($t0) + # before: { maybe: $t0, $t1, $t2, $t4, $t5, $t6 }, after: { maybe: $t0, $t1, $t2, $t5, $t6 } + 2: $t4 := 1 + # before: { maybe: $t0, $t1, $t2, $t5, $t6 }, after: { maybe: $t0, $t1, $t5, $t6 } + 3: $t2 := +($t3, $t4) + # before: { maybe: $t0, $t1, $t5, $t6 }, after: { maybe: $t0, $t5, $t6 } + 4: $t1 := infer($t2) + # before: { maybe: $t0, $t5, $t6 }, after: { maybe: $t0, $t6 } + 5: $t5 := 0 + # before: { maybe: $t0, $t6 }, after: { maybe: $t6 } + 6: $t0 := infer($t5) + # before: { maybe: $t6 }, after: all initialized + 7: $t6 := infer($t1) + # before: all initialized, after: all initialized + 8: goto 0 + 9: label L1 + 10: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_loop.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_loop.move new file mode 100644 index 00000000000000..1747a168047301 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_loop.move @@ -0,0 +1,23 @@ +module 0x8675309::M { + fun tmove() { + let x: u64; + loop { let y = move x + 1; x = 0; y; } + } + + fun tcopy(cond: bool) { + let x: u64; + loop { let y = x + 1; if (cond) { continue }; x = 0; y; } + } + + fun tborrow1() { + let x: u64; + loop { let y = &x; _ = move y; x = 0 } + } + + fun tborrow2(cond: bool) { + let x: u64; + loop { let y = &x; _ = move y; if (cond) { x = 0 }; break }; + x; + } + +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_simple.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_simple.exp new file mode 100644 index 00000000000000..4f90fe2be9a35d --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_simple.exp @@ -0,0 +1,145 @@ +============ initial bytecode ================ + +[variant baseline] +fun M::tborrow() { + var $t0: u64 + var $t1: &u64 + var $t2: M::S + var $t3: &M::S + var $t4: &M::S + 0: $t1 := borrow_local($t0) + 1: $t4 := borrow_local($t2) + 2: $t3 := infer($t4) + 3: return () +} + + +[variant baseline] +fun M::tcopy() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: M::S + var $t4: M::S + var $t5: M::S + 0: $t2 := 1 + 1: $t1 := +($t0, $t2) + 2: $t5 := copy($t3) + 3: $t4 := infer($t5) + 4: return () +} + + +[variant baseline] +fun M::tmove() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: M::S + var $t5: M::S + 0: $t2 := move($t0) + 1: $t3 := 1 + 2: $t1 := +($t2, $t3) + 3: $t5 := infer($t4) + 4: return () +} + + +Diagnostics: +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_simple.move:6:17 + │ +6 │ let _ = move x + 1; + │ ^^^^^^ + +error: use of unassigned local `s` + ┌─ tests/uninit-use-checker/use_before_assign_simple.move:9:13 + │ +9 │ let _s2 = s; + │ ^^^ + +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_simple.move:14:17 + │ +14 │ let _ = x + 1; + │ ^^^^^ + +error: use of unassigned local `s` + ┌─ tests/uninit-use-checker/use_before_assign_simple.move:17:19 + │ +17 │ let _s3 = copy s; + │ ^^^^^^ + +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_simple.move:22:17 + │ +22 │ let _ = &x; + │ ^^ + +error: use of unassigned local `s` + ┌─ tests/uninit-use-checker/use_before_assign_simple.move:25:19 + │ +25 │ let _s2 = &s; + │ ^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun M::tborrow() { + var $t0: u64 + var $t1: &u64 + var $t2: M::S + var $t3: &M::S + var $t4: &M::S + # before: { no: $t0, $t1, $t2, $t3, $t4 }, after: { no: $t0, $t2, $t3, $t4 } + 0: $t1 := borrow_local($t0) + # before: { no: $t0, $t2, $t3, $t4 }, after: { no: $t0, $t2, $t3 } + 1: $t4 := borrow_local($t2) + # before: { no: $t0, $t2, $t3 }, after: { no: $t0, $t2 } + 2: $t3 := infer($t4) + # before: { no: $t0, $t2 }, after: { no: $t0, $t2 } + 3: return () +} + + +[variant baseline] +fun M::tcopy() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: M::S + var $t4: M::S + var $t5: M::S + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t1, $t3, $t4, $t5 } + 0: $t2 := 1 + # before: { no: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t0, $t3, $t4, $t5 } + 1: $t1 := +($t0, $t2) + # before: { no: $t0, $t3, $t4, $t5 }, after: { no: $t0, $t3, $t4 } + 2: $t5 := copy($t3) + # before: { no: $t0, $t3, $t4 }, after: { no: $t0, $t3 } + 3: $t4 := infer($t5) + # before: { no: $t0, $t3 }, after: { no: $t0, $t3 } + 4: return () +} + + +[variant baseline] +fun M::tmove() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: M::S + var $t5: M::S + # before: { no: $t0, $t1, $t2, $t3, $t4, $t5 }, after: { no: $t0, $t1, $t3, $t4, $t5 } + 0: $t2 := move($t0) + # before: { no: $t0, $t1, $t3, $t4, $t5 }, after: { no: $t0, $t1, $t4, $t5 } + 1: $t3 := 1 + # before: { no: $t0, $t1, $t4, $t5 }, after: { no: $t0, $t4, $t5 } + 2: $t1 := +($t2, $t3) + # before: { no: $t0, $t4, $t5 }, after: { no: $t0, $t4 } + 3: $t5 := infer($t4) + # before: { no: $t0, $t4 }, after: { no: $t0, $t4 } + 4: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_simple.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_simple.move new file mode 100644 index 00000000000000..44a1433b1c9e26 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_simple.move @@ -0,0 +1,28 @@ +module 0x8675309::M { + struct S has copy, drop {} + + fun tmove() { + let x: u64; + let _ = move x + 1; + + let s: S; + let _s2 = s; + } + + fun tcopy() { + let x: u64; + let _ = x + 1; + + let s: S; + let _s3 = copy s; + } + + fun tborrow() { + let x: u64; + let _ = &x; + + let s: S; + let _s2 = &s; + } + +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_while.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_while.exp new file mode 100644 index 00000000000000..b9c7981ab9d14e --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_while.exp @@ -0,0 +1,339 @@ +============ initial bytecode ================ + +[variant baseline] +fun M::tborrow1($t0: bool) { + var $t1: u64 + var $t2: &u64 + var $t3: &u64 + var $t4: &u64 + var $t5: u64 + 0: label L0 + 1: if ($t0) goto 2 else goto 9 + 2: label L2 + 3: $t3 := borrow_local($t1) + 4: $t2 := infer($t3) + 5: $t4 := move($t2) + 6: $t5 := 0 + 7: $t1 := infer($t5) + 8: goto 11 + 9: label L3 + 10: goto 13 + 11: label L4 + 12: goto 0 + 13: label L1 + 14: return () +} + + +[variant baseline] +fun M::tborrow2($t0: bool) { + var $t1: u64 + var $t2: &u64 + var $t3: &u64 + var $t4: &u64 + var $t5: u64 + 0: label L0 + 1: if ($t0) goto 2 else goto 15 + 2: label L2 + 3: $t3 := borrow_local($t1) + 4: $t2 := infer($t3) + 5: $t4 := move($t2) + 6: if ($t0) goto 7 else goto 11 + 7: label L5 + 8: $t5 := 0 + 9: $t1 := infer($t5) + 10: goto 12 + 11: label L6 + 12: label L7 + 13: goto 19 + 14: goto 17 + 15: label L3 + 16: goto 19 + 17: label L4 + 18: goto 0 + 19: label L1 + 20: return () +} + + +[variant baseline] +fun M::tcopy($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + var $t7: u64 + 0: label L0 + 1: if ($t0) goto 2 else goto 17 + 2: label L2 + 3: $t4 := move($t1) + 4: $t5 := 1 + 5: $t3 := +($t4, $t5) + 6: $t2 := infer($t3) + 7: if ($t0) goto 8 else goto 11 + 8: label L5 + 9: goto 0 + 10: goto 12 + 11: label L6 + 12: label L7 + 13: $t6 := 0 + 14: $t1 := infer($t6) + 15: $t7 := infer($t2) + 16: goto 19 + 17: label L3 + 18: goto 21 + 19: label L4 + 20: goto 0 + 21: label L1 + 22: return () +} + + +[variant baseline] +fun M::tmove($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + var $t7: u64 + 0: label L0 + 1: if ($t0) goto 2 else goto 11 + 2: label L2 + 3: $t4 := move($t1) + 4: $t5 := 1 + 5: $t3 := +($t4, $t5) + 6: $t2 := infer($t3) + 7: $t6 := 0 + 8: $t1 := infer($t6) + 9: $t7 := infer($t2) + 10: goto 13 + 11: label L3 + 12: goto 15 + 13: label L4 + 14: goto 0 + 15: label L1 + 16: return () +} + + +Diagnostics: +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_while.move:4:32 + │ +4 │ while (cond) { let y = move x + 1; x = 0; y; } + │ ^^^^^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_while.move:9:32 + │ +9 │ while (cond) { let y = move x + 1; if (cond) { continue }; x = 0; y; } + │ ^^^^^^ + +error: use of possibly unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_while.move:14:32 + │ +14 │ while (cond) { let y = &x; _ = move y; x = 0 } + │ ^^ + +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/use_before_assign_while.move:19:32 + │ +19 │ while (cond) { let y = &x; _ = move y; if (cond) { x = 0 }; break } + │ ^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun M::tborrow1($t0: bool) { + var $t1: u64 + var $t2: &u64 + var $t3: &u64 + var $t4: &u64 + var $t5: u64 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 0: label L0 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 1: if ($t0) goto 2 else goto 9 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 2: label L2 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t4, $t5 } + 3: $t3 := borrow_local($t1) + # before: { maybe: $t1, $t2, $t4, $t5 }, after: { maybe: $t1, $t4, $t5 } + 4: $t2 := infer($t3) + # before: { maybe: $t1, $t4, $t5 }, after: { maybe: $t1, $t5 } + 5: $t4 := move($t2) + # before: { maybe: $t1, $t5 }, after: { maybe: $t1 } + 6: $t5 := 0 + # before: { maybe: $t1 }, after: all initialized + 7: $t1 := infer($t5) + # before: all initialized, after: all initialized + 8: goto 11 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 9: label L3 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 10: goto 13 + # before: all initialized, after: all initialized + 11: label L4 + # before: all initialized, after: all initialized + 12: goto 0 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 13: label L1 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 14: return () +} + + +[variant baseline] +fun M::tborrow2($t0: bool) { + var $t1: u64 + var $t2: &u64 + var $t3: &u64 + var $t4: &u64 + var $t5: u64 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 0: label L0 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 1: if ($t0) goto 2 else goto 15 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 2: label L2 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t4, $t5 } + 3: $t3 := borrow_local($t1) + # before: { no: $t1, $t2, $t4, $t5 }, after: { no: $t1, $t4, $t5 } + 4: $t2 := infer($t3) + # before: { no: $t1, $t4, $t5 }, after: { no: $t1, $t5 } + 5: $t4 := move($t2) + # before: { no: $t1, $t5 }, after: { no: $t1, $t5 } + 6: if ($t0) goto 7 else goto 11 + # before: { no: $t1, $t5 }, after: { no: $t1, $t5 } + 7: label L5 + # before: { no: $t1, $t5 }, after: { no: $t1 } + 8: $t5 := 0 + # before: { no: $t1 }, after: all initialized + 9: $t1 := infer($t5) + # before: all initialized, after: all initialized + 10: goto 12 + # before: { no: $t1, $t5 }, after: { no: $t1, $t5 } + 11: label L6 + # before: { maybe: $t1, $t5 }, after: { maybe: $t1, $t5 } + 12: label L7 + # before: { maybe: $t1, $t5 }, after: { maybe: $t1, $t5 } + 13: goto 19 + 14: goto 17 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 15: label L3 + # before: { no: $t1, $t2, $t3, $t4, $t5 }, after: { no: $t1, $t2, $t3, $t4, $t5 } + 16: goto 19 + 17: label L4 + 18: goto 0 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 19: label L1 + # before: { maybe: $t1, $t2, $t3, $t4, $t5 }, after: { maybe: $t1, $t2, $t3, $t4, $t5 } + 20: return () +} + + +[variant baseline] +fun M::tcopy($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + var $t7: u64 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 0: label L0 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 1: if ($t0) goto 2 else goto 17 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 2: label L2 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t5, $t6, $t7 } + 3: $t4 := move($t1) + # before: { maybe: $t1, $t2, $t3, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t6, $t7 } + 4: $t5 := 1 + # before: { maybe: $t1, $t2, $t3, $t6, $t7 }, after: { maybe: $t1, $t2, $t6, $t7 } + 5: $t3 := +($t4, $t5) + # before: { maybe: $t1, $t2, $t6, $t7 }, after: { maybe: $t1, $t6, $t7 } + 6: $t2 := infer($t3) + # before: { maybe: $t1, $t6, $t7 }, after: { maybe: $t1, $t6, $t7 } + 7: if ($t0) goto 8 else goto 11 + # before: { maybe: $t1, $t6, $t7 }, after: { maybe: $t1, $t6, $t7 } + 8: label L5 + # before: { maybe: $t1, $t6, $t7 }, after: { maybe: $t1, $t6, $t7 } + 9: goto 0 + 10: goto 12 + # before: { maybe: $t1, $t6, $t7 }, after: { maybe: $t1, $t6, $t7 } + 11: label L6 + # before: { maybe: $t1, $t6, $t7 }, after: { maybe: $t1, $t6, $t7 } + 12: label L7 + # before: { maybe: $t1, $t6, $t7 }, after: { maybe: $t1, $t7 } + 13: $t6 := 0 + # before: { maybe: $t1, $t7 }, after: { maybe: $t7 } + 14: $t1 := infer($t6) + # before: { maybe: $t7 }, after: all initialized + 15: $t7 := infer($t2) + # before: all initialized, after: all initialized + 16: goto 19 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 17: label L3 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 18: goto 21 + # before: all initialized, after: all initialized + 19: label L4 + # before: all initialized, after: all initialized + 20: goto 0 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 21: label L1 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 22: return () +} + + +[variant baseline] +fun M::tmove($t0: bool) { + var $t1: u64 + var $t2: u64 + var $t3: u64 + var $t4: u64 + var $t5: u64 + var $t6: u64 + var $t7: u64 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 0: label L0 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 1: if ($t0) goto 2 else goto 11 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 2: label L2 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t5, $t6, $t7 } + 3: $t4 := move($t1) + # before: { maybe: $t1, $t2, $t3, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t6, $t7 } + 4: $t5 := 1 + # before: { maybe: $t1, $t2, $t3, $t6, $t7 }, after: { maybe: $t1, $t2, $t6, $t7 } + 5: $t3 := +($t4, $t5) + # before: { maybe: $t1, $t2, $t6, $t7 }, after: { maybe: $t1, $t6, $t7 } + 6: $t2 := infer($t3) + # before: { maybe: $t1, $t6, $t7 }, after: { maybe: $t1, $t7 } + 7: $t6 := 0 + # before: { maybe: $t1, $t7 }, after: { maybe: $t7 } + 8: $t1 := infer($t6) + # before: { maybe: $t7 }, after: all initialized + 9: $t7 := infer($t2) + # before: all initialized, after: all initialized + 10: goto 13 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 11: label L3 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 12: goto 15 + # before: all initialized, after: all initialized + 13: label L4 + # before: all initialized, after: all initialized + 14: goto 0 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 15: label L1 + # before: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 }, after: { maybe: $t1, $t2, $t3, $t4, $t5, $t6, $t7 } + 16: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_while.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_while.move new file mode 100644 index 00000000000000..e50e3571b697db --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_before_assign_while.move @@ -0,0 +1,22 @@ +module 0x8675309::M { + fun tmove(cond: bool) { + let x: u64; + while (cond) { let y = move x + 1; x = 0; y; } + } + + fun tcopy(cond: bool) { + let x: u64; + while (cond) { let y = move x + 1; if (cond) { continue }; x = 0; y; } + } + + fun tborrow1(cond: bool) { + let x: u64; + while (cond) { let y = &x; _ = move y; x = 0 } + } + + fun tborrow2(cond: bool) { + let x: u64; + while (cond) { let y = &x; _ = move y; if (cond) { x = 0 }; break } + } + +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_twice_before_assign.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_twice_before_assign.exp new file mode 100644 index 00000000000000..02bf766afbb73e --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_twice_before_assign.exp @@ -0,0 +1,34 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + 0: $t2 := +($t0, $t0) + 1: $t1 := infer($t2) + 2: return () +} + + +Diagnostics: +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/use_twice_before_assign.move:4:13 + │ +4 │ let y = x + x; + │ ^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + # before: { no: $t0, $t1, $t2 }, after: { no: $t0, $t1 } + 0: $t2 := +($t0, $t0) + # before: { no: $t0, $t1 }, after: { no: $t0 } + 1: $t1 := infer($t2) + # before: { no: $t0 }, after: { no: $t0 } + 2: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_twice_before_assign.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_twice_before_assign.move new file mode 100644 index 00000000000000..54d1ce05817ad4 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/use_twice_before_assign.move @@ -0,0 +1,6 @@ +script { +fun main() { + let x: u64; + let y = x + x; +} +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/uses_before_assign.exp b/third_party/move/move-compiler-v2/tests/uninit-use-checker/uses_before_assign.exp new file mode 100644 index 00000000000000..f485484414f5b6 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/uses_before_assign.exp @@ -0,0 +1,42 @@ +============ initial bytecode ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: u64 + 0: $t3 := +($t0, $t1) + 1: $t2 := infer($t3) + 2: return () +} + + +Diagnostics: +error: use of unassigned local `x` + ┌─ tests/uninit-use-checker/uses_before_assign.move:5:13 + │ +5 │ let z = x + y; + │ ^^^^^ + +error: use of unassigned local `y` + ┌─ tests/uninit-use-checker/uses_before_assign.move:5:13 + │ +5 │ let z = x + y; + │ ^^^^^ + +============ after uninitialized_use_checker: ================ + +[variant baseline] +fun _0::main() { + var $t0: u64 + var $t1: u64 + var $t2: u64 + var $t3: u64 + # before: { no: $t0, $t1, $t2, $t3 }, after: { no: $t0, $t1, $t2 } + 0: $t3 := +($t0, $t1) + # before: { no: $t0, $t1, $t2 }, after: { no: $t0, $t1 } + 1: $t2 := infer($t3) + # before: { no: $t0, $t1 }, after: { no: $t0, $t1 } + 2: return () +} diff --git a/third_party/move/move-compiler-v2/tests/uninit-use-checker/uses_before_assign.move b/third_party/move/move-compiler-v2/tests/uninit-use-checker/uses_before_assign.move new file mode 100644 index 00000000000000..9bbf401a76e481 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/uninit-use-checker/uses_before_assign.move @@ -0,0 +1,7 @@ +script { +fun main() { + let x: u64; + let y: u64; + let z = x + y; +} +} diff --git a/third_party/move/move-model/bytecode/src/stackless_bytecode.rs b/third_party/move/move-model/bytecode/src/stackless_bytecode.rs index 43f20bd95a1150..acaec01be0622f 100644 --- a/third_party/move/move-model/bytecode/src/stackless_bytecode.rs +++ b/third_party/move/move-model/bytecode/src/stackless_bytecode.rs @@ -414,6 +414,7 @@ pub enum Bytecode { Abort(AttrId, TempIndex), Nop(AttrId), + // Extended bytecode: spec-only. SaveMem(AttrId, MemoryLabel, QualifiedInstId), SaveSpecVar(AttrId, MemoryLabel, QualifiedInstId), Prop(AttrId, PropKind, Exp), @@ -489,6 +490,12 @@ impl Bytecode { self.is_conditional_branch() || self.is_unconditional_branch() } + /// Returns true if the bytecode is spec-only. + pub fn is_spec_only(&self) -> bool { + use Bytecode::*; + matches!(self, SaveMem(..) | SaveSpecVar(..) | Prop(..)) + } + /// Return the sources of the instruction (for non-spec-only instructions). /// If the instruction is spec-only instruction, this function panics. pub fn sources(&self) -> Vec { From aa535fddc6111e61fcdb0643172247c1db5ad71d Mon Sep 17 00:00:00 2001 From: Alin Tomescu Date: Wed, 24 Jan 2024 10:51:59 -0800 Subject: [PATCH 04/11] add aptos-dkg crate (#11752) --- Cargo.lock | 125 +++- Cargo.toml | 9 +- crates/aptos-crypto/src/poseidon_bn254.rs | 19 +- crates/aptos-dkg/Cargo.toml | 53 ++ crates/aptos-dkg/README.md | 71 +++ crates/aptos-dkg/benches/crypto.rs | 457 +++++++++++++++ crates/aptos-dkg/benches/lagrange.rs | 61 ++ crates/aptos-dkg/benches/pvss.rs | 256 +++++++++ crates/aptos-dkg/benches/weighted_vuf.rs | 391 +++++++++++++ .../src/algebra/evaluation_domain.rs | 222 +++++++ crates/aptos-dkg/src/algebra/fft.rs | 112 ++++ crates/aptos-dkg/src/algebra/lagrange.rs | 407 +++++++++++++ crates/aptos-dkg/src/algebra/mod.rs | 6 + crates/aptos-dkg/src/algebra/polynomials.rs | 524 +++++++++++++++++ crates/aptos-dkg/src/constants.rs | 20 + crates/aptos-dkg/src/lib.rs | 27 + crates/aptos-dkg/src/pvss/contribution.rs | 103 ++++ crates/aptos-dkg/src/pvss/das/enc.rs | 17 + crates/aptos-dkg/src/pvss/das/input_secret.rs | 32 ++ crates/aptos-dkg/src/pvss/das/mod.rs | 13 + .../src/pvss/das/public_parameters.rs | 135 +++++ .../src/pvss/das/unweighted_protocol.rs | 335 +++++++++++ .../src/pvss/das/weighted_protocol.rs | 540 ++++++++++++++++++ crates/aptos-dkg/src/pvss/dealt_pub_key.rs | 62 ++ .../aptos-dkg/src/pvss/dealt_pub_key_share.rs | 64 +++ crates/aptos-dkg/src/pvss/dealt_secret_key.rs | 140 +++++ .../src/pvss/dealt_secret_key_share.rs | 68 +++ crates/aptos-dkg/src/pvss/encryption_dlog.rs | 219 +++++++ .../aptos-dkg/src/pvss/encryption_elgamal.rs | 86 +++ crates/aptos-dkg/src/pvss/fiat_shamir.rs | 165 ++++++ crates/aptos-dkg/src/pvss/input_secret.rs | 79 +++ .../aptos-dkg/src/pvss/insecure_field/mod.rs | 5 + .../src/pvss/insecure_field/transcript.rs | 208 +++++++ crates/aptos-dkg/src/pvss/low_degree_test.rs | 315 ++++++++++ crates/aptos-dkg/src/pvss/mod.rs | 26 + crates/aptos-dkg/src/pvss/player.rs | 16 + .../aptos-dkg/src/pvss/scalar_secret_key.rs | 40 ++ crates/aptos-dkg/src/pvss/schnorr.rs | 108 ++++ crates/aptos-dkg/src/pvss/test_utils.rs | 212 +++++++ crates/aptos-dkg/src/pvss/threshold_config.rs | 125 ++++ crates/aptos-dkg/src/pvss/traits/mod.rs | 52 ++ .../aptos-dkg/src/pvss/traits/transcript.rs | 213 +++++++ .../src/pvss/weighted/generic_weighting.rs | 233 ++++++++ crates/aptos-dkg/src/pvss/weighted/mod.rs | 7 + .../src/pvss/weighted/weighted_config.rs | 304 ++++++++++ crates/aptos-dkg/src/utils/biguint.rs | 58 ++ crates/aptos-dkg/src/utils/mod.rs | 154 +++++ crates/aptos-dkg/src/utils/random.rs | 210 +++++++ crates/aptos-dkg/src/utils/serialization.rs | 54 ++ crates/aptos-dkg/src/weighted_vuf/bls/mod.rs | 172 ++++++ crates/aptos-dkg/src/weighted_vuf/mod.rs | 5 + .../aptos-dkg/src/weighted_vuf/pinkas/mod.rs | 285 +++++++++ crates/aptos-dkg/src/weighted_vuf/traits.rs | 83 +++ crates/aptos-dkg/tests/accumulator.rs | 89 +++ crates/aptos-dkg/tests/crypto.rs | 171 ++++++ crates/aptos-dkg/tests/dkg.rs | 84 +++ crates/aptos-dkg/tests/fft.rs | 73 +++ crates/aptos-dkg/tests/pvss.rs | 157 +++++ crates/aptos-dkg/tests/weighted_vuf.rs | 185 ++++++ 59 files changed, 8446 insertions(+), 16 deletions(-) create mode 100644 crates/aptos-dkg/Cargo.toml create mode 100644 crates/aptos-dkg/README.md create mode 100644 crates/aptos-dkg/benches/crypto.rs create mode 100644 crates/aptos-dkg/benches/lagrange.rs create mode 100644 crates/aptos-dkg/benches/pvss.rs create mode 100644 crates/aptos-dkg/benches/weighted_vuf.rs create mode 100644 crates/aptos-dkg/src/algebra/evaluation_domain.rs create mode 100644 crates/aptos-dkg/src/algebra/fft.rs create mode 100644 crates/aptos-dkg/src/algebra/lagrange.rs create mode 100644 crates/aptos-dkg/src/algebra/mod.rs create mode 100644 crates/aptos-dkg/src/algebra/polynomials.rs create mode 100644 crates/aptos-dkg/src/constants.rs create mode 100644 crates/aptos-dkg/src/lib.rs create mode 100644 crates/aptos-dkg/src/pvss/contribution.rs create mode 100644 crates/aptos-dkg/src/pvss/das/enc.rs create mode 100644 crates/aptos-dkg/src/pvss/das/input_secret.rs create mode 100644 crates/aptos-dkg/src/pvss/das/mod.rs create mode 100644 crates/aptos-dkg/src/pvss/das/public_parameters.rs create mode 100644 crates/aptos-dkg/src/pvss/das/unweighted_protocol.rs create mode 100644 crates/aptos-dkg/src/pvss/das/weighted_protocol.rs create mode 100644 crates/aptos-dkg/src/pvss/dealt_pub_key.rs create mode 100644 crates/aptos-dkg/src/pvss/dealt_pub_key_share.rs create mode 100644 crates/aptos-dkg/src/pvss/dealt_secret_key.rs create mode 100644 crates/aptos-dkg/src/pvss/dealt_secret_key_share.rs create mode 100644 crates/aptos-dkg/src/pvss/encryption_dlog.rs create mode 100644 crates/aptos-dkg/src/pvss/encryption_elgamal.rs create mode 100644 crates/aptos-dkg/src/pvss/fiat_shamir.rs create mode 100644 crates/aptos-dkg/src/pvss/input_secret.rs create mode 100644 crates/aptos-dkg/src/pvss/insecure_field/mod.rs create mode 100644 crates/aptos-dkg/src/pvss/insecure_field/transcript.rs create mode 100644 crates/aptos-dkg/src/pvss/low_degree_test.rs create mode 100644 crates/aptos-dkg/src/pvss/mod.rs create mode 100644 crates/aptos-dkg/src/pvss/player.rs create mode 100644 crates/aptos-dkg/src/pvss/scalar_secret_key.rs create mode 100644 crates/aptos-dkg/src/pvss/schnorr.rs create mode 100644 crates/aptos-dkg/src/pvss/test_utils.rs create mode 100644 crates/aptos-dkg/src/pvss/threshold_config.rs create mode 100644 crates/aptos-dkg/src/pvss/traits/mod.rs create mode 100644 crates/aptos-dkg/src/pvss/traits/transcript.rs create mode 100644 crates/aptos-dkg/src/pvss/weighted/generic_weighting.rs create mode 100644 crates/aptos-dkg/src/pvss/weighted/mod.rs create mode 100644 crates/aptos-dkg/src/pvss/weighted/weighted_config.rs create mode 100644 crates/aptos-dkg/src/utils/biguint.rs create mode 100644 crates/aptos-dkg/src/utils/mod.rs create mode 100644 crates/aptos-dkg/src/utils/random.rs create mode 100644 crates/aptos-dkg/src/utils/serialization.rs create mode 100644 crates/aptos-dkg/src/weighted_vuf/bls/mod.rs create mode 100644 crates/aptos-dkg/src/weighted_vuf/mod.rs create mode 100644 crates/aptos-dkg/src/weighted_vuf/pinkas/mod.rs create mode 100644 crates/aptos-dkg/src/weighted_vuf/traits.rs create mode 100644 crates/aptos-dkg/tests/accumulator.rs create mode 100644 crates/aptos-dkg/tests/crypto.rs create mode 100644 crates/aptos-dkg/tests/dkg.rs create mode 100644 crates/aptos-dkg/tests/fft.rs create mode 100644 crates/aptos-dkg/tests/pvss.rs create mode 100644 crates/aptos-dkg/tests/weighted_vuf.rs diff --git a/Cargo.lock b/Cargo.lock index b5f3535378eba1..3ae420f065b711 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1007,7 +1007,7 @@ dependencies = [ "libsecp256k1", "merlin", "more-asserts", - "num-bigint 0.4.4", + "num-bigint 0.3.3", "num-integer", "once_cell", "p256", @@ -1230,6 +1230,35 @@ dependencies = [ "tokio", ] +[[package]] +name = "aptos-dkg" +version = "0.1.0" +dependencies = [ + "anyhow", + "aptos-crypto", + "aptos-crypto-derive", + "bcs 0.1.4", + "bellman", + "blstrs", + "criterion", + "ff 0.13.0", + "group 0.13.0", + "hex", + "merlin", + "more-asserts", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "once_cell", + "pairing", + "rand 0.7.3", + "rand_core 0.5.1", + "serde", + "serde_bytes", + "sha3 0.9.1", + "static_assertions", +] + [[package]] name = "aptos-dkg-runtime" version = "0.1.0" @@ -4119,7 +4148,7 @@ dependencies = [ "move-core-types", "move-table-extension", "move-vm-types", - "num-bigint 0.4.4", + "num-bigint 0.3.3", "num-derive", "num-traits", "once_cell", @@ -5174,6 +5203,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bellman" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4dd656ef4fdf7debb6d87d4dd92642fcbcdb78cbf6600c13e25c87e4d1a3807" +dependencies = [ + "bitvec 1.0.1", + "blake2s_simd", + "byteorder", + "ff 0.12.1", + "group 0.12.1", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "better_any" version = "0.1.1" @@ -5320,7 +5364,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" dependencies = [ "arrayvec 0.4.12", - "constant_time_eq", + "constant_time_eq 0.1.5", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" +dependencies = [ + "arrayref", + "arrayvec 0.7.4", + "constant_time_eq 0.3.0", ] [[package]] @@ -5397,6 +5452,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "blstrs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8a8ed6fefbeef4a8c7b460e4110e12c5e22a5b7cf32621aae6ad650c4dcf29" +dependencies = [ + "blst", + "byte-slice-cast", + "ff 0.13.0", + "group 0.13.0", + "pairing", + "rand_core 0.6.4", + "serde", + "subtle", +] + [[package]] name = "bollard" version = "0.15.0" @@ -6096,6 +6167,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "convert_case" version = "0.4.0" @@ -7139,9 +7216,9 @@ dependencies = [ "base64ct", "crypto-bigint 0.5.5", "digest 0.10.7", - "ff", + "ff 0.13.0", "generic-array 0.14.7", - "group", + "group 0.13.0", "pem-rfc7468 0.7.0", "pkcs8 0.10.2", "rand_core 0.6.4", @@ -7631,12 +7708,24 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec 1.0.1", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "ff" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec 1.0.1", "rand_core 0.6.4", "subtle", ] @@ -8362,14 +8451,27 @@ dependencies = [ "async-trait", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.0", + "rand 0.8.5", "rand_core 0.6.4", + "rand_xorshift", "subtle", ] @@ -11404,6 +11506,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "rand 0.7.3", ] [[package]] @@ -11415,7 +11518,6 @@ dependencies = [ "autocfg", "num-integer", "num-traits", - "rand 0.8.5", ] [[package]] @@ -11757,6 +11859,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group 0.13.0", +] + [[package]] name = "parity-scale-codec" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index 5c4f609d097d1a..72de4d6e24ba2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "crates/aptos-crypto", "crates/aptos-crypto-derive", "crates/aptos-debugger", + "crates/aptos-dkg", "crates/aptos-drop-helper", "crates/aptos-enum-conversion-derive", "crates/aptos-faucet/cli", @@ -304,6 +305,7 @@ aptos-db = { path = "storage/aptosdb" } aptos-db-indexer = { path = "storage/indexer" } aptos-db-tool = { path = "storage/db-tool" } aptos-debugger = { path = "crates/aptos-debugger" } +aptos-dkg = { path = "crates/aptos-dkg" } aptos-dkg-runtime = { path = "dkg" } aptos-drop-helper = { path = "crates/aptos-drop-helper" } aptos-event-notifications = { path = "state-sync/inter-component/event-notifications" } @@ -463,12 +465,14 @@ backoff = { version = "0.4.0", features = ["tokio"] } backtrace = "0.3.58" bcs = { git = "https://github.com/aptos-labs/bcs.git", rev = "d31fab9d81748e2594be5cd5cdf845786a30562d" } better_any = "0.1.1" +bellman = { version = "0.13.1", default-features = false } bigdecimal = { version = "0.4.0", features = ["serde"] } version-compare = "0.1.1" bitvec = "1.0.1" blake2 = "0.10.4" blake2-rfc = "0.2.18" blst = "0.3.7" +blstrs = { version = "0.7.1", features = ["serde"] } bollard = "0.15" bulletproofs = { version = "4.0.0" } byteorder = "1.4.3" @@ -515,6 +519,7 @@ env_logger = "0.10.0" erased-serde = "0.3.13" event-listener = "2.5.3" fail = "0.5.0" +ff = "0.13" field_count = "0.1.1" flate2 = "1.0.24" futures = "0.3.29" @@ -527,6 +532,7 @@ git2 = "0.16.1" glob = "0.3.0" goldenfile = "1.5.2" google-cloud-storage = "0.13.0" +group = "0.13" guppy = "0.17.0" handlebars = "4.2.2" heck = "0.4.1" @@ -567,7 +573,7 @@ mockall = "0.11.4" more-asserts = "0.3.0" native-tls = "0.2.10" ntest = "0.9.0" -num-bigint = { version = "0.4.4", features = ["rand"] } +num-bigint = { version = "0.3.2", features = ["rand"] } num_cpus = "1.13.1" num-derive = "0.3.3" num-integer = "0.1.42" @@ -580,6 +586,7 @@ owo-colors = "3.5.0" p256 = { version = "0.13.2" } signature = "2.1.0" sec1 = "0.7.0" +pairing = "0.23" parking_lot = "0.12.0" paste = "1.0.7" passkey-authenticator = { version = "0.2.0", features = ["testable"] } diff --git a/crates/aptos-crypto/src/poseidon_bn254.rs b/crates/aptos-crypto/src/poseidon_bn254.rs index 60c1b77e4cf3c0..65f67f8306e1d4 100644 --- a/crates/aptos-crypto/src/poseidon_bn254.rs +++ b/crates/aptos-crypto/src/poseidon_bn254.rs @@ -3,7 +3,7 @@ //! Implements the Poseidon hash function for BN-254, which hashes $\le$ 16 field elements and //! produces a single field element as output. use anyhow::bail; -use num_bigint::BigUint; +use ark_ff::PrimeField; // TODO(zkid): Figure out the right library for Poseidon. use poseidon_ark::Poseidon; @@ -199,8 +199,7 @@ pub fn pack_bytes_to_one_scalar(chunk: &[u8]) -> anyhow::Result { MAX_NUM_INPUT_BYTES, ); } - let big_uint = BigUint::from_bytes_le(chunk); - let fr = ark_bn254::Fr::from(big_uint); + let fr = ark_bn254::Fr::from_le_bytes_mod_order(chunk); Ok(fr) } @@ -218,7 +217,7 @@ mod test { use std::str::FromStr; #[test] - fn test_poseidon_ark_vectors() { + fn test_poseidon_bn254_poseidon_ark_vectors() { let mut inputs = vec!["1", "2"] .into_iter() .map(|hex| ark_bn254::Fr::from_str(hex).unwrap()) @@ -241,7 +240,7 @@ mod test { } #[test] - fn test_pad_and_hash_bytes() { + fn test_poseidon_bn254_pad_and_hash_bytes() { let aud = "google"; const MAX_AUD_VAL_BYTES: usize = 248; let aud_val_hash = poseidon_bn254::pad_and_hash_string(aud, MAX_AUD_VAL_BYTES).unwrap(); @@ -252,7 +251,7 @@ mod test { } #[test] - fn test_pad_and_hash_bytes_no_collision() { + fn test_poseidon_bn254_pad_and_hash_bytes_no_collision() { let s1: [u8; 3] = [0, 0, 1]; let s2: [u8; 4] = [0, 0, 1, 0]; const MAX_BYTES: usize = 248; @@ -263,7 +262,7 @@ mod test { } #[test] - fn test_pack_bytes() { + fn test_poseidon_bn254_pack_bytes() { // b"" -> vec![Fr(0)] let scalars = pack_bytes_to_scalars(b"").unwrap(); assert_eq!(scalars.len(), 0); @@ -277,7 +276,11 @@ mod test { let pow_2_to_247 = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80"; let scalars = pack_bytes_to_scalars(pow_2_to_247.as_slice()).unwrap(); assert_eq!(scalars.len(), 1); - assert_eq!(scalars[0], ark_bn254::Fr::from(BigUint::from(2u8).pow(247))); + let pow_2_to_247_le_bytes = BigUint::from(2u8).pow(247).to_bytes_le(); + assert_eq!( + scalars[0], + ark_bn254::Fr::from_le_bytes_mod_order(pow_2_to_247_le_bytes.as_slice()) + ); // (2^248).to_le_bytes() -> vec![Fr(32), Fr(2^248)] let pow_2_to_248 = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"; diff --git a/crates/aptos-dkg/Cargo.toml b/crates/aptos-dkg/Cargo.toml new file mode 100644 index 00000000000000..60a2f4e2c8899b --- /dev/null +++ b/crates/aptos-dkg/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "aptos-dkg" +version = "0.1.0" +edition = "2021" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { workspace = true } +aptos-crypto = { workspace = true } +aptos-crypto-derive = { workspace = true } +bcs = { workspace = true } +bellman = { workspace = true } +blstrs = { workspace = true } +criterion = { workspace = true } +ff = { workspace = true } +group = { workspace = true } +hex = { workspace = true } +merlin = { workspace = true } +more-asserts = { workspace = true } +num-bigint = { workspace = true } +num-integer = { workspace = true } +num-traits = { workspace = true } +once_cell = { workspace = true } +pairing = { workspace = true } +rand = { workspace = true } +rand_core = { workspace = true } +serde = { workspace = true } +serde_bytes = { workspace = true } +sha3 = { workspace = true } +static_assertions = { workspace = true } + +[features] +assert-private-keys-not-cloneable = [] +fuzzing = [] + +[[bench]] +name = "pvss" +harness = false + +[[bench]] +name = "crypto" +harness = false + +[[bench]] +name = "weighted_vuf" +harness = false + +[[bench]] +name = "lagrange" +harness = false + +[profile.bench] +debug = true # for running cargo flamegraph on benchmarks diff --git a/crates/aptos-dkg/README.md b/crates/aptos-dkg/README.md new file mode 100644 index 00000000000000..669c2ae03d74f9 --- /dev/null +++ b/crates/aptos-dkg/README.md @@ -0,0 +1,71 @@ +# Summary + +PVSS schemes: + + - Das PVSS with PKs in $\mathbb{G}_2$ and SKs in $\mathbb{G}_1$ + +WVUF schemes: + - Pinkas WVUF with PKs in $\mathbb{G}_2$ and SKs in $\mathbb{G}_1$ + +# TODOs + + - `accumulator_poly` uses a hard-coded FFT threshold to decide when to switch between slow/fast implementations + - multi-threaded `accumulator_poly` + - eliminate extra allocations in `accumulator_poly` + - Consider using affine coordinates for the in-memory representation of a transcript/VUF as a way to speed up multiexps / multipairings + +# Workarounds + +## rand_core_hell + +`aptos_crypto` uses `rand_core 0.5.1`. However, `blstrs` uses `rand_core 0.6` + +This spells disaster: we cannot pass the RNGs from `rand_core 0.6` into the `aptos_crypto` traits that expect a `0.5.1`-version RNG. + +We work around this by generating random seeds using the 0.5.1 RNG that we get from `aptos_crypto` and then create `Scalar`s and points from those seeds manually by hashing the seed to a curve point. + +## blstrs quirks + +### Size-1 multiexps + +`blstrs 0.7.0` had a bug (originally from `blst`) where size-1 multiexps (sometimes) don't output the correct result: see [this issue](https://github.com/filecoin-project/blstrs/issues/57) opened by Sourav Das. + +As a result, some of our 1 out of 1 weighted PVSS tests which did a secret reconstruction via a size-1 multiexp in G2 failed intermittently. (This test was called `weighted_fail` at commit `5cd69cba8908b6676cf4481457aae93850b6245e`; it runs in a loop until it fails; sometimes it doesn't fail; most of the times it does though.) + +We patched this by clumsily checking for the input size before calling `blstrs`'s multiexp wrapper. + +### $g_1^0$ and $g_2^0$ multiexps can fail +test_crypto_g1_multiexp_less_points +See `test_crypto_g_2_to_zero_multiexp` and `test_crypto_g_1_to_zero_multiexp`. + +### Multiexps with more exponents than bases fail. + +See `test_crypto_g1_multiexp_less_points`. + +Instead, they should truncate the exponents to be the size of the bases. + +### Support for generics is lacking + +e.g., no multiexp trait on G1, G2 and GT + +### Cannot sample GT from random bytes due to unexposed `fp12::Fp12` + +# Notes + +We (mostly) rely on the `aptos-crypto` `SerializeKey` and `DeserializeKey` derives for safety during deserialization. +Specifically, each cryptographic object (e.g., public key, public parameters, etc) must implement `ValidCryptoMaterial` for serialization and `TryFrom` for deserialization when these derives are used. + +The G1/G2 group elements in `blstrs` are deserialized safely via calls to `from_[un]compressed` rather than calls to `from_[un]compressed_unchecked` which does not check prime-order subgroup membership. + +Our structs use $(x, y, z)$ projective coordinates, for faster arithmetic operations. +During serialization, we convert to more succinct $(x, y)$ affine coordinates. + +# Cargo flamegraphs + +Example: You indicate the benchmark group with `--bench` and then you append part of the benchmark name at the end (e.g., `accumulator_poly/` so as to exclude `accumulator_poly_slow/`) +``` +sudo cargo flamegraph --bench 'crypto' -- --bench accumulator_poly/ +``` +# References + +[GJM+21e] Aggregatable Distributed Key Generation; by Kobi Gurkan and Philipp Jovanovic and Mary Maller and Sarah Meiklejohn and Gilad Stern and Alin Tomescu; in Cryptology ePrint Archive, Report 2021/005; 2021; https://eprint.iacr.org/2021/005 diff --git a/crates/aptos-dkg/benches/crypto.rs b/crates/aptos-dkg/benches/crypto.rs new file mode 100644 index 00000000000000..06dcc02d85f54e --- /dev/null +++ b/crates/aptos-dkg/benches/crypto.rs @@ -0,0 +1,457 @@ +// Copyright © Aptos Foundation + +#![allow(clippy::useless_conversion)] + +use aptos_dkg::{ + algebra::{ + evaluation_domain::{BatchEvaluationDomain, EvaluationDomain}, + fft::fft_assign, + polynomials, + }, + utils::{ + g1_multi_exp, g2_multi_exp, hash_to_scalar, + random::{ + insecure_random_gt_point, insecure_random_gt_points, random_g1_point, random_g1_points, + random_g2_point, random_g2_points, random_scalar, random_scalars, + }, + }, +}; +use blstrs::{G1Projective, G2Projective, Gt}; +use criterion::{ + criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, BenchmarkId, + Criterion, Throughput, +}; +use rand::{thread_rng, Rng}; +use rand_core::RngCore; +use std::ops::Mul; + +/// FFT sizes for the benchmarks. +pub const FFT_SIZES: [usize; 6] = [32, 64, 128, 256, 512, 1024]; + +/// Small batch sizes used during benchmarking FFT roots-of-unity computations, hashing & polynomial multiplications +pub const SMALL_SIZES: [usize; 13] = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]; + +/// Large batch sizes used during benchmarking multiexps & FFTs +pub const LARGE_SIZES: [usize; 3] = [8192, 16_384, 32_768]; + +pub fn crypto_group(c: &mut Criterion) { + let mut group = c.benchmark_group("crypto"); + + for thresh in [333, 666, 3_333, 6_6666] { + batch_evaluation_domain_new(thresh, &mut group); + fft_assign_bench(thresh, &mut group); + + gt_multiexp_naive(thresh, &mut group); + g1_multiexp(thresh, &mut group); + g2_multiexp(thresh, &mut group); + + accumulator_poly(thresh, &mut group); + accumulator_poly_slow(thresh, &mut group); + } + + random_scalars_and_points_benches(&mut group); + + // Tried to find some optimal parameters but no luck + for naive in [16, 32, 64, 128, 256, 512] { + for fft in [16, 32, 64, 128, 256, 512] { + if fft / naive > 2 { + continue; + } + accumulator_poly_scheduled(333, naive, fft, &mut group); + } + } + + for n in LARGE_SIZES { + g1_multiexp(n, &mut group); + fft_assign_bench(n, &mut group); + } + + for n in SMALL_SIZES { + hash_to_scalar_bench(n, &mut group); + hash_to_g1_bench(n, &mut group); + hash_to_g2_bench(n, &mut group); + evaluation_domain_new(n, &mut group); + batch_evaluation_domain_get_subdomain(n, &mut group); + poly_mul_slow(n, &mut group); + poly_mul_less_slow(n, &mut group); + } + + for n in FFT_SIZES { + poly_mul_fft(n, &mut group); + poly_mul_fft_with_dom(n, &mut group); + } + + group.finish(); +} + +fn random_scalars_and_points_benches(g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.bench_function("random_scalar", move |b| b.iter(|| random_scalar(&mut rng))); + + g.bench_function("random_g1", move |b| b.iter(|| random_g1_point(&mut rng))); + + g.bench_function("random_g2", move |b| b.iter(|| random_g2_point(&mut rng))); + + g.bench_function("random_gt", move |b| { + b.iter(|| insecure_random_gt_point(&mut rng)) + }); +} + +fn hash_to_scalar_bench(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Bytes(n as u64)); + + g.bench_function(BenchmarkId::new("hash_to_scalar", n), move |b| { + b.iter_with_setup( + || { + let mut bytes = vec![0; n]; + + rng.fill(bytes.as_mut_slice()); + + bytes + }, + |bytes| { + hash_to_scalar( + bytes.as_slice(), + b"criterion benchmark domain separation tag", + ) + }, + ) + }); +} + +fn hash_to_g1_bench(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Bytes(n as u64)); + + g.bench_function(BenchmarkId::new("hash_to_g1", n), move |b| { + b.iter_with_setup( + || { + let mut bytes = vec![0; n]; + + rng.fill(bytes.as_mut_slice()); + + bytes + }, + |bytes| { + G1Projective::hash_to_curve( + bytes.as_slice(), + b"criterion benchmark domain separation tag", + b"criterion benchmark augmented data", + ); + }, + ) + }); +} + +fn hash_to_g2_bench(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Bytes(n as u64)); + + g.bench_function(BenchmarkId::new("hash_to_g2", n), move |b| { + b.iter_with_setup( + || { + let mut bytes = vec![0; n]; + + rng.fill(bytes.as_mut_slice()); + + bytes + }, + |bytes| { + G2Projective::hash_to_curve( + bytes.as_slice(), + b"criterion benchmark domain separation tag", + b"criterion benchmark augmented data", + ); + }, + ) + }); +} + +fn batch_evaluation_domain_new(n: usize, g: &mut BenchmarkGroup) { + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function( + BenchmarkId::new("batch_evaluation_domain::new", n), + move |b| b.iter(|| BatchEvaluationDomain::new(n)), + ); +} + +#[allow(non_snake_case)] +fn batch_evaluation_domain_get_subdomain(N: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.bench_function( + BenchmarkId::new("batch_evaluation_domain::get_subdomain", N), + move |b| { + b.iter_with_setup( + || { + let batch_dom = BatchEvaluationDomain::new(N); + let k = rng.next_u64() as usize % batch_dom.N() + 1; + (batch_dom, k) + }, + |(batch_dom, k)| { + batch_dom.get_subdomain(k); + }, + ) + }, + ); +} + +fn evaluation_domain_new(n: usize, g: &mut BenchmarkGroup) { + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("evaluation_domain::new", n), move |b| { + b.iter(|| EvaluationDomain::new(n)) + }); +} + +fn fft_assign_bench(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("fft_assign", n), move |b| { + b.iter_with_setup( + || { + let poly = random_scalars(n, &mut rng); + let dom = EvaluationDomain::new(n).unwrap(); + (poly, dom) + }, + |(mut poly, dom)| { + fft_assign(&mut poly, &dom); + }, + ) + }); +} + +fn poly_mul_fft(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("poly_mul_assign_fft", n), move |b| { + b.iter_with_setup( + || { + let f = random_scalars(n, &mut rng); + let g = random_scalars(n, &mut rng); + + (f, g) + }, + |(mut f, mut g)| { + polynomials::poly_mul_assign_fft(&mut f, &mut g); + }, + ) + }); +} + +fn poly_mul_fft_with_dom(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function( + BenchmarkId::new("poly_mul_assign_fft_with_dom", n), + move |b| { + b.iter_with_setup( + || { + let f = random_scalars(n, &mut rng); + let g = random_scalars(n, &mut rng); + + (f, g, EvaluationDomain::new(2 * n - 1).unwrap()) + }, + |(mut f, mut g, dom)| { + polynomials::poly_mul_assign_fft_with_dom(&mut f, &mut g, &dom); + }, + ) + }, + ); +} + +fn poly_mul_slow(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("poly_mul_slow", n), move |b| { + b.iter_with_setup( + || { + let f = random_scalars(n, &mut rng); + let g = random_scalars(n, &mut rng); + + (f, g) + }, + |(f, g)| { + polynomials::poly_mul_slow(&f, &g); + }, + ) + }); +} + +fn poly_mul_less_slow(n: usize, g: &mut BenchmarkGroup) { + assert_eq!((n & (n - 1)), 0); // should be a power of two + + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("poly_mul_less_slow", n), move |b| { + b.iter_with_setup( + || { + let f = random_scalars(n, &mut rng); + let g = random_scalars(n, &mut rng); + + (f, g) + }, + |(f, g)| { + polynomials::poly_mul_less_slow(&f, &g); + }, + ) + }); +} + +#[allow(non_snake_case)] +fn accumulator_poly(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + let FFT_THRESH = 128; // 256 seems to produce the same result + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("accumulator_poly", n), move |b| { + b.iter_with_setup( + || (random_scalars(n, &mut rng), BatchEvaluationDomain::new(n)), + |(set, batch_dom)| { + polynomials::accumulator_poly(set.as_slice(), &batch_dom, FFT_THRESH); + }, + ) + }); +} + +fn accumulator_poly_slow(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("accumulator_poly_slow", n), move |b| { + b.iter_with_setup( + || random_scalars(n, &mut rng), + |set| { + polynomials::accumulator_poly_slow(set.as_slice()); + }, + ) + }); +} + +#[allow(non_snake_case)] +fn accumulator_poly_scheduled( + n: usize, + naive_thresh: usize, + fft_thresh: usize, + g: &mut BenchmarkGroup, +) { + let mut rng = thread_rng(); + + // 16 FFT, 32 naive -> 24.3ms + // 64 FFT, 128 naive -> 17ms + // 128 FFT, 256 naive -> 15.4 ms + // 256 FFT, 512 naive -> 14.8 ms + // 256 FFT, 128 naive -> 14.1 ms + + g.throughput(Throughput::Elements(n as u64)); + + let name = format!( + "accumulator_poly_scheduled/{}/naive={}/fft={}", + n, naive_thresh, fft_thresh + ); + g.bench_function(name, move |b| { + b.iter_with_setup( + || (random_scalars(n, &mut rng), BatchEvaluationDomain::new(n)), + |(set, batch_dom)| { + polynomials::accumulator_poly_scheduled( + set.as_slice(), + &batch_dom, + naive_thresh, + fft_thresh, + ); + }, + ) + }); +} + +fn g1_multiexp(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("g1_multiexp", n), move |b| { + b.iter_with_setup( + || { + let points = random_g1_points(n, &mut rng); + + let scalars = random_scalars(n, &mut rng); + + (points, scalars) + }, + |(points, scalars)| { + g1_multi_exp(points.as_slice(), scalars.as_ref()); + }, + ) + }); +} + +fn g2_multiexp(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("g2_multiexp", n), move |b| { + b.iter_with_setup( + || { + let points = random_g2_points(n, &mut rng); + + let scalars = random_scalars(n, &mut rng); + + (points, scalars) + }, + |(points, scalars)| { + g2_multi_exp(points.as_slice(), scalars.as_ref()); + }, + ) + }); +} + +fn gt_multiexp_naive(n: usize, g: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function(BenchmarkId::new("gt_multiexp_naive", n), move |b| { + b.iter_with_setup( + || { + let points = insecure_random_gt_points(n, &mut rng); + + let scalars = random_scalars(n, &mut rng); + + (points, scalars) + }, + |(points, scalars)| { + points + .into_iter() + .zip(scalars.into_iter()) + .map(|(p, s)| p.mul(s)) + .sum::() + }, + ) + }); +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(10); + //config = Criterion::default(); + targets = crypto_group); +criterion_main!(benches); diff --git a/crates/aptos-dkg/benches/lagrange.rs b/crates/aptos-dkg/benches/lagrange.rs new file mode 100644 index 00000000000000..d45c6074e7acfc --- /dev/null +++ b/crates/aptos-dkg/benches/lagrange.rs @@ -0,0 +1,61 @@ +// Copyright © Aptos Foundation + +use aptos_dkg::algebra::{ + evaluation_domain::BatchEvaluationDomain, lagrange::lagrange_coefficients, +}; +use blstrs::Scalar; +use criterion::{ + criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, BenchmarkId, + Criterion, Throughput, +}; +use ff::Field; +use more_asserts::{assert_ge, assert_le}; +use rand::{seq::IteratorRandom, thread_rng}; + +pub fn lagrange_group(c: &mut Criterion) { + let mut group = c.benchmark_group("lagrange"); + + lagrange_tcz20(333, 1_000, &mut group); + lagrange_tcz20(666, 1_000, &mut group); + lagrange_tcz20(3333, 10_000, &mut group); + lagrange_tcz20(6666, 10_000, &mut group); + + group.finish(); +} + +#[allow(non_snake_case)] +pub fn lagrange_tcz20(thresh: usize, n: usize, g: &mut BenchmarkGroup) { + assert_ge!(thresh, 1); + assert_le!(thresh, n); + let mut rng = thread_rng(); + + g.throughput(Throughput::Elements(n as u64)); + + g.bench_function( + BenchmarkId::new(format!("tcz20-thresh={thresh}"), n), + move |b| { + b.iter_with_setup( + || { + let players: Vec = (0..n) + .choose_multiple(&mut rng, thresh) + .into_iter() + .collect::>(); + + let batch_dom = BatchEvaluationDomain::new(n); + + (players, batch_dom) + }, + |(players, batch_dom)| { + lagrange_coefficients(&batch_dom, players.as_slice(), &Scalar::ZERO); + }, + ) + }, + ); +} + +criterion_group!( + name = benches; + //config = Criterion::default().sample_size(10); + config = Criterion::default(); + targets = lagrange_group); +criterion_main!(benches); diff --git a/crates/aptos-dkg/benches/pvss.rs b/crates/aptos-dkg/benches/pvss.rs new file mode 100644 index 00000000000000..5d263b31222418 --- /dev/null +++ b/crates/aptos-dkg/benches/pvss.rs @@ -0,0 +1,256 @@ +// Copyright © Aptos Foundation + +#![allow(clippy::ptr_arg)] +#![allow(clippy::needless_borrow)] + +use aptos_crypto::Uniform; +use aptos_dkg::{ + pvss, + pvss::{ + test_utils, + test_utils::{ + get_threshold_configs_for_benchmarking, get_weighted_configs_for_benchmarking, NoAux, + }, + traits::{ + transcript::{MalleableTranscript, Transcript}, + SecretSharingConfig, + }, + GenericWeighting, + }, +}; +use criterion::{ + criterion_group, criterion_main, + measurement::{Measurement, WallTime}, + BenchmarkGroup, Criterion, Throughput, +}; +use more_asserts::assert_le; +use rand::{rngs::ThreadRng, thread_rng, Rng}; + +pub fn all_groups(c: &mut Criterion) { + // unweighted PVSS + for tc in get_threshold_configs_for_benchmarking() { + pvss_group::(&tc, c); + } + + // weighted PVSS + for wc in get_weighted_configs_for_benchmarking() { + pvss_group::(&wc, c); + pvss_group::>(&wc, c); + } +} + +pub fn pvss_group(sc: &T::SecretSharingConfig, c: &mut Criterion) { + let name = T::scheme_name(); + let mut group = c.benchmark_group(format!("pvss/{}", name)); + let mut rng = thread_rng(); + + // TODO: use a lazy pattern to avoid this expensive step when no benchmarks are run + let (pp, ssks, spks, dks, eks, iss, s, _) = + test_utils::setup_dealing::(sc, &mut rng); + + // pvss_transcript_random::(sc, &mut group); + pvss_deal::(sc, &pp, &ssks, &eks, &mut group); + pvss_aggregate::(sc, &mut group); + pvss_verify::(sc, &pp, &ssks, &spks, &eks, &mut group); + pvss_aggregate_verify::(sc, &pp, &ssks, &spks, &eks, &iss[0], 100, &mut group); + pvss_decrypt_own_share::(sc, &pp, &ssks, &dks, &eks, &s, &mut group); + + group.finish(); +} + +fn pvss_deal( + sc: &T::SecretSharingConfig, + pp: &T::PublicParameters, + ssks: &Vec, + eks: &Vec, + g: &mut BenchmarkGroup, +) { + g.throughput(Throughput::Elements(sc.get_total_num_shares() as u64)); + + let mut rng = thread_rng(); + + g.bench_function(format!("deal/{}", sc), move |b| { + b.iter_with_setup( + || { + let s = T::InputSecret::generate(&mut rng); + (s, rng) + }, + |(s, mut rng)| { + T::deal( + &sc, + &pp, + &ssks[0], + &eks, + &s, + &NoAux, + &sc.get_player(0), + &mut rng, + ) + }, + ) + }); +} + +fn pvss_aggregate( + sc: &T::SecretSharingConfig, + g: &mut BenchmarkGroup, +) { + g.throughput(Throughput::Elements(sc.get_total_num_shares() as u64)); + let mut rng = thread_rng(); + + g.bench_function(format!("aggregate/{}", sc), move |b| { + b.iter_with_setup( + || { + let trx = T::generate(&sc, &mut rng); + (trx.clone(), trx) + }, + |(mut first, second)| { + first.aggregate_with(&sc, &second); + }, + ) + }); +} + +fn pvss_verify( + sc: &T::SecretSharingConfig, + pp: &T::PublicParameters, + ssks: &Vec, + spks: &Vec, + eks: &Vec, + g: &mut BenchmarkGroup, +) { + g.throughput(Throughput::Elements(sc.get_total_num_shares() as u64)); + + let mut rng = thread_rng(); + + g.bench_function(format!("verify/{}", sc), move |b| { + b.iter_with_setup( + || { + let s = T::InputSecret::generate(&mut rng); + T::deal( + &sc, + &pp, + &ssks[0], + &eks, + &s, + &NoAux, + &sc.get_player(0), + &mut rng, + ) + }, + |trx| { + trx.verify(&sc, &pp, &vec![spks[0].clone()], &eks, &vec![NoAux]) + .expect("PVSS transcript verification should succeed"); + }, + ) + }); +} + +fn pvss_aggregate_verify( + sc: &T::SecretSharingConfig, + pp: &T::PublicParameters, + ssks: &Vec, + spks: &Vec, + eks: &Vec, + iss: &T::InputSecret, + num_aggr: usize, + g: &mut BenchmarkGroup, +) { + // Currently, our codebase assumes a DKG setting where there are as many dealers as there are + // players obtaining shares. (In other settings, there could be 1 million dealers, dealing a + // secret to only 100 players such that, say, any 50 can reconstruct them.) + assert_le!(num_aggr, sc.get_total_num_players()); + + g.throughput(Throughput::Elements(sc.get_total_num_shares() as u64)); + + let mut rng = thread_rng(); + + // Aggregated transcript will have SoKs from `num_aggr` players. + let mut spks = spks.clone(); + spks.truncate(num_aggr); + + g.bench_function(format!("aggregate_verify/{}/{}", sc, num_aggr), move |b| { + b.iter_with_setup( + || { + let mut trxs = vec![]; + trxs.push(T::deal( + &sc, + &pp, + &ssks[0], + &eks, + iss, + &NoAux, + &sc.get_player(0), + &mut rng, + )); + + for (i, ssk) in ssks.iter().enumerate().skip(1).take(num_aggr) { + let mut trx = trxs[0].clone(); + trx.maul_signature(ssk, &NoAux, &sc.get_player(i)); + trxs.push(trx); + } + + T::aggregate(sc, trxs).unwrap() + }, + |trx| { + trx.verify(&sc, &pp, &spks, &eks, &vec![NoAux; num_aggr]) + .expect("aggregate PVSS transcript verification should succeed"); + }, + ) + }); +} + +fn pvss_decrypt_own_share( + sc: &T::SecretSharingConfig, + pp: &T::PublicParameters, + ssks: &Vec, + dks: &Vec, + eks: &Vec, + s: &T::InputSecret, + g: &mut BenchmarkGroup, +) { + g.throughput(Throughput::Elements(sc.get_total_num_shares() as u64)); + + let mut rng = thread_rng(); + + let trx = T::deal( + &sc, + &pp, + &ssks[0], + &eks, + &s, + &NoAux, + &sc.get_player(0), + &mut rng, + ); + + g.bench_function(format!("decrypt-share/{}", sc), move |b| { + b.iter_with_setup( + || rng.gen_range(0, sc.get_total_num_players()), + |i| { + trx.decrypt_own_share(&sc, &sc.get_player(i), &dks[i]); + }, + ) + }); +} + +#[allow(dead_code)] +fn pvss_transcript_random( + sc: &T::SecretSharingConfig, + g: &mut BenchmarkGroup, +) { + g.throughput(Throughput::Elements(sc.get_total_num_shares() as u64)); + + let mut rng = thread_rng(); + + g.bench_function(format!("transcript-random/{}", sc), move |b| { + b.iter(|| T::generate(&sc, &mut rng)) + }); +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(10); + //config = Criterion::default(); + targets = all_groups); +criterion_main!(benches); diff --git a/crates/aptos-dkg/benches/weighted_vuf.rs b/crates/aptos-dkg/benches/weighted_vuf.rs new file mode 100644 index 00000000000000..429f84eab1df07 --- /dev/null +++ b/crates/aptos-dkg/benches/weighted_vuf.rs @@ -0,0 +1,391 @@ +// Copyright © Aptos Foundation + +#![allow(clippy::needless_range_loop)] +#![allow(clippy::ptr_arg)] +#![allow(clippy::extra_unused_type_parameters)] +#![allow(clippy::needless_borrow)] + +use aptos_dkg::{ + pvss::{ + das, insecure_field, + test_utils::{get_weighted_configs_for_benchmarking, setup_dealing, NoAux}, + traits::{SecretSharingConfig, Transcript}, + GenericWeighting, Player, WeightedConfig, + }, + weighted_vuf::{bls, pinkas::PinkasWUF, traits::WeightedVUF}, +}; +use core::iter::zip; +use criterion::{ + criterion_group, criterion_main, + measurement::{Measurement, WallTime}, + BenchmarkGroup, Criterion, +}; +use rand::{rngs::ThreadRng, thread_rng}; + +const BENCH_MSG: &[u8; 36] = b"some dummy message for the benchmark"; + +pub fn all_groups(c: &mut Criterion) { + let mut group = c.benchmark_group("wvuf/das-pinkas-sk-in-g1"); + wvuf_benches::(&mut group); + group.finish(); + + let mut group = c.benchmark_group("wvuf/insecure-field-bls"); + wvuf_benches::, bls::BlsWUF, WallTime>(&mut group); + group.finish(); +} + +pub fn wvuf_benches< + WT: Transcript, + WVUF: WeightedVUF< + SecretKey = WT::DealtSecretKey, + PubKeyShare = WT::DealtPubKeyShare, + SecretKeyShare = WT::DealtSecretKeyShare, + >, + M: Measurement, +>( + group: &mut BenchmarkGroup, +) where + WVUF::PublicParameters: for<'a> From<&'a WT::PublicParameters>, +{ + let mut rng = thread_rng(); + + let mut bench_cases = vec![]; + for wc in get_weighted_configs_for_benchmarking() { + // TODO: use a lazy pattern to avoid this expensive dealing when no benchmarks are run + let (pvss_pp, ssks, _spks, dks, eks, iss, _s, dsk) = + setup_dealing::(&wc, &mut rng); + + println!( + "Best-case subset size: {}", + wc.get_best_case_eligible_subset_of_players(&mut rng).len() + ); + println!( + "Worst-case subset size: {}", + wc.get_worst_case_eligible_subset_of_players(&mut rng).len() + ); + + println!("Dealing a {} PVSS transcript", WT::scheme_name()); + let trx = WT::deal( + &wc, + &pvss_pp, + &ssks[0], + &eks, + &iss[0], + &NoAux, + &wc.get_player(0), + &mut rng, + ); + + let vuf_pp = WVUF::PublicParameters::from(&pvss_pp); + + let mut sks = vec![]; + let mut pks = vec![]; + let mut asks = vec![]; + let mut apks = vec![]; + let mut deltas = vec![]; + println!( + "Decrypting shares from {} PVSS transcript", + WT::scheme_name() + ); + for i in 0..wc.get_total_num_players() { + let (sk, pk) = trx.decrypt_own_share(&wc, &wc.get_player(i), &dks[i]); + + let (ask, apk) = WVUF::augment_key_pair(&vuf_pp, sk.clone(), pk.clone(), &mut rng); + sks.push(sk); + pks.push(pk); + deltas.push(WVUF::get_public_delta(&apk).clone()); + asks.push(ask); + apks.push(apk); + } + println!(); + + bench_cases.push((wc, vuf_pp, dsk, sks, pks, asks, apks, deltas)); + } + + for (wc, vuf_pp, sk, sks, pks, asks, apks, deltas) in bench_cases { + wvuf_augment_random_keypair::( + &wc, &vuf_pp, &sks, &pks, group, &mut rng, + ); + + wvuf_augment_all_pubkeys::(&wc, &vuf_pp, &pks, &deltas, group); + + wvuf_augment_random_pubkey::( + &wc, &vuf_pp, &pks, &deltas, group, &mut rng, + ); + + wvuf_create_share::(&wc, &asks, group, &mut rng); + + wvuf_verify_share::(&wc, &vuf_pp, &asks, &apks, group, &mut rng); + + // best-case aggregation times (pick players with largest weights) + wvuf_aggregate_shares::( + &wc, + &asks, + &apks, + group, + &mut rng, + WeightedConfig::get_worst_case_eligible_subset_of_players, + "best_case".to_string(), + ); + + // average/random case aggregation time + wvuf_aggregate_shares::( + &wc, + &asks, + &apks, + group, + &mut rng, + WeightedConfig::get_random_eligible_subset_of_players, + "random".to_string(), + ); + + // worst-case aggregation times (pick players with smallest weights) + wvuf_aggregate_shares::( + &wc, + &asks, + &apks, + group, + &mut rng, + WeightedConfig::get_worst_case_eligible_subset_of_players, + "worst_case".to_string(), + ); + + wvuf_eval::(&wc, &sk, group); + + // TODO: verify_proof (but needs efficient create_proof) + + // TODO: derive_eval (but needs efficient create_proof) + } +} + +fn wvuf_augment_random_keypair< + WT: Transcript, + WVUF: WeightedVUF< + SecretKey = WT::DealtSecretKey, + PubKeyShare = WT::DealtPubKeyShare, + SecretKeyShare = WT::DealtSecretKeyShare, + >, + R: rand_core::RngCore + rand_core::CryptoRng, + M: Measurement, +>( + // For efficiency, we re-use the PVSS transcript + wc: &WeightedConfig, + vuf_pp: &WVUF::PublicParameters, + sks: &Vec, + pks: &Vec, + group: &mut BenchmarkGroup, + rng: &mut R, +) where + WVUF::PublicParameters: for<'a> From<&'a WT::PublicParameters>, +{ + group.bench_function(format!("augment_random_keypair/{}", wc), move |b| { + b.iter_with_setup( + || { + // Ugh, borrow checker... + let id = wc.get_random_player(&mut thread_rng()).id; + (sks[id].clone(), pks[id].clone()) + }, + |(sk, pk)| WVUF::augment_key_pair(vuf_pp, sk, pk, rng), + ) + }); +} + +fn wvuf_augment_all_pubkeys< + WT: Transcript, + WVUF: WeightedVUF< + SecretKey = WT::DealtSecretKey, + PubKeyShare = WT::DealtPubKeyShare, + SecretKeyShare = WT::DealtSecretKeyShare, + >, + R: rand_core::RngCore + rand_core::CryptoRng, + M: Measurement, +>( + // For efficiency, we re-use the PVSS transcript + wc: &WeightedConfig, + vuf_pp: &WVUF::PublicParameters, + pks: &Vec, + deltas: &Vec, + group: &mut BenchmarkGroup, +) where + WVUF::PublicParameters: for<'a> From<&'a WT::PublicParameters>, +{ + assert_eq!(pks.len(), wc.get_total_num_players()); + assert_eq!(pks.len(), deltas.len()); + group.bench_function(format!("augment_all_pubkeys/{}", wc), move |b| { + b.iter(|| { + for (pk, delta) in zip(pks, deltas) { + WVUF::augment_pubkey(vuf_pp, pk.clone(), delta.clone()) + .expect("augmentation should have succeeded"); + } + }) + }); +} + +fn wvuf_augment_random_pubkey< + WT: Transcript, + WVUF: WeightedVUF< + SecretKey = WT::DealtSecretKey, + PubKeyShare = WT::DealtPubKeyShare, + SecretKeyShare = WT::DealtSecretKeyShare, + >, + R: rand_core::RngCore + rand_core::CryptoRng, + M: Measurement, +>( + // For efficiency, we re-use the PVSS transcript + wc: &WeightedConfig, + vuf_pp: &WVUF::PublicParameters, + pks: &Vec, + deltas: &Vec, + group: &mut BenchmarkGroup, + rng: &mut R, +) where + WVUF::PublicParameters: for<'a> From<&'a WT::PublicParameters>, +{ + group.bench_function(format!("augment_random_pubkey/{}", wc), move |b| { + b.iter_with_setup( + || { + // Ugh, borrow checker... + let id = wc.get_random_player(rng).id; + let pk = pks[id].clone(); + let delta = deltas[id].clone(); + + (pk, delta) + }, + |(pk, delta)| WVUF::augment_pubkey(vuf_pp, pk, delta), + ) + }); +} + +fn wvuf_create_share< + WT: Transcript, + WVUF: WeightedVUF< + SecretKey = WT::DealtSecretKey, + PubKeyShare = WT::DealtPubKeyShare, + SecretKeyShare = WT::DealtSecretKeyShare, + >, + R: rand_core::RngCore + rand_core::CryptoRng, + M: Measurement, +>( + wc: &WeightedConfig, + asks: &Vec, + group: &mut BenchmarkGroup, + rng: &mut R, +) where + WVUF::PublicParameters: for<'a> From<&'a WT::PublicParameters>, +{ + group.bench_function(format!("create_share/{}", wc), move |b| { + b.iter_with_setup( + || { + let player = wc.get_random_player(rng); + &asks[player.id] + }, + |ask| WVUF::create_share(ask, BENCH_MSG), + ) + }); +} + +fn wvuf_verify_share< + WT: Transcript, + WVUF: WeightedVUF< + SecretKey = WT::DealtSecretKey, + PubKeyShare = WT::DealtPubKeyShare, + SecretKeyShare = WT::DealtSecretKeyShare, + >, + R: rand_core::RngCore + rand_core::CryptoRng, + M: Measurement, +>( + wc: &WeightedConfig, + vuf_pp: &WVUF::PublicParameters, + asks: &Vec, + apks: &Vec, + group: &mut BenchmarkGroup, + rng: &mut R, +) where + WVUF::PublicParameters: for<'a> From<&'a WT::PublicParameters>, +{ + group.bench_function(format!("verify_share/{}", wc), move |b| { + b.iter_with_setup( + || { + let player = wc.get_random_player(rng); + let ask = &asks[player.id]; + + (WVUF::create_share(ask, BENCH_MSG), &apks[player.id]) + }, + |(proof, apk)| WVUF::verify_share(vuf_pp, apk, BENCH_MSG, &proof), + ) + }); +} + +fn wvuf_aggregate_shares< + WT: Transcript, + WVUF: WeightedVUF< + SecretKey = WT::DealtSecretKey, + PubKeyShare = WT::DealtPubKeyShare, + SecretKeyShare = WT::DealtSecretKeyShare, + >, + R: rand_core::RngCore + rand_core::CryptoRng, + M: Measurement, +>( + // For efficiency, we re-use the PVSS transcript + wc: &WeightedConfig, + asks: &Vec, + apks: &Vec, + group: &mut BenchmarkGroup, + rng: &mut R, + pick_subset_fn: fn(&WeightedConfig, &mut R) -> Vec, + subset_type: String, +) where + WVUF::PublicParameters: for<'a> From<&'a WT::PublicParameters>, +{ + group.bench_function( + format!("aggregate_{}_shares/{}", subset_type, wc), + move |b| { + b.iter_with_setup( + || { + let players = pick_subset_fn(wc, rng); + + players + .iter() + .map(|p| { + ( + *p, + apks[p.id].clone(), + WVUF::create_share(&asks[p.id], BENCH_MSG), + ) + }) + .collect::>() + }, + |apks_and_proofs| { + WVUF::aggregate_shares(&wc, apks_and_proofs.as_slice()); + }, + ) + }, + ); +} + +fn wvuf_eval< + WT: Transcript, + WVUF: WeightedVUF< + SecretKey = WT::DealtSecretKey, + PubKeyShare = WT::DealtPubKeyShare, + SecretKeyShare = WT::DealtSecretKeyShare, + >, + M: Measurement, +>( + wc: &WeightedConfig, + sk: &WVUF::SecretKey, + group: &mut BenchmarkGroup, +) where + WVUF::PublicParameters: for<'a> From<&'a WT::PublicParameters>, +{ + group.bench_function(format!("eval/{}", wc), move |b| { + b.iter_with_setup(|| {}, |_| WVUF::eval(sk, BENCH_MSG)) + }); +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(10); + //config = Criterion::default(); + targets = all_groups); +criterion_main!(benches); diff --git a/crates/aptos-dkg/src/algebra/evaluation_domain.rs b/crates/aptos-dkg/src/algebra/evaluation_domain.rs new file mode 100644 index 00000000000000..e66213bca9597c --- /dev/null +++ b/crates/aptos-dkg/src/algebra/evaluation_domain.rs @@ -0,0 +1,222 @@ +// Copyright © Aptos Foundation + +use aptos_crypto::CryptoMaterialError; +use blstrs::Scalar; +use ff::{Field, PrimeField}; +use more_asserts::{assert_gt, assert_le}; +use serde::{Deserialize, Serialize}; + +/// This struct abstracts the notion of an FFT evaluation domain over the scalar field of our curve. +/// This consists of $N = 2^k$ and an $N$th root of unity. (The $\log_2{N}$ field is just handy in our FFT +/// implementation.) +#[allow(non_snake_case)] +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct EvaluationDomain { + /// The actual number $n$ of evaluations we want, which might not be a power of two. + pub(crate) n: usize, + /// The smallest power of two $N \ge n$. + pub(crate) N: usize, + /// $\log_2{N}$ + pub(crate) log_N: usize, + /// An $N$th primitive root of unity $\omega$ that generates a multiplicative subgroup $\{\omega^0, \omega^1, \ldots, \omega^{N-1}\}$. + pub(crate) omega: Scalar, + /// The inverse $\omega^{-1}$. + pub(crate) omega_inverse: Scalar, + // geninv: Scalar, + /// The inverse of $N$ when viewed as a scalar in the multiplicative group of the scalar field. + pub(crate) N_inverse: Scalar, +} + +/// A `BatchEvaluationDomain` struct encodes multiple `EvaluationDomain` structs in a more efficient way. +/// This is very useful when doing FFTs of different sizes (e.g., 1, 2, 4, 8, 16, ... in an accumulator-style +/// polynomial multiplication; see `accumulator_poly` function) and we want to avoid recomputing the +/// same roots of unity multiple times, as well as other scalars. +#[allow(non_snake_case)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct BatchEvaluationDomain { + /// $\log_2{N}$ + pub(crate) log_N: usize, + /// A vector of all $N$th roots of unity $\{\omega^0, \omega^1, \omega^2, \ldots, \omega^{N-1}\}$ + omegas: Vec, + /// A vector of $i^{-1}$, for all $i \in \{1, 2, 4, 8, \ldots, N/2, N\}$ + N_inverses: Vec, +} + +/// Returns the highest $N = 2^k$ such that $N \ge n$. +#[allow(non_snake_case)] +pub fn smallest_power_of_2_greater_than_or_eq(n: usize) -> (usize, usize) { + let mut N = 1; + let mut log_N: usize = 0; + + while N < n { + N <<= 1; + log_N += 1; + } + + (N, log_N) +} + +impl EvaluationDomain { + /// `n` is the max number of evaluations at the roots of unity we want to get, but might not be + /// a power of two. So we find the smallest $N$ which a power of two such that $N >= n$. + /// + /// For FFT-based multiplication, we set $n - 1$ to be the degree of the polynomials we are + /// multiplying. + #[allow(non_snake_case)] + pub fn new(n: usize) -> Result { + // Compute the size of our evaluation domain + let (N, log_N) = smallest_power_of_2_greater_than_or_eq(n); + + // The pairing-friendly curve may not be able to support + // large enough (radix2) evaluation domains. + if log_N >= Scalar::S as usize { + return Err(CryptoMaterialError::WrongLengthError); + } + + // Compute $\omega$, the $N$th primitive root of unity + let omega = Self::get_Nth_root_of_unity(log_N); + + Ok(EvaluationDomain { + n, + N, + log_N, + omega, + omega_inverse: omega.invert().unwrap(), + // geninv: Scalar::multiplicative_generator().invert().unwrap(), + N_inverse: Scalar::from(N as u64).invert().unwrap(), + }) + } + + /// Efficiently returns the $N$th primitive root of unity that's been precomputed in this evaluation domain. + pub fn get_primitive_root_of_unity(&self) -> &Scalar { + &self.omega + } + + /// Returns a primitive $N$th root of unity in the scalar field, given $\log_2{N}$ as an argument. + #[allow(non_snake_case)] + fn get_Nth_root_of_unity(log_N: usize) -> Scalar { + let mut omega = Scalar::ROOT_OF_UNITY; + for _ in log_N..Scalar::S as usize { + omega = omega.square(); + } + omega + } +} + +impl BatchEvaluationDomain { + /// Returns a batch evaluation domain for FFTs of size $1, 2, 4, 8, 16, \ldots n$, where $n$ is the + /// number of coefficients in the polynomial $f(X) \cdot g(X)$. + /// + /// This then allows more efficient fetching of subdomains for any of those sizes than via + /// `get_evaluation_dom_for_multiplication`. + #[allow(non_snake_case)] + pub fn new(n: usize) -> Self { + let (N, log_N) = smallest_power_of_2_greater_than_or_eq(n); + let omega = EvaluationDomain::get_Nth_root_of_unity(log_N); + + let mut omegas = Vec::with_capacity(N); + omegas.push(Scalar::ONE); + + let mut acc = omega; + for _ in 1..N { + omegas.push(acc); + acc *= omega; // $\omega^i$ + } + + debug_assert_eq!(omegas.len(), N); + + let mut N_inverses = Vec::with_capacity(log_N); + let mut i = 1u64; + for _ in 0..=log_N { + N_inverses.push(Scalar::from(i).invert().unwrap()); + + i *= 2; + } + + debug_assert_eq!( + N_inverses.last().unwrap().invert().unwrap(), + Scalar::from(N as u64) + ); + + BatchEvaluationDomain { + log_N, + omegas, + N_inverses, + } + } + + #[allow(non_snake_case)] + pub fn N(&self) -> usize { + self.omegas.len() + } + + /// Returns the equivalent of `EvaluationDomain::new(k)`, but much faster since everything is precomputed. + #[allow(non_snake_case)] + pub fn get_subdomain(&self, k: usize) -> EvaluationDomain { + assert_le!(k, self.omegas.len()); + assert_ne!(k, 0); + + let (K, log_K) = smallest_power_of_2_greater_than_or_eq(k); + assert_gt!(K, 0); + + let K_inverse = self.N_inverses[log_K]; + debug_assert_eq!(K_inverse.invert().unwrap(), Scalar::from(K as u64)); + + let mut idx = 1; + for _ in log_K..self.log_N { + // i.e., omega = omega.square(); + idx *= 2; + } + + let N = self.omegas.len(); + let omega = self.omegas[idx % N]; + debug_assert!(Self::is_order(&omega, K)); + + let omega_inverse = self.omegas[(N - idx) % N]; + debug_assert_eq!(omega_inverse.invert().unwrap(), omega); + + EvaluationDomain { + n: k, + N: K, + log_N: log_K, + omega, + omega_inverse, + N_inverse: K_inverse, + } + } + + /// Efficiently returns the $i$th $N$th root of unity $\omega^i$, for $i\in[0, N)$. + pub fn get_root_of_unity(&self, i: usize) -> Scalar { + self.omegas[i] + } + + /// Efficiently returns all the $N$th roots of unity. + pub fn get_all_roots_of_unity(&self) -> &Vec { + self.omegas.as_ref() + } + + /// Asserts the order of $\omega$ is $K$. + #[allow(non_snake_case)] + fn is_order(omega: &Scalar, K: usize) -> bool { + assert_gt!(K, 0); + let mut acc = omega.clone(); + + // First, check that \omega^1, \omega^2, \omega^{K-1} are NOT the identity. + for _ in 1..=K - 1 { + // println!("\\omega^{i}: {acc}"); + if acc == Scalar::ONE { + return false; + } + acc *= omega; + } + + // Last, check that \omega^K = \omega^0, i.e., the identity + // println!("\\omega^{K}: {acc}"); + if acc != Scalar::ONE { + return false; + } + // println!(); + + return true; + } +} diff --git a/crates/aptos-dkg/src/algebra/fft.rs b/crates/aptos-dkg/src/algebra/fft.rs new file mode 100644 index 00000000000000..175e983666aa1b --- /dev/null +++ b/crates/aptos-dkg/src/algebra/fft.rs @@ -0,0 +1,112 @@ +// Copyright © Aptos Foundation + +use crate::algebra::evaluation_domain::EvaluationDomain; +use blstrs::Scalar; +use ff::Field; +use std::ops::{AddAssign, MulAssign, SubAssign}; + +/// Computes a Discrete Fourier Transform (DFT), a.k.a., an FFT, on the polynomial $f(X)$ in `poly`, +/// returning all the $N$ evaluations at the roots of unity: $f(\omega^0), f(\omega^1), \ldots, f(\omega^{N-1})$ +/// where `dom.log_N` is $\log_2{N}$ and `dom.omega` is $\omega$. +pub fn fft_assign(poly: &mut Vec, dom: &EvaluationDomain) { + // Pad with zeros, if necessary + if poly.len() < dom.N { + poly.resize(dom.N, Scalar::ZERO); + } + + serial_fft_assign(poly.as_mut_slice(), &dom.omega, dom.log_N as u32) +} + +pub fn fft(poly: &Vec, dom: &EvaluationDomain) -> Vec { + let mut evals = Vec::with_capacity(dom.N); + evals.resize(poly.len(), Scalar::ZERO); + evals.copy_from_slice(&poly); + + fft_assign(&mut evals, dom); + + evals +} + +/// Computes the inverse of `fft_assign`. +pub fn ifft_assign(poly: &mut Vec, dom: &EvaluationDomain) { + serial_fft_assign(poly.as_mut_slice(), &dom.omega_inverse, dom.log_N as u32); + + for coeff in poly { + coeff.mul_assign(&dom.N_inverse); + } +} + +/// `bellman`'s FFT code adapted to `blstrs::Scalar`. +fn serial_fft_assign(a: &mut [Scalar], omega: &Scalar, log_n: u32) { + fn bitreverse(mut n: u32, l: u32) -> u32 { + let mut r = 0; + for _ in 0..l { + r = (r << 1) | (n & 1); + n >>= 1; + } + r + } + + let n = a.len() as u32; + assert_eq!(n, 1 << log_n); + + for k in 0..n { + let rk = bitreverse(k, log_n); + if k < rk { + a.swap(rk as usize, k as usize); + } + } + + let mut m = 1; + for _ in 0..log_n { + // TODO(Performance): Could have these precomputed via BatchEvaluationDomain, but need to + // update all upstream calls to pass in the `BatchEvaluationDomain`. + let w_m = omega.pow_vartime([u64::from(n / (2 * m))]); + + let mut k = 0; + while k < n { + let mut w = Scalar::ONE; + for j in 0..m { + let mut t = a[(k + j + m) as usize]; + t.mul_assign(&w); + let mut tmp = a[(k + j) as usize]; + tmp.sub_assign(&t); + a[(k + j + m) as usize] = tmp; + a[(k + j) as usize].add_assign(&t); + w.mul_assign(&w_m); + } + + k += 2 * m; + } + + m *= 2; + } +} + +#[cfg(test)] +mod test { + use crate::{ + algebra::{ + evaluation_domain::{smallest_power_of_2_greater_than_or_eq, EvaluationDomain}, + fft::fft_assign, + }, + utils::random::random_scalars, + }; + use rand::thread_rng; + + #[test] + #[allow(non_snake_case)] + fn fft_assign_full_domain() { + for n in [1, 2, 3, 5, 7, 8, 10, 16] { + let dom = EvaluationDomain::new(n).unwrap(); + let (N, _) = smallest_power_of_2_greater_than_or_eq(n); + + let mut rng = thread_rng(); + let mut f = random_scalars(n, &mut rng); + + assert_eq!(f.len(), n); + fft_assign(&mut f, &dom); + assert_eq!(f.len(), N); + } + } +} diff --git a/crates/aptos-dkg/src/algebra/lagrange.rs b/crates/aptos-dkg/src/algebra/lagrange.rs new file mode 100644 index 00000000000000..bea620f59e3b79 --- /dev/null +++ b/crates/aptos-dkg/src/algebra/lagrange.rs @@ -0,0 +1,407 @@ +// Copyright © Aptos Foundation + +use crate::algebra::{ + evaluation_domain::BatchEvaluationDomain, + fft::{fft, fft_assign}, + polynomials::{accumulator_poly, poly_differentiate, poly_eval, poly_mul_slow}, +}; +use blstrs::Scalar; +use ff::{BatchInvert, Field}; +use more_asserts::{assert_gt, debug_assert_le}; +use std::ops::{Mul, MulAssign}; + +const FFT_THRESH: usize = 64; + +/// Returns all the $N$ Lagrange coefficients for the interpolating set $T = \{\omega^0, \omega^1, \ldots, \omega^{N-1}\}$, +/// where $\omega$ is an $N$th root of unity and $N$ is the size of `dom`. +/// +/// Much faster than calling `lagrange_coefficients` on this set, since each Lagrange coefficient +/// has a nice closed-form formula. +/// +/// Specifically, if $f(X) = \sum_{i = 0}^{N-1} \ell_i(X) f(\omega^i)$, then expanding $\ell_i(X)$ +/// gives $\ell_i(X) = \frac{(1-X^N) \omega^i}{N (\omega^i - X)}$. +/// +/// For $X = \alpha$ we get $\ell_i(\alpha) = \frac{(1-\alpha^N) N^{-1} \omega^i}{(\omega^i - \alpha)}$. +/// +/// (See https://ethresear.ch/t/kate-commitments-from-the-lagrange-basis-without-ffts/6950/2) +#[allow(non_snake_case)] +pub fn all_n_lagrange_coefficients(dom: &BatchEvaluationDomain, alpha: &Scalar) -> Vec { + let alpha_to_N = alpha.pow_vartime([dom.N() as u64]); // \alpha^N + let N_inverse = dom.get_subdomain(dom.N()).N_inverse; // N^{-1} + let one_minus_alpha_to_N = Scalar::ONE - alpha_to_N; // 1 - \alpha^N + + let lhs_numerator = N_inverse * one_minus_alpha_to_N; // (1 - \alpha^N) / N + let omegas = dom.get_all_roots_of_unity(); // \omega^i, for all i + let mut denominators = omegas.clone(); // clone + for i in 0..dom.N() { + denominators[i] -= alpha // \omega^i - \alpha + } + + denominators.batch_invert(); // (\omega^i - \alpha)^{-1} + + debug_assert_eq!(denominators.len(), dom.N()); + + let mut coeffs = Vec::with_capacity(dom.N()); + + for i in 0..dom.N() { + // i.e., (1 - \alpha^N * \omega^i) / (N (\omega^i - \alpha)) + coeffs.push(lhs_numerator * omegas[i] * denominators[i]) + } + + coeffs +} + +/// Let $b$ a bit indicating the `include_zero` truth value. +/// +/// If `include_zero` is false, then: +/// +/// $$S = {\omega^0, ..., \omega^{n-1}}$$ +/// +/// else +/// +/// $$S = {\omega^0, ..., \omega^{n-1}, 0}$$ +/// +/// Returns, for all $e \in S$ the multiplicative inverses of +/// +/// $$A'(e) = \prod_{e' \ne e, e' \in S} (e - e')$$, +/// +/// e.g., when `include_zero` is false: +/// +/// $$A'(\omega^i) = \prod_{j \ne i, j \in [0, n)} (\omega^i - \omega^j)$$ +/// +/// For the `include_zero` is false case, as per Appendix A in [TAB+20e], there is a closed form +/// formula for computing all evaluations: $A'(\omega^i) = n\omega^{-i}$. +/// +/// But we cannot always assume `include_zero` is false, and even if we could, we cannot always assume +/// an $n$th primitive root of unity in this algorithm (i.e., $n$ might not be a power of 2), so we +/// instead do a multipoint evaluation of $A'(X)$ above. +/// +/// [TAB+20e] Aggregatable Subvector Commitments for Stateless Cryptocurrencies; by Alin Tomescu and +/// Ittai Abraham and Vitalik Buterin and Justin Drake and Dankrad Feist and Dmitry Khovratovich; +/// 2020; https://eprint.iacr.org/2020/527 +#[allow(non_snake_case)] +pub fn all_lagrange_denominators( + batch_dom: &BatchEvaluationDomain, + n: usize, + include_zero: bool, +) -> Vec { + // println!( + // "all_lagr_denominators> N: {}, n: {n}, include_zero: {include_zero}", + // batch_dom.N() + // ); + // A(X) = \prod_{i \in [0, n-1]} (X - \omega^i) + let mut A = accumulator_poly_helper(batch_dom, (0..n).collect::>().as_slice()); + + // A'(X) = \sum_{i \in [0, n-1]} \prod_{j \ne i, j \in [0, n-1]} (X - \omega^j) + poly_differentiate(&mut A); + let A_prime = A; + // println!("all_lagr_denominators> |A_prime|: {}", A_prime.len()); + + // A'(\omega^i) = \prod_{j\ne i, j \in [n] } (\omega^i - \omega^j) + let mut denoms = fft(&A_prime, &batch_dom.get_subdomain(n)); + denoms.truncate(n); + + // If `include_zero`, need to: + if include_zero { + // 1. Augment A'(\omega_i) = A'(\omega_i) * \omega^i, for all i\ in [0, n) + for i in 0..n { + denoms[i] *= batch_dom.get_root_of_unity(i); + } + + // 2. Compute A'(0) = \prod_{j \in [0, n)} (0 - \omega^j) + denoms.push((0..n).map(|i| -batch_dom.get_root_of_unity(i)).product()); + } + + denoms.batch_invert(); + + denoms +} + +/// Returns the $|T|$ Lagrange coefficients +/// $\ell_i(\alpha) = \prod_{j \in T, j \ne i} \frac{\alpha - \omega^j}{\omega^i - \omega_j}$ +/// using the $O(|T| \log^2{|T|})$ algorithm from [TCZ+20], where $\omega$ is an $N$th primitive +/// root of unity (see below for $N$). +/// +/// Assumes that the batch evaluation domain in `dom` has all the $N$th roots of unity where $N = 2^k$. +/// +/// $T$ contains player identifiers, which are numbers from 0 to $N - 1$ (inclusive). +/// The player with identifier $i$ is associated with $\omega^i$. +/// +/// [TCZ+20]: **Towards Scalable Threshold Cryptosystems**, by Alin Tomescu and Robert Chen and +/// Yiming Zheng and Ittai Abraham and Benny Pinkas and Guy Golan Gueta and Srinivas Devadas, +/// *in IEEE S\&P'20*, 2020 +#[allow(non_snake_case)] +pub fn lagrange_coefficients( + dom: &BatchEvaluationDomain, + T: &[usize], + alpha: &Scalar, +) -> Vec { + let N = dom.N(); + let t = T.len(); + assert_gt!(N, 0); + + // Technically, the accumulator poly has degree t, so we need to evaluate it on t+1 points, which + // will be a problem when t = N, because the evaluation domain will be of size N, not N+1. However, + // we handle this in `accumulator_poly_helper` + debug_assert_le!(t, N); + + // The set of $\omega_i$'s for all $i\in [0, N)$. + let omegas = dom.get_all_roots_of_unity(); + //println!("N = {N}, |T| = t = {t}, T = {:?}, omegas = {:?}", T, omegas); + + // Let $Z(X) = \prod_{i \in T} (X - \omega^i)$ + let mut Z = accumulator_poly_helper(dom, T); + + //println!("Z(0): {}", &Z[0]); + // Let $Z_i(X) = Z(X) / (X - \omega^i)$, for all $i \in T$. + // The variable below stores $Z_i(\alpha) = Z(\alpha) / (\alpha - \omega^i)$ for all $i\in T$. + let Z_i_at_alpha = if alpha.is_zero_vartime() { + compute_numerators_at_zero(omegas, T, &Z[0]) + } else { + compute_numerators(&Z, omegas, T, alpha) + }; + + // Compute Z'(X), in place, overwriting Z(X) + poly_differentiate(&mut Z); + + // Compute $Z'(\omega^i)$ for all $i\in [0, N)$, in place, overwriting $Z'(X)$. + // (We only need $t$ of them, but computing all of them via an FFT is faster than computing them + // via a multipoint evaluation.) + // + // NOTE: The FFT implementation could be parallelized, but only 17.7% of the time is spent here. + fft_assign(&mut Z, &dom.get_subdomain(N)); + + // Use batch inversion when computing the denominators 1 / Z'(\omega^i) (saves 3 ms) + let mut denominators = Vec::with_capacity(T.len()); + for i in 0..T.len() { + debug_assert_ne!(Z[T[i]], Scalar::ZERO); + denominators.push(Z[T[i]]); + } + denominators.batch_invert(); + + for i in 0..T.len() { + Z[i] = Z_i_at_alpha[i].mul(denominators[i]); + } + + Z.truncate(t); + + Z +} + +/// Computes $Z(X) = \prod_{i \in T} (X - \omega^i)$. +#[allow(non_snake_case)] +fn accumulator_poly_helper(dom: &BatchEvaluationDomain, T: &[usize]) -> Vec { + let omegas = dom.get_all_roots_of_unity(); + + // Build the subset of $\omega_i$'s for all $i\in T$. + let mut set = Vec::with_capacity(T.len()); + for &s in T { + set.push(omegas[s]); + } + + // TODO(Performance): This is the performance bottleneck: 75.58% of the time is spent here. + // + // Let $Z(X) = \prod_{i \in T} (X - \omega^i)$ + // + // We handle a nasty edge case here: when doing N out of N interpolation, with N = 2^k, the batch + // evaluation domain will have N roots of unity, but the degree of the accumulator poly will be + // N+1. This will trigger an error inside `accumulator_poly` when doing the last FFT-based + // multiplication, which would require an FFT evaluation domain of size 2N which is not available. + // + // To fix this, we handle this case separately by splitting the accumulator poly into an `lhs` + // of degree `N` which can be safely interpolated with `accumulator_poly` and an `rhs` of degree + // 1. We then multiply the two together. We do not care about any performance implications of this + // since we will never use N-out-of-N interpolation. + // + // We do this to avoid complicating our Lagrange coefficients API and our BatchEvaluationDomain + // API (e.g., forbid N out of N Lagrange reconstruction by returning a `Result::Err`). + let Z = if set.len() < dom.N() { + accumulator_poly(&set, dom, FFT_THRESH) + } else { + // We handle |set| = 1 manually, since the `else` branch would yield an empty `lhs` vector + // (i.e., a polynomial with zero coefficients) because `set` is empty after `pop()`'ing from + // it. This makes `poly_mul_slow` bork, since it does not have clear semantics for this case. + // TODO: Define polynomial multiplication semantics more carefully to avoid such issues. + if set.len() == 1 { + accumulator_poly(&set, dom, FFT_THRESH) + } else { + let last = set.pop().unwrap(); + + let lhs = accumulator_poly(&set, dom, FFT_THRESH); + let rhs = accumulator_poly(&[last], dom, FFT_THRESH); + + poly_mul_slow(&lhs, &rhs) + } + }; + + Z +} + +/// Let $Z_i(X) = Z(X) / (X - \omega^i)$. Returns a vector of $Z_i(0)$'s, for all $i\in T$. +/// Here, `Z_0` is $Z(0)$. +#[allow(non_snake_case)] +fn compute_numerators_at_zero(omegas: &Vec, T: &[usize], Z_0: &Scalar) -> Vec { + let N = omegas.len(); + + let mut numerators = Vec::with_capacity(T.len()); + + for &i in T { + /* + * Recall that: + * + * When N is even and N > 1: + * a) Inverses can be computed fast as: (\omega^k)^{-1} = \omega^{-k} = \omega^N \omega^{-k} = \omega^{N-k} + * b) Negations can be computed fast as: -\omega^k = \omega^{k + N/2} + * + * So, (0 - \omega^i)^{-1} = (\omega^{i + N/2})^{-1} = \omega^{N - (i + N/2)} = \omega^{N/2 - i} + * If N/2 < i, then you wrap around to N + N/2 - i. + * + * When N = 1 (and thus T = { 0 }), the formula above does not work: it just sets `idx` to + * N / 2 - i = 1/2 - 0 = 0, which leads to the function using \omega^0 itself instead of + * -\omega^0. + */ + if N > 1 { + let idx = if N / 2 < i { N + N / 2 - i } else { N / 2 - i }; + + //println!("Z_{i}(0) = {}", Z_0 * omegas[idx]); + numerators.push(Z_0 * omegas[idx]); + } else { + debug_assert_eq!(T.len(), 1); + debug_assert_eq!(i, 0); + numerators.push(Scalar::ONE); + } + } + + debug_assert_eq!(numerators.len(), T.len()); + + numerators +} + +/// Let $Z_i(X) = Z(X) / (X - \omega^i)$. Returns a vector of $Z_i(\alpha)$'s, for all $i\in T$. +#[allow(non_snake_case)] +fn compute_numerators( + Z: &Vec, + omegas: &Vec, + ids: &[usize], + alpha: &Scalar, +) -> Vec { + let mut numerators = Vec::with_capacity(ids.len()); + + // Z(\alpha) + let Z_of_alpha = poly_eval(Z, alpha); + + for &i in ids { + // \alpha - \omega^i + numerators.push(alpha - omegas[i]); + } + + // (\alpha - \omega^i)^{-1} + numerators.batch_invert(); + + for i in 0..numerators.len() { + // Z(\alpha) / (\alpha - \omega^i)^{-1} + numerators[i].mul_assign(Z_of_alpha); + } + + numerators +} + +#[cfg(test)] +mod test { + use crate::{ + algebra::{ + evaluation_domain::BatchEvaluationDomain, + fft::fft_assign, + lagrange::{all_n_lagrange_coefficients, lagrange_coefficients, FFT_THRESH}, + polynomials::poly_eval, + }, + utils::random::{random_scalar, random_scalars}, + }; + use blstrs::Scalar; + use ff::Field; + use rand::{seq::IteratorRandom, thread_rng}; + use std::ops::Mul; + + #[test] + fn test_lagrange() { + let mut rng = thread_rng(); + + for n in 1..=FFT_THRESH * 2 { + for t in 1..=n { + // println!("t = {t}, n = {n}"); + let deg = t - 1; // the degree of the polynomial + + // pick a random $f(X)$ + let f = random_scalars(deg + 1, &mut rng); + + // give shares to all the $n$ players: i.e., evals[i] = f(\omega^i) + let batch_dom = BatchEvaluationDomain::new(n); + let mut evals = f.clone(); + fft_assign(&mut evals, &batch_dom.get_subdomain(n)); + + // try to reconstruct $f(0)$ from a random subset of t shares + let mut players: Vec = (0..n) + .choose_multiple(&mut rng, t) + .into_iter() + .collect::>(); + + players.sort(); + + let lagr = lagrange_coefficients(&batch_dom, players.as_slice(), &Scalar::ZERO); + // println!("lagr: {:?}", lagr); + + let mut s = Scalar::ZERO; + for i in 0..t { + s += lagr[i].mul(evals[players[i]]); + } + + // println!("s : {s}"); + // println!("f[0]: {}", f[0]); + + assert_eq!(s, f[0]); + } + } + } + + #[test] + #[allow(non_snake_case)] + fn test_all_N_lagrange() { + let mut rng = thread_rng(); + + let mut Ns = vec![2]; + while *Ns.last().unwrap() < FFT_THRESH { + Ns.push(Ns.last().unwrap() * 2); + } + + for N in Ns { + // the degree of the polynomial is N - 1 + + // pick a random $f(X)$ + let f = random_scalars(N, &mut rng); + + // give shares to all the $n$ players: i.e., evals[i] = f(\omega^i) + let batch_dom = BatchEvaluationDomain::new(N); + let mut evals = f.clone(); + fft_assign(&mut evals, &batch_dom.get_subdomain(N)); + + // try to reconstruct $f(\alpha)$ from all $N$ shares + let alpha = random_scalar(&mut rng); + let lagr1 = all_n_lagrange_coefficients(&batch_dom, &alpha); + + let all = (0..N).collect::>(); + let lagr2 = lagrange_coefficients(&batch_dom, all.as_slice(), &alpha); + assert_eq!(lagr1, lagr2); + + let mut f_of_alpha = Scalar::ZERO; + for i in 0..N { + f_of_alpha += lagr1[i].mul(evals[i]); + } + + let f_of_alpha_eval = poly_eval(&f, &alpha); + // println!("f(\\alpha) interpolated: {f_of_alpha}"); + // println!("f(\\alpha) evaluated : {f_of_alpha_eval}"); + + assert_eq!(f_of_alpha, f_of_alpha_eval); + } + } +} diff --git a/crates/aptos-dkg/src/algebra/mod.rs b/crates/aptos-dkg/src/algebra/mod.rs new file mode 100644 index 00000000000000..42f454a44fe779 --- /dev/null +++ b/crates/aptos-dkg/src/algebra/mod.rs @@ -0,0 +1,6 @@ +// Copyright © Aptos Foundation + +pub mod evaluation_domain; +pub mod fft; +pub mod lagrange; +pub mod polynomials; diff --git a/crates/aptos-dkg/src/algebra/polynomials.rs b/crates/aptos-dkg/src/algebra/polynomials.rs new file mode 100644 index 00000000000000..d89493a41e7720 --- /dev/null +++ b/crates/aptos-dkg/src/algebra/polynomials.rs @@ -0,0 +1,524 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::{ + evaluation_domain::{BatchEvaluationDomain, EvaluationDomain}, + fft, + }, + pvss::{input_secret::InputSecret, ThresholdConfig}, + utils::{is_power_of_two, random::random_scalars}, +}; +use blstrs::Scalar; +use ff::Field; +use more_asserts::debug_assert_le; +use std::ops::{AddAssign, Mul, MulAssign, SubAssign}; + +/// Returns $\[1, \tau, \tau^2, \tau^3, \ldots, \tau^{n-1}\]$. +pub fn get_powers_of_tau(tau: &Scalar, n: usize) -> Vec { + let mut taus = Vec::with_capacity(n); + + taus.push(Scalar::ONE); + + for _ in 0..n - 1 { + taus.push(taus.last().unwrap().mul(tau)); + } + debug_assert_eq!(taus.len(), n); + + taus +} + +/// Returns $\[\tau, \tau^2, \tau^3, \ldots, \tau^n\]$. +pub fn get_nonzero_powers_of_tau(tau: &Scalar, n: usize) -> Vec { + let mut taus = Vec::with_capacity(n); + + taus.push(*tau); + + for _ in 0..n - 1 { + taus.push(taus.last().unwrap().mul(tau)); + } + debug_assert_eq!(taus.len(), n); + + taus +} + +/// Returns the size of the evaluation domain needed to multiply these two polynomials via FFT. +pub fn get_evaluation_dom_size_for_multiplication(f: &Vec, g: &Vec) -> usize { + //println!("get_eval_dom: |f| = {}, |g| = {}", f.len(), g.len()); + let f_deg = f.len() - 1; + let g_deg = g.len() - 1; + + // The degree $d$ of $f \cdot g$ will be $\deg{f} + \deg{g}$. + let fg_deg = f_deg + g_deg; + // But we need $d+1$ evaluations to interpolate a degree $d$ polynomial. + let num_evals = fg_deg + 1; + + num_evals +} + +/// Returns an evaluation domain for an FFT of size the number of coefficients in the polynomial $f(X) \cdot g(X)$. +pub fn get_evaluation_dom_for_multiplication(f: &Vec, g: &Vec) -> EvaluationDomain { + let num_evals = get_evaluation_dom_size_for_multiplication(f, g); + EvaluationDomain::new(num_evals).unwrap() +} + +/// Returns $f(x)$ for a polynomial $f$ and a point $x$. +pub fn poly_eval(f: &Vec, x: &Scalar) -> Scalar { + assert!(!f.is_empty()); + + //let deg = f.len() - 1; + let mut eval = Scalar::ZERO; // f(x) + let mut x_i = Scalar::ONE; // x^i, i = {0, 1, ..., deg(f)} + for c_i in f { + eval += c_i * x_i; + + x_i *= x; + } + + eval +} + +/// Lets $f(X) = f(X) + g(X)$. Stores the result in `f` so assumes $\deg(f) \ge \deg(g)$. +pub fn poly_add_assign(f: &mut Vec, g: &[Scalar]) { + if g.len() > f.len() { + f.resize(g.len(), Scalar::ZERO); + } + + for i in 0..g.len() { + f[i].add_assign(g[i]); + } +} + +/// Lets $f(X) = f(X) - g(X)$. Stores the result in `f` so assumes $\deg(f) \ge \deg(g)$. +pub fn poly_sub_assign(f: &mut Vec, g: &[Scalar]) { + if g.len() > f.len() { + f.resize(g.len(), Scalar::ZERO); + } + for i in 0..g.len() { + f[i].sub_assign(g[i]); + } +} + +/// Computes the product of $f$ and $g$, letting $f = f \cdot g$ and $g = FFT(g)$. +/// Let $d = \deg(f) + \deg(g)$. Takes $O(d\log{d})$ time via three FFT. +/// +/// Note: If you already have an `EvaluationDomain` for $n = \deg(f) + \deg(g) + 1$, use the faster +/// `poly_mul_assign_fft_with_dom` which avoids recomputing the roots of unity and the other inverses. +pub fn poly_mul_assign_fft(f: &mut Vec, g: &mut Vec) { + debug_assert!(!f.is_empty()); + debug_assert!(!g.is_empty()); + + poly_mul_assign_fft_with_dom(f, g, &get_evaluation_dom_for_multiplication(f, g)) +} + +/// Like `poly_mul_assign` but returns the result, instead of modifying the arguments. +pub fn poly_mul_fft(f: &Vec, g: &Vec) -> Vec { + debug_assert!(!f.is_empty()); + debug_assert!(!g.is_empty()); + + let mut f_copy = f.clone(); + let mut g_copy = g.clone(); + + poly_mul_assign_fft_with_dom( + &mut f_copy, + &mut g_copy, + &get_evaluation_dom_for_multiplication(f, g), + ); + + f_copy +} + +/// If the caller already has an `EvaluationDomain` for $n = \deg(f) + \deg(g) + 1$, this function +/// will avoid some redundant field operations and be slightly faster than `poly_mul_assign_fft`. +pub fn poly_mul_assign_fft_with_dom( + f: &mut Vec, + g: &mut Vec, + dom: &EvaluationDomain, +) { + debug_assert!(!f.is_empty()); + debug_assert!(!g.is_empty()); + debug_assert_eq!((f.len() - 1) + (g.len() - 1) + 1, dom.n); + + fft::fft_assign(f, &dom); + fft::fft_assign(g, &dom); + for i in 0..dom.N { + f[i].mul_assign(g[i]); + } + + fft::ifft_assign(f, &dom); + f.truncate(dom.n); +} + +/// Like `poly_mul_assign_fft` but slower in time $\deg(f) \cdot \deg(g)$ and returns the product in +/// `out`, leaving `f` and `g` untouched. +/// TODO(Performance): Can we do this in-place over `f` or `g` without a separate `out`. +pub fn poly_mul_assign_slow(f: &Vec, g: &Vec, out: &mut Vec) { + assert!(!f.is_empty()); + assert!(!g.is_empty()); + + let f_len = f.len(); + let g_len = g.len(); + + // Set `out` to all zeros. + out.truncate(0); // Rust docs say "Note that this method has no effect on the allocated capacity of the vector." + out.resize(f_len + g_len - 1, Scalar::ZERO); + for (i1, a) in f.iter().enumerate() { + for (i2, b) in g.iter().enumerate() { + let mut prod = *a; + prod.mul_assign(b); + out[i1 + i2].add_assign(&prod); + } + } +} + +/// Like `poly_mul_fft` but slower: runs in time $\deg(f) \cdot \deg(g)$. +/// Returns the product, leaving `f` and `g` untouched. +/// +/// Performance notes: Beats FFT for $t \ge 32$. +pub fn poly_mul_slow(f: &Vec, g: &Vec) -> Vec { + let mut out = Vec::with_capacity(get_evaluation_dom_size_for_multiplication(f, g)); + // println!( + // "poly_mul_slow: |f| = {}, |g| = {}, |out| = {}", + // f.len(), + // g.len(), + // out.capacity() + // ); + poly_mul_assign_slow(f, g, &mut out); + out +} + +/// Like `poly_mul_assign_fft` but runs in time $O(n)^{1.58})$. +/// Assumes that $\deg{f} - 1 = \deg{g} - 1 = 2^k$ for some $k$. +/// Returns the product in `out`, leaving `f` and `g` untouched. +/// +/// Performance notes: Starts beating `poly_mul_slow` after $t \ge 512$. Never beats FFT, so useless +/// because FFT beats naive after $t > 64$. +pub fn poly_mul_assign_less_slow(f: &Vec, g: &Vec, out: &mut Vec) { + let mut result = poly_mul_less_slow(f.as_slice(), g.as_slice()); + + out.truncate(0); + out.append(&mut result); +} + +pub fn poly_mul_less_slow(f: &[Scalar], g: &[Scalar]) -> Vec { + let n = f.len(); + assert!(is_power_of_two(n)); + + // TODO(Performance): Let the base case be the naive n^2 multiplication for any f_len < threshold? + // handle base case + if n == 1 { + return vec![f[0].mul(g[0])]; + } + + let g_len = g.len(); + let half_n = n / 2; + assert_eq!(n, g_len); + + // split f into halves + let f_0 = &f[0..half_n]; + let f_1 = &f[half_n..n]; + debug_assert_eq!(f_0.len() + f_1.len(), n); + + // split g into halves + let g_0 = &g[0..half_n]; + let g_1 = &g[half_n..n]; + debug_assert_eq!(g_0.len() + g_1.len(), n); + + // let f_01 = f_0 + f_1 + let mut f_01 = Vec::with_capacity(half_n); + f_0.clone_into(&mut f_01); + for i in 0..half_n { + f_01[i].add_assign(f_1[i]); + } + + // let g_01 = g_0 + g_1 + let mut g_01 = Vec::with_capacity(half_n); + g_0.clone_into(&mut g_01); + for i in 0..half_n { + g_01[i].add_assign(g_1[i]); + } + + // $y = (f_0 + f_1) \cdot (g_0 + g_1)$ + let mut y = poly_mul_less_slow(&f_01, &g_01); + // $z = f_0 \cdot g_0$ + let mut z = poly_mul_less_slow(f_1, g_1); + // $u = f_1 \cdot g_0$ + let mut u = poly_mul_less_slow(f_0, g_0); + + // first, compute (y - u - z) + poly_sub_assign(&mut y, &u); + poly_sub_assign(&mut y, &z); + + // second, compute (y - u - z) * X^{n/2} + poly_xnmul_assign(&mut y, half_n); + + // third, compute z * X^n + poly_xnmul_assign(&mut z, n); + + // fourth, add everything up with $u(X)$ + poly_add_assign(&mut u, &y); + poly_add_assign(&mut u, &z); + + u.into() +} +/// Sets $f(X) = f(X) \cdot X^n$, by simply shifting the coefficients. +/// As always we assume $\deg{f}$ is `f.len() - 1`. +pub fn poly_xnmul_assign(f: &mut Vec, n: usize) { + if n == 0 { + return; + } + + let old_len = f.len(); + + // extend with zero coefficients for X^n, X^{n-1}, \dots, X + f.resize(old_len + n, Scalar::ZERO); + + // Shift coefficients by `n` positions + for i in (0..old_len).rev() { + f[i + n] = f[i] + } + + // Set the last n coefficients $f_{n-1}, \cdots, f_0$ to 0. + for i in 0..n { + f[i] = Scalar::ZERO; + } +} + +/// Like `poly_mul_by_xn_assign` but returns the result. +pub fn poly_xnmul(f: &Vec, n: usize) -> Vec { + if n == 0 { + return f.clone(); + } + + let len = n + f.len(); + + // let result = f + let mut result = Vec::with_capacity(len); + result.resize(f.len(), Scalar::ZERO); + result.copy_from_slice(f); + + poly_xnmul_assign(&mut result, n); + + result +} + +/// Calculates the derivative of $f(X)$. +pub fn poly_differentiate(f: &mut Vec) { + let f_deg = f.len() - 1; + + for i in 0..f_deg { + f[i] = f[i + 1].mul(Scalar::from((i + 1) as u64)); + } + + f.truncate(f_deg); +} + +/// Given a set $S$ of scalars, returns the *accumulator* polynomial $Z(X) = \prod_{a \in S} (X - a)$. +#[allow(non_snake_case)] +pub fn accumulator_poly_slow(S: &[Scalar]) -> Vec { + let set_size = S.len(); + // println!("len: {len}"); + + if set_size == 0 { + return vec![]; + } else if set_size == 1 { + // println!("Returning (X - {})", S[0]); + return vec![-S[0], Scalar::ONE]; + } + + let m = set_size / 2; + // println!("m: {m}"); + + let left = &S[0..m]; + // println!("left: {}", left.len()); + let right = &S[m..set_size]; + // println!("right: {}", right.len()); + + let mut left_poly = accumulator_poly_slow(left); + let mut right_poly = accumulator_poly_slow(right); + if left_poly.is_empty() { + return right_poly; + } + if right_poly.is_empty() { + return left_poly; + } + + poly_mul_assign_fft(&mut left_poly, &mut right_poly); + + left_poly +} + +/// Like `accumulator_poly_slow` but: +/// - Avoids recomputing too many different roots of unity (EvaluationDomain::new takes 3.5 microsecs +/// and `accumulator_poly_slow` makes around 2000 calls to it). Saves 10 milliseconds out of total of 70. +/// - Avoids FFTs when multiplying polynomials with $\deg{f} + \deg{g} - 1 \le fft_thresh$. +/// Saves 35 milliseconds. +#[allow(non_snake_case)] +pub fn accumulator_poly( + S: &[Scalar], + batch_dom: &BatchEvaluationDomain, + fft_thresh: usize, +) -> Vec { + let set_size = S.len(); + + if set_size == 0 { + return vec![]; + } else if set_size == 1 { + return vec![-S[0], Scalar::ONE]; + } else if set_size == 2 { + return vec![ + S[0] * S[1], // X^0 coeff is (-1)^0 (S[0] S[1]) = S[0] S[1] + -(S[0] + S[1]), // X^1 coeff is (-1)^1 (S[0] + S[1]) = -(S[0] + S[1]) + Scalar::ONE, // X^2 coeff is 1 + ]; + } else if set_size == 3 { + // https://math.stackexchange.com/questions/88917/relation-betwen-coefficients-and-roots-of-a-polynomial + let s1_add_s2 = S[1] + S[2]; + let s1_mul_s2 = S[1] * S[2]; + + return vec![ + -(S[0] * s1_mul_s2), // X^3 coeff is -(S[0] S[1] S[2]) + S[0] * s1_add_s2 + s1_mul_s2, // X^2 coeff is S[0] S[1] + S[0] S[2] + S[1] S[2] + -(S[0] + S[1] + S[2]), // X^1 coeff is -(S[0] + S[1] + S[2]) + Scalar::ONE, // X^0 coeff is 1 + ]; + } + + let m = set_size / 2; + + let left = &S[0..m]; + let right = &S[m..set_size]; + + let mut left_poly = accumulator_poly(left, batch_dom, fft_thresh); + let mut right_poly = accumulator_poly(right, batch_dom, fft_thresh); + if left_poly.is_empty() { + return right_poly; + } + if right_poly.is_empty() { + return left_poly; + } + + let dom_size = get_evaluation_dom_size_for_multiplication(&left_poly, &right_poly); + if dom_size < fft_thresh { + poly_mul_slow(&left_poly, &right_poly) + } else { + poly_mul_assign_fft_with_dom( + &mut left_poly, + &mut right_poly, + &batch_dom.get_subdomain(dom_size), + ); + left_poly + } +} + +/// More wisely schedules the sizes of the polynomials that are multiplied via FFT. Specifically, +/// always ensures we are multiplying degree (2^k - 1) by degree 2^k, to get a degree (2^{k+1} - 1) +/// polynomial. This ensures optimal usage of the 3 FFTs: the first two FFTs on the degree 2^k - 1 +/// and degree 2^k have 50% usage, while the last inverse FFT that outputs 2^{k+1} coefficients has +/// 100% usage. +/// +/// Expected this to be much faster than `accumulator_poly` but seems to beat by at most 1 millisecond +/// for the following parameters: +/// +/// 128 FFT thresh, 256 naive -thresh > 15.4 ms +/// 256 FFT thresh, 512 naive thresh -> 14.8 ms +/// 256 FFT thresh, 128 naive thresh -> 14.1 ms +#[allow(non_snake_case)] +pub fn accumulator_poly_scheduled( + S: &[Scalar], + batch_dom: &BatchEvaluationDomain, + naive_thresh: usize, + fft_thresh: usize, +) -> Vec { + let mut n = S.len() + 1; + + if S.len() < naive_thresh { + // println!("Directly returning accumulator for set size {}", S.len()); + return accumulator_poly(S, batch_dom, fft_thresh); + } + + let mut batch_size = 1; + while n != 1 { + n /= 2; + batch_size *= 2; + } + batch_size -= 1; // 2^k - 1 <= |S| + + debug_assert_le!(batch_size, S.len()); + assert!(is_power_of_two(batch_size + 1)); + + let left = + accumulator_poly_scheduled_inner(&S[0..batch_size], batch_dom, naive_thresh, fft_thresh); + if batch_size == S.len() { + left + } else { + let right = + accumulator_poly_scheduled(&S[batch_size..], batch_dom, naive_thresh, fft_thresh); + + poly_mul_fft(&left, &right) + } +} + +#[allow(non_snake_case)] +fn accumulator_poly_scheduled_inner( + S: &[Scalar], + batch_dom: &BatchEvaluationDomain, + naive_thresh: usize, + fft_thresh: usize, +) -> Vec { + let len = S.len(); + debug_assert!(is_power_of_two(len + 1)); + + if len < naive_thresh { + // println!("Directly returning accumulator for set size {len}"); + return accumulator_poly(S, batch_dom, fft_thresh); + } + + let batch_size = (len + 1) / 2 - 1; + debug_assert_eq!(batch_size * 2 + 1, len); + + let mut b1 = + accumulator_poly_scheduled_inner(&S[0..batch_size], batch_dom, naive_thresh, fft_thresh); + debug_assert_eq!(b1.len(), batch_size + 1); + let b2 = accumulator_poly_scheduled_inner( + &S[batch_size..2 * batch_size], + batch_dom, + naive_thresh, + fft_thresh, + ); + debug_assert_eq!(b2.len(), batch_size + 1); + let deg1 = accumulator_poly(&S[2 * batch_size..], batch_dom, fft_thresh); + debug_assert_eq!(deg1.len(), 2); + + // println!( + // "Multiplying deg-{} by deg-{}", + // b1.len() - 1, + // b2.len() - 1 + deg1.len() - 1 + // ); + + let mut b2 = poly_mul_slow(°1, &b2); + poly_mul_assign_fft_with_dom(&mut b1, &mut b2, &batch_dom.get_subdomain(len + 1)); + + b1 +} + +/// Deals a secret `s` in a `t`-out-of-`n` fashion (as per `sc`) returning (1) the degree `t-1` +/// polynomial encoding the secret and (2) its evaluations at all the `n` $N$th roots-of-unity where +/// $N$ is the smallest power of two $\ge n$. +/// +/// Any `t` evaluations are sufficient to reconstruct the secret `s`. +pub fn shamir_secret_share< + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +>( + sc: &ThresholdConfig, + s: &InputSecret, + rng: &mut R, +) -> (Vec, Vec) { + // A random, degree t-1 polynomial $f(X) = [a_0, \dots, a_{t-1}]$, with $a_0$ set to `s.a` + let mut f = random_scalars(sc.t, rng); + f[0] = *s.get_secret_a(); + + // Evaluate $f$ at all the $N$th roots of unity. + let mut f_evals = fft::fft(&f, sc.get_evaluation_domain()); + f_evals.truncate(sc.n); + (f, f_evals) +} diff --git a/crates/aptos-dkg/src/constants.rs b/crates/aptos-dkg/src/constants.rs new file mode 100644 index 00000000000000..5811b1c9193204 --- /dev/null +++ b/crates/aptos-dkg/src/constants.rs @@ -0,0 +1,20 @@ +// Copyright © Aptos Foundation + +use num_bigint::BigUint; +use once_cell::sync::Lazy; + +// +// Sizes +// + +/// The size in bytes of a compressed G1 point (efficiently deserializable into projective coordinates) +pub const G1_PROJ_NUM_BYTES: usize = 48; + +/// The size in bytes of a compressed G2 point (efficiently deserializable into projective coordinates) +pub const G2_PROJ_NUM_BYTES: usize = 96; + +/// The size in bytes of a scalar. +pub const SCALAR_NUM_BYTES: usize = 32; + +pub(crate) const SCALAR_FIELD_ORDER: Lazy = + Lazy::new(crate::utils::biguint::get_scalar_field_order_as_biguint); diff --git a/crates/aptos-dkg/src/lib.rs b/crates/aptos-dkg/src/lib.rs new file mode 100644 index 00000000000000..65895c47dd4085 --- /dev/null +++ b/crates/aptos-dkg/src/lib.rs @@ -0,0 +1,27 @@ +// Copyright © Aptos Foundation + +#![allow(clippy::redundant_static_lifetimes)] +#![allow(clippy::needless_return)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::needless_borrow)] +#![allow(clippy::needless_range_loop)] +#![allow(clippy::let_and_return)] +#![allow(clippy::ptr_arg)] +#![allow(clippy::useless_conversion)] +#![allow(clippy::declare_interior_mutable_const)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::map_identity)] +#![allow(clippy::let_unit_value)] +#![allow(clippy::vec_init_then_push)] +#![allow(clippy::to_string_in_format_args)] +#![allow(clippy::borrow_interior_mutable_const)] + +use crate::constants::SCALAR_FIELD_ORDER; +pub use constants::{G1_PROJ_NUM_BYTES, G2_PROJ_NUM_BYTES, SCALAR_NUM_BYTES}; +pub use utils::random::DST_RAND_CORE_HELL; + +pub mod algebra; +pub mod constants; +pub mod pvss; +pub mod utils; +pub mod weighted_vuf; diff --git a/crates/aptos-dkg/src/pvss/contribution.rs b/crates/aptos-dkg/src/pvss/contribution.rs new file mode 100644 index 00000000000000..66db50110c7e7e --- /dev/null +++ b/crates/aptos-dkg/src/pvss/contribution.rs @@ -0,0 +1,103 @@ +// Copyright © Aptos Foundation + +use crate::{ + pvss::{schnorr, Player}, + utils::HasMultiExp, +}; +use anyhow::bail; +use aptos_crypto::bls12381; +use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; +use blstrs::Scalar; +use group::Group; +use serde::{Deserialize, Serialize}; +use std::{fmt::Display, ops::Mul}; + +/// A PVSS contribution, which is signed as part of the PVSS transcript +/// TODO(TechDebt): CryptoHasher does not work with lifetimes. So we cannot make this struct store +/// references, which causes unnecessary cloning in the code. +#[derive(Serialize, Deserialize, CryptoHasher, BCSCryptoHash)] +pub struct Contribution { + pub comm: Gr, + pub player: Player, + pub aux: A, +} + +pub type SoK = (Player, Gr, bls12381::Signature, schnorr::PoK); + +pub fn batch_verify_soks( + soks: &[SoK], + pk_base: &Gr, + pk: &Gr, + spks: &Vec, + aux: &Vec, + tau: &Scalar, +) -> anyhow::Result<()> +where + Gr: Serialize + HasMultiExp + Display + Copy + Group + for<'a> Mul<&'a Scalar>, + A: Serialize + Clone, +{ + if soks.len() != spks.len() { + bail!( + "Expected {} signing PKs, but got {}", + soks.len(), + spks.len() + ); + } + + if soks.len() != aux.len() { + bail!( + "Expected {} auxiliary infos, but got {}", + soks.len(), + aux.len() + ); + } + + // First, the PoKs + let mut c = Gr::identity(); + for (_, c_i, _, _) in soks { + c.add_assign(c_i) + } + + if c.ne(pk) { + bail!( + "The PoK does not correspond to the dealt secret. Expected {} but got {}", + pk, + c + ); + } + + let poks = soks + .iter() + .map(|(_, c, _, pok)| (*c, *pok)) + .collect::)>>(); + + // TODO(Performance): 128-bit exponents instead of powers of tau + schnorr::pok_batch_verify::(&poks, pk_base, &tau)?; + + // Second, the signatures + let msgs = soks + .iter() + .zip(aux) + .map(|((player, comm, _, _), aux)| Contribution:: { + comm: *comm, + player: *player, + aux: aux.clone(), + }) + .collect::>>(); + let msgs_refs = msgs + .iter() + .map(|c| c) + .collect::>>(); + let pks = spks + .iter() + .map(|pk| pk) + .collect::>(); + let sig = bls12381::Signature::aggregate( + soks.iter() + .map(|(_, _, sig, _)| sig.clone()) + .collect::>(), + )?; + + sig.verify_aggregate(&msgs_refs[..], &pks[..])?; + Ok(()) +} diff --git a/crates/aptos-dkg/src/pvss/das/enc.rs b/crates/aptos-dkg/src/pvss/das/enc.rs new file mode 100644 index 00000000000000..59849b8cd8fb8f --- /dev/null +++ b/crates/aptos-dkg/src/pvss/das/enc.rs @@ -0,0 +1,17 @@ +// Copyright © Aptos Foundation + +use crate::pvss::{ + encryption_dlog::g1::{DecryptPrivKey, EncryptPubKey}, + encryption_elgamal::g1::PublicParameters, + traits, +}; +use std::ops::Mul; + +impl traits::Convert for DecryptPrivKey { + /// Given a decryption key $dk$, computes its associated encryption key $g_1^{dk}$ + fn to(&self, pp: &PublicParameters) -> EncryptPubKey { + EncryptPubKey { + ek: pp.pubkey_base().mul(self.dk), + } + } +} diff --git a/crates/aptos-dkg/src/pvss/das/input_secret.rs b/crates/aptos-dkg/src/pvss/das/input_secret.rs new file mode 100644 index 00000000000000..e84e0481617fcf --- /dev/null +++ b/crates/aptos-dkg/src/pvss/das/input_secret.rs @@ -0,0 +1,32 @@ +// Copyright © Aptos Foundation + +use crate::pvss::{ + das, dealt_pub_key::g2::DealtPubKey, dealt_secret_key::g1::DealtSecretKey, + input_secret::InputSecret, traits, traits::HasEncryptionPublicParams, +}; +use std::ops::Mul; + +// +// InputSecret implementation +// + +impl traits::Convert for InputSecret { + fn to(&self, pp: &das::PublicParameters) -> DealtSecretKey { + DealtSecretKey::new( + pp.get_encryption_public_params() + .message_base() + .mul(self.get_secret_a()), + ) + } +} + +impl traits::Convert for InputSecret { + /// Computes the public key associated with the given input secret. + /// NOTE: In the SCRAPE PVSS, a `DealtPublicKey` cannot be computed from a `DealtSecretKey` directly. + fn to(&self, pp: &das::PublicParameters) -> DealtPubKey { + DealtPubKey::new(pp.get_commitment_base().mul(self.get_secret_a())) + } +} + +#[cfg(test)] +mod test {} diff --git a/crates/aptos-dkg/src/pvss/das/mod.rs b/crates/aptos-dkg/src/pvss/das/mod.rs new file mode 100644 index 00000000000000..eaa166bc9ed4ce --- /dev/null +++ b/crates/aptos-dkg/src/pvss/das/mod.rs @@ -0,0 +1,13 @@ +// Copyright © Aptos Foundation + +mod enc; +mod input_secret; +pub mod public_parameters; +pub mod unweighted_protocol; +mod weighted_protocol; + +use crate::pvss::das; +pub use das::{ + public_parameters::PublicParameters, unweighted_protocol::Transcript, + weighted_protocol::Transcript as WeightedTranscript, +}; diff --git a/crates/aptos-dkg/src/pvss/das/public_parameters.rs b/crates/aptos-dkg/src/pvss/das/public_parameters.rs new file mode 100644 index 00000000000000..309c24858d86d5 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/das/public_parameters.rs @@ -0,0 +1,135 @@ +// Copyright © Aptos Foundation + +//! This submodule implements the *public parameters* for this PVSS scheme. + +use crate::{ + constants::G2_PROJ_NUM_BYTES, + pvss::{encryption_elgamal, traits}, +}; +use aptos_crypto::{CryptoMaterialError, ValidCryptoMaterial, ValidCryptoMaterialStringExt}; +use aptos_crypto_derive::{DeserializeKey, SerializeKey}; +use blstrs::{G1Projective, G2Projective}; +use pairing::group::Group; + +/// The size, in number of bytes, of a serialized `PublicParameters` struct. +const NUM_BYTES: usize = encryption_elgamal::g1::PUBLIC_PARAMS_NUM_BYTES + G2_PROJ_NUM_BYTES; + +/// "Nothing up my sleeve" domain-separator tag (DST) for the hash-to-curve operation used +/// to pick our PVSS public parameters (group elements) as `hash_to_curve(seed, dst, group_element_name)`. +pub const DST_PVSS_PUBLIC_PARAMS: &[u8; 32] = b"APTOS_DISTRIBUTED_RANDOMNESS_DST"; +/// "Nothing up my sleeve" seed to deterministically-derive the public parameters. +pub const SEED_PVSS_PUBLIC_PARAMS: &[u8; 33] = b"APTOS_DISTRIBUTED_RANDOMNESS_SEED"; + +/// The cryptographic *public parameters* needed to run the PVSS protocol. +#[derive(DeserializeKey, Clone, SerializeKey, Debug, PartialEq, Eq)] +pub struct PublicParameters { + /// Encryption public parameters + enc: encryption_elgamal::g1::PublicParameters, + /// Base for the commitments to the polynomial evaluations (and for the dealt public key [shares]) + g_2: G2Projective, +} + +impl PublicParameters { + /// Verifiably creates public parameters from a public sequence of bytes `seed`. + pub fn new_from_seed(seed: &[u8]) -> Self { + let g = G1Projective::hash_to_curve(seed, DST_PVSS_PUBLIC_PARAMS.as_slice(), b"g"); + let h = G1Projective::hash_to_curve(seed, DST_PVSS_PUBLIC_PARAMS.as_slice(), b"h"); + debug_assert_ne!(g, h); + PublicParameters { + enc: encryption_elgamal::g1::PublicParameters::new(g, h), + g_2: G2Projective::hash_to_curve(seed, DST_PVSS_PUBLIC_PARAMS.as_slice(), b"g_2"), + } + } + + /// Verifiably creates public parameters from `SEED_PVSS_PUBLIC_PARAMS` but sets + /// the encryption pubkey (and randomness) base $g$ to be the same as the generator in our + /// BLS12-381 consensus signatures. + pub fn default_with_bls_base() -> Self { + let g = G1Projective::generator(); + let h = G1Projective::hash_to_curve( + SEED_PVSS_PUBLIC_PARAMS, + DST_PVSS_PUBLIC_PARAMS.as_slice(), + b"h_with_bls_base", + ); + debug_assert_ne!(g, h); + PublicParameters { + enc: encryption_elgamal::g1::PublicParameters::new( + // Our BLS signatures over BLS12-381 curves use this generator as the base of their + // PKs. We plan on (safely) reusing those BLS PKs as encryption PKs. + g, h, + ), + g_2: G2Projective::hash_to_curve( + SEED_PVSS_PUBLIC_PARAMS, + DST_PVSS_PUBLIC_PARAMS.as_slice(), + b"g_2_with_bls_base", + ), + } + } + + /// Returns the base $g_2$ for the commitment to the polynomial. + pub fn get_commitment_base(&self) -> &G2Projective { + &self.g_2 + } + + /// Serializes the public parameters. + pub fn to_bytes(&self) -> Vec { + let mut bytes = self.enc.to_bytes().to_vec(); + + bytes.append(&mut self.g_2.to_compressed().to_vec()); + + bytes + } +} + +impl ValidCryptoMaterial for PublicParameters { + fn to_bytes(&self) -> Vec { + self.to_bytes() + } +} + +impl Default for PublicParameters { + /// Verifiably creates Aptos-specific public parameters. + fn default() -> Self { + Self::default_with_bls_base() + } +} + +impl traits::HasEncryptionPublicParams for PublicParameters { + type EncryptionPublicParameters = encryption_elgamal::g1::PublicParameters; + + fn get_encryption_public_params(&self) -> &Self::EncryptionPublicParameters { + &self.enc + } +} + +impl TryFrom<&[u8]> for PublicParameters { + type Error = CryptoMaterialError; + + /// Deserialize a `PublicParameters` struct. + fn try_from(bytes: &[u8]) -> std::result::Result { + let slice: &[u8; NUM_BYTES] = match <&[u8; NUM_BYTES]>::try_from(bytes) { + Ok(slice) => slice, + Err(_) => return Err(CryptoMaterialError::WrongLengthError), + }; + + let pp_bytes: [u8; encryption_elgamal::g1::PUBLIC_PARAMS_NUM_BYTES] = slice + [0..encryption_elgamal::g1::PUBLIC_PARAMS_NUM_BYTES] + .try_into() + .unwrap(); + let g2_bytes = slice[encryption_elgamal::g1::PUBLIC_PARAMS_NUM_BYTES..NUM_BYTES] + .try_into() + .unwrap(); + + let enc_pp = encryption_elgamal::g1::PublicParameters::try_from(&pp_bytes[..])?; + let g2_opt = G2Projective::from_compressed(g2_bytes); + + if g2_opt.is_some().unwrap_u8() == 1u8 { + Ok(PublicParameters { + enc: enc_pp, + g_2: g2_opt.unwrap(), + }) + } else { + Err(CryptoMaterialError::DeserializationError) + } + } +} diff --git a/crates/aptos-dkg/src/pvss/das/unweighted_protocol.rs b/crates/aptos-dkg/src/pvss/das/unweighted_protocol.rs new file mode 100644 index 00000000000000..e83435a41e452e --- /dev/null +++ b/crates/aptos-dkg/src/pvss/das/unweighted_protocol.rs @@ -0,0 +1,335 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::polynomials::{get_nonzero_powers_of_tau, shamir_secret_share}, + pvss, + pvss::{ + contribution::{batch_verify_soks, Contribution, SoK}, + das, encryption_dlog, fiat_shamir, schnorr, traits, + traits::{transcript::MalleableTranscript, HasEncryptionPublicParams, SecretSharingConfig}, + LowDegreeTest, Player, ThresholdConfig, + }, + utils::{ + g1_multi_exp, g2_multi_exp, multi_pairing, + random::{ + insecure_random_g1_points, insecure_random_g2_points, random_g1_point, random_g2_point, + random_scalar, + }, + }, +}; +use anyhow::bail; +use aptos_crypto::{bls12381, CryptoMaterialError, Genesis, SigningKey, ValidCryptoMaterial}; +use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; +use blstrs::{G1Projective, G2Projective, Gt}; +use group::Group; +use serde::{Deserialize, Serialize}; +use std::ops::{Add, Mul, Neg, Sub}; + +pub const DAS_SK_IN_G1: &'static str = "das_sk_in_g1"; + +/// Domain-separator tag (DST) for the Fiat-Shamir hashing used to derive randomness from the transcript. +const DAS_PVSS_FIAT_SHAMIR_DST: &[u8; 30] = b"APTOS_DAS_PVSS_FIAT_SHAMIR_DST"; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, BCSCryptoHash, CryptoHasher)] +#[allow(non_snake_case)] +pub struct Transcript { + /// Proofs-of-knowledge (PoKs) for the dealt secret committed in $c = g_2^{p(0)}$. + /// Since the transcript could have been aggregated from other transcripts with their own + /// committed secrets in $c_i = g_2^{p_i(0)}$, this is a vector of PoKs for all these $c_i$'s + /// such that $\prod_i c_i = c$. + /// + /// Also contains BLS signatures from each player $i$ on that player's contribution $c_i$, the + /// player ID $i$ and auxiliary information `aux[i]` provided during dealing. + soks: Vec>, + /// ElGamal encryption randomness $g_2^r \in G_2$ + hat_w: G2Projective, + /// First $n$ elements are commitments to the evaluations of $p(X)$: $g_2^{p(\omega^i)}$, + /// where $i \in [n]$. Last element is $g_2^{p(0)}$ (i.e., the dealt public key). + V: Vec, + /// ElGamal encryptions of the shares $h_1^{p(\omega^i)} ek^r$. + C: Vec, + /// Ciphertext randomness commitment $g_1^r$. + C_0: G1Projective, +} + +impl ValidCryptoMaterial for Transcript { + fn to_bytes(&self) -> Vec { + bcs::to_bytes(&self).expect("unexpected error during PVSS transcript serialization") + } +} + +impl TryFrom<&[u8]> for Transcript { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> Result { + // NOTE: The `serde` implementation in `blstrs` already performs the necessary point validation + // by ultimately calling `GroupEncoding::from_bytes`. + bcs::from_bytes::(bytes).map_err(|_| CryptoMaterialError::DeserializationError) + } +} + +impl traits::Transcript for Transcript { + type DealtPubKey = pvss::dealt_pub_key::g2::DealtPubKey; + type DealtPubKeyShare = pvss::dealt_pub_key_share::g2::DealtPubKeyShare; + type DealtSecretKey = pvss::dealt_secret_key::g1::DealtSecretKey; + type DealtSecretKeyShare = pvss::dealt_secret_key_share::g1::DealtSecretKeyShare; + type DecryptPrivKey = encryption_dlog::g1::DecryptPrivKey; + type EncryptPubKey = encryption_dlog::g1::EncryptPubKey; + type InputSecret = pvss::input_secret::InputSecret; + type PublicParameters = das::PublicParameters; + type SecretSharingConfig = ThresholdConfig; + type SigningPubKey = bls12381::PublicKey; + type SigningSecretKey = bls12381::PrivateKey; + + fn scheme_name() -> String { + DAS_SK_IN_G1.to_string() + } + + #[allow(non_snake_case)] + fn deal( + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + ssk: &Self::SigningSecretKey, + eks: &Vec, + s: &Self::InputSecret, + aux: &A, + dealer: &Player, + mut rng: &mut R, + ) -> Self { + assert_eq!(eks.len(), sc.n); + + let (f, f_evals) = shamir_secret_share(sc, s, rng); + + // Pick ElGamal randomness + let r = random_scalar(&mut rng); + let g_1 = pp.get_encryption_public_params().pubkey_base(); + let g_2 = pp.get_commitment_base(); + let h_1 = *pp.get_encryption_public_params().message_base(); + + let V = (0..sc.n) + .map(|i| g_2.mul(f_evals[i])) + .chain([g_2.mul(f[0])]) + .collect::>(); + + let C = (0..sc.n) + .map(|i| { + g1_multi_exp( + [h_1, Into::::into(&eks[i])].as_slice(), + [f_evals[i], r].as_slice(), + ) + }) + .collect::>(); + + // Compute PoK of input secret committed in V[n] + let pok = schnorr::pok_prove(&f[0], g_2, &V[sc.n], rng); + + debug_assert_eq!(V.len(), sc.n + 1); + debug_assert_eq!(C.len(), sc.n); + + // Sign the secret commitment, player ID and `aux` + let sig = Transcript::sign_contribution(ssk, dealer, aux, &V[sc.n]); + + Transcript { + soks: vec![(*dealer, V[sc.n], sig, pok)], + hat_w: g_2.mul(r), + V, + C, + C_0: g_1.mul(r), + } + } + + fn verify( + &self, + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + spks: &Vec, + eks: &Vec, + aux: &Vec, + ) -> anyhow::Result<()> { + if eks.len() != sc.n { + bail!("Expected {} encryption keys, but got {}", sc.n, eks.len()); + } + + if self.C.len() != sc.n { + bail!("Expected {} ciphertexts, but got {}", sc.n, self.C.len()); + } + + if self.V.len() != sc.n + 1 { + bail!( + "Expected {} (polynomial) commitment elements, but got {}", + sc.n + 1, + self.V.len() + ); + } + + // Derive challenges deterministically via Fiat-Shamir; easier to debug for distributed systems + let (f, extra) = + fiat_shamir::fiat_shamir(self, sc, pp, eks, &DAS_PVSS_FIAT_SHAMIR_DST[..], 2); + + // Verify signature(s) on the secret commitment, player ID and `aux` + let g_2 = *pp.get_commitment_base(); + batch_verify_soks::( + self.soks.as_slice(), + &g_2, + &self.V[sc.n], + spks, + aux, + &extra[0], + )?; + + // Verify the committed polynomial is of the right degree + let ldt = LowDegreeTest::new(f, sc.t, sc.n + 1, true, sc.get_batch_evaluation_domain())?; + ldt.low_degree_test_on_g2(&self.V)?; + + // + // Correctness of encryptions check + // + // (see [WVUF Overleaf](https://www.overleaf.com/project/63a1c2c222be94ece7c4b862) for + // explanation of how batching works) + // + + // TODO(Performance): Change the Fiat-Shamir transform to use 128-bit random exponents. + // r_i = \tau^i, \forall i \in [n] + let taus = get_nonzero_powers_of_tau(&extra[1], sc.n); + + // Compute the multiexps from above. + let v = g2_multi_exp(&self.V[..self.V.len() - 1], taus.as_slice()); + let ek = g1_multi_exp( + eks.iter() + .map(|ek| Into::::into(ek)) + .collect::>() + .as_slice(), + taus.as_slice(), + ); + let c = g1_multi_exp(self.C.as_slice(), taus.as_slice()); + + // Fetch some public parameters + let h_1 = *pp.get_encryption_public_params().message_base(); + let g_1_inverse = pp.get_encryption_public_params().pubkey_base().neg(); + + // The vector of left-hand-side ($\mathbb{G}_1$) inputs to each pairing in the multi-pairing. + let lhs = vec![h_1, ek.add(g_1_inverse), self.C_0.add(c.neg())]; + // The vector of right-hand-side ($\mathbb{G}_2$) inputs to each pairing in the multi-pairing. + let rhs = vec![v, self.hat_w, g_2]; + + let res = multi_pairing(lhs.iter(), rhs.iter()); + if res != Gt::identity() { + bail!("Expected zero, but got {} during multi-pairing check", res); + } + + return Ok(()); + } + + fn get_dealers(&self) -> Vec { + self.soks + .iter() + .map(|(p, _, _, _)| *p) + .collect::>() + } + + fn aggregate_with(&mut self, sc: &Self::SecretSharingConfig, other: &Transcript) { + debug_assert_eq!(self.C.len(), sc.n); + debug_assert_eq!(self.V.len(), sc.n + 1); + + self.hat_w += other.hat_w; + self.C_0 += other.C_0; + + for i in 0..sc.n { + self.C[i] += other.C[i]; + self.V[i] += other.V[i]; + } + self.V[sc.n] += other.V[sc.n]; + + for sok in &other.soks { + self.soks.push(sok.clone()); + } + + debug_assert_eq!(self.C.len(), other.C.len()); + debug_assert_eq!(self.V.len(), other.V.len()); + } + + fn get_public_key_share( + &self, + _sc: &Self::SecretSharingConfig, + player: &Player, + ) -> Self::DealtPubKeyShare { + Self::DealtPubKeyShare::new(Self::DealtPubKey::new(self.V[player.id])) + } + + fn get_dealt_public_key(&self) -> Self::DealtPubKey { + Self::DealtPubKey::new(*self.V.last().unwrap()) + } + + fn decrypt_own_share( + &self, + _sc: &Self::SecretSharingConfig, + player: &Player, + dk: &Self::DecryptPrivKey, + ) -> (Self::DealtSecretKeyShare, Self::DealtPubKeyShare) { + let ctxt = self.C[player.id]; // C_i = h_1^m \ek_i^r = h_1^m g_1^{r sk_i} + let ephemeral_key = self.C_0.mul(dk.dk); // (g_1^r)^{sk_i} = ek_i^r + let dealt_secret_key_share = ctxt.sub(ephemeral_key); + let dealt_pub_key_share = self.V[player.id]; // g_2^{f(\omega^i}) + + ( + Self::DealtSecretKeyShare::new(Self::DealtSecretKey::new(dealt_secret_key_share)), + Self::DealtPubKeyShare::new(Self::DealtPubKey::new(dealt_pub_key_share)), + ) + } + + #[allow(non_snake_case)] + fn generate(sc: &Self::SecretSharingConfig, rng: &mut R) -> Self + where + R: rand_core::RngCore + rand_core::CryptoRng, + { + let sk = bls12381::PrivateKey::genesis(); + Transcript { + soks: vec![( + sc.get_player(0), + random_g2_point(rng), + sk.sign(&Contribution:: { + comm: random_g2_point(rng), + player: sc.get_player(0), + aux: 0, + }) + .unwrap(), + (random_g2_point(rng), random_scalar(rng)), + )], + hat_w: random_g2_point(rng), + V: insecure_random_g2_points(sc.n + 1, rng), + C: insecure_random_g1_points(sc.n, rng), + C_0: random_g1_point(rng), + } + } +} + +impl MalleableTranscript for Transcript { + fn maul_signature( + &mut self, + ssk: &Self::SigningSecretKey, + aux: &A, + player: &Player, + ) { + let comm = self.V.last().unwrap(); + let sig = Transcript::sign_contribution(ssk, player, aux, comm); + self.soks[0].0 = *player; + self.soks[0].1 = *comm; + self.soks[0].2 = sig; + } +} + +impl Transcript { + pub fn sign_contribution( + sk: &bls12381::PrivateKey, + player: &Player, + aux: &A, + comm: &G2Projective, + ) -> bls12381::Signature { + sk.sign(&Contribution:: { + comm: *comm, + player: *player, + aux: aux.clone(), + }) + .expect("signing of PVSS contribution should have succeeded") + } +} diff --git a/crates/aptos-dkg/src/pvss/das/weighted_protocol.rs b/crates/aptos-dkg/src/pvss/das/weighted_protocol.rs new file mode 100644 index 00000000000000..aa52b91584f171 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/das/weighted_protocol.rs @@ -0,0 +1,540 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::polynomials::shamir_secret_share, + pvss, + pvss::{ + contribution::{batch_verify_soks, Contribution, SoK}, + das, encryption_dlog, fiat_shamir, schnorr, traits, + traits::{transcript::MalleableTranscript, HasEncryptionPublicParams, SecretSharingConfig}, + LowDegreeTest, Player, WeightedConfig, + }, + utils::{ + g1_multi_exp, g2_multi_exp, multi_pairing, + random::{ + insecure_random_g1_points, insecure_random_g2_points, random_g1_point, random_scalar, + random_scalars, + }, + HasMultiExp, + }, +}; +use anyhow::bail; +use aptos_crypto::{bls12381, CryptoMaterialError, Genesis, SigningKey, ValidCryptoMaterial}; +use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; +use blstrs::{pairing, G1Affine, G1Projective, G2Affine, G2Projective, Gt}; +use group::{Curve, Group}; +use rand::thread_rng; +use serde::{Deserialize, Serialize}; +use std::ops::{Add, Mul, Neg, Sub}; + +/// Scheme name +pub const WEIGHTED_DAS_SK_IN_G1: &'static str = "provable_weighted_das_sk_in_g1"; + +/// Domain-separator tag (DST) for the Fiat-Shamir hashing used to derive randomness from the transcript. +const DAS_WEIGHTED_PVSS_FIAT_SHAMIR_DST: &[u8; 48] = + b"APTOS_DAS_WEIGHTED_PROVABLY_PVSS_FIAT_SHAMIR_DST"; + +/// A weighted transcript where the max player weight is $M$. +/// Each player has weight $w_i$ and the threshold weight is $w$. +/// The total weight is $W = \sum_{i=1}^n w_i$. +/// Let $s_i = \sum_{j = 1}^{i-1} w_i$. +/// Player $i$ will own shares $p(s_i), p(s_i + 1), \ldots, p(s_i + j - 1)$ in the degree-$w$ +/// polynomial $p(X)$. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, BCSCryptoHash, CryptoHasher)] +#[allow(non_snake_case)] +pub struct Transcript { + /// Proofs-of-knowledge (PoKs) for the dealt secret committed in $c = g_2^{p(0)}$. + /// Since the transcript could have been aggregated from other transcripts with their own + /// committed secrets in $c_i = g_2^{p_i(0)}$, this is a vector of PoKs for all these $c_i$'s + /// such that $\prod_i c_i = c$. + /// + /// Also contains BLS signatures from each player $i$ on that player's contribution $c_i$, the + /// player ID $i$ and auxiliary information `aux[i]` provided during dealing. + soks: Vec>, + /// Commitment to encryption randomness $g_1^{r_j} \in G_1, \forall j \in [W]$ + R: Vec, + /// Same as $R$ except uses $g_2$. + R_hat: Vec, + /// First $W$ elements are commitments to the evaluations of $p(X)$: $g_1^{p(\omega^i)}$, + /// where $i \in [W]$. Last element is $g_1^{p(0)}$ (i.e., the dealt public key). + V: Vec, + /// Same as $V$ except uses $g_2$. + V_hat: Vec, + /// ElGamal encryption of the $j$th share of player $i$: + /// i.e., $C[s_i+j-1] = h_1^{p(\omega^{s_i + j - 1})} ek_i^{r_j}, \forall i \in [n], j \in [w_i]$. + /// We sometimes denote $C[s_i+j-1]$ by C_{i, j}. + C: Vec, +} + +impl ValidCryptoMaterial for Transcript { + fn to_bytes(&self) -> Vec { + bcs::to_bytes(&self).expect("unexpected error during PVSS transcript serialization") + } +} + +impl TryFrom<&[u8]> for Transcript { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> Result { + // NOTE: The `serde` implementation in `blstrs` already performs the necessary point validation + // by ultimately calling `GroupEncoding::from_bytes`. + bcs::from_bytes::(bytes).map_err(|_| CryptoMaterialError::DeserializationError) + } +} + +impl traits::Transcript for Transcript { + type DealtPubKey = pvss::dealt_pub_key::g2::DealtPubKey; + type DealtPubKeyShare = Vec; + type DealtSecretKey = pvss::dealt_secret_key::g1::DealtSecretKey; + type DealtSecretKeyShare = Vec; + type DecryptPrivKey = encryption_dlog::g1::DecryptPrivKey; + type EncryptPubKey = encryption_dlog::g1::EncryptPubKey; + type InputSecret = pvss::input_secret::InputSecret; + type PublicParameters = das::PublicParameters; + type SecretSharingConfig = WeightedConfig; + type SigningPubKey = bls12381::PublicKey; + type SigningSecretKey = bls12381::PrivateKey; + + fn scheme_name() -> String { + WEIGHTED_DAS_SK_IN_G1.to_string() + } + + #[allow(non_snake_case)] + fn deal( + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + ssk: &Self::SigningSecretKey, + eks: &Vec, + s: &Self::InputSecret, + aux: &A, + dealer: &Player, + mut rng: &mut R, + ) -> Self { + let n = sc.get_total_num_players(); + assert_eq!(eks.len(), n); + + // f_evals[k] = f(\omega^k), \forall k \in [0, W-1] + let W = sc.get_total_weight(); + let (f_coeff, f_evals) = shamir_secret_share(sc.get_threshold_config(), s, rng); + assert_eq!(f_coeff.len(), sc.get_threshold_weight()); + assert_eq!(f_evals.len(), W); + + // Pick ElGamal randomness r_j, \forall j \in [W] + // r[j] = r_{j+1}, \forall j \in [0, W-1] + let r = random_scalars(W, &mut rng); + let g_1 = pp.get_encryption_public_params().pubkey_base(); + let g_2 = pp.get_commitment_base(); + let h = *pp.get_encryption_public_params().message_base(); + + // NOTE: Recall s_i is the starting index of player i in the vector of shares + // - V[s_i + j - 1] = g_2^{f(s_i + j - 1)} + // - V[W] = g_2^{f(0)} + let V = (0..W) + .map(|k| g_1.mul(f_evals[k])) + .chain([g_1.mul(f_coeff[0])]) + .collect::>(); + let V_hat = (0..W) + .map(|k| g_2.mul(f_evals[k])) + .chain([g_2.mul(f_coeff[0])]) + .collect::>(); + + // R[j] = g_1^{r_{j + 1}}, \forall j \in [0, W-1] + let R = (0..W).map(|j| g_1.mul(r[j])).collect::>(); + let R_hat = (0..W).map(|j| g_2.mul(r[j])).collect::>(); + + let mut C = Vec::with_capacity(W); + for i in 0..n { + let w_i = sc.get_player_weight(&sc.get_player(i)); + + let bases = vec![h, Into::::into(&eks[i])]; + for j in 0..w_i { + let k = sc.get_share_index(i, j).unwrap(); + + C.push(g1_multi_exp( + bases.as_slice(), + [f_evals[k], r[k]].as_slice(), + )) + } + } + + // Compute PoK of input secret committed in V[n] + let pok = schnorr::pok_prove(&f_coeff[0], g_1, &V[W], rng); + + // Sign the secret commitment, player ID and `aux` + let sig = Self::sign_contribution(ssk, dealer, aux, &V[W]); + + let t = Transcript { + soks: vec![(*dealer, V[W], sig, pok)], + V, + R_hat, + R, + V_hat, + C, + }; + debug_assert!(t.check_sizes(sc).is_ok()); + t + } + + #[allow(non_snake_case)] + fn verify( + &self, + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + spks: &Vec, + eks: &Vec, + aux: &Vec, + ) -> anyhow::Result<()> { + self.check_sizes(sc)?; + let n = sc.get_total_num_players(); + if eks.len() != n { + bail!("Expected {} encryption keys, but got {}", n, eks.len()); + } + + // Derive challenges deterministically via Fiat-Shamir; easier to debug for distributed systems + let (f, extra) = fiat_shamir::fiat_shamir( + self, + sc.get_threshold_config(), + pp, + eks, + &DAS_WEIGHTED_PVSS_FIAT_SHAMIR_DST[..], + 2, + ); + + let g_2 = pp.get_commitment_base(); + let g_1 = pp.get_encryption_public_params().pubkey_base(); + let W = sc.get_total_weight(); + batch_verify_soks::( + self.soks.as_slice(), + g_1, + &self.V[W], + spks, + aux, + &extra[0], + )?; + + let ldt = LowDegreeTest::new( + f, + sc.get_threshold_weight(), + W + 1, + true, + sc.get_batch_evaluation_domain(), + )?; + ldt.low_degree_test_on_g1(&self.V)?; + + // + // Correctness of encryptions check + // + + // TODO: 128-bit scalars from Merlin transcript + let alphas_betas_and_gammas = random_scalars(3 * W + 1, &mut thread_rng()); + let (alphas_and_betas, gammas) = alphas_betas_and_gammas.split_at(2 * W + 1); + let (alphas, betas) = alphas_and_betas.split_at(W + 1); + assert_eq!(alphas.len(), W + 1); + assert_eq!(betas.len(), W); + assert_eq!(gammas.len(), W); + + let lc_VR_hat = G2Projective::multi_exp_iter( + self.V_hat.iter().chain(self.R_hat.iter()), + alphas_and_betas.iter(), + ); + let lc_VRC = G1Projective::multi_exp_iter( + self.V.iter().chain(self.R.iter()).chain(self.C.iter()), + alphas_betas_and_gammas.iter(), + ); + let lc_V_hat = G2Projective::multi_exp_iter(self.V_hat.iter().take(W), gammas.iter()); + let mut lc_R_hat = Vec::with_capacity(n); + + for i in 0..n { + let p = sc.get_player(i); + let weight = sc.get_player_weight(&p); + let s_i = sc.get_player_starting_index(&p); + + lc_R_hat.push(g2_multi_exp( + &self.R_hat[s_i..s_i + weight], + &gammas[s_i..s_i + weight], + )); + } + + let h = pp.get_encryption_public_params().message_base(); + let g_2_neg = g_2.neg(); + let eks = eks + .iter() + .map(Into::::into) + .collect::>(); + // The vector of left-hand-side ($\mathbb{G}_2$) inputs to each pairing in the multi-pairing. + let lhs = [g_1, &lc_VRC, h].into_iter().chain(&eks); + // The vector of right-hand-side ($\mathbb{G}_2$) inputs to each pairing in the multi-pairing. + let rhs = [&lc_VR_hat, &g_2_neg, &lc_V_hat] + .into_iter() + .chain(&lc_R_hat); + + let res = multi_pairing(lhs, rhs); + if res != Gt::identity() { + bail!( + "Expected zero during multi-pairing check for {} {}, but got {}", + sc, + Self::scheme_name(), + res + ); + } + + return Ok(()); + } + + fn get_dealers(&self) -> Vec { + self.soks + .iter() + .map(|(p, _, _, _)| *p) + .collect::>() + } + + #[allow(non_snake_case)] + fn aggregate_with(&mut self, sc: &Self::SecretSharingConfig, other: &Transcript) { + let W = sc.get_total_weight(); + + debug_assert!(self.check_sizes(sc).is_ok()); + debug_assert!(other.check_sizes(sc).is_ok()); + + for i in 0..self.V.len() { + self.V[i] += other.V[i]; + self.V_hat[i] += other.V_hat[i]; + } + + for i in 0..W { + self.R[i] += other.R[i]; + self.R_hat[i] += other.R_hat[i]; + self.C[i] += other.C[i]; + } + + for sok in &other.soks { + self.soks.push(sok.clone()); + } + } + + fn get_public_key_share( + &self, + sc: &Self::SecretSharingConfig, + player: &Player, + ) -> Self::DealtPubKeyShare { + let weight = sc.get_player_weight(player); + let mut pk_shares = Vec::with_capacity(weight); + + for j in 0..weight { + let k = sc.get_share_index(player.id, j).unwrap(); + pk_shares.push(pvss::dealt_pub_key_share::g2::DealtPubKeyShare::new( + Self::DealtPubKey::new(self.V_hat[k]), + )); + } + + pk_shares + } + + fn get_dealt_public_key(&self) -> Self::DealtPubKey { + Self::DealtPubKey::new(*self.V_hat.last().unwrap()) + } + + #[allow(non_snake_case)] + fn decrypt_own_share( + &self, + sc: &Self::SecretSharingConfig, + player: &Player, + dk: &Self::DecryptPrivKey, + ) -> (Self::DealtSecretKeyShare, Self::DealtPubKeyShare) { + let weight = sc.get_player_weight(player); + let mut sk_shares = Vec::with_capacity(weight); + let pk_shares = self.get_public_key_share(sc, player); + + for j in 0..weight { + let k = sc.get_share_index(player.id, j).unwrap(); + + let ctxt = self.C[k]; // h_1^{f(s_i + j - 1)} \ek_i^{r_{s_i + j}} + let ephemeral_key = self.R[k].mul(dk.dk); // (g_1^{r_{s_i + j}}) + let dealt_secret_key_share = ctxt.sub(ephemeral_key); + + sk_shares.push(pvss::dealt_secret_key_share::g1::DealtSecretKeyShare::new( + Self::DealtSecretKey::new(dealt_secret_key_share), + )); + } + + (sk_shares, pk_shares) + } + + #[allow(non_snake_case)] + fn generate(sc: &Self::SecretSharingConfig, rng: &mut R) -> Self + where + R: rand_core::RngCore + rand_core::CryptoRng, + { + let W = sc.get_total_weight(); + let sk = bls12381::PrivateKey::genesis(); + Transcript { + soks: vec![( + sc.get_player(0), + random_g1_point(rng), + sk.sign(&Contribution:: { + comm: random_g1_point(rng), + player: sc.get_player(0), + aux: 0, + }) + .unwrap(), + (random_g1_point(rng), random_scalar(rng)), + )], + R: insecure_random_g1_points(W, rng), + R_hat: insecure_random_g2_points(W, rng), + V: insecure_random_g1_points(W + 1, rng), + V_hat: insecure_random_g2_points(W + 1, rng), + C: insecure_random_g1_points(W, rng), + } + } +} + +impl Transcript { + #[allow(non_snake_case)] + fn check_sizes(&self, sc: &WeightedConfig) -> anyhow::Result<()> { + let W = sc.get_total_weight(); + + if self.V.len() != W + 1 { + bail!( + "Expected {} G_2 (polynomial) commitment elements, but got {}", + W + 1, + self.V.len() + ); + } + + if self.V_hat.len() != W + 1 { + bail!( + "Expected {} G_2 (polynomial) commitment elements, but got {}", + W + 1, + self.V_hat.len() + ); + } + + if self.R.len() != W { + bail!( + "Expected {} G_1 commitment(s) to ElGamal randomness, but got {}", + W, + self.R.len() + ); + } + + if self.R_hat.len() != W { + bail!( + "Expected {} G_2 commitment(s) to ElGamal randomness, but got {}", + W, + self.R_hat.len() + ); + } + + if self.C.len() != W { + bail!("Expected C of length {}, but got {}", W, self.C.len()); + } + + Ok(()) + } + + /// For testing. + #[allow(non_snake_case, unused)] + fn slow_verify( + &self, + sc: &WeightedConfig, + pp: &das::PublicParameters, + eks: &Vec, + ) -> anyhow::Result<()> { + let n = sc.get_total_num_players(); + let g_2 = pp.get_commitment_base(); + let g_1 = pp.get_encryption_public_params().pubkey_base(); + let h_1 = pp.get_encryption_public_params().message_base(); + let W = sc.get_total_weight(); + + let g_1_aff = g_1.to_affine(); + let g_2_aff = g_2.to_affine(); + let V_hat_aff = self + .V_hat + .iter() + .map(|p| p.to_affine()) + .collect::>(); + for i in 0..W + 1 { + let lhs = pairing(&g_1_aff, &V_hat_aff[i]); + let rhs = pairing(&self.V[i].to_affine(), &g_2_aff); + if lhs != rhs { + bail!("V[{}] and V_hat[{}] did not match", i, i); + } + } + + let R_hat_aff = self + .R_hat + .iter() + .map(|p| p.to_affine()) + .collect::>(); + for i in 0..W { + let lhs = pairing(&g_1_aff, &R_hat_aff[i]); + let rhs = pairing(&self.R[i].to_affine(), &g_2_aff); + if lhs != rhs { + bail!("R[{}] and R_hat[{}] did not match", i, i); + } + } + + let h_1_aff = h_1.to_affine(); + let eks = eks + .iter() + .map(Into::::into) + .map(|p| p.to_affine()) + .collect::>(); + for i in 0..n { + let p = sc.get_player(i); + let weight = sc.get_player_weight(&p); + for j in 0..weight { + let k = sc.get_share_index(i, j).unwrap(); + let lhs = pairing(&h_1_aff, &V_hat_aff[k]).add(pairing(&eks[i], &R_hat_aff[k])); + let rhs = pairing(&self.C[k].to_affine(), &g_2_aff); + if lhs != rhs { + bail!("C[{},{}] = C[{}] did not match", i, j, k); + } + } + } + + Ok(()) + } +} + +impl MalleableTranscript for Transcript { + fn maul_signature( + &mut self, + ssk: &Self::SigningSecretKey, + aux: &A, + player: &Player, + ) { + let comm = self.V.last().unwrap(); + let sig = Transcript::sign_contribution(ssk, player, aux, comm); + self.soks[0].0 = *player; + self.soks[0].1 = *comm; + self.soks[0].2 = sig; + } +} + +impl Transcript { + pub fn sign_contribution( + sk: &bls12381::PrivateKey, + player: &Player, + aux: &A, + comm: &G1Projective, + ) -> bls12381::Signature { + sk.sign(&Contribution:: { + comm: *comm, + player: *player, + aux: aux.clone(), + }) + .expect("signing of PVSS contribution should have succeeded") + } + + #[cfg(any(test, feature = "fuzzing"))] + pub fn dummy() -> Self { + Self { + soks: vec![], + R: vec![], + R_hat: vec![], + V: vec![], + V_hat: vec![], + C: vec![], + } + } +} diff --git a/crates/aptos-dkg/src/pvss/dealt_pub_key.rs b/crates/aptos-dkg/src/pvss/dealt_pub_key.rs new file mode 100644 index 00000000000000..65be4bec81db81 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/dealt_pub_key.rs @@ -0,0 +1,62 @@ +// Copyright © Aptos Foundation + +macro_rules! dealt_pub_key_impl { + ($GT_PROJ_NUM_BYTES:ident, $gt_proj_from_bytes:ident, $GTProjective:ident) => { + use crate::{constants::$GT_PROJ_NUM_BYTES, utils::serialization::$gt_proj_from_bytes}; + use aptos_crypto::{ + CryptoMaterialError, ValidCryptoMaterial, ValidCryptoMaterialStringExt, + }; + use aptos_crypto_derive::{DeserializeKey, SerializeKey}; + use blstrs::$GTProjective; + + /// The size of a serialized *dealt public key*. + pub(crate) const DEALT_PK_NUM_BYTES: usize = $GT_PROJ_NUM_BYTES; + + /// The *dealt public key* associated with the the secret key that was dealt via the PVSS transcript. + #[derive(DeserializeKey, Clone, Debug, SerializeKey, PartialEq, Eq)] + pub struct DealtPubKey { + /// A group element $g_1^a \in G$, where $G$ is $G_1$, $G_2$ or $G_T$ + g_a: $GTProjective, + } + + // + // DealtPublicKey + // + + impl DealtPubKey { + pub fn new(g_a: $GTProjective) -> Self { + Self { g_a } + } + + pub fn to_bytes(&self) -> [u8; DEALT_PK_NUM_BYTES] { + self.g_a.to_compressed() + } + + pub fn as_group_element(&self) -> &$GTProjective { + &self.g_a + } + } + + impl ValidCryptoMaterial for DealtPubKey { + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + } + + impl TryFrom<&[u8]> for DealtPubKey { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> std::result::Result { + $gt_proj_from_bytes(bytes).map(|g_a| DealtPubKey { g_a }) + } + } + }; +} + +pub mod g1 { + // dealt_pub_key_impl!(G1_PROJ_NUM_BYTES, g1_proj_from_bytes, G1Projective); +} + +pub mod g2 { + dealt_pub_key_impl!(G2_PROJ_NUM_BYTES, g2_proj_from_bytes, G2Projective); +} diff --git a/crates/aptos-dkg/src/pvss/dealt_pub_key_share.rs b/crates/aptos-dkg/src/pvss/dealt_pub_key_share.rs new file mode 100644 index 00000000000000..728b6d8d31c4c5 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/dealt_pub_key_share.rs @@ -0,0 +1,64 @@ +// Copyright © Aptos Foundation + +// NOTE: I don't think we need this DealtPubKey[Share] anymore, since we never implement any traits +// on it, unlike the DealtSecretKey[Share]. We will keep it in case we want to implement the +// `Reconstructable` trait later on though. + +macro_rules! dealt_pub_key_share_impl { + ($GTProjective:ident, $gt:ident) => { + use crate::pvss::dealt_pub_key::$gt::{DealtPubKey, DEALT_PK_NUM_BYTES}; + use aptos_crypto::{ + CryptoMaterialError, ValidCryptoMaterial, ValidCryptoMaterialStringExt, + }; + use aptos_crypto_derive::{DeserializeKey, SerializeKey}; + use blstrs::$GTProjective; + + /// The size of a serialized *dealt public key share*. + pub(crate) const DEALT_PK_SHARE_NUM_BYTES: usize = DEALT_PK_NUM_BYTES; + + /// A player's *share* of the *dealt public key* from above. + #[derive(DeserializeKey, Clone, Debug, SerializeKey, PartialEq, Eq)] + pub struct DealtPubKeyShare(pub(crate) DealtPubKey); + + // + // DealtPublicKeyShare + // + + impl DealtPubKeyShare { + pub fn new(dealt_pk: DealtPubKey) -> Self { + DealtPubKeyShare(dealt_pk) + } + + pub fn to_bytes(&self) -> [u8; DEALT_PK_SHARE_NUM_BYTES] { + self.0.to_bytes() + } + + pub fn as_group_element(&self) -> &$GTProjective { + self.0.as_group_element() + } + } + + impl ValidCryptoMaterial for DealtPubKeyShare { + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + } + + impl TryFrom<&[u8]> for DealtPubKeyShare { + type Error = CryptoMaterialError; + + /// Deserialize a `DealtPublicKeyShare`. + fn try_from(bytes: &[u8]) -> std::result::Result { + DealtPubKey::try_from(bytes).map(|pk| DealtPubKeyShare(pk)) + } + } + }; +} + +pub mod g1 { + //dealt_pub_key_share_impl!(G1Projective, g1); +} + +pub mod g2 { + dealt_pub_key_share_impl!(G2Projective, g2); +} diff --git a/crates/aptos-dkg/src/pvss/dealt_secret_key.rs b/crates/aptos-dkg/src/pvss/dealt_secret_key.rs new file mode 100644 index 00000000000000..1206a6bedc66e1 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/dealt_secret_key.rs @@ -0,0 +1,140 @@ +// Copyright © Aptos Foundation + +macro_rules! dealt_secret_key_impl { + ( + $GT_PROJ_NUM_BYTES:ident, + $gt_proj_from_bytes:ident, + $gt_multi_exp:ident, + $GTProjective:ident, + $gt:ident + ) => { + use crate::{ + algebra::lagrange::lagrange_coefficients, + constants::$GT_PROJ_NUM_BYTES, + pvss::{ + dealt_secret_key_share::$gt::DealtSecretKeyShare, player::Player, + threshold_config::ThresholdConfig, traits, traits::SecretSharingConfig, + }, + utils::{serialization::$gt_proj_from_bytes, $gt_multi_exp}, + }; + use aptos_crypto::CryptoMaterialError; + use aptos_crypto_derive::{SilentDebug, SilentDisplay}; + use blstrs::{$GTProjective, Scalar}; + use ff::Field; + use more_asserts::{assert_ge, assert_le}; + + /// The size of a serialized *dealt secret key*. + pub(crate) const DEALT_SK_NUM_BYTES: usize = $GT_PROJ_NUM_BYTES; + + /// The *dealt secret key* that will be output by the PVSS reconstruction algorithm. This will be of + /// a different type than the *input secret* that was given as input to PVSS dealing. + /// + /// This secret key will never be reconstructed in plaintext. Instead, we will use a simple/efficient + /// multiparty computation protocol to reconstruct a function of this secret (e.g., a verifiable + /// random function evaluated on an input `x` under this secret). + /// + /// NOTE: We do not implement (de)serialization for this because a dealt secret key `sk` will never be + /// materialized in our protocol. Instead, we always use some form of efficient multi-party computation + /// MPC protocol to materialize a function of `sk`, such as `f(sk, m)` where `f` is a verifiable random + /// function (VRF), for example. + #[derive(SilentDebug, SilentDisplay, PartialEq, Clone)] + pub struct DealtSecretKey { + /// A group element $\hat{h}^a \in G$, where $G$ is $G_1$, $G_2$ or $G_T$ + h_hat: $GTProjective, + } + + #[cfg(feature = "assert-private-keys-not-cloneable")] + static_assertions::assert_not_impl_any!(DealtSecretKey: Clone); + + // + // DealtSecretKey implementation & traits + // + + impl DealtSecretKey { + pub fn new(h_hat: $GTProjective) -> Self { + Self { h_hat } + } + + pub fn to_bytes(&self) -> [u8; DEALT_SK_NUM_BYTES] { + self.h_hat.to_compressed() + } + + pub fn as_group_element(&self) -> &$GTProjective { + &self.h_hat + } + } + + // impl fmt::Debug for DealtSecretKey { + // fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // write!(f, "{}", hex::encode(self.to_bytes())) + // } + // } + + impl TryFrom<&[u8]> for DealtSecretKey { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> std::result::Result { + $gt_proj_from_bytes(bytes).map(|h_hat| DealtSecretKey { h_hat }) + } + } + + impl traits::Reconstructable for DealtSecretKey { + type Share = DealtSecretKeyShare; + + /// Reconstructs the `DealtSecretKey` given a sufficiently-large subset of shares from players. + /// Mainly used for testing the PVSS transcript dealing and decryption. + fn reconstruct(sc: &ThresholdConfig, shares: &Vec<(Player, Self::Share)>) -> Self { + assert_ge!(shares.len(), sc.get_threshold()); + assert_le!(shares.len(), sc.get_total_num_players()); + + let ids = shares.iter().map(|(p, _)| p.id).collect::>(); + let lagr = lagrange_coefficients( + sc.get_batch_evaluation_domain(), + ids.as_slice(), + &Scalar::ZERO, + ); + let bases = shares + .iter() + .map(|(_, share)| share.0.h_hat) + .collect::>(); + + // println!(); + // println!("Lagrange IDs: {:?}", ids); + // println!("Lagrange coeffs"); + // for l in lagr.iter() { + // println!(" + {}", hex::encode(l.to_bytes_le())); + // } + // println!("Bases: "); + // for b in bases.iter() { + // println!(" + {}", hex::encode(b.to_bytes())); + // } + + assert_eq!(lagr.len(), bases.len()); + + DealtSecretKey { + h_hat: $gt_multi_exp(bases.as_slice(), lagr.as_slice()), + } + } + } + }; +} + +pub mod g1 { + dealt_secret_key_impl!( + G1_PROJ_NUM_BYTES, + g1_proj_from_bytes, + g1_multi_exp, + G1Projective, + g1 + ); +} + +pub mod g2 { + // dealt_secret_key_impl!( + // G2_PROJ_NUM_BYTES, + // g2_proj_from_bytes, + // g2_multi_exp, + // G2Projective, + // g2 + // ); +} diff --git a/crates/aptos-dkg/src/pvss/dealt_secret_key_share.rs b/crates/aptos-dkg/src/pvss/dealt_secret_key_share.rs new file mode 100644 index 00000000000000..f2bbdd2f83030c --- /dev/null +++ b/crates/aptos-dkg/src/pvss/dealt_secret_key_share.rs @@ -0,0 +1,68 @@ +// Copyright © Aptos Foundation + +macro_rules! dealt_secret_key_share_impl { + ($GTProjective:ident, $gt:ident) => { + use crate::pvss::dealt_secret_key::$gt::{DealtSecretKey, DEALT_SK_NUM_BYTES}; + use aptos_crypto::{ + CryptoMaterialError, ValidCryptoMaterial, ValidCryptoMaterialStringExt, + }; + use aptos_crypto_derive::{DeserializeKey, SerializeKey, SilentDebug, SilentDisplay}; + use blstrs::$GTProjective; + + /// The size of a serialized *dealt secret key share*. + const DEALT_SK_SHARE_NUM_BYTES: usize = DEALT_SK_NUM_BYTES; + + /// A player's *share* of the secret key that was dealt via the PVSS transcript. + #[derive(DeserializeKey, SerializeKey, SilentDisplay, SilentDebug, PartialEq, Clone)] + pub struct DealtSecretKeyShare(pub(crate) DealtSecretKey); + + #[cfg(feature = "assert-private-keys-not-cloneable")] + static_assertions::assert_not_impl_any!(DealtSecretKeyShare: Clone); + + // + // DealtSecretKeyShare implementation & traits + // + + impl DealtSecretKeyShare { + pub fn new(dealt_sk: DealtSecretKey) -> Self { + DealtSecretKeyShare(dealt_sk) + } + + pub fn to_bytes(&self) -> [u8; DEALT_SK_SHARE_NUM_BYTES] { + self.0.to_bytes() + } + + pub fn as_group_element(&self) -> &$GTProjective { + self.0.as_group_element() + } + } + + impl ValidCryptoMaterial for DealtSecretKeyShare { + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + } + + // impl fmt::Debug for DealtSecretKeyShare { + // fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // write!(f, "{}", hex::encode(self.to_bytes())) + // } + // } + + impl TryFrom<&[u8]> for DealtSecretKeyShare { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> std::result::Result { + DealtSecretKey::try_from(bytes).map(|sk| DealtSecretKeyShare(sk)) + } + } + }; +} + +pub mod g1 { + dealt_secret_key_share_impl!(G1Projective, g1); +} + +pub mod g2 { + //dealt_secret_key_share_impl!(G2Projective, g2); +} diff --git a/crates/aptos-dkg/src/pvss/encryption_dlog.rs b/crates/aptos-dkg/src/pvss/encryption_dlog.rs new file mode 100644 index 00000000000000..b442b19d4e4b4e --- /dev/null +++ b/crates/aptos-dkg/src/pvss/encryption_dlog.rs @@ -0,0 +1,219 @@ +// Copyright © Aptos Foundation + +/// Implements public parameters $h \in G$ for a simple DLOG-based encryption scheme. +macro_rules! encryption_dlog_pp_impl { + ($GT_PROJ_NUM_BYTES:ident, $gt_proj_from_bytes:ident, $GTProjective:ident) => { + pub const PUBLIC_PARAMS_NUM_BYTES: usize = $GT_PROJ_NUM_BYTES; + + /// The public parameters used in the encryption scheme. + #[derive(DeserializeKey, Clone, SerializeKey, PartialEq, Debug, Eq)] + pub struct PublicParameters { + /// A group element $h \in G$, where $G$ is $G_1$, $G_2$ or $G_T$. + h: $GTProjective, + } + + impl PublicParameters { + pub fn new(h: $GTProjective) -> Self { + Self { h } + } + + pub fn to_bytes(&self) -> [u8; $GT_PROJ_NUM_BYTES] { + self.h.to_compressed() + } + + pub fn as_group_element(&self) -> &$GTProjective { + &self.h + } + } + + impl TryFrom<&[u8]> for PublicParameters { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> std::result::Result { + $gt_proj_from_bytes(bytes).map(|h| PublicParameters { h }) + } + } + + impl ValidCryptoMaterial for PublicParameters { + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + } + }; +} + +/// Implements structs for SKs and PKs, where SKs are scalars and the PKs can be implemented to be +/// any function of the SK via the `crate::pvss::traits::Convert` trait (e.g., $pk = h^{sk^{-1}}$). +macro_rules! encryption_dlog_keys_impl { + ($GT_PROJ_NUM_BYTES:ident, $gt_proj_from_bytes:ident, $GTProjective:ident) => { + use crate::{ + constants::{$GT_PROJ_NUM_BYTES, SCALAR_NUM_BYTES}, + utils::{ + random::random_scalar, + serialization::{scalar_from_bytes_le, $gt_proj_from_bytes}, + }, + }; + use aptos_crypto::{ + CryptoMaterialError, Length, Uniform, ValidCryptoMaterial, ValidCryptoMaterialStringExt, + }; + use aptos_crypto_derive::{DeserializeKey, SerializeKey, SilentDebug, SilentDisplay}; + use blstrs::{$GTProjective, Scalar}; + use std::{ + fmt, + hash::{Hash, Hasher}, + }; + + // + // Constants + // + + /// The length in bytes of a decryption key. + pub const DECRYPT_KEY_NUM_BYTES: usize = SCALAR_NUM_BYTES; + + /// The length in bytes of an encryption key. + pub const ENCRYPT_KEY_NUM_BYTES: usize = $GT_PROJ_NUM_BYTES; + + // + // Structs + // + + /// The *encryption (public)* key used to encrypt shares of the dealt secret for each PVSS player. + #[derive(DeserializeKey, SerializeKey, Clone, Eq)] + pub struct EncryptPubKey { + /// A group element $h^{dk^{-1}} \in G_1$. + pub(crate) ek: $GTProjective, + } + + /// The *decryption (secret) key* used by each PVSS player do decrypt their share of the dealt secret. + #[derive(DeserializeKey, SerializeKey, SilentDisplay, SilentDebug)] + pub struct DecryptPrivKey { + /// A scalar $dk \in F$. + pub(crate) dk: Scalar, + } + + #[cfg(feature = "assert-private-keys-not-cloneable")] + static_assertions::assert_not_impl_any!(DecryptPrivKey: Clone); + + // + // DecryptPrivKey + // + + impl DecryptPrivKey { + pub fn to_bytes(&self) -> [u8; DECRYPT_KEY_NUM_BYTES] { + self.dk.to_bytes_le() + } + + pub fn to_bytes_be(&self) -> [u8; DECRYPT_KEY_NUM_BYTES] { + self.dk.to_bytes_be() + } + + pub fn from_bytes_be(bytes: &[u8; DECRYPT_KEY_NUM_BYTES]) -> Self { + Self { + dk: Scalar::from_bytes_be(bytes).unwrap(), + } + } + } + + // impl fmt::Debug for DecryptPrivKey { + // fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // write!(f, "{}", hex::encode(self.to_bytes())) + // } + // } + + impl Length for DecryptPrivKey { + fn length(&self) -> usize { + DECRYPT_KEY_NUM_BYTES + } + } + + // impl PrivateKey for DecryptPrivKey { + // type PublicKeyMaterial = EncryptPubKey; + // } + + impl ValidCryptoMaterial for DecryptPrivKey { + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + } + + impl TryFrom<&[u8]> for DecryptPrivKey { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> std::result::Result { + scalar_from_bytes_le(bytes).map(|dk| DecryptPrivKey { dk }) + } + } + + impl Uniform for DecryptPrivKey { + fn generate(rng: &mut R) -> Self + where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, + { + DecryptPrivKey { + dk: random_scalar(rng), + } + } + } + + // + // EncryptPubKey + // + + impl EncryptPubKey { + /// Serializes an encryption key. + pub fn to_bytes(&self) -> [u8; ENCRYPT_KEY_NUM_BYTES] { + self.ek.to_compressed() + } + } + + impl fmt::Debug for EncryptPubKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.to_bytes())) + } + } + + impl From<&EncryptPubKey> for $GTProjective { + fn from(ek: &EncryptPubKey) -> Self { + ek.ek + } + } + + impl PartialEq for EncryptPubKey { + fn eq(&self, other: &Self) -> bool { + self.to_bytes() == other.to_bytes() + } + } + + impl Hash for EncryptPubKey { + fn hash(&self, state: &mut H) { + state.write(self.to_bytes().as_slice()) + } + } + + impl ValidCryptoMaterial for EncryptPubKey { + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + } + + impl TryFrom<&[u8]> for EncryptPubKey { + type Error = CryptoMaterialError; + + /// Deserialize an `EncryptPubKey`. This method will check that the public key is in the + /// (prime-order) group. + fn try_from(bytes: &[u8]) -> std::result::Result { + $gt_proj_from_bytes(bytes).map(|ek| EncryptPubKey { ek }) + } + } + }; +} + +pub mod g1 { + // PPs not needed, for now. + encryption_dlog_keys_impl!(G1_PROJ_NUM_BYTES, g1_proj_from_bytes, G1Projective); +} + +pub mod g2 { + encryption_dlog_pp_impl!(G2_PROJ_NUM_BYTES, g2_proj_from_bytes, G2Projective); + encryption_dlog_keys_impl!(G2_PROJ_NUM_BYTES, g2_proj_from_bytes, G2Projective); +} diff --git a/crates/aptos-dkg/src/pvss/encryption_elgamal.rs b/crates/aptos-dkg/src/pvss/encryption_elgamal.rs new file mode 100644 index 00000000000000..5e1843f3b84aa7 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/encryption_elgamal.rs @@ -0,0 +1,86 @@ +// Copyright © Aptos Foundation + +/// Implements public parameters $(h, g) \in G$ for an ElGamal encryption scheme where $h$ +/// is the message base and $g$ is the PK base. +macro_rules! encryption_elgamal_pp_impl { + ($GT_PROJ_NUM_BYTES:ident, $gt_proj_from_bytes:ident, $GTProjective:ident) => { + use crate::{constants::$GT_PROJ_NUM_BYTES, utils::serialization::$gt_proj_from_bytes}; + use aptos_crypto::{ + CryptoMaterialError, ValidCryptoMaterial, ValidCryptoMaterialStringExt, + }; + use aptos_crypto_derive::{DeserializeKey, SerializeKey}; + use blstrs::$GTProjective; + + // + // Constants + // + + /// The length in bytes of the public params struct. + pub const PUBLIC_PARAMS_NUM_BYTES: usize = $GT_PROJ_NUM_BYTES * 2; + + // + // Structs + // + + /// The public parameters used in the encryption scheme. + #[derive(DeserializeKey, PartialEq, Clone, SerializeKey, Eq, Debug)] + pub struct PublicParameters { + /// A group element $g \in G$, where $G$ is $G_1$, $G_2$ or $G_T$ used to exponentiate + /// both the (1) ciphertext randomness and the (2) the DSK when computing its EK. + g: $GTProjective, + /// A group element $h \in G$ that is raised to the encrypted message + h: $GTProjective, + } + + impl PublicParameters { + pub fn new(g: $GTProjective, h: $GTProjective) -> Self { + Self { g, h } + } + + pub fn to_bytes(&self) -> [u8; 2 * $GT_PROJ_NUM_BYTES] { + let mut bytes = [0u8; 2 * $GT_PROJ_NUM_BYTES]; + + // Copy bytes from g.to_compressed() into the first half of the bytes array. + bytes[..$GT_PROJ_NUM_BYTES].copy_from_slice(&self.g.to_compressed()); + + // Copy bytes from h.to_compressed() into the second half of the bytes array. + bytes[$GT_PROJ_NUM_BYTES..].copy_from_slice(&self.h.to_compressed()); + + bytes + } + + pub fn pubkey_base(&self) -> &$GTProjective { + &self.g + } + + pub fn message_base(&self) -> &$GTProjective { + &self.h + } + } + + impl ValidCryptoMaterial for PublicParameters { + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + } + + impl TryFrom<&[u8]> for PublicParameters { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> std::result::Result { + let g = $gt_proj_from_bytes(&bytes[0..$GT_PROJ_NUM_BYTES])?; + let h = $gt_proj_from_bytes(&bytes[$GT_PROJ_NUM_BYTES..])?; + + Ok(PublicParameters { g, h }) + } + } + }; +} + +pub mod g1 { + encryption_elgamal_pp_impl!(G1_PROJ_NUM_BYTES, g1_proj_from_bytes, G1Projective); +} + +pub mod g2 { + // Not needed, for now +} diff --git a/crates/aptos-dkg/src/pvss/fiat_shamir.rs b/crates/aptos-dkg/src/pvss/fiat_shamir.rs new file mode 100644 index 00000000000000..760e47dc6ffedb --- /dev/null +++ b/crates/aptos-dkg/src/pvss/fiat_shamir.rs @@ -0,0 +1,165 @@ +// Copyright © Aptos Foundation + +//! For what it's worth, I don't understand why the `merlin` library wants the user to first define +//! a trait with their 'append' operations and them implement that trait on `merlin::Transcript`. +//! I also don't understand how that doesn't break the orphan rule in Rust. +//! I suspect the reason they want the developer to do things these ways is to force them to cleanly +//! define all the things that are appended to the transcript. + +use crate::{ + pvss::{threshold_config::ThresholdConfig, traits::Transcript}, + utils::random::random_scalar_from_uniform_bytes, + SCALAR_NUM_BYTES, +}; +use aptos_crypto::ValidCryptoMaterial; +use blstrs::Scalar; +use ff::PrimeField; + +pub const PVSS_DOM_SEP: &[u8; 21] = b"APTOS_SCRAPE_PVSS_DST"; + +#[allow(non_snake_case)] +pub trait FiatShamirProtocol { + /// Append a domain separator for the PVSS protocol, consisting of a sharing configuration `sc`, + /// which locks in the $t$ out of $n$ threshold. + fn pvss_domain_sep(&mut self, sc: &ThresholdConfig); + + /// Append the public parameters `pp`. + fn append_public_parameters(&mut self, pp: &T::PublicParameters); + + /// Append the encryption keys `eks`. + fn append_encryption_keys(&mut self, eks: &Vec); + + /// Appends the transcript + fn append_transcript(&mut self, trx: &T); + + /// Returns a random dual-code word check polynomial for the SCRAPE LDT test. + fn challenge_dual_code_word_polynomial(&mut self, t: usize, n: usize) -> Vec; + + /// Returns one or more scalars `r` useful for doing linear combinations (e.g., combining + /// pairings in the SCRAPE multipairing check using coefficients $1, r, r^2, r^3, \ldots$ + fn challenge_linear_combination_scalars(&mut self, num_scalars: usize) -> Vec; + fn challenge_linear_combination_128bit(&mut self, num_scalars: usize) -> Vec; +} + +#[allow(non_snake_case)] +impl FiatShamirProtocol for merlin::Transcript { + fn pvss_domain_sep(&mut self, sc: &ThresholdConfig) { + self.append_message(b"dom-sep", PVSS_DOM_SEP); + self.append_message(b"scheme-name", T::scheme_name().as_bytes()); + self.append_u64(b"t", sc.t as u64); + self.append_u64(b"n", sc.n as u64); + } + + fn append_public_parameters(&mut self, pp: &T::PublicParameters) { + self.append_message(b"pp", pp.to_bytes().as_slice()); + } + + fn append_encryption_keys(&mut self, eks: &Vec) { + self.append_u64(b"encryption-keys", eks.len() as u64); + + for ek in eks { + self.append_message(b"ek", ek.to_bytes().as_slice()) + } + } + + fn append_transcript(&mut self, trx: &T) { + self.append_message(b"transcript", trx.to_bytes().as_slice()); + } + + fn challenge_dual_code_word_polynomial(&mut self, t: usize, n_plus_1: usize) -> Vec { + let num_coeffs = n_plus_1 - t; + + let num_bytes = num_coeffs * 2 * SCALAR_NUM_BYTES; + let mut buf = vec![0u8; num_bytes]; + + self.challenge_bytes(b"challenge_dual_code_word_polynomial", &mut buf); + + let mut f = Vec::with_capacity(num_coeffs); + + for chunk in buf.chunks(2 * SCALAR_NUM_BYTES) { + match chunk.try_into() { + Ok(chunk) => { + f.push(random_scalar_from_uniform_bytes(chunk)); + }, + Err(_) => panic!("Expected a slice of size 64, but got a different size"), + } + } + + assert_eq!(f.len(), num_coeffs); + + f + } + + fn challenge_linear_combination_scalars(&mut self, num_scalars: usize) -> Vec { + let mut buf = vec![0u8; num_scalars * 2 * SCALAR_NUM_BYTES]; + self.challenge_bytes(b"challenge_linear_combination", &mut buf); + + let mut v = Vec::with_capacity(num_scalars); + + // To ensure we pick a uniform Scalar, we sample twice the number of bytes in a scalar and + // reduce those bytes modulo the order of the scalar field. + for chunk in buf.chunks(2 * SCALAR_NUM_BYTES) { + match chunk.try_into() { + Ok(chunk) => { + v.push(random_scalar_from_uniform_bytes(chunk)); + }, + Err(_) => panic!("Expected a 64-byte slice, but got a different size"), + } + } + + assert_eq!(v.len(), num_scalars); + + v + } + + fn challenge_linear_combination_128bit(&mut self, num_scalars: usize) -> Vec { + let mut buf = vec![0u8; num_scalars * 16]; + self.challenge_bytes(b"challenge_linear_combination", &mut buf); + + let mut v = Vec::with_capacity(num_scalars); + + for chunk in buf.chunks(16) { + match chunk.try_into() { + Ok(chunk) => { + v.push(Scalar::from_u128(u128::from_le_bytes(chunk))); + }, + Err(_) => panic!("Expected a 16-byte slice, but got a different size"), + } + } + + assert_eq!(v.len(), num_scalars); + + v + } +} + +/// Securely derives a Fiat-Shamir challenge via Merlin. +/// Returns (n+1-t) random scalars for the SCRAPE LDT test (i.e., the random polynomial itself). +/// Additionally returns `num_scalars` random scalars for some linear combinations. +pub(crate) fn fiat_shamir( + trx: &T, + sc: &ThresholdConfig, + pp: &T::PublicParameters, + eks: &Vec, + dst: &'static [u8], + num_scalars: usize, +) -> (Vec, Vec) { + let mut fs_t = merlin::Transcript::new(dst); + + >::pvss_domain_sep(&mut fs_t, sc); + >::append_public_parameters(&mut fs_t, pp); + >::append_encryption_keys(&mut fs_t, eks); + >::append_transcript(&mut fs_t, trx); + + ( + >::challenge_dual_code_word_polynomial( + &mut fs_t, + sc.t, + sc.n + 1, + ), + >::challenge_linear_combination_scalars( + &mut fs_t, + num_scalars, + ), + ) +} diff --git a/crates/aptos-dkg/src/pvss/input_secret.rs b/crates/aptos-dkg/src/pvss/input_secret.rs new file mode 100644 index 00000000000000..9c3b72d80dbf3e --- /dev/null +++ b/crates/aptos-dkg/src/pvss/input_secret.rs @@ -0,0 +1,79 @@ +// Copyright © Aptos Foundation + +use crate::utils::random::random_scalar; +use aptos_crypto::Uniform; +use aptos_crypto_derive::{SilentDebug, SilentDisplay}; +use blstrs::Scalar; +use ff::Field; +use num_traits::Zero; +use rand_core::{CryptoRng, RngCore}; +use std::ops::{Add, AddAssign}; + +/// The *input secret* that will be given as input to the PVSS dealing algorithm. This will be of a +/// different type than the *dealt secret* that will be returned by the PVSS reconstruction algorithm. +/// +/// This secret will NOT need to be stored by validators because a validator (1) picks such a secret +/// and (2) deals it via the PVSS. If the validator crashes during dealing, the entire task will be +/// restarted with a freshly-generated input secret. +#[derive(SilentDebug, SilentDisplay, PartialEq)] +pub struct InputSecret { + /// The actual secret being dealt; a scalar $a \in F$. + a: Scalar, +} + +/// Make sure input secrets can be added together. +impl Add<&InputSecret> for InputSecret { + type Output = InputSecret; + + fn add(self, rhs: &InputSecret) -> Self::Output { + InputSecret { + a: self.a.add(rhs.a), + } + } +} + +impl AddAssign<&InputSecret> for InputSecret { + fn add_assign(&mut self, rhs: &InputSecret) { + self.a.add_assign(rhs.a) + } +} + +impl Add for InputSecret { + type Output = InputSecret; + + fn add(self, rhs: InputSecret) -> Self::Output { + InputSecret { + a: self.a.add(rhs.a), + } + } +} + +impl Zero for InputSecret { + fn zero() -> Self { + InputSecret { a: Scalar::ZERO } + } + + fn is_zero(&self) -> bool { + self.a.is_zero_vartime() + } +} + +#[cfg(feature = "assert-private-keys-not-cloneable")] +static_assertions::assert_not_impl_any!(InputSecret: Clone); + +impl InputSecret { + pub fn get_secret_a(&self) -> &Scalar { + &self.a + } +} + +impl Uniform for InputSecret { + fn generate(rng: &mut R) -> Self + where + R: RngCore + CryptoRng, + { + let a = random_scalar(rng); + + InputSecret { a } + } +} diff --git a/crates/aptos-dkg/src/pvss/insecure_field/mod.rs b/crates/aptos-dkg/src/pvss/insecure_field/mod.rs new file mode 100644 index 00000000000000..e9bad4ce4e8db6 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/insecure_field/mod.rs @@ -0,0 +1,5 @@ +// Copyright © Aptos Foundation + +mod transcript; + +pub use transcript::Transcript; diff --git a/crates/aptos-dkg/src/pvss/insecure_field/transcript.rs b/crates/aptos-dkg/src/pvss/insecure_field/transcript.rs new file mode 100644 index 00000000000000..28da3261b2da22 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/insecure_field/transcript.rs @@ -0,0 +1,208 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::polynomials::shamir_secret_share, + pvss, + pvss::{ + das, encryption_dlog, traits, + traits::{transcript::MalleableTranscript, Convert, SecretSharingConfig}, + Player, ThresholdConfig, + }, + utils::{ + random::{insecure_random_g2_points, random_scalars}, + HasMultiExp, + }, +}; +use anyhow::bail; +use aptos_crypto::{bls12381, CryptoMaterialError, ValidCryptoMaterial}; +use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; +use blstrs::{G2Projective, Scalar}; +use rand::thread_rng; +use serde::{Deserialize, Serialize}; +use std::ops::Mul; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, BCSCryptoHash, CryptoHasher)] +#[allow(non_snake_case)] +pub struct Transcript { + dealers: Vec, + /// Public key shares from 0 to n-1, public key is in V[n] + V: Vec, + /// Secret key shares + C: Vec, +} + +impl ValidCryptoMaterial for Transcript { + fn to_bytes(&self) -> Vec { + bcs::to_bytes(&self).expect("unexpected error during PVSS transcript serialization") + } +} + +impl TryFrom<&[u8]> for Transcript { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> Result { + bcs::from_bytes::(bytes).map_err(|_| CryptoMaterialError::DeserializationError) + } +} + +impl Convert for pvss::input_secret::InputSecret { + fn to(&self, _with: &das::PublicParameters) -> Scalar { + *self.get_secret_a() + } +} + +impl traits::Transcript for Transcript { + type DealtPubKey = pvss::dealt_pub_key::g2::DealtPubKey; + type DealtPubKeyShare = pvss::dealt_pub_key_share::g2::DealtPubKeyShare; + type DealtSecretKey = Scalar; + type DealtSecretKeyShare = Scalar; + type DecryptPrivKey = encryption_dlog::g1::DecryptPrivKey; + type EncryptPubKey = encryption_dlog::g1::EncryptPubKey; + type InputSecret = pvss::input_secret::InputSecret; + type PublicParameters = das::PublicParameters; + type SecretSharingConfig = ThresholdConfig; + type SigningPubKey = bls12381::PublicKey; + type SigningSecretKey = bls12381::PrivateKey; + + fn scheme_name() -> String { + "insecure_field_pvss".to_string() + } + + #[allow(non_snake_case)] + fn deal( + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + _ssk: &Self::SigningSecretKey, + eks: &Vec, + s: &Self::InputSecret, + _aux: &A, + dealer: &Player, + rng: &mut R, + ) -> Self { + assert_eq!(eks.len(), sc.n); + + let (f, C) = shamir_secret_share(sc, s, rng); + + let g_2 = pp.get_commitment_base(); + + let V = (0..sc.n) + .map(|i| g_2.mul(C[i])) + .chain([g_2.mul(f[0])]) + .collect::>(); + + debug_assert_eq!(V.len(), sc.n + 1); + debug_assert_eq!(C.len(), sc.n); + + Transcript { + dealers: vec![*dealer], + V, + C, + } + } + + fn verify( + &self, + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + _spks: &Vec, + eks: &Vec, + _aux: &Vec, + ) -> anyhow::Result<()> { + if eks.len() != sc.n { + bail!("Expected {} encryption keys, but got {}", sc.n, eks.len()); + } + + if self.C.len() != sc.n { + bail!("Expected {} ciphertexts, but got {}", sc.n, self.C.len()); + } + + if self.V.len() != sc.n + 1 { + bail!( + "Expected {} (polynomial) commitment elements, but got {}", + sc.n + 1, + self.V.len() + ); + } + + let alphas = random_scalars(sc.n, &mut thread_rng()); + let g_2 = pp.get_commitment_base(); + + let lc_1 = g_2.mul( + self.C + .iter() + .zip(alphas.iter()) + .map(|(&c, &alpha)| c * alpha) + .sum::(), + ); + let lc_2 = G2Projective::multi_exp_iter(self.V.iter().take(sc.n), alphas.iter()); + + if lc_1 != lc_2 { + bail!("Expected linear combination check test to pass") + } + + return Ok(()); + } + + fn get_dealers(&self) -> Vec { + self.dealers.clone() + } + + fn aggregate_with(&mut self, sc: &Self::SecretSharingConfig, other: &Transcript) { + debug_assert_eq!(self.C.len(), sc.n); + debug_assert_eq!(self.V.len(), sc.n + 1); + + for i in 0..sc.n { + self.C[i] += other.C[i]; + self.V[i] += other.V[i]; + } + self.V[sc.n] += other.V[sc.n]; + self.dealers.extend_from_slice(other.dealers.as_slice()); + + debug_assert_eq!(self.C.len(), other.C.len()); + debug_assert_eq!(self.V.len(), other.V.len()); + } + + fn get_public_key_share( + &self, + _sc: &Self::SecretSharingConfig, + player: &Player, + ) -> Self::DealtPubKeyShare { + Self::DealtPubKeyShare::new(Self::DealtPubKey::new(self.V[player.id])) + } + + fn get_dealt_public_key(&self) -> Self::DealtPubKey { + Self::DealtPubKey::new(*self.V.last().unwrap()) + } + + fn decrypt_own_share( + &self, + sc: &Self::SecretSharingConfig, + player: &Player, + _dk: &Self::DecryptPrivKey, + ) -> (Self::DealtSecretKeyShare, Self::DealtPubKeyShare) { + (self.C[player.id], self.get_public_key_share(sc, player)) + } + + #[allow(non_snake_case)] + fn generate(sc: &Self::SecretSharingConfig, rng: &mut R) -> Self + where + R: rand_core::RngCore + rand_core::CryptoRng, + { + Transcript { + dealers: vec![sc.get_player(0)], + V: insecure_random_g2_points(sc.n + 1, rng), + C: random_scalars(sc.n, rng), + } + } +} + +impl MalleableTranscript for Transcript { + fn maul_signature( + &mut self, + _ssk: &Self::SigningSecretKey, + _aux: &A, + player: &Player, + ) { + self.dealers = vec![*player]; + } +} diff --git a/crates/aptos-dkg/src/pvss/low_degree_test.rs b/crates/aptos-dkg/src/pvss/low_degree_test.rs new file mode 100644 index 00000000000000..b2fb39929b6840 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/low_degree_test.rs @@ -0,0 +1,315 @@ +// Copyright © Aptos Foundation +/// Low-degree test from the SCRAPE paper that checks whether $n$ evaluations encode a degree $\le t-1$ +/// polynomial. +use crate::{ + algebra::{ + evaluation_domain::BatchEvaluationDomain, fft::fft_assign, + lagrange::all_lagrange_denominators, + }, + utils::{g1_multi_exp, g2_multi_exp, random::random_scalars}, +}; +use anyhow::{bail, Context}; +use blstrs::{G1Projective, G2Projective, Scalar}; +use ff::Field; +use group::Group; +use std::ops::Mul; + +/// A dual code word polynomial $f$ of degree $n-t-1$ for checking that the $n$ evaluations of another +/// polynomial (typically at the roots-of-unity $p(\omega^i)$, \forall \in [0, n)$) encode a degree +/// $\le t-1$ polynomial. +/// +/// When `includes_zero` is true, $n-1$ of the $n$ evaluations are at the roots of unity and the $n$th +/// evaluation is at zero. +pub struct LowDegreeTest<'a> { + /// Consider a degree-$(t-1)$ polynomial $p(X)$. Its "dual" polynomial $f(X)$ will be of degree + /// $n - t - 1$, and will have $n - t$ coefficients. + f: Vec, + includes_zero: bool, + t: usize, + n: usize, + batch_dom: &'a BatchEvaluationDomain, +} + +impl<'a> LowDegreeTest<'a> { + /// Creates a new LDT given a pre-generated random polynomial `f` of expected degree `n-t-1`. + pub fn new( + f: Vec, + t: usize, + n: usize, + includes_zero: bool, + batch_dom: &'a BatchEvaluationDomain, + ) -> anyhow::Result { + let min_size = if includes_zero { n - 1 } else { n }; + if batch_dom.N() < min_size { + bail!( + "expected batch evaluation domain size {} to be >= {}", + batch_dom.N(), + min_size + ); + } + + if t > n { + bail!("expected threshold {} to be <= {}", t, n) + } + + if f.len() != n - t { + bail!( + "random polynomial f degree is {}; expected degree n - t - 1 = {}", + f.len() - 1, + n - t - 1 + ) + } + + if f.is_empty() && t != n { + bail!("expected polynomial f to be non-empty when t != n"); + } + + Ok(Self { + f, + includes_zero, + t, + n, + batch_dom, + }) + } + + /// Creates a new LDT by picking a random polynomial `f` of expected degree `n-t-1`. + pub fn random( + mut rng: &mut R, + t: usize, + n: usize, + includes_zero: bool, + batch_dom: &'a BatchEvaluationDomain, + ) -> Self { + Self::new( + random_scalars(n - t, &mut rng), + t, + n, + includes_zero, + batch_dom, + ) + .unwrap() + } + + /// When `include_zero` is false, checks if the evaluations $p(\omega^i)$, \forall \in [0, n)$ stored + /// in `evals[i]` encode a degree $\le t-1$ polynomial. + /// + /// When `include_zero` is true, checks if the evaluations $p(0)$ in `evals[n-1]` and + /// $p(\omega^i)$ in `evals[i]` encode a degree $\le t-1$ polynomial (i.e., there are only $n-1$ + /// evaluations at the roots of unity). + pub fn low_degree_test(self, evals: &Vec) -> anyhow::Result<()> { + // This includes the extra evaluation at zero when `includes_zero` is true. + if evals.len() != self.n { + bail!("Expected {} evaluations; got {}", self.n, evals.len()); + } + + // println!( + // "\nscrape_low_degree_test> N: {}, t: {t}, n: {n}, include_zero: {includes_zero}", + // batch_dom.N() + // ); + + // In this case, $n$ evaluations will always encode a degree $\le n-1$ polynomial, so we + // return true. + if self.t == self.n { + return Ok(()); + } + + let v_times_f = self.dual_code_word(); + + // Let v_i be the coefficients returned by `all_lagrange_denominators` inside the + // `dual_code_word` call. + // + // When `includes_zero` is false, computes \sum_{i \in [0, n)} p(\omega^i) v_i f(\omega^i), which + // should be zero. + // When `includes_zero` is true, computes the same as above, but times an extra term v_n f(0). + debug_assert_eq!(evals.len(), v_times_f.len()); + let zero: Scalar = evals + .iter() + .zip(v_times_f.iter()) + .map(|(p, vf)| p.mul(vf)) + .sum(); + + (zero == Scalar::ZERO).then_some(()).context(format!( + "the LDT scalar inner product should return zero, but instead returned {}", + zero + )) + } + + /// Like `low_degree_test` but for `evals[i]` being $g^{p(\omega^i)} \in \mathbb{G}_1$. + pub fn low_degree_test_on_g1(self, evals: &Vec) -> anyhow::Result<()> { + if evals.len() != self.n { + bail!("Expected {} evaluations; got {}", self.n, evals.len()) + } + + if self.t == self.n { + return Ok(()); + } + + let v_times_f = self.dual_code_word(); + + debug_assert_eq!(evals.len(), v_times_f.len()); + let zero = g1_multi_exp(evals.as_ref(), v_times_f.as_slice()); + + (zero == G1Projective::identity()) + .then_some(()) + .context(format!( + "the LDT G1 multiexp should return zero, but instead returned {}", + zero + )) + } + + /// Like `low_degree_test` but for `evals[i]` being $g^{p(\omega^i)} \in \mathbb{G}_2$. + pub fn low_degree_test_on_g2(self, evals: &Vec) -> anyhow::Result<()> { + if evals.len() != self.n { + bail!("Expected {} evaluations; got {}", self.n, evals.len()) + } + + if self.t == self.n { + return Ok(()); + } + + let v_times_f = self.dual_code_word(); + + debug_assert_eq!(evals.len(), v_times_f.len()); + let zero = g2_multi_exp(evals.as_ref(), v_times_f.as_slice()); + + (zero == G2Projective::identity()) + .then_some(()) + .context(format!( + "the LDT G2 multiexp should return zero, but instead returned {}", + zero + )) + } + + /// Returns the dual code word for the SCRAPE low-degree test (as per Section 2.1 in [CD17e]) + /// on a polynomial of degree `deg` evaluated over either: + /// + /// - all $n$ roots of unity in `batch_dom`, if `include_zero` is false + /// - 0 and all $n-1$ roots of unity in `batch_dom`, if `include_zero` is true + /// + /// [CD17e] SCRAPE: Scalable Randomness Attested by Public Entities; by Ignacio Cascudo and + /// Bernardo David; in Cryptology ePrint Archive, Report 2017/216; 2017; + /// https://eprint.iacr.org/2017/216 + fn dual_code_word(self) -> Vec { + // println!("dual_code_word > t: {t}, n: {n}, includes_zero: {includes_zero}"); + + // Accounts for the size of `f` being the `n` evaluations of f(X) at the roots-of-unity and f(0) + // when `include_zero` is true. + let fft_size = if self.includes_zero { + self.n - 1 + } else { + self.n + }; + let f_0 = self.f[0]; + + // Compute $f(\omega^i)$ for all $i \in [0, n)$ + let dom = self.batch_dom.get_subdomain(fft_size); + let mut f_evals = self.f; + fft_assign(&mut f_evals, &dom); + f_evals.truncate(fft_size); + + let v = all_lagrange_denominators(&self.batch_dom, fft_size, self.includes_zero); + + // Append f(0), if `include_zero` is true + let mut extra = Vec::with_capacity(1); + if self.includes_zero { + extra.push(f_0); + } + + // println!( + // "|v| = {}, |f_evals| = {}, |extra| = {}", + // v.len(), + // f_evals.len(), + // extra.len() + // ); + + // Compute $v_i f(\omega^i), \forall i \in [0, n)$, and $v_n f(0)$ if `include_zero` is true. + debug_assert_eq!(f_evals.len() + extra.len(), v.len()); + f_evals + .iter() + .chain(extra.iter()) + .zip(v.iter()) + .map(|(v, f)| v.mul(f)) + .collect::>() + } +} + +#[cfg(test)] +mod test { + use crate::{ + algebra::{evaluation_domain::BatchEvaluationDomain, fft::fft_assign}, + pvss::{test_utils, LowDegreeTest, ThresholdConfig}, + utils::random::random_scalars, + }; + use blstrs::Scalar; + use rand::{prelude::ThreadRng, thread_rng}; + + #[test] + fn test_ldt_correctness() { + let mut rng = thread_rng(); + + for sc in test_utils::get_threshold_configs_for_testing() { + // A degree t-1 polynomial p(X) + let (p_0, batch_dom, mut evals) = random_polynomial_evals(&mut rng, &sc); + + // Test deg(p) < t, given evals at roots of unity + let ldt = LowDegreeTest::random(&mut rng, sc.t, sc.n, false, &batch_dom); + assert!(ldt.low_degree_test(&evals).is_ok()); + + if sc.t < sc.n { + // Test deg(p) < t + 1, given evals at roots of unity + let ldt = LowDegreeTest::random(&mut rng, sc.t + 1, sc.n, false, &batch_dom); + assert!(ldt.low_degree_test(&evals).is_ok()); + } + + // Test deg(p) < t, given evals at roots of unity and given p(0) + evals.push(p_0); + let ldt = LowDegreeTest::random(&mut rng, sc.t, sc.n + 1, true, &batch_dom); + assert!(ldt.low_degree_test(&evals).is_ok()); + } + } + + /// Test the soundness of the LDT: a polynomial of degree > t - 1 should not pass the check. + #[test] + fn test_ldt_soundness() { + let mut rng = thread_rng(); + + for t in 1..8 { + for n in (t + 1)..(3 * t + 1) { + let sc = ThresholdConfig::new(t, n).unwrap(); + let sc_higher_degree = ThresholdConfig::new(sc.t + 1, sc.n).unwrap(); + + // A degree t polynomial p(X), higher by 1 than what the LDT expects + let (p_0, batch_dom, mut evals) = + random_polynomial_evals(&mut rng, &sc_higher_degree); + + // Test deg(p) < t, given evals at roots of unity + // This should fail, since deg(p) = t + let ldt = LowDegreeTest::random(&mut rng, sc.t, sc.n, false, &batch_dom); + assert!(ldt.low_degree_test(&evals).is_err()); + + // Test deg(p) < t, given evals at roots of unity and given p(0) + // This should fail, since deg(p) = t + evals.push(p_0); + let ldt = LowDegreeTest::random(&mut rng, sc.t, sc.n + 1, true, &batch_dom); + assert!(ldt.low_degree_test(&evals).is_err()); + } + } + } + + fn random_polynomial_evals( + mut rng: &mut ThreadRng, + sc: &ThresholdConfig, + ) -> (Scalar, BatchEvaluationDomain, Vec) { + let p = random_scalars(sc.t, &mut rng); + let p_0 = p[0]; + let batch_dom = BatchEvaluationDomain::new(sc.n); + + // Compute p(\omega^i) for all i's + // (e.g., in SCRAPE we will be given A_i = g^{p(\omega^i)}) + let mut p_evals = p; + fft_assign(&mut p_evals, &batch_dom.get_subdomain(sc.n)); + p_evals.truncate(sc.n); + (p_0, batch_dom, p_evals) + } +} diff --git a/crates/aptos-dkg/src/pvss/mod.rs b/crates/aptos-dkg/src/pvss/mod.rs new file mode 100644 index 00000000000000..d31563f6e67bcb --- /dev/null +++ b/crates/aptos-dkg/src/pvss/mod.rs @@ -0,0 +1,26 @@ +// Copyright © Aptos Foundation + +mod contribution; +pub mod das; +pub(crate) mod dealt_pub_key; +pub(crate) mod dealt_pub_key_share; +pub mod dealt_secret_key; +pub(crate) mod dealt_secret_key_share; +pub mod encryption_dlog; +pub(crate) mod encryption_elgamal; +mod fiat_shamir; +pub mod input_secret; +pub mod insecure_field; +mod low_degree_test; +mod player; +pub mod scalar_secret_key; +mod schnorr; +pub mod test_utils; +mod threshold_config; +pub mod traits; +pub mod weighted; + +pub use low_degree_test::LowDegreeTest; +pub use player::Player; +pub use threshold_config::ThresholdConfig; +pub use weighted::{GenericWeighting, WeightedConfig}; diff --git a/crates/aptos-dkg/src/pvss/player.rs b/crates/aptos-dkg/src/pvss/player.rs new file mode 100644 index 00000000000000..d6ed9aa45633f2 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/player.rs @@ -0,0 +1,16 @@ +// Copyright © Aptos Foundation + +use serde::{Deserialize, Serialize}; + +/// An identifier from 0 to n-1 for the n players involved in the PVSS protocol. +#[derive(Copy, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct Player { + /// A number from 0 to n-1. + pub id: usize, +} + +impl Player { + pub fn get_id(&self) -> usize { + self.id + } +} diff --git a/crates/aptos-dkg/src/pvss/scalar_secret_key.rs b/crates/aptos-dkg/src/pvss/scalar_secret_key.rs new file mode 100644 index 00000000000000..f2c233af6e183f --- /dev/null +++ b/crates/aptos-dkg/src/pvss/scalar_secret_key.rs @@ -0,0 +1,40 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::lagrange::lagrange_coefficients, + pvss::{ + traits::{Reconstructable, SecretSharingConfig}, + Player, ThresholdConfig, + }, +}; +use blstrs::Scalar; +use ff::Field; +use more_asserts::{assert_ge, assert_le}; + +impl Reconstructable for Scalar { + type Share = Scalar; + + fn reconstruct(sc: &ThresholdConfig, shares: &Vec<(Player, Self::Share)>) -> Self { + assert_ge!(shares.len(), sc.get_threshold()); + assert_le!(shares.len(), sc.get_total_num_players()); + + let ids = shares.iter().map(|(p, _)| p.id).collect::>(); + let lagr = lagrange_coefficients( + sc.get_batch_evaluation_domain(), + ids.as_slice(), + &Scalar::ZERO, + ); + let shares = shares + .iter() + .map(|(_, share)| *share) + .collect::>(); + + assert_eq!(lagr.len(), shares.len()); + + shares + .iter() + .zip(lagr.iter()) + .map(|(&share, &lagr)| share * lagr) + .sum::() + } +} diff --git a/crates/aptos-dkg/src/pvss/schnorr.rs b/crates/aptos-dkg/src/pvss/schnorr.rs new file mode 100644 index 00000000000000..ff9d9b0320b076 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/schnorr.rs @@ -0,0 +1,108 @@ +// Copyright © Aptos Foundation + +use crate::utils::{hash_to_scalar, random::random_scalar, HasMultiExp}; +use anyhow::bail; +use aptos_crypto::signing_message; +use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; +use blstrs::Scalar; +use ff::Field; +use group::Group; +use serde::{Deserialize, Serialize}; +use std::ops::{Mul, Neg}; + +const SCHNORR_POK_DST: &[u8; 21] = b"APTOS_SCHNORR_POK_DST"; + +/// A Schnorr PoK for (g, g^a) is a tuple: +/// +/// $$(R = g^r, s = r + H(g^r, g^a, g) a)$$ +pub type PoK = (Gr, Scalar); + +/// This is the Schnorr prover transcript that is hashed to obtain a Fiat-Shamir challenge. +/// TODO(TechDebt): Cannot have references here because CryptoHasher doesn't work with lifetimes. +#[derive(Serialize, Deserialize, BCSCryptoHash, CryptoHasher)] +#[allow(non_snake_case)] +struct Challenge { + R: Gr, // g^r + pk: Gr, // g^a + g: Gr, +} + +#[allow(non_snake_case)] +pub fn pok_prove(a: &Scalar, g: &Gr, pk: &Gr, rng: &mut R) -> PoK +where + Gr: Serialize + Group + for<'a> Mul<&'a Scalar, Output = Gr>, + R: rand_core::RngCore + rand_core::CryptoRng, +{ + debug_assert!(g.mul(a).eq(pk)); + + let r = random_scalar(rng); + let R = g.mul(&r); + let e = schnorr_hash(Challenge:: { R, pk: *pk, g: *g }); + let s = r + e * a; + + (R, s) +} + +/// Computes the Fiat-Shamir challenge in the Schnorr PoK protocol given an instance $(g, pk = g^a)$ +/// and the commitment $R = g^r$. +#[allow(non_snake_case)] +fn schnorr_hash(c: Challenge) -> Scalar +where + Gr: Serialize, +{ + let c = signing_message(&c) + .expect("unexpected error during Schnorr challenge struct serialization"); + + hash_to_scalar(&c, SCHNORR_POK_DST) +} + +/// Verifies all the $n$ Schnorr PoKs by taking a random linear combination of the verification +/// equations using $(1, \alpha, \alpha^2, \ldots, \alpha^{n-1})$ as the randomness. +/// +/// The equation is: +/// +/// $$g^{\sum_i s_i \gamma_i} = \prod_i R_i^{\gamma_i} \pk_i^{e_i \gamma_i}$$ +/// +/// where $e_i$ is the Fiat-Shamir challenge derived by hashing the PK and the generator $g$. +#[allow(non_snake_case)] +pub fn pok_batch_verify<'a, Gr>( + poks: &Vec<(Gr, PoK)>, + g: &Gr, + gamma: &Scalar, +) -> anyhow::Result<()> +where + Gr: Serialize + Group + Mul<&'a Scalar> + HasMultiExp, +{ + let n = poks.len(); + let mut exps = Vec::with_capacity(2 * n + 1); + let mut bases = Vec::with_capacity(2 * n + 1); + + // Compute \gamma_i = \gamma^i, for all i \in [0, n] + let mut gammas = Vec::with_capacity(n); + gammas.push(Scalar::ONE); + for _ in 0..(n - 1) { + gammas.push(gammas.last().unwrap().mul(gamma)); + } + + let mut last_exp = Scalar::ZERO; + for i in 0..n { + let (pk, (R, s)) = poks[i]; + + bases.push(R); + exps.push(gammas[i]); + + bases.push(pk); + exps.push(schnorr_hash(Challenge:: { R, pk, g: *g }) * gammas[i]); + + last_exp += s * gammas[i]; + } + + bases.push(*g); + exps.push(last_exp.neg()); + + if Gr::multi_exp_iter(bases.iter(), exps.iter()) != Gr::identity() { + bail!("Schnorr PoK batch verification failed"); + } + + Ok(()) +} diff --git a/crates/aptos-dkg/src/pvss/test_utils.rs b/crates/aptos-dkg/src/pvss/test_utils.rs new file mode 100644 index 00000000000000..5072a76482fb04 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/test_utils.rs @@ -0,0 +1,212 @@ +// Copyright © Aptos Foundation + +use crate::pvss::{ + traits::{transcript::Transcript, Convert, HasEncryptionPublicParams, SecretSharingConfig}, + Player, ThresholdConfig, WeightedConfig, +}; +use aptos_crypto::{hash::CryptoHash, SigningKey, Uniform}; +use num_traits::Zero; +use rand::{prelude::ThreadRng, thread_rng}; +use serde::Serialize; +use std::ops::AddAssign; + +/// Type used to indicate that dealears are not including any auxiliary data in their PVSS transcript +/// signatures. +#[derive(Clone, Serialize)] +pub struct NoAux; + +/// Helper function that, given a sharing configuration for `n` players, returns an a tuple of: +/// - public parameters +/// - a vector of `n` signing SKs +/// - a vector of `n` signing PKs +/// - a vector of `n` decryption SKs +/// - a vector of `n` encryption PKs +/// - a vector of `n` input secrets, denoted by `iss` +/// - the aggregated dealt secret key from `\sum_i iss[i]` +/// Useful in tests and benchmarks when wanting to quickly deal & verify a transcript. +pub fn setup_dealing( + sc: &T::SecretSharingConfig, + mut rng: &mut R, +) -> ( + T::PublicParameters, + Vec, + Vec, + Vec, + Vec, + Vec, + T::InputSecret, + T::DealtSecretKey, +) { + println!( + "Setting up dealing for {} PVSS, with {}", + T::scheme_name(), + sc + ); + + let pp = T::PublicParameters::default(); + + let ssks = (0..sc.get_total_num_players()) + .map(|_| T::SigningSecretKey::generate(&mut rng)) + .collect::>(); + let spks = ssks + .iter() + .map(|ssk| ssk.verifying_key()) + .collect::>(); + + let dks = (0..sc.get_total_num_players()) + .map(|_| T::DecryptPrivKey::generate(&mut rng)) + .collect::>(); + let eks = dks + .iter() + .map(|dk| dk.to(&pp.get_encryption_public_params())) + .collect(); + + // println!(); + // println!("DKs: {:?}", dks); + // println!("EKs: {:?}", eks); + + let iss = (0..sc.get_total_num_players()) + .map(|_| T::InputSecret::generate(&mut rng)) + .collect::>(); + + let mut s = T::InputSecret::zero(); + for is in &iss { + s.add_assign(is) + } + let sk: ::DealtSecretKey = s.to(&pp); + // println!("Dealt SK: {:?}", sk); + + (pp, ssks, spks, dks, eks, iss, s, sk) +} + +/// Useful for printing types of variables without too much hassle. +pub fn print_type_of(_: &T) { + println!("{}", std::any::type_name::()) +} + +pub fn get_threshold_config_and_rng(t: usize, n: usize) -> (ThresholdConfig, ThreadRng) { + let sc = ThresholdConfig::new(t, n).unwrap(); + + (sc, thread_rng()) +} + +#[allow(unused)] +macro_rules! vec_to_str { + ($vec:ident) => { + $vec.iter() + .map(|e| format!("{}", e)) + .collect::>() + .join(", ") + }; +} + +use crate::pvss::traits::Reconstructable; +#[allow(unused)] +pub(crate) use vec_to_str; + +pub fn get_threshold_configs_for_testing() -> Vec { + let mut tcs = vec![]; + + for t in 1..8 { + for n in t..8 { + let tc = ThresholdConfig::new(t, n).unwrap(); + tcs.push(tc) + } + } + + tcs +} + +pub fn get_weighted_configs_for_testing() -> Vec { + let mut wcs = vec![]; + + // 1-out-of-1 weighted + wcs.push(WeightedConfig::new(1, vec![1]).unwrap()); + + // 1-out-of-2, weights 2 0 + wcs.push(WeightedConfig::new(1, vec![2]).unwrap()); + // 1-out-of-2, weights 1 1 + wcs.push(WeightedConfig::new(1, vec![1, 1]).unwrap()); + // 2-out-of-2, weights 1 1 + wcs.push(WeightedConfig::new(2, vec![1, 1]).unwrap()); + + // 1-out-of-3, weights 1 1 1 + wcs.push(WeightedConfig::new(1, vec![1, 1, 1]).unwrap()); + // 2-out-of-3, weights 1 1 1 + wcs.push(WeightedConfig::new(2, vec![1, 1, 1]).unwrap()); + // 3-out-of-3, weights 1 1 1 + wcs.push(WeightedConfig::new(3, vec![1, 1, 1]).unwrap()); + + // 3-out-of-5, weights 2 1 2 + wcs.push(WeightedConfig::new(3, vec![2, 1, 2]).unwrap()); + + // 3-out-of-7, weights 2 3 2 + wcs.push(WeightedConfig::new(3, vec![2, 3, 2]).unwrap()); + + // 50-out-of-100, weights [11, 13, 9, 10, 12, 8, 7, 14, 10, 6] + wcs.push(WeightedConfig::new(50, vec![11, 13, 9, 10, 12, 8, 7, 14, 10, 6]).unwrap()); + + wcs +} + +pub fn get_threshold_configs_for_benchmarking() -> Vec { + vec![ + ThresholdConfig::new(333, 1_000).unwrap(), + ThresholdConfig::new(666, 1_000).unwrap(), + ThresholdConfig::new(3_333, 10_000).unwrap(), + ThresholdConfig::new(6_666, 10_000).unwrap(), + ] +} + +pub fn get_weighted_configs_for_benchmarking() -> Vec { + let mut wcs = vec![]; + + // Total weight is 9230 + let weights = vec![ + 17, 17, 11, 11, 11, 74, 40, 11, 11, 11, 11, 11, 218, 218, 218, 218, 218, 218, 218, 170, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 18, 11, 11, 11, 192, 218, 11, 11, 52, 11, + 161, 24, 11, 11, 11, 11, 218, 218, 161, 175, 80, 13, 103, 11, 11, 11, 11, 40, 40, 40, 14, + 218, 218, 11, 218, 11, 11, 218, 11, 218, 71, 55, 218, 184, 170, 11, 218, 218, 164, 177, + 171, 18, 209, 11, 20, 12, 147, 18, 169, 13, 35, 208, 13, 218, 218, 218, 218, 218, 218, 163, + 73, 26, + ]; + wcs.push(WeightedConfig::new(3087, weights.clone()).unwrap()); + wcs.push(WeightedConfig::new(6162, weights).unwrap()); + + // Total weight is 850 + let weights = vec![ + 2, 2, 1, 1, 1, 7, 4, 1, 1, 1, 1, 1, 20, 20, 20, 20, 20, 20, 20, 16, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 1, 18, 20, 1, 1, 5, 1, 15, 2, 1, 1, 1, 1, 20, 20, 15, 16, 7, 1, 9, + 1, 1, 1, 1, 4, 4, 4, 1, 20, 20, 1, 20, 1, 1, 20, 1, 20, 7, 5, 20, 17, 16, 1, 20, 20, 15, + 16, 16, 2, 19, 1, 2, 1, 13, 2, 16, 1, 3, 19, 1, 20, 20, 20, 20, 20, 20, 15, 7, 2, + ]; + wcs.push(WeightedConfig::new(290, weights.clone()).unwrap()); + wcs.push(WeightedConfig::new(573, weights).unwrap()); + + wcs +} + +pub fn reconstruct_dealt_secret_key_randomly( + sc: &::SecretSharingConfig, + rng: &mut R, + dks: &Vec<::DecryptPrivKey>, + trx: T, +) -> ::DealtSecretKey +where + R: rand_core::RngCore, +{ + // Test reconstruction from t random shares + let players_and_shares = sc + .get_random_eligible_subset_of_players(rng) + .into_iter() + .map(|p| { + let (sk, pk) = trx.decrypt_own_share(sc, &p, &dks[p.get_id()]); + + assert_eq!(pk, trx.get_public_key_share(sc, &p)); + + (p, sk) + }) + .collect::>(); + + T::DealtSecretKey::reconstruct(sc, &players_and_shares) +} diff --git a/crates/aptos-dkg/src/pvss/threshold_config.rs b/crates/aptos-dkg/src/pvss/threshold_config.rs new file mode 100644 index 00000000000000..be36572119ebab --- /dev/null +++ b/crates/aptos-dkg/src/pvss/threshold_config.rs @@ -0,0 +1,125 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::evaluation_domain::{BatchEvaluationDomain, EvaluationDomain}, + pvss::{traits, Player}, +}; +use anyhow::anyhow; +use rand::{seq::IteratorRandom, Rng}; +use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +/// Encodes the *threshold configuration* for a normal/unweighted PVSS: i.e., the threshold $t$ and +/// the number of players $n$ such that any $t$ or more players can reconstruct a dealt secret given +/// a PVSS transcript. this is Alin leaving his laptop open again... +#[derive(Clone, PartialEq, Deserialize, Serialize, Debug, Eq)] +pub struct ThresholdConfig { + /// The reconstruction threshold $t$ that must be exceeded in order to reconstruct the dealt + /// secret; i.e., $t$ or more shares are needed + pub(crate) t: usize, + /// The total number of players involved in the PVSS protocol + pub(crate) n: usize, + /// Evaluation domain consisting of the $N$th root of unity and other auxiliary information + /// needed to compute an FFT of size $N$. + dom: EvaluationDomain, + /// Batch evaluation domain, consisting of all the $N$th roots of unity (in the scalar field), + /// where N is the smallest power of two such that n <= N. + batch_dom: BatchEvaluationDomain, +} + +impl ThresholdConfig { + /// Creates a new $t$ out of $n$ secret sharing configuration where any subset of $t$ or more + /// players can reconstruct the secret. + pub fn new(t: usize, n: usize) -> anyhow::Result { + if t == 0 { + return Err(anyhow!("expected the reconstruction threshold to be > 0")); + } + + if n == 0 { + return Err(anyhow!("expected the number of shares to be > 0")); + } + + if t > n { + return Err(anyhow!( + "expected the reconstruction threshold {t} to be < than the number of shares {n}" + )); + } + + let batch_dom = BatchEvaluationDomain::new(n); + let dom = batch_dom.get_subdomain(n); + Ok(ThresholdConfig { + t, + n, + dom, + batch_dom, + }) + } + + /// Returns the threshold $t$. Recall that $\ge t$ shares are needed to reconstruct. + pub fn get_threshold(&self) -> usize { + self.t + } + + pub fn get_batch_evaluation_domain(&self) -> &BatchEvaluationDomain { + &self.batch_dom + } + + pub fn get_evaluation_domain(&self) -> &EvaluationDomain { + &self.dom + } +} + +impl Display for ThresholdConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "threshold/{}-out-of-{}", self.t, self.n) + } +} + +impl traits::SecretSharingConfig for ThresholdConfig { + /// For testing only. + fn get_random_player(&self, rng: &mut R) -> Player + where + R: RngCore + CryptoRng, + { + Player { + id: rng.gen_range(0, self.n), + } + } + + /// For testing only. + fn get_random_eligible_subset_of_players(&self, mut rng: &mut R) -> Vec + where + R: RngCore, + { + (0..self.get_total_num_shares()) + .choose_multiple(&mut rng, self.t) + .into_iter() + .map(|i| self.get_player(i)) + .collect::>() + } + + fn get_total_num_players(&self) -> usize { + self.n + } + + fn get_total_num_shares(&self) -> usize { + self.n + } +} + +#[cfg(test)] +mod test { + use crate::pvss::ThresholdConfig; + + #[test] + fn create_many_configs() { + let mut _tcs = vec![]; + + for t in 1..100 { + for n in t..100 { + _tcs.push(ThresholdConfig::new(t, n).unwrap()) + } + } + } +} diff --git a/crates/aptos-dkg/src/pvss/traits/mod.rs b/crates/aptos-dkg/src/pvss/traits/mod.rs new file mode 100644 index 00000000000000..1ab9b541d457f8 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/traits/mod.rs @@ -0,0 +1,52 @@ +// Copyright © Aptos Foundation + +pub mod transcript; + +use crate::pvss::player::Player; +use more_asserts::assert_lt; +use std::fmt::Display; +pub use transcript::Transcript; + +/// Converts a type `Self` to `ToType` using auxiliary data from type `AuxType`. +pub trait Convert { + fn to(&self, with: &AuxType) -> ToType; +} + +/// All PVSS public parameters must give access to the encryption public params. +pub trait HasEncryptionPublicParams { + type EncryptionPublicParameters; + + fn get_encryption_public_params(&self) -> &Self::EncryptionPublicParameters; +} + +pub trait SecretSharingConfig: Display { + /// Creates a new player ID; a number from 0 to `n-1`, where `n = get_total_num_players(&self)`. + fn get_player(&self, i: usize) -> Player { + let n = self.get_total_num_players(); + assert_lt!(i, n); + + Player { id: i } + } + + /// Useful during testing. + fn get_random_player(&self, rng: &mut R) -> Player + where + R: rand_core::RngCore + rand_core::CryptoRng; + + /// Returns a random subset of players who are capable of reconstructing the secret. + /// Useful during testing. + fn get_random_eligible_subset_of_players(&self, rng: &mut R) -> Vec + where + R: rand_core::RngCore; + + fn get_total_num_players(&self) -> usize; + + fn get_total_num_shares(&self) -> usize; +} + +/// All dealt secret keys should be reconstructable from a subset of \[dealt secret key\] shares. +pub trait Reconstructable { + type Share: Clone; + + fn reconstruct(sc: &SSC, shares: &Vec<(Player, Self::Share)>) -> Self; +} diff --git a/crates/aptos-dkg/src/pvss/traits/transcript.rs b/crates/aptos-dkg/src/pvss/traits/transcript.rs new file mode 100644 index 00000000000000..0481322636afb2 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/traits/transcript.rs @@ -0,0 +1,213 @@ +// Copyright © Aptos Foundation + +//! # Traits for authenticated PVSS transcripts +//! +//! ## `InputSecret`, `DealtSecretKey` and `DealtPublicKey` +//! +//! The PVSS dealer picks a uniform *input secret* (`InputSecret`), inputs it into the PVSS dealing +//! algorithm, which deals a *dealt secret key* (`DealtSecretKey`) such that any $t$ or more subset +//! of the $n$ players can reconstruct this *dealt secret key*. Furthermore, the dealing algorithm +//! outputs to every player the *dealt public key* (`DealtPublicKey`) associated with this secret-shared +//! *dealt secret key*! +//! +//! In some PVSS protocols, the *dealt secret key* (e.g., $h_1^a\in G_1$) is a one-way function of the +//! *input secret* $a\in F$. As a result, such protocols only allows for the reconstruction of the +//! *dealt secret*, while the *input secret* cannot be reconstructed efficiently (in polynomial time). +//! +//! ## `EncryptPubKey` and `DecryptPrivKey` traits +//! +//! In a PVSS protocol, the PVSS transcript typically *encrypts* for each player their *dealt secret +//! keys share*. As a result, each player must pick an encryption key-pair: a private *decryption +//! key* (`DecryptPrivKey`) and its associated public *encryption key* (`EncryptPubKey`). +//! The dealer is assumed to have received all player's public encryption keys. This way, the dealer +//! can encrypt the shares for each player in the transcript. +//! +//! ## `DealtSecretShare` and `DealtPubKeyShare` +//! +//! The dealing algorithm outputs a *transcript* which encrypts, for each player $i$, its *share* of +//! the *dealt secret key*. We refer to this as a *dealt secret key share* (`DealtSecretKeyShare`) for +//! player $i$. Furthermore, the transcript also exposes an associated *dealt public key share* +//! (`DealtPubKeyShare`) for each *dealt secret key share*, which will be useful for efficiently +//! implementing threshold verifiable random functions. +//! +//! ## `SigningSecretKey` and `SigningPubKey` +//! +//! When using the PVSS protocol to build a $t$-out-of-$n$ distributed key generation (DKG) protocol, +//! it is necessary for each DKG player to sign their PVSS transcript so as to authenticate that +//! they contributed to the final DKG secret. +//! +//! To prevent replay of signed PVSS transcripts inside higher-level protocols, the PVSS dealer can +//! include some auxiliary data to compute the signature over too. +//! +//! ## A note on `aptos-crypto` traits +//! +//! We do not implement the `PublicKey` and `PrivateKey` traits from `aptos-crypto` for our PVSS +//! `DealtSecretKey[Share]` and `DealtPublicKey[Share]` structs because those traits (wrongly) assume +//! that one can always derive a public key from a secret key, which in our PVSS construction's case +//! does not hold. + +use crate::pvss::{ + traits::{Convert, HasEncryptionPublicParams, Reconstructable, SecretSharingConfig}, + Player, +}; +use anyhow::bail; +use aptos_crypto::{SigningKey, Uniform, ValidCryptoMaterial, VerifyingKey}; +use num_traits::Zero; +use serde::{de::DeserializeOwned, Serialize}; +use std::{fmt::Debug, ops::AddAssign}; + +/// A trait for a PVSS protocol. This trait allows both for: +/// +/// 1. Normal/unweighted $t$-out-of-$n$ PVSS protocols where any $t$ players (or more) can +/// reconstruct the secret (but no fewer can) +/// 2. Weighted $w$-out-of-$W$ PVSS protocols where any players with combined weight $\ge w$ can +/// reconstruct the secret (but players with combined weight $< w$ cannot) +pub trait Transcript: Debug + ValidCryptoMaterial + Clone + PartialEq + Eq { + type SecretSharingConfig: SecretSharingConfig + + DeserializeOwned + + Serialize + + Debug + + PartialEq + + Eq; + + type PublicParameters: HasEncryptionPublicParams + + Default + + ValidCryptoMaterial + + DeserializeOwned + + Serialize + + Debug + + PartialEq + + Eq; + + type SigningSecretKey: Uniform + SigningKey; + type SigningPubKey: VerifyingKey; + + type DealtSecretKeyShare: PartialEq + Clone; + type DealtPubKeyShare: Debug + PartialEq + Clone; + type DealtSecretKey: PartialEq + + Reconstructable; + type DealtPubKey; + + type InputSecret: Uniform + + Zero + + for<'a> AddAssign<&'a Self::InputSecret> + + Convert + + Convert; + + type EncryptPubKey: Debug + + Clone + + ValidCryptoMaterial + + DeserializeOwned + + Serialize + + PartialEq + + Eq; + type DecryptPrivKey: Uniform + + Convert< + Self::EncryptPubKey, + ::EncryptionPublicParameters, + >; + + /// Return a developer-friendly name of the PVSS scheme (e.g., "vanilla_scrape") that can be + /// used in, say, criterion benchmark names. + fn scheme_name() -> String; + + /// Deals the *input secret* $s$ by creating a PVSS transcript which encrypts shares of $s$ for + /// all PVSS players. Signs the transcript with `ssk`. + /// + /// The dealer will sign the transcript (or part of it; typically just a commitment to the dealt + /// secret) together with his player ID in `dealer` and the auxiliary data in `aux` (which might + /// be needed for the security of higher-level protocols; e.g., replay protection). + fn deal( + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + ssk: &Self::SigningSecretKey, + eks: &Vec, + s: &Self::InputSecret, + aux: &A, + dealer: &Player, + rng: &mut R, + ) -> Self; + + /// Verifies the validity of the PVSS transcript: i.e., the transcripts correctly encrypts shares + /// of an `InputSecret` $s$ which has been $(t, n)$ secret-shared such that only $\ge t$ players + /// can reconstruct it as a `DealtSecret`. + /// + /// Additionally, verifies that the transcript was indeed aggregated from a set of players + /// identified by the public keys in `spks`, by verifying each player $i$'s signature on the + /// transcript and on `aux[i]`. + fn verify( + &self, + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + spks: &Vec, + eks: &Vec, + aux: &Vec, + ) -> anyhow::Result<()>; + + /// Returns the set of player IDs who have contributed to this transcript. + /// In other words, the transcript could have been dealt by one player, in which case + /// the set is of size 1, or the transcript could have been obtained by aggregating `n` + /// other transcripts, in which case the set will be of size `n`. + fn get_dealers(&self) -> Vec; + + /// Aggregates two transcripts. + fn aggregate_with(&mut self, sc: &Self::SecretSharingConfig, other: &Self); + + /// Helper function for aggregating a vector of transcripts + fn aggregate(sc: &Self::SecretSharingConfig, mut trxs: Vec) -> anyhow::Result { + if trxs.is_empty() { + bail!("Cannot aggregate empty vector of transcripts") + } + + let n = trxs.len(); + let (first, last) = trxs.split_at_mut(1); + + for other in last { + first[0].aggregate_with(sc, other); + } + + trxs.truncate(1); + let trx = trxs.pop().unwrap(); + assert_eq!(trx.get_dealers().len(), n); + Ok(trx) + } + + /// Returns the dealt pubkey shore of `player`. + fn get_public_key_share( + &self, + sc: &Self::SecretSharingConfig, + player: &Player, + ) -> Self::DealtPubKeyShare; + + /// Given a valid transcript, returns the `DealtPublicKey` of that transcript: i.e., the public + /// key associated with the secret key dealt in the transcript. + fn get_dealt_public_key(&self) -> Self::DealtPubKey; + + /// Given a valid transcript, returns the decrypted `DealtSecretShare` for the player with ID + /// `player_id`. + fn decrypt_own_share( + &self, + sc: &Self::SecretSharingConfig, + player: &Player, + dk: &Self::DecryptPrivKey, + ) -> (Self::DealtSecretKeyShare, Self::DealtPubKeyShare); + + /// Generates a random looking transcript (but not a valid one). + /// Useful for testing and benchmarking. + fn generate(sc: &Self::SecretSharingConfig, rng: &mut R) -> Self + where + R: rand_core::RngCore + rand_core::CryptoRng; +} + +/// This traits defines testing-only and benchmarking-only interfaces. +pub trait MalleableTranscript: Transcript { + /// This is useful for generating many PVSS transcripts from different dealers from a single + /// PVSS transcript by recomputing its signature. It is used to deal quickly when benchmarking + /// aggregated PVSS transcript verification + fn maul_signature( + &mut self, + ssk: &Self::SigningSecretKey, + aux: &A, + dealer: &Player, + ); +} diff --git a/crates/aptos-dkg/src/pvss/weighted/generic_weighting.rs b/crates/aptos-dkg/src/pvss/weighted/generic_weighting.rs new file mode 100644 index 00000000000000..e263ced603c9e0 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/weighted/generic_weighting.rs @@ -0,0 +1,233 @@ +// Copyright © Aptos Foundation +/// A generic transformation from an unweighted PVSS to a weighted PVSS. +/// +/// WARNING: This will **NOT** necessarily be secure for any PVSS scheme, since it will reuse encryption +/// keys, which might not be safe depending on the PVSS scheme. +use crate::pvss::{ + traits::{transcript::MalleableTranscript, Reconstructable, SecretSharingConfig, Transcript}, + Player, ThresholdConfig, WeightedConfig, +}; +use aptos_crypto::{CryptoMaterialError, ValidCryptoMaterial}; +use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; +use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, CryptoHasher, BCSCryptoHash)] +/// A weighting wrapper around a `Transcript` type `T`. Given an implementation of an [unweighted +/// PVSS] `Transcript` for `T`, this wrapper can be used to easily obtain a *weighted* PVSS abiding +/// by the same `Transcript` trait. +pub struct GenericWeighting { + trx: T, +} + +/// Implements weighted reconstruction of a secret `SK` through the existing unweighted reconstruction +/// implementation of `SK`. +impl> Reconstructable for SK { + type Share = Vec; + + fn reconstruct(sc: &WeightedConfig, shares: &Vec<(Player, Self::Share)>) -> Self { + let mut flattened_shares = Vec::with_capacity(sc.get_total_weight()); + + // println!(); + for (player, sub_shares) in shares { + // println!( + // "Flattening {} share(s) for player {player}", + // sub_shares.len() + // ); + for (pos, share) in sub_shares.iter().enumerate() { + let virtual_player = sc.get_virtual_player(player, pos); + + // println!( + // " + Adding share {pos} as virtual player {virtual_player}: {:?}", + // share + // ); + // TODO(Performance): Avoiding the cloning here might be nice + let tuple = (virtual_player, share.clone()); + flattened_shares.push(tuple); + } + } + + SK::reconstruct(sc.get_threshold_config(), &flattened_shares) + } +} + +impl ValidCryptoMaterial for GenericWeighting { + fn to_bytes(&self) -> Vec { + self.trx.to_bytes() + } +} + +impl TryFrom<&[u8]> for GenericWeighting { + type Error = CryptoMaterialError; + + fn try_from(bytes: &[u8]) -> Result { + T::try_from(bytes).map(|trx| Self { trx }) + } +} + +impl GenericWeighting { + fn to_weighted_encryption_keys( + sc: &WeightedConfig, + eks: &Vec, + ) -> Vec { + // Re-organize the encryption key vector so that we deal multiple shares to each player, + // proportional to their weight. + let mut duplicated_eks = Vec::with_capacity(sc.get_total_weight()); + + for (player_id, ek) in eks.iter().enumerate() { + let player = sc.get_player(player_id); + let num_shares = sc.get_player_weight(&player); + for _ in 0..num_shares { + duplicated_eks.push(ek.clone()); + } + } + + duplicated_eks + } +} + +impl> Transcript for GenericWeighting { + type DealtPubKey = T::DealtPubKey; + type DealtPubKeyShare = Vec; + type DealtSecretKey = T::DealtSecretKey; + /// In a weighted PVSS, an SK share is represented as a vector of SK shares in the unweighted + /// PVSS, whose size is proportional to the weight of the owning player. + type DealtSecretKeyShare = Vec; + type DecryptPrivKey = T::DecryptPrivKey; + type EncryptPubKey = T::EncryptPubKey; + type InputSecret = T::InputSecret; + type PublicParameters = T::PublicParameters; + type SecretSharingConfig = WeightedConfig; + type SigningPubKey = T::SigningPubKey; + type SigningSecretKey = T::SigningSecretKey; + + fn scheme_name() -> String { + format!("generic_weighted_{}", T::scheme_name()) + } + + fn deal( + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + ssk: &Self::SigningSecretKey, + eks: &Vec, + s: &Self::InputSecret, + aux: &A, + dealer: &Player, + rng: &mut R, + ) -> Self { + // WARNING: This duplication of encryption keys will NOT be secure in some PVSS schemes. + let duplicated_eks = GenericWeighting::::to_weighted_encryption_keys(sc, eks); + + GenericWeighting { + trx: T::deal( + sc.get_threshold_config(), + pp, + ssk, + &duplicated_eks, + s, + aux, + dealer, + rng, + ), + } + } + + fn verify( + &self, + sc: &Self::SecretSharingConfig, + pp: &Self::PublicParameters, + spk: &Vec, + eks: &Vec, + aux: &Vec, + ) -> anyhow::Result<()> { + let duplicated_eks = GenericWeighting::::to_weighted_encryption_keys(sc, eks); + + T::verify( + &self.trx, + sc.get_threshold_config(), + pp, + spk, + &duplicated_eks, + aux, + ) + } + + fn get_dealers(&self) -> Vec { + T::get_dealers(&self.trx) + } + + fn aggregate_with(&mut self, sc: &Self::SecretSharingConfig, other: &Self) { + T::aggregate_with(&mut self.trx, sc.get_threshold_config(), &other.trx) + } + + fn get_public_key_share( + &self, + sc: &Self::SecretSharingConfig, + player: &Player, + ) -> Self::DealtPubKeyShare { + let weight = sc.get_player_weight(player); + + let mut dpk_share = Vec::with_capacity(weight); + + for i in 0..weight { + // println!("Decrypting share {i} for player {player} with DK {:?}", dk); + let virtual_player = sc.get_virtual_player(player, i); + dpk_share.push(T::get_public_key_share( + &self.trx, + sc.get_threshold_config(), + &virtual_player, + )); + } + + dpk_share + } + + fn get_dealt_public_key(&self) -> Self::DealtPubKey { + T::get_dealt_public_key(&self.trx) + } + + fn decrypt_own_share( + &self, + sc: &Self::SecretSharingConfig, + player: &Player, + dk: &Self::DecryptPrivKey, + ) -> (Self::DealtSecretKeyShare, Self::DealtPubKeyShare) { + let weight = sc.get_player_weight(player); + + let mut weighted_dsk_share = Vec::with_capacity(weight); + let mut weighted_dpk_share = Vec::with_capacity(weight); + + for i in 0..weight { + // println!("Decrypting share {i} for player {player} with DK {:?}", dk); + let virtual_player = sc.get_virtual_player(player, i); + let (dsk_share, dpk_share) = + T::decrypt_own_share(&self.trx, sc.get_threshold_config(), &virtual_player, dk); + weighted_dsk_share.push(dsk_share); + weighted_dpk_share.push(dpk_share); + } + + (weighted_dsk_share, weighted_dpk_share) + } + + fn generate(sc: &Self::SecretSharingConfig, rng: &mut R) -> Self + where + R: RngCore + CryptoRng, + { + GenericWeighting { + trx: T::generate(sc.get_threshold_config(), rng), + } + } +} + +impl> MalleableTranscript + for GenericWeighting +{ + fn maul_signature( + &mut self, + ssk: &Self::SigningSecretKey, + aux: &A, + dealer: &Player, + ) { + ::maul_signature(&mut self.trx, ssk, aux, dealer); + } +} diff --git a/crates/aptos-dkg/src/pvss/weighted/mod.rs b/crates/aptos-dkg/src/pvss/weighted/mod.rs new file mode 100644 index 00000000000000..f2c898ff7da434 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/weighted/mod.rs @@ -0,0 +1,7 @@ +// Copyright © Aptos Foundation + +pub mod generic_weighting; +mod weighted_config; + +pub use generic_weighting::GenericWeighting; +pub use weighted_config::WeightedConfig; diff --git a/crates/aptos-dkg/src/pvss/weighted/weighted_config.rs b/crates/aptos-dkg/src/pvss/weighted/weighted_config.rs new file mode 100644 index 00000000000000..845bf65dbe6cd1 --- /dev/null +++ b/crates/aptos-dkg/src/pvss/weighted/weighted_config.rs @@ -0,0 +1,304 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::evaluation_domain::{BatchEvaluationDomain, EvaluationDomain}, + pvss::{traits, traits::SecretSharingConfig, Player, ThresholdConfig}, +}; +use anyhow::anyhow; +use more_asserts::assert_lt; +use rand::Rng; +use rand_core::{CryptoRng, RngCore}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +/// Encodes the *threshold configuration* for a *weighted* PVSS: i.e., the minimum weight $w$ and +/// the total weight $W$ such that any subset of players with weight $\ge w$ can reconstruct a +/// dealt secret given a PVSS transcript. +#[allow(non_snake_case)] +#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)] +pub struct WeightedConfig { + /// A weighted config is a $w$-out-of-$W$ threshold config, where $w$ is the minimum weight + /// needed to reconstruct the secret and $W$ is the total weight. + tc: ThresholdConfig, + /// The total number of players in the protocol. + num_players: usize, + /// Each player's weight + weight: Vec, + /// Player's starting index `a` in a vector of all `W` shares, such that this player owns shares + /// `W[a, a + weight[player])`. Useful during weighted secret reconstruction. + starting_index: Vec, + /// The maximum weight of any player. + max_player_weight: usize, +} + +impl WeightedConfig { + #[allow(non_snake_case)] + /// Initializes a weighted secret sharing configuration with threshold weight `threshold_weight` + /// and the $i$th player's weight stored in `weight[i]`. + pub fn new(threshold_weight: usize, weights: Vec) -> anyhow::Result { + if threshold_weight == 0 { + return Err(anyhow!( + "expected the minimum reconstruction weight to be > 0" + )); + } + + if weights.is_empty() { + return Err(anyhow!("expected a non-empty vector of player weights")); + } + let max_player_weight = *weights.iter().max().unwrap(); + + for (idx, w) in weights.iter().enumerate() { + if *w == 0 { + return Err(anyhow!("expected player at index {idx} to have weight > 0")); + } + } + + let n = weights.len(); + let W = weights.iter().sum(); + + // e.g., Suppose the weights for players 0, 1 and 2 are [2, 4, 3] + // Then, our PVSS transcript implementation will store a vector of 2 + 4 + 3 = 9 shares, + // such that: + // - Player 0 will own the shares at indices [0..2), i.e.,starting index 0 + // - Player 1 will own the shares at indices [2..2 + 4) = [2..6), i.e.,starting index 2 + // - Player 2 will own the shares at indices [6, 6 + 3) = [6..9), i.e., starting index 6 + let mut starting_index = Vec::with_capacity(weights.len()); + starting_index.push(0); + + for w in weights.iter().take(n - 1) { + starting_index.push(starting_index.last().unwrap() + w); + } + + let tc = ThresholdConfig::new(threshold_weight, W)?; + Ok(WeightedConfig { + tc, + num_players: n, + weight: weights, + starting_index, + max_player_weight, + }) + } + + pub fn get_max_player_weight(&self) -> usize { + self.max_player_weight + } + + pub fn get_threshold_config(&self) -> &ThresholdConfig { + &self.tc + } + + pub fn get_threshold_weight(&self) -> usize { + self.tc.t + } + + pub fn get_total_weight(&self) -> usize { + self.tc.n + } + + pub fn get_player_weight(&self, player: &Player) -> usize { + self.weight[player.id] + } + + pub fn get_player_starting_index(&self, player: &Player) -> usize { + self.starting_index[player.id] + } + + /// In an unweighted secret sharing scheme, each player has one share. We can weigh such a scheme + /// by splitting a player into as many "virtual" players as that player's weight, assigning one + /// share per "virtual player." + /// + /// This function returns the "virtual" player associated with the $i$th sub-share of this player. + pub fn get_virtual_player(&self, player: &Player, j: usize) -> Player { + // println!("WeightedConfig::get_virtual_player({player}, {i})"); + assert_lt!(j, self.weight[player.id]); + + let id = self.get_share_index(player.id, j).unwrap(); + + Player { id } + } + + pub fn get_all_virtual_players(&self, player: &Player) -> Vec { + let w = self.get_player_weight(player); + + (0..w) + .map(|i| self.get_virtual_player(player, i)) + .collect::>() + } + + /// `i` is the player's index, from 0 to `self.tc.n` + /// `j` is the player's share #, from 0 to `self.weight[i]` + /// + /// Returns the index of this player's share in the vector of shares, or None if out of bounds. + pub fn get_share_index(&self, i: usize, j: usize) -> Option { + assert_lt!(i, self.tc.n); + if j < self.weight[i] { + Some(self.starting_index[i] + j) + } else { + None + } + } + + pub fn get_batch_evaluation_domain(&self) -> &BatchEvaluationDomain { + &self.tc.get_batch_evaluation_domain() + } + + pub fn get_evaluation_domain(&self) -> &EvaluationDomain { + &self.tc.get_evaluation_domain() + } + + pub fn get_best_case_eligible_subset_of_players( + &self, + _rng: &mut R, + ) -> Vec { + let mut player_and_weights = self.sort_players_by_weight(); + + self.pop_eligible_subset(&mut player_and_weights) + } + + pub fn get_worst_case_eligible_subset_of_players( + &self, + _rng: &mut R, + ) -> Vec { + let mut player_and_weights = self.sort_players_by_weight(); + + player_and_weights.reverse(); + + self.pop_eligible_subset(&mut player_and_weights) + } + + fn sort_players_by_weight(&self) -> Vec<(usize, usize)> { + // the set of remaining players that we are picking a "capable" subset from + let mut player_and_weights = self + .weight + .iter() + .enumerate() + .map(|(i, w)| (i, *w)) + .collect::>(); + + player_and_weights.sort_by(|a, b| a.1.cmp(&b.1)); + player_and_weights + } + + fn pop_eligible_subset(&self, player_and_weights: &mut Vec<(usize, usize)>) -> Vec { + let mut picked_players = vec![]; + + let mut current_weight = 0; + while current_weight < self.tc.t { + let (player_idx, weight) = player_and_weights.pop().unwrap(); + + picked_players.push(self.get_player(player_idx)); + + // rinse and repeat until the picked players jointly have enough weight + current_weight += weight; + } + + picked_players + } +} + +impl Display for WeightedConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "weighted/{}-out-of-{}/{}-players", + self.tc.t, self.tc.n, self.num_players + ) + } +} + +impl traits::SecretSharingConfig for WeightedConfig { + /// For testing only. + fn get_random_player(&self, rng: &mut R) -> Player + where + R: RngCore + CryptoRng, + { + Player { + id: rng.gen_range(0, self.get_total_num_players()), + } + } + + fn get_random_eligible_subset_of_players(&self, rng: &mut R) -> Vec + where + R: RngCore, + { + // the randomly-picked "capable" subset of players who can reconstruct the secret + let mut picked_players = vec![]; + // the set of remaining players that we are picking a "capable" subset from + let mut player_and_weights = self + .weight + .iter() + .enumerate() + .map(|(i, w)| (i, *w)) + .collect::>(); + let mut current_weight = 0; + + while current_weight < self.tc.t { + // pick a random player, and move it to the picked set + let idx = rng.gen_range(0, player_and_weights.len()); + let (player_id, weight) = player_and_weights[idx]; + picked_players.push(self.get_player(player_id)); + + // efficiently remove the picked player from the set of remaining players + let len = player_and_weights.len(); + if len > 1 { + player_and_weights.swap(idx, len - 1); + player_and_weights.pop(); + } + + // rinse and repeat until the picked players jointly have enough weight + current_weight += weight; + } + + // println!(); + // println!( + // "Returned random capable subset {{ {} }}", + // vec_to_str!(picked_players) + // ); + picked_players + } + + fn get_total_num_players(&self) -> usize { + self.num_players + } + + fn get_total_num_shares(&self) -> usize { + self.tc.n + } +} + +#[cfg(test)] +mod test { + use crate::pvss::{traits::SecretSharingConfig, WeightedConfig}; + + #[test] + fn bvt() { + // 1-out-of-1 weighted + let wc = WeightedConfig::new(1, vec![1]).unwrap(); + assert_eq!(wc.starting_index.len(), 1); + assert_eq!(wc.starting_index[0], 0); + assert_eq!(wc.get_virtual_player(&wc.get_player(0), 0).id, 0); + + // 1-out-of-2, weights 2 + let wc = WeightedConfig::new(1, vec![2]).unwrap(); + assert_eq!(wc.starting_index.len(), 1); + assert_eq!(wc.starting_index[0], 0); + assert_eq!(wc.get_virtual_player(&wc.get_player(0), 0).id, 0); + assert_eq!(wc.get_virtual_player(&wc.get_player(0), 1).id, 1); + + // 1-out-of-2, weights 1, 1 + let wc = WeightedConfig::new(1, vec![1, 1]).unwrap(); + assert_eq!(wc.starting_index.len(), 2); + assert_eq!(wc.starting_index[0], 0); + assert_eq!(wc.starting_index[1], 1); + assert_eq!(wc.get_virtual_player(&wc.get_player(0), 0).id, 0); + assert_eq!(wc.get_virtual_player(&wc.get_player(1), 0).id, 1); + + // 2-out-of-2, weights 1, 1 + let _wc = WeightedConfig::new(1, vec![1, 1]).unwrap(); + assert_eq!(wc.starting_index.len(), 2); + assert_eq!(wc.starting_index[0], 0); + assert_eq!(wc.starting_index[1], 1); + assert_eq!(wc.get_virtual_player(&wc.get_player(0), 0).id, 0); + assert_eq!(wc.get_virtual_player(&wc.get_player(1), 0).id, 1); + } +} diff --git a/crates/aptos-dkg/src/utils/biguint.rs b/crates/aptos-dkg/src/utils/biguint.rs new file mode 100644 index 00000000000000..94c2fe78c91dac --- /dev/null +++ b/crates/aptos-dkg/src/utils/biguint.rs @@ -0,0 +1,58 @@ +// Copyright © Aptos Foundation + +use crate::SCALAR_NUM_BYTES; +use blstrs::Scalar; +use ff::Field; +use num_bigint::BigUint; + +/// Returns the order of the scalar field in our implementation's choice of an elliptic curve group. +pub(crate) fn get_scalar_field_order_as_biguint() -> BigUint { + let r = BigUint::from_bytes_be( + hex::decode("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001") + .unwrap() + .as_slice(), + ); + + // Here, we paranoically assert that r is correct, by checking 0 - 1 mod r (computed via Scalar) equals r-1 (computed from the constant above) + let minus_one = Scalar::ZERO - Scalar::ONE; + let max = &r - 1u8; + assert_eq!( + minus_one.to_bytes_le().as_slice(), + max.to_bytes_le().as_slice() + ); + + r +} + +/// Helper function useful when picking a random scalar and when hashing a message into a scalar. +pub fn biguint_to_scalar(big_uint: &BigUint) -> Scalar { + // `blstrs`'s `Scalar::from_bytes_le` needs `SCALAR_NUM_BYTES` bytes. The current + // implementation of `BigUint::to_bytes_le()` does not always return `SCALAR_NUM_BYTES` bytes + // when the integer is smaller than 32 bytes. So we have to pad it. + let mut bytes = big_uint.to_bytes_le(); + + while bytes.len() < SCALAR_NUM_BYTES { + bytes.push(0u8); + } + + debug_assert_eq!(BigUint::from_bytes_le(&bytes.as_slice()), *big_uint); + + let slice = match <&[u8; SCALAR_NUM_BYTES]>::try_from(bytes.as_slice()) { + Ok(slice) => slice, + Err(_) => { + panic!( + "WARNING: Got {} bytes instead of {SCALAR_NUM_BYTES} (i.e., got {})", + bytes.as_slice().len(), + big_uint.to_string() + ); + }, + }; + + let opt = Scalar::from_bytes_le(slice); + + if opt.is_some().unwrap_u8() == 1u8 { + opt.unwrap() + } else { + panic!("Deserialization of randomly-generated num_bigint::BigUint failed."); + } +} diff --git a/crates/aptos-dkg/src/utils/mod.rs b/crates/aptos-dkg/src/utils/mod.rs new file mode 100644 index 00000000000000..de0ed7229898bd --- /dev/null +++ b/crates/aptos-dkg/src/utils/mod.rs @@ -0,0 +1,154 @@ +// Copyright © Aptos Foundation + +use crate::utils::random::random_scalar_from_uniform_bytes; +use blstrs::{ + pairing, Bls12, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar, +}; +use group::Curve; +use pairing::{MillerLoopResult, MultiMillerLoop}; +use sha3::Digest; +use std::ops::Mul; + +pub(crate) mod biguint; +pub mod random; +pub mod serialization; + +#[inline] +pub fn is_power_of_two(n: usize) -> bool { + n != 0 && (n & (n - 1) == 0) +} + +/// Hashes the specified `msg` and domain separation tag `dst` into a `Scalar` by computing a 512-bit +/// number as SHA3-512(SHA3-512(dst) || msg) and reducing it modulo the order of the field. +/// (Same design as in `curve25519-dalek` explained here https://crypto.stackexchange.com/questions/88002/how-to-map-output-of-hash-algorithm-to-a-finite-field) +/// +/// NOTE: Domain separation from other SHA3-512 calls in our system is left up to the caller. +pub fn hash_to_scalar(msg: &[u8], dst: &[u8]) -> Scalar { + // First, hash the DST as `dst_hash = H(dst)` + let mut hasher = sha3::Sha3_512::new(); + hasher.update(dst); + let binding = hasher.finalize(); + let dst_hash = binding.as_slice(); + + // Second, hash the msg as `H(dst_hash, msg)` + let mut hasher = sha3::Sha3_512::new(); + hasher.update(dst_hash); + hasher.update(msg); + let binding = hasher.finalize(); + let bytes = binding.as_slice(); + + assert_eq!(bytes.len(), 64); + + match bytes.try_into() { + Ok(chunk) => random_scalar_from_uniform_bytes(chunk), + Err(_) => panic!("Expected a 64-byte SHA3-512 hash, but got a different size"), + } +} + +/// Works around the `blst_hell` bug (see README.md). +pub fn g1_multi_exp(bases: &[G1Projective], scalars: &[Scalar]) -> G1Projective { + if bases.len() != scalars.len() { + panic!( + "blstrs's multiexp has heisenbugs when the # of bases != # of scalars ({} != {})", + bases.len(), + scalars.len() + ); + } + + if bases.len() == 1 { + bases[0].mul(scalars[0]) + } else { + G1Projective::multi_exp(bases, scalars) + } +} + +/// Works around the `blst_hell` bug (see README.md). +pub fn g2_multi_exp(bases: &[G2Projective], scalars: &[Scalar]) -> G2Projective { + if bases.len() != scalars.len() { + panic!( + "blstrs's multiexp has heisenbugs when the # of bases != # of scalars ({} != {})", + bases.len(), + scalars.len() + ); + } + + if bases.len() == 1 { + bases[0].mul(scalars[0]) + } else { + G2Projective::multi_exp(bases, scalars) + } +} + +pub fn multi_pairing<'a, I1, I2>(lhs: I1, rhs: I2) -> Gt +where + I1: Iterator, + I2: Iterator, +{ + let res = ::multi_miller_loop( + lhs.zip(rhs) + .map(|(g1, g2)| (g1.to_affine(), G2Prepared::from(g2.to_affine()))) + .collect::>() + .iter() + .map(|(g1, g2)| (g1, g2)) + .collect::>() + .as_slice(), + ); + + res.final_exponentiation() +} + +/// Useful for macro'd WVUF code (because blstrs was not written with generics in mind...). +pub fn multi_pairing_g1_g2<'a, I1, I2>(lhs: I1, rhs: I2) -> Gt +where + I1: Iterator, + I2: Iterator, +{ + multi_pairing(lhs, rhs) +} + +/// Useful for macro'd WVUF code (because blstrs was not written with generics in mind...). +pub fn multi_pairing_g2_g1<'a, I1, I2>(lhs: I1, rhs: I2) -> Gt +where + I1: Iterator, + I2: Iterator, +{ + multi_pairing(rhs, lhs) +} + +/// Useful for macro'd WVUF code (because blstrs was not written with generics in mind...). +pub fn pairing_g1_g2(lhs: &G1Affine, rhs: &G2Affine) -> Gt { + pairing(lhs, rhs) +} + +/// Useful for macro'd WVUF code (because blstrs was not written with generics in mind...). +pub fn pairing_g2_g1(lhs: &G2Affine, rhs: &G1Affine) -> Gt { + pairing(rhs, lhs) +} + +pub trait HasMultiExp: for<'a> Sized + Clone { + fn multi_exp_slice(bases: &[Self], scalars: &[Scalar]) -> Self; + + fn multi_exp_iter<'a, 'b, I>(bases: I, scalars: impl Iterator) -> Self + where + I: Iterator, + Self: 'a, + { + // TODO(Perf): blstrs does not work with iterators, which leads to unnecessary cloning here. + Self::multi_exp_slice( + bases.cloned().collect::>().as_slice(), + scalars.cloned().collect::>().as_slice(), + ) + } +} + +impl HasMultiExp for G2Projective { + fn multi_exp_slice(points: &[Self], scalars: &[Scalar]) -> Self { + g2_multi_exp(points, scalars) + } +} + +impl HasMultiExp for G1Projective { + fn multi_exp_slice(points: &[Self], scalars: &[Scalar]) -> Self { + g1_multi_exp(points, scalars) + } +} diff --git a/crates/aptos-dkg/src/utils/random.rs b/crates/aptos-dkg/src/utils/random.rs new file mode 100644 index 00000000000000..99e55ad0444ba6 --- /dev/null +++ b/crates/aptos-dkg/src/utils/random.rs @@ -0,0 +1,210 @@ +// Copyright © Aptos Foundation +/// TODO(Security): This file is a workaround for the `rand_core_hell` issue, briefly described below. +/// +/// Ideally, we would write the following sane code: +/// +/// ```ignore +/// let mut dk = Scalar::random(rng); +/// while dk.is_zero() { +/// dk = Scalar::random(rng); +/// } +/// ``` +/// +/// But we can't due to `aptos-crypto`'s dependency on an older version of `rand` and `rand_core` +/// compared to `blstrs`'s dependency. +use crate::{G1_PROJ_NUM_BYTES, G2_PROJ_NUM_BYTES, SCALAR_FIELD_ORDER, SCALAR_NUM_BYTES}; +use blstrs::{G1Projective, G2Projective, Gt, Scalar}; +use group::Group; +use num_bigint::{BigUint, RandBigInt}; +use num_integer::Integer; +use num_traits::Zero; +use std::ops::Mul; + +/// Domain-separator for hash-based randomness generation that works around `rand_core_hell`. +pub const DST_RAND_CORE_HELL: &[u8; 24] = b"APTOS_RAND_CORE_HELL_DST"; + +/// Returns a random `blstrs::Scalar` given an older RNG as input. +/// +/// Pretty fast: 623 nanosecond / call. +pub fn random_scalar(rng: &mut R) -> Scalar +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + random_scalar_internal(rng, false) +} + +pub fn random_nonzero_scalar(rng: &mut R) -> Scalar +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + random_scalar_internal(rng, true) +} + +pub fn random_scalar_internal(rng: &mut R, exclude_zero: bool) -> Scalar +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let mut big_uint; + + loop { + // NOTE(Alin): This uses rejection-sampling (e.g., https://cs.stackexchange.com/a/2578/54866) + // An alternative would be to sample twice the size of the scalar field and use + // `random_scalar_from_uniform_bytes`, but that is actually slower (950ns vs 623ns) + big_uint = rng.gen_biguint_below(&SCALAR_FIELD_ORDER); + + // Some key material cannot be zero since it needs to have an inverse in the scalar field. + if !exclude_zero || !big_uint.is_zero() { + break; + } + } + + crate::utils::biguint::biguint_to_scalar(&big_uint) +} + +pub fn random_scalar_from_uniform_bytes(bytes: &[u8; 2 * SCALAR_NUM_BYTES]) -> Scalar { + let bignum = BigUint::from_bytes_le(&bytes[..]); + let remainder = bignum.mod_floor(&SCALAR_FIELD_ORDER); + + crate::utils::biguint::biguint_to_scalar(&remainder) +} + +/// Returns a random `blstrs::G1Projective` given an older RNG as input. +/// +/// Slow: Takes 50 microseconds. +pub fn random_g1_point(rng: &mut R) -> G1Projective +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let mut rand_seed = [0u8; 2 * G1_PROJ_NUM_BYTES]; + rng.fill(rand_seed.as_mut_slice()); + + G1Projective::hash_to_curve(rand_seed.as_slice(), DST_RAND_CORE_HELL, b"G1") +} + +/// Returns a random `blstrs::G2Projective` given an older RNG as input. +/// +/// Slow: Takes 150 microseconds. +pub fn random_g2_point(rng: &mut R) -> G2Projective +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let mut rand_seed = [0u8; 2 * G2_PROJ_NUM_BYTES]; + rng.fill(rand_seed.as_mut_slice()); + + G2Projective::hash_to_curve(rand_seed.as_slice(), DST_RAND_CORE_HELL, b"G2") +} + +/// Returns a random `blstrs::GTProjective` given an older RNG as input. +/// +/// Takes 507 microseconds. +/// +/// NOTE: This function is "insecure" in the sense that the caller learns the discrete log of the +/// random G_T point w.r.t. the generator. In many applications, this is not acceptable. +pub fn insecure_random_gt_point(rng: &mut R) -> Gt +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let s = random_scalar(rng); + + // TODO(TestingPerf): Cannot sample more efficiently than this because `fp12::Fp12` is not exposed. + Gt::generator().mul(s) +} + +/// Returns a vector of random `blstrs::Scalar`'s, given an RNG as input. +pub fn random_scalars(n: usize, rng: &mut R) -> Vec +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let mut v = Vec::with_capacity(n); + + for _ in 0..n { + v.push(random_scalar(rng)); + } + + debug_assert_eq!(v.len(), n); + + v +} + +/// Returns a vector of random `blstrs::G1Projective`'s, given an RNG as input. +pub fn random_g1_points(n: usize, rng: &mut R) -> Vec +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let mut v = Vec::with_capacity(n); + + for _ in 0..n { + v.push(random_g1_point(rng)); + } + + debug_assert_eq!(v.len(), n); + + v +} + +/// Returns a vector of random `blstrs::G2Projective`'s, given an RNG as input. +pub fn random_g2_points(n: usize, rng: &mut R) -> Vec +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let mut v = Vec::with_capacity(n); + + for _ in 0..n { + v.push(random_g2_point(rng)); + } + + debug_assert_eq!(v.len(), n); + + v +} + +/// Returns a vector of random `blstrs::GT`'s, given an RNG as input. +/// +/// WARNING: Insecure. See `insecure_random_gt_point` comments. +pub fn insecure_random_gt_points(n: usize, rng: &mut R) -> Vec +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let mut v = Vec::with_capacity(n); + + for _ in 0..n { + v.push(insecure_random_gt_point(rng)); + } + + debug_assert_eq!(v.len(), n); + + v +} + +/// Sometimes we will want to generate somewhat random-looking points for benchmarking, so we will +/// use this faster **insecure** function instead. +pub fn insecure_random_g1_points(n: usize, rng: &mut R) -> Vec +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let point = random_g1_point(rng); + let shift = random_g1_point(rng); + let mut acc = point; + (0..n) + .map(|_| { + acc = acc.double() + shift; + acc + }) + .collect::>() +} + +/// Like `insecure_random_g1_points` but for G_2. +pub fn insecure_random_g2_points(n: usize, rng: &mut R) -> Vec +where + R: rand_core::RngCore + rand::Rng + rand_core::CryptoRng + rand::CryptoRng, +{ + let point = random_g2_point(rng); + let shift = random_g2_point(rng); + let mut acc = point; + (0..n) + .map(|_| { + acc = acc.double() + shift; + acc + }) + .collect::>() +} diff --git a/crates/aptos-dkg/src/utils/serialization.rs b/crates/aptos-dkg/src/utils/serialization.rs new file mode 100644 index 00000000000000..c04ff2f9410de0 --- /dev/null +++ b/crates/aptos-dkg/src/utils/serialization.rs @@ -0,0 +1,54 @@ +// Copyright © Aptos Foundation + +use crate::{G1_PROJ_NUM_BYTES, G2_PROJ_NUM_BYTES, SCALAR_NUM_BYTES}; +use aptos_crypto::CryptoMaterialError; +use blstrs::{G1Projective, G2Projective, Scalar}; + +/// Helper method to *securely* parse a sequence of bytes into a `G1Projective` point. +/// NOTE: This function will check for prime-order subgroup membership in $\mathbb{G}_1$. +pub fn g1_proj_from_bytes(bytes: &[u8]) -> Result { + let slice = match <&[u8; G1_PROJ_NUM_BYTES]>::try_from(bytes) { + Ok(slice) => slice, + Err(_) => return Err(CryptoMaterialError::WrongLengthError), + }; + + let a = G1Projective::from_compressed(slice); + + if a.is_some().unwrap_u8() == 1u8 { + Ok(a.unwrap()) + } else { + Err(CryptoMaterialError::DeserializationError) + } +} + +/// Helper method to *securely* parse a sequence of bytes into a `G2Projective` point. +/// NOTE: This function will check for prime-order subgroup membership in $\mathbb{G}_2$. +pub fn g2_proj_from_bytes(bytes: &[u8]) -> Result { + let slice = match <&[u8; G2_PROJ_NUM_BYTES]>::try_from(bytes) { + Ok(slice) => slice, + Err(_) => return Err(CryptoMaterialError::WrongLengthError), + }; + + let a = G2Projective::from_compressed(slice); + + if a.is_some().unwrap_u8() == 1u8 { + Ok(a.unwrap()) + } else { + Err(CryptoMaterialError::DeserializationError) + } +} + +/// Helper method to *securely* parse a sequence of bytes into a `Scalar`. +pub(crate) fn scalar_from_bytes_le(bytes: &[u8]) -> Result { + let slice = match <&[u8; SCALAR_NUM_BYTES]>::try_from(bytes) { + Ok(slice) => slice, + Err(_) => return Err(CryptoMaterialError::WrongLengthError), + }; + + let opt = Scalar::from_bytes_le(slice); + if opt.is_some().unwrap_u8() == 1u8 { + Ok(opt.unwrap()) + } else { + Err(CryptoMaterialError::DeserializationError) + } +} diff --git a/crates/aptos-dkg/src/weighted_vuf/bls/mod.rs b/crates/aptos-dkg/src/weighted_vuf/bls/mod.rs new file mode 100644 index 00000000000000..72c7f19a1b89ef --- /dev/null +++ b/crates/aptos-dkg/src/weighted_vuf/bls/mod.rs @@ -0,0 +1,172 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::lagrange::lagrange_coefficients, + pvss, + pvss::{Player, WeightedConfig}, + utils::{g1_multi_exp, multi_pairing}, + weighted_vuf::traits::WeightedVUF, +}; +use anyhow::bail; +use blstrs::{G1Projective, G2Projective, Gt, Scalar}; +use ff::Field; +use group::Group; +use serde::{Deserialize, Serialize}; +use std::ops::{Mul, Neg}; + +pub const BLS_WVUF_DST: &[u8; 18] = b"APTOS_BLS_WVUF_DST"; + +pub struct BlsWUF; +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PublicParameters { + g: G2Projective, +} + +impl From<&pvss::das::PublicParameters> for PublicParameters { + fn from(pp: &pvss::das::PublicParameters) -> Self { + PublicParameters { + g: *pp.get_commitment_base(), + } + } +} + +impl WeightedVUF for BlsWUF { + type AugmentedPubKeyShare = Self::PubKeyShare; + type AugmentedSecretKeyShare = Self::SecretKeyShare; + type Delta = (); + type Evaluation = G1Projective; + type Proof = Self::Evaluation; + type ProofShare = Vec; + type PubKey = pvss::dealt_pub_key::g2::DealtPubKey; + type PubKeyShare = Vec; + type PublicParameters = PublicParameters; + type SecretKey = Scalar; + type SecretKeyShare = Vec; + + fn augment_key_pair( + _pp: &Self::PublicParameters, + sk: Self::SecretKeyShare, + pk: Self::PubKeyShare, + _rng: &mut R, + ) -> (Self::AugmentedSecretKeyShare, Self::AugmentedPubKeyShare) { + (sk, pk) + } + + fn get_public_delta(_apk: &Self::AugmentedPubKeyShare) -> &Self::Delta { + &() + } + + fn augment_pubkey( + _pp: &Self::PublicParameters, + pk: Self::PubKeyShare, + _delta: Self::Delta, + ) -> anyhow::Result { + Ok(pk) + } + + fn create_share(ask: &Self::AugmentedSecretKeyShare, msg: &[u8]) -> Self::ProofShare { + let hash = Self::hash_to_curve(msg); + + ask.iter() + .map(|sk| hash.mul(sk)) + .collect::>() + } + + fn verify_share( + pp: &Self::PublicParameters, + apk: &Self::AugmentedPubKeyShare, + msg: &[u8], + proof: &Self::ProofShare, + ) -> anyhow::Result<()> { + let hash = Self::hash_to_curve(msg); + + let agg_pk = apk + .iter() + .map(|pk| *pk.as_group_element()) + .sum::(); + let agg_sig = proof.iter().copied().sum::(); + + if multi_pairing( + [&hash, &agg_sig].into_iter(), + [&agg_pk, &pp.g.neg()].into_iter(), + ) != Gt::identity() + { + bail!("BlsWVUF ProofShare failed to verify."); + } + + Ok(()) + } + + fn aggregate_shares( + wc: &WeightedConfig, + apks_and_proofs: &[(Player, Self::AugmentedPubKeyShare, Self::ProofShare)], + ) -> Self::Proof { + // Collect all the evaluation points associated with each player + let mut sub_player_ids = Vec::with_capacity(wc.get_total_weight()); + + for (player, _, _) in apks_and_proofs { + for j in 0..wc.get_player_weight(player) { + sub_player_ids.push(wc.get_virtual_player(player, j).id); + } + } + + // Compute the Lagrange coefficients associated with those evaluation points + let batch_dom = wc.get_batch_evaluation_domain(); + let lagr = lagrange_coefficients(batch_dom, &sub_player_ids[..], &Scalar::ZERO); + + // Interpolate the signature + let mut bases = Vec::with_capacity(apks_and_proofs.len()); + for (_, _, share) in apks_and_proofs { + // println!( + // "Flattening {} share(s) for player {player}", + // sub_shares.len() + // ); + bases.extend_from_slice(share.as_slice()) + } + + g1_multi_exp(bases.as_slice(), lagr.as_slice()) + } + + fn eval(sk: &Self::SecretKey, msg: &[u8]) -> Self::Evaluation { + let h = Self::hash_to_curve(msg); + h.mul(sk) + } + + // NOTE: This VUF has the same evaluation as its proof. + fn derive_eval( + _wc: &WeightedConfig, + _pp: &Self::PublicParameters, + _msg: &[u8], + _apks: &[Option], + proof: &Self::Proof, + ) -> anyhow::Result { + Ok(*proof) + } + + /// Verifies the proof shares one by one + fn verify_proof( + pp: &Self::PublicParameters, + pk: &Self::PubKey, + _apks: &[Option], + msg: &[u8], + proof: &Self::Proof, + ) -> anyhow::Result<()> { + let hash = Self::hash_to_curve(msg); + + if multi_pairing( + [&hash, proof].into_iter(), + [pk.as_group_element(), &pp.g.neg()].into_iter(), + ) != Gt::identity() + { + bail!("BlsWVUF Proof failed to verify."); + } + + Ok(()) + } +} + +impl BlsWUF { + fn hash_to_curve(msg: &[u8]) -> G1Projective { + G1Projective::hash_to_curve(msg, BLS_WVUF_DST, b"H(m)") + } +} diff --git a/crates/aptos-dkg/src/weighted_vuf/mod.rs b/crates/aptos-dkg/src/weighted_vuf/mod.rs new file mode 100644 index 00000000000000..2c7348bca68c14 --- /dev/null +++ b/crates/aptos-dkg/src/weighted_vuf/mod.rs @@ -0,0 +1,5 @@ +// Copyright © Aptos Foundation + +pub mod bls; +pub mod pinkas; +pub mod traits; diff --git a/crates/aptos-dkg/src/weighted_vuf/pinkas/mod.rs b/crates/aptos-dkg/src/weighted_vuf/pinkas/mod.rs new file mode 100644 index 00000000000000..a8c9dcf85678b5 --- /dev/null +++ b/crates/aptos-dkg/src/weighted_vuf/pinkas/mod.rs @@ -0,0 +1,285 @@ +// Copyright © Aptos Foundation + +use crate::{ + algebra::{lagrange::lagrange_coefficients, polynomials::get_powers_of_tau}, + pvss, + pvss::{traits::HasEncryptionPublicParams, Player, WeightedConfig}, + utils::{ + g1_multi_exp, g2_multi_exp, multi_pairing, + random::{random_nonzero_scalar, random_scalar}, + }, + weighted_vuf::traits::WeightedVUF, +}; +use anyhow::{anyhow, bail}; +use blstrs::{pairing, G1Projective, G2Projective, Gt, Scalar}; +use ff::Field; +use group::{Curve, Group}; +use rand::thread_rng; +use serde::{Deserialize, Serialize}; +use std::ops::{Mul, Neg}; + +pub const PINKAS_WVUF_DST: &[u8; 21] = b"APTOS_PINKAS_WVUF_DST"; + +pub struct PinkasWUF; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RandomizedPKs { + pi: G1Projective, // \hat{g}^{r} + rks: Vec, // g^{r \sk_i}, for all shares i +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PublicParameters { + g: G1Projective, + g_neg: G1Projective, + g_hat: G2Projective, +} + +impl From<&pvss::das::PublicParameters> for PublicParameters { + fn from(pp: &pvss::das::PublicParameters) -> Self { + let g = pp.get_encryption_public_params().message_base().clone(); + PublicParameters { + g, + g_neg: g.neg(), + g_hat: pp.get_commitment_base().clone(), + } + } +} + +/// Implements the Pinkas weighted VUF scheme, compatible with *any* PVSS scheme with the right kind +/// of secret key and public key. +impl WeightedVUF for PinkasWUF { + type AugmentedPubKeyShare = (RandomizedPKs, Self::PubKeyShare); + type AugmentedSecretKeyShare = (Scalar, Self::SecretKeyShare); + // /// Note: Our BLS PKs are currently in G_1. + // type BlsPubKey = bls12381::PublicKey; + // type BlsSecretKey = bls12381::PrivateKey; + + type Delta = RandomizedPKs; + type Evaluation = Gt; + /// Naive aggregation by concatenation. It is an open problem to get constant-sized aggregation. + type Proof = Vec<(Player, Self::ProofShare)>; + type ProofShare = G2Projective; + type PubKey = pvss::dealt_pub_key::g2::DealtPubKey; + type PubKeyShare = Vec; + type PublicParameters = PublicParameters; + type SecretKey = pvss::dealt_secret_key::g1::DealtSecretKey; + type SecretKeyShare = Vec; + + fn augment_key_pair( + pp: &Self::PublicParameters, + sk: Self::SecretKeyShare, + pk: Self::PubKeyShare, + // lsk: &Self::BlsSecretKey, + rng: &mut R, + ) -> (Self::AugmentedSecretKeyShare, Self::AugmentedPubKeyShare) { + let r = random_nonzero_scalar(rng); + + let rpks = RandomizedPKs { + pi: pp.g.mul(&r), + rks: sk + .iter() + .map(|sk| sk.as_group_element().mul(&r)) + .collect::>(), + }; + + ((r.invert().unwrap(), sk), (rpks, pk)) + } + + fn get_public_delta(apk: &Self::AugmentedPubKeyShare) -> &Self::Delta { + let (rpks, _) = apk; + + rpks + } + + fn augment_pubkey( + pp: &Self::PublicParameters, + pk: Self::PubKeyShare, + // lpk: &Self::BlsPubKey, + delta: Self::Delta, + ) -> anyhow::Result { + if delta.rks.len() != pk.len() { + bail!( + "Expected PKs and RKs to be of the same length. Got {} and {}, respectively.", + delta.rks.len(), + pk.len() + ); + } + + // TODO: Fiat-Shamir transform instead of RNG + let tau = random_scalar(&mut thread_rng()); + + let pks = pk + .iter() + .map(|pk| *pk.as_group_element()) + .collect::>(); + let taus = get_powers_of_tau(&tau, pks.len()); + + let pks_combined = g2_multi_exp(&pks[..], &taus[..]); + let rks_combined = g1_multi_exp(&delta.rks[..], &taus[..]); + + if multi_pairing( + [&delta.pi, &rks_combined].into_iter(), + [&pks_combined, &pp.g_hat.neg()].into_iter(), + ) != Gt::identity() + { + bail!("RPKs were not correctly randomized."); + } + + Ok((delta, pk)) + } + + fn create_share(ask: &Self::AugmentedSecretKeyShare, msg: &[u8]) -> Self::ProofShare { + let (r_inv, _) = ask; + + let hash = Self::hash_to_curve(msg); + + hash.mul(r_inv) + } + + fn verify_share( + pp: &Self::PublicParameters, + apk: &Self::AugmentedPubKeyShare, + msg: &[u8], + proof: &Self::ProofShare, + ) -> anyhow::Result<()> { + let delta = Self::get_public_delta(apk); + + let h = Self::hash_to_curve(msg); + + if multi_pairing([&delta.pi, &pp.g_neg].into_iter(), [proof, &h].into_iter()) + != Gt::identity() + { + bail!("PinkasWVUF ProofShare failed to verify."); + } + + Ok(()) + } + + fn aggregate_shares( + _wc: &WeightedConfig, + apks_and_proofs: &[(Player, Self::AugmentedPubKeyShare, Self::ProofShare)], + ) -> Self::Proof { + let mut players_and_shares = Vec::with_capacity(apks_and_proofs.len()); + + for (p, _, share) in apks_and_proofs { + players_and_shares.push((p.clone(), share.clone())); + } + + players_and_shares + } + + fn eval(sk: &Self::SecretKey, msg: &[u8]) -> Self::Evaluation { + let h = Self::hash_to_curve(msg).to_affine(); + + pairing(&sk.as_group_element().to_affine(), &h) + } + + // NOTE: This VUF has the same evaluation as its proof. + fn derive_eval( + wc: &WeightedConfig, + _pp: &Self::PublicParameters, + _msg: &[u8], + apks: &[Option], + proof: &Self::Proof, + ) -> anyhow::Result { + // Collect all the evaluation points associated with each player's augmented pubkey sub shares. + let mut sub_player_ids = Vec::with_capacity(wc.get_total_weight()); + + for (player, _) in proof { + for j in 0..wc.get_player_weight(player) { + sub_player_ids.push(wc.get_virtual_player(player, j).id); + } + } + + // Compute the Lagrange coefficients associated with those evaluation points + let batch_dom = wc.get_batch_evaluation_domain(); + let lagr = lagrange_coefficients(batch_dom, &sub_player_ids[..], &Scalar::ZERO); + + // Interpolate the WVUF Proof + let mut k = 0; + let mut lhs = Vec::with_capacity(proof.len()); + let mut rhs = Vec::with_capacity(proof.len()); + for (player, share) in proof { + // println!( + // "Flattening {} share(s) for player {player}", + // sub_shares.len() + // ); + let apk = apks[player.id] + .as_ref() + .ok_or(anyhow!("Missing APK for player {}", player.get_id()))?; + let rks = &apk.0.rks; + let num_shares = rks.len(); + + rhs.push(share); + lhs.push(g1_multi_exp(&rks[..], &lagr[k..k + num_shares])); + + k += num_shares; + } + + Ok(multi_pairing(lhs.iter().map(|r| r), rhs.into_iter())) + } + + /// Verifies the proof shares one by one + fn verify_proof( + pp: &Self::PublicParameters, + _pk: &Self::PubKey, + apks: &[Option], + msg: &[u8], + proof: &Self::Proof, + ) -> anyhow::Result<()> { + if proof.len() >= apks.len() { + bail!("Number of proof shares ({}) exceeds number of APKs ({}) when verifying aggregated WVUF proof", proof.len(), apks.len()); + } + + // TODO: Fiat-Shamir transform instead of RNG + let tau = random_scalar(&mut thread_rng()); + let taus = get_powers_of_tau(&tau, proof.len()); + + // [share_i^{\tau^i}]_{i \in [0, n)} + let shares = proof + .iter() + .map(|(_, share)| share) + .zip(taus.iter()) + .map(|(share, tau)| share.mul(tau)) + .collect::>(); + + let mut pis = Vec::with_capacity(proof.len()); + for (player, _) in proof { + if player.id >= apks.len() { + bail!( + "Player index {} falls outside APK vector of length {}", + player.id, + apks.len() + ); + } + + pis.push( + apks[player.id] + .as_ref() + .ok_or(anyhow!("Missing APK for player {}", player.get_id()))? + .0 + .pi, + ); + } + + let h = Self::hash_to_curve(msg); + let sum_of_taus: Scalar = taus.iter().sum(); + + if multi_pairing( + pis.iter().chain([pp.g_neg].iter()), + shares.iter().chain([h.mul(sum_of_taus)].iter()), + ) != Gt::identity() + { + bail!("Multipairing check in batched aggregate verification failed"); + } + + Ok(()) + } +} + +impl PinkasWUF { + fn hash_to_curve(msg: &[u8]) -> G2Projective { + G2Projective::hash_to_curve(msg, &PINKAS_WVUF_DST[..], b"H(m)") + } +} diff --git a/crates/aptos-dkg/src/weighted_vuf/traits.rs b/crates/aptos-dkg/src/weighted_vuf/traits.rs new file mode 100644 index 00000000000000..e3361a100bfc0e --- /dev/null +++ b/crates/aptos-dkg/src/weighted_vuf/traits.rs @@ -0,0 +1,83 @@ +// Copyright © Aptos Foundation + +//! Notes: Unlike PVSS, we do NOT want to use a generic unweighted-to-weighted VUF transformation, +//! since we have a more optimized transformation for some VRF schemes (e.g., BLS and [GJM+21e]). +//! +//! As a result, we only define weighted VUF traits here. + +use crate::pvss::{Player, WeightedConfig}; +use serde::Serialize; +use std::fmt::Debug; + +/// Weighted (not-verifiable) unpredictable function (WUF) traits. +pub trait WeightedVUF { + type PublicParameters; + type PubKey; + type SecretKey; + type PubKeyShare: Clone; + type SecretKeyShare; + + type Delta: Clone; + + type AugmentedPubKeyShare: Clone + Debug + Eq; + type AugmentedSecretKeyShare; + + type ProofShare; + type Proof; + + /// NOTE: Not unsafe to have Debug here, since if an evaluation was aggregated, more than 33% of + /// the stake must've contributed to create it. + type Evaluation: Serialize + Debug + Eq; + + fn augment_key_pair( + pp: &Self::PublicParameters, + sk: Self::SecretKeyShare, + pk: Self::PubKeyShare, + rng: &mut R, + ) -> (Self::AugmentedSecretKeyShare, Self::AugmentedPubKeyShare); + + fn get_public_delta(pk: &Self::AugmentedPubKeyShare) -> &Self::Delta; + + fn augment_pubkey( + pp: &Self::PublicParameters, + pk: Self::PubKeyShare, + delta: Self::Delta, + ) -> anyhow::Result; + + fn create_share(ask: &Self::AugmentedSecretKeyShare, msg: &[u8]) -> Self::ProofShare; + + fn verify_share( + pp: &Self::PublicParameters, + apk: &Self::AugmentedPubKeyShare, + msg: &[u8], + proof: &Self::ProofShare, + ) -> anyhow::Result<()>; + + fn aggregate_shares( + wc: &WeightedConfig, + apks_and_proofs: &[(Player, Self::AugmentedPubKeyShare, Self::ProofShare)], + ) -> Self::Proof; + + /// Used for testing only. + fn eval(sk: &Self::SecretKey, msg: &[u8]) -> Self::Evaluation; + + fn derive_eval( + wc: &WeightedConfig, + pp: &Self::PublicParameters, + msg: &[u8], + apks: &[Option], + proof: &Self::Proof, + ) -> anyhow::Result; + + /// Verifies an aggregated proof against the `pk` and, for some WVUF constructions, against the + /// `apks`. We use a vector of `Option`'s here since players might not necessarily have agreed + /// on all other players' APKs. In that case, proof verification might fail if it depends on the + /// APKs of missing players. + fn verify_proof( + pp: &Self::PublicParameters, + pk: &Self::PubKey, + apks: &[Option], + msg: &[u8], + proof: &Self::Proof, + ) -> anyhow::Result<()>; +} diff --git a/crates/aptos-dkg/tests/accumulator.rs b/crates/aptos-dkg/tests/accumulator.rs new file mode 100644 index 00000000000000..c7df2b10bafe7f --- /dev/null +++ b/crates/aptos-dkg/tests/accumulator.rs @@ -0,0 +1,89 @@ +// Copyright © Aptos Foundation + +use aptos_dkg::{ + algebra::{ + evaluation_domain::BatchEvaluationDomain, + polynomials::{ + accumulator_poly, accumulator_poly_scheduled, accumulator_poly_slow, poly_eval, + }, + }, + utils::random::{random_scalar, random_scalars}, +}; +use blstrs::Scalar; +use ff::Field; +use rand::thread_rng; +use std::ops::{MulAssign, Sub}; + +#[test] +#[allow(non_snake_case)] +fn test_accumulator_poly_scheduled() { + let mut rng = thread_rng(); + + let set_size = 3_300; + let batch_dom = BatchEvaluationDomain::new(set_size); + + let naive_thresh = 128; + let fft_thresh = 256; + + let S = random_scalars(set_size, &mut rng); + let _ = accumulator_poly_scheduled(S.as_slice(), &batch_dom, naive_thresh, fft_thresh); +} + +#[test] +#[allow(non_snake_case)] +fn test_accumulator_poly() { + let mut rng = thread_rng(); + let max_N = 128; + let batch_dom = BatchEvaluationDomain::new(max_N); + let naive_thresh = 32; + let fft_thresh = 16; + + // size 0 + + let e = accumulator_poly_slow(&[]); + assert!(e.is_empty()); + + let e = accumulator_poly(&[], &batch_dom, fft_thresh); + assert!(e.is_empty()); + + let e = accumulator_poly_scheduled(&[], &batch_dom, naive_thresh, fft_thresh); + assert!(e.is_empty()); + + // size 1 + + let r = random_scalar(&mut rng); + let Z_slow = accumulator_poly_slow(vec![r].as_slice()); + assert_eq!(Z_slow[1], Scalar::ONE); + assert_eq!(Z_slow[0], -r); + + let Z = accumulator_poly(vec![r].as_slice(), &batch_dom, fft_thresh); + assert_eq!(Z, Z_slow); + + let Z_sched = + accumulator_poly_scheduled(vec![r].as_slice(), &batch_dom, naive_thresh, fft_thresh); + assert_eq!(Z_sched, Z_slow); + + // arbitrary size + for set_size in 2..max_N { + // println!("Testing set size {set_size} (degree {})", set_size + 1); + + let S = random_scalars(set_size, &mut rng); + let Z1 = accumulator_poly_slow(S.as_slice()); + let Z2 = accumulator_poly(S.as_slice(), &batch_dom, fft_thresh); + let Z3 = accumulator_poly_scheduled(S.as_slice(), &batch_dom, naive_thresh, fft_thresh); + + assert_eq!(Z1, Z2); + assert_eq!(Z1, Z3); + + // Test if $Z(X) = \prod_{i \in S} (X - s_i)$ via Schwartz-Zippel by comparing to $Z(r)$ + // for random $r$. + let r = random_scalar(&mut rng); + let Z_r = poly_eval(&Z1, &r); + let mut expected = Scalar::ONE; + for s in S { + expected.mul_assign(r.sub(s)); + } + + assert_eq!(Z_r, expected); + } +} diff --git a/crates/aptos-dkg/tests/crypto.rs b/crates/aptos-dkg/tests/crypto.rs new file mode 100644 index 00000000000000..eb1557bbe387c8 --- /dev/null +++ b/crates/aptos-dkg/tests/crypto.rs @@ -0,0 +1,171 @@ +// Copyright © Aptos Foundation + +use aptos_dkg::{ + algebra::polynomials::{ + poly_eval, poly_mul_fft, poly_mul_less_slow, poly_mul_slow, poly_xnmul, + }, + utils::random::{random_g2_point, random_scalar, random_scalars}, +}; +use blstrs::{G1Projective, G2Projective, Scalar}; +use ff::Field; +use group::Group; +use rand::thread_rng; +use std::ops::Mul; + +/// TODO(Security): This shouldn't fail, but it does. +#[test] +#[should_panic] +#[ignore] +fn test_crypto_g1_multiexp_more_points() { + let bases = vec![G1Projective::identity(), G1Projective::identity()]; + let scalars = vec![Scalar::ONE]; + + let result = G1Projective::multi_exp(&bases, &scalars); + + assert_eq!(result, bases[0]); +} + +/// TODO(Security): This failed once out of the blue. Can never call G1Projective::multi_exp directly +/// because of this. +/// +/// Last reproduced on Dec. 5th, 2023 with blstrs 0.7.1: +/// ``` +/// failures: +/// +/// ---- test_multiexp_less_points stdout ---- +/// thread 'test_multiexp_less_points' panicked at 'assertion failed: `(left == right)` +/// left: `G1Projective { x: Fp(0x015216375988dea7b8f1642e6667482a0fe06709923f24e629468da4cf265ea6f03f593188d3557d5cf20a50ff28f870), y: Fp(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000), z: Fp(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001) }`, +/// right: `G1Projective { x: Fp(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000), y: Fp(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000), z: Fp(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) }`', crates/aptos-dkg/tests/crypto.rs:32:5 +/// ``` +#[test] +#[ignore] +fn test_crypto_g1_multiexp_less_points() { + let bases = vec![G1Projective::identity()]; + let scalars = vec![Scalar::ONE, Scalar::ONE]; + + let result = G1Projective::multi_exp(&bases, &scalars); + + assert_eq!(result, bases[0]); +} + +/// At some point I suspected that size-1 multiexps where the scalar is set to 1 had a bug in them. +/// But they seem fine. +#[test] +fn test_crypto_size_1_multiexp_random_base() { + let mut rng = thread_rng(); + + let bases = vec![random_g2_point(&mut rng)]; + let scalars = vec![Scalar::ONE]; + + let result = G2Projective::multi_exp(&bases, &scalars); + + assert_eq!(result, bases[0]); +} + +/// TODO(Security): Size-1 G2 multiexps on the generator where the scalar is set to one WILL +/// sometimes fail. Can never call G2Projective::multi_exp directly because of this. +/// +/// Last reproduced on Dec. 5th, 2023 with blstrs 0.7.0: +/// ``` +/// ---- test_size_1_g2_multiexp_generator_base stdout ---- +/// thread 'test_size_1_g2_multiexp_generator_base' panicked at 'assertion failed: `(left == right)` +/// left: `G2Projective { x: Fp2 { c0: Fp(0x0eebd388297e6ad4aa4abe2dd6d2b65061c8a38ce9ac87718432dbdf9843c3a60bbc9706251cb8fa74bc9f5a8572a531), c1: Fp(0x18e7670f7afe6f13acd673491d6d835719c40e5ee1786865ea411262ccafa75c6aef2b28ff973b4532cc4b80e5be4936) }, y: Fp2 { c0: Fp(0x0a4548b4e05e80f16df8a1209b68de65252a7a6f8d8a133bc673ac1505ea59eb30a537e1c1b4e64394d8b2f3aa1f0f14), c1: Fp(0x00b47b3a434ab44b045f5009bcf93b6c47710ffd17c90f35b6ae39864af8d4994003fb223e29a209d609b092042cebbd) }, z: Fp2 { c0: Fp(0x06df5e339dc55dc159f0a845f3f792ea1dee8a0933dc0ed950ed588b21cb553cd6b616f49b73ea3e44ab7618125c9875), c1: Fp(0x0e9d03aee09a7603dc069da045848488f10a51bc5655baffd31f4a7b0e3746cdf93fb3345950f70617730e440f71a8e2) } }`, +/// right: `G2Projective { x: Fp2 { c0: Fp(0x024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8), c1: Fp(0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e) }, y: Fp2 { c0: Fp(0x0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801), c1: Fp(0x0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be) }, z: Fp2 { c0: Fp(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001), c1: Fp(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) } }`', crates/aptos-dkg/tests/crypto.rs:67:5 +/// ``` +#[test] +#[ignore] +fn test_crypto_g_2_to_zero_multiexp() { + let bases = vec![G2Projective::generator()]; + let scalars = vec![Scalar::ONE]; + + let result = G2Projective::multi_exp(&bases, &scalars); + + assert_eq!(result, bases[0]); +} + +/// Size-1 G1 multiexps on the generator where the scalar is set to one do NOT seem to be buggy. +#[test] +fn test_crypto_g_1_to_zero_multiexp() { + let generator = G1Projective::generator(); + let result = G1Projective::multi_exp([generator].as_slice(), [Scalar::ONE].as_slice()); + + assert_eq!(result, generator); +} + +#[test] +fn test_crypto_poly_multiply() { + let mut rng = thread_rng(); + for num_coeffs_f in [1, 2, 3, 4, 5, 6, 7, 8] { + for num_coeffs_g in [1, 2, 3, 4, 5, 6, 7, 8] { + let f = random_scalars(num_coeffs_f, &mut rng); + let g = random_scalars(num_coeffs_g, &mut rng); + + // FFT-based multiplication + let fft_fg = poly_mul_fft(&f, &g); + + // Naive multiplication + let naive_fg = poly_mul_slow(&f, &g); + + // We test correctness of $h(X) = f(X) \cdot g(X)$ by picking a random point $r$ and + // comparing $h(r)$ with $f(r) \cdot g(r)$. + let r = random_scalar(&mut rng); + + let fg_rand = poly_eval(&f, &r).mul(poly_eval(&g, &r)); + let fft_fg_rand = poly_eval(&fft_fg, &r); + assert_eq!(fft_fg_rand, fg_rand); + + // We also test correctness of the naive multiplication algorithm + let naive_fg_rand = poly_eval(&naive_fg, &r); + assert_eq!(naive_fg_rand, fg_rand); + + // Lastly, of course the naive result should be the same as the FFT result (since they are both correct) + assert_eq!(naive_fg, fft_fg); + } + } +} + +#[test] +fn test_crypto_poly_multiply_divide_and_conquer() { + let mut rng = thread_rng(); + for log_n in [1, 2, 3, 4, 5, 6, 7, 8] { + let n = 1 << log_n; + let f = random_scalars(n, &mut rng); + let g = random_scalars(n, &mut rng); + + let fg = poly_mul_less_slow(&f, &g); + + // FFT-based multiplication + let fft_fg = poly_mul_fft(&f, &g); + assert_eq!(fg, fft_fg); + + // Schwartz-Zippel test + let r = random_scalar(&mut rng); + let fg_rand = poly_eval(&f, &r).mul(poly_eval(&g, &r)); + let our_fg_rand = poly_eval(&fg, &r); + assert_eq!(our_fg_rand, fg_rand); + } +} + +#[test] +#[allow(non_snake_case)] +fn test_crypto_poly_shift() { + let mut rng = thread_rng(); + for num_coeffs_f in [1, 2, 3, 4, 5, 6, 7, 8] { + for n in 0..16 { + // compute the coefficients of X^n + let mut Xn = Vec::with_capacity(n + 1); + Xn.resize(n + 1, Scalar::ZERO); + Xn[n] = Scalar::ONE; + + // pick a random f + let f = random_scalars(num_coeffs_f, &mut rng); + + // f(X) * X^n via shift + let shifted1 = poly_xnmul(&f, n); + // f(X) * X^n via multiplication + let shifted2 = poly_mul_fft(&f, &Xn); + + assert_eq!(shifted1, shifted2); + } + } +} diff --git a/crates/aptos-dkg/tests/dkg.rs b/crates/aptos-dkg/tests/dkg.rs new file mode 100644 index 00000000000000..bd99c40d47b69f --- /dev/null +++ b/crates/aptos-dkg/tests/dkg.rs @@ -0,0 +1,84 @@ +// Copyright © Aptos Foundation + +use aptos_crypto::hash::CryptoHash; +use aptos_dkg::{ + pvss::{ + das, + das::unweighted_protocol, + insecure_field, test_utils, + test_utils::{reconstruct_dealt_secret_key_randomly, NoAux}, + traits::{SecretSharingConfig, Transcript}, + weighted::generic_weighting::GenericWeighting, + }, + utils::random::random_scalar, +}; +use rand::{rngs::StdRng, thread_rng}; +use rand_core::SeedableRng; + +#[test] +fn test_dkg_all_unweighted() { + let mut rng = thread_rng(); + let tcs = test_utils::get_threshold_configs_for_testing(); + let seed = random_scalar(&mut rng); + + aggregatable_dkg::(tcs.last().unwrap(), seed.to_bytes_le()); + aggregatable_dkg::(tcs.last().unwrap(), seed.to_bytes_le()); +} + +#[test] +fn test_dkg_all_weighted() { + let mut rng = thread_rng(); + let wcs = test_utils::get_weighted_configs_for_testing(); + let seed = random_scalar(&mut rng); + + aggregatable_dkg::>( + wcs.last().unwrap(), + seed.to_bytes_le(), + ); + aggregatable_dkg::>(wcs.last().unwrap(), seed.to_bytes_le()); + aggregatable_dkg::(wcs.last().unwrap(), seed.to_bytes_le()); +} + +/// Deals `n` times, aggregates all transcripts, and attempts to reconstruct the secret dealt in this +/// aggregated transcript. +fn aggregatable_dkg(sc: &T::SecretSharingConfig, seed_bytes: [u8; 32]) { + let mut rng = StdRng::from_seed(seed_bytes); + + let (pp, ssks, spks, dks, eks, iss, _, sk) = + test_utils::setup_dealing::(sc, &mut rng); + + let mut trxs = vec![]; + + // Deal `n` transcripts + for i in 0..sc.get_total_num_players() { + trxs.push(T::deal( + sc, + &pp, + &ssks[i], + &eks, + &iss[i], + &NoAux, + &sc.get_player(i), + &mut rng, + )); + } + + // Aggregate all `n` transcripts + let trx = T::aggregate(sc, trxs).unwrap(); + + // Verify the aggregated transcript + trx.verify( + sc, + &pp, + &spks, + &eks, + &(0..sc.get_total_num_players()) + .map(|_| NoAux) + .collect::>(), + ) + .expect("aggregated PVSS transcript failed verification"); + + if sk != reconstruct_dealt_secret_key_randomly::(sc, &mut rng, &dks, trx) { + panic!("Reconstructed SK did not match"); + } +} diff --git a/crates/aptos-dkg/tests/fft.rs b/crates/aptos-dkg/tests/fft.rs new file mode 100644 index 00000000000000..bfa03942e83553 --- /dev/null +++ b/crates/aptos-dkg/tests/fft.rs @@ -0,0 +1,73 @@ +// Copyright © Aptos Foundation + +#![allow(clippy::needless_range_loop)] + +use aptos_dkg::{ + algebra::{ + evaluation_domain::{BatchEvaluationDomain, EvaluationDomain}, + fft::{fft_assign, ifft_assign}, + polynomials::poly_eval, + }, + utils::random::random_scalars, +}; +use blstrs::Scalar; +use ff::Field; +use rand::thread_rng; + +#[test] +#[allow(non_snake_case)] +fn test_fft_batch_evaluation_domain() { + for N in 1..=16 { + let batch_dom = BatchEvaluationDomain::new(N); + + for k in 1..=N { + let dom1 = EvaluationDomain::new(k).unwrap(); + let dom2 = batch_dom.get_subdomain(k); + assert_eq!(dom1, dom2); + } + } +} + +#[test] +#[allow(non_snake_case)] +fn test_fft_assign() { + let mut rng = thread_rng(); + for n in [3, 5, 7, 8, 10, 16] { + let dom = EvaluationDomain::new(n).unwrap(); + + let mut f = random_scalars(n, &mut rng); + //println!("f(X): {:#?}", f); + let f_orig = f.clone(); + + // Computes $f(\omega^i)$ for all $i \in n$, where $\omega$ is an $n$th root of unity and + // $N$ is the smallest power of two larger than $n$ (or $n$ itself if $n=2^k$). + fft_assign(&mut f, &dom); + //println!("FFT(f(X)): {:#?}", f); + + // Correctness test #1: Test against inverse FFT. + let mut f_inv = f.clone(); + ifft_assign(&mut f_inv, &dom); + //println!("FFT^{{-1}}(FFT(f(X))): {:#?}", f_inv); + + if f_inv.len() > f_orig.len() { + let mut i = f_orig.len(); + while i < f_inv.len() { + assert_eq!(f_inv[i], Scalar::ZERO); + i += 1; + } + f_inv.truncate(f_orig.len()); + } + assert_eq!(f_inv, f_orig); + + // Correctness test #2: Test by re-evaluating naively at the roots of unity + let mut omega = Scalar::ONE; + for i in 0..n { + let y_i = poly_eval(&f_orig, &omega); + + //println!("y[{i}]: {}", y_i); + assert_eq!(y_i, f[i]); + + omega *= dom.get_primitive_root_of_unity(); + } + } +} diff --git a/crates/aptos-dkg/tests/pvss.rs b/crates/aptos-dkg/tests/pvss.rs new file mode 100644 index 00000000000000..5bc80c38a29883 --- /dev/null +++ b/crates/aptos-dkg/tests/pvss.rs @@ -0,0 +1,157 @@ +// Copyright © Aptos Foundation + +#![allow(clippy::needless_borrow)] +#![allow(clippy::ptr_arg)] +#![allow(clippy::let_and_return)] + +//! PVSS scheme-independent testing +use aptos_crypto::hash::CryptoHash; +use aptos_dkg::{ + constants::{G1_PROJ_NUM_BYTES, G2_PROJ_NUM_BYTES}, + pvss::{ + das, + das::unweighted_protocol, + insecure_field, test_utils, + test_utils::{reconstruct_dealt_secret_key_randomly, NoAux}, + traits::{transcript::Transcript, SecretSharingConfig}, + GenericWeighting, ThresholdConfig, + }, + utils::random::random_scalar, +}; +use rand::{rngs::StdRng, thread_rng}; +use rand_core::SeedableRng; + +#[test] +fn test_pvss_all_unweighted() { + let mut rng = thread_rng(); + + // + // Unweighted PVSS tests + // + let tcs = test_utils::get_threshold_configs_for_testing(); + for tc in tcs { + println!("\nTesting {tc} PVSS"); + + let seed = random_scalar(&mut rng); + + // Das + pvss_deal_verify_and_reconstruct::(&tc, seed.to_bytes_le()); + + // Insecure testing-only field-element PVSS + pvss_deal_verify_and_reconstruct::(&tc, seed.to_bytes_le()); + } +} + +#[test] +fn test_pvss_all_weighted() { + let mut rng = thread_rng(); + + // + // PVSS weighted tests + // + let wcs = test_utils::get_weighted_configs_for_testing(); + + for wc in wcs { + println!("\nTesting {wc} PVSS"); + let seed = random_scalar(&mut rng); + + // Generically-weighted Das + // WARNING: Insecure, due to encrypting different shares with the same randomness, do not use! + pvss_deal_verify_and_reconstruct::>( + &wc, + seed.to_bytes_le(), + ); + + // Generically-weighted field-element PVSS + // WARNING: Insecure, reveals the dealt secret and its shares. + pvss_deal_verify_and_reconstruct::>( + &wc, + seed.to_bytes_le(), + ); + + // Provably-secure Das PVSS + pvss_deal_verify_and_reconstruct::(&wc, seed.to_bytes_le()); + } +} + +#[test] +fn test_pvss_transcript_size() { + for (t, n) in [(333, 1_000), (666, 1_000), (3_333, 10_000), (6_666, 10_000)] { + println!(); + print_transcript_size::(t, n); + } +} + +fn print_transcript_size>(t: usize, n: usize) { + let name = T::scheme_name(); + let expected_size = expected_transcript_size::(t, n); + let actual_size = actual_transcript_size::(t, n); + + println!("Expected transcript size for {t}-out-of-{n} {name}: {expected_size} bytes"); + println!("Actual transcript size for {t}-out-of-{n} {name}: {actual_size} bytes"); +} + +// +// Helper functions +// + +/// Basic viability test for a PVSS transcript (weighted or unweighted): +/// 1. Deals a secret, creating a transcript +/// 2. Verifies the transcript. +/// 3. Ensures the a sufficiently-large random subset of the players can recover the dealt secret +fn pvss_deal_verify_and_reconstruct( + sc: &T::SecretSharingConfig, + seed_bytes: [u8; 32], +) { + // println!(); + // println!("Seed: {}", hex::encode(seed_bytes.as_slice())); + let mut rng = StdRng::from_seed(seed_bytes); + + let (pp, ssks, spks, dks, eks, _, s, sk) = test_utils::setup_dealing::(sc, &mut rng); + + // Test dealing + let trx = T::deal( + &sc, + &pp, + &ssks[0], + &eks, + &s, + &NoAux, + &sc.get_player(0), + &mut rng, + ); + trx.verify(&sc, &pp, &vec![spks[0].clone()], &eks, &vec![NoAux]) + .expect("PVSS transcript failed verification"); + + // Test transcript (de)serialization + let trx_deserialized = T::try_from(trx.to_bytes().as_slice()) + .expect("serialized transcript should deserialize correctly"); + + assert_eq!(trx, trx_deserialized); + if sk != reconstruct_dealt_secret_key_randomly::(sc, &mut rng, &dks, trx) { + panic!("Reconstructed SK did not match"); + } +} + +fn actual_transcript_size>( + t: usize, + n: usize, +) -> usize { + let (sc, mut rng) = test_utils::get_threshold_config_and_rng(t, n); + + let trx = T::generate(&sc, &mut rng); + let actual_size = trx.to_bytes().len(); + + actual_size +} + +fn expected_transcript_size>( + _t: usize, + n: usize, +) -> usize { + if T::scheme_name() == unweighted_protocol::DAS_SK_IN_G1 { + G2_PROJ_NUM_BYTES + (n + 1) * (G2_PROJ_NUM_BYTES + G1_PROJ_NUM_BYTES) + } else { + panic!("Did not implement support for '{}' yet", T::scheme_name()) + } +} diff --git a/crates/aptos-dkg/tests/weighted_vuf.rs b/crates/aptos-dkg/tests/weighted_vuf.rs new file mode 100644 index 00000000000000..5a1504226b8ad9 --- /dev/null +++ b/crates/aptos-dkg/tests/weighted_vuf.rs @@ -0,0 +1,185 @@ +#![allow(clippy::ptr_arg)] +#![allow(clippy::needless_borrow)] + +// Copyright © Aptos Foundation + +use aptos_dkg::{ + pvss, + pvss::{ + test_utils, + test_utils::NoAux, + traits::{Convert, SecretSharingConfig, Transcript}, + Player, WeightedConfig, + }, + utils::random::random_scalar, + weighted_vuf::{pinkas::PinkasWUF, traits::WeightedVUF}, +}; +use rand::{rngs::StdRng, thread_rng}; +use rand_core::SeedableRng; +use sha3::{Digest, Sha3_256}; + +#[test] +fn test_wvuf_basic_viability() { + weighted_wvuf_bvt::(); +} + +fn weighted_wvuf_bvt< + T: Transcript, + WVUF: WeightedVUF< + SecretKey = T::DealtSecretKey, + PubKey = T::DealtPubKey, + PubKeyShare = T::DealtPubKeyShare, + SecretKeyShare = T::DealtSecretKeyShare, + >, +>() +where + WVUF::PublicParameters: for<'a> From<&'a T::PublicParameters>, +{ + let mut rng = thread_rng(); + let seed = random_scalar(&mut rng); + + // Do a weighted PVSS + let mut rng = StdRng::from_seed(seed.to_bytes_le()); + + let (wc, pvss_pp, dks, sk, pk, trx) = weighted_pvss::(&mut rng); + + // Test decrypting SK shares, creating VUF proof shares, and aggregating those shares into a VUF + // proof, verifying that proof and finally deriving the VUF evaluation. + wvuf_randomly_aggregate_verify_and_derive_eval::( + &wc, &sk, &pk, &dks, &pvss_pp, &trx, &mut rng, + ); +} + +fn weighted_pvss>( + mut rng: &mut StdRng, +) -> ( + WeightedConfig, + T::PublicParameters, + Vec, + T::DealtSecretKey, + T::DealtPubKey, + T, +) { + let wc = WeightedConfig::new(10, vec![3, 5, 3, 4, 2, 1, 1, 7]).unwrap(); + + let (pvss_pp, ssks, spks, dks, eks, _, s, sk) = + test_utils::setup_dealing::(&wc, rng); + + let trx = T::deal( + &wc, + &pvss_pp, + &ssks[0], + &eks, + &s, + &NoAux, + &wc.get_player(0), + &mut rng, + ); + + let pk: ::DealtPubKey = s.to(&pvss_pp); + + // Make sure the PVSS dealt correctly + trx.verify(&wc, &pvss_pp, &vec![spks[0].clone()], &eks, &vec![NoAux]) + .expect("PVSS transcript failed verification"); + + (wc, pvss_pp, dks, sk, pk, trx) +} + +/// 1. Evaluates the VUF using the `sk` directly. +/// 2. Picks a random eligible subset of players and aggregates a VUF from it. +/// 3. Checks that the evaluation is the same as that from `sk`. +/// +/// `T` is a (non-weighted) `pvss::traits::Transcript` type. +fn wvuf_randomly_aggregate_verify_and_derive_eval< + T: Transcript, + WVUF: WeightedVUF< + SecretKey = T::DealtSecretKey, + PubKey = T::DealtPubKey, + PubKeyShare = T::DealtPubKeyShare, + SecretKeyShare = T::DealtSecretKeyShare, + >, + R: rand_core::RngCore + rand_core::CryptoRng, +>( + wc: &WeightedConfig, + sk: &T::DealtSecretKey, + pk: &T::DealtPubKey, + dks: &Vec, + pvss_pp: &T::PublicParameters, + trx: &T, + rng: &mut R, +) where + WVUF::PublicParameters: for<'a> From<&'a T::PublicParameters>, +{ + // Note: A WVUF scheme needs to implement conversion from all PVSS's public parameters to its own. + let vuf_pp = WVUF::PublicParameters::from(&pvss_pp); + + let msg = b"some msg"; + let eval = WVUF::eval(&sk, msg.as_slice()); + + let (mut sks, pks): (Vec, Vec) = (0..wc + .get_total_num_players()) + .map(|p| { + let (sk, pk) = trx.decrypt_own_share(&wc, &wc.get_player(p), &dks[p]); + (sk, pk) + }) + .collect::>() + .into_iter() + .unzip(); + + // we are going to be popping the SKs in reverse below (simplest way to move them out of the Vec) + sks.reverse(); + let augmented_key_pairs = (0..wc.get_total_num_players()) + .map(|p| { + let sk = sks.pop().unwrap(); + let pk = pks[p].clone(); + let (ask, apk) = WVUF::augment_key_pair(&vuf_pp, sk, pk.clone(), rng); + + // Test that pubkey augmentation works + let delta = WVUF::get_public_delta(&apk); + assert_eq!( + apk, + WVUF::augment_pubkey(&vuf_pp, pk, delta.clone()).unwrap() + ); + + (ask, apk) + }) + .collect::>(); + + let apks = augmented_key_pairs + .iter() + .map(|(_, apk)| Some(apk.clone())) + .collect::>>(); + + let apks_and_proofs = wc + .get_random_eligible_subset_of_players(rng) + .into_iter() + .map(|p| { + let ask = &augmented_key_pairs[p.id].0; + let apk = augmented_key_pairs[p.id].1.clone(); + + let proof = WVUF::create_share(ask, msg); + WVUF::verify_share(&vuf_pp, &apk, msg, &proof).expect("WVUF proof share should verify"); + + (p, apk, proof) + }) + .collect::>(); + + // Aggregate the VUF from the subset of capable players + let proof = WVUF::aggregate_shares(&wc, &apks_and_proofs); + + // Make sure the aggregated proof is valid + WVUF::verify_proof(&vuf_pp, pk, &apks[..], msg, &proof) + .expect("WVUF aggregated proof should verify"); + + // Derive the VUF evaluation + let eval_aggr = WVUF::derive_eval(&wc, &vuf_pp, msg, &apks[..], &proof) + .expect("WVUF derivation was expected to succeed"); + + // TODO: When APKs are missing, not yet testing proof verification and derivation. + + // Test that we can hash this via, say, SHA3 + let eval_bytes = bcs::to_bytes(&eval).unwrap(); + let _hash = Sha3_256::digest(eval_bytes.as_slice()).to_vec(); + + assert_eq!(eval_aggr, eval); +} From 861c974e167b53283a3b0c93e2f1cb580f330512 Mon Sep 17 00:00:00 2001 From: rtso <8248583+rtso@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:13:40 -0500 Subject: [PATCH 05/11] Fix tps metric --- .../indexer-grpc-fullnode/src/fullnode_data_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/fullnode_data_service.rs b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/fullnode_data_service.rs index 1f5bafe2a1a842..be5bd28c1c0e59 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/fullnode_data_service.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/fullnode_data_service.rs @@ -134,7 +134,7 @@ impl FullnodeData for FullnodeDataService { Some(highest_known_version as i64), Some(ma.avg() * 1000.0), Some(start_time.elapsed().as_secs_f64()), - Some(ma.sum() as i64), + Some((max_version - coordinator.current_version + 1) as i64), ); } }, From b8eefb595c118b28cb141d3f10907ba1fe3c2ea0 Mon Sep 17 00:00:00 2001 From: larry-aptos <112209412+larry-aptos@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:13:27 -0800 Subject: [PATCH 06/11] update the typescript proto gen. (#11761) --- .../aptos/bigquery_schema/v1/transaction.ts | 14 ++++++------ .../src/aptos/indexer/v1/raw_data.ts | 10 ++++----- .../internal/fullnode/v1/fullnode_data.ts | 8 +++---- .../src/aptos/transaction/v1/transaction.ts | 22 +++++++++---------- .../src/aptos/util/timestamp/timestamp.ts | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/protos/typescript/src/aptos/bigquery_schema/v1/transaction.ts b/protos/typescript/src/aptos/bigquery_schema/v1/transaction.ts index 0ecc8c533f8985..21dd57c89bbb36 100644 --- a/protos/typescript/src/aptos/bigquery_schema/v1/transaction.ts +++ b/protos/typescript/src/aptos/bigquery_schema/v1/transaction.ts @@ -59,13 +59,13 @@ export const Transaction = { encode(message: Transaction, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { if (message.version !== undefined && message.version !== BigInt("0")) { if (BigInt.asIntN(64, message.version) !== message.version) { - throw new Error("value provided for field message.version of type int64 too large"); + throw new globalThis.Error("value provided for field message.version of type int64 too large"); } writer.uint32(8).int64(message.version.toString()); } if (message.blockHeight !== undefined && message.blockHeight !== BigInt("0")) { if (BigInt.asIntN(64, message.blockHeight) !== message.blockHeight) { - throw new Error("value provided for field message.blockHeight of type int64 too large"); + throw new globalThis.Error("value provided for field message.blockHeight of type int64 too large"); } writer.uint32(16).int64(message.blockHeight.toString()); } @@ -89,7 +89,7 @@ export const Transaction = { } if (message.gasUsed !== undefined && message.gasUsed !== BigInt("0")) { if (BigInt.asUintN(64, message.gasUsed) !== message.gasUsed) { - throw new Error("value provided for field message.gasUsed of type uint64 too large"); + throw new globalThis.Error("value provided for field message.gasUsed of type uint64 too large"); } writer.uint32(72).uint64(message.gasUsed.toString()); } @@ -104,25 +104,25 @@ export const Transaction = { } if (message.numEvents !== undefined && message.numEvents !== BigInt("0")) { if (BigInt.asIntN(64, message.numEvents) !== message.numEvents) { - throw new Error("value provided for field message.numEvents of type int64 too large"); + throw new globalThis.Error("value provided for field message.numEvents of type int64 too large"); } writer.uint32(104).int64(message.numEvents.toString()); } if (message.numWriteSetChanges !== undefined && message.numWriteSetChanges !== BigInt("0")) { if (BigInt.asIntN(64, message.numWriteSetChanges) !== message.numWriteSetChanges) { - throw new Error("value provided for field message.numWriteSetChanges of type int64 too large"); + throw new globalThis.Error("value provided for field message.numWriteSetChanges of type int64 too large"); } writer.uint32(112).int64(message.numWriteSetChanges.toString()); } if (message.epoch !== undefined && message.epoch !== BigInt("0")) { if (BigInt.asIntN(64, message.epoch) !== message.epoch) { - throw new Error("value provided for field message.epoch of type int64 too large"); + throw new globalThis.Error("value provided for field message.epoch of type int64 too large"); } writer.uint32(120).int64(message.epoch.toString()); } if (message.insertedAt !== undefined && message.insertedAt !== BigInt("0")) { if (BigInt.asIntN(64, message.insertedAt) !== message.insertedAt) { - throw new Error("value provided for field message.insertedAt of type int64 too large"); + throw new globalThis.Error("value provided for field message.insertedAt of type int64 too large"); } writer.uint32(128).int64(message.insertedAt.toString()); } diff --git a/protos/typescript/src/aptos/indexer/v1/raw_data.ts b/protos/typescript/src/aptos/indexer/v1/raw_data.ts index 871f4af0166ae0..98e66947060f3f 100644 --- a/protos/typescript/src/aptos/indexer/v1/raw_data.ts +++ b/protos/typescript/src/aptos/indexer/v1/raw_data.ts @@ -64,7 +64,7 @@ export const TransactionsInStorage = { } if (message.startingVersion !== undefined) { if (BigInt.asUintN(64, message.startingVersion) !== message.startingVersion) { - throw new Error("value provided for field message.startingVersion of type uint64 too large"); + throw new globalThis.Error("value provided for field message.startingVersion of type uint64 too large"); } writer.uint32(16).uint64(message.startingVersion.toString()); } @@ -174,19 +174,19 @@ export const GetTransactionsRequest = { encode(message: GetTransactionsRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { if (message.startingVersion !== undefined) { if (BigInt.asUintN(64, message.startingVersion) !== message.startingVersion) { - throw new Error("value provided for field message.startingVersion of type uint64 too large"); + throw new globalThis.Error("value provided for field message.startingVersion of type uint64 too large"); } writer.uint32(8).uint64(message.startingVersion.toString()); } if (message.transactionsCount !== undefined) { if (BigInt.asUintN(64, message.transactionsCount) !== message.transactionsCount) { - throw new Error("value provided for field message.transactionsCount of type uint64 too large"); + throw new globalThis.Error("value provided for field message.transactionsCount of type uint64 too large"); } writer.uint32(16).uint64(message.transactionsCount.toString()); } if (message.batchSize !== undefined) { if (BigInt.asUintN(64, message.batchSize) !== message.batchSize) { - throw new Error("value provided for field message.batchSize of type uint64 too large"); + throw new globalThis.Error("value provided for field message.batchSize of type uint64 too large"); } writer.uint32(24).uint64(message.batchSize.toString()); } @@ -311,7 +311,7 @@ export const TransactionsResponse = { } if (message.chainId !== undefined) { if (BigInt.asUintN(64, message.chainId) !== message.chainId) { - throw new Error("value provided for field message.chainId of type uint64 too large"); + throw new globalThis.Error("value provided for field message.chainId of type uint64 too large"); } writer.uint32(16).uint64(message.chainId.toString()); } diff --git a/protos/typescript/src/aptos/internal/fullnode/v1/fullnode_data.ts b/protos/typescript/src/aptos/internal/fullnode/v1/fullnode_data.ts index 761ef5bb6ed55d..be987c3af53ff7 100644 --- a/protos/typescript/src/aptos/internal/fullnode/v1/fullnode_data.ts +++ b/protos/typescript/src/aptos/internal/fullnode/v1/fullnode_data.ts @@ -201,13 +201,13 @@ export const StreamStatus = { } if (message.startVersion !== undefined && message.startVersion !== BigInt("0")) { if (BigInt.asUintN(64, message.startVersion) !== message.startVersion) { - throw new Error("value provided for field message.startVersion of type uint64 too large"); + throw new globalThis.Error("value provided for field message.startVersion of type uint64 too large"); } writer.uint32(16).uint64(message.startVersion.toString()); } if (message.endVersion !== undefined) { if (BigInt.asUintN(64, message.endVersion) !== message.endVersion) { - throw new Error("value provided for field message.endVersion of type uint64 too large"); + throw new globalThis.Error("value provided for field message.endVersion of type uint64 too large"); } writer.uint32(24).uint64(message.endVersion.toString()); } @@ -325,13 +325,13 @@ export const GetTransactionsFromNodeRequest = { encode(message: GetTransactionsFromNodeRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { if (message.startingVersion !== undefined) { if (BigInt.asUintN(64, message.startingVersion) !== message.startingVersion) { - throw new Error("value provided for field message.startingVersion of type uint64 too large"); + throw new globalThis.Error("value provided for field message.startingVersion of type uint64 too large"); } writer.uint32(8).uint64(message.startingVersion.toString()); } if (message.transactionsCount !== undefined) { if (BigInt.asUintN(64, message.transactionsCount) !== message.transactionsCount) { - throw new Error("value provided for field message.transactionsCount of type uint64 too large"); + throw new globalThis.Error("value provided for field message.transactionsCount of type uint64 too large"); } writer.uint32(16).uint64(message.transactionsCount.toString()); } diff --git a/protos/typescript/src/aptos/transaction/v1/transaction.ts b/protos/typescript/src/aptos/transaction/v1/transaction.ts index de07f13d39dd60..061b8e21cd2842 100644 --- a/protos/typescript/src/aptos/transaction/v1/transaction.ts +++ b/protos/typescript/src/aptos/transaction/v1/transaction.ts @@ -1097,7 +1097,7 @@ export const Block = { } if (message.height !== undefined && message.height !== BigInt("0")) { if (BigInt.asUintN(64, message.height) !== message.height) { - throw new Error("value provided for field message.height of type uint64 too large"); + throw new globalThis.Error("value provided for field message.height of type uint64 too large"); } writer.uint32(16).uint64(message.height.toString()); } @@ -1254,7 +1254,7 @@ export const Transaction = { } if (message.version !== undefined && message.version !== BigInt("0")) { if (BigInt.asUintN(64, message.version) !== message.version) { - throw new Error("value provided for field message.version of type uint64 too large"); + throw new globalThis.Error("value provided for field message.version of type uint64 too large"); } writer.uint32(16).uint64(message.version.toString()); } @@ -1263,13 +1263,13 @@ export const Transaction = { } if (message.epoch !== undefined && message.epoch !== BigInt("0")) { if (BigInt.asUintN(64, message.epoch) !== message.epoch) { - throw new Error("value provided for field message.epoch of type uint64 too large"); + throw new globalThis.Error("value provided for field message.epoch of type uint64 too large"); } writer.uint32(32).uint64(message.epoch.toString()); } if (message.blockHeight !== undefined && message.blockHeight !== BigInt("0")) { if (BigInt.asUintN(64, message.blockHeight) !== message.blockHeight) { - throw new Error("value provided for field message.blockHeight of type uint64 too large"); + throw new globalThis.Error("value provided for field message.blockHeight of type uint64 too large"); } writer.uint32(40).uint64(message.blockHeight.toString()); } @@ -1527,7 +1527,7 @@ export const BlockMetadataTransaction = { } if (message.round !== undefined && message.round !== BigInt("0")) { if (BigInt.asUintN(64, message.round) !== message.round) { - throw new Error("value provided for field message.round of type uint64 too large"); + throw new globalThis.Error("value provided for field message.round of type uint64 too large"); } writer.uint32(16).uint64(message.round.toString()); } @@ -2094,7 +2094,7 @@ export const Event = { } if (message.sequenceNumber !== undefined && message.sequenceNumber !== BigInt("0")) { if (BigInt.asUintN(64, message.sequenceNumber) !== message.sequenceNumber) { - throw new Error("value provided for field message.sequenceNumber of type uint64 too large"); + throw new globalThis.Error("value provided for field message.sequenceNumber of type uint64 too large"); } writer.uint32(16).uint64(message.sequenceNumber.toString()); } @@ -2267,7 +2267,7 @@ export const TransactionInfo = { } if (message.gasUsed !== undefined && message.gasUsed !== BigInt("0")) { if (BigInt.asUintN(64, message.gasUsed) !== message.gasUsed) { - throw new Error("value provided for field message.gasUsed of type uint64 too large"); + throw new globalThis.Error("value provided for field message.gasUsed of type uint64 too large"); } writer.uint32(40).uint64(message.gasUsed.toString()); } @@ -2475,7 +2475,7 @@ export const EventKey = { encode(message: EventKey, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { if (message.creationNumber !== undefined && message.creationNumber !== BigInt("0")) { if (BigInt.asUintN(64, message.creationNumber) !== message.creationNumber) { - throw new Error("value provided for field message.creationNumber of type uint64 too large"); + throw new globalThis.Error("value provided for field message.creationNumber of type uint64 too large"); } writer.uint32(8).uint64(message.creationNumber.toString()); } @@ -2595,19 +2595,19 @@ export const UserTransactionRequest = { } if (message.sequenceNumber !== undefined && message.sequenceNumber !== BigInt("0")) { if (BigInt.asUintN(64, message.sequenceNumber) !== message.sequenceNumber) { - throw new Error("value provided for field message.sequenceNumber of type uint64 too large"); + throw new globalThis.Error("value provided for field message.sequenceNumber of type uint64 too large"); } writer.uint32(16).uint64(message.sequenceNumber.toString()); } if (message.maxGasAmount !== undefined && message.maxGasAmount !== BigInt("0")) { if (BigInt.asUintN(64, message.maxGasAmount) !== message.maxGasAmount) { - throw new Error("value provided for field message.maxGasAmount of type uint64 too large"); + throw new globalThis.Error("value provided for field message.maxGasAmount of type uint64 too large"); } writer.uint32(24).uint64(message.maxGasAmount.toString()); } if (message.gasUnitPrice !== undefined && message.gasUnitPrice !== BigInt("0")) { if (BigInt.asUintN(64, message.gasUnitPrice) !== message.gasUnitPrice) { - throw new Error("value provided for field message.gasUnitPrice of type uint64 too large"); + throw new globalThis.Error("value provided for field message.gasUnitPrice of type uint64 too large"); } writer.uint32(32).uint64(message.gasUnitPrice.toString()); } diff --git a/protos/typescript/src/aptos/util/timestamp/timestamp.ts b/protos/typescript/src/aptos/util/timestamp/timestamp.ts index 9d3078718d9e7a..4e41623d83e780 100644 --- a/protos/typescript/src/aptos/util/timestamp/timestamp.ts +++ b/protos/typescript/src/aptos/util/timestamp/timestamp.ts @@ -28,7 +28,7 @@ export const Timestamp = { encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { if (message.seconds !== undefined && message.seconds !== BigInt("0")) { if (BigInt.asIntN(64, message.seconds) !== message.seconds) { - throw new Error("value provided for field message.seconds of type int64 too large"); + throw new globalThis.Error("value provided for field message.seconds of type int64 too large"); } writer.uint32(8).int64(message.seconds.toString()); } From ced5cfd9c06a3556c6277651b7a06b697f2781b4 Mon Sep 17 00:00:00 2001 From: "Daniel Porteous (dport)" Date: Wed, 24 Jan 2024 12:46:09 -0800 Subject: [PATCH 07/11] Update generated proto code for TS (#11758) --- protos/typescript/CHANGELOG.md | 3 +++ protos/typescript/CONTRIBUTING.md | 3 +-- protos/typescript/package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/protos/typescript/CHANGELOG.md b/protos/typescript/CHANGELOG.md index 9418b622443db6..d051baaa1b12df 100644 --- a/protos/typescript/CHANGELOG.md +++ b/protos/typescript/CHANGELOG.md @@ -4,5 +4,8 @@ All notable changes to the Aptos Protos will be captured in this file. This chan ## Unreleased +## 1.1.3 +- Regenerated code with latest codegen tooling. + ## 1.1.2 - Initial release. diff --git a/protos/typescript/CONTRIBUTING.md b/protos/typescript/CONTRIBUTING.md index e0537fdb459034..4adf16385d12eb 100644 --- a/protos/typescript/CONTRIBUTING.md +++ b/protos/typescript/CONTRIBUTING.md @@ -4,8 +4,7 @@ To update the changelog do the following: 1. Bump the version in `package.json` according to [semver](https://semver.org/). -2. Bump the version in `version.ts`. -3. Add the change description in the CHANGELOG under the "Unreleased" section. +1. Add the change description in the CHANGELOG under the "Unreleased" section. ## Release process To release a new version of the package do the following. diff --git a/protos/typescript/package.json b/protos/typescript/package.json index 7957e2e34c315d..6aab84bbe9bbcc 100644 --- a/protos/typescript/package.json +++ b/protos/typescript/package.json @@ -1,7 +1,7 @@ { "name": "@aptos-labs/aptos-protos", "description": "Code generated from protobuf definitions for the Aptos tech stack", - "version": "1.1.2", + "version": "1.1.3", "packageManager": "pnpm@8.6.2", "license": "Apache-2.0", "engines": { From d38c66b65ad67f3035f7c0476aeb99d797cdc7f1 Mon Sep 17 00:00:00 2001 From: Teng Zhang Date: Wed, 24 Jan 2024 16:16:07 -0500 Subject: [PATCH 08/11] Add move tests for compiler V2 to CI (#11700) * add move test action * add framework to move coverage * fix --- .../move-tests-compiler-v2/action.yaml | 37 +++++++++++++++++ .github/workflows/coverage-move-only.yaml | 17 +++++++- .github/workflows/move-test-compiler-v2.yaml | 40 +++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 .github/actions/move-tests-compiler-v2/action.yaml create mode 100644 .github/workflows/move-test-compiler-v2.yaml diff --git a/.github/actions/move-tests-compiler-v2/action.yaml b/.github/actions/move-tests-compiler-v2/action.yaml new file mode 100644 index 00000000000000..66e3f0e2a5a632 --- /dev/null +++ b/.github/actions/move-tests-compiler-v2/action.yaml @@ -0,0 +1,37 @@ +name: Aptos Move Test for Compiler V2 +description: Runs Aptos Move tests with compiler V2 +inputs: + GIT_CREDENTIALS: + description: "Optional credentials to pass to git. Useful if you need to pull private repos for dependencies" + required: false + +runs: + using: composite + steps: + # Checkout the repository and setup the rust toolchain + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # get all the history because cargo xtest --change-since origin/main requires it. + - uses: aptos-labs/aptos-core/.github/actions/rust-setup@main + with: + GIT_CREDENTIALS: ${{ inputs.GIT_CREDENTIALS }} + + # Install nextest + - uses: taiki-e/install-action@v1.5.6 + with: + tool: nextest + + # Run Aptos Move tests with compiler V2 + - name: Run Aptos Move tests with compiler V2 + run: cargo nextest run --profile ci --locked -p e2e-move-tests -p aptos-framework --retries 3 --no-fail-fast + shell: bash + env: + MOVE_COMPILER_EXP: no-safety + MOVE_COMPILER_V2: true + RUST_MIN_STACK: 4297152 + MVP_TEST_ON_CI: true + SOLC_EXE: /home/runner/bin/solc + Z3_EXE: /home/runner/bin/z3 + CVC5_EXE: /home/runner/bin/cvc5 + DOTNET_ROOT: /home/runner/.dotnet + BOOGIE_EXE: /home/runner/.dotnet/tools/boogie diff --git a/.github/workflows/coverage-move-only.yaml b/.github/workflows/coverage-move-only.yaml index 80a4fd13577fa5..487a6df5bd9601 100644 --- a/.github/workflows/coverage-move-only.yaml +++ b/.github/workflows/coverage-move-only.yaml @@ -8,10 +8,14 @@ on: paths: - 'third_party/move/**' - 'aptos-move/e2e-move-tests/**' + - 'aptos-move/framework/**' + - '.github/workflows/coverage-move-only.yaml' pull_request: paths: - 'third_party/move/**' - 'aptos-move/e2e-move-tests/**' + - 'aptos-move/framework/**' + - '.github/workflows/coverage-move-only.yaml' env: CARGO_INCREMENTAL: "0" @@ -30,12 +34,23 @@ jobs: steps: - uses: actions/checkout@v3 - uses: aptos-labs/aptos-core/.github/actions/rust-setup@main + - name: prepare move lang prover tooling. + shell: bash + run: | + echo 'Z3_EXE='/home/runner/bin/z3 | tee -a $GITHUB_ENV + echo 'CVC5_EXE='/home/runner/bin/cvc5 | tee -a $GITHUB_ENV + echo 'DOTNET_ROOT='/home/runner/.dotnet/ | tee -a $GITHUB_ENV + echo 'BOOGIE_EXE='/home/runner/.dotnet/tools/boogie | tee -a $GITHUB_ENV + echo 'MVP_TEST_ON_CI'='1' | tee -a $GITHUB_ENV + echo "/home/runner/bin" | tee -a $GITHUB_PATH + echo "/home/runner/.dotnet" | tee -a $GITHUB_PATH + echo "/home/runner/.dotnet/tools" | tee -a $GITHUB_PATH - run: rustup component add llvm-tools-preview - uses: taiki-e/install-action@6f1ebcd9e21315fc37d7f7bc851dfcc8356d7da3 # pin@v1.5.6 with: tool: nextest,cargo-llvm-cov - run: docker run --detach -p 5432:5432 cimg/postgres:14.2 - - run: cargo llvm-cov --ignore-run-fail -p "move*" -p e2e-move-tests --lcov --jobs 32 --output-path lcov_unit.info + - run: cargo llvm-cov --ignore-run-fail -p aptos-framework -p "move*" -p e2e-move-tests --lcov --jobs 32 --output-path lcov_unit.info env: INDEXER_DATABASE_URL: postgresql://postgres@localhost/postgres - uses: actions/upload-artifact@v3 diff --git a/.github/workflows/move-test-compiler-v2.yaml b/.github/workflows/move-test-compiler-v2.yaml new file mode 100644 index 00000000000000..f3cffab69de9b2 --- /dev/null +++ b/.github/workflows/move-test-compiler-v2.yaml @@ -0,0 +1,40 @@ +name: "Aptos Move Test for Compiler V2" +on: + workflow_dispatch: + push: + branches: + - 'main' + paths: + - 'aptos-move/e2e-move-tests/**' + - 'aptos-move/framework/**' + - 'third_party/move/**' + - '.github/workflows/move-test-compiler-v2.yaml' + - '.github/actions/move-tests-compiler-v2/**' + pull_request: + paths: + - 'aptos-move/e2e-move-tests/**' + - 'aptos-move/framework/**' + - 'third_party/move/**' + - '.github/workflows/move-test-compiler-v2.yaml' + - '.github/actions/move-tests-compiler-v2/**' + +env: + CARGO_INCREMENTAL: "0" + CARGO_TERM_COLOR: always + +# cancel redundant builds +concurrency: + # cancel redundant builds on PRs (only on PR, not on branches) + group: ${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.ref) || github.sha }} + cancel-in-progress: true + +jobs: + # Run Aptos Move tests. + rust-move-tests: + runs-on: high-perf-docker + steps: + - uses: actions/checkout@v3 + - name: Run Aptos Move tests with compiler V2 + uses: ./.github/actions/move-tests-compiler-v2 + with: + GIT_CREDENTIALS: ${{ secrets.GIT_CREDENTIALS }} From e22fadc22dd607d431051be51cfe79680948ff0d Mon Sep 17 00:00:00 2001 From: Vineeth Kashyap Date: Wed, 24 Jan 2024 17:20:27 -0500 Subject: [PATCH 09/11] [minor] Removing profile causing build warnings (#11765) --- CODEOWNERS | 3 +++ crates/aptos-dkg/Cargo.toml | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index bdaaaa6e4977a0..996f685988c581 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -108,4 +108,7 @@ # Owners for the `/terraform` directory and all its subdirectories. /terraform/ @aptos-labs/prod-eng +# Owners for the `aptos-dkg` crate. +/crates/aptos-dkg @alinush + /types/src/transaction/authenticator.rs @alinush diff --git a/crates/aptos-dkg/Cargo.toml b/crates/aptos-dkg/Cargo.toml index 60a2f4e2c8899b..d8f40167fec40a 100644 --- a/crates/aptos-dkg/Cargo.toml +++ b/crates/aptos-dkg/Cargo.toml @@ -48,6 +48,3 @@ harness = false [[bench]] name = "lagrange" harness = false - -[profile.bench] -debug = true # for running cargo flamegraph on benchmarks From e772f560966e20aea2459d173e726b6303888778 Mon Sep 17 00:00:00 2001 From: larry-aptos <112209412+larry-aptos@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:53:23 -0800 Subject: [PATCH 10/11] dynamic tasks for processing the fullnode parsing. (#11698) * dynamic tasks for processing the fullnode parsing. * dynamic tasks for processing the fullnode parsing. * update comments. * update comments. * update comments. * skip the cache processing. * enable compression at grpc. * remove the enable_expensive loggin from fullnode; no longer used. * remove the enable_expensive loggin from fullnode; no longer used. * refactor. * refactor. --- Cargo.lock | 2 + config/src/config/indexer_grpc_config.rs | 37 --- .../indexer-grpc-fullnode/Cargo.toml | 2 + .../src/fullnode_data_service.rs | 6 +- .../src/localnet_data_service.rs | 6 +- .../indexer-grpc-fullnode/src/runtime.rs | 14 +- .../src/stream_coordinator.rs | 240 ++++++++++-------- .../indexer-grpc-utils/src/lib.rs | 5 +- 8 files changed, 156 insertions(+), 156 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ae420f065b711..3444021c62be3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2236,6 +2236,7 @@ dependencies = [ "aptos-vm", "aptos-vm-validator", "base64 0.13.1", + "bcs 0.1.4", "bytes", "chrono", "fail 0.5.1", @@ -2243,6 +2244,7 @@ dependencies = [ "goldenfile", "hex", "hyper", + "itertools 0.10.5", "move-binary-format", "move-core-types", "move-package", diff --git a/config/src/config/indexer_grpc_config.rs b/config/src/config/indexer_grpc_config.rs index 001b1f9c6ac576..7b275a6fc15f4b 100644 --- a/config/src/config/indexer_grpc_config.rs +++ b/config/src/config/indexer_grpc_config.rs @@ -41,8 +41,6 @@ pub struct IndexerGrpcConfig { /// Number of transactions returned in a single stream response pub output_batch_size: u16, - - pub enable_expensive_logging: bool, } impl Debug for IndexerGrpcConfig { @@ -57,7 +55,6 @@ impl Debug for IndexerGrpcConfig { .field("processor_task_count", &self.processor_task_count) .field("processor_batch_size", &self.processor_batch_size) .field("output_batch_size", &self.output_batch_size) - .field("enable_expensive_logging", &self.enable_expensive_logging) .finish() } } @@ -77,7 +74,6 @@ impl Default for IndexerGrpcConfig { processor_task_count: DEFAULT_PROCESSOR_TASK_COUNT, processor_batch_size: DEFAULT_PROCESSOR_BATCH_SIZE, output_batch_size: DEFAULT_OUTPUT_BATCH_SIZE, - enable_expensive_logging: false, } } } @@ -117,19 +113,6 @@ impl ConfigOptimizer for IndexerGrpcConfig { return Ok(false); } - // TODO: we really shouldn't be overriding the configs if they are - // specified in the local node config file. This optimizer should - // migrate to the pattern used by other optimizers, but for now, we'll - // just keep the legacy behaviour to avoid breaking anything. - - // Override with environment variables if they are set - indexer_config.enable_expensive_logging = env_var_or_default( - "INDEXER_GRPC_ENABLE_EXPENSIVE_LOGGING", - Some(indexer_config.enable_expensive_logging), - None, - ) - .unwrap_or(false); - Ok(true) } } @@ -194,23 +177,3 @@ mod tests { .unwrap(); } } - -/// Returns the value of the environment variable `env_var` -/// if it is set, otherwise returns `default`. -fn env_var_or_default( - env_var: &'static str, - default: Option, - expected_message: Option, -) -> Option { - let partial = std::env::var(env_var).ok().map(|s| s.parse().ok()); - match default { - None => partial.unwrap_or_else(|| { - panic!( - "{}", - expected_message - .unwrap_or_else(|| { format!("Expected env var {} to be set", env_var) }) - ) - }), - Some(default_value) => partial.unwrap_or(Some(default_value)), - } -} diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/Cargo.toml b/ecosystem/indexer-grpc/indexer-grpc-fullnode/Cargo.toml index adbec42b6b10e6..e687cf779d6c14 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/Cargo.toml +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/Cargo.toml @@ -15,12 +15,14 @@ rust-version = { workspace = true } [dependencies] anyhow = { workspace = true } base64 = { workspace = true } +bcs = { workspace = true } bytes = { workspace = true } chrono = { workspace = true } fail = { workspace = true } futures = { workspace = true } hex = { workspace = true } hyper = { workspace = true } +itertools = { workspace = true } once_cell = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/fullnode_data_service.rs b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/fullnode_data_service.rs index be5bd28c1c0e59..fe9f1364ea0d95 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/fullnode_data_service.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/fullnode_data_service.rs @@ -16,7 +16,6 @@ use tonic::{Request, Response, Status}; pub struct FullnodeDataService { pub service_context: ServiceContext, - pub enable_expensive_logging: bool, } type FullnodeResponseStream = @@ -50,7 +49,6 @@ impl FullnodeData for FullnodeDataService { let processor_task_count = self.service_context.processor_task_count; let processor_batch_size = self.service_context.processor_batch_size; let output_batch_size = self.service_context.output_batch_size; - let enable_expensive_logging = self.enable_expensive_logging; // Some node metadata let context = self.service_context.context.clone(); @@ -93,9 +91,7 @@ impl FullnodeData for FullnodeDataService { loop { let start_time = std::time::Instant::now(); // Processes and sends batch of transactions to client - let results = coordinator - .process_next_batch(enable_expensive_logging) - .await; + let results = coordinator.process_next_batch().await; let max_version = match IndexerStreamCoordinator::get_max_batch_version(results) { Ok(max_version) => max_version, Err(e) => { diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/localnet_data_service.rs b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/localnet_data_service.rs index 3891aa67f0b36d..8e72f82a8f53b4 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/localnet_data_service.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/localnet_data_service.rs @@ -23,7 +23,6 @@ type TransactionResponseStream = pub struct LocalnetDataService { pub service_context: ServiceContext, - pub enable_expensive_logging: bool, } /// External service on the fullnode is for testing/local development only. @@ -48,7 +47,6 @@ impl RawData for LocalnetDataService { // Creates a channel to send the stream to the client let (tx, mut rx) = mpsc::channel(TRANSACTION_CHANNEL_SIZE); let (external_service_tx, external_service_rx) = mpsc::channel(TRANSACTION_CHANNEL_SIZE); - let enable_expensive_logging = self.enable_expensive_logging; tokio::spawn(async move { // Initialize the coordinator that tracks starting version and processes transactions @@ -64,9 +62,7 @@ impl RawData for LocalnetDataService { ); loop { // Processes and sends batch of transactions to client - let results = coordinator - .process_next_batch(enable_expensive_logging) - .await; + let results = coordinator.process_next_batch().await; let max_version = match IndexerStreamCoordinator::get_max_batch_version(results) { Ok(max_version) => max_version, Err(e) => { diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/runtime.rs b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/runtime.rs index af4013bdeb52f3..79a92569572cbd 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/runtime.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/runtime.rs @@ -48,7 +48,6 @@ pub fn bootstrap( let processor_task_count = node_config.indexer_grpc.processor_task_count; let processor_batch_size = node_config.indexer_grpc.processor_batch_size; let output_batch_size = node_config.indexer_grpc.output_batch_size; - let enable_expensive_logging = node_config.indexer_grpc.enable_expensive_logging; runtime.spawn(async move { let context = Arc::new(Context::new(chain_id, db, mp_sender, node_config)); @@ -61,12 +60,8 @@ pub fn bootstrap( // If we are here, we know indexer grpc is enabled. let server = FullnodeDataService { service_context: service_context.clone(), - enable_expensive_logging, - }; - let localnet_data_server = LocalnetDataService { - service_context, - enable_expensive_logging, }; + let localnet_data_server = LocalnetDataService { service_context }; let reflection_service = tonic_reflection::server::Builder::configure() // Note: It is critical that the file descriptor set is registered for every @@ -88,7 +83,12 @@ pub fn bootstrap( .add_service(reflection_service_clone); let router = match use_data_service_interface { - false => tonic_server.add_service(FullnodeDataServer::new(server)), + false => { + let svc = FullnodeDataServer::new(server) + .send_compressed(CompressionEncoding::Gzip) + .accept_compressed(CompressionEncoding::Gzip); + tonic_server.add_service(svc) + }, true => { let svc = RawDataServer::new(localnet_data_server) .send_compressed(CompressionEncoding::Gzip) diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/stream_coordinator.rs b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/stream_coordinator.rs index 2880d11f69a89e..3e0793b4271f97 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/stream_coordinator.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/stream_coordinator.rs @@ -3,7 +3,7 @@ use crate::{ convert::convert_transaction, - counters::{FETCHED_LATENCY_IN_SECS, FETCHED_TRANSACTION, UNABLE_TO_FETCH_TRANSACTION}, + counters::{FETCHED_TRANSACTION, UNABLE_TO_FETCH_TRANSACTION}, runtime::{DEFAULT_NUM_RETRIES, RETRY_TIME_MILLIS}, }; use aptos_api::context::Context; @@ -19,18 +19,18 @@ use aptos_protos::{ transactions_from_node_response, TransactionsFromNodeResponse, TransactionsOutput, }, transaction::v1::Transaction as TransactionPB, + util::timestamp::Timestamp, }; use aptos_vm::data_cache::AsMoveResolver; -use std::{ - sync::Arc, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; +use itertools::Itertools; +use std::{sync::Arc, time::Duration}; use tokio::sync::mpsc; use tonic::Status; type EndVersion = u64; const SERVICE_TYPE: &str = "indexer_fullnode"; +const MINIMUM_TASK_LOAD_SIZE_IN_BYTES: usize = 1_000_000; // Basically a handler for a single GRPC stream request pub struct IndexerStreamCoordinator { @@ -80,69 +80,75 @@ impl IndexerStreamCoordinator { /// 2. Convert transactions to rust objects (for example stringifying move structs into json) /// 3. Convert into protobuf objects /// 4. Encode protobuf objects (base64) - pub async fn process_next_batch( - &mut self, - enable_expensive_logging: bool, - ) -> Vec> { + pub async fn process_next_batch(&mut self) -> Vec> { + let fetching_start_time = std::time::Instant::now(); + // Stage 1: fetch transactions from storage. + let sorted_transactions_from_storage_with_size = + self.fetch_transactions_from_storage().await; + let first_version = sorted_transactions_from_storage_with_size + .first() + .map(|(txn, _)| txn.version) + .unwrap() as i64; + let end_version = sorted_transactions_from_storage_with_size + .last() + .map(|(txn, _)| txn.version) + .unwrap() as i64; + let num_transactions = sorted_transactions_from_storage_with_size.len(); + let highest_known_version = self.highest_known_version as i64; + let (_, _, block_event) = self + .context + .db + .get_block_info_by_version(end_version as u64) + .unwrap_or_else(|_| { + panic!( + "[Indexer Fullnode] Could not get block_info for version {}", + end_version, + ) + }); + let last_transaction_timestamp_in_microseconds = block_event.proposed_time(); + let last_transaction_timestamp = Some(Timestamp { + seconds: (last_transaction_timestamp_in_microseconds / 1_000_000) as i64, + nanos: ((last_transaction_timestamp_in_microseconds % 1_000_000) * 1000) as i32, + }); + + log_grpc_step_fullnode( + IndexerGrpcStep::FullnodeFetchedBatch, + Some(first_version), + Some(end_version), + last_transaction_timestamp.as_ref(), + Some(highest_known_version), + None, + Some(fetching_start_time.elapsed().as_secs_f64()), + Some(num_transactions as i64), + ); + // Stage 2: convert transactions to rust objects. CPU-bound load. + let decoding_start_time = std::time::Instant::now(); + let mut task_batches = vec![]; + let mut current_batch = vec![]; + let mut current_batch_size = 0; + for (txn, size) in sorted_transactions_from_storage_with_size { + current_batch.push(txn); + current_batch_size += size; + if current_batch_size > MINIMUM_TASK_LOAD_SIZE_IN_BYTES { + task_batches.push(current_batch); + current_batch = vec![]; + current_batch_size = 0; + } + } + if !current_batch.is_empty() { + task_batches.push(current_batch); + } + + let output_batch_size = self.output_batch_size; let ledger_chain_id = self.context.chain_id().id(); let mut tasks = vec![]; - let batches = self.get_batches().await; - let output_batch_size = self.output_batch_size; - - for batch in batches { + for batch in task_batches { let context = self.context.clone(); - let ledger_version = self.highest_known_version; - let transaction_sender = self.transactions_sender.clone(); - let task = tokio::spawn(async move { - let fetch_start_time = std::time::Instant::now(); - // Fetch and convert transactions from API - let raw_txns = - Self::fetch_raw_txns_with_retries(context.clone(), ledger_version, batch).await; - let first_raw_transaction = raw_txns.first().unwrap(); - let last_raw_transaction = raw_txns.last().unwrap(); - let mut last_transaction_timestamp = None; - if enable_expensive_logging { - // Reusing the conversion methods which need a vec, so make a vec of size 1 - let api_txn = Self::convert_to_api_txns(context.clone(), vec![ - last_raw_transaction.clone(), - ]) - .await; - let pb_txn = Self::convert_to_pb_txns(api_txn); - last_transaction_timestamp = pb_txn.first().unwrap().timestamp.clone(); - } - log_grpc_step_fullnode( - IndexerGrpcStep::FullnodeFetchedBatch, - Some(first_raw_transaction.version as i64), - Some(last_raw_transaction.version as i64), - last_transaction_timestamp.as_ref(), - Some(ledger_version as i64), - None, - Some(fetch_start_time.elapsed().as_secs_f64()), - Some(raw_txns.len() as i64), - ); - - let convert_start_time = std::time::Instant::now(); - let api_txns = Self::convert_to_api_txns(context, raw_txns).await; - api_txns.last().map(record_fetched_transaction_latency); + let raw_txns = batch; + let api_txns = Self::convert_to_api_txns(context, raw_txns); let pb_txns = Self::convert_to_pb_txns(api_txns); - let start_transaction = pb_txns.first().unwrap(); - let end_transaction = pb_txns.last().unwrap(); - let end_txn_timestamp = end_transaction.timestamp.clone(); - - log_grpc_step_fullnode( - IndexerGrpcStep::FullnodeDecodedBatch, - Some(start_transaction.version as i64), - Some(end_transaction.version as i64), - end_txn_timestamp.as_ref(), - Some(ledger_version as i64), - None, - Some(convert_start_time.elapsed().as_secs_f64()), - Some(pb_txns.len() as i64), - ); - - let send_start_time = std::time::Instant::now(); - + let mut responses = vec![]; // Wrap in stream response object and send to channel for chunk in pb_txns.chunks(output_batch_size as usize) { for chunk in chunk_transactions(chunk.to_vec(), MESSAGE_SIZE_LIMIT) { @@ -154,39 +160,85 @@ impl IndexerStreamCoordinator { )), chain_id: ledger_chain_id as u32, }; - match transaction_sender.send(Result::<_, Status>::Ok(item)).await { - Ok(_) => {}, - Err(_) => { - // Client disconnects. - return Err(Status::aborted( - "[Indexer Fullnode] Client disconnected", - )); - }, - } + responses.push(item); } } - - log_grpc_step_fullnode( - IndexerGrpcStep::FullnodeSentBatch, - Some(start_transaction.version as i64), - Some(end_transaction.version as i64), - end_txn_timestamp.as_ref(), - Some(ledger_version as i64), - None, - Some(send_start_time.elapsed().as_secs_f64()), - Some(pb_txns.len() as i64), - ); - Ok(end_transaction.version) + responses }); tasks.push(task); } - match futures::future::try_join_all(tasks).await { - Ok(res) => res, + let responses = match futures::future::try_join_all(tasks).await { + Ok(res) => res.into_iter().flatten().collect::>(), Err(err) => panic!( "[Indexer Fullnode] Error processing transaction batches: {:?}", err ), + }; + log_grpc_step_fullnode( + IndexerGrpcStep::FullnodeDecodedBatch, + Some(first_version), + Some(end_version), + last_transaction_timestamp.as_ref(), + Some(highest_known_version), + None, + Some(decoding_start_time.elapsed().as_secs_f64()), + Some(num_transactions as i64), + ); + // Stage 3: send responses to stream + let sending_start_time = std::time::Instant::now(); + for response in responses { + if let Err(err) = self.transactions_sender.send(Ok(response)).await { + panic!( + "[Indexer Fullnode] Error sending transaction response to stream: {:?}", + err + ); + } } + log_grpc_step_fullnode( + IndexerGrpcStep::FullnodeSentBatch, + Some(first_version), + Some(end_version), + last_transaction_timestamp.as_ref(), + Some(highest_known_version), + None, + Some(sending_start_time.elapsed().as_secs_f64()), + Some(num_transactions as i64), + ); + vec![Ok(end_version as u64)] + } + + /// Fetches transactions from storage with each transaction's size. + /// Results are transactions sorted by version. + async fn fetch_transactions_from_storage(&mut self) -> Vec<(TransactionOnChainData, usize)> { + let batches = self.get_batches().await; + let mut storage_fetch_tasks = vec![]; + let ledger_version = self.highest_known_version; + for batch in batches { + let context = self.context.clone(); + let task = tokio::spawn(async move { + Self::fetch_raw_txns_with_retries(context.clone(), ledger_version, batch).await + }); + storage_fetch_tasks.push(task); + } + + let transactions_from_storage = + match futures::future::try_join_all(storage_fetch_tasks).await { + Ok(res) => res, + Err(err) => panic!( + "[Indexer Fullnode] Error fetching transaction batches: {:?}", + err + ), + }; + + transactions_from_storage + .into_iter() + .flatten() + .sorted_by(|a, b| a.version.cmp(&b.version)) + .map(|txn| { + let size = bcs::serialized_size(&txn).expect("Unable to serialize txn"); + (txn, size) + }) + .collect::>() } /// Gets the last version of the batch if the entire batch is successful, otherwise return error @@ -276,7 +328,7 @@ impl IndexerStreamCoordinator { } } - async fn convert_to_api_txns( + fn convert_to_api_txns( context: Arc, raw_txns: Vec, ) -> Vec { @@ -435,17 +487,3 @@ impl IndexerStreamCoordinator { } } } - -/// Record the transaction fetched from the storage latency. -fn record_fetched_transaction_latency(txn: &aptos_api_types::Transaction) { - let current_time_in_secs = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Current time is before UNIX_EPOCH") - .as_secs_f64(); - let txn_timestamp = txn.timestamp(); - - if txn_timestamp > 0 { - let txn_timestemp_in_secs = txn_timestamp as f64 / 1_000_000.0; - FETCHED_LATENCY_IN_SECS.set(current_time_in_secs - txn_timestemp_in_secs); - } -} diff --git a/ecosystem/indexer-grpc/indexer-grpc-utils/src/lib.rs b/ecosystem/indexer-grpc/indexer-grpc-utils/src/lib.rs index d008dfbcdd6769..a4ed1d966259d2 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-utils/src/lib.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-utils/src/lib.rs @@ -17,6 +17,7 @@ use aptos_protos::{ }; use prost::Message; use std::time::Duration; +use tonic::codec::CompressionEncoding; use url::Url; pub type GrpcClientType = FullnodeDataClient; @@ -38,7 +39,9 @@ pub async fn create_grpc_client(address: Url) -> GrpcClientType { ); Ok(client .max_decoding_message_size(usize::MAX) - .max_encoding_message_size(usize::MAX)) + .max_encoding_message_size(usize::MAX) + .send_compressed(CompressionEncoding::Gzip) + .accept_compressed(CompressionEncoding::Gzip)) }, Err(e) => { tracing::error!( From b04e03c76589316348cfcc29af80436f29cc179d Mon Sep 17 00:00:00 2001 From: Manu Dhundi Date: Wed, 24 Jan 2024 18:31:59 -0800 Subject: [PATCH 11/11] Compute correct accumulated effective block gas fees with module publishing txn(s) in the block (#11582) * Compute correct accumulated effective block gas fees with module publishing txn(s) in the block Start computing the accumulated block gas fee normally until you find a module publishing txn. Then recompute the accumulated block gas fee by setting conflict_multiplier == conflict_penalty_window for all preceeding txns, and continue accumulating with the same conflict_multiplier for subsequent txns in the block subsequent txns as well * Address review comments * Set 'use_module_publishing_block_conflict' flag and rebase with main * Address review comments; modify 'append_and_check_module_rw_conflict()' logic * Set 'use_module_publishing_block_conflict' in default_for_genesis() to true --- aptos-move/block-executor/src/executor.rs | 17 +++- .../block-executor/src/limit_processor.rs | 90 ++++++++++++++++++- .../src/txn_last_input_output.rs | 39 ++++---- execution/executor-benchmark/src/lib.rs | 6 +- types/src/block_executor/config.rs | 2 +- types/src/on_chain_config/execution_config.rs | 13 ++- 6 files changed, 144 insertions(+), 23 deletions(-) diff --git a/aptos-move/block-executor/src/executor.rs b/aptos-move/block-executor/src/executor.rs index 077d3ce9a56644..a1cb8daac55c94 100644 --- a/aptos-move/block-executor/src/executor.rs +++ b/aptos-move/block-executor/src/executor.rs @@ -1229,6 +1229,9 @@ where num_txns, ); + let last_input_output: TxnLastInputOutput = + TxnLastInputOutput::new(num_txns as TxnIndex); + for (idx, txn) in signature_verified_block.iter().enumerate() { let latest_view = LatestView::::new( base_view, @@ -1241,7 +1244,6 @@ where idx as TxnIndex, ); let res = executor.execute_transaction(&latest_view, txn, idx as TxnIndex); - let must_skip = matches!(res, ExecutionStatus::SkipRest(_)); match res { ExecutionStatus::Success(output) | ExecutionStatus::SkipRest(output) => { @@ -1267,6 +1269,7 @@ where } as u64 }); + let sequential_reads = latest_view.take_sequential_reads(); let read_write_summary = self .config .onchain @@ -1274,10 +1277,18 @@ where .conflict_penalty_window() .map(|_| { ReadWriteSummary::new( - latest_view.take_sequential_reads().get_read_summary(), + sequential_reads.get_read_summary(), output.get_write_summary(), ) }); + + if last_input_output.check_and_append_module_rw_conflict( + sequential_reads.module_reads.iter(), + output.module_write_set().keys(), + ) { + block_limit_processor.process_module_rw_conflict(); + } + block_limit_processor.accumulate_fee_statement( fee_statement, read_write_summary, @@ -1417,7 +1428,7 @@ where msg, ))); }, - } + }; // When the txn is a SkipRest txn, halt sequential execution. if must_skip { break; diff --git a/aptos-move/block-executor/src/limit_processor.rs b/aptos-move/block-executor/src/limit_processor.rs index 71781a739ad400..d5b09662a47da4 100644 --- a/aptos-move/block-executor/src/limit_processor.rs +++ b/aptos-move/block-executor/src/limit_processor.rs @@ -17,6 +17,7 @@ pub struct BlockGasLimitProcessor { txn_fee_statements: Vec, txn_read_write_summaries: Vec>, block_limit_reached: bool, + module_rw_conflict: bool, } impl BlockGasLimitProcessor { @@ -29,6 +30,7 @@ impl BlockGasLimitProcessor { txn_fee_statements: Vec::with_capacity(init_size), txn_read_write_summaries: Vec::with_capacity(init_size), block_limit_reached: false, + module_rw_conflict: false, } } @@ -58,7 +60,11 @@ impl BlockGasLimitProcessor { txn_read_write_summary.collapse_resource_group_conflicts() }, ); - self.compute_conflict_multiplier(conflict_overlap_length as usize) + if self.module_rw_conflict { + conflict_overlap_length as u64 + } else { + self.compute_conflict_multiplier(conflict_overlap_length as usize) + } } else { assert_none!(txn_read_write_summary); 1 @@ -83,6 +89,33 @@ impl BlockGasLimitProcessor { } } + pub(crate) fn process_module_rw_conflict(&mut self) { + if self.module_rw_conflict + || !self + .block_gas_limit_type + .use_module_publishing_block_conflict() + { + return; + } + + let conflict_multiplier = if let Some(conflict_overlap_length) = + self.block_gas_limit_type.conflict_penalty_window() + { + conflict_overlap_length + } else { + return; + }; + + self.accumulated_effective_block_gas = conflict_multiplier as u64 + * (self.accumulated_fee_statement.execution_gas_used() + * self + .block_gas_limit_type + .execution_gas_effective_multiplier() + + self.accumulated_fee_statement.io_gas_used() + * self.block_gas_limit_type.io_gas_effective_multiplier()); + self.module_rw_conflict = true; + } + fn should_end_block(&mut self, mode: &str) -> bool { if let Some(per_block_gas_limit) = self.block_gas_limit_type.block_gas_limit() { // When the accumulated block gas of the committed txns exceeds @@ -427,4 +460,59 @@ mod test { assert_eq!(processor.accumulated_effective_block_gas, 20); assert!(!processor.should_end_block_parallel()); } + + #[test] + fn test_module_publishing_txn_conflict() { + let conflict_penalty_window = 4; + let block_gas_limit = BlockGasLimitType::ComplexLimitV1 { + effective_block_gas_limit: 1000, + execution_gas_effective_multiplier: 1, + io_gas_effective_multiplier: 1, + conflict_penalty_window, + use_module_publishing_block_conflict: true, + block_output_limit: None, + include_user_txn_size_in_block_output: true, + add_block_limit_outcome_onchain: false, + use_granular_resource_group_conflicts: true, + }; + + let mut processor = BlockGasLimitProcessor::::new(block_gas_limit, 10); + processor.accumulate_fee_statement( + execution_fee(10), + Some(ReadWriteSummary::new( + to_map(&[InputOutputKey::Group(2, 2)]), + to_map(&[InputOutputKey::Group(2, 2)]), + )), + None, + ); + processor.accumulate_fee_statement( + execution_fee(20), + Some(ReadWriteSummary::new( + to_map(&[InputOutputKey::Group(1, 1)]), + to_map(&[InputOutputKey::Group(1, 1)]), + )), + None, + ); + assert_eq!(1, processor.compute_conflict_multiplier(8)); + assert_eq!(processor.accumulated_effective_block_gas, 30); + + processor.process_module_rw_conflict(); + assert_eq!( + processor.accumulated_effective_block_gas, + 30 * conflict_penalty_window as u64 + ); + + processor.accumulate_fee_statement( + execution_fee(25), + Some(ReadWriteSummary::new( + to_map(&[InputOutputKey::Group(1, 1)]), + to_map(&[InputOutputKey::Group(1, 1)]), + )), + None, + ); + assert_eq!( + processor.accumulated_effective_block_gas, + 55 * conflict_penalty_window as u64 + ); + } } diff --git a/aptos-move/block-executor/src/txn_last_input_output.rs b/aptos-move/block-executor/src/txn_last_input_output.rs index 21ccef3979cca2..972d588160e7b2 100644 --- a/aptos-move/block-executor/src/txn_last_input_output.rs +++ b/aptos-move/block-executor/src/txn_last_input_output.rs @@ -138,21 +138,10 @@ impl, E: Debug + Send + Clone> | ExecutionStatus::DelayedFieldsCodeInvariantError(_) => BTreeMap::new(), }; - if !self.module_read_write_intersection.load(Ordering::Relaxed) { - // Check if adding new read & write modules leads to intersections. - if Self::append_and_check( - input.module_reads.iter(), - &self.module_reads, - &self.module_writes, - ) || Self::append_and_check( - written_modules.keys(), - &self.module_writes, - &self.module_reads, - ) { - self.module_read_write_intersection - .store(true, Ordering::Release); - return false; - } + if self + .check_and_append_module_rw_conflict(input.module_reads.iter(), written_modules.keys()) + { + return false; } self.inputs[txn_idx as usize].store(Some(Arc::new(input))); @@ -161,6 +150,26 @@ impl, E: Debug + Send + Clone> true } + pub(crate) fn check_and_append_module_rw_conflict<'a>( + &self, + module_reads_keys: impl Iterator, + module_writes_keys: impl Iterator, + ) -> bool { + if self.module_read_write_intersection.load(Ordering::Relaxed) { + return true; + } + + // Check if adding new read & write modules leads to intersections. + if Self::append_and_check(module_reads_keys, &self.module_reads, &self.module_writes) + || Self::append_and_check(module_writes_keys, &self.module_writes, &self.module_reads) + { + self.module_read_write_intersection + .store(true, Ordering::Release); + return true; + } + false + } + pub(crate) fn read_set(&self, txn_idx: TxnIndex) -> Option>> { self.inputs[txn_idx as usize].load_full() } diff --git a/execution/executor-benchmark/src/lib.rs b/execution/executor-benchmark/src/lib.rs index cee8e602c7bb15..ada5b29b24944a 100644 --- a/execution/executor-benchmark/src/lib.rs +++ b/execution/executor-benchmark/src/lib.rs @@ -596,9 +596,11 @@ impl OverallMeasuring { ); info!("{} GPS: {} gas/s", prefix, delta_gas.gas / elapsed); info!( - "{} effectiveGPS: {} gas/s", + "{} effectiveGPS: {} gas/s ({} effective block gas, in {} s)", prefix, - delta_gas.effective_block_gas / elapsed + delta_gas.effective_block_gas / elapsed, + delta_gas.effective_block_gas, + elapsed ); info!("{} ioGPS: {} gas/s", prefix, delta_gas.io_gas / elapsed); info!( diff --git a/types/src/block_executor/config.rs b/types/src/block_executor/config.rs index a83eed4025a68a..2ae51eb88daff3 100644 --- a/types/src/block_executor/config.rs +++ b/types/src/block_executor/config.rs @@ -40,7 +40,7 @@ impl BlockExecutorConfigFromOnchain { io_gas_effective_multiplier: 1, block_output_limit: Some(1_000_000_000_000), conflict_penalty_window: 8, - use_module_publishing_block_conflict: false, + use_module_publishing_block_conflict: true, include_user_txn_size_in_block_output: true, add_block_limit_outcome_onchain: false, use_granular_resource_group_conflicts: false, diff --git a/types/src/on_chain_config/execution_config.rs b/types/src/on_chain_config/execution_config.rs index 4a1b1c4e938b13..da8ef22466c296 100644 --- a/types/src/on_chain_config/execution_config.rs +++ b/types/src/on_chain_config/execution_config.rs @@ -77,7 +77,7 @@ impl OnChainExecutionConfig { io_gas_effective_multiplier: 1, conflict_penalty_window: 6, use_granular_resource_group_conflicts: false, - use_module_publishing_block_conflict: false, + use_module_publishing_block_conflict: true, block_output_limit: Some(3 * 1024 * 1024), include_user_txn_size_in_block_output: true, add_block_limit_outcome_onchain: false, @@ -255,6 +255,17 @@ impl BlockGasLimitType { } } + pub fn use_module_publishing_block_conflict(&self) -> bool { + match self { + BlockGasLimitType::NoLimit => false, + BlockGasLimitType::Limit(_) => false, + BlockGasLimitType::ComplexLimitV1 { + use_module_publishing_block_conflict, + .. + } => *use_module_publishing_block_conflict, + } + } + pub fn include_user_txn_size_in_block_output(&self) -> bool { match self { BlockGasLimitType::NoLimit => false,