1#![allow(clippy::op_ref)]
2
3use digest::{core_api::BlockSizeUser, Digest};
4use ff::{Field, FromUniformBytes, PrimeField};
5use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
6
7use crate::{ff_ext::Legendre, CurveExt};
8
9pub enum Method<C: CurveExt> {
10 SSWU(Iso<C>),
11 SVDW,
12}
13
14#[allow(clippy::type_complexity)]
17pub struct Iso<C: CurveExt> {
18 pub(crate) a: C::Base,
19 pub(crate) b: C::Base,
20 pub(crate) map: Box<dyn Fn(C::Base, C::Base, C::Base) -> C>,
21}
22
23pub struct Suite<C: CurveExt, D: Digest + BlockSizeUser, const L: usize> {
24 domain: Vec<u8>,
25 map_to_curve: Box<dyn Fn(C::Base) -> C>,
26 _marker: std::marker::PhantomData<D>,
27}
28
29pub(crate) fn expand_message<D: Digest + BlockSizeUser>(
30 domain_prefix: &[u8],
31 domain: &[u8],
32 message: &[u8],
33 out_len: usize,
34) -> Vec<u8> {
35 assert!(
36 domain_prefix.len() + domain.len() < 256,
37 "long dst is not supported yet"
38 );
39
40 let mut h = D::new();
41 h.update(vec![0; D::block_size()]);
42 h.update(message);
43 h.update([(out_len >> 8) as u8, out_len as u8, 0]);
44 h.update(domain_prefix);
45 h.update(domain);
46 h.update([(domain.len() + domain_prefix.len()) as u8]);
47 let b_0 = h.finalize();
48
49 let mut h = D::new();
50 h.update(&b_0);
51 h.update([1]);
52 h.update(domain_prefix);
53 h.update(domain);
54 h.update([(domain.len() + domain_prefix.len()) as u8]);
55 let mut b_i = h.finalize();
56
57 let output_size = <D as Digest>::output_size();
58 let ell = (out_len + output_size - 1) / output_size;
59 let mut out = vec![0u8; out_len];
60
61 for i in 1..ell {
62 let mut h = D::new();
63 b_0.iter()
64 .zip(b_i.iter())
65 .for_each(|(b_0, b_i)| h.update([*b_0 ^ *b_i]));
66 h.update([1 + i as u8]);
67 h.update(domain_prefix);
68 h.update(domain);
69 h.update([(domain.len() + domain_prefix.len()) as u8]);
70
71 out.iter_mut()
72 .skip((i - 1) * output_size)
73 .zip(b_i.iter())
74 .for_each(|(out, b_i)| *out = *b_i);
75
76 b_i = h.finalize();
77 }
78
79 out.iter_mut()
80 .skip((ell - 1) * output_size)
81 .zip(b_i.iter())
82 .for_each(|(out, b_i)| *out = *b_i);
83
84 out
85}
86
87#[allow(clippy::type_complexity)]
88pub fn hash_to_curve<'a, C, D: Digest + BlockSizeUser + 'a, const L: usize>(
89 domain_prefix: &'a str,
90 suite: Suite<C, D, L>,
91) -> Box<dyn Fn(&[u8]) -> C + 'a>
92where
93 C: CurveExt,
94 C::Base: Legendre,
95 C::Base: FromUniformBytes<L>,
96{
97 Box::new(move |message| suite.hash_to_curve(domain_prefix, message))
98}
99
100impl<C: CurveExt, D: Digest + BlockSizeUser, const L: usize> Suite<C, D, L>
101where
102 C::Base: Legendre + FromUniformBytes<L>,
103{
104 pub(crate) fn new(domain: &[u8], z: C::Base, method: Method<C>) -> Self {
105 assert!((C::Base::NUM_BITS as usize + 128) / 8 <= L);
108
109 let map_to_curve: Box<dyn Fn(C::Base) -> C> = match method {
110 Method::SSWU(iso) => {
111 let Iso { a, b, map } = iso;
112 Box::new(move |u| {
113 let (x, y, z) = sswu_map_to_curve::<C>(u, z, a, b);
114 map(x, y, z)
115 })
116 }
117
118 Method::SVDW => {
119 let [c1, c2, c3, c4] = svdw_precomputed_constants::<C>(z);
120 Box::new(move |u| svdw_map_to_curve::<C>(u, c1, c2, c3, c4, z))
121 }
122 };
123
124 Self {
125 map_to_curve,
126 domain: domain.to_vec(),
127 _marker: std::marker::PhantomData,
128 }
129 }
130
131 pub(crate) fn hash_to_field(&self, domain_prefix: &[u8], message: &[u8]) -> (C::Base, C::Base) {
132 let out = expand_message::<D>(domain_prefix, &self.domain[..], message, L * 2);
133
134 let u0 = {
135 let mut out = out[0..L].to_vec();
136 out.reverse();
137 let out: [u8; L] = out.try_into().unwrap();
138 C::Base::from_uniform_bytes(&out)
139 };
140
141 let u1 = {
142 let mut out = out[L..L * 2].to_vec();
143 out.reverse();
144 let out: [u8; L] = out.try_into().unwrap();
145 C::Base::from_uniform_bytes(&out)
146 };
147
148 (u0, u1)
149 }
150
151 pub fn hash_to_curve(&self, domain_prefix: &str, message: &[u8]) -> C {
152 let (u0, u1) = self.hash_to_field(domain_prefix.as_bytes(), message);
153 (self.map_to_curve)(u0) + (self.map_to_curve)(u1)
154 }
155}
156
157pub(crate) fn svdw_precomputed_constants<C: CurveExt>(z: C::Base) -> [C::Base; 4] {
158 let a = C::a();
159 let b = C::b();
160 let one = C::Base::ONE;
161 let three = one + one + one;
162 let four = three + one;
163 let tmp = three * z.square() + four * a;
164
165 let c1 = (z.square() + a) * z + b;
167 let c2 = -z * C::Base::TWO_INV;
169 let c3 = {
171 let c3 = (-c1 * tmp).sqrt().unwrap();
172 C::Base::conditional_select(&c3, &-c3, c3.is_odd())
173 };
174 let c4 = -four * c1 * tmp.invert().unwrap();
176
177 [c1, c2, c3, c4]
178}
179
180#[allow(clippy::too_many_arguments)]
182pub(crate) fn sswu_map_to_curve<C>(
183 u: C::Base,
184 z: C::Base,
185 a: C::Base,
186 b: C::Base,
187) -> (C::Base, C::Base, C::Base)
188where
189 C: CurveExt,
190{
191 fn sqrt_ratio<F: PrimeField>(num: &F, div: &F, z: &F) -> (Choice, F) {
194 let a = div.invert().unwrap_or(F::ZERO) * num;
210 let b = a * z;
211 let sqrt_a = a.sqrt();
212 let sqrt_b = b.sqrt();
213
214 let num_is_zero = num.is_zero();
215 let div_is_zero = div.is_zero();
216 let is_square = sqrt_a.is_some();
217 let is_nonsquare = sqrt_b.is_some();
218 assert!(bool::from(
219 num_is_zero | div_is_zero | (is_square ^ is_nonsquare)
220 ));
221
222 (
223 is_square & (num_is_zero | !div_is_zero),
224 CtOption::conditional_select(&sqrt_b, &sqrt_a, is_square).unwrap(),
225 )
226 }
227
228 let tv1 = u.square();
230 let tv1 = z * tv1;
232 let tv2 = tv1.square();
234 let tv2 = tv2 + tv1;
236 let tv3 = tv2 + C::Base::ONE;
238 let tv3 = b * tv3;
240 let tv2_is_not_zero = !tv2.ct_eq(&C::Base::ZERO);
242 let tv4 = C::Base::conditional_select(&z, &-tv2, tv2_is_not_zero);
243 let tv4 = a * tv4;
245 let tv2 = tv3.square();
247 let tv6 = tv4.square();
249 let tv5 = a * tv6;
251 let tv2 = tv2 + tv5;
253 let tv2 = tv2 * tv3;
255 let tv6 = tv6 * tv4;
257 let tv5 = b * tv6;
259 let tv2 = tv2 + tv5;
261 let x = tv1 * tv3;
263 let (is_gx1_square, y1) = sqrt_ratio(&tv2, &tv6, &z);
265 let y = tv1 * u;
267 let y = y * y1;
269 let x = C::Base::conditional_select(&x, &tv3, is_gx1_square);
271 let y = C::Base::conditional_select(&y, &y1, is_gx1_square);
273 let e1 = u.is_odd().ct_eq(&y.is_odd());
275 let y = C::Base::conditional_select(&-y, &y, e1);
277
278 (x, y * tv4, tv4)
286}
287
288#[allow(clippy::too_many_arguments)]
289pub(crate) fn svdw_map_to_curve<C>(
290 u: C::Base,
291 c1: C::Base,
292 c2: C::Base,
293 c3: C::Base,
294 c4: C::Base,
295 z: C::Base,
296) -> C
297where
298 C: CurveExt,
299 C::Base: Legendre,
300{
301 let one = C::Base::ONE;
302 let a = C::a();
303 let b = C::b();
304
305 let tv1 = u.square();
307 let tv1 = tv1 * c1;
309 let tv2 = one + tv1;
311 let tv1 = one - tv1;
313 let tv3 = tv1 * tv2;
315 let tv3 = tv3.invert().unwrap_or(C::Base::ZERO);
317 let tv4 = u * tv1;
319 let tv4 = tv4 * tv3;
321 let tv4 = tv4 * c3;
323 let x1 = c2 - tv4;
325 let gx1 = x1.square();
327 let gx1 = gx1 + a;
329 let gx1 = gx1 * x1;
331 let gx1 = gx1 + b;
333 let e1 = !gx1.ct_quadratic_non_residue();
335 let x2 = c2 + tv4;
337 let gx2 = x2.square();
339 let gx2 = gx2 + a;
341 let gx2 = gx2 * x2;
343 let gx2 = gx2 + b;
345 let e2 = !gx2.ct_quadratic_non_residue() & (!e1);
347 let x3 = tv2.square();
349 let x3 = x3 * tv3;
351 let x3 = x3.square();
353 let x3 = x3 * c4;
355 let x3 = x3 + z;
357 let x = C::Base::conditional_select(&x3, &x1, e1);
359 let x = C::Base::conditional_select(&x, &x2, e2);
361 let gx = x.square();
363 let gx = gx + a;
365 let gx = gx * x;
367 let gx = gx + b;
369 let y = gx.sqrt().unwrap();
371 let e3 = u.is_odd().ct_eq(&y.is_odd());
373 let y = C::Base::conditional_select(&-y, &y, e3);
375 C::new_jacobian(x, y, one).unwrap()
377}
378
379#[cfg(test)]
380mod test {
381
382 use std::marker::PhantomData;
383
384 use sha2::{Sha256, Sha512};
385
386 use super::*;
387
388 #[test]
389 fn test_expand_message() {
390 struct Test<D: Digest + BlockSizeUser> {
394 msg: &'static [u8],
395 expect: Vec<u8>,
396 _marker: PhantomData<D>,
397 }
398
399 impl<D: Digest + BlockSizeUser> Test<D> {
400 fn new(msg: &'static [u8], expect: &str) -> Self {
401 Self {
402 msg,
403 expect: crate::tests::hex_to_bytes(expect),
404 _marker: PhantomData,
405 }
406 }
407
408 fn run(&self, domain_prefix: &[u8], domain: &[u8]) {
409 let outlen = self.expect.len();
410 let out = expand_message::<D>(domain_prefix, domain, self.msg, outlen);
411 assert_eq!(out, self.expect);
412 }
413 }
414 [
415 Test::<Sha256>::new(
417 b"",
418 "68a985b87eb6b46952128911f2a4412bbc302a9d759667f87f7a21d803f07235",
419 ),
420 Test::<Sha256>::new(
421 b"abc",
422 "d8ccab23b5985ccea865c6c97b6e5b8350e794e603b4b97902f53a8a0d605615",
423 ),
424 Test::<Sha256>::new(
425 b"abcdef0123456789",
426 "eff31487c770a893cfb36f912fbfcbff40d5661771ca4b2cb4eafe524333f5c1",
427 ),
428 Test::<Sha256>::new(
429 b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
430 "b23a1d2b4d97b2ef7785562a7e8bac7eed54ed6e97e29aa51bfe3f12ddad1ff9",
431 ),
432 Test::<Sha256>::new(
433 b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
434 "4623227bcc01293b8c130bf771da8c298dede7383243dc0993d2d94823958c4c",
435 ),
436 Test::<Sha256>::new(
438 b"",
439 "af84c27ccfd45d41914fdff5df25293e221afc53d8ad2ac06d5e3e29485dadbee0d121587713a3e0dd4d5e69e93eb7cd4f5df4cd103e188cf60cb02edc3edf18eda8576c412b18ffb658e3dd6ec849469b979d444cf7b26911a08e63cf31f9dcc541708d3491184472c2c29bb749d4286b004ceb5ee6b9a7fa5b646c993f0ced",
440 ),
441 Test::<Sha256>::new(
442 b"abc",
443 "abba86a6129e366fc877aab32fc4ffc70120d8996c88aee2fe4b32d6c7b6437a647e6c3163d40b76a73cf6a5674ef1d890f95b664ee0afa5359a5c4e07985635bbecbac65d747d3d2da7ec2b8221b17b0ca9dc8a1ac1c07ea6a1e60583e2cb00058e77b7b72a298425cd1b941ad4ec65e8afc50303a22c0f99b0509b4c895f40",
444 ),
445 Test::<Sha256>::new(
446 b"abcdef0123456789",
447 "ef904a29bffc4cf9ee82832451c946ac3c8f8058ae97d8d629831a74c6572bd9ebd0df635cd1f208e2038e760c4994984ce73f0d55ea9f22af83ba4734569d4bc95e18350f740c07eef653cbb9f87910d833751825f0ebefa1abe5420bb52be14cf489b37fe1a72f7de2d10be453b2c9d9eb20c7e3f6edc5a60629178d9478df",
448 ),
449 Test::<Sha256>::new(
450 b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
451 "80be107d0884f0d881bb460322f0443d38bd222db8bd0b0a5312a6fedb49c1bbd88fd75d8b9a09486c60123dfa1d73c1cc3169761b17476d3c6b7cbbd727acd0e2c942f4dd96ae3da5de368d26b32286e32de7e5a8cb2949f866a0b80c58116b29fa7fabb3ea7d520ee603e0c25bcaf0b9a5e92ec6a1fe4e0391d1cdbce8c68a",
452 ),
453 Test::<Sha256>::new(
454 b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
455 "546aff5444b5b79aa6148bd81728704c32decb73a3ba76e9e75885cad9def1d06d6792f8a7d12794e90efed817d96920d728896a4510864370c207f99bd4a608ea121700ef01ed879745ee3e4ceef777eda6d9e5e38b90c86ea6fb0b36504ba4a45d22e86f6db5dd43d98a294bebb9125d5b794e9d2a81181066eb954966a487",
456 ),
457
458 ]
459 .iter()
460 .for_each(|test| {
461 test.run(b"QUUX-V01-CS02-with-expander-",b"SHA256-128");
462 });
463
464 [
465 Test::<Sha512>::new(
467 b"",
468 "6b9a7312411d92f921c6f68ca0b6380730a1a4d982c507211a90964c394179ba",
469 ),
470 Test::<Sha512>::new(
471 b"abc",
472 "0da749f12fbe5483eb066a5f595055679b976e93abe9be6f0f6318bce7aca8dc",
473 ),
474 Test::<Sha512>::new(
475 b"abcdef0123456789",
476 "087e45a86e2939ee8b91100af1583c4938e0f5fc6c9db4b107b83346bc967f58",
477 ),
478 Test::<Sha512>::new(
479 b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
480 "7336234ee9983902440f6bc35b348352013becd88938d2afec44311caf8356b3",
481 ),
482 Test::<Sha512>::new(
483 b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
484 "57b5f7e766d5be68a6bfe1768e3c2b7f1228b3e4b3134956dd73a59b954c66f4",
485 ),
486 Test::<Sha512>::new(
488 b"",
489 "41b037d1734a5f8df225dd8c7de38f851efdb45c372887be655212d07251b921b052b62eaed99b46f72f2ef4cc96bfaf254ebbbec091e1a3b9e4fb5e5b619d2e0c5414800a1d882b62bb5cd1778f098b8eb6cb399d5d9d18f5d5842cf5d13d7eb00a7cff859b605da678b318bd0e65ebff70bec88c753b159a805d2c89c55961",
490 ),
491 Test::<Sha512>::new(
492 b"abc",
493 "7f1dddd13c08b543f2e2037b14cefb255b44c83cc397c1786d975653e36a6b11bdd7732d8b38adb4a0edc26a0cef4bb45217135456e58fbca1703cd6032cb1347ee720b87972d63fbf232587043ed2901bce7f22610c0419751c065922b488431851041310ad659e4b23520e1772ab29dcdeb2002222a363f0c2b1c972b3efe1",
494 ),
495 Test::<Sha512>::new(
496 b"abcdef0123456789",
497 "3f721f208e6199fe903545abc26c837ce59ac6fa45733f1baaf0222f8b7acb0424814fcb5eecf6c1d38f06e9d0a6ccfbf85ae612ab8735dfdf9ce84c372a77c8f9e1c1e952c3a61b7567dd0693016af51d2745822663d0c2367e3f4f0bed827feecc2aaf98c949b5ed0d35c3f1023d64ad1407924288d366ea159f46287e61ac",
498 ),
499 Test::<Sha512>::new(
500 b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
501 "b799b045a58c8d2b4334cf54b78260b45eec544f9f2fb5bd12fb603eaee70db7317bf807c406e26373922b7b8920fa29142703dd52bdf280084fb7ef69da78afdf80b3586395b433dc66cde048a258e476a561e9deba7060af40adf30c64249ca7ddea79806ee5beb9a1422949471d267b21bc88e688e4014087a0b592b695ed",
502 ),
503 Test::<Sha512>::new(
504 b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
505 "05b0bfef265dcee87654372777b7c44177e2ae4c13a27f103340d9cd11c86cb2426ffcad5bd964080c2aee97f03be1ca18e30a1f14e27bc11ebbd650f305269cc9fb1db08bf90bfc79b42a952b46daf810359e7bc36452684784a64952c343c52e5124cd1f71d474d5197fefc571a92929c9084ffe1112cf5eea5192ebff330b",
506 ),
507 ]
508 .iter()
509 .for_each(|test| {
510 test.run(b"QUUX-V01-CS02-with-expander-", b"SHA512-256");
511 });
512 }
513}