diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bbf7011..d3349e3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,7 +100,7 @@ jobs: RUSTDOCFLAGS: -D warnings with: command: doc - args: --no-deps --document-private-items + args: --no-deps --document-private-items --features std,p256 format: diff --git a/src/group/expand.rs b/src/group/expand.rs index 71da41c..69d7e47 100644 --- a/src/group/expand.rs +++ b/src/group/expand.rs @@ -6,7 +6,7 @@ // of this source tree. use crate::errors::InternalError; -use crate::serialization::i2osp; +use crate::util::i2osp; use core::ops::Add; use digest::{BlockInput, Digest}; use generic_array::{ @@ -28,11 +28,13 @@ fn xor>(x: GenericArray, y: GenericArray) -> Ge /// Corresponds to the expand_message_xmd() function defined in /// pub fn expand_message_xmd< + 'a, H: BlockInput + Digest, L: ArrayLength, + M: IntoIterator, D: ArrayLength + Add, >( - msg: &[u8], + msg: M, dst: GenericArray, ) -> Result, InternalError> where @@ -46,19 +48,20 @@ where let dst_prime = dst.concat(i2osp::(D::USIZE)?); let z_pad = i2osp::<::BlockSize>(0)?; let l_i_b_str = i2osp::(L::USIZE)?; - let msg_0 = i2osp::(0)?; - let msg_prime = - core::array::IntoIter::new([z_pad.as_slice(), msg, &l_i_b_str, &msg_0, &dst_prime]); let mut h = H::new(); + + // msg_prime = Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime + h.update(z_pad); + for bytes in msg { + h.update(bytes) + } + h.update(l_i_b_str); + h.update(i2osp::(0)?); + h.update(&dst_prime); + // b[0] - let b_0 = msg_prime - .into_iter() - .fold(&mut h, |h, msg| { - h.update(msg); - h - }) - .finalize_reset(); + let b_0 = h.finalize_reset(); let mut b_i = GenericArray::default(); let mut uniform_bytes = GenericArray::default(); @@ -192,10 +195,16 @@ mod tests { for tv in test_vectors { let uniform_bytes = match tv.len_in_bytes { - 32 => super::expand_message_xmd::(tv.msg.as_bytes(), dst) - .map(|bytes| bytes.to_vec()), - 128 => super::expand_message_xmd::(tv.msg.as_bytes(), dst) - .map(|bytes| bytes.to_vec()), + 32 => super::expand_message_xmd::( + Some(tv.msg.as_bytes()), + dst, + ) + .map(|bytes| bytes.to_vec()), + 128 => super::expand_message_xmd::( + Some(tv.msg.as_bytes()), + dst, + ) + .map(|bytes| bytes.to_vec()), _ => unimplemented!(), } .unwrap(); diff --git a/src/group/mod.rs b/src/group/mod.rs index e8defb6..e13ef19 100644 --- a/src/group/mod.rs +++ b/src/group/mod.rs @@ -57,8 +57,13 @@ pub trait Group: >::Output: ArrayLength; /// Hashes a slice of pseudo-random bytes to a scalar - fn hash_to_scalar + Add>( - input: &[u8], + fn hash_to_scalar< + 'a, + H: BlockInput + Digest, + D: ArrayLength + Add, + I: IntoIterator, + >( + input: I, dst: GenericArray, ) -> Result where diff --git a/src/group/p256.rs b/src/group/p256.rs index d08fee4..593e2a9 100644 --- a/src/group/p256.rs +++ b/src/group/p256.rs @@ -75,7 +75,7 @@ impl Group for ProjectivePoint { // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3 // `hash_to_field` calls `expand_message` with a `len_in_bytes` of `count * L` let uniform_bytes = - super::expand::expand_message_xmd::>::Output, _>(msg, dst)?; + super::expand::expand_message_xmd::>::Output, _, _>(Some(msg), dst)?; // hash to curve let (q0x, q0y) = hash_to_curve_simple_swu(&uniform_bytes[..L::USIZE], &A, &B, &P, &Z); @@ -97,8 +97,13 @@ impl Group for ProjectivePoint { // Implements the `HashToScalar()` function from // https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.3 - fn hash_to_scalar + Add>( - input: &[u8], + fn hash_to_scalar< + 'a, + H: BlockInput + Digest, + D: ArrayLength + Add, + I: IntoIterator, + >( + input: I, dst: GenericArray, ) -> Result where @@ -115,7 +120,7 @@ impl Group for ProjectivePoint { // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3 // `HashToScalar` is `hash_to_field` - let uniform_bytes = super::expand::expand_message_xmd::(input, dst)?; + let uniform_bytes = super::expand::expand_message_xmd::(input, dst)?; let bytes = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes) .mod_floor(&N) .to_bytes_be() @@ -180,7 +185,7 @@ impl Group for ProjectivePoint { /// /// `cmov`, `mod_floor` and `modpow` needs to be made constant-time, which /// will be supported after crypto-bigint is no longer experimental. See -/// https://github.com/novifinancial/voprf/issues/13 for more context. +/// for more context. #[allow(clippy::many_single_char_names)] fn hash_to_curve_simple_swu>( @@ -536,11 +541,12 @@ mod tests { let dst = GenericArray::from(*b"QUUX-V01-CS02-with-P256_XMD:SHA-256_SSWU_RO_"); for tv in test_vectors { - let uniform_bytes = super::super::expand::expand_message_xmd::( - tv.msg.as_bytes(), - dst, - ) - .unwrap(); + let uniform_bytes = + super::super::expand::expand_message_xmd::( + Some(tv.msg.as_bytes()), + dst, + ) + .unwrap(); let u0 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[..48]).mod_floor(&P); let u1 = BigInt::from_bytes_be(Sign::Plus, &uniform_bytes[48..]).mod_floor(&P); diff --git a/src/group/ristretto.rs b/src/group/ristretto.rs index 70fd83b..9790805 100644 --- a/src/group/ristretto.rs +++ b/src/group/ristretto.rs @@ -42,7 +42,7 @@ impl Group for RistrettoPoint { where >::Output: ArrayLength, { - let uniform_bytes = super::expand::expand_message_xmd::(msg, dst)?; + let uniform_bytes = super::expand::expand_message_xmd::(Some(msg), dst)?; Ok(RistrettoPoint::from_uniform_bytes( uniform_bytes @@ -54,14 +54,19 @@ impl Group for RistrettoPoint { // Implements the `HashToScalar()` function from // https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-07.html#section-4.1 - fn hash_to_scalar + Add>( - input: &[u8], + fn hash_to_scalar< + 'a, + H: BlockInput + Digest, + D: ArrayLength + Add, + I: IntoIterator, + >( + input: I, dst: GenericArray, ) -> Result where >::Output: ArrayLength, { - let uniform_bytes = super::expand::expand_message_xmd::(input, dst)?; + let uniform_bytes = super::expand::expand_message_xmd::(input, dst)?; Ok(Scalar::from_bytes_mod_order_wide( uniform_bytes diff --git a/src/lib.rs b/src/lib.rs index 3840982..11e41ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -425,6 +425,8 @@ extern crate alloc; #[macro_use] mod impls; +#[macro_use] +mod util; pub mod errors; pub mod group; mod serialization; diff --git a/src/serialization.rs b/src/serialization.rs index 4caff32..78abe13 100644 --- a/src/serialization.rs +++ b/src/serialization.rs @@ -19,7 +19,7 @@ use crate::{ use alloc::vec::Vec; use core::marker::PhantomData; use digest::{BlockInput, Digest}; -use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; +use generic_array::typenum::Unsigned; ////////////////////////////////////////////////////////// // Serialization and Deserialization for High-Level API // @@ -190,96 +190,3 @@ impl EvaluationElement { }) } } - -////////////////////// -// Helper Functions // -// ================ // -////////////////////// - -// Corresponds to the I2OSP() function from RFC8017 -pub(crate) fn i2osp>( - input: usize, -) -> Result, InternalError> { - const SIZEOF_USIZE: usize = core::mem::size_of::(); - - // Check if input >= 256^length - if (SIZEOF_USIZE as u32 - input.leading_zeros() / 8) > L::U32 { - return Err(InternalError::SerializationError); - } - - if L::USIZE <= SIZEOF_USIZE { - return Ok(GenericArray::clone_from_slice( - &input.to_be_bytes()[SIZEOF_USIZE - L::USIZE..], - )); - } - - let mut output = GenericArray::default(); - output[L::USIZE - SIZEOF_USIZE..L::USIZE].copy_from_slice(&input.to_be_bytes()); - Ok(output) -} - -// Computes I2OSP(len(input), max_bytes) || input -pub(crate) fn serialize>(input: &[u8]) -> Result, InternalError> { - Ok([&i2osp::(input.len())?, input].concat()) -} - -#[cfg(test)] -mod unit_tests { - use super::*; - use curve25519_dalek::ristretto::RistrettoPoint; - use generic_array::typenum::{U1, U2}; - use proptest::{collection::vec, prelude::*}; - use sha2::Sha512; - - // Test the error condition for I2OSP - #[test] - fn test_i2osp_err_check() { - assert!(i2osp::(0).is_ok()); - - assert!(i2osp::(255).is_ok()); - assert!(i2osp::(256).is_err()); - assert!(i2osp::(257).is_err()); - - assert!(i2osp::(256 * 256 - 1).is_ok()); - assert!(i2osp::(256 * 256).is_err()); - assert!(i2osp::(256 * 256 + 1).is_err()); - } - - proptest! { - #[test] - fn test_nocrash_nonverifiable_client(bytes in vec(any::(), 0..200)) { - NonVerifiableClient::::deserialize(&bytes[..]).map_or(true, |_| true); - } - - #[test] - fn test_nocrash_verifiable_client(bytes in vec(any::(), 0..200)) { - VerifiableClient::::deserialize(&bytes[..]).map_or(true, |_| true); - } - - #[test] - fn test_nocrash_nonverifiable_server(bytes in vec(any::(), 0..200)) { - NonVerifiableServer::::deserialize(&bytes[..]).map_or(true, |_| true); - } - - #[test] - fn test_nocrash_verifiable_server(bytes in vec(any::(), 0..200)) { - VerifiableServer::::deserialize(&bytes[..]).map_or(true, |_| true); - } - - #[test] - fn test_nocrash_blinded_element(bytes in vec(any::(), 0..200)) { - BlindedElement::::deserialize(&bytes[..]).map_or(true, |_| true); - } - - #[test] - fn test_nocrash_evaluation_element(bytes in vec(any::(), 0..200)) { - EvaluationElement::::deserialize(&bytes[..]).map_or(true, |_| true); - } - - #[test] - fn test_nocrash_proof(bytes in vec(any::(), 0..200)) { - Proof::::deserialize(&bytes[..]).map_or(true, |_| true); - } - - } -} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..f1881ed --- /dev/null +++ b/src/util.rs @@ -0,0 +1,180 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under both the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree and the Apache +// License, Version 2.0 found in the LICENSE-APACHE file in the root directory +// of this source tree. + +//! Helper functions + +use crate::errors::InternalError; +use core::array::IntoIter; +use generic_array::{typenum::U0, ArrayLength, GenericArray}; + +// Corresponds to the I2OSP() function from RFC8017 +pub(crate) fn i2osp>( + input: usize, +) -> Result, InternalError> { + const SIZEOF_USIZE: usize = core::mem::size_of::(); + + // Check if input >= 256^length + if (SIZEOF_USIZE as u32 - input.leading_zeros() / 8) > L::U32 { + return Err(InternalError::SerializationError); + } + + if L::USIZE <= SIZEOF_USIZE { + return Ok(GenericArray::clone_from_slice( + &input.to_be_bytes()[SIZEOF_USIZE - L::USIZE..], + )); + } + + let mut output = GenericArray::default(); + output[L::USIZE - SIZEOF_USIZE..L::USIZE].copy_from_slice(&input.to_be_bytes()); + Ok(output) +} + +/// Simplifies handling of [`serialize()`] output and implements [`Iterator`]. +pub(crate) struct Serialized<'a, L1: ArrayLength, L2: ArrayLength> { + octet: GenericArray, + input: Input<'a, L2>, +} + +enum Input<'a, L: ArrayLength> { + Owned(GenericArray), + Borrowed(&'a [u8]), +} + +impl<'a, L1: ArrayLength, L2: ArrayLength> IntoIterator for &'a Serialized<'a, L1, L2> { + type Item = &'a [u8]; + + type IntoIter = IntoIter<&'a [u8], 2>; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new([ + &self.octet, + match self.input { + Input::Owned(ref bytes) => bytes, + Input::Borrowed(bytes) => bytes, + }, + ]) + } +} + +// Computes I2OSP(len(input), max_bytes) || input +pub(crate) fn serialize>( + input: &[u8], +) -> Result, InternalError> { + Ok(Serialized { + octet: i2osp::(input.len())?, + input: Input::Borrowed(input), + }) +} + +// Variation of `serialize` that takes an owned `input` +pub(crate) fn serialize_owned, L2: ArrayLength>( + input: GenericArray, +) -> Result, InternalError> { + Ok(Serialized { + octet: i2osp::(input.len())?, + input: Input::Owned(input), + }) +} + +macro_rules! chain_name { + ($var:ident, $mod:ident) => { + $mod + }; + ($var:ident) => { + $var + }; +} + +macro_rules! chain_skip { + ($var:ident, $feed:expr) => { + $feed + }; + ($var:ident) => { + &$var + }; +} + +/// The purpose of this macro is to simplify [`concat`](alloc::slice::Concat::concat)ing +/// slices into an [`Iterator`] to avoid allocation +macro_rules! chain { + ( + $var:ident, + $item1:expr $(=> |$mod1:ident| $feed1:expr)?, + $($item2:expr $(=> |$mod2:ident| $feed2:expr)?),+$(,)? + ) => { + let chain_name!(__temp$(, $mod1)?) = $item1; + let $var = (chain_skip!(__temp$(, $feed1)?)).into_iter(); + $( + let chain_name!(__temp$(, $mod2)?) = $item2; + let $var = $var.chain(chain_skip!(__temp$(, $feed2)?)); + )+ + }; +} + +#[cfg(test)] +mod unit_tests { + use super::*; + use crate::voprf::{ + BlindedElement, EvaluationElement, NonVerifiableClient, NonVerifiableServer, Proof, + VerifiableClient, VerifiableServer, + }; + use curve25519_dalek::ristretto::RistrettoPoint; + use generic_array::typenum::{U1, U2}; + use proptest::{collection::vec, prelude::*}; + use sha2::Sha512; + + // Test the error condition for I2OSP + #[test] + fn test_i2osp_err_check() { + assert!(i2osp::(0).is_ok()); + + assert!(i2osp::(255).is_ok()); + assert!(i2osp::(256).is_err()); + assert!(i2osp::(257).is_err()); + + assert!(i2osp::(256 * 256 - 1).is_ok()); + assert!(i2osp::(256 * 256).is_err()); + assert!(i2osp::(256 * 256 + 1).is_err()); + } + + proptest! { + #[test] + fn test_nocrash_nonverifiable_client(bytes in vec(any::(), 0..200)) { + NonVerifiableClient::::deserialize(&bytes[..]).map_or(true, |_| true); + } + + #[test] + fn test_nocrash_verifiable_client(bytes in vec(any::(), 0..200)) { + VerifiableClient::::deserialize(&bytes[..]).map_or(true, |_| true); + } + + #[test] + fn test_nocrash_nonverifiable_server(bytes in vec(any::(), 0..200)) { + NonVerifiableServer::::deserialize(&bytes[..]).map_or(true, |_| true); + } + + #[test] + fn test_nocrash_verifiable_server(bytes in vec(any::(), 0..200)) { + VerifiableServer::::deserialize(&bytes[..]).map_or(true, |_| true); + } + + #[test] + fn test_nocrash_blinded_element(bytes in vec(any::(), 0..200)) { + BlindedElement::::deserialize(&bytes[..]).map_or(true, |_| true); + } + + #[test] + fn test_nocrash_evaluation_element(bytes in vec(any::(), 0..200)) { + EvaluationElement::::deserialize(&bytes[..]).map_or(true, |_| true); + } + + #[test] + fn test_nocrash_proof(bytes in vec(any::(), 0..200)) { + Proof::::deserialize(&bytes[..]).map_or(true, |_| true); + } + } +} diff --git a/src/voprf.rs b/src/voprf.rs index 8c37dae..b123fae 100644 --- a/src/voprf.rs +++ b/src/voprf.rs @@ -10,7 +10,7 @@ use crate::{ errors::InternalError, group::Group, - serialization::{i2osp, serialize}, + util::{i2osp, serialize, serialize_owned}, }; use alloc::vec::Vec; use core::convert::TryInto; @@ -348,7 +348,7 @@ impl NonVerifiableServer { pub fn new_from_seed(seed: &[u8]) -> Result { let dst = GenericArray::from(*STR_HASH_TO_SCALAR).concat(get_context_string::(Mode::Base)?); - let sk = G::hash_to_scalar::(seed, dst)?; + let sk = G::hash_to_scalar::(Some(seed), dst)?; Ok(Self { sk, hash: PhantomData, @@ -368,15 +368,15 @@ impl NonVerifiableServer { blinded_element: BlindedElement, metadata: Option<&[u8]>, ) -> Result, InternalError> { - let context = [ - STR_CONTEXT, - &get_context_string::(Mode::Base)?, - &serialize::(metadata.unwrap_or_default())?, - ] - .concat(); + chain!( + context, + STR_CONTEXT => |x| Some(x), + get_context_string::(Mode::Base)? => |x| Some(x.as_slice()), + serialize::(metadata.unwrap_or_default())?, + ); let dst = GenericArray::from(*STR_HASH_TO_SCALAR).concat(get_context_string::(Mode::Base)?); - let m = G::hash_to_scalar::(&context, dst)?; + let m = G::hash_to_scalar::(context, dst)?; let t = self.sk + &m; let evaluation_element = blinded_element.value * &G::scalar_invert(&t); Ok(NonVerifiableServerEvaluateResult { @@ -415,7 +415,7 @@ impl VerifiableServer { pub fn new_from_seed(seed: &[u8]) -> Result { let dst = GenericArray::from(*STR_HASH_TO_SCALAR) .concat(get_context_string::(Mode::Verifiable)?); - let sk = G::hash_to_scalar::(seed, dst)?; + let sk = G::hash_to_scalar::(Some(seed), dst)?; let pk = G::base_point() * &sk; Ok(Self { sk, @@ -458,15 +458,14 @@ impl VerifiableServer { &'a I: IntoIterator>, <&'a I as IntoIterator>::IntoIter: ExactSizeIterator, { - let context = [ - STR_CONTEXT, - &get_context_string::(Mode::Verifiable)?, - &serialize::(metadata.unwrap_or_default())?, - ] - .concat(); + chain!(context, + STR_CONTEXT => |x| Some(x), + get_context_string::(Mode::Verifiable)? => |x| Some(x.as_slice()), + serialize::(metadata.unwrap_or_default())?, + ); let dst = GenericArray::from(*STR_HASH_TO_SCALAR) .concat(get_context_string::(Mode::Verifiable)?); - let m = G::hash_to_scalar::(&context, dst)?; + let m = G::hash_to_scalar::(context, dst)?; let t = self.sk + &m; let evaluation_elements: Vec> = blinded_elements .into_iter() @@ -639,16 +638,15 @@ where &'a I: IntoIterator>, <&'a I as IntoIterator>::IntoIter: ExactSizeIterator, { - let context = [ - STR_CONTEXT, - &get_context_string::(Mode::Verifiable)?, - &serialize::(info)?, - ] - .concat(); + chain!(context, + STR_CONTEXT => |x| Some(x), + get_context_string::(Mode::Verifiable)? => |x| Some(x.as_slice()), + serialize::(info)?, + ); let dst = GenericArray::from(*STR_HASH_TO_SCALAR).concat(get_context_string::(Mode::Verifiable)?); - let m = G::hash_to_scalar::(&context, dst)?; + let m = G::hash_to_scalar::(context, dst)?; let g = G::base_point(); let t = g * &m; @@ -684,20 +682,20 @@ fn generate_proof( let challenge_dst = GenericArray::from(*STR_CHALLENGE).concat(get_context_string::(Mode::Verifiable)?); - let h2_input = [ - serialize::(&b.to_arr())?, - serialize::(&m.to_arr())?, - serialize::(&z.to_arr())?, - serialize::(&t2.to_arr())?, - serialize::(&t3.to_arr())?, - serialize::(&challenge_dst)?, - ] - .concat(); + chain!( + h2_input, + serialize_owned::(b.to_arr())?, + serialize_owned::(m.to_arr())?, + serialize_owned::(z.to_arr())?, + serialize_owned::(t2.to_arr())?, + serialize_owned::(t3.to_arr())?, + serialize_owned::(challenge_dst)?, + ); let hash_to_scalar_dst = GenericArray::from(*STR_HASH_TO_SCALAR).concat(get_context_string::(Mode::Verifiable)?); - let c_scalar = G::hash_to_scalar::(&h2_input, hash_to_scalar_dst)?; + let c_scalar = G::hash_to_scalar::(h2_input, hash_to_scalar_dst)?; let s_scalar = r - &(c_scalar * &k); Ok(Proof { @@ -721,19 +719,19 @@ fn verify_proof( let challenge_dst = GenericArray::from(*STR_CHALLENGE).concat(get_context_string::(Mode::Verifiable)?); - let h2_input = [ - serialize::(&b.to_arr())?, - serialize::(&m.to_arr())?, - serialize::(&z.to_arr())?, - serialize::(&t2.to_arr())?, - serialize::(&t3.to_arr())?, - serialize::(&challenge_dst)?, - ] - .concat(); + chain!( + h2_input, + serialize_owned::(b.to_arr())?, + serialize_owned::(m.to_arr())?, + serialize_owned::(z.to_arr())?, + serialize_owned::(t2.to_arr())?, + serialize_owned::(t3.to_arr())?, + serialize_owned::(challenge_dst)?, + ); let hash_to_scalar_dst = GenericArray::from(*STR_HASH_TO_SCALAR).concat(get_context_string::(Mode::Verifiable)?); - let c = G::hash_to_scalar::(&h2_input, hash_to_scalar_dst)?; + let c = G::hash_to_scalar::(h2_input, hash_to_scalar_dst)?; match c.ct_eq(&proof.c_scalar).into() { true => Ok(()), @@ -755,15 +753,17 @@ fn finalize_after_unblind< inputs_and_unblinded_elements .map(|(input, unblinded_element)| { - Ok(::digest( - &[ - serialize::(input)?, - serialize::(info)?, - serialize::(&unblinded_element.to_arr())?, - serialize::(&finalize_dst)?, - ] - .concat(), - )) + chain!( + hash_input, + serialize::(input)?, + serialize::(info)?, + serialize_owned::(unblinded_element.to_arr())?, + serialize_owned::(finalize_dst)?, + ); + + Ok(hash_input + .fold(::new(), |h, bytes| h.chain(bytes)) + .finalize()) }) .collect() } @@ -782,24 +782,29 @@ fn compute_composites( let composite_dst = GenericArray::from(*STR_COMPOSITE).concat(get_context_string::(Mode::Verifiable)?); - let h1_input = [serialize::(&b.to_arr())?, serialize::(&seed_dst)?].concat(); - let seed = ::digest(&h1_input); + chain!( + h1_input, + serialize_owned::(b.to_arr())?, + serialize_owned::(seed_dst)?, + ); + let seed = h1_input + .fold(::new(), |h, bytes| h.chain(bytes)) + .finalize(); let mut m = G::identity(); let mut z = G::identity(); for (i, (c, d)) in c_slice.zip(d_slice).enumerate() { - let h2_input = [ - serialize::(&seed)?.as_slice(), - &i2osp::(i)?, - &serialize::(&c.value.to_arr())?, - &serialize::(&d.value.to_arr())?, - &serialize::(&composite_dst)?, - ] - .concat(); + chain!(h2_input, + serialize_owned::(seed.clone())?, + i2osp::(i)? => |x| Some(x.as_slice()), + serialize_owned::(c.value.to_arr())?, + serialize_owned::(d.value.to_arr())?, + serialize_owned::(composite_dst)?, + ); let dst = GenericArray::from(*STR_HASH_TO_SCALAR) .concat(get_context_string::(Mode::Verifiable)?); - let di = G::hash_to_scalar::(&h2_input, dst)?; + let di = G::hash_to_scalar::(h2_input, dst)?; m = c.value * &di + &m; z = match k_option { Some(_) => z, @@ -846,15 +851,15 @@ mod tests { GenericArray::from(*STR_HASH_TO_GROUP).concat(get_context_string::(mode).unwrap()); let point = G::hash_to_curve::(input, dst).unwrap(); - let context = [ - STR_CONTEXT, - &get_context_string::(mode).unwrap(), - &serialize::(info).unwrap(), - ] - .concat(); + chain!(context, + STR_CONTEXT => |x| Some(x), + get_context_string::(mode).unwrap() => |x| Some(x.as_slice()), + serialize::(info).unwrap(), + ); + let dst = GenericArray::from(*STR_HASH_TO_SCALAR).concat(get_context_string::(mode).unwrap()); - let m = ::hash_to_scalar::(&context, dst).unwrap(); + let m = ::hash_to_scalar::(context, dst).unwrap(); let res = point * &::scalar_invert(&(key + &m));