blob: 4cee2da027a45d210dbc9b0a6d65a666748192f0 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use core::fmt::Debug;
/// Collection of types used to provide an implementation of ed25519, the Edwards-curve Digital
/// Signature Algorithm scheme using sha-512 (sha2) and Curve25519
pub trait Ed25519Provider {
/// The keypair which includes both public and secret halves of an asymmetric key.
type KeyPair: KeyPair<PublicKey = Self::PublicKey, Signature = Self::Signature>;
/// The ed25519 public key, used when verifying a message
type PublicKey: PublicKey<Signature = Self::Signature>;
/// The ed25519 signature which is the result of signing a message
type Signature: Signature;
}
/// The length of a ed25519 `Signature`, in bytes.
pub const SIGNATURE_LENGTH: usize = 64;
/// The length of an ed25519 `KeyPair`, in bytes.
pub const KEY_PAIR_LENGTH: usize = 64;
/// The length of an ed25519 `PublicKey`, in bytes.
pub const KEY_LENGTH: usize = 32;
/// The keypair which includes both public and secret halves of an asymmetric key.
pub trait KeyPair: Sized {
/// The ed25519 public key, used when verifying a message
type PublicKey: PublicKey;
/// The ed25519 signature returned when signing a message
type Signature: Signature;
/// Converts the key-pair to an array of bytes consisting
/// of the bytes of the private key followed by the bytes
/// of the public key. This method should only ever be called
/// by code which securely stores private credentials.
fn to_bytes(&self) -> [u8; KEY_PAIR_LENGTH];
/// Builds this key-pair from an array of bytes in the
/// format yielded by `to_bytes`. This method should
/// only ever be called by code which securely stores private
/// credentials.
fn from_bytes(bytes: [u8; KEY_PAIR_LENGTH]) -> Result<Self, InvalidBytes>
where
Self: Sized;
/// Sign the given message and return a digital signature
fn sign(&self, msg: &[u8]) -> Self::Signature;
/// Generate an ed25519 keypair from a CSPRNG
/// generate is not available in `no-std`
#[cfg(feature = "std")]
fn generate() -> Self;
/// getter function for the Public Key of the key pair
fn public(&self) -> Self::PublicKey;
}
/// An ed25519 signature
pub trait Signature: Sized {
/// Create a new signature from a byte slice, and return an error on an invalid signature
/// An `Ok` result does not guarantee that the Signature is valid, however it will catch a
/// number of invalid signatures relatively inexpensively.
fn from_bytes(bytes: &[u8]) -> Result<Self, InvalidSignature>;
/// Returns a slice of the signature bytes
fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH];
}
/// An ed25519 public key
pub trait PublicKey {
/// the signature type being used by verify
type Signature: Signature;
/// Builds this public key from an array of bytes in
/// the format yielded by `to_bytes`.
fn from_bytes(bytes: [u8; KEY_LENGTH]) -> Result<Self, InvalidBytes>
where
Self: Sized;
/// Yields the bytes of the public key
fn to_bytes(&self) -> [u8; KEY_LENGTH];
/// Succeeds if the signature was a valid signature created by this Keypair on the prehashed_message.
fn verify_strict(
&self,
message: &[u8],
signature: &Self::Signature,
) -> Result<(), SignatureError>;
}
/// error returned when bad bytes are provided to generate keypair
#[derive(Debug)]
pub struct InvalidBytes;
/// Error returned if the verification on the signature + message fails
#[derive(Debug)]
pub struct SignatureError;
/// Error returned if invalid signature bytes are provided
#[derive(Debug)]
pub struct InvalidSignature;
#[cfg(feature = "testing")]
/// Utilities for testing. Implementations can use the test cases and functions provided to test
/// their implementation.
pub mod testing {
extern crate alloc;
extern crate std;
use crate::ed25519::{Ed25519Provider, KeyPair, PublicKey, Signature};
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use wycheproof::TestResult;
// These are test vectors from the creators of Ed25519: https://ed25519.cr.yp.to/ which are referenced
// as the SOT for the test vectors in the RFC: https://www.rfc-editor.org/rfc/rfc8032#section-7.1
// The vectors have been formatted into a easily parsable/readable format by libgcrypt which is
// also used for test cases in the above RFC:
// https://dev.gnupg.org/source/libgcrypt/browse/master/tests/t-ed25519.inp
const PATH_TO_RFC_VECTORS_FILE: &str =
"crypto/crypto_provider/src/testdata/ecdsa/rfc_test_vectors.txt";
/// Runs set of Ed25519 wycheproof test vectors against a provided ed25519 implementation
/// Tests vectors from Project Wycheproof: <https://github.com/google/wycheproof>
pub fn run_wycheproof_test_vectors<E>()
where
E: Ed25519Provider,
{
let test_set = wycheproof::eddsa::TestSet::load(wycheproof::eddsa::TestName::Ed25519)
.expect("should be able to load test set");
for test_group in test_set.test_groups {
let key_pair = test_group.key;
let public_key = key_pair.pk;
let secret_key = key_pair.sk;
for test in test_group.tests {
let tc_id = test.tc_id;
let comment = test.comment;
let sig = test.sig;
let msg = test.msg;
let valid = match test.result {
TestResult::Invalid => false,
TestResult::Valid | TestResult::Acceptable => true,
};
let result = run_test::<E>(
public_key.clone(),
secret_key.clone(),
sig.clone(),
msg.clone(),
);
if valid {
if let Err(desc) = result {
panic!(
"\n\
Failed test {}: {}\n\
msg:\t{:?}\n\
sig:\t{:?}\n\
comment:\t{:?}\n",
tc_id, desc, msg, sig, comment,
);
}
} else {
assert!(result.is_err())
}
}
}
}
/// Runs the RFC specified test vectors against an Ed25519 implementation
pub fn run_rfc_test_vectors<E>()
where
E: Ed25519Provider,
{
let file_contents =
std::fs::read_to_string(test_helper::get_data_file(PATH_TO_RFC_VECTORS_FILE))
.expect("should be able to read file");
let mut split_cases: Vec<&str> = file_contents.as_str().split("\n\n").collect();
// remove the comments
split_cases.remove(0);
for case in split_cases {
let test_case: Vec<&str> = case.split('\n').collect();
let tc_id = extract_string(test_case[0]);
let sk = extract_hex(test_case[1]);
let pk = extract_hex(test_case[2]);
let msg = extract_hex(test_case[3]);
let sig = extract_hex(test_case[4]);
let result = run_test::<E>(pk.clone(), sk.clone(), sig.clone(), msg.clone());
if let Err(desc) = result {
panic!(
"\n\
Failed test {}: {}\n\
msg:\t{:?}\n\
sig:\t{:?}\n\"",
tc_id, desc, msg, sig,
);
}
}
}
fn extract_hex(line: &str) -> Vec<u8> {
test_helper::string_to_hex(extract_string(line).as_str())
}
fn extract_string(line: &str) -> String {
line.split(':').collect::<Vec<&str>>()[1].trim().to_owned()
}
fn run_test<E>(
pub_key: Vec<u8>,
secret_key: Vec<u8>,
sig: Vec<u8>,
msg: Vec<u8>,
) -> Result<(), &'static str>
where
E: Ed25519Provider,
{
let kp_bytes: [u8; 64] = [secret_key.as_slice(), pub_key.as_slice()]
.concat()
.try_into()
.map_err(|_| "invalid length keypair")?;
let kp = E::KeyPair::from_bytes(kp_bytes)
.map_err(|_| "Should be able to create Keypair from bytes")?;
let sig_result = kp.sign(msg.as_slice());
(sig.as_slice() == sig_result.to_bytes())
.then_some(())
.ok_or("sig not matching expected")?;
let signature = E::Signature::from_bytes(sig.as_slice())
.map_err(|_| "unable to parse sign from test case")?;
let pub_key = kp.public();
pub_key
.verify_strict(msg.as_slice(), &signature)
.map_err(|_| "verify failed")?;
Ok(())
}
}