Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no_std #4

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,33 @@ 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]
docinclude = [] # Used only for activating `doc(include="...")` on stable.
default = ["std"]
std = []

[dependencies.lazy_static]
version = "1.4.0"
features = ["spin_no_std"]

[dependencies.num-bigint]
version = "0.4.0"
default-features = false
features = ["serde"]

[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"
Expand All @@ -34,22 +44,20 @@ features = ["alloc"]

[dependencies.sha3]
version = "0.9.1"
default-features = false

[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"]
104 changes: 13 additions & 91 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![no_std]

// Copyright 2021 Lolo_32
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -14,7 +16,6 @@

#![cfg_attr(feature = "docinclude", feature(external_doc))]
#![deny(
missing_docs,
missing_copy_implementations,
missing_debug_implementations,
trivial_numeric_casts,
Expand Down Expand Up @@ -115,102 +116,18 @@
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},
digest::{ExtendableOutput, Update, XofReader},
Shake256,
};

pub use crate::error::Ed448Error;

pub use private_key::PrivateKey;
pub use public_key::PublicKey;
use std::borrow::Cow;
pub use point::Point;

mod error;
mod point;
Expand Down Expand Up @@ -263,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
Expand All @@ -282,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)
}
};
Expand Down
8 changes: 5 additions & 3 deletions src/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ 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};

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());
Expand Down Expand Up @@ -60,7 +62,7 @@ lazy_static! {
);
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Field(BigInt);

impl Field {
Expand Down Expand Up @@ -248,7 +250,7 @@ impl Div<&'_ Field> for &'_ Field {
}
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Point {
x: Field,
y: Field,
Expand Down Expand Up @@ -334,7 +336,7 @@ impl Point {

/// Unserialize number from bits.
fn frombytes(x: &[u8]) -> crate::Result<Field> {
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 {
Expand Down
25 changes: 11 additions & 14 deletions src/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@

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},
digest::{ExtendableOutput, Update, XofReader},
Shake256,
};

Expand Down Expand Up @@ -48,6 +50,8 @@ impl PrivateKey {
/// use ed448_rust::PrivateKey;
/// let private_key = PrivateKey::new(&mut OsRng);
/// ```

#[cfg(feature = "std")]
pub fn new<T>(rnd: &mut T) -> Self
where
T: CryptoRng + RngCore,
Expand All @@ -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] {
Expand All @@ -76,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();

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Loading