From 72e11e334abc1a3a381fed0a7c87d91e2539b360 Mon Sep 17 00:00:00 2001 From: Michael Lodder Date: Wed, 22 Dec 2021 14:45:07 -0700 Subject: [PATCH] feat: add hash to field Signed-off-by: Michael Lodder --- elliptic-curve/Cargo.lock | 42 ++++++----- elliptic-curve/Cargo.toml | 2 + elliptic-curve/src/hash2field.rs | 33 +++++++++ elliptic-curve/src/hash2field/expand_msg.rs | 5 ++ .../src/hash2field/expand_msg_xmd.rs | 72 +++++++++++++++++++ .../src/hash2field/expand_msg_xof.rs | 27 +++++++ elliptic-curve/src/lib.rs | 6 ++ 7 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 elliptic-curve/src/hash2field.rs create mode 100644 elliptic-curve/src/hash2field/expand_msg.rs create mode 100644 elliptic-curve/src/hash2field/expand_msg_xmd.rs create mode 100644 elliptic-curve/src/hash2field/expand_msg_xof.rs diff --git a/elliptic-curve/Cargo.lock b/elliptic-curve/Cargo.lock index 9352ce4cd..812bece07 100644 --- a/elliptic-curve/Cargo.lock +++ b/elliptic-curve/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "base64ct" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "392c772b012d685a640cdad68a5a21f4a45e696f85a2c2c907aab2fe49a91e19" +checksum = "a30f9c631ae8b97868a2e841015b72f2c99b05e3f03a14d543b843816a0a5b4d" [[package]] name = "bitvec" @@ -28,9 +28,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "const-oid" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d355758f44afa81c21e66e1301d47cbffbbcde4b405cbe46b8b19f213abf9f60" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "crypto-bigint" @@ -54,6 +54,15 @@ dependencies = [ "pem-rfc7468", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "elliptic-curve" version = "0.11.6" @@ -61,6 +70,7 @@ dependencies = [ "base64ct", "crypto-bigint", "der", + "digest", "ff", "generic-array", "group", @@ -131,15 +141,15 @@ checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "libc" -version = "0.2.107" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "pem-rfc7468" @@ -178,9 +188,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "sec1" @@ -197,15 +207,15 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.130" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" [[package]] name = "serde_json" -version = "1.0.71" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" dependencies = [ "itoa", "ryu", @@ -214,9 +224,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8a277a21925310de1d31bb6b021da3550b00e9127096ef84ee38f44609925c4" +checksum = "964d3a6f8b7ef6d6d20887f4c30c4848f4ffa05f600c87277d30a5b4fe32cb4b" dependencies = [ "base64ct", "der", diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index 848b3e5d0..242ce3452 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -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 } @@ -47,6 +48,7 @@ arithmetic = ["ff", "group"] bits = ["arithmetic", "ff/bits"] dev = ["arithmetic", "hex-literal", "pem", "pkcs8"] ecdh = ["arithmetic"] +hashing = ["digest"] hazmat = [] jwk = ["alloc", "base64ct/alloc", "serde", "serde_json", "zeroize/alloc"] osswu = ["ff"] diff --git a/elliptic-curve/src/hash2field.rs b/elliptic-curve/src/hash2field.rs new file mode 100644 index 000000000..a68a06edc --- /dev/null +++ b/elliptic-curve/src/hash2field.rs @@ -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: Sized { + /// Convert a byte sequence into a scalar + fn from_okm(data: &[u8; L]) -> Self; +} + +/// Convert an arbitrary byte sequence according to +/// +pub fn hash_to_field( + data: &[u8], + domain: &[u8], +) -> [T; COUNT] +where + E: ExpandMsg, + T: FromOkm + 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 +} diff --git a/elliptic-curve/src/hash2field/expand_msg.rs b/elliptic-curve/src/hash2field/expand_msg.rs new file mode 100644 index 000000000..ba08f98f7 --- /dev/null +++ b/elliptic-curve/src/hash2field/expand_msg.rs @@ -0,0 +1,5 @@ +/// Trait for types implementing expand_message interface for hash_to_field +pub trait ExpandMsg { + /// Expands `msg` to the required number of bytes in `buf` + fn expand_message(msg: &[u8], dst: &[u8]) -> [u8; OUT]; +} diff --git a/elliptic-curve/src/hash2field/expand_msg_xmd.rs b/elliptic-curve/src/hash2field/expand_msg_xmd.rs new file mode 100644 index 000000000..e99f8b6e0 --- /dev/null +++ b/elliptic-curve/src/hash2field/expand_msg_xmd.rs @@ -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 { + phantom: PhantomData, +} + +/// ExpandMsgXmd implements expand_message_xmd for the ExpandMsg trait +impl ExpandMsg for ExpandMsgXmd +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::::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::::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 + } +} diff --git a/elliptic-curve/src/hash2field/expand_msg_xof.rs b/elliptic-curve/src/hash2field/expand_msg_xof.rs new file mode 100644 index 000000000..0ec1df024 --- /dev/null +++ b/elliptic-curve/src/hash2field/expand_msg_xof.rs @@ -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 { + phantom: PhantomData, +} + +/// ExpandMsgXof implements expand_message_xof for the ExpandMsg trait +impl ExpandMsg for ExpandMsgXof +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 + } +} diff --git a/elliptic-curve/src/lib.rs b/elliptic-curve/src/lib.rs index 2131cae74..211bf3ee5 100644 --- a/elliptic-curve/src/lib.rs +++ b/elliptic-curve/src/lib.rs @@ -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 +/// +#[cfg(feature = "hashing")] +#[cfg_attr(docsrs, doc(cfg(feature = "hashing")))] +pub mod hash2field; + pub use crate::{ error::{Error, Result}, point::{