From 42cf98aa365b5dacecadfc0d706084ee3e7c496f Mon Sep 17 00:00:00 2001 From: deuszx Date: Thu, 20 Feb 2025 13:50:22 +0000 Subject: [PATCH] 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 | 25 +++-- linera-service/tests/local_net_tests.rs | 16 +-- linera-storage/src/lib.rs | 2 +- 31 files changed, 399 insertions(+), 154 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..3c343ff89cce 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, }, )?) 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));