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;
117 let num_x = if gx1_square { num_x1 } else { num_x2 };
118 let y = if gx1_square { y1 } else { y2 };
119
120 let x_affine = num_x / div;
121 let y_affine = if parity(&y) == parity(&element) {
122 y
123 } else {
124 -y
125 };
126 let point_on_curve = Affine::new_unchecked(x_affine, y_affine);
127 debug_assert!(
128 point_on_curve.is_on_curve(),
129 "swu mapped to a point off the curve"
130 );
131 Ok(point_on_curve)
132 }
133}
134
135#[cfg(test)]
136mod test {
137 #[cfg(all(
138 target_has_atomic = "8",
139 target_has_atomic = "16",
140 target_has_atomic = "32",
141 target_has_atomic = "64",
142 target_has_atomic = "ptr"
143 ))]
144 type DefaultHasher = ahash::AHasher;
145
146 #[cfg(not(all(
147 target_has_atomic = "8",
148 target_has_atomic = "16",
149 target_has_atomic = "32",
150 target_has_atomic = "64",
151 target_has_atomic = "ptr"
152 )))]
153 type DefaultHasher = fnv::FnvHasher;
154
155 use crate::{
156 hashing::{map_to_curve_hasher::MapToCurveBasedHasher, HashToCurve},
157 CurveConfig,
158 };
159 use ark_ff::field_hashers::DefaultFieldHasher;
160 use ark_std::vec::*;
161
162 use super::*;
163 use ark_ff::{fields::Fp64, MontBackend, MontFp};
164 use hashbrown::HashMap;
165 use sha2::Sha256;
166
167 #[derive(ark_ff::MontConfig)]
168 #[modulus = "127"]
169 #[generator = "6"]
170 pub(crate) struct F127Config;
171 pub(crate) type F127 = Fp64<MontBackend<F127Config, 1>>;
172
173 const F127_ONE: F127 = MontFp!("1");
174
175 struct TestSWUMapToCurveConfig;
176
177 impl CurveConfig for TestSWUMapToCurveConfig {
178 const COFACTOR: &[u64] = &[1];
179
180 const COFACTOR_INV: F127 = F127_ONE;
181
182 type BaseField = F127;
183 type ScalarField = F127;
184 }
185
186 impl SWCurveConfig for TestSWUMapToCurveConfig {
201 const COEFF_A: F127 = F127_ONE;
203
204 const COEFF_B: F127 = MontFp!("63");
206
207 const GENERATOR: Affine<Self> = Affine::new_unchecked(MontFp!("62"), MontFp!("70"));
209
210 type ZeroFlag = bool;
212 }
213
214 impl SWUConfig for TestSWUMapToCurveConfig {
215 const ZETA: F127 = MontFp!("-1");
216 }
217
218 #[test]
220 fn test_field_element_construction() {
221 let a1 = F127::from(1);
222 let a2 = F127::from(2);
223 let a3 = F127::from(125);
224
225 assert!(F127::from(0) == a2 + a3);
226 assert!(F127::from(0) == a2 * a1 + a3);
227 }
228
229 #[test]
230 fn test_field_division() {
231 let num = F127::from(0x3d);
232 let den = F127::from(0x7b);
233 let num_on_den = F127::from(0x50);
234
235 assert!(num / den == num_on_den);
236 }
237
238 #[test]
241 fn hash_arbitrary_string_to_curve_swu() {
242 let test_swu_to_curve_hasher = MapToCurveBasedHasher::<
243 Projective<TestSWUMapToCurveConfig>,
244 DefaultFieldHasher<Sha256, 128>,
245 SWUMap<TestSWUMapToCurveConfig>,
246 >::new(&[1])
247 .unwrap();
248
249 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");
250
251 assert!(
252 hash_result.is_on_curve(),
253 "hash results into a point off the curve"
254 );
255 }
256
257 #[test]
261 fn map_field_to_curve_swu() {
262 SWUMap::<TestSWUMapToCurveConfig>::check_parameters().unwrap();
263
264 let mut map_range: Vec<Affine<TestSWUMapToCurveConfig>> = Vec::with_capacity(128);
265 for current_field_element in 0..127 {
266 let element = F127::from(current_field_element as u64);
267 map_range.push(SWUMap::map_to_curve(element).unwrap());
268 }
269
270 let mut counts =
271 HashMap::with_hasher(core::hash::BuildHasherDefault::<DefaultHasher>::default());
272
273 let mode = map_range
274 .iter()
275 .copied()
276 .max_by_key(|&n| {
277 let count = counts.entry(n).or_insert(0);
278 *count += 1;
279 *count
280 })
281 .unwrap();
282
283 assert!(
284 *counts.get(&mode).unwrap() != 127,
285 "a constant hash function is not good."
286 );
287 }
288}