p521/arithmetic/
hash2curve.rs

1use super::FieldElement;
2use crate::{AffinePoint, NistP521, ProjectivePoint, Scalar};
3use elliptic_curve::{
4    bigint::{ArrayEncoding, U576},
5    consts::U98,
6    generic_array::GenericArray,
7    hash2curve::{FromOkm, GroupDigest, MapToCurve, OsswuMap, OsswuMapParams, Sgn0},
8    ops::Reduce,
9    point::DecompressPoint,
10    subtle::Choice,
11};
12
13impl GroupDigest for NistP521 {
14    type FieldElement = FieldElement;
15}
16
17impl FromOkm for FieldElement {
18    type Length = U98;
19
20    fn from_okm(data: &GenericArray<u8, Self::Length>) -> Self {
21        const F_2_392: FieldElement = FieldElement::from_hex(
22            "000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
23        );
24
25        let mut d0 = GenericArray::default();
26        d0[23..].copy_from_slice(&data[0..49]);
27        let d0 = FieldElement::from_uint_unchecked(U576::from_be_byte_array(d0));
28
29        let mut d1 = GenericArray::default();
30        d1[23..].copy_from_slice(&data[49..]);
31        let d1 = FieldElement::from_uint_unchecked(U576::from_be_byte_array(d1));
32
33        d0 * F_2_392 + d1
34    }
35}
36
37impl Sgn0 for FieldElement {
38    fn sgn0(&self) -> Choice {
39        self.is_odd()
40    }
41}
42
43impl OsswuMap for FieldElement {
44    const PARAMS: OsswuMapParams<Self> = OsswuMapParams {
45        c1: &[
46            0xffff_ffff_ffff_ffff,
47            0xffff_ffff_ffff_ffff,
48            0xffff_ffff_ffff_ffff,
49            0xffff_ffff_ffff_ffff,
50            0xffff_ffff_ffff_ffff,
51            0xffff_ffff_ffff_ffff,
52            0xffff_ffff_ffff_ffff,
53            0xffff_ffff_ffff_ffff,
54            0x0000_0000_0000_007f,
55        ],
56        c2: FieldElement::from_hex(
57            "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002",
58        ),
59        map_a: FieldElement::from_u64(3).neg(),
60        map_b: FieldElement::from_hex(
61            "0000000000000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00",
62        ),
63        z: FieldElement::from_u64(4).neg(),
64    };
65}
66
67impl MapToCurve for FieldElement {
68    type Output = ProjectivePoint;
69
70    fn map_to_curve(&self) -> Self::Output {
71        let (qx, qy) = self.osswu();
72
73        // TODO(tarcieri): assert that `qy` is correct? less circuitous conversion?
74        AffinePoint::decompress(&qx.to_bytes(), qy.is_odd())
75            .unwrap()
76            .into()
77    }
78}
79
80impl FromOkm for Scalar {
81    type Length = U98;
82
83    fn from_okm(data: &GenericArray<u8, Self::Length>) -> Self {
84        const F_2_392: Scalar = Scalar::from_hex(
85            "000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
86        );
87
88        let mut d0 = GenericArray::default();
89        d0[23..].copy_from_slice(&data[0..49]);
90        let d0 = Scalar::reduce(U576::from_be_byte_array(d0));
91
92        let mut d1 = GenericArray::default();
93        d1[23..].copy_from_slice(&data[49..]);
94        let d1 = Scalar::reduce(U576::from_be_byte_array(d1));
95
96        d0 * F_2_392 + d1
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use crate::{
103        arithmetic::field::{FieldElement, MODULUS},
104        NistP521, Scalar,
105    };
106    use elliptic_curve::{
107        bigint::{ArrayEncoding, CheckedSub, NonZero, U576, U896},
108        consts::U98,
109        generic_array::GenericArray,
110        group::cofactor::CofactorGroup,
111        hash2curve::{self, ExpandMsgXmd, FromOkm, GroupDigest, MapToCurve, OsswuMap},
112        ops::Reduce,
113        sec1::{self, ToEncodedPoint},
114        Curve,
115    };
116    use hex_literal::hex;
117    use proptest::{num, prelude::ProptestConfig, proptest};
118    use sha2::Sha512;
119
120    #[test]
121    fn params() {
122        let params = <FieldElement as OsswuMap>::PARAMS;
123
124        let c1 = MODULUS.checked_sub(&U576::from_u8(3)).unwrap()
125            / NonZero::new(U576::from_u8(4)).unwrap();
126        assert_eq!(
127            GenericArray::from_iter(params.c1.iter().rev().flat_map(|v| v.to_be_bytes())),
128            c1.to_be_byte_array()
129        );
130
131        let c2 = FieldElement::from_u64(4).sqrt().unwrap();
132        assert_eq!(params.c2, c2);
133    }
134
135    #[test]
136    fn hash_to_curve() {
137        struct TestVector {
138            msg: &'static [u8],
139            p_x: [u8; 66],
140            p_y: [u8; 66],
141            u_0: [u8; 66],
142            u_1: [u8; 66],
143            q0_x: [u8; 66],
144            q0_y: [u8; 66],
145            q1_x: [u8; 66],
146            q1_y: [u8; 66],
147        }
148
149        const DST: &[u8] = b"QUUX-V01-CS02-with-P521_XMD:SHA-512_SSWU_RO_";
150
151        const TEST_VECTORS: &[TestVector] = &[
152            TestVector {
153                msg: b"",
154                p_x: hex!("00fd767cebb2452030358d0e9cf907f525f50920c8f607889a6a35680727f64f4d66b161fafeb2654bea0d35086bec0a10b30b14adef3556ed9f7f1bc23cecc9c088"),
155                p_y: hex!("0169ba78d8d851e930680322596e39c78f4fe31b97e57629ef6460ddd68f8763fd7bd767a4e94a80d3d21a3c2ee98347e024fc73ee1c27166dc3fe5eeef782be411d"),
156                u_0: hex!("01e5f09974e5724f25286763f00ce76238c7a6e03dc396600350ee2c4135fb17dc555be99a4a4bae0fd303d4f66d984ed7b6a3ba386093752a855d26d559d69e7e9e"),
157                u_1: hex!("00ae593b42ca2ef93ac488e9e09a5fe5a2f6fb330d18913734ff602f2a761fcaaf5f596e790bcc572c9140ec03f6cccc38f767f1c1975a0b4d70b392d95a0c7278aa"),
158                q0_x: hex!("00b70ae99b6339fffac19cb9bfde2098b84f75e50ac1e80d6acb954e4534af5f0e9c4a5b8a9c10317b8e6421574bae2b133b4f2b8c6ce4b3063da1d91d34fa2b3a3c"),
159                q0_y: hex!("007f368d98a4ddbf381fb354de40e44b19e43bb11a1278759f4ea7b485e1b6db33e750507c071250e3e443c1aaed61f2c28541bb54b1b456843eda1eb15ec2a9b36e"),
160                q1_x: hex!("01143d0e9cddcdacd6a9aafe1bcf8d218c0afc45d4451239e821f5d2a56df92be942660b532b2aa59a9c635ae6b30e803c45a6ac871432452e685d661cd41cf67214"),
161                q1_y: hex!("00ff75515df265e996d702a5380defffab1a6d2bc232234c7bcffa433cd8aa791fbc8dcf667f08818bffa739ae25773b32073213cae9a0f2a917a0b1301a242dda0c"),
162            },
163            TestVector {
164                msg: b"abc",
165                p_x: hex!("002f89a1677b28054b50d15e1f81ed6669b5a2158211118ebdef8a6efc77f8ccaa528f698214e4340155abc1fa08f8f613ef14a043717503d57e267d57155cf784a4"),
166                p_y: hex!("010e0be5dc8e753da8ce51091908b72396d3deed14ae166f66d8ebf0a4e7059ead169ea4bead0232e9b700dd380b316e9361cfdba55a08c73545563a80966ecbb86d"),
167                u_0: hex!("003d00c37e95f19f358adeeaa47288ec39998039c3256e13c2a4c00a7cb61a34c8969472960150a27276f2390eb5e53e47ab193351c2d2d9f164a85c6a5696d94fe8"),
168                u_1: hex!("01f3cbd3df3893a45a2f1fecdac4d525eb16f345b03e2820d69bc580f5cbe9cb89196fdf720ef933c4c0361fcfe29940fd0db0a5da6bafb0bee8876b589c41365f15"),
169                q0_x: hex!("01b254e1c99c835836f0aceebba7d77750c48366ecb07fb658e4f5b76e229ae6ca5d271bb0006ffcc42324e15a6d3daae587f9049de2dbb0494378ffb60279406f56"),
170                q0_y: hex!("01845f4af72fc2b1a5a2fe966f6a97298614288b456cfc385a425b686048b25c952fbb5674057e1eb055d04568c0679a8e2dda3158dc16ac598dbb1d006f5ad915b0"),
171                q1_x: hex!("007f08e813c620e527c961b717ffc74aac7afccb9158cebc347d5715d5c2214f952c97e194f11d114d80d3481ed766ac0a3dba3eb73f6ff9ccb9304ad10bbd7b4a36"),
172                q1_y: hex!("0022468f92041f9970a7cc025d71d5b647f822784d29ca7b3bc3b0829d6bb8581e745f8d0cc9dc6279d0450e779ac2275c4c3608064ad6779108a7828ebd9954caeb"),
173            },
174            TestVector {
175                msg: b"abcdef0123456789",
176                p_x: hex!("006e200e276a4a81760099677814d7f8794a4a5f3658442de63c18d2244dcc957c645e94cb0754f95fcf103b2aeaf94411847c24187b89fb7462ad3679066337cbc4"),
177                p_y: hex!("001dd8dfa9775b60b1614f6f169089d8140d4b3e4012949b52f98db2deff3e1d97bf73a1fa4d437d1dcdf39b6360cc518d8ebcc0f899018206fded7617b654f6b168"),
178                u_0: hex!("00183ee1a9bbdc37181b09ec336bcaa34095f91ef14b66b1485c166720523dfb81d5c470d44afcb52a87b704dbc5c9bc9d0ef524dec29884a4795f55c1359945baf3"),
179                u_1: hex!("00504064fd137f06c81a7cf0f84aa7e92b6b3d56c2368f0a08f44776aa8930480da1582d01d7f52df31dca35ee0a7876500ece3d8fe0293cd285f790c9881c998d5e"),
180                q0_x: hex!("0021482e8622aac14da60e656043f79a6a110cbae5012268a62dd6a152c41594549f373910ebed170ade892dd5a19f5d687fae7095a461d583f8c4295f7aaf8cd7da"),
181                q0_y: hex!("0177e2d8c6356b7de06e0b5712d8387d529b848748e54a8bc0ef5f1475aa569f8f492fa85c3ad1c5edc51faf7911f11359bfa2a12d2ef0bd73df9cb5abd1b101c8b1"),
182                q1_x: hex!("00abeafb16fdbb5eb95095678d5a65c1f293291dfd20a3751dbe05d0a9bfe2d2eef19449fe59ec32cdd4a4adc3411177c0f2dffd0159438706159a1bbd0567d9b3d0"),
183                q1_y: hex!("007cc657f847db9db651d91c801741060d63dab4056d0a1d3524e2eb0e819954d8f677aa353bd056244a88f00017e00c3ce8beeedb4382d83d74418bd48930c6c182"),
184            },
185            TestVector {
186                msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
187                p_x: hex!("01b264a630bd6555be537b000b99a06761a9325c53322b65bdc41bf196711f9708d58d34b3b90faf12640c27b91c70a507998e55940648caa8e71098bf2bc8d24664"),
188                p_y: hex!("01ea9f445bee198b3ee4c812dcf7b0f91e0881f0251aab272a12201fd89b1a95733fd2a699c162b639e9acdcc54fdc2f6536129b6beb0432be01aa8da02df5e59aaa"),
189                u_0: hex!("0159871e222689aad7694dc4c3480a49807b1eedd9c8cb4ae1b219d5ba51655ea5b38e2e4f56b36bf3e3da44a7b139849d28f598c816fe1bc7ed15893b22f63363c3"),
190                u_1: hex!("004ef0cffd475152f3858c0a8ccbdf7902d8261da92744e98df9b7fadb0a5502f29c5086e76e2cf498f47321434a40b1504911552ce44ad7356a04e08729ad9411f5"),
191                q0_x: hex!("0005eac7b0b81e38727efcab1e375f6779aea949c3e409b53a1d37aa2acbac87a7e6ad24aafbf3c52f82f7f0e21b872e88c55e17b7fa21ce08a94ea2121c42c2eb73"),
192                q0_y: hex!("00a173b6a53a7420dbd61d4a21a7c0a52de7a5c6ce05f31403bef747d16cc8604a039a73bdd6e114340e55dacd6bea8e217ffbadfb8c292afa3e1b2afc839a6ce7bb"),
193                q1_x: hex!("01881e3c193a69e4d88d8180a6879b74782a0bc7e529233e9f84bf7f17d2f319c36920ffba26f9e57a1e045cc7822c834c239593b6e142a694aa00c757b0db79e5e8"),
194                q1_y: hex!("01558b16d396d866e476e001f2dd0758927655450b84e12f154032c7c2a6db837942cd9f44b814f79b4d729996ced61eec61d85c675139cbffe3fbf071d2c21cfecb"),
195            },
196            TestVector {
197                msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
198                p_x: hex!("00c12bc3e28db07b6b4d2a2b1167ab9e26fc2fa85c7b0498a17b0347edf52392856d7e28b8fa7a2dd004611159505835b687ecf1a764857e27e9745848c436ef3925"),
199                p_y: hex!("01cd287df9a50c22a9231beb452346720bb163344a41c5f5a24e8335b6ccc595fd436aea89737b1281aecb411eb835f0b939073fdd1dd4d5a2492e91ef4a3c55bcbd"),
200                u_0: hex!("0033d06d17bc3b9a3efc081a05d65805a14a3050a0dd4dfb4884618eb5c73980a59c5a246b18f58ad022dd3630faa22889fbb8ba1593466515e6ab4aeb7381c26334"),
201                u_1: hex!("0092290ab99c3fea1a5b8fb2ca49f859994a04faee3301cefab312d34227f6a2d0c3322cf76861c6a3683bdaa2dd2a6daa5d6906c663e065338b2344d20e313f1114"),
202                q0_x: hex!("00041f6eb92af8777260718e4c22328a7d74203350c6c8f5794d99d5789766698f459b83d5068276716f01429934e40af3d1111a22780b1e07e72238d2207e5386be"),
203                q0_y: hex!("001c712f0182813942b87cab8e72337db017126f52ed797dd234584ac9ae7e80dfe7abea11db02cf1855312eae1447dbaecc9d7e8c880a5e76a39f6258074e1bc2e0"),
204                q1_x: hex!("0125c0b69bcf55eab49280b14f707883405028e05c927cd7625d4e04115bd0e0e6323b12f5d43d0d6d2eff16dbcf244542f84ec058911260dc3bb6512ab5db285fbd"),
205                q1_y: hex!("008bddfb803b3f4c761458eb5f8a0aee3e1f7f68e9d7424405fa69172919899317fb6ac1d6903a432d967d14e0f80af63e7035aaae0c123e56862ce969456f99f102"),
206            },
207        ];
208
209        for test_vector in TEST_VECTORS {
210            // in parts
211            let mut u = [FieldElement::default(), FieldElement::default()];
212            hash2curve::hash_to_field::<ExpandMsgXmd<Sha512>, FieldElement>(
213                &[test_vector.msg],
214                &[DST],
215                &mut u,
216            )
217            .unwrap();
218
219            /// Assert that the provided projective point matches the given test vector.
220            // TODO(tarcieri): use coordinate APIs. See zkcrypto/group#30
221            macro_rules! assert_point_eq {
222                ($actual:expr, $expected_x:expr, $expected_y:expr) => {
223                    let point = $actual.to_affine().to_encoded_point(false);
224                    let (actual_x, actual_y) = match point.coordinates() {
225                        sec1::Coordinates::Uncompressed { x, y } => (x, y),
226                        _ => unreachable!(),
227                    };
228
229                    assert_eq!(&$expected_x, actual_x.as_slice());
230                    assert_eq!(&$expected_y, actual_y.as_slice());
231                };
232            }
233
234            assert_eq!(u[0].to_bytes().as_slice(), test_vector.u_0);
235            assert_eq!(u[1].to_bytes().as_slice(), test_vector.u_1);
236
237            let q0 = u[0].map_to_curve();
238            assert_point_eq!(q0, test_vector.q0_x, test_vector.q0_y);
239
240            let q1 = u[1].map_to_curve();
241            assert_point_eq!(q1, test_vector.q1_x, test_vector.q1_y);
242
243            let p = q0.clear_cofactor() + q1.clear_cofactor();
244            assert_point_eq!(p, test_vector.p_x, test_vector.p_y);
245
246            // complete run
247            let pt = NistP521::hash_from_bytes::<ExpandMsgXmd<Sha512>>(&[test_vector.msg], &[DST])
248                .unwrap();
249            assert_point_eq!(pt, test_vector.p_x, test_vector.p_y);
250        }
251    }
252
253    /// Taken from <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-voprf#appendix-A.5>.
254    #[test]
255    fn hash_to_scalar_voprf() {
256        struct TestVector {
257            dst: &'static [u8],
258            key_info: &'static [u8],
259            seed: &'static [u8],
260            sk_sm: &'static [u8],
261        }
262
263        const TEST_VECTORS: &[TestVector] = &[
264            TestVector {
265                dst: b"DeriveKeyPairOPRFV1-\x00-P521-SHA512",
266                key_info: &hex!("74657374206b6579"),
267                seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"),
268                sk_sm: &hex!("0153441b8faedb0340439036d6aed06d1217b34c42f17f8db4c5cc610a4a955d698a688831b16d0dc7713a1aa3611ec60703bffc7dc9c84e3ed673b3dbe1d5fccea6"),
269            },
270            TestVector {
271                dst: b"DeriveKeyPairOPRFV1-\x01-P521-SHA512",
272                key_info: b"test key",
273                seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"),
274                sk_sm: &hex!("015c7fc1b4a0b1390925bae915bd9f3d72009d44d9241b962428aad5d13f22803311e7102632a39addc61ea440810222715c9d2f61f03ea424ec9ab1fe5e31cf9238"),
275            },
276            TestVector {
277                dst: b"DeriveKeyPairOPRFV1-\x02-P521-SHA512",
278                key_info: b"test key",
279                seed: &hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3"),
280                sk_sm: &hex!("014893130030ce69cf714f536498a02ff6b396888f9bb507985c32928c4427d6d39de10ef509aca4240e8569e3a88debc0d392e3361bcd934cb9bdd59e339dff7b27"),
281            },
282        ];
283
284        'outer: for test_vector in TEST_VECTORS {
285            let key_info_len = u16::try_from(test_vector.key_info.len())
286                .unwrap()
287                .to_be_bytes();
288
289            for counter in 0_u8..=u8::MAX {
290                let scalar = NistP521::hash_to_scalar::<ExpandMsgXmd<Sha512>>(
291                    &[
292                        test_vector.seed,
293                        &key_info_len,
294                        test_vector.key_info,
295                        &counter.to_be_bytes(),
296                    ],
297                    &[test_vector.dst],
298                )
299                .unwrap();
300
301                if !bool::from(scalar.is_zero()) {
302                    assert_eq!(scalar.to_bytes().as_slice(), test_vector.sk_sm);
303                    continue 'outer;
304                }
305            }
306
307            panic!("deriving key failed");
308        }
309    }
310
311    #[test]
312    fn from_okm_fuzz() {
313        let mut wide_order = GenericArray::default();
314        wide_order[40..].copy_from_slice(NistP521::ORDER.to_be_byte_array().as_slice());
315        // TODO: This could be reduced to `U832` when `crypto-bigint` implements `ArrayEncoding`.
316        let wide_order = NonZero::new(U896::from_be_byte_array(wide_order)).unwrap();
317
318        let simple_from_okm = move |data: GenericArray<u8, U98>| -> Scalar {
319            let mut wide_data = GenericArray::default();
320            wide_data[14..].copy_from_slice(data.as_slice());
321            let wide_data = U896::from_be_byte_array(wide_data);
322
323            let scalar = wide_data % wide_order;
324            let reduced_scalar = U576::from_be_slice(&scalar.to_be_byte_array()[40..]);
325
326            Scalar::reduce(reduced_scalar)
327        };
328
329        proptest!(
330            ProptestConfig::with_cases(1000),
331            |(
332                b0 in num::u64::ANY,
333                b1 in num::u64::ANY,
334                b2 in num::u64::ANY,
335                b3 in num::u64::ANY,
336                b4 in num::u64::ANY,
337                b5 in num::u64::ANY,
338                b6 in num::u64::ANY,
339                b7 in num::u64::ANY,
340                b8 in num::u64::ANY,
341                b9 in num::u64::ANY,
342                b10 in num::u64::ANY,
343                b11 in num::u64::ANY,
344                b12 in num::u16::ANY,
345            )| {
346                let mut data = GenericArray::default();
347                data[..8].copy_from_slice(&b0.to_be_bytes());
348                data[8..16].copy_from_slice(&b1.to_be_bytes());
349                data[16..24].copy_from_slice(&b2.to_be_bytes());
350                data[24..32].copy_from_slice(&b3.to_be_bytes());
351                data[32..40].copy_from_slice(&b4.to_be_bytes());
352                data[40..48].copy_from_slice(&b5.to_be_bytes());
353                data[48..56].copy_from_slice(&b6.to_be_bytes());
354                data[56..64].copy_from_slice(&b7.to_be_bytes());
355                data[64..72].copy_from_slice(&b8.to_be_bytes());
356                data[72..80].copy_from_slice(&b9.to_be_bytes());
357                data[80..88].copy_from_slice(&b10.to_be_bytes());
358                data[88..96].copy_from_slice(&b11.to_be_bytes());
359                data[96..].copy_from_slice(&b12.to_be_bytes());
360
361                let from_okm = Scalar::from_okm(&data);
362                let simple_from_okm = simple_from_okm(data);
363                assert_eq!(from_okm, simple_from_okm);
364            }
365        );
366    }
367}