Skip to content

Commit

Permalink
Domain separation without copying
Browse files Browse the repository at this point in the history
  • Loading branch information
jschneider-bensch committed Oct 1, 2024
1 parent 6b30119 commit 86e7879
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 59 deletions.
86 changes: 28 additions & 58 deletions libcrux-ml-dsa/src/ml_dsa_generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
},
ntt::ntt,
polynomial::PolynomialRingElement,
pre_hash::{PreHash, PreHashOID, PRE_HASH_OID_LEN},
pre_hash::{DomainSeparationContext, PreHash},
sample::{sample_challenge_ring_element, sample_mask_vector},
samplex4,
simd::traits::Operations,
Expand Down Expand Up @@ -141,7 +141,6 @@ pub(crate) fn sign_pre_hashed<
if context.len() > CONTEXT_MAX_LEN {
return Err(SigningError::ContextTooLongError);
}
let (domain_separated_context, ctx_len) = domain_separate_context(context, Some(&PH::oid()));
let pre_hashed_message = PH::hash(message);

sign_internal::<
Expand All @@ -166,7 +165,7 @@ pub(crate) fn sign_pre_hashed<
>(
&signing_key,
&pre_hashed_message,
&domain_separated_context[..ctx_len],
DomainSeparationContext::new(context, Some(&PH::oid()))?,
randomness,
)
}
Expand Down Expand Up @@ -197,10 +196,6 @@ pub(crate) fn sign<
context: &[u8],
randomness: [u8; SIGNING_RANDOMNESS_SIZE],
) -> Result<MLDSASignature<SIGNATURE_SIZE>, SigningError> {
if context.len() > CONTEXT_MAX_LEN {
return Err(SigningError::ContextTooLongError);
}
let (domain_separated_context, ctx_len) = domain_separate_context(context, None);
sign_internal::<
SIMDUnit,
Shake128X4,
Expand All @@ -223,7 +218,7 @@ pub(crate) fn sign<
>(
&signing_key,
message,
&domain_separated_context[..ctx_len],
DomainSeparationContext::new(context, None)?,
randomness,
)
}
Expand Down Expand Up @@ -251,7 +246,7 @@ pub(crate) fn sign_internal<
>(
signing_key: &[u8; SIGNING_KEY_SIZE],
message: &[u8],
domain_separated_context: &[u8],
domain_separation_context: DomainSeparationContext,
randomness: [u8; SIGNING_RANDOMNESS_SIZE],
) -> Result<MLDSASignature<SIGNATURE_SIZE>, SigningError> {
let (seed_for_A, seed_for_signing, verification_key_hash, s1_as_ntt, s2_as_ntt, t0_as_ntt) =
Expand All @@ -271,7 +266,7 @@ pub(crate) fn sign_internal<
let mut message_representative = [0; MESSAGE_REPRESENTATIVE_SIZE];
derive_message_representative(
verification_key_hash,
domain_separated_context,
domain_separation_context,
message,
&mut message_representative,
);
Expand Down Expand Up @@ -419,19 +414,36 @@ pub(crate) fn sign_internal<
/// This corresponds to line 6 in algorithm 7 in FIPS 204 (line 7 in algorithm
/// 8, resp.).
///
/// Applies domain separation and length encoding to the context string,
/// before appending the message (in the regular variant) or the
/// pre-hash OID as well as the pre-hashed message digest.
///
/// In FIPS 204 M' is the concatenation of the domain separated context, any
/// potential pre-hash OID and the message (or the message pre-hash). We do not
/// explicitely construct the concatenation in memory since it is of statically unknown
/// length, but feed its components directly into the incremental XOF.
///
/// Refer to line 10 of Algorithm 2 (and line 5 of Algorithm 3, resp.) in [FIPS
/// 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf#section.5)
/// for details on the domain separation for regular ML-DSA. Line
/// 23 of Algorithm 4 (and line 18 of Algorithm 5,resp.) describe domain separation for the HashMl-DSA
/// variant.
fn derive_message_representative(
verification_key_hash: [u8; 64],
domain_separated_context: &[u8],
domain_separation_context: DomainSeparationContext,
message: &[u8],
message_representative: &mut [u8; 64],
) {
let mut shake = Shake256Absorb::new();
shake.absorb(&verification_key_hash);
shake.absorb(domain_separated_context);
shake.absorb(&[domain_separation_context.pre_hash_oid().is_some() as u8]);
shake.absorb(&[domain_separation_context.context().len() as u8]);
shake.absorb(domain_separation_context.context());

if let Some(pre_hash_oid) = domain_separation_context.pre_hash_oid() {
shake.absorb(pre_hash_oid)
}

let mut shake = shake.absorb_final(message);
shake.squeeze(message_representative);
}
Expand All @@ -457,7 +469,7 @@ pub(crate) fn verify_internal<
>(
verification_key_serialized: &[u8; VERIFICATION_KEY_SIZE],
message: &[u8],
domain_separated_context: &[u8],
domain_separation_context: DomainSeparationContext,
signature_serialized: &[u8; SIGNATURE_SIZE],
) -> Result<(), VerificationError> {
let (seed_for_A, t1) =
Expand Down Expand Up @@ -490,7 +502,7 @@ pub(crate) fn verify_internal<
let mut message_representative = [0; MESSAGE_REPRESENTATIVE_SIZE];
derive_message_representative(
verification_key_hash,
domain_separated_context,
domain_separation_context,
message,
&mut message_representative,
);
Expand Down Expand Up @@ -560,11 +572,6 @@ pub(crate) fn verify<
context: &[u8],
signature_serialized: &[u8; SIGNATURE_SIZE],
) -> Result<(), VerificationError> {
if context.len() > CONTEXT_MAX_LEN {
return Err(VerificationError::ContextTooLongError);
}
let (domain_separated_context, ctx_len) = domain_separate_context(context, None);

verify_internal::<
SIMDUnit,
Shake128X4,
Expand All @@ -585,7 +592,7 @@ pub(crate) fn verify<
>(
&verification_key_serialized,
message,
&domain_separated_context[..ctx_len],
DomainSeparationContext::new(context, None)?,
&signature_serialized,
)
}
Expand Down Expand Up @@ -616,10 +623,6 @@ pub(crate) fn verify_pre_hashed<
context: &[u8],
signature_serialized: &[u8; SIGNATURE_SIZE],
) -> Result<(), VerificationError> {
if context.len() > CONTEXT_MAX_LEN {
return Err(VerificationError::ContextTooLongError);
}
let (domain_separated_context, ctx_len) = domain_separate_context(context, Some(&PH::oid()));
let pre_hashed_message = PH::hash(message);

verify_internal::<
Expand All @@ -642,40 +645,7 @@ pub(crate) fn verify_pre_hashed<
>(
&verification_key_serialized,
&pre_hashed_message,
&domain_separated_context[..ctx_len],
DomainSeparationContext::new(context, Some(&PH::oid()))?,
&signature_serialized,
)
}

