From cba093392a810f9c8cf2df3a34f7d003d7d3e68d Mon Sep 17 00:00:00 2001 From: Nikita Strygin Date: Tue, 7 Nov 2023 20:25:00 +0300 Subject: [PATCH] [refactor] #3422: migrate iroha_crypto to use k256 This makes all the dependency tree of iroha_crypto wasm-compatible (except `getrandom`, which either requires a "js" feature enabled when used from web, or a custom getrandom implementation in other cases) Signed-off-by: Nikita Strygin --- Cargo.lock | 10 +- crypto/Cargo.toml | 10 +- crypto/src/signature/secp256k1.rs | 150 ++++++++++++++++-------------- p2p/src/lib.rs | 4 +- 4 files changed, 101 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ccdccded2d4..c17f8cb7b8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,6 +54,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "amcl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee5cca1ddc8b9dceb55b7f1272a9d1e643d73006f350a20ab4926d24e33f0f0d" + [[package]] name = "amcl_wrapper" version = "0.4.0" @@ -1401,7 +1407,6 @@ dependencies = [ "ff", "generic-array 0.14.7", "group", - "hkdf", "pkcs8", "rand_core 0.6.4", "sec1", @@ -2840,13 +2845,16 @@ name = "iroha_crypto" version = "2.0.0-pre-rc.20" dependencies = [ "aead", + "amcl", "amcl_wrapper", "arrayref", "blake2", "chacha20poly1305", "curve25519-dalek", "derive_more", + "digest 0.10.7", "ed25519-dalek", + "elliptic-curve", "getset", "hex", "hex-literal", diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index a4ee396a4d0..8d472157fa2 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -16,8 +16,10 @@ default = ["std"] # Please refer to https://docs.rust-embedded.org/book/intro/no-std.html std = [ "dep:blake2", + "dep:digest", "dep:sha2", "dep:hkdf", + "dep:amcl", "dep:amcl_wrapper", "dep:ed25519-dalek", "dep:curve25519-dalek", @@ -29,6 +31,7 @@ std = [ "dep:arrayref", "dep:aead", "dep:chacha20poly1305", + "dep:elliptic-curve", "dep:k256", ] # Force static linking @@ -50,12 +53,16 @@ parity-scale-codec = { workspace = true, features = ["derive", "full"] } serde = { workspace = true, features = ["derive"] } serde_with = { workspace = true, features = ["macros"] } hex = { workspace = true, features = ["alloc", "serde"] } +# TODO: iroha_crypto no longer depends on openssl (did it ever?) +# currently it's being used by iroha_client through attohttpc and iroha_cli through warp's tokeio-tunstenite openssl-sys = { version = "0.9.93", features = ["vendored"], optional = true } getset = { workspace = true } +digest = { version = "0.10.7", optional = true } blake2 = { version = "0.10.6", optional = true } sha2 = { version = "0.10.8", optional = true } hkdf = { version = "0.12.3", optional = true } +amcl = { version = "0.2.0", optional = true, default-features = false, features = ["secp256k1"] } amcl_wrapper = { version = "0.4.0", optional = true } ed25519-dalek = { version = "2.0.0", optional = true, features = ["rand_core"] } @@ -73,7 +80,8 @@ arrayref = { version = "0.3.7", optional = true } aead = { workspace = true, optional = true } chacha20poly1305 = { version = "0.10.1", optional = true } -k256 = { version = "0.13.1", optional = true, features = ["ecdh", "ecdsa", "sha256"]} +elliptic-curve = { version = "0.13.6", optional = true } +k256 = { version = "0.13.1", optional = true, features = ["ecdsa", "sha256"]} [dev-dependencies] hex-literal = { workspace = true } diff --git a/crypto/src/signature/secp256k1.rs b/crypto/src/signature/secp256k1.rs index 5f84b58f50e..d7256f46801 100644 --- a/crypto/src/signature/secp256k1.rs +++ b/crypto/src/signature/secp256k1.rs @@ -1,14 +1,12 @@ // TODO: clean up & remove -#![allow(missing_docs)] - -use rand::rngs::OsRng; -use sha2::digest::generic_array::typenum::U32; +#![allow(missing_docs, unused)] use crate::{Algorithm, Error, KeyGenOption, PrivateKey, PublicKey}; pub const PRIVATE_KEY_SIZE: usize = 32; pub const PUBLIC_KEY_SIZE: usize = 33; pub const SIGNATURE_SIZE: usize = 64; +pub const PUBLIC_UNCOMPRESSED_KEY_SIZE: usize = 65; const ALGORITHM: Algorithm = Algorithm::Secp256k1; @@ -45,30 +43,64 @@ impl EcdsaSecp256k1Sha256 { } mod ecdsa_secp256k1 { + use amcl::secp256k1::ecp; use arrayref::array_ref; + use digest::{consts::U32, Digest}; + use ed25519_dalek::{Signer, Verifier}; + use elliptic_curve::sec1::FromEncodedPoint; use iroha_primitives::const_vec::ConstVec; - use rand::{RngCore, SeedableRng}; + use k256; + use rand::{rngs::OsRng, RngCore, SeedableRng}; use rand_chacha::ChaChaRng; - use secp256k1; - use sha2::Digest; use zeroize::Zeroize; - use super::*; + use super::{ALGORITHM, PRIVATE_KEY_SIZE, PUBLIC_KEY_SIZE, PUBLIC_UNCOMPRESSED_KEY_SIZE}; + use crate::{Error, KeyGenOption, PrivateKey, PublicKey}; - pub struct EcdsaSecp256k1Impl(secp256k1::Secp256k1); + pub struct EcdsaSecp256k1Impl; impl EcdsaSecp256k1Impl { + pub fn public_key_compressed(&self, pk: &PublicKey) -> Vec { + assert_eq!(pk.digest_function, ALGORITHM); + let mut compressed = [0u8; PUBLIC_KEY_SIZE]; + ecp::ECP::frombytes(&pk.payload[..]).tobytes(&mut compressed, true); + compressed.to_vec() + } + + pub fn public_key_uncompressed(&self, pk: &PublicKey) -> Vec { + assert_eq!(pk.digest_function, ALGORITHM); + let mut uncompressed = [0u8; PUBLIC_UNCOMPRESSED_KEY_SIZE]; + ecp::ECP::frombytes(&pk.payload[..]).tobytes(&mut uncompressed, false); + uncompressed.to_vec() + } + + pub fn parse(&self, data: &[u8]) -> Result { + match data.len() { + PUBLIC_KEY_SIZE => Ok(PublicKey { + digest_function: ALGORITHM, + payload: ConstVec::new(data.to_vec()), + }), + PUBLIC_UNCOMPRESSED_KEY_SIZE => { + let mut compressed = [0u8; PUBLIC_KEY_SIZE]; + ecp::ECP::frombytes(data).tobytes(&mut compressed, true); + Ok(PublicKey { + digest_function: ALGORITHM, + payload: ConstVec::new(compressed.to_vec()), + }) + } + _ => Err(Error::Parse("Invalid key length".to_string())), + } + } + pub fn new() -> Self { - Self(secp256k1::Secp256k1::new()) + Self {} } - pub(crate) fn keypair( + + pub fn keypair>( &self, option: Option, - ) -> Result<(PublicKey, PrivateKey), Error> - where - D: Digest, - { - let sk = match option { + ) -> Result<(PublicKey, PrivateKey), Error> { + let signing_key = match option { Some(mut o) => match o { KeyGenOption::UseSeed(ref mut seed) => { let mut s = [0u8; PRIVATE_KEY_SIZE]; @@ -77,44 +109,43 @@ mod ecdsa_secp256k1 { rng.fill_bytes(&mut s); let k = D::digest(&s); s.zeroize(); - secp256k1::SecretKey::from_slice(k.as_slice())? + k256::SecretKey::from_slice(k.as_slice())? } KeyGenOption::FromPrivateKey(ref s) => { assert_eq!(s.digest_function, ALGORITHM); - secp256k1::SecretKey::from_slice(&s.payload[..])? + k256::SecretKey::from_slice(&s.payload[..])? } }, - None => { - let mut s = [0u8; PRIVATE_KEY_SIZE]; - OsRng.fill_bytes(&mut s); - let k = D::digest(&s); - s.zeroize(); - secp256k1::SecretKey::from_slice(k.as_slice())? - } + None => k256::SecretKey::random(&mut OsRng), }; - let pk = secp256k1::PublicKey::from_secret_key(&self.0, &sk); + + let public_key = signing_key.public_key(); + let compressed = public_key.to_sec1_bytes(); //serialized as compressed point Ok(( PublicKey { digest_function: ALGORITHM, - payload: ConstVec::new(pk.serialize().to_vec()), + payload: ConstVec::new(compressed), }, PrivateKey { digest_function: ALGORITHM, - payload: ConstVec::new(sk[..].to_vec()), + payload: ConstVec::new(signing_key.to_bytes().to_vec()), }, )) } + pub fn sign(&self, message: &[u8], sk: &PrivateKey) -> Result, Error> where D: Digest, { assert_eq!(sk.digest_function, ALGORITHM); - let h = D::digest(message); - let msg = secp256k1::Message::from_digest_slice(h.as_slice())?; - let s = secp256k1::SecretKey::from_slice(&sk.payload[..])?; - let sig = self.0.sign_ecdsa(&msg, &s); - Ok(sig.serialize_compact().to_vec()) + let signing_key = k256::SecretKey::from_slice(&sk.payload[..]) + .map_err(|e| Error::Signing(format!("{:?}", e)))?; + let signing_key = k256::ecdsa::SigningKey::from(signing_key); + + let signature: k256::ecdsa::Signature = signing_key.sign(message); + Ok(signature.to_bytes().to_vec()) } + pub fn verify( &self, message: &[u8], @@ -124,50 +155,31 @@ mod ecdsa_secp256k1 { where D: Digest, { - assert_eq!(pk.digest_function, ALGORITHM); - let h = D::digest(message); - let msg = secp256k1::Message::from_digest_slice(h.as_slice())?; - let p = secp256k1::PublicKey::from_slice(&pk.payload[..])?; - let sig = secp256k1::ecdsa::Signature::from_compact(signature)?; - let res = self.0.verify_ecdsa(&msg, &sig, &p); - match res { - Ok(()) => Ok(true), - Err(secp256k1::Error::IncorrectSignature) => Ok(false), - Err(err) => Err(Error::from(err)), - } + let compressed_pk = self.public_key_compressed(pk); + let verifying_key = k256::PublicKey::from_sec1_bytes(&compressed_pk) + .map_err(|e| Error::Signing(format!("{:?}", e)))?; + let signature = k256::ecdsa::Signature::from_slice(signature) + .map_err(|e| Error::Signing(format!("{:?}", e)))?; + + let verifying_key = k256::ecdsa::VerifyingKey::from(verifying_key); + + Ok(verifying_key.verify(message, &signature).is_ok()) } + pub fn normalize_s(&self, signature: &mut [u8]) -> Result<(), Error> { - let mut sig = secp256k1::ecdsa::Signature::from_compact(signature)?; + let mut sig = k256::ecdsa::Signature::from_slice(signature) + .map_err(|e| Error::Signing(format!("{:?}", e)))?; sig.normalize_s(); - let compact = sig.serialize_compact(); - signature.clone_from_slice(&compact[..]); + signature.copy_from_slice(&sig.to_bytes()); Ok(()) } } } -impl From for Error { - fn from(error: secp256k1::Error) -> Error { - match error { - secp256k1::Error::IncorrectSignature => Error::Parse("Incorrect Signature".to_string()), - secp256k1::Error::InvalidMessage => Error::Parse("Invalid Message".to_string()), - secp256k1::Error::InvalidPublicKey => Error::Parse("Invalid Public Key".to_string()), - secp256k1::Error::InvalidSignature => Error::Parse("Invalid Signature".to_string()), - secp256k1::Error::InvalidSecretKey => Error::Parse("Invalid Secret Key".to_string()), - secp256k1::Error::InvalidRecoveryId => Error::Parse("Invalid Recovery Id".to_string()), - secp256k1::Error::InvalidTweak => Error::Parse("Invalid Tweak".to_string()), - secp256k1::Error::NotEnoughMemory => Error::Parse("Not Enough Memory".to_string()), - secp256k1::Error::InvalidSharedSecret => { - Error::Parse("Invalid Shared Secret".to_string()) - } - secp256k1::Error::InvalidPublicKeySum => { - Error::Parse("Invalid Public Key Sum".to_string()) - } - secp256k1::Error::InvalidParityValue(e) => { - Error::Parse(format!("Invalid Parity Value: {}", e)) - } - secp256k1::Error::InvalidEllSwift => Error::Parse("Invalid Ell Swift".to_string()), - } +impl From for Error { + fn from(error: elliptic_curve::Error) -> Error { + // rust crypto doesn't expose any kind of error information.. + Error::Other(format!("{}", error)) } } diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index fb34a8049b4..7d8ede3481a 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -71,8 +71,8 @@ pub enum CryptographicError { /// Encryption failed #[from(ignore)] Encrypt(aead::Error), - /// Ursa Cryptography error - Ursa(CryptoError), + /// Cryptography error + Crypto(CryptoError), } impl> From for Error {