Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions elliptic-curve/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 elliptic-curve/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ zeroize = { version = "1", default-features = false }

# optional dependencies
base64ct = { version = "1", optional = true, default-features = false }
digest = { version = "0.9", optional = true, default-features = false }
ff = { version = "0.11", optional = true, default-features = false }
group = { version = "0.11", optional = true, default-features = false }
hex-literal = { version = "0.3", optional = true }
Expand All @@ -47,6 +48,7 @@ arithmetic = ["ff", "group"]
bits = ["arithmetic", "ff/bits"]
dev = ["arithmetic", "hex-literal", "pem", "pkcs8"]
ecdh = ["arithmetic"]
hashing = ["digest"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elsewhere in RustCrypto we generally just keep digest-related functionality gated behind the digest feature

hazmat = []
jwk = ["alloc", "base64ct/alloc", "serde", "serde_json", "zeroize/alloc"]
osswu = ["ff"]
Expand Down
33 changes: 33 additions & 0 deletions elliptic-curve/src/hash2field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
mod expand_msg;
mod expand_msg_xmd;
mod expand_msg_xof;

use core::convert::TryFrom;
pub use expand_msg::*;
pub use expand_msg_xmd::*;
pub use expand_msg_xof::*;

/// The trait for helping to convert to a scalar
pub trait FromOkm<const L: usize>: Sized {
/// Convert a byte sequence into a scalar
fn from_okm(data: &[u8; L]) -> Self;
}

/// Convert an arbitrary byte sequence according to
/// <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3>
pub fn hash_to_field<E, T, const L: usize, const COUNT: usize, const OUT: usize>(
data: &[u8],
domain: &[u8],
) -> [T; COUNT]
where
E: ExpandMsg<OUT>,
T: FromOkm<L> + Default + Copy,
{
let random_bytes = E::expand_message(data, domain);
let mut out = [T::default(); COUNT];
for i in 0..COUNT {
let u = <[u8; L]>::try_from(&random_bytes[(L * i)..L * (i + 1)]).expect("not enough bytes");
out[i] = T::from_okm(&u);
}
out
}
5 changes: 5 additions & 0 deletions elliptic-curve/src/hash2field/expand_msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// 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];
}
72 changes: 72 additions & 0 deletions elliptic-curve/src/hash2field/expand_msg_xmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use super::ExpandMsg;
use core::marker::PhantomData;
use digest::{
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>,
}

/// ExpandMsgXmd implements expand_message_xmd for the ExpandMsg trait
impl<HashT, const LEN_IN_BYTES: usize> ExpandMsg<LEN_IN_BYTES> for ExpandMsgXmd<HashT>
where
HashT: Digest + BlockInput,
{
fn expand_message(msg: &[u8], dst: &[u8]) -> [u8; LEN_IN_BYTES] {
let b_in_bytes = HashT::OutputSize::to_usize();
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 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])
.finalize();

let mut b_vals = HashT::new()
.chain(&b_0[..])
.chain([1u8])
.chain(dst)
.chain([dst.len() as u8])
.finalize();

let mut buf = [0u8; LEN_IN_BYTES];
let mut offset = 0;

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;
}
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;
}
buf
}
}
27 changes: 27 additions & 0 deletions elliptic-curve/src/hash2field/expand_msg_xof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use super::ExpandMsg;
use core::marker::PhantomData;
use digest::{ExtendableOutput, Update, XofReader};

/// Placeholder type for implementing expand_message_xof based on an extendable output function
#[derive(Debug)]
pub struct ExpandMsgXof<HashT> {
phantom: PhantomData<HashT>,
}

/// ExpandMsgXof implements expand_message_xof for the ExpandMsg trait
impl<HashT, const LEN_IN_BYTES: usize> ExpandMsg<LEN_IN_BYTES> for ExpandMsgXof<HashT>
where
HashT: Default + ExtendableOutput + Update,
{
fn expand_message(msg: &[u8], dst: &[u8]) -> [u8; LEN_IN_BYTES] {
let mut buf = [0u8; LEN_IN_BYTES];
let mut r = HashT::default()
.chain(msg)
.chain([(LEN_IN_BYTES >> 8) as u8, LEN_IN_BYTES as u8])
.chain(dst)
.chain([dst.len() as u8])
.finalize_xof();
r.read(&mut buf);
buf
}
}
6 changes: 6 additions & 0 deletions elliptic-curve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ mod jwk;
#[cfg_attr(docsrs, doc(cfg(feature = "osswu")))]
pub mod osswu;

/// Traits for computing hash to field as described in
/// <https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve>
#[cfg(feature = "hashing")]
#[cfg_attr(docsrs, doc(cfg(feature = "hashing")))]
pub mod hash2field;

pub use crate::{
error::{Error, Result},
point::{
Expand Down