/// Apply domain separation and length encoding to the context string.
///
/// Returns a buffer that contains the domain separated context
/// string, as well as the length of the domain separated context
/// string within the buffer.
/// If a pre_hash option is provided the domain separated context
/// string is extended by the pre-hash OID.
///
/// Refer to line 10 of Algorithm 2 (and line 5 of Algorithm 3, resp.) in [FIPS
/// 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf#section.5)
/// for details on the domain separation for regular ML-DSA. Line
/// 23 of Algorithm 4 (and line 18 of Algorithm 5,resp.) describe domain separation for the HashMl-DSA
/// variant.
fn domain_separate_context(
context: &[u8],
pre_hash_oid: Option<&PreHashOID>,
) -> ([u8; CONTEXT_MAX_LEN + PRE_HASH_OID_LEN + 2], usize) {
debug_assert!(context.len() <= CONTEXT_MAX_LEN);
let mut domain_separated_context = [0u8; CONTEXT_MAX_LEN + PRE_HASH_OID_LEN + 2];
domain_separated_context[1] = context.len() as u8;
let mut len = context.len() + 2;
domain_separated_context[2..len].copy_from_slice(context);

if let Some(pre_hash_oid) = pre_hash_oid {
domain_separated_context[0] = 1;

domain_separated_context[len..len + PRE_HASH_OID_LEN].copy_from_slice(pre_hash_oid);
len += PRE_HASH_OID_LEN;
}

(domain_separated_context, len)
}
58 changes: 57 additions & 1 deletion libcrux-ml-dsa/src/pre_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
//! of FIPS 204, any NIST-approved hash function or XOF can be used to
//!/perform the pre-hash of the message. This module implements the
//! pre-hash trait for SHAKE-128, with a digest length of 256 bytes.
use crate::hash_functions::shake128::Xof;
use crate::{
constants::CONTEXT_MAX_LEN, hash_functions::shake128::Xof, SigningError, VerificationError,
};

pub(crate) const PRE_HASH_OID_LEN: usize = 11;
pub(crate) type PreHashOID = [u8; PRE_HASH_OID_LEN];
Expand Down Expand Up @@ -37,3 +39,57 @@ impl PreHash<256> for SHAKE128_PH {
output
}
}

/// Binds the context string to an optional pre-hash OID identifying
/// the hash function or XOF used for pre-hashing.
pub(crate) struct DomainSeparationContext<'a> {
context: &'a [u8],
pre_hash_oid: Option<&'a PreHashOID>,
}

pub(crate) enum DomainSeparationError {
ContextTooLongError,
}

impl<'a> DomainSeparationContext<'a> {
/// `context` must be at most 255 bytes long.
pub(crate) fn new(
context: &'a [u8],
pre_hash_oid: Option<&'a PreHashOID>,
) -> Result<Self, DomainSeparationError> {
if context.len() > CONTEXT_MAX_LEN {
Err(DomainSeparationError::ContextTooLongError)
} else {
Ok(Self {
context,
pre_hash_oid,
})
}
}

/// Returns the context, guaranteed to be at most 255 bytes long.
pub fn context(&self) -> &[u8] {
self.context
}

/// Returns the pre-hash OID, if any.
pub fn pre_hash_oid(&self) -> Option<&PreHashOID> {
self.pre_hash_oid
}
}

impl From<DomainSeparationError> for SigningError {
fn from(e: DomainSeparationError) -> SigningError {
match e {
DomainSeparationError::ContextTooLongError => SigningError::ContextTooLongError,
}
}
}

impl From<DomainSeparationError> for VerificationError {
fn from(e: DomainSeparationError) -> VerificationError {
match e {
DomainSeparationError::ContextTooLongError => VerificationError::ContextTooLongError,
}
}
}

0 comments on commit 86e7879

Please sign in to comment.