Skip to content

Commit 0f797db

Browse files
committed
Add hash to scalar primitive
1 parent 924bc18 commit 0f797db

File tree

7 files changed

+104
-2
lines changed

7 files changed

+104
-2
lines changed

docs/hash_to_scalar.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Hashes the input and outputs scalar
2+
3+
Input can be any structured data that implements [`Digestable`](udigest::Digestable)
4+
trait (see [udigest] crate).
5+
6+
## How it works
7+
It works by instantiating [`HashRng`](rand_hash::HashRng) CSPRNG seeded from provided data.
8+
Then it's used to derive the scalar.
9+
10+
## Security considerations
11+
It's not constant time. It doesn't follow any existing standards for hash to scalar primitive.

generic-ec-zkp/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ keywords = ["elliptic-curves", "zk-proof"]
1313

1414
[dependencies]
1515
generic-ec = { version = "0.3", path = "../generic-ec", default-features = false }
16-
udigest = { version = "0.1", features = ["derive"], optional = true }
16+
udigest = { version = "0.2.0-rc1", features = ["derive"], optional = true }
1717

1818
subtle = { version = "2.4", default-features = false }
1919
rand_core = { version = "0.6", default-features = false }

generic-ec/Cargo.toml

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ keywords = ["elliptic-curves"]
1515
[dependencies]
1616
generic-ec-core = { version = "0.2", path = "../generic-ec-core" }
1717
generic-ec-curves = { version = "0.2", path = "../generic-ec-curves", optional = true }
18-
udigest = { version = "0.1", features = ["derive"], optional = true }
18+
udigest = { version = "0.2.0-rc1", features = ["derive"], optional = true }
1919

2020
subtle = { version = "2.4", default-features = false }
2121
rand_core = { version = "0.6", default-features = false }
@@ -27,13 +27,17 @@ hex = { version = "0.4", default-features = false, optional = true }
2727

2828
phantom-type = { version = "0.4", default-features = false }
2929

30+
digest = { version = "0.10", default-features = false, optional = true }
31+
rand_hash = { git = "https://github.com/dfns/rand_hash", branch = "first-version", optional = true }
32+
3033
# We use this dependency when both `curve-ed25519` and `alloc` features are enabled,
3134
# to provide `generic_ec::multiscalar::Dalek`
3235
curve25519-dalek = { version = "4", default-features = false, optional = true }
3336

3437
[dev-dependencies]
3538
rand = "0.8"
3639
rand_dev = "0.1"
40+
sha2 = "0.10"
3741
serde_json = "1"
3842
serde_test = "1"
3943

@@ -53,6 +57,8 @@ curve-stark = ["curves", "generic-ec-curves/stark"]
5357
curve-ed25519 = ["curves", "generic-ec-curves/ed25519", "curve25519-dalek"]
5458
all-curves = ["curve-secp256k1", "curve-secp256r1", "curve-stark", "curve-ed25519"]
5559

60+
hash-to-scalar = ["dep:rand_hash", "dep:digest", "udigest"]
61+
5662
[package.metadata.docs.rs]
5763
all-features = true
5864
rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"]

generic-ec/docs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../docs

generic-ec/src/non_zero/mod.rs

+28
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,34 @@ impl<E: Curve> NonZero<Scalar<E>> {
6161
}
6262
}
6363

64+
#[doc = include_str!("../../docs/hash_to_scalar.md")]
65+
///
66+
/// ## Example
67+
/// ```rust
68+
/// use generic_ec::{Scalar, NonZero, curves::Secp256k1};
69+
/// use sha2::Sha256;
70+
///
71+
/// #[derive(udigest::Digestable)]
72+
/// struct Data<'a> {
73+
/// nonce: &'a [u8],
74+
/// param_a: &'a str,
75+
/// param_b: u128,
76+
/// // ...
77+
/// }
78+
///
79+
/// let scalar = NonZero::<Scalar<Secp256k1>>::from_hash::<Sha256>(&Data {
80+
/// nonce: b"some data",
81+
/// param_a: "some other data",
82+
/// param_b: 12345,
83+
/// // ...
84+
/// });
85+
/// ```
86+
#[cfg(feature = "hash-to-scalar")]
87+
pub fn from_hash<D: digest::Digest>(data: &impl udigest::Digestable) -> Self {
88+
let mut rng = rand_hash::HashRng::<D, _>::from_seed(data);
89+
Self::random(&mut rng)
90+
}
91+
6492
/// Constructs $S = 1$
6593
pub fn one() -> Self {
6694
// Correctness: constructed scalar = 1, so it's non-zero

generic-ec/src/scalar.rs

+28
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,34 @@ impl<E: Curve> Scalar<E> {
170170
NonZero::<Scalar<E>>::random(rng).into()
171171
}
172172

173+
#[doc = include_str!("../docs/hash_to_scalar.md")]
174+
///
175+
/// ## Example
176+
/// ```rust
177+
/// use generic_ec::{Scalar, curves::Secp256k1};
178+
/// use sha2::Sha256;
179+
///
180+
/// #[derive(udigest::Digestable)]
181+
/// struct Data<'a> {
182+
/// nonce: &'a [u8],
183+
/// param_a: &'a str,
184+
/// param_b: u128,
185+
/// // ...
186+
/// }
187+
///
188+
/// let scalar = Scalar::<Secp256k1>::from_hash::<Sha256>(&Data {
189+
/// nonce: b"some data",
190+
/// param_a: "some other data",
191+
/// param_b: 12345,
192+
/// // ...
193+
/// });
194+
/// ```
195+
#[cfg(feature = "hash-to-scalar")]
196+
pub fn from_hash<D: digest::Digest>(data: &impl udigest::Digestable) -> Self {
197+
let mut rng = rand_hash::HashRng::<D, _>::from_seed(data);
198+
Self::random(&mut rng)
199+
}
200+
173201
/// Returns size of bytes buffer that can fit serialized scalar
174202
pub fn serialized_len() -> usize {
175203
E::ScalarArray::zeroes().as_ref().len()

generic-ec/src/secret_scalar/mod.rs

+28
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,34 @@ impl<E: Curve> SecretScalar<E> {
3333
Self::new(&mut scalar)
3434
}
3535

36+
#[doc = include_str!("../../docs/hash_to_scalar.md")]
37+
///
38+
/// ## Example
39+
/// ```rust
40+
/// use generic_ec::{SecretScalar, curves::Secp256k1};
41+
/// use sha2::Sha256;
42+
///
43+
/// #[derive(udigest::Digestable)]
44+
/// struct Data<'a> {
45+
/// nonce: &'a [u8],
46+
/// param_a: &'a str,
47+
/// param_b: u128,
48+
/// // ...
49+
/// }
50+
///
51+
/// let scalar = SecretScalar::<Secp256k1>::from_hash::<Sha256>(&Data {
52+
/// nonce: b"some data",
53+
/// param_a: "some other data",
54+
/// param_b: 12345,
55+
/// // ...
56+
/// });
57+
/// ```
58+
#[cfg(feature = "hash-to-scalar")]
59+
pub fn from_hash<D: digest::Digest>(data: &impl udigest::Digestable) -> Self {
60+
let mut rng = rand_hash::HashRng::<D, _>::from_seed(data);
61+
Self::random(&mut rng)
62+
}
63+
3664
/// Decodes scalar from its bytes representation in big-endian order
3765
pub fn from_be_bytes(bytes: &[u8]) -> Result<Self, InvalidScalar> {
3866
let mut scalar = Scalar::from_be_bytes(bytes)?;

0 commit comments

Comments
 (0)