-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Michael Lodder <redmike7@gmail.com>
- Loading branch information
1 parent
a6f22ef
commit c59e45e
Showing
10 changed files
with
319 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
use crate::hash2field::{hash_to_field, ExpandMsg, FromOkm}; | ||
use crate::map2curve::MapToCurve; | ||
use group::cofactor::CofactorGroup; | ||
|
||
/// Adds hashing arbitrary byte sequences to a valid group element | ||
pub trait GroupDigest { | ||
/// The field element representation for a group value with multiple elements | ||
type FieldElement: FromOkm + Default + Copy; | ||
/// The resulting group element | ||
type Output: CofactorGroup<Subgroup = Self::Output> | ||
+ MapToCurve<FieldElement = Self::FieldElement, Output = Self::Output>; | ||
|
||
/// Computes the hash to curve routine according to | ||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html> | ||
/// which says | ||
/// Uniform encoding from byte strings to points in G. | ||
/// That is, the distribution of its output is statistically close | ||
/// to uniform in G. | ||
/// This function is suitable for most applications requiring a random | ||
/// oracle returning points in G assuming a cryptographically secure | ||
/// hash function is used. | ||
/// | ||
/// Examples | ||
/// | ||
/// Using a fixed size hash function | ||
/// | ||
/// let pt = ProjectivePoint::hash_from_bytes::<hash2field::ExpandMsgXmd<sha2::Sha256>>(b"test data", b"CURVE_XMD:SHA-256_SSWU_RO_"); | ||
/// | ||
/// Using an extendable output function | ||
/// | ||
/// let pt = ProjectivePoint::hash_from_bytes::<hash2field::ExpandMsgXof<sha3::Shake256>>(b"test data", b"CURVE_XOF:SHAKE-256_SSWU_RO_"); | ||
/// | ||
fn hash_from_bytes<X: ExpandMsg>(msg: &[u8], dst: &'static [u8]) -> Self::Output { | ||
let mut u = [Self::FieldElement::default(), Self::FieldElement::default()]; | ||
hash_to_field::<X, _>(msg, dst, &mut u); | ||
let q0 = Self::Output::map_to_curve(u[0]); | ||
let q1 = Self::Output::map_to_curve(u[1]); | ||
// Ideally we could add and then clear cofactor once | ||
// thus saving a call but the field elements may not | ||
// add properly due to the underlying implementation | ||
// which could result in an incorrect subgroup | ||
q0.clear_cofactor() + q1.clear_cofactor() | ||
} | ||
|
||
/// Computes the encode to curve routine according to | ||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html> | ||
/// which says | ||
/// Nonuniform encoding from byte strings to | ||
/// points in G. That is, the distribution of its output is not | ||
/// uniformly random in G: the set of possible outputs of | ||
/// encode_to_curve is only a fraction of the points in G, and some | ||
/// points in this set are more likely to be output than others. | ||
fn encode_from_bytes<X: ExpandMsg>(msg: &[u8], dst: &'static [u8]) -> Self::Output { | ||
let mut u = [Self::FieldElement::default()]; | ||
hash_to_field::<X, _>(msg, dst, &mut u); | ||
let q0 = Self::Output::map_to_curve(u[0]); | ||
q0.clear_cofactor() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,74 @@ | ||
use digest_traits::{Digest, ExtendableOutputDirty, Update, XofReader}; | ||
use generic_array::{ArrayLength, GenericArray}; | ||
|
||
/// Salt when the DST is too long | ||
const OVERSIZE_DST_SALT: &[u8] = b"H2C-OVERSIZE-DST-"; | ||
/// Maximum domain separation tag length | ||
const MAX_DST_LEN: usize = 255; | ||
|
||
/// Trait for types implementing expand_message interface for hash_to_field | ||
pub trait ExpandMsg<const OUT: usize> { | ||
/// Expands `msg` to the required number of bytes in `buf` | ||
fn expand_message(msg: &[u8], dst: &[u8]) -> [u8; OUT]; | ||
pub trait ExpandMsg { | ||
/// Expands `msg` to the required number of bytes | ||
/// Returns an expander that can be used to call `read` until enough | ||
/// bytes have been consumed | ||
fn expand_message(msg: &[u8], dst: &'static [u8], len_in_bytes: usize) -> Self; | ||
|
||
/// Fill the array with the expanded bytes | ||
fn fill_bytes(&mut self, okm: &mut [u8]); | ||
} | ||
|
||
/// The domain separation tag | ||
/// | ||
/// Implements [section 5.4.3 of `draft-irtf-cfrg-hash-to-curve-13`][dst]. | ||
/// | ||
/// [dst]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-13#section-5.4.3 | ||
pub(crate) enum Domain<L: ArrayLength<u8>> { | ||
/// > 255 | ||
Hashed(GenericArray<u8, L>), | ||
/// <= 255 | ||
Array(&'static [u8]), | ||
} | ||
|
||
impl<L: ArrayLength<u8>> Domain<L> { | ||
pub fn xof<X>(dst: &'static [u8]) -> Self | ||
where | ||
X: Default + ExtendableOutputDirty + Update, | ||
{ | ||
if dst.len() > MAX_DST_LEN { | ||
let mut data = GenericArray::<u8, L>::default(); | ||
X::default() | ||
.chain(OVERSIZE_DST_SALT) | ||
.chain(dst) | ||
.finalize_xof_dirty() | ||
.read(&mut data); | ||
Self::Hashed(data) | ||
} else { | ||
Self::Array(dst) | ||
} | ||
} | ||
|
||
pub fn xmd<X>(dst: &'static [u8]) -> Self | ||
where | ||
X: Digest<OutputSize = L>, | ||
{ | ||
if dst.len() > MAX_DST_LEN { | ||
Self::Hashed(X::new().chain(OVERSIZE_DST_SALT).chain(dst).finalize()) | ||
} else { | ||
Self::Array(dst) | ||
} | ||
} | ||
|
||
pub fn data(&self) -> &[u8] { | ||
match self { | ||
Self::Hashed(d) => &d[..], | ||
Self::Array(d) => *d, | ||
} | ||
} | ||
|
||
pub fn len(&self) -> usize { | ||
match self { | ||
Self::Hashed(_) => L::to_usize(), | ||
Self::Array(d) => d.len(), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,72 +1,94 @@ | ||
use super::ExpandMsg; | ||
use core::marker::PhantomData; | ||
use digest::{ | ||
use super::{Domain, ExpandMsg}; | ||
use digest_traits::{ | ||
generic_array::{typenum::Unsigned, GenericArray}, | ||
BlockInput, Digest, | ||
}; | ||
use subtle::{Choice, ConditionallySelectable}; | ||
|
||
/// Placeholder type for implementing expand_message_xmd based on a hash function | ||
#[derive(Debug)] | ||
pub struct ExpandMsgXmd<HashT> { | ||
phantom: PhantomData<HashT>, | ||
pub struct ExpandMsgXmd<HashT> | ||
where | ||
HashT: Digest + BlockInput, | ||
{ | ||
b_0: GenericArray<u8, HashT::OutputSize>, | ||
b_vals: GenericArray<u8, HashT::OutputSize>, | ||
domain: Domain<HashT::OutputSize>, | ||
index: usize, | ||
offset: usize, | ||
ell: usize, | ||
} | ||
|
||
impl<HashT> ExpandMsgXmd<HashT> | ||
where | ||
HashT: Digest + BlockInput, | ||
{ | ||
fn next(&mut self) -> bool { | ||
if self.index < self.ell { | ||
self.index += 1; | ||
self.offset = 0; | ||
// b_0 XOR b_(idx - 1) | ||
let mut tmp = GenericArray::<u8, HashT::OutputSize>::default(); | ||
self.b_0 | ||
.iter() | ||
.zip(&self.b_vals[..]) | ||
.enumerate() | ||
.for_each(|(j, (b0val, bi1val))| tmp[j] = b0val ^ bi1val); | ||
self.b_vals = HashT::new() | ||
.chain(tmp) | ||
.chain([self.index as u8]) | ||
.chain(self.domain.data()) | ||
.chain([self.domain.len() as u8]) | ||
.finalize(); | ||
true | ||
} else { | ||
false | ||
} | ||
} | ||
} | ||
|
||
/// ExpandMsgXmd implements expand_message_xmd for the ExpandMsg trait | ||
impl<HashT, const LEN_IN_BYTES: usize> ExpandMsg<LEN_IN_BYTES> for ExpandMsgXmd<HashT> | ||
impl<HashT> ExpandMsg for ExpandMsgXmd<HashT> | ||
where | ||
HashT: Digest + BlockInput, | ||
{ | ||
fn expand_message(msg: &[u8], dst: &[u8]) -> [u8; LEN_IN_BYTES] { | ||
fn expand_message(msg: &[u8], dst: &'static [u8], len_in_bytes: usize) -> Self { | ||
let b_in_bytes = HashT::OutputSize::to_usize(); | ||
let ell = (LEN_IN_BYTES + b_in_bytes - 1) / b_in_bytes; | ||
let ell = (len_in_bytes + b_in_bytes - 1) / b_in_bytes; | ||
if ell > 255 { | ||
panic!("ell was too big in expand_message_xmd"); | ||
} | ||
let domain = Domain::xmd::<HashT>(dst); | ||
let b_0 = HashT::new() | ||
.chain(GenericArray::<u8, HashT::BlockSize>::default()) | ||
.chain(msg) | ||
.chain([(LEN_IN_BYTES >> 8) as u8, LEN_IN_BYTES as u8, 0u8]) | ||
.chain(dst) | ||
.chain([dst.len() as u8]) | ||
.chain([(len_in_bytes >> 8) as u8, len_in_bytes as u8, 0u8]) | ||
.chain(domain.data()) | ||
.chain([domain.len() as u8]) | ||
.finalize(); | ||
|
||
let mut b_vals = HashT::new() | ||
let b_vals = HashT::new() | ||
.chain(&b_0[..]) | ||
.chain([1u8]) | ||
.chain(dst) | ||
.chain([dst.len() as u8]) | ||
.chain(domain.data()) | ||
.chain([domain.len() as u8]) | ||
.finalize(); | ||
|
||
let mut buf = [0u8; LEN_IN_BYTES]; | ||
let mut offset = 0; | ||
Self { | ||
b_0, | ||
b_vals, | ||
domain, | ||
index: 1, | ||
offset: 0, | ||
ell, | ||
} | ||
} | ||
|
||
for i in 1..ell { | ||
// b_0 XOR b_(idx - 1) | ||
let mut tmp = GenericArray::<u8, HashT::OutputSize>::default(); | ||
b_0.iter() | ||
.zip(&b_vals[..]) | ||
.enumerate() | ||
.for_each(|(j, (b0val, bi1val))| tmp[j] = b0val ^ bi1val); | ||
for b in b_vals { | ||
buf[offset % LEN_IN_BYTES].conditional_assign( | ||
&b, | ||
Choice::from(if offset < LEN_IN_BYTES { 1 } else { 0 }), | ||
); | ||
offset += 1; | ||
fn fill_bytes(&mut self, okm: &mut [u8]) { | ||
for b in okm { | ||
if self.offset == self.b_vals.len() && !self.next() { | ||
return; | ||
} | ||
b_vals = HashT::new() | ||
.chain(tmp) | ||
.chain([(i + 1) as u8]) | ||
.chain(dst) | ||
.chain([dst.len() as u8]) | ||
.finalize(); | ||
} | ||
for b in b_vals { | ||
buf[offset % LEN_IN_BYTES] | ||
.conditional_assign(&b, Choice::from(if offset < LEN_IN_BYTES { 1 } else { 0 })); | ||
offset += 1; | ||
*b = self.b_vals[self.offset]; | ||
self.offset += 1; | ||
} | ||
buf | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,33 @@ | ||
use super::ExpandMsg; | ||
use core::marker::PhantomData; | ||
use digest::{ExtendableOutput, Update, XofReader}; | ||
use crate::hash2field::Domain; | ||
use digest_traits::{ExtendableOutput, ExtendableOutputDirty, Update, XofReader}; | ||
use generic_array::typenum::U32; | ||
|
||
/// Placeholder type for implementing expand_message_xof based on an extendable output function | ||
#[derive(Debug)] | ||
pub struct ExpandMsgXof<HashT> { | ||
phantom: PhantomData<HashT>, | ||
pub struct ExpandMsgXof<HashT> | ||
where | ||
HashT: Default + ExtendableOutput + ExtendableOutputDirty + Update, | ||
{ | ||
reader: <HashT as ExtendableOutput>::Reader, | ||
} | ||
|
||
/// ExpandMsgXof implements expand_message_xof for the ExpandMsg trait | ||
impl<HashT, const LEN_IN_BYTES: usize> ExpandMsg<LEN_IN_BYTES> for ExpandMsgXof<HashT> | ||
impl<HashT> ExpandMsg for ExpandMsgXof<HashT> | ||
where | ||
HashT: Default + ExtendableOutput + Update, | ||
HashT: Default + ExtendableOutput + ExtendableOutputDirty + Update, | ||
{ | ||
fn expand_message(msg: &[u8], dst: &[u8]) -> [u8; LEN_IN_BYTES] { | ||
let mut buf = [0u8; LEN_IN_BYTES]; | ||
let mut r = HashT::default() | ||
fn expand_message(msg: &[u8], dst: &'static [u8], len_in_bytes: usize) -> Self { | ||
let domain = Domain::<U32>::xof::<HashT>(dst); | ||
let reader = HashT::default() | ||
.chain(msg) | ||
.chain([(LEN_IN_BYTES >> 8) as u8, LEN_IN_BYTES as u8]) | ||
.chain(dst) | ||
.chain([dst.len() as u8]) | ||
.chain([(len_in_bytes >> 8) as u8, len_in_bytes as u8]) | ||
.chain(domain.data()) | ||
.chain([domain.len() as u8]) | ||
.finalize_xof(); | ||
r.read(&mut buf); | ||
buf | ||
Self { reader } | ||
} | ||
|
||
fn fill_bytes(&mut self, okm: &mut [u8]) { | ||
self.reader.read(okm); | ||
} | ||
} |
Oops, something went wrong.