diff --git a/Cargo.lock b/Cargo.lock index 69e352be7b6b5..d248ddf6f1719 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -676,6 +676,12 @@ dependencies = [ "sha3 0.9.1", ] +[[package]] +name = "bytemuck" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a" + [[package]] name = "byteorder" version = "1.4.3" @@ -5425,6 +5431,12 @@ dependencies = [ "winreg", ] +[[package]] +name = "retain_mut" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" + [[package]] name = "ring" version = "0.16.20" @@ -5440,6 +5452,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "roaring" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd539cab4e32019956fe7e0cf160bb6d4802f4be2b52c4253d76d3bb0f85a5f7" +dependencies = [ + "bytemuck", + "byteorder", + "retain_mut", +] + [[package]] name = "rocksdb" version = "0.18.0" @@ -6852,6 +6875,7 @@ dependencies = [ "opentelemetry", "rand 0.7.3", "rand 0.8.5", + "roaring", "schemars", "serde 1.0.138", "serde-name", @@ -8400,6 +8424,7 @@ dependencies = [ "bstr", "bumpalo", "bytecode-interpreter-crypto", + "bytemuck", "byteorder", "bytes", "bzip2-sys", @@ -8774,7 +8799,9 @@ dependencies = [ "regex-syntax", "remove_dir_all", "reqwest", + "retain_mut", "ring", + "roaring", "rocksdb", "rust-ini", "rustc-demangle", diff --git a/crates/sui-benchmark/src/benchmark/transaction_creator.rs b/crates/sui-benchmark/src/benchmark/transaction_creator.rs index 045b206ae1436..09c2b39c388ca 100644 --- a/crates/sui-benchmark/src/benchmark/transaction_creator.rs +++ b/crates/sui-benchmark/src/benchmark/transaction_creator.rs @@ -68,7 +68,10 @@ fn make_cert(network_config: &NetworkConfig, tx: &Transaction) -> CertifiedTrans .key_pair(); let pubx = secx.public_key_bytes(); let sig = AuthoritySignature::new(&certificate.data, secx); - certificate.auth_sign_info.signatures.push((*pubx, sig)); + certificate + .auth_sign_info + .add_signature(sig, *pubx, &committee) + .unwrap(); } certificate } diff --git a/crates/sui-core/src/authority_active/checkpoint_driver/mod.rs b/crates/sui-core/src/authority_active/checkpoint_driver/mod.rs index 1db951db29eb4..e75fba393514a 100644 --- a/crates/sui-core/src/authority_active/checkpoint_driver/mod.rs +++ b/crates/sui-core/src/authority_active/checkpoint_driver/mod.rs @@ -445,10 +445,18 @@ where } Action::NewCert => { let available_authorities: BTreeSet<_> = checkpoint - .signatory_authorities() - .filter(|a| **a != self_name) - .cloned() - .collect(); + .signatory_authorities(committee) + .filter_map(|x| match x { + Ok(&a) => { + if a != self_name { + Some(Ok(a)) + } else { + None + } + } + Err(e) => Some(Err(e)), + }) + .collect::>()?; let (_, contents) = get_one_checkpoint_with_contents( active_authority.net.load().clone(), checkpoint.summary.sequence_number, @@ -485,8 +493,10 @@ where // We use the latest available authorities not the authorities that signed the checkpoint // since these might be gone after the epoch they were active. let available_authorities: BTreeSet<_> = latest_known_checkpoint - .signatory_authorities() - .cloned() + .signatory_authorities(&net.committee) + .collect::>>()? + .iter() + .map(|&&x| x) .collect(); // Check if the latest checkpoint is merely a signed checkpoint, and if diff --git a/crates/sui-core/src/authority_aggregator.rs b/crates/sui-core/src/authority_aggregator.rs index 5eea8c822a329..63bcad2e88a94 100644 --- a/crates/sui-core/src/authority_aggregator.rs +++ b/crates/sui-core/src/authority_aggregator.rs @@ -358,10 +358,11 @@ where let mut candidate_source_authorties: HashSet = cert .certificate .auth_sign_info - .signatures + .authorities(&self.committee) + .collect::>>()? .iter() - .map(|(name, _)| *name) - .collect(); + .map(|&&name| name) + .collect::>(); // Sample a `retries` number of distinct authorities by stake. let mut source_authorities: Vec = Vec::new(); @@ -1111,7 +1112,8 @@ where self.committee.epoch(), transaction_ref.clone(), state.signatures.clone(), - )); + &self.committee, + )?); } } // If we get back an error, then we aggregate and check @@ -1369,11 +1371,12 @@ where good_stake = stake, "Found an effect with good stake over threshold" ); - return Ok(CertifiedTransactionEffects::new( + return CertifiedTransactionEffects::new( certificate.auth_sign_info.epoch, effects, signatures, - )); + &self.committee, + ); } } diff --git a/crates/sui-core/src/unit_tests/authority_aggregator_tests.rs b/crates/sui-core/src/unit_tests/authority_aggregator_tests.rs index bd682b319a09a..c837377dd5deb 100644 --- a/crates/sui-core/src/unit_tests/authority_aggregator_tests.rs +++ b/crates/sui-core/src/unit_tests/authority_aggregator_tests.rs @@ -280,7 +280,9 @@ where committee.epoch(), transaction.unwrap().to_transaction(), votes, + committee, ) + .unwrap() } pub async fn do_cert( diff --git a/crates/sui-core/src/unit_tests/authority_tests.rs b/crates/sui-core/src/unit_tests/authority_tests.rs index c14d7e5d77c60..68443136a428f 100644 --- a/crates/sui-core/src/unit_tests/authority_tests.rs +++ b/crates/sui-core/src/unit_tests/authority_tests.rs @@ -471,6 +471,9 @@ pub async fn send_and_confirm_transaction( // Collect signatures from a quorum of authorities let committee = authority.committee.load(); + println!("{:?}", committee.voting_rights); + println!("{:?}", committee.index_map); + println!("{:?}", vote.auth_sign_info.authority); let mut builder = SignatureAggregator::try_new(transaction, &committee).unwrap(); let certificate = builder .append(vote.auth_sign_info.authority, vote.auth_sign_info.signature) @@ -522,6 +525,7 @@ async fn test_publish_dependent_module_ok() { bytes }; let authority = init_state_with_objects(vec![gas_payment_object]).await; + println!("{:?}", authority.committee.load().index_map); let data = TransactionData::new_module( sender, diff --git a/crates/sui-json-rpc-api/src/rpc_types.rs b/crates/sui-json-rpc-api/src/rpc_types.rs index 01c3ab1e31e17..4dd901e51ce3e 100644 --- a/crates/sui-json-rpc-api/src/rpc_types.rs +++ b/crates/sui-json-rpc-api/src/rpc_types.rs @@ -991,12 +991,8 @@ impl Display for SuiCertifiedTransaction { writeln!(writer, "Transaction Signature: {:?}", self.tx_signature)?; writeln!( writer, - "Signed Authorities : {:?}", - self.auth_sign_info - .signatures - .iter() - .map(|(name, _)| name) - .collect::>() + "Signed Authorities Bitmap: {:?}", + self.auth_sign_info.signers_map )?; write!(writer, "{}", &self.data)?; write!(f, "{}", writer) diff --git a/crates/sui-open-rpc/spec/openrpc.json b/crates/sui-open-rpc/spec/openrpc.json index af5fdc4bb270f..190d6424575a5 100644 --- a/crates/sui-open-rpc/spec/openrpc.json +++ b/crates/sui-open-rpc/spec/openrpc.json @@ -1267,7 +1267,8 @@ "type": "object", "required": [ "epoch", - "signatures" + "signatures", + "signers_map" ], "properties": { "epoch": { @@ -1278,18 +1279,11 @@ "signatures": { "type": "array", "items": { - "type": "array", - "items": [ - { - "$ref": "#/components/schemas/PublicKeyBytes" - }, - { - "$ref": "#/components/schemas/AuthoritySignature" - } - ], - "maxItems": 2, - "minItems": 2 + "$ref": "#/components/schemas/AuthoritySignature" } + }, + "signers_map": { + "$ref": "#/components/schemas/Base64" } } }, @@ -2352,9 +2346,6 @@ } ] }, - "PublicKeyBytes": { - "$ref": "#/components/schemas/Base64" - }, "PublishResponse": { "type": "object", "required": [ diff --git a/crates/sui-types/Cargo.toml b/crates/sui-types/Cargo.toml index af8b9468c371e..02397dff50e65 100644 --- a/crates/sui-types/Cargo.toml +++ b/crates/sui-types/Cargo.toml @@ -33,6 +33,7 @@ schemars ="0.8.10" tonic = "0.7" strum = "^0.24" strum_macros = "^0.24" +roaring = "0.9.0" # This version is incompatible with ed25519-dalek rand_latest = { version = "0.8.5", package = "rand" } diff --git a/crates/sui-types/src/committee.rs b/crates/sui-types/src/committee.rs index 7a7f8cbfcebe0..8a93013ddbe68 100644 --- a/crates/sui-types/src/committee.rs +++ b/crates/sui-types/src/committee.rs @@ -19,11 +19,15 @@ pub type StakeUnit = u64; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Committee { pub epoch: EpochId, - voting_rights: Vec<(AuthorityName, StakeUnit)>, + pub voting_rights: Vec<(AuthorityName, StakeUnit)>, pub total_votes: StakeUnit, // Note: this is a derived structure, no need to store. #[serde(skip)] expanded_keys: HashMap, + #[serde(skip)] + pub index_map: HashMap, + #[serde(skip)] + loaded: bool, } impl Committee { @@ -55,20 +59,63 @@ impl Committee { voting_rights.sort_by_key(|(a, _)| *a); let total_votes = voting_rights.iter().map(|(_, votes)| *votes).sum(); - let expanded_keys: HashMap<_, _> = voting_rights - .iter() - // TODO: Verify all code path to make sure we always have valid public keys. - // e.g. when a new validator is registering themself on-chain. - .map(|(addr, _)| (*addr, (*addr).try_into().expect("Invalid Authority Key"))) - .collect(); + + let (expanded_keys, index_map) = Self::load_inner(&voting_rights); + Ok(Committee { epoch, voting_rights, total_votes, expanded_keys, + index_map, + loaded: true, }) } + // We call this if these have not yet been computed + pub fn load_inner( + voting_rights: &[(AuthorityName, StakeUnit)], + ) -> ( + HashMap, + HashMap, + ) { + let expanded_keys: HashMap = voting_rights + .iter() + // TODO: Verify all code path to make sure we always have valid public keys. + // e.g. when a new validator is registering themself on-chain. + .map(|(addr, _)| (*addr, (*addr).try_into().expect("Invalid Authority Key"))) + .collect(); + + let index_map: HashMap = voting_rights + .iter() + .enumerate() + .map(|(index, (addr, _))| (*addr, index)) + .collect(); + (expanded_keys, index_map) + } + + pub fn reload_fields(&mut self) { + let (expanded_keys, index_map) = Committee::load_inner(&self.voting_rights); + self.expanded_keys = expanded_keys; + self.index_map = index_map; + self.loaded = true; + } + + pub fn authority_index(&self, author: &AuthorityName) -> Option { + if !self.loaded { + return self + .voting_rights + .iter() + .position(|(a, _)| a == author) + .map(|i| i as u32); + } + self.index_map.get(author).map(|i| *i as u32) + } + + pub fn authority_by_index(&self, index: u32) -> Option<&AuthorityName> { + self.voting_rights.get(index as usize).map(|(name, _)| name) + } + pub fn epoch(&self) -> EpochId { self.epoch } diff --git a/crates/sui-types/src/crypto.rs b/crates/sui-types/src/crypto.rs index a42ec0981a8b8..a8145c53ad874 100644 --- a/crates/sui-types/src/crypto.rs +++ b/crates/sui-types/src/crypto.rs @@ -5,6 +5,7 @@ use crate::committee::{Committee, EpochId}; use crate::error::{SuiError, SuiResult}; use crate::sui_serde::Base64; use crate::sui_serde::Readable; +use crate::sui_serde::SuiBitmap; use anyhow::anyhow; use anyhow::Error; use base64ct::Encoding; @@ -14,13 +15,14 @@ use ed25519_dalek::{Keypair as DalekKeypair, Verifier}; use narwhal_crypto::ed25519::{Ed25519KeyPair, Ed25519PrivateKey, Ed25519PublicKey}; use once_cell::sync::OnceCell; use rand::rngs::OsRng; +use roaring::RoaringBitmap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::serde_as; use serde_with::Bytes; use sha3::Sha3_256; use std::borrow::Borrow; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::str::FromStr; @@ -503,10 +505,14 @@ impl AuthoritySignInfo { /// at least the quorum threshold (2f+1) of the committee; when STRONG_THRESHOLD is false, /// the quorum is valid when the total stake is at least the validity threshold (f+1) of /// the committee. +#[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct AuthorityQuorumSignInfo { pub epoch: EpochId, - pub signatures: Vec<(AuthorityName, AuthoritySignature)>, + pub signatures: Vec, + #[schemars(with = "Base64")] + #[serde_as(as = "SuiBitmap")] + pub signers_map: RoaringBitmap, } pub type AuthorityStrongQuorumSignInfo = AuthorityQuorumSignInfo; @@ -528,6 +534,67 @@ static_assertions::assert_not_impl_any!(AuthorityWeakQuorumSignInfo: Hash, Eq, P impl AuthoritySignInfoTrait for AuthorityQuorumSignInfo {} impl AuthorityQuorumSignInfo { + pub fn new(epoch: EpochId) -> Self { + AuthorityQuorumSignInfo { + epoch, + signatures: vec![], + signers_map: RoaringBitmap::new(), + } + } + + pub fn new_with_signatures( + epoch: EpochId, + mut signatures: Vec<(PublicKeyBytes, AuthoritySignature)>, + committee: &Committee, + ) -> SuiResult { + let mut map = RoaringBitmap::new(); + + signatures.sort_by_key(|(public_key, _)| *public_key); + + for (pk, _) in &signatures { + map.insert( + committee + .authority_index(pk) + .ok_or(SuiError::UnknownSigner)? as u32, + ); + } + let sigs: Vec = signatures.into_iter().map(|(_, sig)| sig).collect(); + + Ok(AuthorityQuorumSignInfo { + epoch, + signatures: sigs, + signers_map: map, + }) + } + + // This takes log(sig) time, do not use if not necessary + pub fn add_signature( + &mut self, + sig: AuthoritySignature, + pk: PublicKeyBytes, + committee: &Committee, + ) -> SuiResult<()> { + println!("{:?}", committee.index_map); + let index = committee + .authority_index(&pk) + .ok_or(SuiError::UnknownSigner)? as u32; + self.signers_map.insert(index); + self.signatures + .insert((self.signers_map.rank(index) - 1) as usize, sig); + Ok(()) + } + + pub fn authorities<'a>( + &'a self, + committee: &'a Committee, + ) -> impl Iterator> { + self.signers_map.iter().map(|i| { + committee + .authority_by_index(i) + .ok_or(SuiError::InvalidAuthenticator) + }) + } + pub fn add_to_verification_obligation( &self, committee: &Committee, @@ -541,18 +608,26 @@ impl AuthorityQuorumSignInfo { expected_epoch: committee.epoch() } ); + fp_ensure!( + self.signatures.len() as u64 == self.signers_map.len(), + SuiError::InvalidAuthorityBitmap { + error: "Authority bitmap and signatures have different lengths".to_string() + } + ); let mut weight = 0; - let mut used_authorities = HashSet::new(); // Create obligations for the committee signatures - for (authority, signature) in self.signatures.iter() { - // Check that each authority only appears once. - fp_ensure!( - !used_authorities.contains(authority), - SuiError::CertificateAuthorityReuse - ); - used_authorities.insert(*authority); + for signature in self.signatures.iter() { + obligation.signatures.push(signature.0); + obligation.message_index.push(message_index); + } + + for authority_index in self.signers_map.iter() { + let authority = committee + .authority_by_index(authority_index) + .ok_or(SuiError::UnknownSigner)?; + // Update weight. let voting_rights = committee.weight(authority); fp_ensure!(voting_rights > 0, SuiError::UnknownSigner); @@ -561,8 +636,6 @@ impl AuthorityQuorumSignInfo { obligation .public_keys .push(committee.public_key(authority)?); - obligation.signatures.push(signature.0); - obligation.message_index.push(message_index); } let threshold = if STRONG_THRESHOLD { diff --git a/crates/sui-types/src/error.rs b/crates/sui-types/src/error.rs index 595155ad87dd6..a74ab6cf840d0 100644 --- a/crates/sui-types/src/error.rs +++ b/crates/sui-types/src/error.rs @@ -96,6 +96,8 @@ pub enum SuiError { expected_sequence: SequenceNumber, given_sequence: SequenceNumber, }, + #[error("Invalid Authority Bitmap: {}", error)] + InvalidAuthorityBitmap { error: String }, #[error("Conflicting transaction already received: {pending_transaction:?}")] ConflictingTransaction { pending_transaction: TransactionDigest, diff --git a/crates/sui-types/src/messages.rs b/crates/sui-types/src/messages.rs index fc34dd0e0e16f..f9c48aee7e491 100644 --- a/crates/sui-types/src/messages.rs +++ b/crates/sui-types/src/messages.rs @@ -1155,11 +1155,14 @@ impl CertifiedTransactionEffects { epoch: EpochId, effects: TransactionEffects, signatures: Vec<(AuthorityName, AuthoritySignature)>, - ) -> Self { - Self { + committee: &Committee, + ) -> SuiResult { + Ok(Self { effects, - auth_signature: AuthorityStrongQuorumSignInfo { epoch, signatures }, - } + auth_signature: AuthorityStrongQuorumSignInfo::new_with_signatures( + epoch, signatures, committee, + )?, + }) } pub fn to_unsigned_effects(self) -> UnsignedTransactionEffects { @@ -1251,8 +1254,7 @@ impl<'a> SignatureAggregator<'a> { // Update certificate. self.partial .auth_sign_info - .signatures - .push((authority, signature)); + .add_signature(signature, authority, self.committee)?; if self.weight >= self.committee.quorum_threshold() { Ok(Some(self.partial.clone())) @@ -1264,21 +1266,30 @@ impl<'a> SignatureAggregator<'a> { impl CertifiedTransaction { pub fn new(epoch: EpochId, transaction: Transaction) -> CertifiedTransaction { - Self::new_with_signatures(epoch, transaction, vec![]) + CertifiedTransaction { + transaction_digest: transaction.transaction_digest, + is_verified: false, + data: transaction.data, + tx_signature: transaction.tx_signature, + auth_sign_info: AuthorityStrongQuorumSignInfo::new(epoch), + } } pub fn new_with_signatures( epoch: EpochId, transaction: Transaction, signatures: Vec<(AuthorityName, AuthoritySignature)>, - ) -> CertifiedTransaction { - CertifiedTransaction { + committee: &Committee, + ) -> SuiResult { + Ok(CertifiedTransaction { transaction_digest: transaction.transaction_digest, is_verified: false, data: transaction.data, tx_signature: transaction.tx_signature, - auth_sign_info: AuthorityStrongQuorumSignInfo { epoch, signatures }, - } + auth_sign_info: AuthorityStrongQuorumSignInfo::new_with_signatures( + epoch, signatures, committee, + )?, + }) } pub fn to_transaction(self) -> Transaction { @@ -1324,12 +1335,8 @@ impl Display for CertifiedTransaction { writeln!(writer, "Transaction Hash: {:?}", self.digest())?; writeln!( writer, - "Signed Authorities : {:?}", - self.auth_sign_info - .signatures - .iter() - .map(|(name, _)| name) - .collect::>() + "Signed Authorities Bitmap : {:?}", + self.auth_sign_info.signers_map )?; write!(writer, "{}", &self.data.kind)?; write!(f, "{}", writer) diff --git a/crates/sui-types/src/messages_checkpoint.rs b/crates/sui-types/src/messages_checkpoint.rs index fd8bd9da46e06..5f4c93c047a0f 100644 --- a/crates/sui-types/src/messages_checkpoint.rs +++ b/crates/sui-types/src/messages_checkpoint.rs @@ -6,6 +6,7 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::base_types::ExecutionDigests; use crate::committee::EpochId; use crate::crypto::{AuthoritySignInfo, AuthorityWeakQuorumSignInfo, Signable}; +use crate::error::SuiResult; use crate::messages::CertifiedTransaction; use crate::waypoint::{Waypoint, WaypointDiff}; use crate::{ @@ -310,21 +311,25 @@ impl CertifiedCheckpointSummary { let certified_checkpoint = CertifiedCheckpointSummary { summary: signed_checkpoints[0].summary.clone(), - auth_signature: AuthorityWeakQuorumSignInfo { - epoch: committee.epoch, - signatures: signed_checkpoints + auth_signature: AuthorityWeakQuorumSignInfo::new_with_signatures( + committee.epoch, + signed_checkpoints .into_iter() .map(|v| (v.auth_signature.authority, v.auth_signature.signature)) .collect(), - }, + committee, + )?, }; certified_checkpoint.verify(committee)?; Ok(certified_checkpoint) } - pub fn signatory_authorities(&self) -> impl Iterator { - self.auth_signature.signatures.iter().map(|(name, _)| name) + pub fn signatory_authorities<'a>( + &'a self, + committee: &'a Committee, + ) -> impl Iterator> { + self.auth_signature.authorities(committee) } /// Check that a certificate is valid, and signed by a quorum of authorities diff --git a/crates/sui-types/src/sui_serde.rs b/crates/sui-types/src/sui_serde.rs index 0e36309a0e0f0..004de18a43182 100644 --- a/crates/sui-types/src/sui_serde.rs +++ b/crates/sui-types/src/sui_serde.rs @@ -10,13 +10,14 @@ use move_core_types::account_address::AccountAddress; use schemars::JsonSchema; use serde; use serde::de::{Deserializer, Error}; -use serde::ser::Serializer; +use serde::ser::{Error as SerError, Serializer}; use serde::Deserialize; use serde::Serialize; -use serde_with::{DeserializeAs, SerializeAs}; +use serde_with::{Bytes, DeserializeAs, SerializeAs}; use crate::base_types::{decode_bytes_hex, encode_bytes_hex}; +#[inline] fn to_custom_error<'de, D, E>(e: E) -> D::Error where E: Debug, @@ -25,6 +26,15 @@ where D::Error::custom(format!("byte deserialization failed, cause by: {:?}", e)) } +#[inline] +fn to_custom_ser_error(e: E) -> S::Error +where + E: Debug, + S: Serializer, +{ + S::Error::custom(format!("byte serialization failed, cause by: {:?}", e)) +} + /// Use with serde_as to encode/decode bytes to/from Base64/Hex for human-readable serializer and deserializer /// E : Encoding of the human readable output /// R : serde_as SerializeAs/DeserializeAs delegation @@ -181,6 +191,34 @@ impl Base64 { } } +/// Serializes a bitmap according to the roaring bitmap on-disk standard. +/// https://github.com/RoaringBitmap/RoaringFormatSpec +pub struct SuiBitmap; + +impl SerializeAs for SuiBitmap { + fn serialize_as(source: &roaring::RoaringBitmap, serializer: S) -> Result + where + S: Serializer, + { + let mut bytes = vec![]; + + source + .serialize_into(&mut bytes) + .map_err(to_custom_ser_error::)?; + Bytes::serialize_as(&bytes, serializer) + } +} + +impl<'de> DeserializeAs<'de, roaring::RoaringBitmap> for SuiBitmap { + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes: Vec = Bytes::deserialize_as(deserializer)?; + roaring::RoaringBitmap::deserialize_from(&bytes[..]).map_err(to_custom_error::<'de, D, _>) + } +} + impl Encoding for Hex { fn decode(s: &str) -> Result, anyhow::Error> { decode_bytes_hex(s) diff --git a/crates/sui-types/src/unit_tests/messages_tests.rs b/crates/sui-types/src/unit_tests/messages_tests.rs index 9e871685cab0f..2d1a430f0b7cc 100644 --- a/crates/sui-types/src/unit_tests/messages_tests.rs +++ b/crates/sui-types/src/unit_tests/messages_tests.rs @@ -4,6 +4,8 @@ use std::collections::BTreeMap; +use roaring::RoaringBitmap; + use crate::crypto::get_key_pair; use super::*; @@ -145,3 +147,220 @@ fn test_certificates() { assert!(SignatureAggregator::try_new(bad_transaction, &committee).is_err()); } + +#[derive(Serialize, Deserialize)] +struct Foo(String); +impl BcsSignable for Foo {} + +#[test] +fn test_new_with_signatures() { + let mut signatures: Vec<(AuthorityName, AuthoritySignature)> = Vec::new(); + let mut authorities = BTreeMap::new(); + + for _ in 0..5 { + let (_, sec) = get_key_pair(); + let sig = AuthoritySignature::new(&Foo("some data".to_string()), &sec); + signatures.push((*sec.public_key_bytes(), sig)); + authorities.insert(*sec.public_key_bytes(), 1); + } + let (_, sec) = get_key_pair(); + authorities.insert(*sec.public_key_bytes(), 1); + + let committee = Committee::new(0, authorities.clone()).unwrap(); + let quorum = + AuthorityStrongQuorumSignInfo::new_with_signatures(0, signatures.clone(), &committee) + .unwrap(); + + let sig_clone = signatures.clone(); + let mut alphabetical_authorities = sig_clone + .iter() + .map(|(pubx, _)| pubx) + .collect::>(); + alphabetical_authorities.sort(); + assert_eq!( + quorum + .authorities(&committee) + .collect::>>() + .unwrap(), + alphabetical_authorities + ); +} + +#[test] +fn test_add_signatures() { + let mut signatures: Vec<(AuthorityName, AuthoritySignature)> = Vec::new(); + let mut authorities = BTreeMap::new(); + let mut quorum = AuthorityStrongQuorumSignInfo::new(0); + + for _ in 0..5 { + let (_, sec) = get_key_pair(); + let sig = AuthoritySignature::new(&Foo("some data".to_string()), &sec); + signatures.push((*sec.public_key_bytes(), sig)); + authorities.insert(*sec.public_key_bytes(), 1); + } + + let committee = Committee::new(0, authorities.clone()).unwrap(); + + for (name, sig) in &signatures { + assert!(quorum.add_signature(*sig, *name, &committee).is_ok()); + } + + let sig_clone = signatures.clone(); + let mut alphabetical_authorities = sig_clone + .iter() + .map(|(pubx, _)| pubx) + .collect::>(); + alphabetical_authorities.sort(); + + assert_eq!( + quorum + .authorities(&committee) + .collect::>>() + .unwrap(), + alphabetical_authorities + ); +} + +fn get_obligation_input(value: &T) -> (VerificationObligation, usize) +where + T: BcsSignable, +{ + let mut obligation = VerificationObligation::default(); + + // Add the obligation of the authority signature verifications. + let mut sign_message: Vec = Vec::new(); + value.write(&mut sign_message); + let idx = obligation.add_message(sign_message); + (obligation, idx) +} + +#[test] +fn test_handle_reject_malicious_signature() { + let message: messages_tests::Foo = Foo("some data".to_string()); + let mut signatures: Vec<(AuthorityName, AuthoritySignature)> = Vec::new(); + let mut authorities = BTreeMap::new(); + + for _ in 0..5 { + let (_, sec) = get_key_pair(); + let sig = AuthoritySignature::new(&Foo("some data".to_string()), &sec); + authorities.insert(*sec.public_key_bytes(), 1); + signatures.push((*sec.public_key_bytes(), sig)); + } + + let committee = Committee::new(0, authorities.clone()).unwrap(); + let mut quorum = + AuthorityStrongQuorumSignInfo::new_with_signatures(0, signatures, &committee).unwrap(); + { + let (_, sec) = get_key_pair(); + let sig = AuthoritySignature::new(&message, &sec); + let sigs_len = quorum.signatures.len(); + quorum.signatures[sigs_len - 1] = sig; + } + let (mut obligation, idx) = get_obligation_input(&message); + assert!(quorum + .add_to_verification_obligation(&committee, &mut obligation, idx) + .is_ok()); + assert!(obligation.verify_all().is_err()); +} + +#[test] +fn test_bitmap_out_of_range() { + let message: messages_tests::Foo = Foo("some data".to_string()); + let mut signatures: Vec<(AuthorityName, AuthoritySignature)> = Vec::new(); + let mut authorities = BTreeMap::new(); + for _ in 0..5 { + let (_, sec) = get_key_pair(); + let sig = AuthoritySignature::new(&Foo("some data".to_string()), &sec); + authorities.insert(*sec.public_key_bytes(), 1); + signatures.push((*sec.public_key_bytes(), sig)); + } + + let committee = Committee::new(0, authorities.clone()).unwrap(); + let mut quorum = + AuthorityStrongQuorumSignInfo::new_with_signatures(0, signatures, &committee).unwrap(); + + // Insert outside of range + quorum.signers_map.insert(10); + + let (mut obligation, idx) = get_obligation_input(&message); + assert!(quorum + .add_to_verification_obligation(&committee, &mut obligation, idx) + .is_err()); +} + +#[test] +fn test_reject_extra_public_key() { + let message: messages_tests::Foo = Foo("some data".to_string()); + let mut signatures: Vec<(AuthorityName, AuthoritySignature)> = Vec::new(); + let mut authorities = BTreeMap::new(); + for _ in 0..5 { + let (_, sec) = get_key_pair(); + let sig = AuthoritySignature::new(&Foo("some data".to_string()), &sec); + authorities.insert(*sec.public_key_bytes(), 1); + signatures.push((*sec.public_key_bytes(), sig)); + } + + signatures.sort_by_key(|k| k.0); + + let used_signatures: Vec<(AuthorityName, AuthoritySignature)> = + vec![signatures[0], signatures[1], signatures[2], signatures[3]]; + + let committee = Committee::new(0, authorities.clone()).unwrap(); + let mut quorum = + AuthorityStrongQuorumSignInfo::new_with_signatures(0, used_signatures, &committee).unwrap(); + + quorum.signers_map.insert(3); + + let (mut obligation, idx) = get_obligation_input(&message); + assert!(quorum + .add_to_verification_obligation(&committee, &mut obligation, idx) + .is_ok()); +} + +#[test] +fn test_reject_reuse_signatures() { + let message: messages_tests::Foo = Foo("some data".to_string()); + let mut signatures: Vec<(AuthorityName, AuthoritySignature)> = Vec::new(); + let mut authorities = BTreeMap::new(); + for _ in 0..5 { + let (_, sec) = get_key_pair(); + let sig = AuthoritySignature::new(&Foo("some data".to_string()), &sec); + authorities.insert(*sec.public_key_bytes(), 1); + signatures.push((*sec.public_key_bytes(), sig)); + } + + let used_signatures: Vec<(AuthorityName, AuthoritySignature)> = + vec![signatures[0], signatures[1], signatures[2], signatures[2]]; + + let committee = Committee::new(0, authorities.clone()).unwrap(); + let quorum = + AuthorityStrongQuorumSignInfo::new_with_signatures(0, used_signatures, &committee).unwrap(); + + let (mut obligation, idx) = get_obligation_input(&message); + assert!(quorum + .add_to_verification_obligation(&committee, &mut obligation, idx) + .is_err()); +} + +#[test] +fn test_empty_bitmap() { + let message: messages_tests::Foo = Foo("some data".to_string()); + let mut signatures: Vec<(AuthorityName, AuthoritySignature)> = Vec::new(); + let mut authorities = BTreeMap::new(); + for _ in 0..5 { + let (_, sec) = get_key_pair(); + let sig = AuthoritySignature::new(&Foo("some data".to_string()), &sec); + authorities.insert(*sec.public_key_bytes(), 1); + signatures.push((*sec.public_key_bytes(), sig)); + } + + let committee = Committee::new(0, authorities.clone()).unwrap(); + let mut quorum = + AuthorityStrongQuorumSignInfo::new_with_signatures(0, signatures, &committee).unwrap(); + quorum.signers_map = RoaringBitmap::new(); + + let (mut obligation, idx) = get_obligation_input(&message); + assert!(quorum + .add_to_verification_obligation(&committee, &mut obligation, idx) + .is_err()); +} diff --git a/crates/workspace-hack/Cargo.toml b/crates/workspace-hack/Cargo.toml index 17fb0ff76d91f..a291f463babae 100644 --- a/crates/workspace-hack/Cargo.toml +++ b/crates/workspace-hack/Cargo.toml @@ -65,6 +65,7 @@ blst = { version = "0.3" } bs58 = { version = "0.4", features = ["alloc", "std"] } bstr = { version = "0.2", features = ["lazy_static", "regex-automata", "serde", "serde1", "serde1-nostd", "std", "unicode"] } bytecode-interpreter-crypto = { git = "https://github.com/move-language/move", rev = "f07e99473e6edfff22f30596dd493ac770f0bb4a", features = ["fiat"] } +bytemuck = { version = "1", default-features = false } byteorder = { version = "1", features = ["i128", "std"] } bytes = { version = "1", features = ["std"] } bzip2-sys = { version = "0.1", default-features = false, features = ["static"] } @@ -387,7 +388,9 @@ regex-automata = { version = "0.1", features = ["regex-syntax", "std"] } regex-syntax = { version = "0.6", features = ["unicode", "unicode-age", "unicode-bool", "unicode-case", "unicode-gencat", "unicode-perl", "unicode-script", "unicode-segment"] } remove_dir_all = { version = "0.5", default-features = false } reqwest = { version = "0.11", features = ["__tls", "blocking", "default-tls", "hyper-tls", "json", "native-tls-crate", "serde_json", "tokio-native-tls"] } +retain_mut = { version = "0.1", default-features = false } ring = { version = "0.16", features = ["alloc", "dev_urandom_fallback", "once_cell"] } +roaring = { version = "0.9", default-features = false } rocksdb = { version = "0.18", features = ["bzip2", "lz4", "snappy", "zlib", "zstd"] } rust-ini = { version = "0.13", default-features = false } rustc-demangle = { version = "0.1", default-features = false } @@ -611,6 +614,7 @@ bs58 = { version = "0.4", features = ["alloc", "std"] } bstr = { version = "0.2", features = ["lazy_static", "regex-automata", "serde", "serde1", "serde1-nostd", "std", "unicode"] } bumpalo = { version = "3" } bytecode-interpreter-crypto = { git = "https://github.com/move-language/move", rev = "f07e99473e6edfff22f30596dd493ac770f0bb4a", features = ["fiat"] } +bytemuck = { version = "1", default-features = false } byteorder = { version = "1", features = ["i128", "std"] } bytes = { version = "1", features = ["std"] } bzip2-sys = { version = "0.1", default-features = false, features = ["static"] } @@ -985,7 +989,9 @@ regex-automata = { version = "0.1", features = ["regex-syntax", "std"] } regex-syntax = { version = "0.6", features = ["unicode", "unicode-age", "unicode-bool", "unicode-case", "unicode-gencat", "unicode-perl", "unicode-script", "unicode-segment"] } remove_dir_all = { version = "0.5", default-features = false } reqwest = { version = "0.11", features = ["__tls", "blocking", "default-tls", "hyper-tls", "json", "native-tls-crate", "serde_json", "tokio-native-tls"] } +retain_mut = { version = "0.1", default-features = false } ring = { version = "0.16", features = ["alloc", "dev_urandom_fallback", "once_cell"] } +roaring = { version = "0.9", default-features = false } rocksdb = { version = "0.18", features = ["bzip2", "lz4", "snappy", "zlib", "zstd"] } rust-ini = { version = "0.13", default-features = false } rustc-demangle = { version = "0.1", default-features = false }