From 309af5234b3375e1c4484af670e2bb1d9c25ab3c Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 09:34:10 +0000 Subject: [PATCH 1/9] no_std conversion --- Cargo.toml | 17 +++++---- src/lib.rs | 90 ++-------------------------------------------- src/private_key.rs | 17 ++++----- src/public_key.rs | 46 ++++++------------------ 4 files changed, 29 insertions(+), 141 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e6dfb9..66aaef7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,22 +10,28 @@ repository = "https://github.com/lolo32/ed448-rust" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -docinclude = [] # Used only for activating `doc(include="...")` on stable. +default = ["std"] +std = [] [dependencies.lazy_static] version = "1.4.0" +default-features = false [dependencies.num-bigint] version = "0.4.0" +default-features = false [dependencies.num-integer] version = "0.1.44" +default-features = false [dependencies.num-traits] version = "0.2.14" +default-features = false [dependencies.opaque-debug] version = "0.3.0" +default-features = false [dependencies.rand_core] version = "0.6.2" @@ -38,18 +44,15 @@ version = "0.9.1" [dependencies.subtle] version = "2.4.0" default-features = false -features = ["std"] [dev-dependencies.base64] version = "0.13.0" +default-features = false [dev-dependencies.hex] version = "0.4.3" [dev-dependencies.rand_core] version = "0.6.2" -features = ["getrandom"] - -[package.metadata.docs.rs] -rustc-args = ["--cfg", "docsrs"] -features = ["docinclude"] # Activate `docinclude` during docs.rs build. +default-features = false +features = ["getrandom"] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 93cc579..c3352ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ #![cfg_attr(feature = "docinclude", feature(external_doc))] #![deny( - missing_docs, missing_copy_implementations, missing_debug_implementations, trivial_numeric_casts, @@ -115,92 +114,8 @@ clippy::module_name_repetitions )] -//! # EdDSA implementation for ed448 -//! -//! This is a Edwards-Curve Digital Signature Algorithm (EdDSA) for ed448 only -//! in pure rust. -//! -//! # Usage -//! -//! There is two variants that can be combined to sign/verify: -//! -//! 1. [`PrivateKey::sign`](crate::PrivateKey::sign) to sign all the content -//! as-it and [`PrivateKey::sign_ph`](crate::PrivateKey::sign_ph) to -//! pre-hash the message internaly before signing it. It will be hashed -//! using Shake256 and the result of 64 byte will be signed/verified. -//! -//! Note: use the same variant for verifying the signature. -//! -//! 2. The second parameter of [`sign`](crate::PrivateKey::sign)/ -//! [`sign_ph`](crate::PrivateKey::sign_ph) and the third of -//! [`verify`](crate::PublicKey::verify)/ -//! [`verify_ph`](crate::PublicKey::verify_ph) if an optional context -//! of 255 byte length max. -//! -//! The context can be used to facilitate different signature over -//! different protocol, but it must be immuable over the protocol. -//! More information about this can be found at -//! [RFC 8032 Use of Contexts](https://tools.ietf.org/html/rfc8032#section-8.3). -//! -//! # Examples -//! -//! ## Generating a new key pair -//! -//! ``` -//! use rand_core::OsRng; -//! use ed448_rust::{PrivateKey, PublicKey}; -//! let private_key = PrivateKey::new(&mut OsRng); -//! let public_key = PublicKey::from(&private_key); -//! ``` -//! -//! ## Sign a message -//! -//! ``` -//! # use rand_core::OsRng; -//! use ed448_rust::{PrivateKey, Ed448Error}; -//! # let retrieve_pkey = || PrivateKey::new(&mut OsRng); -//! let message = b"Message to sign"; -//! let private_key = retrieve_pkey(); -//! match private_key.sign(message, None) { -//! Ok(signature) => { -//! // Signature OK, use it -//! // This is a slice of 144 byte length -//! } -//! Err(Ed448Error::ContextTooLong) => { -//! // The used context is more than 255 bytes length -//! } -//! Err(_) => unreachable!() -//! } -//! ``` -//! -//! ## Verify a signature -//! -//! ``` -//! # use rand_core::OsRng; -//! use ed448_rust::{PublicKey, Ed448Error}; -//! # let private_key = ed448_rust::PrivateKey::new(&mut OsRng); -//! let message = b"Signed message to verify"; -//! # let retrieve_signature = || private_key.sign(message, None).unwrap(); -//! # let retrieve_pubkey = || PublicKey::from(&private_key); -//! let public_key = retrieve_pubkey(); // A slice or array of KEY_LENGTH byte length -//! let signature = retrieve_signature(); // A slice or array of SIG_LENGTH byte length -//! match public_key.verify(message, &signature, None) { -//! Ok(()) => { -//! // Signature OK, use the message -//! } -//! Err(Ed448Error::InvalidSignature) => { -//! // The verification of the signature is invalid -//! } -//! Err(Ed448Error::ContextTooLong) => { -//! // The used context is more than 255 bytes length -//! } -//! Err(Ed448Error::WrongSignatureLength) => { -//! // The signature is not 144 bytes length -//! } -//! Err(_) => unreachable!() -//! } -//! ``` - +extern crate alloc; +use alloc::{vec::Vec, boxed::Box, borrow::Cow}; use sha3::{ digest::{ExtendableOutput, Update}, Shake256, @@ -210,7 +125,6 @@ pub use crate::error::Ed448Error; pub use private_key::PrivateKey; pub use public_key::PublicKey; -use std::borrow::Cow; mod error; mod point; diff --git a/src/private_key.rs b/src/private_key.rs index 5e2b238..fdc61e0 100644 --- a/src/private_key.rs +++ b/src/private_key.rs @@ -14,7 +14,9 @@ use core::convert::{TryFrom, TryInto}; +use alloc::vec; use num_bigint::{BigInt, Sign}; +#[cfg(feature = "std")] use rand_core::{CryptoRng, RngCore}; use sha3::{ digest::{ExtendableOutput, Update}, @@ -48,6 +50,8 @@ impl PrivateKey { /// use ed448_rust::PrivateKey; /// let private_key = PrivateKey::new(&mut OsRng); /// ``` + + #[cfg(feature = "std")] pub fn new(rnd: &mut T) -> Self where T: CryptoRng + RngCore, @@ -57,16 +61,6 @@ impl PrivateKey { Self::from(key) } - /// Convert the private key to a format exportable. - /// - /// # Example - /// - /// ``` - /// # use rand_core::OsRng; - /// # use ed448_rust::PrivateKey; - /// # let private_key = PrivateKey::new(&mut OsRng); - /// let exportable_pkey = private_key.as_bytes(); - /// ``` #[inline] #[must_use] pub const fn as_bytes(&self) -> &[u8; KEY_LENGTH] { @@ -230,8 +224,10 @@ impl From<&'_ PrivateKeyRaw> for PrivateKey { #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "std")] use rand_core::OsRng; + #[cfg(feature = "std")] #[test] fn create_new_pkey() { let pkey = PrivateKey::new(&mut OsRng); @@ -245,6 +241,7 @@ mod tests { assert_eq!(invalid_pk.unwrap_err(), Ed448Error::WrongKeyLength); } + #[cfg(feature = "std")] #[test] fn invalid_context_length() { let pkey = PrivateKey::new(&mut OsRng); diff --git a/src/public_key.rs b/src/public_key.rs index edcb866..7a9b2f7 100644 --- a/src/public_key.rs +++ b/src/public_key.rs @@ -14,6 +14,7 @@ use core::convert::TryFrom; +use alloc::vec; use num_bigint::{BigInt, Sign}; use crate::{ @@ -40,42 +41,6 @@ impl PublicKey { self.0.encode() } - /// Verify signature with public key. - /// - /// # Example - /// - /// ``` - /// # use rand_core::OsRng; - /// use ed448_rust::{PublicKey, Ed448Error}; - /// # let private_key = ed448_rust::PrivateKey::new(&mut OsRng); - /// let message = b"Signed message to verify"; - /// # let retrieve_signature = || private_key.sign(message, None).unwrap(); - /// # let retrieve_pubkey = || PublicKey::from(&private_key); - /// let public_key = retrieve_pubkey(); - /// let signature = retrieve_signature(); - /// match public_key.verify(message, &signature, None) { - /// Ok(()) => { - /// // Signature OK, use the message - /// } - /// Err(Ed448Error::InvalidSignature) => { - /// // The verification of the signature is invalid - /// } - /// Err(Ed448Error::ContextTooLong) => { - /// // The used context is more than 255 bytes length - /// } - /// Err(Ed448Error::WrongSignatureLength) => { - /// // The signature is not 144 bytes length - /// } - /// Err(_) => unreachable!() - /// } - /// ``` - /// - /// # Errors - /// - /// * [`Ed448Error::InvalidSignature`] if the signature is not valid, either the public key - /// or the signature used are not the right, or the message has been altered. - /// * [`Ed448Error::ContextTooLong`] if the optional context is more than 255 byte length. - /// * [`Ed448Error::WrongSignatureLength`] if the signature is not `SIG_LENGTH` byte. #[inline] pub fn verify(&self, msg: &[u8], sign: &[u8], ctx: Option<&[u8]>) -> crate::Result<()> { self.verify_real(msg, sign, ctx, PreHash::False) @@ -189,9 +154,12 @@ impl TryFrom<&[u8]> for PublicKey { #[cfg(test)] mod tests { + #[cfg(feature = "std")] use std::convert::TryFrom; use super::*; + + #[cfg(feature = "std")] use rand_core::OsRng; #[test] @@ -213,6 +181,7 @@ mod tests { assert_eq!(&public.as_byte()[..], &ref_public[..]); } + #[cfg(feature = "std")] #[test] fn wrong_verification_with_another_pub_key() { let secret_1 = PrivateKey::new(&mut OsRng); @@ -231,6 +200,7 @@ mod tests { assert_eq!(pub_key.unwrap_err(), Ed448Error::WrongPublicKeyLength); } + #[cfg(feature = "std")] #[test] fn wrong_sign_length() { let pubkey = PublicKey::from(&PrivateKey::new(&mut OsRng)); @@ -241,6 +211,7 @@ mod tests { ); } + #[cfg(feature = "std")] #[test] fn instantiate_pubkey() { let pkey = PrivateKey::new(&mut OsRng); @@ -251,6 +222,7 @@ mod tests { assert_eq!(pub_key1.as_byte(), pub_key2.as_byte()); } + #[cfg(feature = "std")] #[test] fn wrong_with_altered_message() { let secret = PrivateKey::new(&mut OsRng); @@ -265,6 +237,7 @@ mod tests { ); } + #[cfg(feature = "std")] #[test] fn wrong_with_forged_pub_key() { let secret = PrivateKey::new(&mut OsRng); @@ -278,6 +251,7 @@ mod tests { ); } + #[cfg(feature = "std")] #[test] fn wrong_with_forged_signature() { let secret = PrivateKey::new(&mut OsRng); From 21d2b766712a11f65cb679f6b805feadf5a49e27 Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 09:55:41 +0000 Subject: [PATCH 2/9] serde support --- Cargo.toml | 4 ++++ src/point.rs | 5 +++-- src/public_key.rs | 5 +++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66aaef7..df42e28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ repository = "https://github.com/lolo32/ed448-rust" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +serde = { version = "1.0", default-features = false, features = ["derive"] } + [features] default = ["std"] std = [] @@ -20,6 +23,7 @@ default-features = false [dependencies.num-bigint] version = "0.4.0" default-features = false +features = ["serde"] [dependencies.num-integer] version = "0.1.44" diff --git a/src/point.rs b/src/point.rs index e952fe7..74a73be 100644 --- a/src/point.rs +++ b/src/point.rs @@ -20,6 +20,7 @@ use core::{ use lazy_static::lazy_static; use num_bigint::{BigInt, Sign}; use num_traits::{One, Zero}; +use serde::{Serialize, Deserialize}; use crate::{Ed448Error, KEY_LENGTH}; use subtle::{Choice, ConstantTimeEq}; @@ -60,7 +61,7 @@ lazy_static! { ); } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Field(BigInt); impl Field { @@ -248,7 +249,7 @@ impl Div<&'_ Field> for &'_ Field { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Point { x: Field, y: Field, diff --git a/src/public_key.rs b/src/public_key.rs index 7a9b2f7..3b443be 100644 --- a/src/public_key.rs +++ b/src/public_key.rs @@ -16,6 +16,7 @@ use core::convert::TryFrom; use alloc::vec; use num_bigint::{BigInt, Sign}; +use serde::{Serialize, Deserialize}; use crate::{ init_sig, @@ -27,7 +28,7 @@ use crate::{ /// This is a public key. _Should be distributed._ /// /// You can extract a `PublicKey` by calling [`Self::from()`]. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct PublicKey(Point); opaque_debug::implement!(PublicKey); @@ -158,7 +159,7 @@ mod tests { use std::convert::TryFrom; use super::*; - + #[cfg(feature = "std")] use rand_core::OsRng; From afca8dde7c42946d1e7f50ca30a53ef970b8837e Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 11:03:27 +0000 Subject: [PATCH 3/9] no_std --- src/lib.rs | 2 ++ src/public_key.rs | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3352ca..237feaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![no_std] + // Copyright 2021 Lolo_32 // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/public_key.rs b/src/public_key.rs index 3b443be..ac4f15f 100644 --- a/src/public_key.rs +++ b/src/public_key.rs @@ -155,9 +155,6 @@ impl TryFrom<&[u8]> for PublicKey { #[cfg(test)] mod tests { - #[cfg(feature = "std")] - use std::convert::TryFrom; - use super::*; #[cfg(feature = "std")] From 5929669712d30bcbd6792df6c569992c6e34053b Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 12:26:42 +0000 Subject: [PATCH 4/9] sha no_std conversion --- Cargo.toml | 1 + src/lib.rs | 12 +++++++++--- src/private_key.rs | 8 ++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index df42e28..d477ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ features = ["alloc"] [dependencies.sha3] version = "0.9.1" +default-features = false [dependencies.subtle] version = "2.4.0" diff --git a/src/lib.rs b/src/lib.rs index 237feaf..6a063a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,8 +119,9 @@ extern crate alloc; use alloc::{vec::Vec, boxed::Box, borrow::Cow}; use sha3::{ - digest::{ExtendableOutput, Update}, + digest::{ExtendableOutput, Update, XofReader}, Shake256, + Sha3_256 }; pub use crate::error::Ed448Error; @@ -179,7 +180,9 @@ fn shake256(items: Vec<&[u8]>, ctx: &[u8], pre_hash: PreHash) -> Box<[u8]> { for item in items { shake.update(item); } - shake.finalize_boxed(114) + let mut h = [0_u8; 114]; + shake.finalize_xof().read(&mut h); + Box::new(h) } /// Common tasks for signing/verifying @@ -198,7 +201,10 @@ fn init_sig<'a, 'b>( let msg = match pre_hash { PreHash::False => Cow::Borrowed(msg), PreHash::True => { - let hash = Shake256::default().chain(msg).finalize_boxed(64).to_vec(); + let mut h = [0_u8; 64]; + Shake256::default().chain(msg).finalize_xof().read(&mut h); + + let hash = h.to_vec(); Cow::Owned(hash) } }; diff --git a/src/private_key.rs b/src/private_key.rs index fdc61e0..5b01606 100644 --- a/src/private_key.rs +++ b/src/private_key.rs @@ -19,7 +19,7 @@ use num_bigint::{BigInt, Sign}; #[cfg(feature = "std")] use rand_core::{CryptoRng, RngCore}; use sha3::{ - digest::{ExtendableOutput, Update}, + digest::{ExtendableOutput, Update, XofReader}, Shake256, }; @@ -70,9 +70,9 @@ impl PrivateKey { pub(crate) fn expand(&self) -> (PrivateKeyRaw, SeedRaw) { // 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the // digest in a 114-octet large buffer, denoted h. - let h = Shake256::default() - .chain(self.as_bytes()) - .finalize_boxed(114); + let mut h = [0_u8; 114]; + Shake256::default() + .chain(self.as_bytes()).finalize_xof().read(&mut h); // Only the lower 57 bytes are used for generating the public key. let mut s: [u8; KEY_LENGTH] = h[..KEY_LENGTH].try_into().unwrap(); From 598e153d72af2795db7f24cd657ff109bd9af1b7 Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 12:33:58 +0000 Subject: [PATCH 5/9] lazy_static no_std --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d477ea2..50a6109 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ std = [] [dependencies.lazy_static] version = "1.4.0" -default-features = false +features = ["spin_no_std"] [dependencies.num-bigint] version = "0.4.0" From ab912df08a9b14f4fbda0f91e05e2e98d300e8ee Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 13:21:46 +0000 Subject: [PATCH 6/9] reexport Point --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6a063a8..3129c26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,13 +121,13 @@ use alloc::{vec::Vec, boxed::Box, borrow::Cow}; use sha3::{ digest::{ExtendableOutput, Update, XofReader}, Shake256, - Sha3_256 }; pub use crate::error::Ed448Error; pub use private_key::PrivateKey; pub use public_key::PublicKey; +pub use point::Point; mod error; mod point; From 9a3db15ee7e5e489747e36b2d097135536ed34e7 Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 13:26:23 +0000 Subject: [PATCH 7/9] pubkey from byte --- src/public_key.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/public_key.rs b/src/public_key.rs index ac4f15f..6c91c3c 100644 --- a/src/public_key.rs +++ b/src/public_key.rs @@ -42,6 +42,13 @@ impl PublicKey { self.0.encode() } + #[inline] + #[must_use] + pub fn from_byte(s: [u8; 57]) -> Result { + // 4. The public key A is the encoding of the point [s]B. + Ok(Self(Point::decode(&s)?)) + } + #[inline] pub fn verify(&self, msg: &[u8], sign: &[u8], ctx: Option<&[u8]>) -> crate::Result<()> { self.verify_real(msg, sign, ctx, PreHash::False) From ce7393da5caecbf8f82c01b5455130bebc6de558 Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 13:27:22 +0000 Subject: [PATCH 8/9] pubkey from byte --- src/public_key.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/public_key.rs b/src/public_key.rs index 6c91c3c..8313458 100644 --- a/src/public_key.rs +++ b/src/public_key.rs @@ -45,7 +45,6 @@ impl PublicKey { #[inline] #[must_use] pub fn from_byte(s: [u8; 57]) -> Result { - // 4. The public key A is the encoding of the point [s]B. Ok(Self(Point::decode(&s)?)) } From 4f99146232431bcd4fe2041a06344dbd85f11c0a Mon Sep 17 00:00:00 2001 From: Bartosz Nowak Date: Thu, 31 Aug 2023 14:58:48 +0000 Subject: [PATCH 9/9] modulus extracted to lasy_static --- src/point.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/point.rs b/src/point.rs index 74a73be..7541eb1 100644 --- a/src/point.rs +++ b/src/point.rs @@ -28,6 +28,7 @@ use subtle::{Choice, ConstantTimeEq}; lazy_static! { // 2 ^ 448 - 2 ^224 - 1 static ref p: BigInt = BigInt::from(2).pow(448).sub(BigInt::from(2).pow(224)) - 1; + static ref m: BigInt = BigInt::from(2).pow(455); static ref d: Field = Field::new(BigInt::from(-39081)); static ref f0: Field = Field::new(BigInt::zero()); static ref f1: Field = Field::new(BigInt::one()); @@ -335,7 +336,7 @@ impl Point { /// Unserialize number from bits. fn frombytes(x: &[u8]) -> crate::Result { - let rv = BigInt::from_bytes_le(Sign::Plus, x) % BigInt::from(2).pow(455); + let rv = BigInt::from_bytes_le(Sign::Plus, x) % &m as &BigInt; if &rv < &p as &BigInt { Ok(Field::new(rv)) } else {