Merge pull request #27 from txpipe/nicolasdp/ed25519-plus
add Ed25519 and Ed25519Extended asymmetric keys
This commit is contained in:
commit
c735d9e7b1
6 changed files with 774 additions and 2 deletions
|
|
@ -16,4 +16,10 @@ authors = [
|
||||||
minicbor = { version = "0.12" }
|
minicbor = { version = "0.12" }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
cryptoxide = { version = "0.3.6" }
|
cryptoxide = { version = "0.3.6" }
|
||||||
|
thiserror = "1.0"
|
||||||
|
rand_core = "0.6"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
quickcheck = "1.0"
|
||||||
|
quickcheck_macros = "1.0"
|
||||||
|
rand = "0.8"
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ Crate with all the cryptographic material to support Cardano protocol:
|
||||||
|
|
||||||
- [x] Blake2b 256
|
- [x] Blake2b 256
|
||||||
- [x] Blake2b 224
|
- [x] Blake2b 224
|
||||||
- [ ] Ed25519 asymmetric key pair and ECDSA
|
- [x] Ed25519 asymmetric key pair and EdDSA
|
||||||
- [ ] Ed25519 Extended asymmetric key pair
|
- [x] Ed25519 Extended asymmetric key pair
|
||||||
- [ ] Bip32-Ed25519 key derivation
|
- [ ] Bip32-Ed25519 key derivation
|
||||||
- [ ] BIP39 mnemonics
|
- [ ] BIP39 mnemonics
|
||||||
- [ ] VRF
|
- [ ] VRF
|
||||||
|
|
|
||||||
528
pallas-crypto/src/key/ed25519.rs
Normal file
528
pallas-crypto/src/key/ed25519.rs
Normal file
|
|
@ -0,0 +1,528 @@
|
||||||
|
//! Ed25519 and Ed25519Extended Asymmetric Keys
|
||||||
|
//!
|
||||||
|
//! In this module we have both [`SecretKey`] which is a normal Ed25519
|
||||||
|
//! asymmetric key and [`SecretKeyExtended`] asymmetric key.
|
||||||
|
//! They can both be used to generate [`Signature`] and submit valid
|
||||||
|
//! transactions.
|
||||||
|
//!
|
||||||
|
//! However, only the [`SecretKeyExtended`] can be used for HD derivation
|
||||||
|
//! (using [ed25519_bip32] or otherwise).
|
||||||
|
//!
|
||||||
|
|
||||||
|
use crate::memsec::Scrubbed as _;
|
||||||
|
use cryptoxide::ed25519::{
|
||||||
|
self, PRIVATE_KEY_LENGTH, PUBLIC_KEY_LENGTH, SEED_LENGTH, SIGNATURE_LENGTH,
|
||||||
|
};
|
||||||
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
use std::{any::type_name, convert::TryFrom, fmt, str::FromStr};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Ed25519 Secret Key
|
||||||
|
///
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SecretKey([u8; Self::SIZE]);
|
||||||
|
|
||||||
|
/// Ed25519 Extended Secret Key
|
||||||
|
///
|
||||||
|
/// unlike [`SecretKey`], an extended key can be derived see [`pallas_crypto::derivation`]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SecretKeyExtended([u8; Self::SIZE]);
|
||||||
|
|
||||||
|
/// Ed25519 Public Key. Can be used to verify a [`Signature`]. A [`PublicKey`]
|
||||||
|
/// is associated to a [`SecretKey`]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct PublicKey([u8; Self::SIZE]);
|
||||||
|
|
||||||
|
/// Ed25519 Signature. Is created by a [`SecretKey`] and is verified
|
||||||
|
/// with a [`PublicKey`].
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Signature([u8; Self::SIZE]);
|
||||||
|
|
||||||
|
/// Error type used when retrieving a [`PublicKey`] via the [`TryFrom`]
|
||||||
|
/// trait.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum TryFromPublicKeyError {
|
||||||
|
#[error("Invalid size, expecting {}", PublicKey::SIZE)]
|
||||||
|
InvalidSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error type used when retrieving a [`Signature`] via the [`TryFrom`]
|
||||||
|
/// trait.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum TryFromSignatureError {
|
||||||
|
#[error("Invalid size, expecting {}", Signature::SIZE)]
|
||||||
|
InvalidSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_size_zero {
|
||||||
|
($Type:ty, $Size:expr) => {
|
||||||
|
impl $Type {
|
||||||
|
/// This is the size of the type in bytes.
|
||||||
|
pub const SIZE: usize = $Size;
|
||||||
|
|
||||||
|
/// create a zero object. This is not a _"valid"_ one. It is
|
||||||
|
/// used to initialize a ready to use data structure in this module.
|
||||||
|
#[inline]
|
||||||
|
fn zero() -> Self {
|
||||||
|
Self([0; Self::SIZE])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_size_zero!(SecretKey, SEED_LENGTH);
|
||||||
|
impl_size_zero!(SecretKeyExtended, PRIVATE_KEY_LENGTH);
|
||||||
|
impl_size_zero!(PublicKey, PUBLIC_KEY_LENGTH);
|
||||||
|
impl_size_zero!(Signature, SIGNATURE_LENGTH);
|
||||||
|
|
||||||
|
impl SecretKey {
|
||||||
|
/// generate a new [`SecretKey`] with the given random number generator
|
||||||
|
///
|
||||||
|
pub fn new<Rng>(mut rng: Rng) -> Self
|
||||||
|
where
|
||||||
|
Rng: RngCore + CryptoRng,
|
||||||
|
{
|
||||||
|
let mut s = Self::zero();
|
||||||
|
rng.fill_bytes(&mut s.0);
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the [`PublicKey`] associated to this key
|
||||||
|
///
|
||||||
|
/// Unlike the [`SecretKey`], the [`PublicKey`] can be safely
|
||||||
|
/// publicly shared. The key can then be used to verify any
|
||||||
|
/// [`Signature`] generated with this [`SecretKey`] and the original
|
||||||
|
/// message.
|
||||||
|
pub fn public_key(&self) -> PublicKey {
|
||||||
|
let (mut sk, pk) = ed25519::keypair(&self.0);
|
||||||
|
|
||||||
|
// the `sk` is a private component, scrubbing it reduce the
|
||||||
|
// risk of an adversary accessing the memory remains of this
|
||||||
|
// value
|
||||||
|
sk.scrub();
|
||||||
|
|
||||||
|
PublicKey(pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// create a [`Signature`] for the given message with this [`SecretKey`].
|
||||||
|
///
|
||||||
|
/// The [`Signature`] can then be verified against the associated [`PublicKey`]
|
||||||
|
/// and the original message.
|
||||||
|
pub fn sign<T>(&self, msg: T) -> Signature
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
let (mut sk, _) = ed25519::keypair(&self.0);
|
||||||
|
|
||||||
|
let signature = ed25519::signature(msg.as_ref(), &sk);
|
||||||
|
|
||||||
|
// we don't need this signature component, make sure to scrub the
|
||||||
|
// content before releasing the results
|
||||||
|
sk.scrub();
|
||||||
|
|
||||||
|
Signature(signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SecretKeyExtended {
|
||||||
|
/// generate a new [`SecretKeyExtended`] with the given random number generator
|
||||||
|
///
|
||||||
|
pub fn new<Rng>(mut rng: Rng) -> Self
|
||||||
|
where
|
||||||
|
Rng: RngCore + CryptoRng,
|
||||||
|
{
|
||||||
|
let mut s = Self::zero();
|
||||||
|
rng.fill_bytes(&mut s.0);
|
||||||
|
|
||||||
|
s.0[0] &= 0b1111_1000;
|
||||||
|
s.0[31] &= 0b0011_1111;
|
||||||
|
s.0[31] |= 0b0100_0000;
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
s.check_structure(),
|
||||||
|
"checking we properly set the bit tweaks for the extended Ed25519"
|
||||||
|
);
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::verbose_bit_mask)]
|
||||||
|
fn check_structure(&self) -> bool {
|
||||||
|
(self.0[0] & 0b0000_0111) == 0
|
||||||
|
&& (self.0[31] & 0b0100_0000) == 0b0100_0000
|
||||||
|
&& (self.0[31] & 0b1000_0000) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the [`PublicKey`] associated to this key
|
||||||
|
///
|
||||||
|
/// Unlike the [`SecretKeyExtended`], the [`PublicKey`] can be safely
|
||||||
|
/// publicly shared. The key can then be used to verify any
|
||||||
|
/// [`Signature`] generated with this [`SecretKeyExtended`] and the original
|
||||||
|
/// message.
|
||||||
|
pub fn public_key(&self) -> PublicKey {
|
||||||
|
let pk = ed25519::to_public(&self.0);
|
||||||
|
|
||||||
|
PublicKey::from(pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// create a `Signature` for the given message with this `SecretKey`.
|
||||||
|
///
|
||||||
|
/// The `Signature` can then be verified against the associated `PublicKey`
|
||||||
|
/// and the original message.
|
||||||
|
pub fn sign<T: AsRef<[u8]>>(&self, msg: T) -> Signature {
|
||||||
|
let signature = ed25519::signature_extended(msg.as_ref(), &self.0);
|
||||||
|
|
||||||
|
Signature::from(signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PublicKey {
|
||||||
|
/// verify the cryptographic [`Signature`] against the `message` and the
|
||||||
|
/// [`PublicKey`] `self`.
|
||||||
|
///
|
||||||
|
#[inline]
|
||||||
|
pub fn verify<T>(&self, message: T, signature: &Signature) -> bool
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
ed25519::verify(message.as_ref(), &self.0, signature.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop ******************************************************************** */
|
||||||
|
|
||||||
|
impl Drop for SecretKey {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.scrub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for SecretKeyExtended {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.scrub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format ****************************************************************** */
|
||||||
|
|
||||||
|
impl fmt::Display for Signature {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&hex::encode(self.as_ref()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PublicKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&hex::encode(self.as_ref()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Signature {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("Signature<Ed25519>")
|
||||||
|
.field(&hex::encode(self.as_ref()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for PublicKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("PublicKey<Ed25519>")
|
||||||
|
.field(&hex::encode(self.as_ref()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_secret_fmt {
|
||||||
|
($Type:ty) => {
|
||||||
|
/// conveniently provide a proper implementation to debug for the
|
||||||
|
/// SecretKey types when only *testing* the library
|
||||||
|
#[cfg(test)]
|
||||||
|
impl fmt::Debug for $Type {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple(&format!(
|
||||||
|
"SecretKey<{typename}>",
|
||||||
|
typename = type_name::<Self>()
|
||||||
|
))
|
||||||
|
.field(&hex::encode(&self.0))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// conveniently provide an incomplete implementation of Debug for the
|
||||||
|
/// SecretKey.
|
||||||
|
#[cfg(not(test))]
|
||||||
|
impl fmt::Debug for $Type {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct(&format!(
|
||||||
|
"SecretKey<{typename}>",
|
||||||
|
typename = type_name::<Self>()
|
||||||
|
))
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_secret_fmt!(SecretKey);
|
||||||
|
impl_secret_fmt!(SecretKeyExtended);
|
||||||
|
|
||||||
|
/* AsRef ******************************************************************* */
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for PublicKey {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Signature {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Conversion ************************************************************** */
|
||||||
|
|
||||||
|
impl<'a> From<&'a Signature> for String {
|
||||||
|
fn from(s: &'a Signature) -> Self {
|
||||||
|
s.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Signature> for String {
|
||||||
|
fn from(s: Signature) -> Self {
|
||||||
|
s.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[u8; Self::SIZE]> for PublicKey {
|
||||||
|
fn from(bytes: [u8; Self::SIZE]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PublicKey> for [u8; PublicKey::SIZE] {
|
||||||
|
fn from(pk: PublicKey) -> Self {
|
||||||
|
pk.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<[u8; Self::SIZE]> for Signature {
|
||||||
|
fn from(bytes: [u8; Self::SIZE]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a [u8]> for PublicKey {
|
||||||
|
type Error = TryFromPublicKeyError;
|
||||||
|
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() != Self::SIZE {
|
||||||
|
Err(Self::Error::InvalidSize)
|
||||||
|
} else {
|
||||||
|
let mut s = Self::zero();
|
||||||
|
s.0.copy_from_slice(value);
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a [u8]> for Signature {
|
||||||
|
type Error = TryFromSignatureError;
|
||||||
|
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() != Self::SIZE {
|
||||||
|
Err(Self::Error::InvalidSize)
|
||||||
|
} else {
|
||||||
|
let mut s = Self::zero();
|
||||||
|
s.0.copy_from_slice(value);
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for PublicKey {
|
||||||
|
type Err = hex::FromHexError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut r = Self::zero();
|
||||||
|
hex::decode_to_slice(s, &mut r.0)?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Signature {
|
||||||
|
type Err = hex::FromHexError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut r = Self::zero();
|
||||||
|
hex::decode_to_slice(s, &mut r.0)?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for Signature {
|
||||||
|
type Error = <Self as FromStr>::Err;
|
||||||
|
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
s.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use quickcheck::{Arbitrary, Gen, TestResult};
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
impl Arbitrary for SecretKey {
|
||||||
|
fn arbitrary(g: &mut Gen) -> Self {
|
||||||
|
let mut s = Self::zero();
|
||||||
|
s.0.iter_mut().for_each(|byte| {
|
||||||
|
*byte = u8::arbitrary(g);
|
||||||
|
});
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for SecretKeyExtended {
|
||||||
|
fn arbitrary(g: &mut Gen) -> Self {
|
||||||
|
let mut s = Self::zero();
|
||||||
|
s.0.iter_mut().for_each(|byte| {
|
||||||
|
*byte = u8::arbitrary(g);
|
||||||
|
});
|
||||||
|
|
||||||
|
s.0[0] &= 0b1111_1000;
|
||||||
|
s.0[31] &= 0b0011_1111;
|
||||||
|
s.0[31] |= 0b0100_0000;
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for PublicKey {
|
||||||
|
fn arbitrary(g: &mut Gen) -> Self {
|
||||||
|
let mut s = Self::zero();
|
||||||
|
s.0.iter_mut().for_each(|byte| {
|
||||||
|
*byte = u8::arbitrary(g);
|
||||||
|
});
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Signature {
|
||||||
|
fn arbitrary(g: &mut Gen) -> Self {
|
||||||
|
let mut s = Self::zero();
|
||||||
|
s.0.iter_mut().for_each(|byte| {
|
||||||
|
*byte = u8::arbitrary(g);
|
||||||
|
});
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn signing_verify_works(signing_key: SecretKey, message: Vec<u8>) -> bool {
|
||||||
|
let public_key = signing_key.public_key();
|
||||||
|
let signature = signing_key.sign(&message);
|
||||||
|
|
||||||
|
public_key.verify(message, &signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn signing_verify_works_extended(signing_key: SecretKeyExtended, message: Vec<u8>) -> bool {
|
||||||
|
let public_key = signing_key.public_key();
|
||||||
|
let signature = signing_key.sign(&message);
|
||||||
|
|
||||||
|
public_key.verify(message, &signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn verify_random_signature_does_not_work(
|
||||||
|
public_key: PublicKey,
|
||||||
|
signature: Signature,
|
||||||
|
message: Vec<u8>,
|
||||||
|
) -> bool {
|
||||||
|
// NOTE: this test may fail but it is impossible to see this happening in normal condition.
|
||||||
|
// we are generating 32 random bytes of public key and 64 random bytes
|
||||||
|
// of signature with an randomly generated message of a random number
|
||||||
|
// of bytes in. If the message were empty, the probability to have
|
||||||
|
// a signature that matches the verify key would still be 1 out of 2^96.
|
||||||
|
//
|
||||||
|
// if this test fails and it is not a bug, go buy a lottery ticket.
|
||||||
|
!public_key.verify(message, &signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn public_key_try_from_correct_size(public_key: PublicKey) -> TestResult {
|
||||||
|
match PublicKey::try_from(public_key.as_ref()) {
|
||||||
|
Ok(_) => TestResult::passed(),
|
||||||
|
Err(TryFromPublicKeyError::InvalidSize) => {
|
||||||
|
TestResult::error("was expecting the test to pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn public_key_try_from_incorrect_size(bytes: Vec<u8>) -> TestResult {
|
||||||
|
if bytes.len() == PublicKey::SIZE {
|
||||||
|
return TestResult::discard();
|
||||||
|
}
|
||||||
|
match PublicKey::try_from(bytes.as_slice()) {
|
||||||
|
Ok(_) => TestResult::error(
|
||||||
|
"Expecting to fail with invalid size instead of having a valid value",
|
||||||
|
),
|
||||||
|
Err(TryFromPublicKeyError::InvalidSize) => TestResult::passed(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn signature_try_from_correct_size(signature: Signature) -> TestResult {
|
||||||
|
match Signature::try_from(signature.as_ref()) {
|
||||||
|
Ok(_) => TestResult::passed(),
|
||||||
|
Err(TryFromSignatureError::InvalidSize) => {
|
||||||
|
TestResult::error("was expecting the test to pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn signature_try_from_incorrect_size(bytes: Vec<u8>) -> TestResult {
|
||||||
|
if bytes.len() == Signature::SIZE {
|
||||||
|
return TestResult::discard();
|
||||||
|
}
|
||||||
|
match Signature::try_from(bytes.as_slice()) {
|
||||||
|
Ok(_) => TestResult::error(
|
||||||
|
"Expecting to fail with invalid size instead of having a valid value",
|
||||||
|
),
|
||||||
|
Err(TryFromSignatureError::InvalidSize) => TestResult::passed(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn public_key_from_str(public_key: PublicKey) -> TestResult {
|
||||||
|
let s = public_key.to_string();
|
||||||
|
|
||||||
|
match s.parse::<PublicKey>() {
|
||||||
|
Ok(decoded) => {
|
||||||
|
if decoded == public_key {
|
||||||
|
TestResult::passed()
|
||||||
|
} else {
|
||||||
|
TestResult::error("the decoded key is not equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => TestResult::error(error.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn signature_from_str(signature: Signature) -> TestResult {
|
||||||
|
let s = signature.to_string();
|
||||||
|
|
||||||
|
match s.parse::<Signature>() {
|
||||||
|
Ok(decoded) => {
|
||||||
|
if decoded == signature {
|
||||||
|
TestResult::passed()
|
||||||
|
} else {
|
||||||
|
TestResult::error("the decoded signature is not equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => TestResult::error(error.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
pallas-crypto/src/key/mod.rs
Normal file
1
pallas-crypto/src/key/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod ed25519;
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
pub mod hash;
|
pub mod hash;
|
||||||
|
pub mod key;
|
||||||
|
pub mod memsec;
|
||||||
|
|
|
||||||
235
pallas-crypto/src/memsec.rs
Normal file
235
pallas-crypto/src/memsec.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
/*!
|
||||||
|
# Memsec utility functions
|
||||||
|
Most of the types defined here implements `Scrubbed` trait.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
/// Types implementing this can be scrubbed, the memory is cleared and
|
||||||
|
/// erased with a dummy value.
|
||||||
|
pub trait Scrubbed {
|
||||||
|
fn scrub(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform a secure memset. This function is guaranteed not to be elided
|
||||||
|
/// or reordered.
|
||||||
|
///
|
||||||
|
/// # Performance consideration
|
||||||
|
///
|
||||||
|
/// On `nightly`, the function use a more efficient.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The destination memory (`dst` to `dst+count`) must be properly allocated
|
||||||
|
/// and ready to use.
|
||||||
|
#[inline(never)]
|
||||||
|
pub unsafe fn memset(dst: *mut u8, val: u8, count: usize) {
|
||||||
|
for i in 0..count {
|
||||||
|
ptr::write_volatile(dst.add(i), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// compare the equality of the 2 given arrays, constant in time
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// The function will panic if it is called with a `len` of 0.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Expecting to have both valid pointer and the count to fit in
|
||||||
|
/// both the allocated memories
|
||||||
|
#[inline(never)]
|
||||||
|
pub unsafe fn memeq(v1: *const u8, v2: *const u8, len: usize) -> bool {
|
||||||
|
let mut sum = 0;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
len != 0,
|
||||||
|
"Cannot perform equality comparison if the length is 0"
|
||||||
|
);
|
||||||
|
|
||||||
|
for i in 0..len {
|
||||||
|
let val1 = ptr::read_volatile(v1.add(i));
|
||||||
|
let val2 = ptr::read_volatile(v2.add(i));
|
||||||
|
|
||||||
|
let xor = val1 ^ val2;
|
||||||
|
|
||||||
|
sum |= xor;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constant time comparison
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// The function will panic if it is called with a `len` of 0.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Expecting to have both valid pointer and the count to fit in
|
||||||
|
/// both the allocated memories
|
||||||
|
#[inline(never)]
|
||||||
|
pub unsafe fn memcmp(v1: *const u8, v2: *const u8, len: usize) -> std::cmp::Ordering {
|
||||||
|
let mut res = 0;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
len != 0,
|
||||||
|
"Cannot perform ordering comparison if the length is 0"
|
||||||
|
);
|
||||||
|
|
||||||
|
for i in (0..len).rev() {
|
||||||
|
let val1 = ptr::read_volatile(v1.add(i)) as i32;
|
||||||
|
let val2 = ptr::read_volatile(v2.add(i)) as i32;
|
||||||
|
let diff = val1 - val2;
|
||||||
|
res = (res & (((diff - 1) & !diff) >> 8)) | diff;
|
||||||
|
}
|
||||||
|
let res = ((res - 1) >> 8) + (res >> 8) + 1;
|
||||||
|
|
||||||
|
res.cmp(&0)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_scrubbed_primitive {
|
||||||
|
($t:ty) => {
|
||||||
|
impl Scrubbed for $t {
|
||||||
|
#[inline(never)]
|
||||||
|
fn scrub(&mut self) {
|
||||||
|
*self = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_scrubbed_primitive!(u8);
|
||||||
|
impl_scrubbed_primitive!(u16);
|
||||||
|
impl_scrubbed_primitive!(u32);
|
||||||
|
impl_scrubbed_primitive!(u64);
|
||||||
|
impl_scrubbed_primitive!(u128);
|
||||||
|
impl_scrubbed_primitive!(usize);
|
||||||
|
impl_scrubbed_primitive!(i8);
|
||||||
|
impl_scrubbed_primitive!(i16);
|
||||||
|
impl_scrubbed_primitive!(i32);
|
||||||
|
impl_scrubbed_primitive!(i64);
|
||||||
|
impl_scrubbed_primitive!(i128);
|
||||||
|
impl_scrubbed_primitive!(isize);
|
||||||
|
|
||||||
|
macro_rules! impl_scrubbed_array {
|
||||||
|
($t:ty) => {
|
||||||
|
impl Scrubbed for $t {
|
||||||
|
fn scrub(&mut self) {
|
||||||
|
unsafe { memset(self.as_mut_ptr(), 0, self.len()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_scrubbed_array!([u8]);
|
||||||
|
impl_scrubbed_array!(str);
|
||||||
|
|
||||||
|
impl<const N: usize> Scrubbed for [u8; N] {
|
||||||
|
fn scrub(&mut self) {
|
||||||
|
unsafe { memset(self.as_mut_ptr(), 0, self.len()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Scrubbed> Scrubbed for Option<T> {
|
||||||
|
fn scrub(&mut self) {
|
||||||
|
self.as_mut().map(Scrubbed::scrub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Scrubbed> Scrubbed for Vec<T> {
|
||||||
|
fn scrub(&mut self) {
|
||||||
|
self.iter_mut().for_each(Scrubbed::scrub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Scrubbed> Scrubbed for Box<T> {
|
||||||
|
fn scrub(&mut self) {
|
||||||
|
self.as_mut().scrub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Scrubbed> Scrubbed for std::cell::Cell<T> {
|
||||||
|
fn scrub(&mut self) {
|
||||||
|
self.get_mut().scrub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Scrubbed> Scrubbed for std::cell::RefCell<T> {
|
||||||
|
fn scrub(&mut self) {
|
||||||
|
self.get_mut().scrub()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use quickcheck::TestResult;
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn eq_empty() {
|
||||||
|
let bytes = Vec::new();
|
||||||
|
unsafe { memeq(bytes.as_ptr(), bytes.as_ptr(), bytes.len()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn ord_empty() {
|
||||||
|
let bytes = Vec::new();
|
||||||
|
unsafe { memcmp(bytes.as_ptr(), bytes.as_ptr(), bytes.len()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn eq(bytes: Vec<u8>) -> TestResult {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
TestResult::discard()
|
||||||
|
} else {
|
||||||
|
let b = unsafe { memeq(bytes.as_ptr(), bytes.as_ptr(), bytes.len()) };
|
||||||
|
TestResult::from_bool(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn ord_eq(bytes: Vec<u8>) -> TestResult {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
TestResult::discard()
|
||||||
|
} else {
|
||||||
|
let ord = unsafe { memcmp(bytes.as_ptr(), bytes.as_ptr(), bytes.len()) };
|
||||||
|
TestResult::from_bool(ord == Ordering::Equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn neq(a: Vec<u8>, b: Vec<u8>) -> TestResult {
|
||||||
|
let len = std::cmp::min(a.len(), b.len());
|
||||||
|
|
||||||
|
if a[..len] == b[..len] || len == 0 {
|
||||||
|
TestResult::discard()
|
||||||
|
} else {
|
||||||
|
let b = unsafe { memeq(a.as_ptr(), b.as_ptr(), len) };
|
||||||
|
|
||||||
|
TestResult::from_bool(!b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn ord(a: Vec<u8>, b: Vec<u8>) -> TestResult {
|
||||||
|
let len = std::cmp::min(a.len(), b.len());
|
||||||
|
|
||||||
|
if len == 0 {
|
||||||
|
TestResult::discard()
|
||||||
|
} else {
|
||||||
|
let a = &a[..len];
|
||||||
|
let b = &b[..len];
|
||||||
|
let ord = unsafe { memcmp(a.as_ptr(), b.as_ptr(), len) };
|
||||||
|
|
||||||
|
TestResult::from_bool(ord == a.cmp(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue