diff --git a/CLI.md b/CLI.md index eb5687bb712a..6925e5e77179 100644 --- a/CLI.md +++ b/CLI.md @@ -95,7 +95,7 @@ A Byzantine-fault tolerant sidechain with low-latency finality and high throughp * `create-application` — Create an application * `publish-and-create` — Create an application, and publish the required bytecode * `request-application` — Request an application from another chain, so it can be used on this one -* `keygen` — Create an unassigned key-pair +* `keygen` — Create an unassigned key pair * `assign` — Link an owner with a key pair in the wallet to a chain that was created for that owner * `retry-pending-block` — Retry a block we unsuccessfully tried to propose earlier * `wallet` — Show the contents of the wallet @@ -700,7 +700,7 @@ Request an application from another chain, so it can be used on this one ## `linera keygen` -Create an unassigned key-pair +Create an unassigned key pair **Usage:** `linera keygen` diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 34167d6062c8..03b753d1477d 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -4350,9 +4350,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "oneshot" diff --git a/linera-base/src/crypto/ed25519.rs b/linera-base/src/crypto/ed25519.rs index 303a89b731f0..d5246f085f79 100644 --- a/linera-base/src/crypto/ed25519.rs +++ b/linera-base/src/crypto/ed25519.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; use super::{ le_bytes_to_u64_array, u64_array_to_le_bytes, BcsHashable, BcsSignable, CryptoError, - CryptoHash, HasTypeName, Hashable, ValidatorPublicKey, ValidatorSignature, + CryptoHash, HasTypeName, Hashable, }; use crate::{doc_scalar, identifiers::Owner}; @@ -32,25 +32,25 @@ pub struct Ed25519Signature(pub dalek::Signature); impl Ed25519SecretKey { #[cfg(all(with_getrandom, with_testing))] - /// Generates a new key-pair. + /// Generates a new key pair. pub fn generate() -> Self { let mut rng = rand::rngs::OsRng; Self::generate_from(&mut rng) } #[cfg(with_getrandom)] - /// Generates a new key-pair from the given RNG. Use with care. + /// Generates a new key pair from the given RNG. Use with care. pub fn generate_from(rng: &mut R) -> Self { let keypair = dalek::SigningKey::generate(rng); Ed25519SecretKey(keypair) } - /// Obtains the public key of a key-pair. + /// Obtains the public key of a key pair. pub fn public(&self) -> Ed25519PublicKey { Ed25519PublicKey(self.0.verifying_key().to_bytes()) } - /// Copies the key-pair, **including the secret key**. + /// Copies the key pair, **including the secret key**. /// /// The `Clone` and `Copy` traits are deliberately not implemented for `KeyPair` to prevent /// accidental copies of secret keys. @@ -330,7 +330,7 @@ impl Ed25519Signature { pub fn verify_batch<'a, 'de, T, I>(value: &'a T, votes: I) -> Result<(), CryptoError> where T: BcsSignable<'de>, - I: IntoIterator, + I: IntoIterator, { Ed25519Signature::verify_batch_internal(value, votes).map_err(|error| { CryptoError::InvalidSignature { diff --git a/linera-base/src/crypto/mod.rs b/linera-base/src/crypto/mod.rs index 2ae5fb6c341c..40ec1fcdaca1 100644 --- a/linera-base/src/crypto/mod.rs +++ b/linera-base/src/crypto/mod.rs @@ -54,6 +54,8 @@ pub enum CryptoError { IncorrectPublicKeySize(usize), #[error("Could not parse integer: {0}")] ParseIntError(#[from] ParseIntError), + #[error("secp256k1 error: {0}")] + Secp256k1Error(::secp256k1::Error), } #[cfg(with_getrandom)] diff --git a/linera-base/src/crypto/secp256k1.rs b/linera-base/src/crypto/secp256k1.rs index be165d704e7d..709699cad7bd 100644 --- a/linera-base/src/crypto/secp256k1.rs +++ b/linera-base/src/crypto/secp256k1.rs @@ -4,39 +4,156 @@ //! Defines secp256k1 signature primitives used by the Linera protocol. -use std::fmt; +use std::{fmt, str::FromStr, sync::LazyLock}; -use secp256k1::{self, Message}; +use secp256k1::{self, All, Message, Secp256k1}; use serde::{Deserialize, Serialize}; use super::{BcsSignable, CryptoError, CryptoHash, HasTypeName}; use crate::doc_scalar; +/// Static secp256k1 context for reuse. +pub static SECP256K1: LazyLock> = LazyLock::new(secp256k1::Secp256k1::new); + +/// A secp256k1 secret key. +#[derive(Eq, PartialEq)] +pub struct Secp256k1SecretKey(pub secp256k1::SecretKey); + +/// A secp256k1 public key. +#[derive(Eq, PartialEq, Copy, Clone)] +pub struct Secp256k1PublicKey(pub secp256k1::PublicKey); + +/// Secp256k1 public/secret key pair. +#[derive(Debug, PartialEq, Eq)] +pub struct Secp256k1KeyPair { + /// Secret key. + pub secret_key: Secp256k1SecretKey, + /// Public key. + pub public_key: Secp256k1PublicKey, +} + /// A secp256k1 signature. #[derive(Eq, PartialEq, Copy, Clone)] pub struct Secp256k1Signature(pub secp256k1::ecdsa::Signature); +impl fmt::Debug for Secp256k1SecretKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} + +impl Serialize for Secp256k1PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(&hex::encode(self.0.serialize())) + } else { + serializer.serialize_newtype_struct("Secp256k1PublicKey", &self.0) + } + } +} + +impl<'de> Deserialize<'de> for Secp256k1PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + let value = hex::decode(s).map_err(serde::de::Error::custom)?; + let pk = secp256k1::PublicKey::from_slice(&value).map_err(serde::de::Error::custom)?; + Ok(Secp256k1PublicKey(pk)) + } else { + #[derive(Deserialize)] + #[serde(rename = "Secp256k1PublicKey")] + struct Foo(secp256k1::PublicKey); + + let value = Foo::deserialize(deserializer)?; + Ok(Self(value.0)) + } + } +} + +impl FromStr for Secp256k1PublicKey { + type Err = CryptoError; + + fn from_str(s: &str) -> Result { + let pk = secp256k1::PublicKey::from_str(s).map_err(CryptoError::Secp256k1Error)?; + Ok(Secp256k1PublicKey(pk)) + } +} + +impl TryFrom<&[u8]> for Secp256k1PublicKey { + type Error = CryptoError; + + fn try_from(value: &[u8]) -> Result { + let pk = secp256k1::PublicKey::from_slice(value).map_err(CryptoError::Secp256k1Error)?; + Ok(Secp256k1PublicKey(pk)) + } +} + +impl fmt::Display for Secp256k1PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = hex::encode(self.0.serialize()); + write!(f, "{}", s) + } +} + +impl fmt::Debug for Secp256k1PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(&self.0.serialize()[0..9])) + } +} + +impl Secp256k1KeyPair { + /// Generates a new key pair. + #[cfg(all(with_getrandom, with_testing))] + pub fn generate() -> Self { + let mut rng = rand::rngs::OsRng; + Self::generate_from(&mut rng) + } + + /// Generates a new key pair from the given RNG. Use with care. + #[cfg(with_getrandom)] + pub fn generate_from(rng: &mut R) -> Self { + let (sk, pk) = SECP256K1.generate_keypair(rng); + Secp256k1KeyPair { + secret_key: Secp256k1SecretKey(sk), + public_key: Secp256k1PublicKey(pk), + } + } +} + +impl Secp256k1SecretKey { + /// Returns a public key for the given secret key. + pub fn to_public(&self) -> Secp256k1PublicKey { + Secp256k1PublicKey(self.0.public_key(&SECP256K1)) + } +} + impl Secp256k1Signature { /// Computes a secp256k1 signature for `value` using the given `secret`. /// It first serializes the `T` type and then creates the `CryptoHash` from the serialized bytes. - pub fn new<'de, T>(value: &T, secret: &secp256k1::SecretKey) -> Self + pub fn new<'de, T>(value: &T, secret: &Secp256k1SecretKey) -> Self where T: BcsSignable<'de>, { - let secp = secp256k1::Secp256k1::new(); + let secp = secp256k1::Secp256k1::signing_only(); let message = Message::from_digest(CryptoHash::new(value).as_bytes().0); - let signature = secp.sign_ecdsa(&message, secret); + let signature = secp.sign_ecdsa(&message, &secret.0); Secp256k1Signature(signature) } /// Checks a signature. - pub fn check<'de, T>(&self, value: &T, author: &secp256k1::PublicKey) -> Result<(), CryptoError> + pub fn check<'de, T>(&self, value: &T, author: &Secp256k1PublicKey) -> Result<(), CryptoError> where T: BcsSignable<'de> + fmt::Debug, { - let secp = secp256k1::Secp256k1::new(); let message = Message::from_digest(CryptoHash::new(value).as_bytes().0); - secp.verify_ecdsa(&message, &self.0, author) + SECP256K1 + .verify_ecdsa(&message, &self.0, &author.0) .map_err(|error| CryptoError::InvalidSignature { error: error.to_string(), type_name: T::type_name().to_string(), @@ -100,24 +217,27 @@ mod secp256k1_tests { fn test_signatures() { use serde::{Deserialize, Serialize}; - use crate::crypto::{secp256k1::Secp256k1Signature, BcsSignable, TestString}; + use crate::crypto::{ + secp256k1::{Secp256k1KeyPair, Secp256k1Signature}, + BcsSignable, TestString, + }; #[derive(Debug, Serialize, Deserialize)] struct Foo(String); impl<'de> BcsSignable<'de> for Foo {} - let (sk1, pk1) = secp256k1::Secp256k1::new().generate_keypair(&mut rand::thread_rng()); - let (_sk2, pk2) = secp256k1::Secp256k1::new().generate_keypair(&mut rand::thread_rng()); + let keypair1 = Secp256k1KeyPair::generate(); + let keypair2 = Secp256k1KeyPair::generate(); let ts = TestString("hello".into()); let tsx = TestString("hellox".into()); let foo = Foo("hello".into()); - let s = Secp256k1Signature::new(&ts, &sk1); - assert!(s.check(&ts, &pk1).is_ok()); - assert!(s.check(&ts, &pk2).is_err()); - assert!(s.check(&tsx, &pk1).is_err()); - assert!(s.check(&foo, &pk1).is_err()); + let s = Secp256k1Signature::new(&ts, &keypair1.secret_key); + assert!(s.check(&ts, &keypair1.public_key).is_ok()); + assert!(s.check(&ts, &keypair2.public_key).is_err()); + assert!(s.check(&tsx, &keypair1.public_key).is_err()); + assert!(s.check(&foo, &keypair1.public_key).is_err()); } } diff --git a/linera-chain/src/unit_tests/data_types_tests.rs b/linera-chain/src/unit_tests/data_types_tests.rs index f40c919d763b..a99bec38d8cd 100644 --- a/linera-chain/src/unit_tests/data_types_tests.rs +++ b/linera-chain/src/unit_tests/data_types_tests.rs @@ -84,10 +84,8 @@ fn test_certificates() { let key1 = ValidatorSecretKey::generate(); let key2 = ValidatorSecretKey::generate(); let key3 = ValidatorSecretKey::generate(); - let validator1_pk = key1.public(); - let validator2_pk = key2.public(); - let committee = Committee::make_simple(vec![validator1_pk, validator2_pk]); + let committee = Committee::make_simple(vec![key1.public(), key2.public()]); let block = make_first_block(ChainId::root(1)).with_simple_transfer(ChainId::root(1), Amount::ONE); diff --git a/linera-client/src/client_options.rs b/linera-client/src/client_options.rs index 8ad908110e64..7bf5f65c8c7b 100644 --- a/linera-client/src/client_options.rs +++ b/linera-client/src/client_options.rs @@ -875,7 +875,7 @@ pub enum ClientCommand { requester_chain_id: Option, }, - /// Create an unassigned key-pair. + /// Create an unassigned key pair. Keygen, /// Link an owner with a key pair in the wallet to a chain that was created for that owner.