Skip to content

Commit

Permalink
Add Ed25519 support
Browse files Browse the repository at this point in the history
  • Loading branch information
rpiasetskyi committed Sep 1, 2023
1 parent ee4faa3 commit e7f913c
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 0 deletions.
24 changes: 24 additions & 0 deletions heimlig/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions heimlig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ cbc = { version = "0.1", default-features = false, features = ["block-padding",
ccm = { version = "0.5", default-features = false }
chacha20poly1305 = { version = "0.10", default-features = false }
ecdsa = { version = "0.16", default-features = false }
ed25519-dalek = { version = "2.0", default-features = false, features = ["zeroize"] }
elliptic-curve = { version = "0.13", default-features = false }
generic-array = { version = "0.14", default-features = false, features = ["more_lengths"] }
heapless = { version = "0.7", default-features = false, features = ["cas", "x86-sync-pool"] }
Expand All @@ -32,3 +33,4 @@ zeroize = { version = "1.6", default-features = false }
[dev-dependencies]
heapless = "0.7"
hex = "0.4"
ed25519-dalek = { version = "2.0", default-features = false, features = ["zeroize", "rand_core"] }
219 changes: 219 additions & 0 deletions heimlig/src/crypto/ed25519.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
use crate::crypto::Error;
use ed25519_dalek::{SecretKey, Signature, Signer, SigningKey, Verifier, VerifyingKey};

/// Ed25519 signature size in bytes.
pub const SIGNATURE_SIZE: usize = ed25519_dalek::SIGNATURE_LENGTH;
/// Ed25519 private key size in bytes.
pub const PRIVATE_KEY_SIZE: usize = ed25519_dalek::SECRET_KEY_LENGTH;
/// Ed25519 public key size in bytes.
pub const PUBLIC_KEY_SIZE: usize = ed25519_dalek::PUBLIC_KEY_LENGTH;

/// Signs the message using the Ed25519 algorithm.
///
/// # Arguments
///
/// * `private_key`: A slice containing private key bytes.
/// The private key has to be `PRIVATE_KEY_SIZE` bytes long.
/// * `message`: A slice the message to sign bytes.
/// * `signature`: A mutable slice where the computed signature will be stored.
/// The signature slice length has to be `SIGNATURE_SIZE` bytes long.
///
/// # Errors
///
/// The function returns an error if:
/// * `InvalidBufferSize`: The length of the `signature` is not `SIGNATURE_SIZE` bytes.
/// * `InvalidPrivateKey`: The length of the `private_key` is not `PRIVATE_KEY_SIZE` bytes.
pub fn ed25519_sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<(), Error> {
if signature.len() != SIGNATURE_SIZE {
return Err(Error::InvalidBufferSize);
}

let signing_key = SigningKey::from_bytes(
&SecretKey::try_from(private_key).map_err(|_| Error::InvalidPrivateKey)?,
);

signature.copy_from_slice(&signing_key.sign(message).to_bytes());

Ok(())
}

/// Verifies the message signature using the Ed25519 algorithm.
///
/// # Arguments
///
/// * `public_key`: A slice containing public key bytes.
/// The public key has to have the valid format.
/// * `message`: A slice containing message to verify bytes.
/// * `signature`: A slice containing signature to verify bytes.
/// The signature slice length has to be `SIGNATURE_SIZE` bytes long.
///
/// # Errors
///
/// The function returns an error if:
/// * `InvalidPublicKey`: `public_key` has invalid format.
/// * `InvalidSignature`: Signature verification fails.
pub fn ed25519_verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<(), Error> {
let verifying_key = VerifyingKey::try_from(public_key).map_err(|_| Error::InvalidPublicKey)?;
let signature = Signature::from_slice(signature).map_err(|_| Error::InvalidSignature)?;

verifying_key
.verify(message, &signature)
.map_err(|_| Error::InvalidSignature)?;

Ok(())
}

/// Computes the public key for the given private key for Ed25519 algorithm.
///
/// # Arguments
///
/// * `private_key`: A slice containing the private key bytes. The private key has to
/// be `PRIVATE_KEY_SIZE` bytes long.
/// * `public_key`: A mutable slice where the computed public key will be stored. The
/// public key slice length has be `PUBLIC_KEY_SIZE` bytes long.
///
/// # Errors
///
/// The function returns an error if:
/// * `InvalidBufferSize`: The length of the `public_key` is not `PUBLIC_KEY_SIZE` bytes.
/// * `InvalidPrivateKey`: The length of the `private_key` is not `PRIVATE_KEY_SIZE` bytes.
pub fn ed25519_calculate_public_key(
private_key: &[u8],
public_key: &mut [u8],
) -> Result<(), Error> {
if public_key.len() != PUBLIC_KEY_SIZE {
return Err(Error::InvalidBufferSize);
}

let signing_key = SigningKey::from_bytes(
&SecretKey::try_from(private_key).map_err(|_| Error::InvalidPrivateKey)?,
);

public_key.copy_from_slice(signing_key.verifying_key().as_bytes());

Ok(())
}

#[cfg(test)]
mod test {
use super::*;
use crate::crypto::rng::{test::TestEntropySource, Rng};

const MESSAGE: &[u8] =
b"Know thy self, know thy enemy. A thousand battles, a thousand victories.";

#[test]
fn test_ed25519_sign_verify() {
let mut rng = Rng::new(TestEntropySource::default(), None);

let signing_key = SigningKey::generate(&mut rng);
let private_key = signing_key.to_bytes();

let mut signature = [0u8; SIGNATURE_SIZE];
ed25519_sign(&private_key, MESSAGE, &mut signature).expect("signing error");

let mut public_key = [0u8; PUBLIC_KEY_SIZE];
ed25519_calculate_public_key(&private_key, &mut public_key)
.expect("public key calculation error");

ed25519_verify(&public_key, MESSAGE, &signature).expect("verifying error");
}

#[test]
fn test_ed25519_size_errors() {
const BUFF_SIZE: usize = 128;
const INVALID_PRIVATE_KEY_LEN: [usize; 6] = [0, 1, 31, 33, 64, 128];
const INVALID_PUBLIC_KEY_LEN: [usize; 6] = INVALID_PRIVATE_KEY_LEN;
const INVALID_SIGNATURE_LEN: [usize; 6] = [0, 1, 32, 63, 65, 128];

let private_key_buffer = [0u8; BUFF_SIZE];
let mut public_key_buffer = [0u8; BUFF_SIZE];
let mut signature_buffer = [0u8; BUFF_SIZE];

for len in INVALID_PRIVATE_KEY_LEN {
assert_eq!(
ed25519_sign(
&private_key_buffer[..len],
MESSAGE,
&mut signature_buffer[..SIGNATURE_SIZE],
),
Err(Error::InvalidPrivateKey)
);
}

for len in INVALID_SIGNATURE_LEN {
assert_eq!(
ed25519_sign(
&private_key_buffer[..PRIVATE_KEY_SIZE],
MESSAGE,
&mut signature_buffer[..len],
),
Err(Error::InvalidBufferSize)
);
}

for len in INVALID_PUBLIC_KEY_LEN {
assert_eq!(
ed25519_verify(
&public_key_buffer[..len],
MESSAGE,
&signature_buffer[..SIGNATURE_SIZE],
),
Err(Error::InvalidPublicKey)
);
}

for len in INVALID_SIGNATURE_LEN {
assert_eq!(
ed25519_verify(
&public_key_buffer[..PUBLIC_KEY_SIZE],
MESSAGE,
&signature_buffer[..len],
),
Err(Error::InvalidSignature)
);
}

for len in INVALID_PRIVATE_KEY_LEN {
assert_eq!(
ed25519_calculate_public_key(
&private_key_buffer[..len],
&mut public_key_buffer[..PUBLIC_KEY_SIZE],
),
Err(Error::InvalidPrivateKey)
);
}

for len in INVALID_PUBLIC_KEY_LEN {
assert_eq!(
ed25519_calculate_public_key(
&private_key_buffer[..PRIVATE_KEY_SIZE],
&mut public_key_buffer[..len],
),
Err(Error::InvalidBufferSize)
);
}
}

#[test]
fn test_ed25519_invalid_signature() {
let mut rng = Rng::new(TestEntropySource::default(), None);

let signing_key = SigningKey::generate(&mut rng);
let private_key = signing_key.to_bytes();

let mut signature = [0u8; SIGNATURE_SIZE];
ed25519_sign(&private_key, MESSAGE, &mut signature).expect("signing error");

let mut public_key = [0u8; PUBLIC_KEY_SIZE];
ed25519_calculate_public_key(&private_key, &mut public_key)
.expect("public key calculation error");

signature[0] = signature[0] ^ 0xFF;

assert_eq!(
ed25519_verify(&public_key, MESSAGE, &signature),
Err(Error::InvalidSignature)
);
}
}
3 changes: 3 additions & 0 deletions heimlig/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod chacha20poly1305;
mod ecc;
pub mod ecdh;
pub mod ecdsa;
pub mod ed25519;
pub mod hash;
pub mod rng;
pub mod x25519;
Expand All @@ -28,6 +29,8 @@ pub enum Error {
InvalidPrivateKey,
/// Invalid public key format.
InvalidPublicKey,
/// Invalid signature.
InvalidSignature,
}

/// Validation of key and initialization vector/nonce sizes.
Expand Down

0 comments on commit e7f913c

Please sign in to comment.