airbender_crypto/bls12_381/
eip2537.rs1use super::curves::{g1, g2};
7use super::{Fq, Fq2, G1Affine, G2Affine};
8use crate::ark_ec::hashing::curve_maps::swu::SWUMap;
9use crate::ark_ec::hashing::curve_maps::wb::{IsogenyMap, WBConfig};
10use crate::ark_ec::hashing::map_to_curve_hasher::MapToCurve;
11use crate::ark_ec::hashing::HashToCurveError;
12use crate::ark_ec::models::short_weierstrass::SWCurveConfig;
13use crate::ark_ec::short_weierstrass::Affine;
14use crate::ark_ec::AffineRepr;
15use crate::ark_ff::{AdditiveGroup, Field, PrimeField};
16
17const FIELD_ELEMENT_LEN: usize = 64;
18const G1_LEN: usize = 128;
19const G2_LEN: usize = 256;
20
21#[inline(never)]
22pub fn parse_fq_bytes(input: &[u8; FIELD_ELEMENT_LEN]) -> Option<Fq> {
23 if input[..16].iter().all(|el| *el == 0) == false {
24 return None;
25 }
26 let mut repr = <Fq as PrimeField>::BigInt::zero();
27 let repr_slice = repr.as_mut();
28 for (dst, src) in repr_slice.iter_mut().zip(input[16..].chunks_exact(8).rev()) {
29 *dst = u64::from_be_bytes(src.try_into().unwrap());
30 }
31 Fq::from_bigint(repr)
32}
33
34#[inline(never)]
35pub fn parse_fq2_bytes(input: &[u8; FIELD_ELEMENT_LEN * 2]) -> Option<Fq2> {
36 let c0 = parse_fq_bytes(input[0..64].try_into().ok()?)?;
37 let c1 = parse_fq_bytes(input[64..128].try_into().ok()?)?;
38 Some(Fq2 { c0, c1 })
39}
40
41#[inline(never)]
42pub fn parse_g1_bytes(input: &[u8; G1_LEN]) -> Option<(G1Affine, bool)> {
43 if input.iter().all(|el| *el == 0) {
44 return Some((G1Affine::identity(), false));
45 }
46 let x = parse_fq_bytes(input[0..64].try_into().ok()?)?;
47 let y = parse_fq_bytes(input[64..128].try_into().ok()?)?;
48 let point = G1Affine::new_unchecked(x, y);
49
50 if !point.is_on_curve() {
51 return None;
52 }
53
54 Some((point, true))
55}
56
57#[inline(never)]
58pub fn parse_g2_bytes(input: &[u8; G2_LEN]) -> Option<(G2Affine, bool)> {
59 if input.iter().all(|el| *el == 0) {
60 return Some((G2Affine::identity(), false));
61 }
62 let x = parse_fq2_bytes(input[0..128].try_into().ok()?)?;
63 let y = parse_fq2_bytes(input[128..256].try_into().ok()?)?;
64 let point = G2Affine::new_unchecked(x, y);
65
66 if !point.is_on_curve() {
67 return None;
68 }
69
70 Some((point, true))
71}
72
73#[inline(never)]
74pub fn serialize_fq_bytes(el: Fq, output: &mut [u8; FIELD_ELEMENT_LEN]) {
75 output[..16].fill(0);
76 let bigint = el.into_bigint();
77 let words = bigint.as_ref();
78 for (i, word) in words.iter().take(6).enumerate() {
79 let bytes = word.to_be_bytes();
80 let start = 16 + (5 - i) * 8;
81 output[start..start + 8].copy_from_slice(&bytes);
82 }
83}
84
85#[inline(never)]
86pub fn serialize_fq2_bytes(el: Fq2, output: &mut [u8; FIELD_ELEMENT_LEN * 2]) {
87 let (left, right) = output.split_at_mut(64);
88 serialize_fq_bytes(el.c0, left.try_into().unwrap());
89 serialize_fq_bytes(el.c1, right.try_into().unwrap());
90}
91
92#[inline(never)]
93pub fn serialize_g1_bytes(el: G1Affine, output: &mut [u8; G1_LEN]) {
94 if let Some((x, y)) = el.xy() {
95 let (left, right) = output.split_at_mut(64);
96 serialize_fq_bytes(x, left.try_into().unwrap());
97 serialize_fq_bytes(y, right.try_into().unwrap());
98 } else {
99 output.fill(0);
100 }
101}
102
103#[inline(never)]
104pub fn serialize_g2_bytes(el: G2Affine, output: &mut [u8; G2_LEN]) {
105 if let Some((x, y)) = el.xy() {
106 let (left, right) = output.split_at_mut(128);
107 serialize_fq2_bytes(x, left.try_into().unwrap());
108 serialize_fq2_bytes(y, right.try_into().unwrap());
109 } else {
110 output.fill(0);
111 }
112}
113
114fn evaluate_polynomial<F: Field>(coeffs: &[F], x: &F) -> F {
117 if coeffs.is_empty() {
118 return F::ZERO;
119 }
120 if x.is_zero() {
121 return coeffs[0];
122 }
123 coeffs
124 .iter()
125 .rfold(F::ZERO, |result, coeff| result * x + coeff)
126}
127
128fn apply_isogeny_map<
130 Domain: SWCurveConfig,
131 Codomain: SWCurveConfig<BaseField = Domain::BaseField>,
132>(
133 map: &IsogenyMap<'_, Domain, Codomain>,
134 domain_point: Affine<Domain>,
135) -> Result<Affine<Codomain>, HashToCurveError> {
136 match domain_point.xy() {
137 Some((x, y)) => {
138 let x_num = evaluate_polynomial(map.x_map_numerator, &x);
139 let x_den = evaluate_polynomial(map.x_map_denominator, &x);
140 let y_num = evaluate_polynomial(map.y_map_numerator, &x);
141 let y_den = evaluate_polynomial(map.y_map_denominator, &x);
142
143 let zero = Domain::BaseField::ZERO;
144 let prod = x_den * y_den;
145 let (x_den_inv, y_den_inv) = if let Some(prod_inv) = prod.inverse() {
146 (y_den * prod_inv, x_den * prod_inv)
147 } else {
148 (
149 x_den.inverse().unwrap_or(zero),
150 y_den.inverse().unwrap_or(zero),
151 )
152 };
153 let img_x = x_num * x_den_inv;
154 let img_y = (y_num * y) * y_den_inv;
155 Ok(Affine::<Codomain>::new_unchecked(img_x, img_y))
156 }
157 None => Ok(Affine::identity()),
158 }
159}
160
161#[inline(never)]
162pub fn map_fp_to_g1(element: Fq) -> Result<G1Affine, HashToCurveError> {
163 let point_on_iso_curve =
164 SWUMap::<<g1::Config as WBConfig>::IsogenousCurve>::map_to_curve(element)?;
165 apply_isogeny_map(&g1::Config::ISOGENY_MAP, point_on_iso_curve)
166}
167
168#[inline(never)]
169pub fn map_fp2_to_g2(element: Fq2) -> Result<G2Affine, HashToCurveError> {
170 let point_on_iso_curve =
171 SWUMap::<<g2::Config as WBConfig>::IsogenousCurve>::map_to_curve(element)?;
172 apply_isogeny_map(&g2::Config::ISOGENY_MAP, point_on_iso_curve)
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::ark_ec::hashing::curve_maps::wb::WBMap;
179 use proptest::{prop_assert_eq, proptest};
180
181 #[test]
182 fn map_fp_to_g1_matches_arkworks() {
183 proptest!(|(bytes: [u8; 48])| {
184 let mut repr = <Fq as PrimeField>::BigInt::zero();
185 for (dst, src) in repr.as_mut().iter_mut().zip(bytes.chunks_exact(8)) {
186 *dst = u64::from_le_bytes(src.try_into().unwrap());
187 }
188 if let Some(element) = Fq::from_bigint(repr) {
189 let ours = map_fp_to_g1(element).unwrap();
190 let reference = WBMap::<g1::Config>::map_to_curve(element).unwrap();
191 prop_assert_eq!(ours, reference);
192 }
193 })
194 }
195
196 #[test]
197 fn apply_isogeny_map_zero_denominator() {
198 let one = Fq::ONE;
201 let zero = Fq::ZERO;
202
203 let isogeny = IsogenyMap::<g1::Config, g1::Config> {
205 x_map_numerator: &[one],
206 x_map_denominator: &[zero], y_map_numerator: &[one],
208 y_map_denominator: &[one],
209 };
210
211 let input = G1Affine::generator();
212 let result = apply_isogeny_map(&isogeny, input).unwrap();
213 assert_eq!(result.x, Fq::ZERO);
215 assert_ne!(result.y, Fq::ZERO);
216
217 let isogeny_both_zero = IsogenyMap::<g1::Config, g1::Config> {
219 x_map_numerator: &[one],
220 x_map_denominator: &[zero],
221 y_map_numerator: &[one],
222 y_map_denominator: &[zero],
223 };
224 let result = apply_isogeny_map(&isogeny_both_zero, input).unwrap();
225 assert_eq!(result.x, Fq::ZERO);
226 assert_eq!(result.y, Fq::ZERO);
227 }
228
229 #[test]
230 fn map_fp2_to_g2_matches_arkworks() {
231 proptest!(|(bytes: [u8; 96])| {
232 let mut repr0 = <Fq as PrimeField>::BigInt::zero();
233 let mut repr1 = <Fq as PrimeField>::BigInt::zero();
234 for (dst, src) in repr0.as_mut().iter_mut().zip(bytes[..48].chunks_exact(8)) {
235 *dst = u64::from_le_bytes(src.try_into().unwrap());
236 }
237 for (dst, src) in repr1.as_mut().iter_mut().zip(bytes[48..].chunks_exact(8)) {
238 *dst = u64::from_le_bytes(src.try_into().unwrap());
239 }
240 if let (Some(c0), Some(c1)) = (Fq::from_bigint(repr0), Fq::from_bigint(repr1)) {
241 let element = Fq2 { c0, c1 };
242 let ours = map_fp2_to_g2(element).unwrap();
243 let reference = WBMap::<g2::Config>::map_to_curve(element).unwrap();
244 prop_assert_eq!(ours, reference);
245 }
246 })
247 }
248}