From 74a0942fc1971bae5cb34afdea8bd9bf31737b17 Mon Sep 17 00:00:00 2001 From: deuszx Date: Thu, 20 Feb 2025 13:50:22 +0000 Subject: [PATCH 1/6] Use secp256k1 for validator keys. --- CLI.md | 3 +- Cargo.lock | 1 + linera-base/Cargo.toml | 1 + linera-base/src/crypto/ed25519.rs | 39 ++++--- linera-base/src/crypto/mod.rs | 8 +- linera-base/src/crypto/secp256k1.rs | 102 +++++++++++++++++- linera-chain/src/chain.rs | 4 +- linera-chain/src/data_types.rs | 12 +-- linera-chain/src/test.rs | 3 +- linera-chain/src/unit_tests/chain_tests.rs | 8 +- .../src/unit_tests/data_types_tests.rs | 58 +++++++--- linera-client/src/client_options.rs | 6 +- linera-client/src/config.rs | 6 +- linera-client/src/unit_tests/util.rs | 9 +- linera-core/src/chain_worker/config.rs | 28 ++++- linera-core/src/client/mod.rs | 10 +- linera-core/src/data_types.rs | 9 +- linera-core/src/unit_tests/test_utils.rs | 24 ++--- linera-core/src/unit_tests/worker_tests.rs | 16 +-- linera-core/src/worker.rs | 22 +++- linera-execution/src/committee.rs | 19 +++- linera-execution/tests/test_execution.rs | 10 +- linera-rpc/src/grpc/conversions.rs | 35 +++--- linera-sdk/src/test/validator.rs | 25 +++-- linera-service/src/cli_wrappers/local_net.rs | 27 +++-- linera-service/src/cli_wrappers/wallet.rs | 14 ++- linera-service/src/linera/main.rs | 3 + linera-service/src/proxy/grpc.rs | 8 +- linera-service/src/server.rs | 30 ++++-- linera-service/tests/local_net_tests.rs | 16 +-- linera-storage/src/lib.rs | 2 +- 31 files changed, 403 insertions(+), 155 deletions(-) diff --git a/CLI.md b/CLI.md index 6925e5e77179..3f8ffca2c2d8 100644 --- a/CLI.md +++ b/CLI.md @@ -404,11 +404,12 @@ Synchronizes a validator with the local state of chains Add or modify a validator (admin only) -**Usage:** `linera set-validator [OPTIONS] --public-key --address
` +**Usage:** `linera set-validator [OPTIONS] --public-key --account-key --address
` ###### **Options:** * `--public-key ` — The public key of the validator +* `--account-key ` — The public key of the account controlled by the validator * `--address
` — Network address * `--votes ` — Voting power diff --git a/Cargo.lock b/Cargo.lock index 911e9cb15a47..c6a8a2bc7748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4387,6 +4387,7 @@ dependencies = [ "async-graphql-derive", "async-trait", "bcs", + "bincode", "cfg-if", "cfg_aliases", "chrono", diff --git a/linera-base/Cargo.toml b/linera-base/Cargo.toml index 2bbb5fb8ba7d..a7fc20e0850c 100644 --- a/linera-base/Cargo.toml +++ b/linera-base/Cargo.toml @@ -79,6 +79,7 @@ port-selector.workspace = true zstd.workspace = true [dev-dependencies] +bincode.workspace = true linera-base = { path = ".", default-features = false, features = ["test"] } linera-witty = { workspace = true, features = ["test"] } test-case.workspace = true diff --git a/linera-base/src/crypto/ed25519.rs b/linera-base/src/crypto/ed25519.rs index d5246f085f79..294d815c3d0c 100644 --- a/linera-base/src/crypto/ed25519.rs +++ b/linera-base/src/crypto/ed25519.rs @@ -288,23 +288,6 @@ impl Ed25519Signature { }) } - /// Checks an optional signature. - pub fn check_optional_signature<'de, T>( - signature: Option<&Self>, - value: &T, - author: &Ed25519PublicKey, - ) -> Result<(), CryptoError> - where - T: BcsSignable<'de> + fmt::Debug, - { - match signature { - Some(sig) => sig.check(value, *author), - None => Err(CryptoError::MissingSignature { - type_name: T::type_name().to_string(), - }), - } - } - fn verify_batch_internal<'a, 'de, T, I>( value: &'a T, votes: I, @@ -435,4 +418,26 @@ mod tests { assert!(s.check(&tsx, addr1).is_err()); assert!(s.check(&foo, addr1).is_err()); } + + #[test] + fn test_publickey_serialization() { + use crate::crypto::ed25519::Ed25519PublicKey; + let key_in = Ed25519PublicKey::test_key(0); + let s = serde_json::to_string(&key_in).unwrap(); + let key_out: Ed25519PublicKey = serde_json::from_str(&s).unwrap(); + assert_eq!(key_out, key_in); + + let s = bincode::serialize(&key_in).unwrap(); + let key_out: Ed25519PublicKey = bincode::deserialize(&s).unwrap(); + assert_eq!(key_out, key_in); + } + + #[test] + fn test_secretkey_serialization() { + use crate::crypto::ed25519::Ed25519SecretKey; + let key_in = Ed25519SecretKey::generate(); + let s = serde_json::to_string(&key_in).unwrap(); + let key_out: Ed25519SecretKey = serde_json::from_str(&s).unwrap(); + assert_eq!(key_out.0.to_bytes(), key_in.0.to_bytes()); + } } diff --git a/linera-base/src/crypto/mod.rs b/linera-base/src/crypto/mod.rs index 40ec1fcdaca1..6baf3f1464e6 100644 --- a/linera-base/src/crypto/mod.rs +++ b/linera-base/src/crypto/mod.rs @@ -17,11 +17,13 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; /// The public key of a validator. -pub type ValidatorPublicKey = ed25519::Ed25519PublicKey; +pub type ValidatorPublicKey = secp256k1::Secp256k1PublicKey; /// The private key of a validator. -pub type ValidatorSecretKey = ed25519::Ed25519SecretKey; +pub type ValidatorSecretKey = secp256k1::Secp256k1SecretKey; /// The signature of a validator. -pub type ValidatorSignature = ed25519::Ed25519Signature; +pub type ValidatorSignature = secp256k1::Secp256k1Signature; +/// The key pair of a validator. +pub type ValidatorKeypair = secp256k1::Secp256k1KeyPair; /// The public key of a chain owner. /// The corresponding private key is allowed to propose blocks diff --git a/linera-base/src/crypto/secp256k1.rs b/linera-base/src/crypto/secp256k1.rs index 709699cad7bd..be7a6cd60c0d 100644 --- a/linera-base/src/crypto/secp256k1.rs +++ b/linera-base/src/crypto/secp256k1.rs @@ -20,7 +20,7 @@ pub static SECP256K1: LazyLock> = LazyLock::new(secp256k1::Secp25 pub struct Secp256k1SecretKey(pub secp256k1::SecretKey); /// A secp256k1 public key. -#[derive(Eq, PartialEq, Copy, Clone)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash)] pub struct Secp256k1PublicKey(pub secp256k1::PublicKey); /// Secp256k1 public/secret key pair. @@ -36,12 +36,49 @@ pub struct Secp256k1KeyPair { #[derive(Eq, PartialEq, Copy, Clone)] pub struct Secp256k1Signature(pub secp256k1::ecdsa::Signature); +impl Secp256k1PublicKey { + /// A fake public key used for testing. + #[cfg(with_testing)] + pub fn test_key(seed: u8) -> Self { + use rand::SeedableRng; + + let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64); + let secp = secp256k1::Secp256k1::signing_only(); + Self(secp.generate_keypair(&mut rng).1) + } +} + impl fmt::Debug for Secp256k1SecretKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "") } } +impl Serialize for Secp256k1SecretKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + // This is only used for JSON configuration. + assert!(serializer.is_human_readable()); + serializer.serialize_str(&hex::encode(self.0.secret_bytes())) + } +} + +impl<'de> Deserialize<'de> for Secp256k1SecretKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + // This is only used for JSON configuration. + assert!(deserializer.is_human_readable()); + let str = String::deserialize(deserializer)?; + let bytes = hex::decode(&str).map_err(serde::de::Error::custom)?; + let sk = secp256k1::SecretKey::from_slice(&bytes).map_err(serde::de::Error::custom)?; + Ok(Secp256k1SecretKey(sk)) + } +} + impl Serialize for Secp256k1PublicKey { fn serialize(&self, serializer: S) -> Result where @@ -50,7 +87,10 @@ impl Serialize for Secp256k1PublicKey { if serializer.is_human_readable() { serializer.serialize_str(&hex::encode(self.0.serialize())) } else { - serializer.serialize_newtype_struct("Secp256k1PublicKey", &self.0) + #[derive(Serialize)] + #[serde(rename = "Secp256k1PublicKey")] + struct Foo<'a>(&'a secp256k1::PublicKey); + Foo(&self.0).serialize(serializer) } } } @@ -69,7 +109,6 @@ impl<'de> Deserialize<'de> for Secp256k1PublicKey { #[derive(Deserialize)] #[serde(rename = "Secp256k1PublicKey")] struct Foo(secp256k1::PublicKey); - let value = Foo::deserialize(deserializer)?; Ok(Self(value.0)) } @@ -128,9 +167,17 @@ impl Secp256k1KeyPair { impl Secp256k1SecretKey { /// Returns a public key for the given secret key. - pub fn to_public(&self) -> Secp256k1PublicKey { + pub fn public(&self) -> Secp256k1PublicKey { Secp256k1PublicKey(self.0.public_key(&SECP256K1)) } + + /// Copies the key pair, **including the secret key**. + /// + /// The `Clone` and `Copy` traits are deliberately not implemented for `Secp256k1SecretKey` to prevent + /// accidental copies of secret keys. + pub fn copy(&self) -> Self { + Self(self.0) + } } impl Secp256k1Signature { @@ -159,6 +206,28 @@ impl Secp256k1Signature { type_name: T::type_name().to_string(), }) } + + pub fn verify_batch<'a, 'de, T, I>(value: &'a T, votes: I) -> Result<(), CryptoError> + where + T: BcsSignable<'de> + fmt::Debug, + I: IntoIterator, + { + let message = Message::from_digest(CryptoHash::new(value).as_bytes().0); + for (author, signature) in votes { + SECP256K1 + .verify_ecdsa(&message, &signature.0, &author.0) + .expect("Invalid signature"); + } + Ok(()) + } + + /// Converts the signature to a byte array. + /// Expects the signature to be serialized in compact form. + pub fn from_slice>(bytes: A) -> Result { + let sig = secp256k1::ecdsa::Signature::from_compact(bytes.as_ref()) + .map_err(CryptoError::Secp256k1Error)?; + Ok(Secp256k1Signature(sig)) + } } impl Serialize for Secp256k1Signature { @@ -210,9 +279,10 @@ impl fmt::Debug for Secp256k1Signature { } doc_scalar!(Secp256k1Signature, "A secp256k1 signature value"); +doc_scalar!(Secp256k1PublicKey, "A secp256k1 public key value"); #[cfg(with_testing)] -mod secp256k1_tests { +mod tests { #[test] fn test_signatures() { use serde::{Deserialize, Serialize}; @@ -240,4 +310,26 @@ mod secp256k1_tests { assert!(s.check(&tsx, &keypair1.public_key).is_err()); assert!(s.check(&foo, &keypair1.public_key).is_err()); } + + #[test] + fn test_publickey_serialization() { + use crate::crypto::secp256k1::Secp256k1PublicKey; + let key_in = Secp256k1PublicKey::test_key(0); + let s = serde_json::to_string(&key_in).unwrap(); + let key_out: Secp256k1PublicKey = serde_json::from_str(&s).unwrap(); + assert_eq!(key_out, key_in); + + let s = bincode::serialize(&key_in).unwrap(); + let key_out: Secp256k1PublicKey = bincode::deserialize(&s).unwrap(); + assert_eq!(key_out, key_in); + } + + #[test] + fn test_secretkey_deserialization() { + use crate::crypto::secp256k1::{Secp256k1KeyPair, Secp256k1SecretKey}; + let key_in = Secp256k1KeyPair::generate().secret_key; + let s = serde_json::to_string(&key_in).unwrap(); + let key_out: Secp256k1SecretKey = serde_json::from_str(&s).unwrap(); + assert_eq!(key_out, key_in); + } } diff --git a/linera-chain/src/chain.rs b/linera-chain/src/chain.rs index 05a3a608d998..563f142be269 100644 --- a/linera-chain/src/chain.rs +++ b/linera-chain/src/chain.rs @@ -608,7 +608,7 @@ where self.execution_state.system.ownership.get().clone(), BlockHeight(0), local_time, - maybe_committee.flat_map(|(_, committee)| committee.keys_and_weights()), + maybe_committee.flat_map(|(_, committee)| committee.fallback_keys_and_weights()), ) } @@ -1040,7 +1040,7 @@ where let maybe_committee = self.execution_state.system.current_committee().into_iter(); let ownership = self.execution_state.system.ownership.get().clone(); let fallback_owners = - maybe_committee.flat_map(|(_, committee)| committee.keys_and_weights()); + maybe_committee.flat_map(|(_, committee)| committee.fallback_keys_and_weights()); self.pending_validated_blobs.clear(); self.pending_proposed_blobs.clear(); self.manager diff --git a/linera-chain/src/data_types.rs b/linera-chain/src/data_types.rs index c1a4fa096a34..4550bc83170e 100644 --- a/linera-chain/src/data_types.rs +++ b/linera-chain/src/data_types.rs @@ -810,13 +810,13 @@ impl BlockProposal { impl LiteVote { /// Uses the signing key to create a signed object. - pub fn new(value: LiteValue, round: Round, key_pair: &ValidatorSecretKey) -> Self { + pub fn new(value: LiteValue, round: Round, secret_key: &ValidatorSecretKey) -> Self { let hash_and_round = VoteValue(value.value_hash, round, value.kind); - let signature = ValidatorSignature::new(&hash_and_round, key_pair); + let signature = ValidatorSignature::new(&hash_and_round, secret_key); Self { value, round, - public_key: key_pair.public(), + public_key: secret_key.public(), signature, } } @@ -824,7 +824,7 @@ impl LiteVote { /// Verifies the signature in the vote. pub fn check(&self) -> Result<(), ChainError> { let hash_and_round = VoteValue(self.value.value_hash, self.round, self.value.kind); - Ok(self.signature.check(&hash_and_round, self.public_key)?) + Ok(self.signature.check(&hash_and_round, &self.public_key)?) } } @@ -858,7 +858,7 @@ impl<'a, T> SignatureAggregator<'a, T> { T: CertificateValue, { let hash_and_round = VoteValue(self.partial.hash(), self.partial.round, T::KIND); - signature.check(&hash_and_round, public_key)?; + signature.check(&hash_and_round, &public_key)?; // Check that each validator only appears once. ensure!( !self.used_validators.contains(&public_key), @@ -916,7 +916,7 @@ pub(crate) fn check_signatures( ); // All that is left is checking signatures! let hash_and_round = VoteValue(value_hash, round, certificate_kind); - ValidatorSignature::verify_batch(&hash_and_round, signatures.iter().map(|(v, s)| (v, s)))?; + ValidatorSignature::verify_batch(&hash_and_round, signatures.iter())?; Ok(()) } diff --git a/linera-chain/src/test.rs b/linera-chain/src/test.rs index c4bdda9f7a72..121672d09c3c 100644 --- a/linera-chain/src/test.rs +++ b/linera-chain/src/test.rs @@ -4,7 +4,7 @@ //! Test utilities use linera_base::{ - crypto::AccountSecretKey, + crypto::{AccountPublicKey, AccountSecretKey}, data_types::{Amount, BlockHeight, Round, Timestamp}, hashed::Hashed, identifiers::{ChainId, Owner}, @@ -139,6 +139,7 @@ impl VoteTestExt for Vote { let state = ValidatorState { network_address: "".to_string(), votes: 100, + account_public_key: AccountPublicKey::test_key(1), }; let committee = Committee::new( vec![(self.public_key, state)].into_iter().collect(), diff --git a/linera-chain/src/unit_tests/chain_tests.rs b/linera-chain/src/unit_tests/chain_tests.rs index 2a9b68c075cb..f5664dcdf4c3 100644 --- a/linera-chain/src/unit_tests/chain_tests.rs +++ b/linera-chain/src/unit_tests/chain_tests.rs @@ -93,7 +93,10 @@ fn make_admin_message_id(height: BlockHeight) -> MessageId { } fn make_open_chain_config() -> OpenChainConfig { - let committee = Committee::make_simple(vec![ValidatorPublicKey::test_key(1)]); + let committee = Committee::make_simple(vec![( + ValidatorPublicKey::test_key(1), + AccountPublicKey::test_key(1), + )]); OpenChainConfig { ownership: ChainOwnership::single(AccountPublicKey::test_key(0).into()), admin_id: admin_id(), @@ -112,7 +115,7 @@ async fn test_block_size_limit() { let mut chain = ChainStateView::new(chain_id).await; // The size of the executed valid block below. - let maximum_executed_block_size = 685; + let maximum_executed_block_size = 720; // Initialize the chain. let mut config = make_open_chain_config(); @@ -124,6 +127,7 @@ async fn test_block_size_limit() { ValidatorState { network_address: ValidatorPublicKey::test_key(1).to_string(), votes: 1, + account_public_key: AccountPublicKey::test_key(1), }, )]), ResourceControlPolicy { diff --git a/linera-chain/src/unit_tests/data_types_tests.rs b/linera-chain/src/unit_tests/data_types_tests.rs index a99bec38d8cd..b60b3e60870d 100644 --- a/linera-chain/src/unit_tests/data_types_tests.rs +++ b/linera-chain/src/unit_tests/data_types_tests.rs @@ -2,7 +2,7 @@ // Copyright (c) Zefchain Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use linera_base::data_types::Amount; +use linera_base::{crypto::ValidatorKeypair, data_types::Amount}; use super::*; use crate::{ @@ -12,9 +12,8 @@ use crate::{ #[test] fn test_signed_values() { - let key1 = ValidatorSecretKey::generate(); - let key2 = ValidatorSecretKey::generate(); - let validator_1 = key1.public(); + let validator1_keypair = ValidatorKeypair::generate(); + let validator2_keypair = ValidatorKeypair::generate(); let block = make_first_block(ChainId::root(1)).with_simple_transfer(ChainId::root(2), Amount::ONE); @@ -27,18 +26,30 @@ fn test_signed_values() { .with(block); let confirmed_value = Hashed::new(ConfirmedBlock::new(executed_block.clone())); - let confirmed_vote = LiteVote::new(LiteValue::new(&confirmed_value), Round::Fast, &key1); + let confirmed_vote = LiteVote::new( + LiteValue::new(&confirmed_value), + Round::Fast, + &validator1_keypair.secret_key, + ); assert!(confirmed_vote.check().is_ok()); let validated_value = Hashed::new(ValidatedBlock::new(executed_block)); - let validated_vote = LiteVote::new(LiteValue::new(&validated_value), Round::Fast, &key1); + let validated_vote = LiteVote::new( + LiteValue::new(&validated_value), + Round::Fast, + &validator1_keypair.secret_key, + ); assert_ne!( confirmed_vote.value, validated_vote.value, "Confirmed and validated votes should be different, even if for the same executed block" ); - let mut v = LiteVote::new(LiteValue::new(&confirmed_value), Round::Fast, &key2); - v.public_key = validator_1; + let mut v = LiteVote::new( + LiteValue::new(&confirmed_value), + Round::Fast, + &validator2_keypair.secret_key, + ); + v.public_key = validator1_keypair.public_key; assert!(v.check().is_err()); assert!(validated_vote.check().is_ok()); @@ -81,11 +92,16 @@ fn test_hashes() { #[test] fn test_certificates() { - let key1 = ValidatorSecretKey::generate(); - let key2 = ValidatorSecretKey::generate(); - let key3 = ValidatorSecretKey::generate(); + let validator1_keypair = ValidatorKeypair::generate(); + let account1_secret = AccountSecretKey::generate(); + let validator2_keypair = ValidatorKeypair::generate(); + let account2_secret = AccountSecretKey::generate(); + let validator3_keypair = ValidatorKeypair::generate(); - let committee = Committee::make_simple(vec![key1.public(), key2.public()]); + let committee = Committee::make_simple(vec![ + (validator1_keypair.public_key, account1_secret.public()), + (validator2_keypair.public_key, account2_secret.public()), + ]); let block = make_first_block(ChainId::root(1)).with_simple_transfer(ChainId::root(1), Amount::ONE); @@ -98,9 +114,21 @@ fn test_certificates() { .with(block); let value = Hashed::new(ConfirmedBlock::new(executed_block)); - let v1 = LiteVote::new(LiteValue::new(&value), Round::Fast, &key1); - let v2 = LiteVote::new(LiteValue::new(&value), Round::Fast, &key2); - let v3 = LiteVote::new(LiteValue::new(&value), Round::Fast, &key3); + let v1 = LiteVote::new( + LiteValue::new(&value), + Round::Fast, + &validator1_keypair.secret_key, + ); + let v2 = LiteVote::new( + LiteValue::new(&value), + Round::Fast, + &validator2_keypair.secret_key, + ); + let v3 = LiteVote::new( + LiteValue::new(&value), + Round::Fast, + &validator3_keypair.secret_key, + ); let mut builder = SignatureAggregator::new(value.clone(), Round::Fast, &committee); assert!(builder diff --git a/linera-client/src/client_options.rs b/linera-client/src/client_options.rs index 7bf5f65c8c7b..4e76cec3e8df 100644 --- a/linera-client/src/client_options.rs +++ b/linera-client/src/client_options.rs @@ -10,7 +10,7 @@ use std::{ use chrono::{DateTime, Utc}; use linera_base::{ - crypto::{CryptoHash, ValidatorPublicKey}, + crypto::{AccountPublicKey, CryptoHash, ValidatorPublicKey}, data_types::{Amount, ApplicationPermissions, TimeDelta}, identifiers::{ Account, ApplicationId, BytecodeId, ChainId, MessageId, Owner, UserApplicationId, @@ -477,6 +477,10 @@ pub enum ClientCommand { #[arg(long)] public_key: ValidatorPublicKey, + /// The public key of the account controlled by the validator. + #[arg(long)] + account_key: AccountPublicKey, + /// Network address #[arg(long)] address: String, diff --git a/linera-client/src/config.rs b/linera-client/src/config.rs index f602f381cbba..d6ba99396850 100644 --- a/linera-client/src/config.rs +++ b/linera-client/src/config.rs @@ -49,6 +49,8 @@ util::impl_from_dynamic!(Error:Persistence, persistent::file::Error); pub struct ValidatorConfig { /// The public key of the validator. pub public_key: ValidatorPublicKey, + /// The account key of the validator. + pub account_key: AccountPublicKey, /// The network configuration for the validator. pub network: ValidatorPublicNetworkConfig, } @@ -57,7 +59,8 @@ pub struct ValidatorConfig { #[derive(Serialize, Deserialize)] pub struct ValidatorServerConfig { pub validator: ValidatorConfig, - pub key: ValidatorSecretKey, + pub validator_secret: ValidatorSecretKey, + pub account_secret: AccountSecretKey, pub internal_network: ValidatorInternalNetworkConfig, } @@ -83,6 +86,7 @@ impl CommitteeConfig { ValidatorState { network_address: v.network.to_string(), votes: 100, + account_public_key: v.account_key, }, ) }) diff --git a/linera-client/src/unit_tests/util.rs b/linera-client/src/unit_tests/util.rs index 6faa5aed1d9b..8a898c1eef28 100644 --- a/linera-client/src/unit_tests/util.rs +++ b/linera-client/src/unit_tests/util.rs @@ -17,11 +17,14 @@ pub fn make_genesis_config(builder: &TestBuilder) -> Genes host: "localhost".to_string(), port: 8080, }; - let validator_names = builder.initial_committee.validators().keys(); - let validators = validator_names - .map(|public_key| ValidatorConfig { + let validators = builder + .initial_committee + .validators() + .iter() + .map(|(public_key, state)| ValidatorConfig { public_key: *public_key, network: network.clone(), + account_key: state.account_public_key, }) .collect(); let mut genesis_config = GenesisConfig::new( diff --git a/linera-core/src/chain_worker/config.rs b/linera-core/src/chain_worker/config.rs index 516b19c30fed..eeb9bea0892f 100644 --- a/linera-core/src/chain_worker/config.rs +++ b/linera-core/src/chain_worker/config.rs @@ -5,7 +5,10 @@ use std::sync::Arc; -use linera_base::{crypto::ValidatorSecretKey, time::Duration}; +use linera_base::{ + crypto::{AccountSecretKey, ValidatorSecretKey}, + time::Duration, +}; /// Configuration parameters for the [`ChainWorkerState`][`super::state::ChainWorkerState`]. #[derive(Clone, Default)] @@ -13,6 +16,8 @@ pub struct ChainWorkerConfig { /// The signature key pair of the validator. The key may be missing for replicas /// without voting rights (possibly with a partial view of chains). pub key_pair: Option>, + /// The account secret key of the validator. Can be used to sign blocks as chain owner. + pub account_key: Option>, /// Whether inactive chains are allowed in storage. pub allow_inactive_chains: bool, /// Whether new messages from deprecated epochs are allowed. @@ -26,8 +31,20 @@ pub struct ChainWorkerConfig { impl ChainWorkerConfig { /// Configures the `key_pair` in this [`ChainWorkerConfig`]. - pub fn with_key_pair(mut self, key_pair: impl Into>) -> Self { - self.key_pair = key_pair.into().map(Arc::new); + pub fn with_key_pair( + mut self, + key_pair: Option<(ValidatorSecretKey, AccountSecretKey)>, + ) -> Self { + match key_pair { + Some((validator_secret, account_secret)) => { + self.key_pair = Some(Arc::new(validator_secret)); + self.account_key = Some(Arc::new(account_secret)); + } + None => { + self.key_pair = None; + self.account_key = None; + } + } self } @@ -35,4 +52,9 @@ impl ChainWorkerConfig { pub fn key_pair(&self) -> Option<&ValidatorSecretKey> { self.key_pair.as_ref().map(Arc::as_ref) } + + /// Gets a reference to the [`AccountSecretKey`], if available. + pub fn account_key(&self) -> Option<&AccountSecretKey> { + self.account_key.as_ref().map(Arc::as_ref) + } } diff --git a/linera-core/src/client/mod.rs b/linera-core/src/client/mod.rs index 2324704be572..fa3636abb5ab 100644 --- a/linera-core/src/client/mod.rs +++ b/linera-core/src/client/mod.rs @@ -28,7 +28,7 @@ use linera_base::data_types::Bytecode; use linera_base::prometheus_util::MeasureLatency as _; use linera_base::{ abi::Abi, - crypto::{CryptoHash, ValidatorPublicKey, ValidatorSecretKey}, + crypto::{AccountPublicKey, AccountSecretKey, CryptoHash, ValidatorPublicKey}, data_types::{ Amount, ApplicationPermissions, ArithmeticError, Blob, BlockHeight, Round, Timestamp, }, @@ -285,7 +285,7 @@ impl Client { pub fn create_chain_client( self: &Arc, chain_id: ChainId, - known_key_pairs: Vec, + known_key_pairs: Vec, admin_id: ChainId, block_hash: Option, timestamp: Timestamp, @@ -976,7 +976,7 @@ where /// Obtains the key pair associated to the current identity. #[instrument(level = "trace")] - pub async fn key_pair(&self) -> Result { + pub async fn key_pair(&self) -> Result { let id = self.identity().await?; Ok(self .state() @@ -988,7 +988,7 @@ where /// Obtains the public key associated to the current identity. #[instrument(level = "trace")] - pub async fn public_key(&self) -> Result { + pub async fn public_key(&self) -> Result { Ok(self.key_pair().await?.public()) } @@ -2690,7 +2690,7 @@ where #[instrument(level = "trace", skip(key_pair))] pub async fn rotate_key_pair( &self, - key_pair: ValidatorSecretKey, + key_pair: AccountSecretKey, ) -> Result, ChainClientError> { let new_public_key = self.state_mut().insert_known_key_pair(key_pair); self.transfer_ownership(new_public_key.into()).await diff --git a/linera-core/src/data_types.rs b/linera-core/src/data_types.rs index 9f2f5cbc3fd8..439bac57fb17 100644 --- a/linera-core/src/data_types.rs +++ b/linera-core/src/data_types.rs @@ -307,8 +307,13 @@ impl ChainInfoResponse { self.signature = Some(ValidatorSignature::new(&*self.info, key_pair)); } - pub fn check(&self, name: &ValidatorPublicKey) -> Result<(), CryptoError> { - ValidatorSignature::check_optional_signature(self.signature.as_ref(), &*self.info, name) + pub fn check(&self, public_key: &ValidatorPublicKey) -> Result<(), CryptoError> { + match self.signature.as_ref() { + Some(sig) => sig.check(&*self.info, public_key), + None => Err(CryptoError::MissingSignature { + type_name: "ValidatorSignature".to_string(), + }), + } } /// Returns the committee in the latest epoch. diff --git a/linera-core/src/unit_tests/test_utils.rs b/linera-core/src/unit_tests/test_utils.rs index 0dc8145abe5c..9a04f2450a97 100644 --- a/linera-core/src/unit_tests/test_utils.rs +++ b/linera-core/src/unit_tests/test_utils.rs @@ -17,7 +17,7 @@ use futures::{ }; use linera_base::{ crypto::{ - AccountPublicKey, AccountSecretKey, CryptoHash, ValidatorPublicKey, ValidatorSecretKey, + AccountPublicKey, AccountSecretKey, CryptoHash, ValidatorKeypair, ValidatorPublicKey, }, data_types::*, identifiers::{BlobId, ChainDescription, ChainId}, @@ -733,33 +733,33 @@ where let mut key_pairs = Vec::new(); let mut validators = Vec::new(); for _ in 0..count { - let key_pair = ValidatorSecretKey::generate(); - let name = key_pair.public(); - validators.push(name); - key_pairs.push(key_pair); + let validator_keypair = ValidatorKeypair::generate(); + let account_secret = AccountSecretKey::generate(); + validators.push((validator_keypair.public_key, account_secret.public())); + key_pairs.push((validator_keypair.secret_key, account_secret)); } let initial_committee = Committee::make_simple(validators); let mut validator_clients = Vec::new(); let mut validator_storages = HashMap::new(); let mut faulty_validators = HashSet::new(); - for (i, key_pair) in key_pairs.into_iter().enumerate() { - let name = key_pair.public(); + for (i, (validator_secret, account_secret)) in key_pairs.into_iter().enumerate() { + let validator_public_key = validator_secret.public(); let storage = storage_builder.build().await?; let state = WorkerState::new( format!("Node {}", i), - Some(key_pair), + Some((validator_secret, account_secret)), storage.clone(), NonZeroUsize::new(100).expect("Chain worker limit should not be zero"), ) .with_allow_inactive_chains(false) .with_allow_messages_from_deprecated_epochs(false); - let validator = LocalValidatorClient::new(name, state); + let validator = LocalValidatorClient::new(validator_public_key, state); if i < with_faulty_validators { - faulty_validators.insert(name); + faulty_validators.insert(validator_public_key); validator.set_fault_type(FaultType::Malicious).await; } validator_clients.push(validator); - validator_storages.insert(name, storage); + validator_storages.insert(validator_public_key, storage); } tracing::info!( "Test will use the following faulty validators: {:?}", @@ -894,7 +894,7 @@ where pub async fn make_client( &mut self, chain_id: ChainId, - key_pair: ValidatorSecretKey, + key_pair: AccountSecretKey, block_hash: Option, block_height: BlockHeight, ) -> Result, B::Storage>, anyhow::Error> { diff --git a/linera-core/src/unit_tests/worker_tests.rs b/linera-core/src/unit_tests/worker_tests.rs index eeab1f0c2340..fb605a101aed 100644 --- a/linera-core/src/unit_tests/worker_tests.rs +++ b/linera-core/src/unit_tests/worker_tests.rs @@ -17,7 +17,7 @@ use std::{ use assert_matches::assert_matches; use linera_base::{ - crypto::{AccountPublicKey, AccountSecretKey, CryptoHash, ValidatorSecretKey}, + crypto::{AccountPublicKey, AccountSecretKey, CryptoHash, ValidatorKeypair}, data_types::*, hashed::Hashed, identifiers::{ @@ -89,11 +89,15 @@ fn init_worker( where S: Storage + Clone + Send + Sync + 'static, { - let key_pair = ValidatorSecretKey::generate(); - let committee = Committee::make_simple(vec![key_pair.public()]); + let validator_keypair = ValidatorKeypair::generate(); + let account_secret = AccountSecretKey::generate(); + let committee = Committee::make_simple(vec![( + validator_keypair.public_key, + account_secret.public(), + )]); let worker = WorkerState::new( "Single validator node".to_string(), - Some(key_pair), + Some((validator_keypair.secret_key, account_secret)), storage, NonZeroUsize::new(10).expect("Chain worker limit should not be zero"), ) @@ -3742,9 +3746,9 @@ where // Now we are in fallback mode, and the validator is the leader. let (response, _) = worker.handle_chain_info_query(query.clone()).await?; let manager = response.info.manager; - let validator_key = worker.public_key(); + let account_key = worker.account_key(); assert_eq!(manager.current_round, Round::Validator(0)); - assert_eq!(manager.leader, Some(Owner::from(validator_key))); + assert_eq!(manager.leader, Some(Owner::from(account_key))); Ok(()) } diff --git a/linera-core/src/worker.rs b/linera-core/src/worker.rs index 501e13a84758..37a1e736153a 100644 --- a/linera-core/src/worker.rs +++ b/linera-core/src/worker.rs @@ -10,8 +10,10 @@ use std::{ }; use futures::future::Either; +#[cfg(with_testing)] +use linera_base::crypto::AccountPublicKey; use linera_base::{ - crypto::{CryptoError, CryptoHash, ValidatorPublicKey, ValidatorSecretKey}, + crypto::{AccountSecretKey, CryptoError, CryptoHash, ValidatorPublicKey, ValidatorSecretKey}, data_types::{ ArithmeticError, Blob, BlockHeight, DecompressionError, Round, UserApplicationDescription, }, @@ -288,7 +290,7 @@ where #[instrument(level = "trace", skip(nickname, key_pair, storage))] pub fn new( nickname: String, - key_pair: Option, + key_pair: Option<(ValidatorSecretKey, AccountSecretKey)>, storage: StorageClient, chain_worker_limit: NonZeroUsize, ) -> Self { @@ -1065,4 +1067,20 @@ where ) .public() } + + /// Gets a reference to the validator's [`AccountPublicKey`]. + /// + /// # Panics + /// + /// If the validator doesn't have an account secret key assigned to it. + #[instrument(level = "trace", skip(self))] + pub fn account_key(&self) -> AccountPublicKey { + self.chain_worker_config + .account_key() + .expect( + "Test validator should have a key pair assigned to it \ + in order to obtain it's account key", + ) + .public() + } } diff --git a/linera-execution/src/committee.rs b/linera-execution/src/committee.rs index 0c4882343fa6..2941a845c38e 100644 --- a/linera-execution/src/committee.rs +++ b/linera-execution/src/committee.rs @@ -6,7 +6,7 @@ use std::{borrow::Cow, collections::BTreeMap, str::FromStr}; use async_graphql::InputObject; use linera_base::{ - crypto::{CryptoError, ValidatorPublicKey}, + crypto::{AccountPublicKey, CryptoError, ValidatorPublicKey}, data_types::ArithmeticError, }; use serde::{Deserialize, Serialize}; @@ -91,12 +91,14 @@ impl<'de> Deserialize<'de> for ValidatorName { } /// Public state of a validator. -#[derive(Eq, PartialEq, Hash, Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize)] pub struct ValidatorState { /// The network address (in a string format understood by the networking layer). pub network_address: String, /// The voting power. pub votes: u64, + /// The public key of the account associated with the validator. + pub account_public_key: AccountPublicKey, } /// A set of validators (identified by their public keys) and their voting rights. @@ -311,15 +313,16 @@ impl Committee { } #[cfg(with_testing)] - pub fn make_simple(keys: Vec) -> Self { + pub fn make_simple(keys: Vec<(ValidatorPublicKey, AccountPublicKey)>) -> Self { let map = keys .into_iter() .map(|k| { ( - k, + k.0, ValidatorState { - network_address: k.to_string(), + network_address: k.0.to_string(), votes: 1, + account_public_key: k.1, }, ) }) @@ -340,6 +343,12 @@ impl Committee { .map(|(name, validator)| (*name, validator.votes)) } + pub fn fallback_keys_and_weights(&self) -> impl Iterator + '_ { + self.validators + .values() + .map(|validator| (validator.account_public_key, validator.votes)) + } + pub fn network_address(&self, author: &ValidatorPublicKey) -> Option<&str> { self.validators .get(author) diff --git a/linera-execution/tests/test_execution.rs b/linera-execution/tests/test_execution.rs index 320cab872575..790dce8fcb69 100644 --- a/linera-execution/tests/test_execution.rs +++ b/linera-execution/tests/test_execution.rs @@ -1412,7 +1412,10 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< /// Tests the system API calls `open_chain` and `chain_ownership`. #[tokio::test] async fn test_open_chain() -> anyhow::Result<()> { - let committee = Committee::make_simple(vec![ValidatorPublicKey::test_key(0)]); + let committee = Committee::make_simple(vec![( + ValidatorPublicKey::test_key(0), + AccountPublicKey::test_key(0), + )]); let committees = BTreeMap::from([(Epoch::ZERO, committee)]); let chain_key = AccountPublicKey::test_key(1); let ownership = ChainOwnership::single(chain_key.into()); @@ -1517,7 +1520,10 @@ async fn test_open_chain() -> anyhow::Result<()> { /// Tests the system API call `close_chain``. #[tokio::test] async fn test_close_chain() -> anyhow::Result<()> { - let committee = Committee::make_simple(vec![ValidatorPublicKey::test_key(0)]); + let committee = Committee::make_simple(vec![( + ValidatorPublicKey::test_key(0), + AccountPublicKey::test_key(0), + )]); let committees = BTreeMap::from([(Epoch::ZERO, committee)]); let ownership = ChainOwnership::single(AccountPublicKey::test_key(1).into()); let state = SystemExecutionState { diff --git a/linera-rpc/src/grpc/conversions.rs b/linera-rpc/src/grpc/conversions.rs index d2ee82e1e3c8..0b80c27d985a 100644 --- a/linera-rpc/src/grpc/conversions.rs +++ b/linera-rpc/src/grpc/conversions.rs @@ -628,7 +628,7 @@ impl From for api::AccountPublicKey { impl From for api::ValidatorPublicKey { fn from(public_key: ValidatorPublicKey) -> Self { Self { - bytes: public_key.0.to_vec(), + bytes: public_key.0.serialize().to_vec(), } } } @@ -660,7 +660,7 @@ impl From for api::AccountSignature { impl From for api::ValidatorSignature { fn from(signature: ValidatorSignature) -> Self { Self { - bytes: signature.0.to_vec(), + bytes: signature.0.serialize_compact().to_vec(), } } } @@ -669,7 +669,7 @@ impl TryFrom for ValidatorSignature { type Error = GrpcProtoConversionError; fn try_from(signature: api::ValidatorSignature) -> Result { - Ok(Self(signature.bytes.as_slice().try_into()?)) + Self::from_slice(signature.bytes.as_slice()).map_err(GrpcProtoConversionError::CryptoError) } } @@ -987,7 +987,7 @@ pub mod tests { use std::{borrow::Cow, fmt::Debug}; use linera_base::{ - crypto::{AccountSecretKey, BcsSignable, CryptoHash, ValidatorSecretKey}, + crypto::{AccountSecretKey, BcsSignable, CryptoHash, ValidatorKeypair}, data_types::{Amount, Blob, Round, Timestamp}, }; use linera_chain::{ @@ -1027,14 +1027,15 @@ pub mod tests { let account_key = AccountSecretKey::generate().public(); round_trip_check::<_, api::AccountPublicKey>(account_key); - let validator_key = ValidatorSecretKey::generate().public(); + let validator_key = ValidatorKeypair::generate().public_key; round_trip_check::<_, api::ValidatorPublicKey>(validator_key); } #[test] pub fn test_signature() { - let validator_key_pair = ValidatorSecretKey::generate(); - let validator_signature = ValidatorSignature::new(&Foo("test".into()), &validator_key_pair); + let validator_key_pair = ValidatorKeypair::generate(); + let validator_signature = + ValidatorSignature::new(&Foo("test".into()), &validator_key_pair.secret_key); round_trip_check::<_, api::ValidatorSignature>(validator_signature); let account_key_pair = AccountSecretKey::generate(); @@ -1093,7 +1094,7 @@ pub mod tests { info: chain_info, signature: Some(ValidatorSignature::new( &Foo("test".into()), - &ValidatorSecretKey::generate(), + &ValidatorKeypair::generate().secret_key, )), }; round_trip_check::<_, api::ChainInfoResponse>(chain_info_response_some); @@ -1148,7 +1149,7 @@ pub mod tests { #[test] pub fn test_lite_certificate() { - let key_pair = ValidatorSecretKey::generate(); + let key_pair = ValidatorKeypair::generate(); let certificate = LiteCertificate { value: LiteValue { value_hash: CryptoHash::new(&Foo("value".into())), @@ -1157,8 +1158,8 @@ pub mod tests { }, round: Round::MultiLeader(2), signatures: Cow::Owned(vec![( - key_pair.public(), - ValidatorSignature::new(&Foo("test".into()), &key_pair), + key_pair.public_key, + ValidatorSignature::new(&Foo("test".into()), &key_pair.secret_key), )]), }; let request = HandleLiteCertRequest { @@ -1171,7 +1172,7 @@ pub mod tests { #[test] pub fn test_certificate() { - let key_pair = ValidatorSecretKey::generate(); + let key_pair = ValidatorKeypair::generate(); let certificate = ValidatedBlockCertificate::new( Hashed::new(ValidatedBlock::new( BlockExecutionOutcome { @@ -1182,8 +1183,8 @@ pub mod tests { )), Round::MultiLeader(3), vec![( - key_pair.public(), - ValidatorSignature::new(&Foo("test".into()), &key_pair), + key_pair.public_key, + ValidatorSignature::new(&Foo("test".into()), &key_pair.secret_key), )], ); let request = HandleValidatedCertificateRequest { certificate }; @@ -1216,7 +1217,7 @@ pub mod tests { #[test] pub fn test_block_proposal() { - let key_pair = ValidatorSecretKey::generate(); + let key_pair = ValidatorKeypair::generate(); let outcome = BlockExecutionOutcome { state_hash: CryptoHash::new(&Foo("validated".into())), ..BlockExecutionOutcome::default() @@ -1225,8 +1226,8 @@ pub mod tests { Hashed::new(ValidatedBlock::new(outcome.clone().with(get_block()))), Round::SingleLeader(2), vec![( - key_pair.public(), - ValidatorSignature::new(&Foo("signed".into()), &key_pair), + key_pair.public_key, + ValidatorSignature::new(&Foo("signed".into()), &key_pair.secret_key), )], ) .lite_certificate() diff --git a/linera-sdk/src/test/validator.rs b/linera-sdk/src/test/validator.rs index c1c4440e9548..8df934785617 100644 --- a/linera-sdk/src/test/validator.rs +++ b/linera-sdk/src/test/validator.rs @@ -11,7 +11,7 @@ use std::{num::NonZeroUsize, sync::Arc}; use dashmap::DashMap; use futures::FutureExt as _; use linera_base::{ - crypto::{AccountSecretKey, ValidatorSecretKey}, + crypto::{AccountSecretKey, ValidatorKeypair, ValidatorSecretKey}, data_types::{Amount, ApplicationPermissions, Timestamp}, identifiers::{ApplicationId, BytecodeId, ChainDescription, ChainId, MessageId, Owner}, ownership::ChainOwnership, @@ -43,7 +43,8 @@ use crate::ContractAbi; /// # }); /// ``` pub struct TestValidator { - key_pair: ValidatorSecretKey, + validator_secret: ValidatorSecretKey, + account_secret: AccountSecretKey, committee: Committee, storage: DbStorage, worker: WorkerState>, @@ -54,7 +55,8 @@ pub struct TestValidator { impl Clone for TestValidator { fn clone(&self) -> Self { TestValidator { - key_pair: self.key_pair.copy(), + validator_secret: self.validator_secret.copy(), + account_secret: self.account_secret.copy(), committee: self.committee.clone(), storage: self.storage.clone(), worker: self.worker.clone(), @@ -67,8 +69,12 @@ impl Clone for TestValidator { impl TestValidator { /// Creates a new [`TestValidator`]. pub async fn new() -> Self { - let key_pair = ValidatorSecretKey::generate(); - let committee = Committee::make_simple(vec![key_pair.public()]); + let validator_keypair = ValidatorKeypair::generate(); + let account_secret = AccountSecretKey::generate(); + let committee = Committee::make_simple(vec![( + validator_keypair.public_key, + account_secret.public(), + )]); let wasm_runtime = Some(WasmRuntime::default()); let storage = DbStorage::::make_test_storage(wasm_runtime) .now_or_never() @@ -76,13 +82,14 @@ impl TestValidator { let clock = storage.clock().clone(); let worker = WorkerState::new( "Single validator node".to_string(), - Some(key_pair.copy()), + Some((validator_keypair.secret_key.copy(), account_secret.copy())), storage.clone(), NonZeroUsize::new(20).expect("Chain worker limit should not be zero"), ); let validator = TestValidator { - key_pair, + validator_secret: validator_keypair.secret_key, + account_secret, committee, storage, worker, @@ -155,8 +162,8 @@ impl TestValidator { } /// Returns the keys this test validator uses for signing certificates. - pub fn key_pair(&self) -> &AccountSecretKey { - &self.key_pair + pub fn key_pair(&self) -> &ValidatorSecretKey { + &self.validator_secret } /// Returns the committee that this test validator is part of. diff --git a/linera-service/src/cli_wrappers/local_net.rs b/linera-service/src/cli_wrappers/local_net.rs index 9f1ee3aae003..8550134d75dc 100644 --- a/linera-service/src/cli_wrappers/local_net.rs +++ b/linera-service/src/cli_wrappers/local_net.rs @@ -173,7 +173,7 @@ pub struct LocalNet { next_client_id: usize, num_initial_validators: usize, num_shards: usize, - validator_names: BTreeMap, + validator_keys: BTreeMap, running_validators: BTreeMap, namespace: String, validators_with_initialized_storage: HashSet, @@ -352,7 +352,7 @@ impl LocalNet { next_client_id: 0, num_initial_validators, num_shards, - validator_names: BTreeMap::new(), + validator_keys: BTreeMap::new(), running_validators: BTreeMap::new(), namespace, validators_with_initialized_storage: HashSet::new(), @@ -395,7 +395,7 @@ impl LocalNet { } fn configuration_string(&self, server_number: usize) -> Result { - let n = server_number; + let n: usize = server_number; let path = self .path_provider .path() @@ -458,10 +458,16 @@ impl LocalNet { .args(["--committee", "committee.json"]) .spawn_and_wait_for_stdout() .await?; - self.validator_names = output + self.validator_keys = output .split_whitespace() .map(str::to_string) + .map(|keys| keys.split(',').map(str::to_string).collect::>()) .enumerate() + .map(|(i, keys)| { + let public_key = keys[0].to_string(); + let secret_key = keys[1].to_string(); + (i, (public_key, secret_key)) + }) .collect(); Ok(()) } @@ -659,8 +665,8 @@ impl LocalNet { #[cfg(with_testing)] impl LocalNet { - pub fn validator_name(&self, validator: usize) -> Option<&String> { - self.validator_names.get(&validator) + pub fn validator_keys(&self, validator: usize) -> Option<&(String, String)> { + self.validator_keys.get(&validator) } pub async fn generate_validator_config(&mut self, validator: usize) -> Result<()> { @@ -672,8 +678,13 @@ impl LocalNet { .arg(&self.configuration_string(validator)?) .spawn_and_wait_for_stdout() .await?; - self.validator_names - .insert(validator, stdout.trim().to_string()); + let keys = stdout + .trim() + .split(',') + .map(str::to_string) + .collect::>(); + self.validator_keys + .insert(validator, (keys[0].clone(), keys[1].clone())); Ok(()) } diff --git a/linera-service/src/cli_wrappers/wallet.rs b/linera-service/src/cli_wrappers/wallet.rs index d309661c4a39..ce45589636e4 100644 --- a/linera-service/src/cli_wrappers/wallet.rs +++ b/linera-service/src/cli_wrappers/wallet.rs @@ -881,12 +881,18 @@ impl ClientWrapper { .is_some_and(|wallet| wallet.get(chain).is_some()) } - pub async fn set_validator(&self, public_key: &str, port: usize, votes: usize) -> Result<()> { + pub async fn set_validator( + &self, + validator_key: &(String, String), + port: usize, + votes: usize, + ) -> Result<()> { let address = format!("{}:127.0.0.1:{}", self.network.short(), port); self.command() .await? .arg("set-validator") - .args(["--public-key", public_key]) + .args(["--public-key", &validator_key.0]) + .args(["--account-key", &validator_key.1]) .args(["--address", &address]) .args(["--votes", &votes.to_string()]) .spawn_and_wait_for_stdout() @@ -894,11 +900,11 @@ impl ClientWrapper { Ok(()) } - pub async fn remove_validator(&self, public_key: &str) -> Result<()> { + pub async fn remove_validator(&self, validator_key: &str) -> Result<()> { self.command() .await? .arg("remove-validator") - .args(["--public-key", public_key]) + .args(["--public-key", validator_key]) .spawn_and_wait_for_stdout() .await?; Ok(()) diff --git a/linera-service/src/linera/main.rs b/linera-service/src/linera/main.rs index ce00da830661..7d92ec10fa5f 100644 --- a/linera-service/src/linera/main.rs +++ b/linera-service/src/linera/main.rs @@ -523,6 +523,7 @@ impl Runnable for Job { let mut context = context.lock().await; if let SetValidator { public_key, + account_key: _, address, votes: _, skip_online_check: false, @@ -576,6 +577,7 @@ impl Runnable for Job { match command { SetValidator { public_key, + account_key, address, votes, skip_online_check: _, @@ -585,6 +587,7 @@ impl Runnable for Job { ValidatorState { network_address: address, votes, + account_public_key: account_key, }, ); } diff --git a/linera-service/src/proxy/grpc.rs b/linera-service/src/proxy/grpc.rs index 2b0e36e01760..d780633d7739 100644 --- a/linera-service/src/proxy/grpc.rs +++ b/linera-service/src/proxy/grpc.rs @@ -698,14 +698,14 @@ mod proto_message_cap { data_types::{BlockExecutionOutcome, ExecutedBlock}, types::{Certificate, ConfirmedBlock, ConfirmedBlockCertificate}, }; - use linera_sdk::base::{ChainId, TestString, ValidatorSecretKey, ValidatorSignature}; + use linera_sdk::base::{ChainId, TestString, ValidatorKeypair, ValidatorSignature}; use super::{CertificatesBatchResponse, GrpcMessageLimiter}; fn test_certificate() -> Certificate { - let keypair = ValidatorSecretKey::generate(); - let validator = keypair.public(); - let signature = ValidatorSignature::new(&TestString::new("Test"), &keypair); + let keypair = ValidatorKeypair::generate(); + let validator = keypair.public_key; + let signature = ValidatorSignature::new(&TestString::new("Test"), &keypair.secret_key); let executed_block = ExecutedBlock { block: linera_chain::test::make_first_block(ChainId::root(0)), outcome: BlockExecutionOutcome::default(), diff --git a/linera-service/src/server.rs b/linera-service/src/server.rs index 8618b4a5ce20..cfc3d908a6c6 100644 --- a/linera-service/src/server.rs +++ b/linera-service/src/server.rs @@ -14,10 +14,7 @@ use std::{ use anyhow::{bail, Context}; use async_trait::async_trait; use futures::{stream::FuturesUnordered, FutureExt as _, StreamExt, TryFutureExt as _}; -use linera_base::{ - crypto::{CryptoRng, ValidatorSecretKey}, - listen_for_shutdown_signals, -}; +use linera_base::{crypto::CryptoRng, listen_for_shutdown_signals}; use linera_client::{ config::{CommitteeConfig, GenesisConfig, ValidatorConfig, ValidatorServerConfig}, persistent::{self, Persist}, @@ -32,6 +29,7 @@ use linera_rpc::{ }, grpc, simple, }; +use linera_sdk::base::{AccountSecretKey, ValidatorKeypair}; #[cfg(with_metrics)] use linera_service::prometheus_server; use linera_service::util; @@ -63,10 +61,16 @@ impl ServerContext { { let shard = self.server_config.internal_network.shard(shard_id); info!("Shard booted on {}", shard.host); - info!("Public key: {}", self.server_config.key.public()); + info!( + "Public key: {}", + self.server_config.validator_secret.public() + ); let state = WorkerState::new( format!("Shard {} @ {}:{}", shard_id, local_ip_addr, shard.port), - Some(self.server_config.key.copy()), + Some(( + self.server_config.validator_secret.copy(), + self.server_config.account_secret.copy(), + )), storage, self.max_loaded_chains, ) @@ -283,8 +287,9 @@ fn make_server_config( rng: &mut R, options: ValidatorOptions, ) -> anyhow::Result> { - let key = ValidatorSecretKey::generate_from(rng); - let public_key = key.public(); + let validator_keypair = ValidatorKeypair::generate_from(rng); + let account_secret = AccountSecretKey::generate_from(rng); + let public_key = validator_keypair.public_key; let network = ValidatorPublicNetworkConfig { protocol: options.external_protocol, host: options.host, @@ -302,12 +307,14 @@ fn make_server_config( let validator = ValidatorConfig { network, public_key, + account_key: account_secret.public(), }; Ok(persistent::File::new( path, ValidatorServerConfig { validator, - key, + validator_secret: validator_keypair.secret_key, + account_secret, internal_network, }, )?) @@ -568,7 +575,10 @@ async fn run(options: ServerOptions) { .await .expect("Unable to write server config file"); info!("Wrote server config {}", path.to_str().unwrap()); - println!("{}", server.validator.public_key); + println!( + "{},{}", + server.validator.public_key, server.validator.account_key + ); config_validators.push(Persist::into_value(server).validator); } if let Some(committee) = committee { diff --git a/linera-service/tests/local_net_tests.rs b/linera-service/tests/local_net_tests.rs index 851e041e8a55..1a394b2c43ae 100644 --- a/linera-service/tests/local_net_tests.rs +++ b/linera-service/tests/local_net_tests.rs @@ -115,7 +115,7 @@ async fn test_end_to_end_reconfiguration(config: LocalNetConfig) -> Result<()> { // Add 5th validator client - .set_validator(net.validator_name(4).unwrap(), LocalNet::proxy_port(4), 100) + .set_validator(net.validator_keys(4).unwrap(), LocalNet::proxy_port(4), 100) .await?; client.finalize_committee().await?; @@ -128,7 +128,7 @@ async fn test_end_to_end_reconfiguration(config: LocalNetConfig) -> Result<()> { // Add 6th validator client - .set_validator(net.validator_name(5).unwrap(), LocalNet::proxy_port(5), 100) + .set_validator(net.validator_keys(5).unwrap(), LocalNet::proxy_port(5), 100) .await?; client.finalize_committee().await?; if matches!(network, Network::Grpc) { @@ -137,7 +137,7 @@ async fn test_end_to_end_reconfiguration(config: LocalNetConfig) -> Result<()> { // Remove 5th validator client - .remove_validator(net.validator_name(4).unwrap()) + .remove_validator(&net.validator_keys(4).unwrap().0) .await?; client.finalize_committee().await?; net.remove_validator(4)?; @@ -154,8 +154,8 @@ async fn test_end_to_end_reconfiguration(config: LocalNetConfig) -> Result<()> { // Remove the first 4 validators, so only the last one remains. for i in 0..4 { - let name = net.validator_name(i).unwrap(); - client.remove_validator(name).await?; + let validator_key = net.validator_keys(i).unwrap(); + client.remove_validator(&validator_key.0).await?; client.finalize_committee().await?; if let Some(service) = &node_service_2 { service.process_inbox(&chain_2).await?; @@ -268,7 +268,7 @@ async fn test_end_to_end_receipt_of_old_create_committee_messages( // Add 5th validator to the network client - .set_validator(net.validator_name(4).unwrap(), LocalNet::proxy_port(4), 100) + .set_validator(net.validator_keys(4).unwrap(), LocalNet::proxy_port(4), 100) .await?; client.query_validators(None).await?; @@ -360,7 +360,7 @@ async fn test_end_to_end_receipt_of_old_remove_committee_messages( // Add 5th validator to the network client - .set_validator(net.validator_name(4).unwrap(), LocalNet::proxy_port(4), 100) + .set_validator(net.validator_keys(4).unwrap(), LocalNet::proxy_port(4), 100) .await?; client.finalize_committee().await?; @@ -398,7 +398,7 @@ async fn test_end_to_end_receipt_of_old_remove_committee_messages( // Add 6th validator to the network client - .set_validator(net.validator_name(5).unwrap(), LocalNet::proxy_port(5), 100) + .set_validator(net.validator_keys(5).unwrap(), LocalNet::proxy_port(5), 100) .await?; client.query_validators(None).await?; diff --git a/linera-storage/src/lib.rs b/linera-storage/src/lib.rs index 1a27f04e2ab3..7aa4edc6bb1b 100644 --- a/linera-storage/src/lib.rs +++ b/linera-storage/src/lib.rs @@ -222,7 +222,7 @@ pub trait Storage: Sized { ChainOwnership::single(owner), BlockHeight(0), self.clock().current_time(), - committee.keys_and_weights(), + committee.fallback_keys_and_weights(), )?; let system_state = &mut chain.execution_state.system; system_state.description.set(Some(description)); From 03c41f07afd3d08a919d45387e02e94052164830 Mon Sep 17 00:00:00 2001 From: deuszx Date: Fri, 21 Feb 2025 16:06:44 +0000 Subject: [PATCH 2/6] Print stderr on error --- linera-base/src/command.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/linera-base/src/command.rs b/linera-base/src/command.rs index 9f199fa635bc..73913fce2a9f 100644 --- a/linera-base/src/command.rs +++ b/linera-base/src/command.rs @@ -163,9 +163,10 @@ impl CommandExt for tokio::process::Command { .with_context(|| self.description())?; ensure!( output.status.success(), - "{}: got non-zero error code {}", + "{}: got non-zero error code {}. Stderr: \n{:?}\n", self.description(), - output.status + output.status, + String::from_utf8(output.stderr), ); String::from_utf8(output.stdout).with_context(|| self.description()) } From 6979124b5932362dac2ec36114d7a4f40da28c96 Mon Sep 17 00:00:00 2001 From: deuszx Date: Fri, 21 Feb 2025 18:17:55 +0000 Subject: [PATCH 3/6] Fix RPC format generation. --- linera-rpc/tests/format.rs | 14 +++++++- .../tests/snapshots/format__format.yaml.snap | 33 ++++++++++++------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/linera-rpc/tests/format.rs b/linera-rpc/tests/format.rs index 32dd1416f51e..fcfa28325be1 100644 --- a/linera-rpc/tests/format.rs +++ b/linera-rpc/tests/format.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use linera_base::{ + crypto::TestString, data_types::{BlobContent, OracleResponse, Round}, hashed::Hashed, identifiers::{AccountOwner, BlobType, ChainDescription, Destination, GenericApplicationId}, @@ -27,9 +28,20 @@ fn get_registry() -> Result { .record_samples_for_newtype_structs(true) .record_samples_for_tuple_structs(true), ); - let samples = Samples::new(); + let mut samples = Samples::new(); // 1. Record samples for types with custom deserializers. // 2. Trace the main entry point(s) + every enum separately. + { + // Record sample values for Secp256k1PublicKey and Secp256k1Signature + // as the ones generated by serde-reflection are not valid and will fail. + let validator_keypair = linera_base::crypto::ValidatorKeypair::generate(); + let validator_signature = linera_base::crypto::ValidatorSignature::new( + &TestString::new("signature".to_string()), + &validator_keypair.secret_key, + ); + tracer.trace_value(&mut samples, &validator_keypair.public_key)?; + tracer.trace_value(&mut samples, &validator_signature)?; + } tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index 5dcb728d45ac..3f46e6005149 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -270,7 +270,7 @@ ChainInfoResponse: TYPENAME: ChainInfo - signature: OPTION: - TYPENAME: Ed25519Signature + TYPENAME: Secp256k1Signature ChainManagerInfo: STRUCT: - ownership: @@ -340,7 +340,7 @@ Committee: - validators: MAP: KEY: - TYPENAME: Ed25519PublicKey + TYPENAME: Secp256k1PublicKey VALUE: TYPENAME: ValidatorState - policy: @@ -354,8 +354,8 @@ ConfirmedBlockCertificate: - signatures: SEQ: TUPLE: - - TYPENAME: Ed25519PublicKey - - TYPENAME: Ed25519Signature + - TYPENAME: Secp256k1PublicKey + - TYPENAME: Secp256k1Signature CrateVersion: STRUCT: - major: U32 @@ -466,8 +466,8 @@ LiteCertificate: - signatures: SEQ: TUPLE: - - TYPENAME: Ed25519PublicKey - - TYPENAME: Ed25519Signature + - TYPENAME: Secp256k1PublicKey + - TYPENAME: Secp256k1Signature LiteValue: STRUCT: - value_hash: @@ -483,9 +483,9 @@ LiteVote: - round: TYPENAME: Round - public_key: - TYPENAME: Ed25519PublicKey + TYPENAME: Secp256k1PublicKey - signature: - TYPENAME: Ed25519Signature + TYPENAME: Secp256k1Signature LockingBlock: ENUM: 0: @@ -940,6 +940,13 @@ RpcMessage: CrossChainRequest: NEWTYPE: TYPENAME: CrossChainRequest +Secp256k1PublicKey: + NEWTYPESTRUCT: + TUPLEARRAY: + CONTENT: U8 + SIZE: 33 +Secp256k1Signature: + NEWTYPESTRUCT: BYTES StreamId: STRUCT: - application_id: @@ -1133,8 +1140,8 @@ TimeoutCertificate: - signatures: SEQ: TUPLE: - - TYPENAME: Ed25519PublicKey - - TYPENAME: Ed25519Signature + - TYPENAME: Secp256k1PublicKey + - TYPENAME: Secp256k1Signature TimeoutConfig: STRUCT: - fast_round_duration: @@ -1167,12 +1174,14 @@ ValidatedBlockCertificate: - signatures: SEQ: TUPLE: - - TYPENAME: Ed25519PublicKey - - TYPENAME: Ed25519Signature + - TYPENAME: Secp256k1PublicKey + - TYPENAME: Secp256k1Signature ValidatorState: STRUCT: - network_address: STR - votes: U64 + - account_public_key: + TYPENAME: Ed25519PublicKey VersionInfo: STRUCT: - crate_version: From edfb7bc5c4ef0158034e5d34cceffd76b9cba919 Mon Sep 17 00:00:00 2001 From: deuszx Date: Sat, 22 Feb 2025 17:31:22 +0000 Subject: [PATCH 4/6] Review part 1. --- Cargo.lock | 1 - linera-base/Cargo.toml | 2 +- linera-base/src/crypto/ed25519.rs | 12 ++++---- linera-base/src/crypto/mod.rs | 4 +-- linera-base/src/crypto/secp256k1.rs | 18 ++++++------ linera-chain/src/chain.rs | 4 +-- .../src/unit_tests/data_types_tests.rs | 28 +++++++++---------- linera-core/src/data_types.rs | 4 +-- linera-execution/src/committee.rs | 10 +++---- linera-service/src/cli_wrappers/local_net.rs | 9 +++--- linera-storage/src/lib.rs | 2 +- 11 files changed, 46 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6a8a2bc7748..911e9cb15a47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4387,7 +4387,6 @@ dependencies = [ "async-graphql-derive", "async-trait", "bcs", - "bincode", "cfg-if", "cfg_aliases", "chrono", diff --git a/linera-base/Cargo.toml b/linera-base/Cargo.toml index a7fc20e0850c..2b22afaf5aaf 100644 --- a/linera-base/Cargo.toml +++ b/linera-base/Cargo.toml @@ -79,7 +79,7 @@ port-selector.workspace = true zstd.workspace = true [dev-dependencies] -bincode.workspace = true +bcs.workspace = true linera-base = { path = ".", default-features = false, features = ["test"] } linera-witty = { workspace = true, features = ["test"] } test-case.workspace = true diff --git a/linera-base/src/crypto/ed25519.rs b/linera-base/src/crypto/ed25519.rs index 294d815c3d0c..9b12b2dfea5f 100644 --- a/linera-base/src/crypto/ed25519.rs +++ b/linera-base/src/crypto/ed25519.rs @@ -93,9 +93,9 @@ impl<'de> Deserialize<'de> for Ed25519PublicKey { } else { #[derive(Deserialize)] #[serde(rename = "Ed25519PublicKey")] - struct Foo([u8; dalek::PUBLIC_KEY_LENGTH]); + struct PublicKey([u8; dalek::PUBLIC_KEY_LENGTH]); - let value = Foo::deserialize(deserializer)?; + let value = PublicKey::deserialize(deserializer)?; Ok(Self(value.0)) } } @@ -420,20 +420,20 @@ mod tests { } #[test] - fn test_publickey_serialization() { + fn test_public_key_serialization() { use crate::crypto::ed25519::Ed25519PublicKey; let key_in = Ed25519PublicKey::test_key(0); let s = serde_json::to_string(&key_in).unwrap(); let key_out: Ed25519PublicKey = serde_json::from_str(&s).unwrap(); assert_eq!(key_out, key_in); - let s = bincode::serialize(&key_in).unwrap(); - let key_out: Ed25519PublicKey = bincode::deserialize(&s).unwrap(); + let s = bcs::to_bytes(&key_in).unwrap(); + let key_out: Ed25519PublicKey = bcs::from_bytes(&s).unwrap(); assert_eq!(key_out, key_in); } #[test] - fn test_secretkey_serialization() { + fn test_secret_key_serialization() { use crate::crypto::ed25519::Ed25519SecretKey; let key_in = Ed25519SecretKey::generate(); let s = serde_json::to_string(&key_in).unwrap(); diff --git a/linera-base/src/crypto/mod.rs b/linera-base/src/crypto/mod.rs index 6baf3f1464e6..f7a8d6c12870 100644 --- a/linera-base/src/crypto/mod.rs +++ b/linera-base/src/crypto/mod.rs @@ -40,8 +40,8 @@ pub type AccountSignature = ed25519::Ed25519Signature; pub enum CryptoError { #[error("Signature for object {type_name} is not valid: {error}")] InvalidSignature { error: String, type_name: String }, - #[error("Signature for object {type_name} is missing")] - MissingSignature { type_name: String }, + #[error("Signature from validator is missing")] + MissingValidatorSignature, #[error(transparent)] NonHexDigits(#[from] hex::FromHexError), #[error( diff --git a/linera-base/src/crypto/secp256k1.rs b/linera-base/src/crypto/secp256k1.rs index be7a6cd60c0d..db04e9ca61a0 100644 --- a/linera-base/src/crypto/secp256k1.rs +++ b/linera-base/src/crypto/secp256k1.rs @@ -89,8 +89,8 @@ impl Serialize for Secp256k1PublicKey { } else { #[derive(Serialize)] #[serde(rename = "Secp256k1PublicKey")] - struct Foo<'a>(&'a secp256k1::PublicKey); - Foo(&self.0).serialize(serializer) + struct PublicKey<'a>(&'a secp256k1::PublicKey); + PublicKey(&self.0).serialize(serializer) } } } @@ -108,8 +108,8 @@ impl<'de> Deserialize<'de> for Secp256k1PublicKey { } else { #[derive(Deserialize)] #[serde(rename = "Secp256k1PublicKey")] - struct Foo(secp256k1::PublicKey); - let value = Foo::deserialize(deserializer)?; + struct PublicKey(secp256k1::PublicKey); + let value = PublicKey::deserialize(deserializer)?; Ok(Self(value.0)) } } @@ -221,7 +221,7 @@ impl Secp256k1Signature { Ok(()) } - /// Converts the signature to a byte array. + /// Creates a signature from the bytes. /// Expects the signature to be serialized in compact form. pub fn from_slice>(bytes: A) -> Result { let sig = secp256k1::ecdsa::Signature::from_compact(bytes.as_ref()) @@ -312,20 +312,20 @@ mod tests { } #[test] - fn test_publickey_serialization() { + fn test_public_key_serialization() { use crate::crypto::secp256k1::Secp256k1PublicKey; let key_in = Secp256k1PublicKey::test_key(0); let s = serde_json::to_string(&key_in).unwrap(); let key_out: Secp256k1PublicKey = serde_json::from_str(&s).unwrap(); assert_eq!(key_out, key_in); - let s = bincode::serialize(&key_in).unwrap(); - let key_out: Secp256k1PublicKey = bincode::deserialize(&s).unwrap(); + let s = bcs::to_bytes(&key_in).unwrap(); + let key_out: Secp256k1PublicKey = bcs::from_bytes(&s).unwrap(); assert_eq!(key_out, key_in); } #[test] - fn test_secretkey_deserialization() { + fn test_secret_key_serialization() { use crate::crypto::secp256k1::{Secp256k1KeyPair, Secp256k1SecretKey}; let key_in = Secp256k1KeyPair::generate().secret_key; let s = serde_json::to_string(&key_in).unwrap(); diff --git a/linera-chain/src/chain.rs b/linera-chain/src/chain.rs index 563f142be269..1b61468ae584 100644 --- a/linera-chain/src/chain.rs +++ b/linera-chain/src/chain.rs @@ -608,7 +608,7 @@ where self.execution_state.system.ownership.get().clone(), BlockHeight(0), local_time, - maybe_committee.flat_map(|(_, committee)| committee.fallback_keys_and_weights()), + maybe_committee.flat_map(|(_, committee)| committee.account_keys_and_weights()), ) } @@ -1040,7 +1040,7 @@ where let maybe_committee = self.execution_state.system.current_committee().into_iter(); let ownership = self.execution_state.system.ownership.get().clone(); let fallback_owners = - maybe_committee.flat_map(|(_, committee)| committee.fallback_keys_and_weights()); + maybe_committee.flat_map(|(_, committee)| committee.account_keys_and_weights()); self.pending_validated_blobs.clear(); self.pending_proposed_blobs.clear(); self.manager diff --git a/linera-chain/src/unit_tests/data_types_tests.rs b/linera-chain/src/unit_tests/data_types_tests.rs index b60b3e60870d..21ad1145cf36 100644 --- a/linera-chain/src/unit_tests/data_types_tests.rs +++ b/linera-chain/src/unit_tests/data_types_tests.rs @@ -12,8 +12,8 @@ use crate::{ #[test] fn test_signed_values() { - let validator1_keypair = ValidatorKeypair::generate(); - let validator2_keypair = ValidatorKeypair::generate(); + let validator1_key_pair = ValidatorKeypair::generate(); + let validator2_key_pair = ValidatorKeypair::generate(); let block = make_first_block(ChainId::root(1)).with_simple_transfer(ChainId::root(2), Amount::ONE); @@ -29,7 +29,7 @@ fn test_signed_values() { let confirmed_vote = LiteVote::new( LiteValue::new(&confirmed_value), Round::Fast, - &validator1_keypair.secret_key, + &validator1_key_pair.secret_key, ); assert!(confirmed_vote.check().is_ok()); @@ -37,7 +37,7 @@ fn test_signed_values() { let validated_vote = LiteVote::new( LiteValue::new(&validated_value), Round::Fast, - &validator1_keypair.secret_key, + &validator1_key_pair.secret_key, ); assert_ne!( confirmed_vote.value, validated_vote.value, @@ -47,9 +47,9 @@ fn test_signed_values() { let mut v = LiteVote::new( LiteValue::new(&confirmed_value), Round::Fast, - &validator2_keypair.secret_key, + &validator2_key_pair.secret_key, ); - v.public_key = validator1_keypair.public_key; + v.public_key = validator1_key_pair.public_key; assert!(v.check().is_err()); assert!(validated_vote.check().is_ok()); @@ -92,15 +92,15 @@ fn test_hashes() { #[test] fn test_certificates() { - let validator1_keypair = ValidatorKeypair::generate(); + let validator1_key_pair = ValidatorKeypair::generate(); let account1_secret = AccountSecretKey::generate(); - let validator2_keypair = ValidatorKeypair::generate(); + let validator2_key_pair = ValidatorKeypair::generate(); let account2_secret = AccountSecretKey::generate(); - let validator3_keypair = ValidatorKeypair::generate(); + let validator3_key_pair = ValidatorKeypair::generate(); let committee = Committee::make_simple(vec![ - (validator1_keypair.public_key, account1_secret.public()), - (validator2_keypair.public_key, account2_secret.public()), + (validator1_key_pair.public_key, account1_secret.public()), + (validator2_key_pair.public_key, account2_secret.public()), ]); let block = @@ -117,17 +117,17 @@ fn test_certificates() { let v1 = LiteVote::new( LiteValue::new(&value), Round::Fast, - &validator1_keypair.secret_key, + &validator1_key_pair.secret_key, ); let v2 = LiteVote::new( LiteValue::new(&value), Round::Fast, - &validator2_keypair.secret_key, + &validator2_key_pair.secret_key, ); let v3 = LiteVote::new( LiteValue::new(&value), Round::Fast, - &validator3_keypair.secret_key, + &validator3_key_pair.secret_key, ); let mut builder = SignatureAggregator::new(value.clone(), Round::Fast, &committee); diff --git a/linera-core/src/data_types.rs b/linera-core/src/data_types.rs index 439bac57fb17..3c5ad0c2256d 100644 --- a/linera-core/src/data_types.rs +++ b/linera-core/src/data_types.rs @@ -310,9 +310,7 @@ impl ChainInfoResponse { pub fn check(&self, public_key: &ValidatorPublicKey) -> Result<(), CryptoError> { match self.signature.as_ref() { Some(sig) => sig.check(&*self.info, public_key), - None => Err(CryptoError::MissingSignature { - type_name: "ValidatorSignature".to_string(), - }), + None => Err(CryptoError::MissingValidatorSignature), } } diff --git a/linera-execution/src/committee.rs b/linera-execution/src/committee.rs index 2941a845c38e..d5c8d50fad3e 100644 --- a/linera-execution/src/committee.rs +++ b/linera-execution/src/committee.rs @@ -316,13 +316,13 @@ impl Committee { pub fn make_simple(keys: Vec<(ValidatorPublicKey, AccountPublicKey)>) -> Self { let map = keys .into_iter() - .map(|k| { + .map(|(validator_key, account_key)| { ( - k.0, + validator_key, ValidatorState { - network_address: k.0.to_string(), + network_address: validator_key.to_string(), votes: 1, - account_public_key: k.1, + account_public_key: account_key, }, ) }) @@ -343,7 +343,7 @@ impl Committee { .map(|(name, validator)| (*name, validator.votes)) } - pub fn fallback_keys_and_weights(&self) -> impl Iterator + '_ { + pub fn account_keys_and_weights(&self) -> impl Iterator + '_ { self.validators .values() .map(|validator| (validator.account_public_key, validator.votes)) diff --git a/linera-service/src/cli_wrappers/local_net.rs b/linera-service/src/cli_wrappers/local_net.rs index 8550134d75dc..d4c5b32e0eb2 100644 --- a/linera-service/src/cli_wrappers/local_net.rs +++ b/linera-service/src/cli_wrappers/local_net.rs @@ -395,7 +395,7 @@ impl LocalNet { } fn configuration_string(&self, server_number: usize) -> Result { - let n: usize = server_number; + let n = server_number; let path = self .path_provider .path() @@ -464,9 +464,9 @@ impl LocalNet { .map(|keys| keys.split(',').map(str::to_string).collect::>()) .enumerate() .map(|(i, keys)| { - let public_key = keys[0].to_string(); - let secret_key = keys[1].to_string(); - (i, (public_key, secret_key)) + let validator_key = keys[0].to_string(); + let account_key = keys[1].to_string(); + (i, (validator_key, account_key)) }) .collect(); Ok(()) @@ -665,6 +665,7 @@ impl LocalNet { #[cfg(with_testing)] impl LocalNet { + /// Returns the validating key and an account key of the validator. pub fn validator_keys(&self, validator: usize) -> Option<&(String, String)> { self.validator_keys.get(&validator) } diff --git a/linera-storage/src/lib.rs b/linera-storage/src/lib.rs index 7aa4edc6bb1b..9dbf385a6ac1 100644 --- a/linera-storage/src/lib.rs +++ b/linera-storage/src/lib.rs @@ -222,7 +222,7 @@ pub trait Storage: Sized { ChainOwnership::single(owner), BlockHeight(0), self.clock().current_time(), - committee.fallback_keys_and_weights(), + committee.account_keys_and_weights(), )?; let system_state = &mut chain.execution_state.system; system_state.description.set(Some(description)); From c2cad694c00323576314a0d18d5d6f2b16b4bde7 Mon Sep 17 00:00:00 2001 From: deuszx Date: Sat, 22 Feb 2025 18:04:23 +0000 Subject: [PATCH 5/6] Review part 2. --- linera-base/src/crypto/ed25519.rs | 4 ++-- linera-rpc/tests/format.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/linera-base/src/crypto/ed25519.rs b/linera-base/src/crypto/ed25519.rs index 9b12b2dfea5f..4009fcbc14bc 100644 --- a/linera-base/src/crypto/ed25519.rs +++ b/linera-base/src/crypto/ed25519.rs @@ -351,9 +351,9 @@ impl<'de> Deserialize<'de> for Ed25519Signature { } else { #[derive(Deserialize)] #[serde(rename = "Ed25519Signature")] - struct Foo(dalek::Signature); + struct Signature(dalek::Signature); - let value = Foo::deserialize(deserializer)?; + let value = Signature::deserialize(deserializer)?; Ok(Self(value.0)) } } diff --git a/linera-rpc/tests/format.rs b/linera-rpc/tests/format.rs index fcfa28325be1..8cdba6ffff9b 100644 --- a/linera-rpc/tests/format.rs +++ b/linera-rpc/tests/format.rs @@ -30,7 +30,6 @@ fn get_registry() -> Result { ); let mut samples = Samples::new(); // 1. Record samples for types with custom deserializers. - // 2. Trace the main entry point(s) + every enum separately. { // Record sample values for Secp256k1PublicKey and Secp256k1Signature // as the ones generated by serde-reflection are not valid and will fail. @@ -42,6 +41,7 @@ fn get_registry() -> Result { tracer.trace_value(&mut samples, &validator_keypair.public_key)?; tracer.trace_value(&mut samples, &validator_signature)?; } + // 2. Trace the main entry point(s) + every enum separately. tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; From ab824fbfedae0a452e138c2ab01f9d51215929bb Mon Sep 17 00:00:00 2001 From: deuszx Date: Sat, 22 Feb 2025 18:44:53 +0000 Subject: [PATCH 6/6] Serialize secp256k1 signatures in a compact form rather than DER. --- Cargo.lock | 29 ++++++++ Cargo.toml | 1 + examples/Cargo.lock | 70 +++++++++++++++++++ linera-base/Cargo.toml | 1 + linera-base/src/crypto/secp256k1.rs | 25 +++++-- .../tests/snapshots/format__format.yaml.snap | 5 +- 6 files changed, 126 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 911e9cb15a47..2d15a8859785 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4408,6 +4408,7 @@ dependencies = [ "serde-name", "serde_bytes", "serde_json", + "serde_with", "test-case", "test-strategy", "thiserror 1.0.69", @@ -7446,6 +7447,34 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.95", +] + [[package]] name = "serde_yaml" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index 532c7fb80fd6..f7811050854f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,6 +147,7 @@ serde_yaml = "0.8.26" serde-name = "0.2.1" serde-reflection = "0.3.6" serde-wasm-bindgen = "0.6.5" +serde_with = { version = "3", default-features = false, features = ["alloc", "macros"] } sha3 = "0.10.8" similar-asserts = "1.5.0" static_assertions = "1.1.0" diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 03b753d1477d..481c82419bc1 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -1393,6 +1393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "num-traits", + "serde", ] [[package]] @@ -1970,6 +1971,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivative" version = "2.2.0" @@ -3518,6 +3528,7 @@ dependencies = [ "serde-name", "serde_bytes", "serde_json", + "serde_with", "test-strategy", "thiserror 1.0.65", "tokio", @@ -4276,6 +4287,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -4563,6 +4580,12 @@ dependencies = [ "serde", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -5565,6 +5588,34 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.95", +] + [[package]] name = "sha2" version = "0.10.8" @@ -6069,6 +6120,25 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "tiny-keccak" version = "2.0.2" diff --git a/linera-base/Cargo.toml b/linera-base/Cargo.toml index 2b22afaf5aaf..9e4045e9d3c2 100644 --- a/linera-base/Cargo.toml +++ b/linera-base/Cargo.toml @@ -52,6 +52,7 @@ serde.workspace = true serde-name.workspace = true serde_bytes.workspace = true serde_json.workspace = true +serde_with.workspace = true test-strategy = { workspace = true, optional = true } thiserror.workspace = true tokio = { workspace = true, features = ["time"] } diff --git a/linera-base/src/crypto/secp256k1.rs b/linera-base/src/crypto/secp256k1.rs index db04e9ca61a0..de478156ab4b 100644 --- a/linera-base/src/crypto/secp256k1.rs +++ b/linera-base/src/crypto/secp256k1.rs @@ -238,7 +238,8 @@ impl Serialize for Secp256k1Signature { if serializer.is_human_readable() { serializer.serialize_str(&hex::encode(self.0.serialize_der())) } else { - serializer.serialize_newtype_struct("Secp256k1Signature", &self.0) + let compact = serde_utils::CompactSignature(self.0.serialize_compact()); + serializer.serialize_newtype_struct("Secp256k1Signature", &compact) } } } @@ -257,10 +258,10 @@ impl<'de> Deserialize<'de> for Secp256k1Signature { } else { #[derive(Deserialize)] #[serde(rename = "Secp256k1Signature")] - struct Foo(secp256k1::ecdsa::Signature); + struct Signature(serde_utils::CompactSignature); - let value = Foo::deserialize(deserializer)?; - Ok(Self(value.0)) + let value = Signature::deserialize(deserializer)?; + Self::from_slice(value.0 .0.as_ref()).map_err(serde::de::Error::custom) } } } @@ -281,6 +282,22 @@ impl fmt::Debug for Secp256k1Signature { doc_scalar!(Secp256k1Signature, "A secp256k1 signature value"); doc_scalar!(Secp256k1PublicKey, "A secp256k1 public key value"); +mod serde_utils { + use serde::{Deserialize, Serialize}; + use serde_with::serde_as; + + /// Wrapper around compact signature serialization + /// so that we can implement custom serializer for it that uses fixed length. + // Serde treats arrays larger than 32 as variable length arrays, and adds the length as a prefix. + // Since we want a fixed size representation, we wrap it in this helper struct and use serde_as. + #[serde_as] + #[derive(Serialize, Deserialize)] + #[serde(transparent)] + pub struct CompactSignature( + #[serde_as(as = "[_; 64]")] pub [u8; secp256k1::constants::COMPACT_SIGNATURE_SIZE], + ); +} + #[cfg(with_testing)] mod tests { #[test] diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index 3f46e6005149..138e9eb58222 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -946,7 +946,10 @@ Secp256k1PublicKey: CONTENT: U8 SIZE: 33 Secp256k1Signature: - NEWTYPESTRUCT: BYTES + NEWTYPESTRUCT: + TUPLEARRAY: + CONTENT: U8 + SIZE: 64 StreamId: STRUCT: - application_id: