Skip to content

Commit

Permalink
Add basic HMAC and HKDF functions (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
benr-ml committed Sep 19, 2022
1 parent 5251ea9 commit 8cec139
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 64 deletions.
2 changes: 2 additions & 0 deletions 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ blst = "0.3.10"
digest = "0.10.5"
once_cell = "1.14.0"
readonly = "0.2.2"
sha2 = "0.10.2"
thiserror = "1.0.32"

fastcrypto-derive = { path = "./fastcrypto-derive", version = "0.1.0" }

Expand Down
25 changes: 25 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use thiserror::Error;

// A function should validate its arguments and return an indicative errors where needed.
// However, once the function is executing the cryptographic protocol/algorithm (directly/
// indirectly) then it should not return explicit errors as it might leak private information.
// In those cases the function should return the opaque, general error FastCryptoError::GeneralError.
// When in doubt, prefer FastCryptoError::GeneralError.

#[derive(Error, Debug, PartialEq, Eq)]
pub enum FastCryptoError {
#[error("Invalid value was given to the function")]
InvalidInput,

#[error("Expected input of length at least {0}")]
InputTooShort(usize),

#[error("Expected input of length at most {0}")]
InputTooLong(usize),

#[error("General cryptographic error")]
GeneralError,
}
54 changes: 47 additions & 7 deletions src/hkdf.rs → src/hmac.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
use crate::traits::{KeyPair, SigningKey, ToFromBytes};
use crate::error::FastCryptoError;
use crate::{
traits::{KeyPair, SigningKey, ToFromBytes},
Digest, DIGEST_LEN,
};
use digest::{
block_buffer::Eager,
consts::U256,
core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore},
typenum::{IsLess, Le, NonZero},
HashMarker, OutputSizeUser,
};
use hkdf::hmac::Hmac;

use crate::private_seed::PrivateSeed;
use hkdf::hmac::{Hmac, Mac};

/// Creation of a keypair using the [RFC 5869](https://tools.ietf.org/html/rfc5869) HKDF specification.
/// This requires choosing an HMAC function of the correct length (conservatively, the size of a private key for this curve).
Expand All @@ -18,7 +24,7 @@ use hkdf::hmac::Hmac;
/// ```rust
/// use sha3::Sha3_256;
/// use fastcrypto::ed25519::Ed25519KeyPair;
/// use fastcrypto::hkdf::hkdf_generate_from_ikm;
/// use fastcrypto::hmac::hkdf_generate_from_ikm;
/// # fn main() {
/// let ikm = b"some_ikm";
/// let info = b"my_app";
Expand All @@ -35,7 +41,7 @@ use hkdf::hmac::Hmac;
/// use sha3::Sha3_256;
/// use fastcrypto::bls12381::BLS12381KeyPair;
/// use fastcrypto::traits::{KeyPair, SigningKey, ToFromBytes};
/// use fastcrypto::hkdf::hkdf_generate_from_ikm;
/// use fastcrypto::hmac::hkdf_generate_from_ikm;
///
/// # fn main() {
/// let ikm = b"02345678001234567890123456789012";
Expand All @@ -52,7 +58,7 @@ pub fn hkdf_generate_from_ikm<H, K>(
ikm: &[u8], // IKM (32 bytes).
salt: &[u8], // Salt (can be empty).
info: &[u8], // Info (can be empty).
) -> Result<K, signature::Error>
) -> Result<K, FastCryptoError>
where
// This is a tad tedious, because of HKDF's use of a sealed trait. But mostly harmless.
H: CoreProxy + OutputSizeUser,
Expand All @@ -72,10 +78,44 @@ where

let mut okm = vec![0u8; K::PrivKey::LENGTH];
hk.expand(info, &mut okm)
.map_err(|_| signature::Error::new())?;
.map_err(|_| FastCryptoError::GeneralError)?;

let secret_key = K::PrivKey::from_bytes(&okm[..]).map_err(|_| signature::Error::new())?;
let secret_key = K::PrivKey::from_bytes(&okm[..]).unwrap();

let keypair = K::from(secret_key);
Ok(keypair)
}

////////////////////////////////////////////////////////////////////////
/// HMAC-SHA256 based functions

const HMAC_KEY_RECOMMENDED_LENGTH: usize = 32;
const HKDF_KEY_RECOMMENDED_LENGTH: usize = 32;

pub type HmacKey = PrivateSeed<HMAC_KEY_RECOMMENDED_LENGTH, false>;
pub type HkdfIkm = PrivateSeed<HKDF_KEY_RECOMMENDED_LENGTH, false>;

pub fn hmac(key: &HmacKey, message: &[u8]) -> Digest {
let mut hash = Hmac::<sha2::Sha256>::new_from_slice(key.as_bytes()).unwrap();
hash.update(message);
let output: [u8; DIGEST_LEN] = hash.finalize().into_bytes().as_slice().try_into().unwrap();
Digest::new(output)
}

pub fn hkdf(
ikm: &HkdfIkm,
salt: &[u8],
info: &[u8],
output_length: usize,
) -> Result<Vec<u8>, FastCryptoError> {
if output_length > sha2::Sha256::output_size() * 255 {
return Err(FastCryptoError::InputTooLong(
sha2::Sha256::output_size() * 255,
));
}
let hk = hkdf::Hkdf::<sha2::Sha256, Hmac<sha2::Sha256>>::new(Some(salt), ikm.as_bytes());
let mut output: Vec<u8> = vec![0; output_length];
hk.expand(info, output.as_mut_slice())
.map_err(|_| FastCryptoError::GeneralError)?;
Ok(output)
}
14 changes: 7 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,22 @@ pub mod bulletproofs_tests;
pub mod aes_tests;

#[cfg(test)]
#[path = "tests/hkdf_tests.rs"]
pub mod hkdf_tests;
#[path = "tests/hmac_tests.rs"]
pub mod hmac_tests;

// Signing traits
pub mod traits;
// Key scheme implementations
pub mod aes;
pub mod bls12381;
pub mod bulletproofs;
pub mod ed25519;
pub mod hmac;
pub mod secp256k1;

pub mod bulletproofs;

pub mod aes;

// Other tooling
pub mod hkdf;
pub mod error;
pub mod private_seed;
pub mod pubkey_bytes;
pub mod serde_helpers;

Expand Down
37 changes: 37 additions & 0 deletions src/private_seed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::traits::{FromUniformBytes, ToFromBytes};
use zeroize::{Zeroize, ZeroizeOnDrop};

/// Private key/seed of any/fixed size.
///
#[derive(Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct PrivateSeed<const RECOMMENDED_LENGTH: usize, const FIXED_LENGTH_ONLY: bool> {
bytes: Vec<u8>,
}

impl<const N: usize, const B: bool> FromUniformBytes<N> for PrivateSeed<N, B> {}

impl<const N: usize, const B: bool> AsRef<[u8]> for PrivateSeed<N, B> {
fn as_ref(&self) -> &[u8] {
self.bytes.as_ref()
}
}

impl<const RECOMMENDED_LENGTH: usize, const FIXED_LENGTH_ONLY: bool> ToFromBytes
for PrivateSeed<RECOMMENDED_LENGTH, FIXED_LENGTH_ONLY>
{
fn from_bytes(bytes: &[u8]) -> Result<Self, signature::Error> {
if FIXED_LENGTH_ONLY && bytes.len() != RECOMMENDED_LENGTH {
return Err(signature::Error::new());
}
Ok(Self {
bytes: bytes.to_vec(),
})
}

fn as_bytes(&self) -> &[u8] {
self.as_ref()
}
}
2 changes: 1 addition & 1 deletion src/tests/bls12381_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
BLS12381AggregateSignature, BLS12381KeyPair, BLS12381PrivateKey, BLS12381PublicKey,
BLS12381PublicKeyBytes, BLS12381Signature,
},
hkdf::hkdf_generate_from_ikm,
hmac::hkdf_generate_from_ikm,
traits::{
AggregateAuthenticator, EncodeDecodeBase64, KeyPair, SigningKey, ToFromBytes, VerifyingKey,
},
Expand Down
2 changes: 1 addition & 1 deletion src/tests/ed25519_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
Ed25519AggregateSignature, Ed25519KeyPair, Ed25519PrivateKey, Ed25519PublicKey,
Ed25519PublicKeyBytes, Ed25519Signature, ED25519_PRIVATE_KEY_LENGTH,
},
hkdf::hkdf_generate_from_ikm,
hmac::hkdf_generate_from_ikm,
traits::{AggregateAuthenticator, EncodeDecodeBase64, KeyPair, ToFromBytes, VerifyingKey},
};
use ed25519_consensus::VerificationKey;
Expand Down
47 changes: 0 additions & 47 deletions src/tests/hkdf_tests.rs

This file was deleted.

0 comments on commit 8cec139

Please sign in to comment.