halo2curves/
hash_to_curve.rs

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/// Map the homogeneous coordinates of a point from the isogenous curve to a
15/// point in the original curve.
16#[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        // Check for the target bits of  security `k`. Currently, the target security is
106        // 128 bits. See: <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#section-5.1>
107        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    // 1. c1 = g(Z)
166    let c1 = (z.square() + a) * z + b;
167    // 2. c2 = -Z / 2
168    let c2 = -z * C::Base::TWO_INV;
169    // 3. c3 = sqrt(-g(Z) * (3 * Z^2 + 4 * A))    # sgn0(c3) MUST equal 0
170    let c3 = {
171        let c3 = (-c1 * tmp).sqrt().unwrap();
172        C::Base::conditional_select(&c3, &-c3, c3.is_odd())
173    };
174    // 4. c4 = -4 * g(Z) / (3 * Z^2 + 4 * A)
175    let c4 = -four * c1 * tmp.invert().unwrap();
176
177    [c1, c2, c3, c4]
178}
179
180// Implementation of <https://datatracker.ietf.org/doc/html/rfc9380#name-simplified-swu-method>
181#[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    // Implement https://datatracker.ietf.org/doc/html/rfc9380#name-sqrt_ratio-for-any-field
192    // Copied from ff sqrt_ratio_generic substituting F::ROOT_OF_UNITY for input Z
193    fn sqrt_ratio<F: PrimeField>(num: &F, div: &F, z: &F) -> (Choice, F) {
194        // General implementation:
195        //
196        // a = num * inv0(div)
197        //   = {    0    if div is zero
198        //     { num/div otherwise
199        //
200        // b = z * a
201        //   = {      0      if div is zero
202        //     { z*num/div otherwise
203
204        // Since z is non-square, a and b are either both zero (and both square), or
205        // only one of them is square. We can therefore choose the square root to return
206        // based on whether a is square, but for the boolean output we need to handle
207        // the num != 0 && div == 0 case specifically.
208
209        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    // 1. tv1 = u^2
229    let tv1 = u.square();
230    // 2. tv1 = Z * tv1
231    let tv1 = z * tv1;
232    // 3. tv2 = tv1^2
233    let tv2 = tv1.square();
234    // 4. tv2 = tv2 + tv1
235    let tv2 = tv2 + tv1;
236    // 5. tv3 = tv2 + 1
237    let tv3 = tv2 + C::Base::ONE;
238    // 6. tv3 = B * tv3
239    let tv3 = b * tv3;
240    // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) # tv4 = z if tv2 is 0 else tv4 = -tv2
241    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    // 8. tv4 = A * tv4
244    let tv4 = a * tv4;
245    // 9. tv2 = tv3^2
246    let tv2 = tv3.square();
247    // 10. tv6 = tv4^2
248    let tv6 = tv4.square();
249    // 11. tv5 = A * tv6
250    let tv5 = a * tv6;
251    // 12. tv2 = tv2 + tv5
252    let tv2 = tv2 + tv5;
253    // 13. tv2 = tv2 * tv3
254    let tv2 = tv2 * tv3;
255    // 14. tv6 = tv6 * tv4
256    let tv6 = tv6 * tv4;
257    // 15. tv5 = B * tv6
258    let tv5 = b * tv6;
259    // 16. tv2 = tv2 + tv5
260    let tv2 = tv2 + tv5;
261    // 17. x = tv1 * tv3
262    let x = tv1 * tv3;
263    // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6)
264    let (is_gx1_square, y1) = sqrt_ratio(&tv2, &tv6, &z);
265    // 19. y = tv1 * u
266    let y = tv1 * u;
267    // 20. y = y * y1
268    let y = y * y1;
269    // 21. x = CMOV(x, tv3, is_gx1_square)
270    let x = C::Base::conditional_select(&x, &tv3, is_gx1_square);
271    // 22. y = CMOV(y, y1, is_gx1_square)
272    let y = C::Base::conditional_select(&y, &y1, is_gx1_square);
273    // 23. e1 = sgn0(u) == sgn0(y)
274    let e1 = u.is_odd().ct_eq(&y.is_odd());
275    // 24. y = CMOV(-y, y, e1) # Select correct sign of y
276    let y = C::Base::conditional_select(&-y, &y, e1);
277
278    // In the original algorithm:
279    // 25. x = x / tv4
280    // 26. return (x, y)
281    //
282    // we omit instruction 25. and return the result in homogeneous coordinates
283    // (instead of the previous jacobi).
284
285    (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    // 1. tv1 = u^2
306    let tv1 = u.square();
307    // 2. tv1 = tv1 * c1
308    let tv1 = tv1 * c1;
309    // 3. tv2 = 1 + tv1
310    let tv2 = one + tv1;
311    // 4. tv1 = 1 - tv1
312    let tv1 = one - tv1;
313    // 5. tv3 = tv1 * tv2
314    let tv3 = tv1 * tv2;
315    // 6. tv3 = inv0(tv3)
316    let tv3 = tv3.invert().unwrap_or(C::Base::ZERO);
317    // 7. tv4 = u * tv1
318    let tv4 = u * tv1;
319    // 8. tv4 = tv4 * tv3
320    let tv4 = tv4 * tv3;
321    // 9. tv4 = tv4 * c3
322    let tv4 = tv4 * c3;
323    // 10. x1 = c2 - tv4
324    let x1 = c2 - tv4;
325    // 11. gx1 = x1^2
326    let gx1 = x1.square();
327    // 12. gx1 = gx1 + A
328    let gx1 = gx1 + a;
329    // 13. gx1 = gx1 * x1
330    let gx1 = gx1 * x1;
331    // 14. gx1 = gx1 + B
332    let gx1 = gx1 + b;
333    // 15. e1 = is_square(gx1)
334    let e1 = !gx1.ct_quadratic_non_residue();
335    // 16. x2 = c2 + tv4
336    let x2 = c2 + tv4;
337    // 17. gx2 = x2^2
338    let gx2 = x2.square();
339    // 18. gx2 = gx2 + A
340    let gx2 = gx2 + a;
341    // 19. gx2 = gx2 * x2
342    let gx2 = gx2 * x2;
343    // 20. gx2 = gx2 + B
344    let gx2 = gx2 + b;
345    // 21. e2 = is_square(gx2) AND NOT e1    # Avoid short-circuit logic ops
346    let e2 = !gx2.ct_quadratic_non_residue() & (!e1);
347    // 22. x3 = tv2^2
348    let x3 = tv2.square();
349    // 23. x3 = x3 * tv3
350    let x3 = x3 * tv3;
351    // 24. x3 = x3^2
352    let x3 = x3.square();
353    // 25. x3 = x3 * c4
354    let x3 = x3 * c4;
355    // 26. x3 = x3 + Z
356    let x3 = x3 + z;
357    // 27. x = CMOV(x3, x1, e1)    # x = x1 if gx1 is square, else x = x3
358    let x = C::Base::conditional_select(&x3, &x1, e1);
359    // 28. x = CMOV(x, x2, e2)    # x = x2 if gx2 is square and gx1 is not
360    let x = C::Base::conditional_select(&x, &x2, e2);
361    // 29. gx = x^2
362    let gx = x.square();
363    // 30. gx = gx + A
364    let gx = gx + a;
365    // 31. gx = gx * x
366    let gx = gx * x;
367    // 32. gx = gx + B
368    let gx = gx + b;
369    // 33. y = sqrt(gx)
370    let y = gx.sqrt().unwrap();
371    // 34. e3 = sgn0(u) == sgn0(y)
372    let e3 = u.is_odd().ct_eq(&y.is_odd());
373    // 35. y = CMOV(-y, y, e3)    # Select correct sign of y
374    let y = C::Base::conditional_select(&-y, &y, e3);
375    // 36. return (x, y)
376    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        // Test vectors are taken from:
391        // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-expand_message_xmdsha-256
392
393        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            // out len 0x20
416            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            // out len 0x80
437            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            // out len 0x20
466            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            // out len 0x80
487            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}