ark_ec/hashing/curve_maps/
swu.rs1use crate::models::short_weierstrass::SWCurveConfig;
2use ark_ff::{Field, One, Zero};
3use core::marker::PhantomData;
4
5use crate::{
6    hashing::{curve_maps::parity, map_to_curve_hasher::MapToCurve, HashToCurveError},
7    models::short_weierstrass::{Affine, Projective},
8};
9
10pub trait SWUConfig: SWCurveConfig {
16    const ZETA: Self::BaseField;
21}
22
23pub struct SWUMap<P: SWUConfig>(PhantomData<fn() -> P>);
25
26impl<P: SWUConfig> MapToCurve<Projective<P>> for SWUMap<P> {
27    fn check_parameters() -> Result<(), HashToCurveError> {
29        debug_assert!(
31            P::ZETA.legendre().is_qnr(),
32            "ZETA should be a quadratic non-residue for the SWU map"
33        );
34
35        debug_assert!(!P::COEFF_A.is_zero() && !P::COEFF_B.is_zero(),
37		      "Simplified SWU requires a * b != 0 in the short Weierstrass form of y^2 = x^3 + a*x + b ");
38
39        Ok(())
40    }
41
42    fn map_to_curve(element: P::BaseField) -> Result<Affine<P>, HashToCurveError> {
46        let a = P::COEFF_A;
68        let b = P::COEFF_B;
69
70        let zeta_u2 = P::ZETA * element.square();
71        let ta = zeta_u2.square() + zeta_u2;
72        let num_x1 = b * (ta + <P::BaseField as One>::one());
73        let div = a * if ta.is_zero() { P::ZETA } else { -ta };
74
75        let num2_x1 = num_x1.square();
76        let div2 = div.square();
77        let div3 = div2 * div;
78        let num_gx1 = (num2_x1 + a * div2) * num_x1 + b * div3;
79
80        let num_x2 = zeta_u2 * num_x1; let gx1_square;
87        let gx1;
88
89        debug_assert!(
90            !div3.is_zero(),
91            "we have checked that neither a or ZETA are zero. Q.E.D."
92        );
93        let y1: P::BaseField = {
94            gx1 = num_gx1 / div3;
95            if gx1.legendre().is_qr() {
96                gx1_square = true;
97                gx1.sqrt()
98                    .expect("We have checked that gx1 is a quadratic residue. Q.E.D")
99            } else {
100                let zeta_gx1 = P::ZETA * gx1;
101                gx1_square = false;
102                zeta_gx1.sqrt().expect(
103                    "ZETA * gx1 is a quadratic residue because legard is multiplicative. Q.E.D",
104                )
105            }
106        };
107
108        let y2 = zeta_u2 * element * y1;
125        let num_x = if gx1_square { num_x1 } else { num_x2 };
126        let y = if gx1_square { y1 } else { y2 };
127
128        let x_affine = num_x / div;
129        let y_affine = if parity(&y) != parity(&element) {
130            -y
131        } else {
132            y
133        };
134        let point_on_curve = Affine::<P>::new_unchecked(x_affine, y_affine);
135        debug_assert!(
136            point_on_curve.is_on_curve(),
137            "swu mapped to a point off the curve"
138        );
139        Ok(point_on_curve)
140    }
141}
142
143#[cfg(test)]
144mod test {
145    #[cfg(all(
146        target_has_atomic = "8",
147        target_has_atomic = "16",
148        target_has_atomic = "32",
149        target_has_atomic = "64",
150        target_has_atomic = "ptr"
151    ))]
152    type DefaultHasher = ahash::AHasher;
153
154    #[cfg(not(all(
155        target_has_atomic = "8",
156        target_has_atomic = "16",
157        target_has_atomic = "32",
158        target_has_atomic = "64",
159        target_has_atomic = "ptr"
160    )))]
161    type DefaultHasher = fnv::FnvHasher;
162
163    use crate::{
164        hashing::{map_to_curve_hasher::MapToCurveBasedHasher, HashToCurve},
165        CurveConfig,
166    };
167    use ark_ff::field_hashers::DefaultFieldHasher;
168    use ark_std::vec::*;
169
170    use super::*;
171    use ark_ff::{fields::Fp64, MontBackend, MontFp};
172    use hashbrown::HashMap;
173    use sha2::Sha256;
174
175    #[derive(ark_ff::MontConfig)]
176    #[modulus = "127"]
177    #[generator = "6"]
178    pub struct F127Config;
179    pub type F127 = Fp64<MontBackend<F127Config, 1>>;
180
181    const F127_ONE: F127 = MontFp!("1");
182
183    struct TestSWUMapToCurveConfig;
184
185    impl CurveConfig for TestSWUMapToCurveConfig {
186        const COFACTOR: &'static [u64] = &[1];
187
188    #[rustfmt::skip]
189        const COFACTOR_INV: F127 = F127_ONE;
190
191        type BaseField = F127;
192        type ScalarField = F127;
193    }
194
195    impl SWCurveConfig for TestSWUMapToCurveConfig {
210        const COEFF_A: F127 = F127_ONE;
212
213        const COEFF_B: F127 = MontFp!("63");
215
216        const GENERATOR: Affine<Self> = Affine::new_unchecked(MontFp!("62"), MontFp!("70"));
218    }
219
220    impl SWUConfig for TestSWUMapToCurveConfig {
221        const ZETA: F127 = MontFp!("-1");
222    }
223
224    #[test]
226    fn test_field_element_construction() {
227        let a1 = F127::from(1);
228        let a2 = F127::from(2);
229        let a3 = F127::from(125);
230
231        assert!(F127::from(0) == a2 + a3);
232        assert!(F127::from(0) == a2 * a1 + a3);
233    }
234
235    #[test]
236    fn test_field_division() {
237        let num = F127::from(0x3d);
238        let den = F127::from(0x7b);
239        let num_on_den = F127::from(0x50);
240
241        assert!(num / den == num_on_den);
242    }
243
244    #[test]
247    fn hash_arbitrary_string_to_curve_swu() {
248        let test_swu_to_curve_hasher = MapToCurveBasedHasher::<
249            Projective<TestSWUMapToCurveConfig>,
250            DefaultFieldHasher<Sha256, 128>,
251            SWUMap<TestSWUMapToCurveConfig>,
252        >::new(&[1])
253        .unwrap();
254
255        let hash_result = test_swu_to_curve_hasher.hash(b"if you stick a Babel fish in your ear you can instantly understand anything said to you in any form of language.").expect("fail to hash the string to curve");
256
257        assert!(
258            hash_result.is_on_curve(),
259            "hash results into a point off the curve"
260        );
261    }
262
263    #[test]
267    fn map_field_to_curve_swu() {
268        SWUMap::<TestSWUMapToCurveConfig>::check_parameters().unwrap();
269
270        let mut map_range: Vec<Affine<TestSWUMapToCurveConfig>> = vec![];
271        for current_field_element in 0..127 {
272            let element = F127::from(current_field_element as u64);
273            map_range.push(SWUMap::<TestSWUMapToCurveConfig>::map_to_curve(element).unwrap());
274        }
275
276        let mut counts =
277            HashMap::with_hasher(core::hash::BuildHasherDefault::<DefaultHasher>::default());
278
279        let mode = map_range
280            .iter()
281            .copied()
282            .max_by_key(|&n| {
283                let count = counts.entry(n).or_insert(0);
284                *count += 1;
285                *count
286            })
287            .unwrap();
288
289        assert!(
290            *counts.get(&mode).unwrap() != 127,
291            "a constant hash function is not good."
292        );
293    }
294}