Skip to content

Commit

Permalink
Use secp256k1 for validator keys.
Browse files Browse the repository at this point in the history
  • Loading branch information
deuszx committed Feb 20, 2025
1 parent e4d3b92 commit 7f80f13
Show file tree
Hide file tree
Showing 26 changed files with 335 additions and 131 deletions.
3 changes: 2 additions & 1 deletion CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <PUBLIC_KEY> --address <ADDRESS>`
**Usage:** `linera set-validator [OPTIONS] --public-key <PUBLIC_KEY> --account-key <ACCOUNT_KEY> --address <ADDRESS>`

###### **Options:**

* `--public-key <PUBLIC_KEY>` — The public key of the validator
* `--account-key <ACCOUNT_KEY>` — The public key of the account controlled by the validator
* `--address <ADDRESS>` — Network address
* `--votes <VOTES>` — Voting power

Expand Down
17 changes: 0 additions & 17 deletions linera-base/src/crypto/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
43 changes: 40 additions & 3 deletions linera-base/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -77,6 +79,41 @@ impl From<Option<u64>> for Box<dyn CryptoRng> {
}
}

#[cfg(all(with_testing))]

Check failure on line 82 in linera-base/src/crypto/mod.rs

View workflow job for this annotation

GitHub Actions / lint-cargo-clippy

unneeded sub `cfg` when there is only one condition
mod test_rng {
use rand::{rngs::mock::StepRng, RngCore};

/// A random number generator for testing.
/// We cannot use `StepRng` directly b/c of the Rust orphan rules.
pub(crate) struct TestRng(StepRng);

impl TestRng {
pub(crate) fn new(seed: u64) -> Self {
Self(StepRng::new(seed, 1))
}
}

impl RngCore for TestRng {
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}

fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.0.try_fill_bytes(dest)
}
}

impl rand::CryptoRng for TestRng {}
}

/// Something that we know how to hash.
pub trait Hashable<Hasher> {
/// Send the content of `Self` to the given hasher.
Expand Down
61 changes: 59 additions & 2 deletions linera-base/src/crypto/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub static SECP256K1: LazyLock<Secp256k1<All>> = 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.
Expand All @@ -36,12 +36,46 @@ 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(all(with_testing))]

Check failure on line 41 in linera-base/src/crypto/secp256k1.rs

View workflow job for this annotation

GitHub Actions / lint-cargo-clippy

unneeded sub `cfg` when there is only one condition
pub fn test_key(seed: u8) -> Self {
use super::test_rng::TestRng;

let mut rng = TestRng::new(seed as u64);
Secp256k1KeyPair::generate_from(&mut rng).public_key

Check failure on line 46 in linera-base/src/crypto/secp256k1.rs

View workflow job for this annotation

GitHub Actions / lint-wasm-applications

no function or associated item named `generate_from` found for struct `Secp256k1KeyPair` in the current scope
}
}

impl fmt::Debug for Secp256k1SecretKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<redacted for Secp256k1 secret key>")
}
}

impl Serialize for Secp256k1SecretKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
// This is only used for JSON configuration.
assert!(deserializer.is_human_readable());
let s = String::deserialize(deserializer)?;
todo!("Deserializing secret keys is not supported: {}", s)
}
}

impl Serialize for Secp256k1PublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
Expand Down Expand Up @@ -128,9 +162,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 {
Expand Down Expand Up @@ -159,6 +201,20 @@ 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<Item = &'a (Secp256k1PublicKey, Secp256k1Signature)>,
{
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(())
}
}

impl Serialize for Secp256k1Signature {
Expand Down Expand Up @@ -210,6 +266,7 @@ 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 {
Expand Down
4 changes: 2 additions & 2 deletions linera-chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
)
}

Expand Down Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions linera-chain/src/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -810,21 +810,21 @@ 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,
}
}

/// 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)?)
}
}

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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(())
}

Expand Down
3 changes: 2 additions & 1 deletion linera-chain/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -139,6 +139,7 @@ impl<T: CertificateValue> VoteTestExt<T> for Vote<T> {
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(),
Expand Down
6 changes: 5 additions & 1 deletion linera-chain/src/unit_tests/chain_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 7f80f13

Please sign in to comment.