diff --git a/derive/src/field/mod.rs b/derive/src/field/mod.rs index f6e4c836..566fb9ee 100644 --- a/derive/src/field/mod.rs +++ b/derive/src/field/mod.rs @@ -113,6 +113,8 @@ pub(crate) fn impl_field(input: TokenStream) -> TokenStream { let modulus_limbs_32_ident = quote! {[#(#modulus_limbs_32,)*]}; let to_token = |e: &BigUint| big_to_token(e, num_limbs); + let half_modulus = (&modulus - 1usize) >> 1; + let half_modulus = to_token(&half_modulus); // binary modulus let t = BigUint::from(1u64) << (num_limbs * limb_size as usize); @@ -282,6 +284,17 @@ pub(crate) fn impl_field(input: TokenStream) -> TokenStream { } } + impl crate::serde::endian::EndianRepr for #field { + const ENDIAN: crate::serde::endian::Endian = crate::serde::endian::Endian::#endian; + + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> subtle::CtOption { + #field::from_bytes(bytes[..#field::SIZE].try_into().unwrap()) + } + } impl #field { pub const SIZE: usize = #num_limbs * 8; @@ -313,9 +326,9 @@ pub(crate) fn impl_field(input: TokenStream) -> TokenStream { /// Attempts to convert a <#endian>-endian byte representation of /// a scalar into a `$field`, failing if the input is not canonical. pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> subtle::CtOption { + use crate::serde::endian::EndianRepr; let mut el = #field::default(); - use crate::serde::endian::Endian; - crate::serde::endian::#endian::from_bytes(bytes, &mut el.0); + #field::ENDIAN.from_bytes(bytes, &mut el.0); subtle::CtOption::new(el * Self::R2, subtle::Choice::from(Self::is_less_than_modulus(&el.0) as u8)) } @@ -323,10 +336,10 @@ pub(crate) fn impl_field(input: TokenStream) -> TokenStream { /// Converts an element of `$field` into a byte representation in /// <#endian>-endian byte order. pub fn to_bytes(&self) -> [u8; Self::SIZE] { - use crate::serde::endian::Endian; + use crate::serde::endian::EndianRepr; let el = self.from_mont(); let mut res = [0; Self::SIZE]; - crate::serde::endian::#endian::to_bytes(&mut res, &el); + #field::ENDIAN.to_bytes(&mut res, &el); res.into() } @@ -341,18 +354,6 @@ pub(crate) fn impl_field(input: TokenStream) -> TokenStream { crate::ff_ext::jacobi::jacobi::<#jacobi_constant>(&self.0, &#modulus_limbs_ident) } - // Returns the multiplicative inverse of the element. If it is zero, the method fails. - #[inline(always)] - fn invert(&self) -> subtle::CtOption { - const BYINVERTOR: crate::ff_ext::inverse::BYInverter<#by_inverter_constant> = - crate::ff_ext::inverse::BYInverter::<#by_inverter_constant>::new(&#modulus_limbs_ident, &#r2); - - if let Some(inverse) = BYINVERTOR.invert::<{ Self::NUM_LIMBS }>(&self.0) { - subtle::CtOption::new(Self(inverse), subtle::Choice::from(1)) - } else { - subtle::CtOption::new(Self::zero(), subtle::Choice::from(0)) - } - } #[inline(always)] pub(crate) fn is_less_than_modulus(limbs: &[u64; Self::NUM_LIMBS]) -> bool { @@ -361,6 +362,18 @@ pub(crate) fn impl_field(input: TokenStream) -> TokenStream { }); (borrow as u8) & 1 == 1 } + + /// Returns whether or not this element is strictly lexicographically + /// larger than its negation. + pub fn lexicographically_largest(&self) -> Choice { + const HALF_MODULUS: [u64; #num_limbs]= #half_modulus; + let tmp = self.from_mont(); + let borrow = tmp + .into_iter() + .zip(HALF_MODULUS.into_iter()) + .fold(0, |borrow, (t, m)| crate::arithmetic::sbb(t, m, borrow).1); + !Choice::from((borrow as u8) & 1) + } } impl ff::Field for #field { @@ -450,8 +463,7 @@ pub(crate) fn impl_field(input: TokenStream) -> TokenStream { fn from_repr(repr: Self::Repr) -> subtle::CtOption { let mut el = #field::default(); - use crate::serde::endian::Endian; - crate::serde::endian::LE::from_bytes(repr.as_ref(), &mut el.0); + crate::serde::endian::Endian::LE.from_bytes(repr.as_ref(), &mut el.0); subtle::CtOption::new(el * Self::R2, subtle::Choice::from(Self::is_less_than_modulus(&el.0) as u8)) } @@ -459,7 +471,7 @@ pub(crate) fn impl_field(input: TokenStream) -> TokenStream { use crate::serde::endian::Endian; let el = self.from_mont(); let mut res = [0; #size]; - crate::serde::endian::LE::to_bytes(&mut res, &el); + crate::serde::endian::Endian::LE.to_bytes(&mut res, &el); res.into() } diff --git a/src/bls12381/engine.rs b/src/bls12381/engine.rs new file mode 100644 index 00000000..a0d9e0e9 --- /dev/null +++ b/src/bls12381/engine.rs @@ -0,0 +1,126 @@ +use super::fq12::Fq12; +use super::fq2::Fq2; +use super::{Fr, G1Affine, G2Affine, BLS_X, G1, G2}; +use crate::ff_ext::quadratic::QuadSparseMul; +use crate::ff_ext::ExtField; +use core::borrow::Borrow; +use core::iter::Sum; +use core::ops::{Add, Mul, Neg, Sub}; +use ff::Field; +use ff::PrimeField; +use group::prime::PrimeCurveAffine; +use group::Group; +use pairing::{Engine, MillerLoopResult, MultiMillerLoop, PairingCurveAffine}; +use rand::RngCore; +use std::ops::MulAssign; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +crate::impl_gt!(Gt, Fq12, Fr); +crate::impl_miller_loop_components!(Bls12381, G1, G1Affine, G2, G2Affine, Fq12, Gt, Fr); + +impl MillerLoopResult for Fq12 { + type Gt = Gt; + + fn final_exponentiation(&self) -> Gt { + #[must_use] + fn exp_by_x(f: Fq12) -> Fq12 { + let mut acc = Fq12::one(); + for (i, b) in BLS_X.into_iter().enumerate() { + (i != 0).then(|| acc.cyclotomic_square()); + (b == 1).then(|| acc *= f); + } + acc.conjugate(); + acc + } + + let mut t0 = *self; + t0.frobenius_map(6); + + Gt(self + .invert() + .map(|mut t1| { + let mut t2 = t0 * t1; + t1 = t2; + t2.frobenius_map(2); + t2 *= t1; + t1 = t2; + t1.cyclotomic_square(); + t1.conjugate(); + let mut t3 = exp_by_x(t2); + let mut t4 = t3; + t4.cyclotomic_square(); + let mut t5 = t1 * t3; + t1 = exp_by_x(t5); + t0 = exp_by_x(t1); + let mut t6 = exp_by_x(t0) * t4; + t4 = exp_by_x(t6); + t5.conjugate(); + t4 *= t5 * t2; + t1 *= t2; + t1.frobenius_map(3); + t2.conjugate(); + t6 *= t2; + t6.frobenius_map(1); + t3 *= t0; + t3.frobenius_map(2); + t3 * t4 * t1 * t6 + }) + .unwrap()) + } +} + +pub fn multi_miller_loop(terms: &[(&G1Affine, &G2Affine)]) -> Fq12 { + let terms = terms + .iter() + .filter_map(|&(p, q)| { + if bool::from(p.is_identity()) || bool::from(q.is_identity()) { + None + } else { + Some((p, q)) + } + }) + .collect::>(); + + let mut f = Fq12::one(); + let mut r = terms.iter().map(|(_, q)| q.to_curve()).collect::>(); + + for (i, x) in BLS_X.iter().map(|&b| b == 1).skip(1).enumerate() { + if i != 0 { + f.square_assign(); + } + + terms.iter().zip(r.iter_mut()).for_each(|((p, _), r)| { + double(&mut f, r, p); + }); + + if x { + for ((p, q), r) in terms.iter().zip(r.iter_mut()) { + add(&mut f, r, q, p); + } + } + } + + f.conjugate(); + f +} + +fn ell(f: &mut Fq12, coeffs: &(Fq2, Fq2, Fq2), p: &G1Affine) { + let mut c0 = coeffs.0; + let mut c1 = coeffs.1; + c0.c0.mul_assign(&p.y); + c0.c1.mul_assign(&p.y); + c1.c0.mul_assign(&p.x); + c1.c1.mul_assign(&p.x); + Fq12::mul_by_014(f, &coeffs.2, &c1, &c0); +} + +#[cfg(test)] +mod test { + use super::super::{Bls12381, Fr, G1, G2}; + use super::{multi_miller_loop, Fq12, G1Affine, G2Affine, Gt}; + use ff::Field; + use group::{prime::PrimeCurveAffine, Curve, Group}; + use pairing::{Engine as _, MillerLoopResult, PairingCurveAffine}; + use rand_core::OsRng; + crate::test_pairing!(Bls12381, G1, G1Affine, G2, G2Affine, Fq12, Gt, Fr); +} diff --git a/src/bls12381/fq.rs b/src/bls12381/fq.rs new file mode 100644 index 00000000..451657e4 --- /dev/null +++ b/src/bls12381/fq.rs @@ -0,0 +1,56 @@ +use core::convert::TryInto; +use halo2derive::impl_field; +use rand::RngCore; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +impl_field!( + bls12381_base, + Fq, + modulus = "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + mul_gen = "2", + zeta = "1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac", + from_uniform = [64, 96], + endian = "big", +); + +crate::extend_field_legendre!(Fq); +crate::impl_binops_calls!(Fq); +crate::impl_binops_additive!(Fq, Fq); +crate::impl_binops_multiplicative!(Fq, Fq); +crate::field_bits!(Fq); +crate::serialize_deserialize_primefield!(Fq); +crate::impl_from_u64!(Fq); + +use ff::Field; + +use crate::ff_ext::ExtField; +const NEGATIVE_ONE: Fq = Fq::ZERO.sub_const(&Fq::ONE); +impl ExtField for Fq { + const NON_RESIDUE: Self = NEGATIVE_ONE; + fn mul_by_nonresidue(&self) -> Self { + self.neg() + } + fn frobenius_map(&mut self, _: usize) {} +} + +#[cfg(test)] +mod test { + use super::*; + crate::field_testing_suite!(Fq, "field_arithmetic"); + crate::field_testing_suite!(Fq, "conversion"); + crate::field_testing_suite!(Fq, "serialization"); + crate::field_testing_suite!(Fq, "quadratic_residue"); + crate::field_testing_suite!(Fq, "bits"); + crate::field_testing_suite!(Fq, "serialization_check"); + crate::field_testing_suite!(Fq, "constants"); + crate::field_testing_suite!(Fq, "sqrt"); + crate::field_testing_suite!(Fq, "zeta"); + crate::field_testing_suite!(Fq, "from_uniform_bytes", 64, 96); + #[test] + fn test_fq_mul_nonresidue() { + let e = Fq::random(rand_core::OsRng); + let a0 = e.mul_by_nonresidue(); + let a1 = e * Fq::NON_RESIDUE; + assert_eq!(a0, a1); + } +} diff --git a/src/bls12381/fq12.rs b/src/bls12381/fq12.rs new file mode 100644 index 00000000..c43cad26 --- /dev/null +++ b/src/bls12381/fq12.rs @@ -0,0 +1,284 @@ +use super::fq::Fq; +use super::fq2::Fq2; +use super::fq6::Fq6; +use crate::ff_ext::{ + quadratic::{QuadExtField, QuadExtFieldArith, QuadSparseMul}, + ExtField, +}; + +pub type Fq12 = QuadExtField; + +impl QuadExtFieldArith for Fq12 { + type Base = Fq6; +} + +impl QuadSparseMul for Fq12 { + type Base = Fq2; +} + +impl ExtField for Fq12 { + const NON_RESIDUE: Self = Fq12::zero(); // no needs + + fn frobenius_map(&mut self, power: usize) { + self.c0.frobenius_map(power); + self.c1.frobenius_map(power); + self.c1.c0.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + self.c1.c1.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + self.c1.c2.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + } +} + +crate::impl_binops_additive!(Fq12, Fq12); +crate::impl_binops_multiplicative!(Fq12, Fq12); +crate::impl_binops_calls!(Fq12); +crate::impl_sum_prod!(Fq12); +crate::impl_cyclotomic_square!(Fq2, Fq12); + +pub const FROBENIUS_COEFF_FQ12_C1: [Fq2; 12] = [ + // z = u + 1 + // z ^ ((p ^ 0 - 1) / 6) + Fq2 { + c0: Fq([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ ((p ^ 1 - 1) / 6) + Fq2 { + c0: Fq([ + 0x07089552b319d465, + 0xc6695f92b50a8313, + 0x97e83cccd117228f, + 0xa35baecab2dc29ee, + 0x1ce393ea5daace4d, + 0x08f2220fb0fb66eb, + ]), + c1: Fq([ + 0xb2f66aad4ce5d646, + 0x5842a06bfc497cec, + 0xcf4895d42599d394, + 0xc11b9cba40a8e8d0, + 0x2e3813cbe5a0de89, + 0x110eefda88847faf, + ]), + }, + // z ^ ((p ^ 2 - 1) / 6) + Fq2 { + c0: Fq([ + 0xecfb361b798dba3a, + 0xc100ddb891865a2c, + 0x0ec08ff1232bda8e, + 0xd5c13cc6f1ca4721, + 0x47222a47bf7b5c04, + 0x0110f184e51c5f59, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ ((p ^ 3 - 1) / 6) + Fq2 { + c0: Fq([ + 0x3e2f585da55c9ad1, + 0x4294213d86c18183, + 0x382844c88b623732, + 0x92ad2afd19103e18, + 0x1d794e4fac7cf0b9, + 0x0bd592fc7d825ec8, + ]), + c1: Fq([ + 0x7bcfa7a25aa30fda, + 0xdc17dec12a927e7c, + 0x2f088dd86b4ebef1, + 0xd1ca2087da74d4a7, + 0x2da2596696cebc1d, + 0x0e2b7eedbbfd87d2, + ]), + }, + // z ^ ((p ^ 4 - 1) / 6) + Fq2 { + c0: Fq([ + 0x30f1361b798a64e8, + 0xf3b8ddab7ece5a2a, + 0x16a8ca3ac61577f7, + 0xc26a2ff874fd029b, + 0x3636b76660701c6e, + 0x051ba4ab241b6160, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ ((p ^ 5 - 1) / 6) + Fq2 { + c0: Fq([ + 0x3726c30af242c66c, + 0x7c2ac1aad1b6fe70, + 0xa04007fbba4b14a2, + 0xef517c3266341429, + 0x0095ba654ed2226b, + 0x02e370eccc86f7dd, + ]), + c1: Fq([ + 0x82d83cf50dbce43f, + 0xa2813e53df9d018f, + 0xc6f0caa53c65e181, + 0x7525cf528d50fe95, + 0x4a85ed50f4798a6b, + 0x171da0fd6cf8eebd, + ]), + }, + // z ^ ((p ^ 6 - 1) / 6) + Fq2 { + c0: Fq([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x07e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x040ab3263eff0206, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ ((p ^ 7 - 1) / 6) + Fq2 { + c0: Fq([ + 0xb2f66aad4ce5d646, + 0x5842a06bfc497cec, + 0xcf4895d42599d394, + 0xc11b9cba40a8e8d0, + 0x2e3813cbe5a0de89, + 0x110eefda88847faf, + ]), + c1: Fq([ + 0x07089552b319d465, + 0xc6695f92b50a8313, + 0x97e83cccd117228f, + 0xa35baecab2dc29ee, + 0x1ce393ea5daace4d, + 0x08f2220fb0fb66eb, + ]), + }, + // z ^ ((p ^ 8 - 1) / 6) + Fq2 { + c0: Fq([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ ((p ^ 9 - 1) / 6) + Fq2 { + c0: Fq([ + 0x7bcfa7a25aa30fda, + 0xdc17dec12a927e7c, + 0x2f088dd86b4ebef1, + 0xd1ca2087da74d4a7, + 0x2da2596696cebc1d, + 0x0e2b7eedbbfd87d2, + ]), + c1: Fq([ + 0x3e2f585da55c9ad1, + 0x4294213d86c18183, + 0x382844c88b623732, + 0x92ad2afd19103e18, + 0x1d794e4fac7cf0b9, + 0x0bd592fc7d825ec8, + ]), + }, + // z ^ ((p ^ 10 - 1) / 6) + Fq2 { + c0: Fq([ + 0x890dc9e4867545c3, + 0x2af322533285a5d5, + 0x50880866309b7e2c, + 0xa20d1b8c7e881024, + 0x14e4f04fe2db9068, + 0x14e56d3f1564853a, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ ((p ^ 11 - 1) / 6) + Fq2 { + c0: Fq([ + 0x82d83cf50dbce43f, + 0xa2813e53df9d018f, + 0xc6f0caa53c65e181, + 0x7525cf528d50fe95, + 0x4a85ed50f4798a6b, + 0x171da0fd6cf8eebd, + ]), + c1: Fq([ + 0x3726c30af242c66c, + 0x7c2ac1aad1b6fe70, + 0xa04007fbba4b14a2, + 0xef517c3266341429, + 0x0095ba654ed2226b, + 0x02e370eccc86f7dd, + ]), + }, +]; + +#[cfg(test)] +mod test { + use super::*; + crate::field_testing_suite!(Fq12, "field_arithmetic"); + // extension field-specific + crate::field_testing_suite!(Fq12, "quadratic_sparse_mul", Fq6, Fq2); + crate::field_testing_suite!( + Fq12, + "frobenius", + // Frobenius endomorphism power parameter for extension field + // ϕ: E → E + // (x, y) ↦ (x^p, y^p) + // p: modulus of base field (Here, Fq::MODULUS) + Fq::MODULUS_LIMBS + ); +} diff --git a/src/bls12381/fq2.rs b/src/bls12381/fq2.rs new file mode 100644 index 00000000..0ee075da --- /dev/null +++ b/src/bls12381/fq2.rs @@ -0,0 +1,93 @@ +use super::fq::Fq; +use crate::ff::{Field, FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; +use crate::ff_ext::quadratic::{QuadExtField, QuadExtFieldArith, SQRT}; +use crate::ff_ext::{ExtField, Legendre}; +use core::convert::TryInto; +use std::cmp::Ordering; +use subtle::{Choice, CtOption}; + +crate::impl_binops_additive!(Fq2, Fq2); +crate::impl_binops_multiplicative!(Fq2, Fq2); +crate::impl_binops_calls!(Fq2); +crate::impl_sum_prod!(Fq2); +crate::impl_tower2!(Fq, Fq2); +crate::impl_tower2_from_uniform_bytes!(Fq, Fq2, 128); + +pub type Fq2 = QuadExtField; +impl QuadExtFieldArith for Fq2 { + type Base = Fq; + const SQRT: SQRT = SQRT::Algorithm9 { + q_minus_3_over_4: &[ + 0xee7fbfffffffeaaa, + 0x07aaffffac54ffff, + 0xd9cc34a83dac3d89, + 0xd91dd2e13ce144af, + 0x92c6e9ed90d2eb35, + 0x0680447a8e5ff9a6, + ], + q_minus_1_over_2: &[ + 0xdcff7fffffffd555, + 0x0f55ffff58a9ffff, + 0xb39869507b587b12, + 0xb23ba5c279c2895f, + 0x258dd3db21a5d66b, + 0x0d0088f51cbff34d, + ], + }; + + fn square_assign(el: &mut QuadExtField) { + let a = el.c0 + el.c1; + let b = el.c0 - el.c1; + let c = el.c0.double(); + el.c0 = a * b; + el.c1 = c * el.c1; + } +} + +impl ExtField for Fq2 { + const NON_RESIDUE: Self = Fq2::new(Fq::ONE, Fq::ONE); + + fn mul_by_nonresidue(&self) -> Self { + Self { + c0: self.c0 - self.c1, + c1: self.c0 + self.c1, + } + } + + fn frobenius_map(&mut self, power: usize) { + if power % 2 != 0 { + self.conjugate(); + } + } +} + +#[cfg(test)] +mod test { + + use super::*; + crate::field_testing_suite!(Fq2, "field_arithmetic"); + crate::field_testing_suite!(Fq2, "conversion"); + crate::field_testing_suite!(Fq2, "serialization"); + crate::field_testing_suite!(Fq2, "quadratic_residue"); + crate::field_testing_suite!(Fq2, "sqrt"); + crate::field_testing_suite!(Fq2, "zeta", Fq); + // extension field-specific + crate::field_testing_suite!(Fq2, "f2_tests", Fq); + crate::field_testing_suite!( + Fq2, + "frobenius", + // Frobenius endomorphism power parameter for extension field + // ϕ: E → E + // (x, y) ↦ (x^p, y^p) + // p: modulus of base field (Here, Fq::MODULUS) + Fq::MODULUS_LIMBS + ); + + #[test] + fn test_fq2_mul_nonresidue() { + let e = Fq2::random(rand_core::OsRng); + let a0 = e.mul_by_nonresidue(); + let a1 = e * Fq2::NON_RESIDUE; + assert_eq!(a0, a1); + } +} diff --git a/src/bls12381/fq6.rs b/src/bls12381/fq6.rs new file mode 100644 index 00000000..bb3ed5b2 --- /dev/null +++ b/src/bls12381/fq6.rs @@ -0,0 +1,301 @@ +use super::fq::Fq; +use super::fq2::Fq2; +use crate::ff_ext::{ + cubic::{CubicExtField, CubicExtFieldArith, CubicSparseMul}, + ExtField, +}; +use ff::Field; + +crate::impl_binops_additive!(Fq6, Fq6); +crate::impl_binops_multiplicative!(Fq6, Fq6); +crate::impl_binops_calls!(Fq6); +crate::impl_sum_prod!(Fq6); +pub type Fq6 = CubicExtField; + +impl CubicExtFieldArith for Fq6 { + type Base = Fq2; +} + +impl CubicSparseMul for Fq6 { + type Base = Fq2; +} + +impl ExtField for Fq6 { + const NON_RESIDUE: Self = Fq6::new(Fq2::ZERO, Fq2::ONE, Fq2::ZERO); + + fn frobenius_map(&mut self, power: usize) { + self.c0.frobenius_map(power); + self.c1.frobenius_map(power); + self.c2.frobenius_map(power); + self.c1.mul_assign(&FROBENIUS_COEFF_FQ6_C1[power % 6]); + self.c2.mul_assign(&FROBENIUS_COEFF_FQ6_C2[power % 6]); + } + + fn mul_by_nonresidue(self: &Fq6) -> Fq6 { + let c0 = self.c2.mul_by_nonresidue(); + let c1 = self.c0; + let c2 = self.c1; + Self { c0, c1, c2 } + } +} + +pub const FROBENIUS_COEFF_FQ6_C1: [Fq2; 6] = [ + // z ^ (( p ^ 0 - 1) / 3) + Fq2 { + c0: Fq([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ (( p ^ 1 - 1) / 3) + Fq2 { + c0: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + c1: Fq([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741, + ]), + }, + // z ^ (( p ^ 2 - 1) / 3) + Fq2 { + c0: Fq([ + 0x30f1361b798a64e8, + 0xf3b8ddab7ece5a2a, + 0x16a8ca3ac61577f7, + 0xc26a2ff874fd029b, + 0x3636b76660701c6e, + 0x051ba4ab241b6160, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ (( p ^ 3 - 1) / 3) + Fq2 { + c0: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + c1: Fq([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ]), + }, + // z ^ (( p ^ 4 - 1) / 3) + Fq2 { + c0: Fq([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ (( p ^ 5 - 1) / 3) + Fq2 { + c0: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + c1: Fq([ + 0x30f1361b798a64e8, + 0xf3b8ddab7ece5a2a, + 0x16a8ca3ac61577f7, + 0xc26a2ff874fd029b, + 0x3636b76660701c6e, + 0x051ba4ab241b6160, + ]), + }, +]; + +// z = u + 1 +pub const FROBENIUS_COEFF_FQ6_C2: [Fq2; 6] = [ + // z ^ (( 2 * p ^ 0 - 2) / 3) + Fq2 { + c0: Fq([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ (( 2 * p ^ 1 - 2) / 3) + Fq2 { + c0: Fq([ + 0x890dc9e4867545c3, + 0x2af322533285a5d5, + 0x50880866309b7e2c, + 0xa20d1b8c7e881024, + 0x14e4f04fe2db9068, + 0x14e56d3f1564853a, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ (( 2 * p ^ 2 - 2) / 3) + Fq2 { + c0: Fq([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ (( 2 * p ^ 3 - 2) / 3) + Fq2 { + c0: Fq([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x07e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x040ab3263eff0206, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ (( 2 * p ^ 4 - 2) / 3) + Fq2 { + c0: Fq([ + 0x30f1361b798a64e8, + 0xf3b8ddab7ece5a2a, + 0x16a8ca3ac61577f7, + 0xc26a2ff874fd029b, + 0x3636b76660701c6e, + 0x051ba4ab241b6160, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, + // z ^ (( 2 * p ^ 5 - 2) / 3) + Fq2 { + c0: Fq([ + 0xecfb361b798dba3a, + 0xc100ddb891865a2c, + 0x0ec08ff1232bda8e, + 0xd5c13cc6f1ca4721, + 0x47222a47bf7b5c04, + 0x0110f184e51c5f59, + ]), + c1: Fq([ + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]), + }, +]; + +#[cfg(test)] +mod test { + use super::*; + crate::field_testing_suite!(Fq6, "field_arithmetic"); + // extension field-specific + crate::field_testing_suite!(Fq6, "cubic_sparse_mul", Fq2); + crate::field_testing_suite!( + Fq6, + "frobenius", + // Frobenius endomorphism power parameter for extension field + // ϕ: E → E + // (x, y) ↦ (x^p, y^p) + // p: modulus of base field (Here, Fq::MODULUS) + Fq::MODULUS_LIMBS + ); + + #[test] + fn test_fq6_mul_nonresidue() { + use ff::Field; + let e = Fq6::random(rand_core::OsRng); + let a0 = e.mul_by_nonresidue(); + let a1 = e * Fq6::NON_RESIDUE; + assert_eq!(a0, a1); + } +} diff --git a/src/bls12381/fr.rs b/src/bls12381/fr.rs new file mode 100644 index 00000000..b409ebf4 --- /dev/null +++ b/src/bls12381/fr.rs @@ -0,0 +1,37 @@ +use core::convert::TryInto; +use halo2derive::impl_field; +use rand::RngCore; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +impl_field!( + bls12381_scalar, + Fr, + modulus = "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", + mul_gen = "7", + zeta = "ac45a4010001a40200000000ffffffff", + from_uniform = [64], + endian = "little", +); + +crate::extend_field_legendre!(Fr); +crate::impl_binops_calls!(Fr); +crate::impl_binops_additive!(Fr, Fr); +crate::impl_binops_multiplicative!(Fr, Fr); +crate::field_bits!(Fr); +crate::serialize_deserialize_primefield!(Fr); +crate::impl_from_u64!(Fr); + +#[cfg(test)] +mod test { + use super::*; + crate::field_testing_suite!(Fr, "field_arithmetic"); + crate::field_testing_suite!(Fr, "conversion"); + crate::field_testing_suite!(Fr, "serialization"); + crate::field_testing_suite!(Fr, "quadratic_residue"); + crate::field_testing_suite!(Fr, "bits"); + crate::field_testing_suite!(Fr, "serialization_check"); + crate::field_testing_suite!(Fr, "constants"); + crate::field_testing_suite!(Fr, "sqrt"); + crate::field_testing_suite!(Fr, "zeta"); + crate::field_testing_suite!(Fr, "from_uniform_bytes", 64); +} diff --git a/src/bls12381/g1.rs b/src/bls12381/g1.rs new file mode 100644 index 00000000..451f1f0e --- /dev/null +++ b/src/bls12381/g1.rs @@ -0,0 +1,692 @@ +use super::fq::Fq; +use super::Fr; +use crate::serde::{Compressed, CompressedFlagConfig}; +use crate::{ + impl_binops_additive, impl_binops_additive_specify_output, impl_binops_multiplicative, + impl_binops_multiplicative_mixed, new_curve_impl, +}; +use core::cmp; +use core::iter::Sum; +use core::ops::{Add, Mul, Neg, Sub}; +use ff::PrimeField; +use ff::WithSmallOrderMulGroup; +use group::cofactor::CofactorGroup; +use group::{ff::Field, prime::PrimeCurveAffine, Curve, Group, GroupEncoding}; +use pasta_curves::arithmetic::{Coordinates, CurveAffine, CurveExt}; +use rand_core::RngCore; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +new_curve_impl!( + (pub), + G1, + G1Affine, + Fq, + Fr, + (GENERATOR_X, GENERATOR_Y), + A, + B, + "bls12381_g1", + |domain_prefix| hash_to_curve(domain_prefix, hash_to_curve_suite(b"BLS12381G1_XMD:SHA-256_SSWU_RO_")), + crate::serde::CompressedFlagConfig::ThreeSpare +); + +impl Compressed for G1Compressed { + const CONFIG: CompressedFlagConfig = CompressedFlagConfig::ThreeSpare; + fn sign(c: &G1Affine) -> Choice { + c.y.lexicographically_largest() & !c.is_identity() + } + fn resolve(x: Fq, sign_set: Choice) -> CtOption { + G1Affine::y2(x).sqrt().map(|y| { + let y = Fq::conditional_select(&y, &-y, sign_set ^ y.lexicographically_largest()); + G1Affine { x, y } + }) + } +} + +impl group::cofactor::CofactorGroup for G1 { + type Subgroup = G1; + + fn clear_cofactor(&self) -> Self { + self - self.mul_by_x() + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, 1.into()) + } + + fn is_torsion_free(&self) -> Choice { + self.mul_by_x().mul_by_x().neg().endo().ct_eq(self) + } +} + +const GENERATOR_X: Fq = Fq([ + 0x5cb38790fd530c16, + 0x7817fc679976fff5, + 0x154f95c7143ba1c1, + 0xf0ae6acdf3d0e747, + 0xedce6ecc21dbf440, + 0x120177419e0bfb75, +]); + +const GENERATOR_Y: Fq = Fq([ + 0xbaac93d50ce72271, + 0x8c22631a7918fd8e, + 0xdd595f13570725ce, + 0x51ac582950405194, + 0x0e1c8c3fad0059c0, + 0x0bbc3efc5008a26a, +]); + +const A: Fq = Fq::ZERO; +const B: Fq = Fq::from_raw([4, 0, 0, 0, 0, 0]); + +impl G1 { + fn mul_by_x(&self) -> G1 { + let mut acc = G1::identity(); + for (i, b) in super::BLS_X.into_iter().enumerate() { + (i != 0).then(|| acc = acc.double()); + (b == 1).then(|| acc += self); + } + acc.neg() + } +} + +fn hash_to_curve_suite(domain: &[u8]) -> crate::hash_to_curve::Suite { + const SSWU_Z: Fq = Fq::from_raw([11, 0, 0, 0, 0, 0]); + + pub const ISO_A: Fq = Fq([ + 0x2f65_aa0e_9af5_aa51, + 0x8646_4c2d_1e84_16c3, + 0xb85c_e591_b7bd_31e2, + 0x27e1_1c91_b5f2_4e7c, + 0x2837_6eda_6bfc_1835, + 0x1554_55c3_e507_1d85, + ]); + + pub const ISO_B: Fq = Fq([ + 0xfb99_6971_fe22_a1e0, + 0x9aa9_3eb3_5b74_2d6f, + 0x8c47_6013_de99_c5c4, + 0x873e_27c3_a221_e571, + 0xca72_b5e4_5a52_d888, + 0x0682_4061_418a_386b, + ]); + + let iso_map = crate::hash_to_curve::Iso { + a: ISO_A, + b: ISO_B, + map: Box::new(iso_map), + }; + + crate::hash_to_curve::Suite::new(domain, SSWU_Z, crate::hash_to_curve::Method::SSWU(iso_map)) +} + +/// Maps an iso-G1 point to a G1 point. +fn iso_map(x: Fq, y: Fq, z: Fq) -> G1 { + const COEFFS: [&[Fq]; 4] = [&ISO11_XNUM, &ISO11_XDEN, &ISO11_YNUM, &ISO11_YDEN]; + + // xnum, xden, ynum, yden + let mut mapvals = [Fq::zero(); 4]; + + // pre-compute powers of z + let zpows = { + let mut zpows = [Fq::zero(); 15]; + zpows[0] = z; + for idx in 1..zpows.len() { + zpows[idx] = zpows[idx - 1] * z; + } + zpows + }; + + // compute map value by Horner's rule + for idx in 0..4 { + let coeff = COEFFS[idx]; + let clast = coeff.len() - 1; + mapvals[idx] = coeff[clast]; + for jdx in 0..clast { + mapvals[idx] = mapvals[idx] * x + zpows[jdx] * coeff[clast - 1 - jdx]; + } + } + + // x denominator is order 1 less than x numerator, so we need an extra factor of z + mapvals[1] *= z; + + // multiply result of Y map by the y-coord, y / z + mapvals[2] *= y; + mapvals[3] *= z; + + G1 { + x: mapvals[0] * mapvals[3], // xnum * yden, + y: mapvals[2] * mapvals[1], // ynum * xden, + z: mapvals[1] * mapvals[3], // xden * yden + } +} + +#[allow(clippy::type_complexity)] +pub(crate) fn hash_to_curve<'a>( + domain_prefix: &'a str, + suite: crate::hash_to_curve::Suite, +) -> Box G1 + 'a> { + Box::new(move |message| suite.hash_to_curve(domain_prefix, message).clear_cofactor()) +} + +#[cfg(test)] +mod test { + use crate::arithmetic::CurveEndo; + use crate::tests::curve::TestH2C; + use group::UncompressedEncoding; + + use super::*; + crate::curve_testing_suite!(G1); + crate::curve_testing_suite!(G1, "endo_consistency"); + crate::curve_testing_suite!(G1, "endo"); + + #[test] + fn test_cofactor() { + assert!(bool::from( + G1Affine::identity().to_curve().is_torsion_free() + )); + assert!(bool::from( + G1Affine::generator().to_curve().is_torsion_free() + )); + } + + #[test] + fn test_hash_to_curve() { + [ + TestH2C::::new( + b"", + crate::tests::point_from_hex( + "052926add2207b76ca4fa57a8734416c8dc95e24501772c814278700eed6d1e4e8cf62d9c09db0fac349612b759e79a1", + "08ba738453bfed09cb546dbb0783dbb3a5f1f566ed67bb6be0e8c67e2e81a4cc68ee29813bb7994998f3eae0c9c6a265", + ), + ), + TestH2C::::new( + b"abc", + crate::tests::point_from_hex( + "03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f6903", + "0b9c15f3fe6e5cf4211f346271d7b01c8f3b28be689c8429c85b67af215533311f0b8dfaaa154fa6b88176c229f2885d", + ), + ), + TestH2C::::new( + b"abcdef0123456789", + crate::tests::point_from_hex( + "11e0b079dea29a68f0383ee94fed1b940995272407e3bb916bbf268c263ddd57a6a27200a784cbc248e84f357ce82d98", + "03a87ae2caf14e8ee52e51fa2ed8eefe80f02457004ba4d486d6aa1f517c0889501dc7413753f9599b099ebcbbd2d709", + ), + ), + TestH2C::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + crate::tests::point_from_hex( + "15f68eaa693b95ccb85215dc65fa81038d69629f70aeee0d0f677cf22285e7bf58d7cb86eefe8f2e9bc3f8cb84fac488", + "1807a1d50c29f430b8cafc4f8638dfeeadf51211e1602a5f184443076715f91bb90a48ba1e370edce6ae1062f5e6dd38", + ), + ), + TestH2C::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + crate::tests::point_from_hex( + "082aabae8b7dedb0e78aeb619ad3bfd9277a2f77ba7fad20ef6aabdc6c31d19ba5a6d12283553294c1825c4b3ca2dcfe", + "05b84ae5a942248eea39e1d91030458c40153f3b654ab7872d779ad1e942856a20c438e8d99bc8abfbf74729ce1f7ac8", + ), + ) + ].iter().for_each(|test| { + test.run("QUUX-V01-CS02-with-"); + }); + } +} + +/// Coefficients of the 11-isogeny x map's numerator +const ISO11_XNUM: [Fq; 12] = [ + Fq::from_raw([ + 0x4d18_b6f3_af00_131c, + 0x19fa_2197_93fe_e28c, + 0x3f28_85f1_467f_19ae, + 0x23dc_ea34_f2ff_b304, + 0xd15b_58d2_ffc0_0054, + 0x0913_be20_0a20_bef4, + ]), + Fq::from_raw([ + 0x8989_8538_5cdb_bd8b, + 0x3c79_e43c_c7d9_66aa, + 0x1597_e193_f4cd_233a, + 0x8637_ef1e_4d66_23ad, + 0x11b2_2dee_d20d_827b, + 0x0709_7bc5_9987_84ad, + ]), + Fq::from_raw([ + 0xa542_583a_480b_664b, + 0xfc71_69c0_26e5_68c6, + 0x5ba2_ef31_4ed8_b5a6, + 0x5b54_91c0_5102_f0e7, + 0xdf6e_9970_7d2a_0079, + 0x0784_151e_d760_5524, + ]), + Fq::from_raw([ + 0x494e_2128_70f7_2741, + 0xab9b_e52f_bda4_3021, + 0x26f5_5779_94e3_4c3d, + 0x049d_fee8_2aef_bd60, + 0x65da_dd78_2850_5289, + 0x0e93_d431_ea01_1aeb, + ]), + Fq::from_raw([ + 0x90ee_774b_d6a7_4d45, + 0x7ada_1c8a_41bf_b185, + 0x0f1a_8953_b325_f464, + 0x104c_2421_1be4_805c, + 0x1691_39d3_19ea_7a8f, + 0x09f2_0ead_8e53_2bf6, + ]), + Fq::from_raw([ + 0x6ddd_93e2_f436_26b7, + 0xa548_2c9a_a1cc_d7bd, + 0x1432_4563_1883_f4bd, + 0x2e0a_94cc_f77e_c0db, + 0xb028_2d48_0e56_489f, + 0x18f4_bfcb_b436_8929, + ]), + Fq::from_raw([ + 0x23c5_f0c9_5340_2dfd, + 0x7a43_ff69_58ce_4fe9, + 0x2c39_0d3d_2da5_df63, + 0xd0df_5c98_e1f9_d70f, + 0xffd8_9869_a572_b297, + 0x1277_ffc7_2f25_e8fe, + ]), + Fq::from_raw([ + 0x79f4_f049_0f06_a8a6, + 0x85f8_94a8_8030_fd81, + 0x12da_3054_b18b_6410, + 0xe2a5_7f65_0588_0d65, + 0xbba0_74f2_60e4_00f1, + 0x08b7_6279_f621_d028, + ]), + Fq::from_raw([ + 0xe672_45ba_78d5_b00b, + 0x8456_ba9a_1f18_6475, + 0x7888_bff6_e6b3_3bb4, + 0xe215_85b9_a30f_86cb, + 0x05a6_9cdc_ef55_feee, + 0x09e6_99dd_9adf_a5ac, + ]), + Fq::from_raw([ + 0x0de5_c357_bff5_7107, + 0x0a0d_b4ae_6b1a_10b2, + 0xe256_bb67_b3b3_cd8d, + 0x8ad4_5657_4e9d_b24f, + 0x0443_915f_50fd_4179, + 0x098c_4bf7_de8b_6375, + ]), + Fq::from_raw([ + 0xe6b0_617e_7dd9_29c7, + 0xfe6e_37d4_4253_7375, + 0x1daf_deda_137a_489e, + 0xe4ef_d1ad_3f76_7ceb, + 0x4a51_d866_7f0f_e1cf, + 0x054f_df4b_bf1d_821c, + ]), + Fq::from_raw([ + 0x72db_2a50_658d_767b, + 0x8abf_91fa_a257_b3d5, + 0xe969_d683_3764_ab47, + 0x4641_7014_2a10_09eb, + 0xb14f_01aa_db30_be2f, + 0x18ae_6a85_6f40_715d, + ]), +]; + +/// Coefficients of the 11-isogeny x map's denominator +const ISO11_XDEN: [Fq; 11] = [ + Fq::from_raw([ + 0xb962_a077_fdb0_f945, + 0xa6a9_740f_efda_13a0, + 0xc14d_568c_3ed6_c544, + 0xb43f_c37b_908b_133e, + 0x9c0b_3ac9_2959_9016, + 0x0165_aa6c_93ad_115f, + ]), + Fq::from_raw([ + 0x2327_9a3b_a506_c1d9, + 0x92cf_ca0a_9465_176a, + 0x3b29_4ab1_3755_f0ff, + 0x116d_da1c_5070_ae93, + 0xed45_3092_4cec_2045, + 0x0833_83d6_ed81_f1ce, + ]), + Fq::from_raw([ + 0x9885_c2a6_449f_ecfc, + 0x4a2b_54cc_d377_33f0, + 0x17da_9ffd_8738_c142, + 0xa0fb_a727_32b3_fafd, + 0xff36_4f36_e54b_6812, + 0x0f29_c13c_6605_23e2, + ]), + Fq::from_raw([ + 0xe349_cc11_8278_f041, + 0xd487_228f_2f32_04fb, + 0xc9d3_2584_9ade_5150, + 0x43a9_2bd6_9c15_c2df, + 0x1c2c_7844_bc41_7be4, + 0x1202_5184_f407_440c, + ]), + Fq::from_raw([ + 0x587f_65ae_6acb_057b, + 0x1444_ef32_5140_201f, + 0xfbf9_95e7_1270_da49, + 0xccda_0660_7243_6a42, + 0x7408_904f_0f18_6bb2, + 0x13b9_3c63_edf6_c015, + ]), + Fq::from_raw([ + 0xfb91_8622_cd14_1920, + 0x4a4c_6442_3eca_ddb4, + 0x0beb_2329_27f7_fb26, + 0x30f9_4df6_f83a_3dc2, + 0xaeed_d424_d780_f388, + 0x06cc_402d_d594_bbeb, + ]), + Fq::from_raw([ + 0xd41f_7611_51b2_3f8f, + 0x32a9_2465_4357_19b3, + 0x64f4_36e8_88c6_2cb9, + 0xdf70_a9a1_f757_c6e4, + 0x6933_a38d_5b59_4c81, + 0x0c6f_7f72_37b4_6606, + ]), + Fq::from_raw([ + 0x693c_0874_7876_c8f7, + 0x22c9_850b_f9cf_80f0, + 0x8e90_71da_b950_c124, + 0x89bc_62d6_1c7b_af23, + 0xbc6b_e2d8_dad5_7c23, + 0x1791_6987_aa14_a122, + ]), + Fq::from_raw([ + 0x1be3_ff43_9c13_16fd, + 0x9965_243a_7571_dfa7, + 0xc7f7_f629_62f5_cd81, + 0x32c6_aa9a_f394_361c, + 0xbbc2_ee18_e1c2_27f4, + 0x0c10_2cba_c531_bb34, + ]), + Fq::from_raw([ + 0x9976_14c9_7bac_bf07, + 0x61f8_6372_b991_92c0, + 0x5b8c_95fc_1435_3fc3, + 0xca2b_066c_2a87_492f, + 0x1617_8f5b_bf69_8711, + 0x12a6_dcd7_f0f4_e0e8, + ]), + Fq::from_raw([ + 0x7609_0000_0002_fffd, + 0xebf4_000b_c40c_0002, + 0x5f48_9857_53c7_58ba, + 0x77ce_5853_7052_5745, + 0x5c07_1a97_a256_ec6d, + 0x15f6_5ec3_fa80_e493, + ]), +]; + +/// Coefficients of the 11-isogeny y map's numerator +const ISO11_YNUM: [Fq; 16] = [ + Fq::from_raw([ + 0x2b56_7ff3_e283_7267, + 0x1d4d_9e57_b958_a767, + 0xce02_8fea_04bd_7373, + 0xcc31_a30a_0b6c_d3df, + 0x7d7b_18a6_8269_2693, + 0x0d30_0744_d42a_0310, + ]), + Fq::from_raw([ + 0x99c2_555f_a542_493f, + 0xfe7f_53cc_4874_f878, + 0x5df0_608b_8f97_608a, + 0x14e0_3832_052b_49c8, + 0x7063_26a6_957d_d5a4, + 0x0a8d_add9_c241_4555, + ]), + Fq::from_raw([ + 0x13d9_4292_2a5c_f63a, + 0x357e_33e3_6e26_1e7d, + 0xcf05_a27c_8456_088d, + 0x0000_bd1d_e7ba_50f0, + 0x83d0_c753_2f8c_1fde, + 0x13f7_0bf3_8bbf_2905, + ]), + Fq::from_raw([ + 0x5c57_fd95_bfaf_bdbb, + 0x28a3_59a6_5e54_1707, + 0x3983_ceb4_f636_0b6d, + 0xafe1_9ff6_f97e_6d53, + 0xb346_8f45_5019_2bf7, + 0x0bb6_cde4_9d8b_a257, + ]), + Fq::from_raw([ + 0x590b_62c7_ff8a_513f, + 0x314b_4ce3_72ca_cefd, + 0x6bef_32ce_94b8_a800, + 0x6ddf_84a0_9571_3d5f, + 0x64ea_ce4c_b098_2191, + 0x0386_213c_651b_888d, + ]), + Fq::from_raw([ + 0xa531_0a31_111b_bcdd, + 0xa14a_c0f5_da14_8982, + 0xf9ad_9cc9_5423_d2e9, + 0xaa6e_c095_283e_e4a7, + 0xcf5b_1f02_2e1c_9107, + 0x01fd_df5a_ed88_1793, + ]), + Fq::from_raw([ + 0x65a5_72b0_d7a7_d950, + 0xe25c_2d81_8347_3a19, + 0xc2fc_ebe7_cb87_7dbd, + 0x05b2_d36c_769a_89b0, + 0xba12_961b_e86e_9efb, + 0x07eb_1b29_c1df_de1f, + ]), + Fq::from_raw([ + 0x93e0_9572_f7c4_cd24, + 0x364e_9290_7679_5091, + 0x8569_467e_68af_51b5, + 0xa47d_a894_39f5_340f, + 0xf4fa_9180_82e4_4d64, + 0x0ad5_2ba3_e669_5a79, + ]), + Fq::from_raw([ + 0x9114_2984_4e0d_5f54, + 0xd03f_51a3_516b_b233, + 0x3d58_7e56_4053_6e66, + 0xfa86_d2a3_a9a7_3482, + 0xa90e_d5ad_f1ed_5537, + 0x149c_9c32_6a5e_7393, + ]), + Fq::from_raw([ + 0x462b_beb0_3c12_921a, + 0xdc9a_f5fa_0a27_4a17, + 0x9a55_8ebd_e836_ebed, + 0x649e_f8f1_1a4f_ae46, + 0x8100_e165_2b3c_dc62, + 0x1862_bd62_c291_dacb, + ]), + Fq::from_raw([ + 0x05c9_b8ca_89f1_2c26, + 0x0194_160f_a9b9_ac4f, + 0x6a64_3d5a_6879_fa2c, + 0x1466_5bdd_8846_e19d, + 0xbb1d_0d53_af3f_f6bf, + 0x12c7_e1c3_b289_62e5, + ]), + Fq::from_raw([ + 0xb55e_bf90_0b8a_3e17, + 0xfedc_77ec_1a92_01c4, + 0x1f07_db10_ea1a_4df4, + 0x0dfb_d15d_c41a_594d, + 0x3895_47f2_334a_5391, + 0x0241_9f98_1658_71a4, + ]), + Fq::from_raw([ + 0xb416_af00_0745_fc20, + 0x8e56_3e9d_1ea6_d0f5, + 0x7c76_3e17_763a_0652, + 0x0145_8ef0_159e_bbef, + 0x8346_fe42_1f96_bb13, + 0x0d2d_7b82_9ce3_24d2, + ]), + Fq::from_raw([ + 0x9309_6bb5_38d6_4615, + 0x6f2a_2619_951d_823a, + 0x8f66_b3ea_5951_4fa4, + 0xf563_e637_04f7_092f, + 0x724b_136c_4cf2_d9fa, + 0x0469_59cf_cfd0_bf49, + ]), + Fq::from_raw([ + 0xea74_8d4b_6e40_5346, + 0x91e9_079c_2c02_d58f, + 0x4106_4965_946d_9b59, + 0xa067_31f1_d2bb_e1ee, + 0x07f8_97e2_67a3_3f1b, + 0x1017_2909_1921_0e5f, + ]), + Fq::from_raw([ + 0x872a_a6c1_7d98_5097, + 0xeecc_5316_1264_562a, + 0x07af_e37a_fff5_5002, + 0x5475_9078_e5be_6838, + 0xc4b9_2d15_db8a_cca8, + 0x106d_87d1_b51d_13b9, + ]), +]; + +/// Coefficients of the 11-isogeny y map's denominator +const ISO11_YDEN: [Fq; 16] = [ + Fq::from_raw([ + 0xeb6c_359d_47e5_2b1c, + 0x18ef_5f8a_1063_4d60, + 0xddfa_71a0_889d_5b7e, + 0x723e_71dc_c5fc_1323, + 0x52f4_5700_b70d_5c69, + 0x0a8b_981e_e476_91f1, + ]), + Fq::from_raw([ + 0x616a_3c4f_5535_b9fb, + 0x6f5f_0373_95db_d911, + 0xf25f_4cc5_e35c_65da, + 0x3e50_dffe_a3c6_2658, + 0x6a33_dca5_2356_0776, + 0x0fad_eff7_7b6b_fe3e, + ]), + Fq::from_raw([ + 0x2be9_b66d_f470_059c, + 0x24a2_c159_a3d3_6742, + 0x115d_be7a_d10c_2a37, + 0xb663_4a65_2ee5_884d, + 0x04fe_8bb2_b8d8_1af4, + 0x01c2_a7a2_56fe_9c41, + ]), + Fq::from_raw([ + 0xf27b_f8ef_3b75_a386, + 0x898b_3674_76c9_073f, + 0x2448_2e6b_8c2f_4e5f, + 0xc8e0_bbd6_fe11_0806, + 0x59b0_c17f_7631_448a, + 0x1103_7cd5_8b3d_bfbd, + ]), + Fq::from_raw([ + 0x31c7_912e_a267_eec6, + 0x1dbf_6f1c_5fcd_b700, + 0xd30d_4fe3_ba86_fdb1, + 0x3cae_528f_bee9_a2a4, + 0xb1cc_e69b_6aa9_ad9a, + 0x0443_93bb_632d_94fb, + ]), + Fq::from_raw([ + 0xc66e_f6ef_eeb5_c7e8, + 0x9824_c289_dd72_bb55, + 0x71b1_a4d2_f119_981d, + 0x104f_c1aa_fb09_19cc, + 0x0e49_df01_d942_a628, + 0x096c_3a09_7732_72d4, + ]), + Fq::from_raw([ + 0x9abc_11eb_5fad_eff4, + 0x32dc_a50a_8857_28f0, + 0xfb1f_a372_1569_734c, + 0xc4b7_6271_ea65_06b3, + 0xd466_a755_99ce_728e, + 0x0c81_d464_5f4c_b6ed, + ]), + Fq::from_raw([ + 0x4199_f10e_5b8b_e45b, + 0xda64_e495_b1e8_7930, + 0xcb35_3efe_9b33_e4ff, + 0x9e9e_fb24_aa64_24c6, + 0xf08d_3368_0a23_7465, + 0x0d33_7802_3e4c_7406, + ]), + Fq::from_raw([ + 0x7eb4_ae92_ec74_d3a5, + 0xc341_b4aa_9fac_3497, + 0x5be6_0389_9e90_7687, + 0x03bf_d9cc_a75c_bdeb, + 0x564c_2935_a96b_fa93, + 0x0ef3_c333_71e2_fdb5, + ]), + Fq::from_raw([ + 0x7ee9_1fd4_49f6_ac2e, + 0xe5d5_bd5c_b935_7a30, + 0x773a_8ca5_196b_1380, + 0xd0fd_a172_174e_d023, + 0x6cb9_5e0f_a776_aead, + 0x0d22_d5a4_0cec_7cff, + ]), + Fq::from_raw([ + 0xf727_e092_85fd_8519, + 0xdc9d_55a8_3017_897b, + 0x7549_d8bd_0578_94ae, + 0x1784_1961_3d90_d8f8, + 0xfce9_5ebd_eb5b_490a, + 0x0467_ffae_f23f_c49e, + ]), + Fq::from_raw([ + 0xc176_9e6a_7c38_5f1b, + 0x79bc_930d_eac0_1c03, + 0x5461_c75a_23ed_e3b5, + 0x6e20_829e_5c23_0c45, + 0x828e_0f1e_772a_53cd, + 0x116a_efa7_4912_7bff, + ]), + Fq::from_raw([ + 0x101c_10bf_2744_c10a, + 0xbbf1_8d05_3a6a_3154, + 0xa0ec_f39e_f026_f602, + 0xfc00_9d49_96dc_5153, + 0xb900_0209_d5bd_08d3, + 0x189e_5fe4_470c_d73c, + ]), + Fq::from_raw([ + 0x7ebd_546c_a157_5ed2, + 0xe47d_5a98_1d08_1b55, + 0x57b2_b625_b6d4_ca21, + 0xb0a1_ba04_2285_20cc, + 0x9873_8983_c210_7ff3, + 0x13dd_dbc4_799d_81d6, + ]), + Fq::from_raw([ + 0x0931_9f2e_3983_4935, + 0x039e_952c_bdb0_5c21, + 0x55ba_77a9_a2f7_6493, + 0xfd04_e3df_c608_6467, + 0xfb95_832e_7d78_742e, + 0x0ef9_c24e_ccaf_5e0e, + ]), + Fq::from_raw([ + 0x7609_0000_0002_fffd, + 0xebf4_000b_c40c_0002, + 0x5f48_9857_53c7_58ba, + 0x77ce_5853_7052_5745, + 0x5c07_1a97_a256_ec6d, + 0x15f6_5ec3_fa80_e493, + ]), +]; diff --git a/src/bls12381/g2.rs b/src/bls12381/g2.rs new file mode 100644 index 00000000..df77bb51 --- /dev/null +++ b/src/bls12381/g2.rs @@ -0,0 +1,640 @@ +use crate::bls12381::fq::Fq; +use crate::bls12381::fq2::Fq2; +use crate::bls12381::fr::Fr; +use crate::ff::WithSmallOrderMulGroup; +use crate::ff::{Field, PrimeField}; +use crate::ff_ext::ExtField; +use crate::group::Curve; +use crate::group::{cofactor::CofactorGroup, prime::PrimeCurveAffine, Group, GroupEncoding}; +use crate::serde::{Compressed, CompressedFlagConfig}; +use crate::{ + impl_binops_additive, impl_binops_additive_specify_output, impl_binops_multiplicative, + impl_binops_multiplicative_mixed, new_curve_impl, +}; +use crate::{Coordinates, CurveAffine, CurveExt}; +use core::cmp; +use core::fmt::Debug; +use core::iter::Sum; +use core::ops::{Add, Mul, Neg, Sub}; +use rand::RngCore; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +const G2_GENERATOR_X: Fq2 = Fq2 { + c0: Fq([ + 0xf5f2_8fa2_0294_0a10, + 0xb3f5_fb26_87b4_961a, + 0xa1a8_93b5_3e2a_e580, + 0x9894_999d_1a3c_aee9, + 0x6f67_b763_1863_366b, + 0x0581_9192_4350_bcd7, + ]), + c1: Fq([ + 0xa5a9_c075_9e23_f606, + 0xaaa0_c59d_bccd_60c3, + 0x3bb1_7e18_e286_7806, + 0x1b1a_b6cc_8541_b367, + 0xc2b6_ed0e_f215_8547, + 0x1192_2a09_7360_edf3, + ]), +}; + +const G2_GENERATOR_Y: Fq2 = Fq2 { + c0: Fq([ + 0x4c73_0af8_6049_4c4a, + 0x597c_fa1f_5e36_9c5a, + 0xe7e6_856c_aa0a_635a, + 0xbbef_b5e9_6e0d_495f, + 0x07d3_a975_f0ef_25a2, + 0x0083_fd8e_7e80_dae5, + ]), + c1: Fq([ + 0xadc0_fc92_df64_b05d, + 0x18aa_270a_2b14_61dc, + 0x86ad_ac6a_3be4_eba0, + 0x7949_5c4e_c93d_a33a, + 0xe717_5850_a43c_caed, + 0x0b2b_c2a1_63de_1bf2, + ]), +}; + +const G2_B: Fq2 = Fq2 { + c0: Fq::from_raw([4, 0, 0, 0, 0, 0]), + c1: Fq::from_raw([4, 0, 0, 0, 0, 0]), +}; + +const G2_A: Fq2 = Fq2::ZERO; + +new_curve_impl!( + (pub), + G2, + G2Affine, + Fq2, + Fr, + (G2_GENERATOR_X, G2_GENERATOR_Y), + G2_A, + G2_B, + "bls12381_g2", + |domain_prefix| hash_to_curve(domain_prefix, hash_to_curve_suite(b"BLS12381G2_XMD:SHA-256_SSWU_RO_")), + crate::serde::CompressedFlagConfig::ThreeSpare + +); + +impl crate::serde::endian::EndianRepr for Fq2 { + const ENDIAN: crate::serde::endian::Endian = Fq::ENDIAN; + + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> subtle::CtOption { + Fq2::from_bytes(bytes[..Fq2::SIZE].try_into().unwrap()) + } +} + +impl Compressed for G2Compressed { + const CONFIG: CompressedFlagConfig = CompressedFlagConfig::ThreeSpare; + fn sign(c: &G2Affine) -> Choice { + c.y.lexicographically_largest() & !c.is_identity() + } + fn resolve(x: Fq2, sign_set: Choice) -> CtOption { + G2Affine::y2(x).sqrt().map(|y| { + let y = Fq2::conditional_select(&y, &-y, sign_set ^ y.lexicographically_largest()); + G2Affine { x, y } + }) + } +} + +impl group::cofactor::CofactorGroup for G2 { + type Subgroup = G2; + + /// Clears the cofactor, using [Budroni-Pintore](https://ia.cr/2017/419). + /// This is equivalent to multiplying by $h\_\textrm{eff} = 3(z^2 - 1) \cdot + /// h_2$, where $h_2$ is the cofactor of $\mathbb{G}\_2$ and $z$ is the + /// parameter of BLS12-381. + fn clear_cofactor(&self) -> G2 { + let t1 = self.mul_by_x(); // [x] P + let t2 = self.psi(); // psi(P) + + self.double().psi2() // psi^2(2P) + + (t1 + t2).mul_by_x() // psi^2(2P) + [x^2] P + [x] psi(P) + - t1 // psi^2(2P) + [x^2 - x] P + [x] psi(P) + - t2 // psi^2(2P) + [x^2 - x] P + [x - 1] psi(P) + - self // psi^2(2P) + [x^2 - x - 1] P + [x - 1] psi(P) + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, 1.into()) + } + + /// Returns true if this point is free of an $h$-torsion component, and so it + /// exists within the $q$-order subgroup $\mathbb{G}_2$. This should always return true + /// unless an "unchecked" API was used. + fn is_torsion_free(&self) -> Choice { + // Algorithm from Section 4 of https://eprint.iacr.org/2021/1130 + // Updated proof of correctness in https://eprint.iacr.org/2022/352 + // + // Check that psi(P) == [x] P + self.psi().ct_eq(&self.mul_by_x()) + } +} + +impl G2 { + /// Multiply `self` by `crate::BLS_X`, using double and add. + + fn mul_by_x(&self) -> G2 { + let mut acc = G2::identity(); + for (i, b) in super::BLS_X.into_iter().enumerate() { + (i != 0).then(|| acc = acc.double()); + (b == 1).then(|| acc += self); + } + acc.neg() + } + + fn psi(&self) -> G2 { + // 1 / ((u+1) ^ ((q-1)/3)) + let psi_coeff_x = Fq2 { + c0: Fq::zero(), + c1: Fq([ + 0x890dc9e4867545c3, + 0x2af322533285a5d5, + 0x50880866309b7e2c, + 0xa20d1b8c7e881024, + 0x14e4f04fe2db9068, + 0x14e56d3f1564853a, + ]), + }; + // 1 / ((u+1) ^ (p-1)/2) + let psi_coeff_y = Fq2 { + c0: Fq([ + 0x3e2f585da55c9ad1, + 0x4294213d86c18183, + 0x382844c88b623732, + 0x92ad2afd19103e18, + 0x1d794e4fac7cf0b9, + 0x0bd592fc7d825ec8, + ]), + c1: Fq([ + 0x7bcfa7a25aa30fda, + 0xdc17dec12a927e7c, + 0x2f088dd86b4ebef1, + 0xd1ca2087da74d4a7, + 0x2da2596696cebc1d, + 0x0e2b7eedbbfd87d2, + ]), + }; + + // x = frobenius(x)/((u+1)^((p-1)/3)) + let mut x = self.x; + x.frobenius_map(1); + x.mul_assign(&psi_coeff_x); + + // y = frobenius(y)/(u+1)^((p-1)/2) + let mut y = self.y; + y.frobenius_map(1); + y.mul_assign(&psi_coeff_y); + + // z = frobenius(z) + let mut z = self.z; + z.frobenius_map(1); + + G2 { x, y, z } + } + + fn psi2(&self) -> G2 { + // 1 / 2 ^ ((q-1)/3) + let psi2_coeff_x = Fq2 { + c0: Fq([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741, + ]), + c1: Fq::zero(), + }; + + G2 { + // x = frobenius^2(x)/2^((p-1)/3); note that q^2 is the order of the field. + x: self.x * psi2_coeff_x, + // y = -frobenius^2(y); note that q^2 is the order of the field. + y: self.y.neg(), + // z = z + z: self.z, + } + } +} + +fn hash_to_curve_suite(domain: &[u8]) -> crate::hash_to_curve::Suite { + const SSWU_Z: Fq2 = Fq2 { + c0: Fq([ + 0x87eb_ffff_fff9_555c, + 0x656f_ffe5_da8f_fffa, + 0x0fd0_7493_45d3_3ad2, + 0xd951_e663_0665_76f4, + 0xde29_1a3d_41e9_80d3, + 0x0815_664c_7dfe_040d, + ]), + c1: Fq([ + 0x43f5_ffff_fffc_aaae, + 0x32b7_fff2_ed47_fffd, + 0x07e8_3a49_a2e9_9d69, + 0xeca8_f331_8332_bb7a, + 0xef14_8d1e_a0f4_c069, + 0x040a_b326_3eff_0206, + ]), + }; + + const ISO_A: Fq2 = Fq2 { + c0: Fq::zero(), + c1: Fq([ + 0xe53a_0000_0313_5242, + 0x0108_0c0f_def8_0285, + 0xe788_9edb_e340_f6bd, + 0x0b51_3751_2631_0601, + 0x02d6_9857_17c7_44ab, + 0x1220_b4e9_79ea_5467, + ]), + }; + + const ISO_B: Fq2 = Fq2 { + c0: Fq([ + 0x22ea_0000_0cf8_9db2, + 0x6ec8_32df_7138_0aa4, + 0x6e1b_9440_3db5_a66e, + 0x75bf_3c53_a794_73ba, + 0x3dd3_a569_412c_0a34, + 0x125c_db5e_74dc_4fd1, + ]), + c1: Fq([ + 0x22ea_0000_0cf8_9db2, + 0x6ec8_32df_7138_0aa4, + 0x6e1b_9440_3db5_a66e, + 0x75bf_3c53_a794_73ba, + 0x3dd3_a569_412c_0a34, + 0x125c_db5e_74dc_4fd1, + ]), + }; + let iso_map = crate::hash_to_curve::Iso { + a: ISO_A, + b: ISO_B, + map: Box::new(iso_map), + }; + + crate::hash_to_curve::Suite::new(domain, SSWU_Z, crate::hash_to_curve::Method::SSWU(iso_map)) +} + +/// Maps an iso-G1 point to a G1 point. +fn iso_map(x: Fq2, y: Fq2, z: Fq2) -> G2 { + const COEFFS: [&[Fq2]; 4] = [&ISO3_XNUM, &ISO3_XDEN, &ISO3_YNUM, &ISO3_YDEN]; + + // xnum, xden, ynum, yden + let mut mapvals = [Fq2::ZERO; 4]; + + // compute powers of z + let zsq = z.square(); + let zpows = [z, zsq, zsq * z]; + + // compute map value by Horner's rule + for idx in 0..4 { + let coeff = COEFFS[idx]; + let clast = coeff.len() - 1; + mapvals[idx] = coeff[clast]; + for jdx in 0..clast { + mapvals[idx] = mapvals[idx] * x + zpows[jdx] * coeff[clast - 1 - jdx]; + } + } + + // x denominator is order 1 less than x numerator, so we need an extra factor of z + mapvals[1] *= z; + + // multiply result of Y map by the y-coord, y / z + mapvals[2] *= y; + mapvals[3] *= z; + + G2 { + x: mapvals[0] * mapvals[3], // xnum * yden, + y: mapvals[2] * mapvals[1], // ynum * xden, + z: mapvals[1] * mapvals[3], // xden * yden + } +} + +#[allow(clippy::type_complexity)] +pub(crate) fn hash_to_curve<'a>( + domain_prefix: &'a str, + suite: crate::hash_to_curve::Suite, +) -> Box G2 + 'a> { + Box::new(move |message| suite.hash_to_curve(domain_prefix, message).clear_cofactor()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::arithmetic::CurveEndo; + use group::UncompressedEncoding; + + crate::curve_testing_suite!(G2); + crate::curve_testing_suite!(G2, "endo_consistency"); + crate::curve_testing_suite!(G2, "endo"); + + #[test] + fn test_cofactor() { + assert!(bool::from( + G2Affine::identity().to_curve().is_torsion_free() + )); + assert!(bool::from( + G2Affine::generator().to_curve().is_torsion_free() + )); + } + + #[test] + fn test_hash_to_curve() { + pub(crate) fn point_from_hex(x0: &str, x1: &str, y0: &str, y1: &str) -> G2Affine { + let x0: Fq = crate::tests::hex_to_field(x0); + let x1: Fq = crate::tests::hex_to_field(x1); + let x = Fq2 { c0: x0, c1: x1 }; + let y0: Fq = crate::tests::hex_to_field(y0); + let y1: Fq = crate::tests::hex_to_field(y1); + let y = Fq2 { c0: y0, c1: y1 }; + G2Affine::from_xy(x, y).unwrap() + } + + struct Test { + msg: &'static [u8], + expect: G2Affine, + } + + impl Test { + fn new(msg: &'static [u8], expect: G2Affine) -> Self { + Self { msg, expect } + } + + fn run(&self, domain_prefix: &str) { + let r0 = G2::hash_to_curve(domain_prefix)(self.msg); + assert_eq!(r0.to_affine(), self.expect); + } + } + + let tests = [ + Test::new( + b"", + point_from_hex( + "0141ebfbdca40eb85b87142e130ab689c673cf60f1a3e98d69335266f30d9b8d4ac44c1038e9dcdd5393faf5c41fb78a", + "05cb8437535e20ecffaef7752baddf98034139c38452458baeefab379ba13dff5bf5dd71b72418717047f5b0f37da03d", + "0503921d7f6a12805e72940b963c0cf3471c7b2a524950ca195d11062ee75ec076daf2d4bc358c4b190c0c98064fdd92", + "12424ac32561493f3fe3c260708a12b7c620e7be00099a974e259ddc7d1f6395c3c811cdd19f1e8dbf3e9ecfdcbab8d6", + ), + ), + Test::new( + b"abc", + point_from_hex( + "02c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e6", + "139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd8", + "1787327b68159716a37440985269cf584bcb1e621d3a7202be6ea05c4cfe244aeb197642555a0645fb87bf7466b2ba48", + "00aa65dae3c8d732d10ecd2c50f8a1baf3001578f71c694e03866e9f3d49ac1e1ce70dd94a733534f106d4cec0eddd16", + ), + ), + Test::new( + b"abcdef0123456789", + point_from_hex( + "121982811d2491fde9ba7ed31ef9ca474f0e1501297f68c298e9f4c0028add35aea8bb83d53c08cfc007c1e005723cd0", + "190d119345b94fbd15497bcba94ecf7db2cbfd1e1fe7da034d26cbba169fb3968288b3fafb265f9ebd380512a71c3f2c", + "05571a0f8d3c08d094576981f4a3b8eda0a8e771fcdcc8ecceaf1356a6acf17574518acb506e435b639353c2e14827c8", + "0bb5e7572275c567462d91807de765611490205a941a5a6af3b1691bfe596c31225d3aabdf15faff860cb4ef17c7c3be", + ), + ), + Test::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + point_from_hex( + "19a84dd7248a1066f737cc34502ee5555bd3c19f2ecdb3c7d9e24dc65d4e25e50d83f0f77105e955d78f4762d33c17da", + "0934aba516a52d8ae479939a91998299c76d39cc0c035cd18813bec433f587e2d7a4fef038260eef0cef4d02aae3eb91", + "14f81cd421617428bc3b9fe25afbb751d934a00493524bc4e065635b0555084dd54679df1536101b2c979c0152d09192", + "09bcccfa036b4847c9950780733633f13619994394c23ff0b32fa6b795844f4a0673e20282d07bc69641cee04f5e5662", + ), + ), + Test::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + point_from_hex( + "01a6ba2f9a11fa5598b2d8ace0fbe0a0eacb65deceb476fbbcb64fd24557c2f4b18ecfc5663e54ae16a84f5ab7f62534", + "11fca2ff525572795a801eed17eb12785887c7b63fb77a42be46ce4a34131d71f7a73e95fee3f812aea3de78b4d01569", + "0b6798718c8aed24bc19cb27f866f1c9effcdbf92397ad6448b5c9db90d2b9da6cbabf48adc1adf59a1a28344e79d57e", + "03a47f8e6d1763ba0cad63d6114c0accbef65707825a511b251a660a9b3994249ae4e63fac38b23da0c398689ee2ab52", + ), + ), + ]; + + tests.iter().for_each(|test| { + test.run("QUUX-V01-CS02-with-"); + }); + } +} + +/// Coefficients of the 3-isogeny x map's numerator +const ISO3_XNUM: [Fq2; 4] = [ + Fq2 { + c0: Fq([ + 0x47f6_71c7_1ce0_5e62, + 0x06dd_5707_1206_393e, + 0x7c80_cd2a_f3fd_71a2, + 0x0481_03ea_9e6c_d062, + 0xc545_16ac_c8d0_37f6, + 0x1380_8f55_0920_ea41, + ]), + c1: Fq([ + 0x47f6_71c7_1ce0_5e62, + 0x06dd_5707_1206_393e, + 0x7c80_cd2a_f3fd_71a2, + 0x0481_03ea_9e6c_d062, + 0xc545_16ac_c8d0_37f6, + 0x1380_8f55_0920_ea41, + ]), + }, + Fq2 { + c0: Fq::zero(), + c1: Fq([ + 0x5fe5_5555_554c_71d0, + 0x873f_ffdd_236a_aaa3, + 0x6a6b_4619_b26e_f918, + 0x21c2_8884_0887_4945, + 0x2836_cda7_028c_abc5, + 0x0ac7_3310_a7fd_5abd, + ]), + }, + Fq2 { + c0: Fq([ + 0x0a0c_5555_5559_71c3, + 0xdb0c_0010_1f9e_aaae, + 0xb1fb_2f94_1d79_7997, + 0xd396_0742_ef41_6e1c, + 0xb700_40e2_c205_56f4, + 0x149d_7861_e581_393b, + ]), + c1: Fq([ + 0xaff2_aaaa_aaa6_38e8, + 0x439f_ffee_91b5_5551, + 0xb535_a30c_d937_7c8c, + 0x90e1_4442_0443_a4a2, + 0x941b_66d3_8146_55e2, + 0x0563_9988_53fe_ad5e, + ]), + }, + Fq2 { + c0: Fq([ + 0x40aa_c71c_71c7_25ed, + 0x1909_5555_7a84_e38e, + 0xd817_050a_8f41_abc3, + 0xd864_85d4_c87f_6fb1, + 0x696e_b479_f885_d059, + 0x198e_1a74_3280_02d2, + ]), + c1: Fq::zero(), + }, +]; + +/// Coefficients of the 3-isogeny x map's denominator +const ISO3_XDEN: [Fq2; 3] = [ + Fq2 { + c0: Fq::zero(), + c1: Fq([ + 0x1f3a_ffff_ff13_ab97, + 0xf25b_fc61_1da3_ff3e, + 0xca37_57cb_3819_b208, + 0x3e64_2736_6f8c_ec18, + 0x0397_7bc8_6095_b089, + 0x04f6_9db1_3f39_a952, + ]), + }, + Fq2 { + c0: Fq([ + 0x4476_0000_0027_552e, + 0xdcb8_009a_4348_0020, + 0x6f7e_e9ce_4a6e_8b59, + 0xb103_30b7_c0a9_5bc6, + 0x6140_b1fc_fb1e_54b7, + 0x0381_be09_7f0b_b4e1, + ]), + c1: Fq([ + 0x7588_ffff_ffd8_557d, + 0x41f3_ff64_6e0b_ffdf, + 0xf7b1_e8d2_ac42_6aca, + 0xb374_1acd_32db_b6f8, + 0xe9da_f5b9_482d_581f, + 0x167f_53e0_ba74_31b8, + ]), + }, + Fq2::one(), +]; + +/// Coefficients of the 3-isogeny y map's numerator +const ISO3_YNUM: [Fq2; 4] = [ + Fq2 { + c0: Fq([ + 0x96d8_f684_bdfc_77be, + 0xb530_e4f4_3b66_d0e2, + 0x184a_88ff_3796_52fd, + 0x57cb_23ec_fae8_04e1, + 0x0fd2_e39e_ada3_eba9, + 0x08c8_055e_31c5_d5c3, + ]), + c1: Fq([ + 0x96d8_f684_bdfc_77be, + 0xb530_e4f4_3b66_d0e2, + 0x184a_88ff_3796_52fd, + 0x57cb_23ec_fae8_04e1, + 0x0fd2_e39e_ada3_eba9, + 0x08c8_055e_31c5_d5c3, + ]), + }, + Fq2 { + c0: Fq::zero(), + c1: Fq([ + 0xbf0a_71c7_1c91_b406, + 0x4d6d_55d2_8b76_38fd, + 0x9d82_f98e_5f20_5aee, + 0xa27a_a27b_1d1a_18d5, + 0x02c3_b2b2_d293_8e86, + 0x0c7d_1342_0b09_807f, + ]), + }, + Fq2 { + c0: Fq([ + 0xd7f9_5555_5553_1c74, + 0x21cf_fff7_48da_aaa8, + 0x5a9a_d186_6c9b_be46, + 0x4870_a221_0221_d251, + 0x4a0d_b369_c0a3_2af1, + 0x02b1_ccc4_29ff_56af, + ]), + c1: Fq([ + 0xe205_aaaa_aaac_8e37, + 0xfcdc_0007_6879_5556, + 0x0c96_011a_8a15_37dd, + 0x1c06_a963_f163_406e, + 0x010d_f44c_82a8_81e6, + 0x174f_4526_0f80_8feb, + ]), + }, + Fq2 { + c0: Fq([ + 0xa470_bda1_2f67_f35c, + 0xc0fe_38e2_3327_b425, + 0xc9d3_d0f2_c6f0_678d, + 0x1c55_c993_5b5a_982e, + 0x27f6_c0e2_f074_6764, + 0x117c_5e6e_28aa_9054, + ]), + c1: Fq::zero(), + }, +]; + +/// Coefficients of the 3-isogeny y map's denominator +const ISO3_YDEN: [Fq2; 4] = [ + Fq2 { + c0: Fq([ + 0x0162_ffff_fa76_5adf, + 0x8f7b_ea48_0083_fb75, + 0x561b_3c22_59e9_3611, + 0x11e1_9fc1_a9c8_75d5, + 0xca71_3efc_0036_7660, + 0x03c6_a03d_41da_1151, + ]), + c1: Fq([ + 0x0162_ffff_fa76_5adf, + 0x8f7b_ea48_0083_fb75, + 0x561b_3c22_59e9_3611, + 0x11e1_9fc1_a9c8_75d5, + 0xca71_3efc_0036_7660, + 0x03c6_a03d_41da_1151, + ]), + }, + Fq2 { + c0: Fq::zero(), + c1: Fq([ + 0x5db0_ffff_fd3b_02c5, + 0xd713_f523_58eb_fdba, + 0x5ea6_0761_a84d_161a, + 0xbb2c_75a3_4ea6_c44a, + 0x0ac6_7359_21c1_119b, + 0x0ee3_d913_bdac_fbf6, + ]), + }, + Fq2 { + c0: Fq([ + 0x66b1_0000_003a_ffc5, + 0xcb14_00e7_64ec_0030, + 0xa73e_5eb5_6fa5_d106, + 0x8984_c913_a0fe_09a9, + 0x11e1_0afb_78ad_7f13, + 0x0542_9d0e_3e91_8f52, + ]), + c1: Fq([ + 0x534d_ffff_ffc4_aae6, + 0x5397_ff17_4c67_ffcf, + 0xbff2_73eb_870b_251d, + 0xdaf2_8271_5287_0915, + 0x393a_9cba_ca9e_2dc3, + 0x14be_74db_faee_5748, + ]), + }, + Fq2::one(), +]; diff --git a/src/bls12381/mod.rs b/src/bls12381/mod.rs new file mode 100644 index 00000000..5ab08f15 --- /dev/null +++ b/src/bls12381/mod.rs @@ -0,0 +1,36 @@ +mod engine; +mod fq; +mod fq12; +mod fq2; +mod fq6; +mod fr; +mod g1; +mod g2; + +pub use engine::*; +pub use fq::*; +pub use fq12::*; +pub use fq2::*; +pub use fq6::*; +pub use fr::*; +pub use g1::*; +pub use g2::*; + +const BLS_X: [u8; 64] = [ + 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const ENDO_PARAMS: EndoParameters = EndoParameters { + // round(b2/n) + gamma2: [0x63f6e522f6cfee30u64, 0x7c6becf1e01faadd, 0x01, 0x0], + // round(-b1/n) + gamma1: [0x02u64, 0x0, 0x0, 0x0], + b1: [0x01u64, 0x0, 0x0, 0x0], + b2: [0x0000000100000000, 0xac45a4010001a402, 0x0, 0x0], +}; + +use crate::arithmetic::{mul_512, sbb, CurveEndo, EndoParameters}; +use ff::{PrimeField, WithSmallOrderMulGroup}; +crate::endo!(G1, Fr, ENDO_PARAMS); +crate::endo!(G2, Fr, ENDO_PARAMS); diff --git a/src/bn256/curve.rs b/src/bn256/curve.rs index 98f38ee4..f6a31556 100644 --- a/src/bn256/curve.rs +++ b/src/bn256/curve.rs @@ -5,7 +5,6 @@ use crate::arithmetic::EndoParameters; use crate::bn256::Fq; use crate::bn256::Fq2; use crate::bn256::Fr; -use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::endo; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; @@ -24,8 +23,17 @@ use rand::RngCore; use std::convert::TryInto; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -#[cfg(feature = "derive_serde")] -use serde::{Deserialize, Serialize}; +impl crate::serde::endian::EndianRepr for Fq2 { + const ENDIAN: crate::serde::endian::Endian = Fq::ENDIAN; + + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> subtle::CtOption { + Fq2::from_bytes(bytes[..Fq2::SIZE].try_into().unwrap()) + } +} new_curve_impl!( (pub), @@ -38,6 +46,8 @@ new_curve_impl!( G1_B, "bn256_g1", |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, G1::default_hash_to_curve_suite()), + crate::serde::CompressedFlagConfig::TwoSpare, + standard_sign ); new_curve_impl!( @@ -51,6 +61,8 @@ new_curve_impl!( G2_B, "bn256_g2", |domain_prefix| hash_to_curve_g2(domain_prefix), + crate::serde::CompressedFlagConfig::TwoSpare, + standard_sign ); #[allow(clippy::type_complexity)] diff --git a/src/derive/curve.rs b/src/derive/curve.rs index 983dfb88..84f4c56f 100644 --- a/src/derive/curve.rs +++ b/src/derive/curve.rs @@ -49,13 +49,6 @@ macro_rules! endo { }; } -// Sign mask for 0, 1 and 2 spare bits. -pub(crate) const SIGN_MASK: u8 = 0b1000_0000; -pub(crate) const SIGN_SHIFT: u8 = 7; -// Identity mask for 0 and 2 spare bits (1 spare bit does not use it). -pub(crate) const IDENTITY_MASK: u8 = 0b0100_0000; -pub(crate) const IDENTITY_SHIFT: u8 = 6; - #[macro_export] macro_rules! new_curve_impl { (($($privacy:tt)*), @@ -68,412 +61,125 @@ macro_rules! new_curve_impl { $constant_b:expr, $curve_id:literal, $hash_to_curve:expr, + $flag_config:expr, + standard_sign ) => { - - // **Compressed formats**: - // The encoding of the x-coordinate can be Little Endian or Big Endian (inherited from the - // field encoding). - // The bit flags appear in the MSB of the encoded x-coordinate in the 1 and 2 Spare bits - // case, and in an extra byte after the encoded x-coordinate in the 0 Spare bits case. - // `BS` is the base size: the number of bytes required to encode a coordinate. - // - // According to the number of spare bits. - // 1 Spare bit: - // - // | | sign | x-coordinate | - // | Byte pos. (LE) | BS-1 .. 0 | - // | Byte pos. (BE) | 0 .. BS-1 | - // | Bit pos. | 7 | | - // | ---------------- | -------- | ------------ | - // | Identity | 0 | 0 | - // | Non-identity $P$ | $sgn0(P)$ | $P.x$ | - // - // --- - // 2 Spare bits: - // | | sign | ident | x-coordinate | - // | Byte pos. (LE) | BS-1 .. 0 | - // | Byte pos. (BE) | 0 .. BS-1 | - // | Bit pos. | 7 | 6 | | - // | ---------------- | -------- | -------- | -------- | - // | Identity | 0 | 1 | 0 | - // | Non-identity $P$ | $sgn0(P)$ | 0 | $P.x$ | - // - // --- - // 0 Spare bits: - // Add an extra byte after the compressed x-coordinate to hold the flags. Then follow - // the 2 spare bit flag format. - // - // | | sign | ident | 000000 | x-coordinate | - // | Byte pos. (LE) | BS | BS-1 .. 0 | - // | Byte pos. (BE) | BS | 0 .. BS-1 | - // | Bit pos. | 7 | 6 | 5..0 | | - // | ---------------- | --------- | -------- | ------ | ------------ | - // | Identity | 0 | 1 | 000000 | 0 | - // | Non-identity $P$ | $sgn0(P)$ | 0 | 000000 | $P.x$ | - // - macro_rules! impl_compressed { - ($spare_bits: expr) => { - paste::paste! { - - // The compressed size is the size of the x-coordinate (one base field element) - // when there is at least 1 spare bit. When there is no spare bits (secp256k1) - // the size is increased by 1 byte. - #[allow(non_upper_case_globals)] - const [< $name _COMPRESSED_SIZE >]: usize = - if $spare_bits == 0 { - $base::SIZE + 1 - } else { - $base::SIZE - }; - - #[derive(Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "derive_serde", derive(Serialize, Deserialize))] - pub struct [<$name Compressed >]( - #[cfg_attr(feature = "derive_serde", serde(with = "serde_arrays"))] - [u8; [< $name _COMPRESSED_SIZE >]] - ); - - impl std::fmt::Debug for [< $name Compressed >] { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0[..].fmt(f) - } - } - - impl Default for [< $name Compressed >] { - fn default() -> Self { - [< $name Compressed >]([0; [< $name _COMPRESSED_SIZE >]]) - } - } - - impl AsRef<[u8]> for [< $name Compressed >] { - fn as_ref(&self) -> &[u8] { - &self.0 - } - } - - impl AsMut<[u8]> for [< $name Compressed >] { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } + paste::paste! { + impl $crate::serde::Compressed<$name_affine> for [<$name:upper Compressed >] { + const CONFIG: $crate::serde::CompressedFlagConfig = $flag_config; + fn sign(c: &$name_affine) -> subtle::Choice { + Choice::from(c.y.to_repr()[0] as u8 & 1) & !c.is_identity() } - - impl GroupEncoding for $name { - type Repr = [< $name Compressed >]; - - fn from_bytes(bytes: &Self::Repr) -> CtOption { - $name_affine::from_bytes(bytes).map(Self::from) - } - - fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { - $name_affine::from_bytes_unchecked(bytes).map(Self::from) - } - - fn to_bytes(&self) -> Self::Repr { - $name_affine::from(self).to_bytes() - } + fn resolve(x: $base, sign_set: Choice) -> CtOption<$name_affine> { + $name_affine::y2(x).sqrt().map(|y| { + let y = $base::conditional_select(&y, &-y, sign_set ^ Choice::from(y.to_repr()[0] as u8 & 1)); + $name_affine { x, y } + }) } + } + } - // The flags are placed in the last byte (the most significant byte). - #[allow(non_upper_case_globals)] - const [< $name _FLAG_BYTE_INDEX>]: usize= [< $name _COMPRESSED_SIZE >]-1 ; - - #[allow(non_upper_case_globals)] - const [< $name _FLAG_BITS >]: u8 = - if $spare_bits == 1 { - 0b1000_0000 - } else if $spare_bits == 2 { - 0b1100_0000 - } else { - //$spare_bits == 0 - 0b1111_1111 - }; - - impl group::GroupEncoding for $name_affine { - type Repr = [< $name Compressed >]; - - - fn from_bytes(bytes: &Self::Repr) -> CtOption { - let mut tmp = bytes.0; - - let flag_byte = tmp[[< $name _FLAG_BYTE_INDEX>]]; - // Get identity and sign flags. - let identity_flag = if $spare_bits == 0 || $spare_bits == 2 { - Choice::from((flag_byte & IDENTITY_MASK) >> IDENTITY_SHIFT ) - } else { - Choice::from(0u8) - }; - - let sign_flag = Choice::from( (flag_byte & SIGN_MASK) >> SIGN_SHIFT ); - - let extra_bits = if $spare_bits == 0 { - // In the case of 0 spare bits, an extra byte is added to hold the flags. - // In this byte, the 6 least significant bits must be set to 0. - Choice::from(u8::from((flag_byte & 0b0011_1111) !=0) ) - } else { - // There are no extra bits in the rest of cases. - Choice::from(0u8) - }; - - // Clear flag bits - tmp[[< $name _FLAG_BYTE_INDEX>]] &= ![< $name _FLAG_BITS >]; - - // Get x-coordinate - let mut xbytes = [0u8; $base::SIZE]; - xbytes.copy_from_slice(&tmp[..$base::SIZE]); - - - - $base::from_bytes(&xbytes).and_then(|x| { - - // Decide if the point is the identity and the validity of the encoding. - let (is_valid, is_identity) = - if $spare_bits == 0 || $spare_bits == 2 { - // Correct encoding follows one of the following: - // 1. Identity: - // identity_flag = 1, sign = 0, x = 0, extra_bits = 0 - // 2. Non-identity: - // identity_flag = 0, , extra_bits = 0 - // - // is_valid = !identity_flag \/ (!sign /\ x.is_zero()) /\ !extra_bits - ( (!identity_flag | (!sign_flag & x.is_zero())) & !extra_bits, identity_flag) - } else { - // Correct encoding follows one of the following: - // 1. Identity: - // sign = 0, x = 0 - // 2. Non-identity: - // x!=0 - // - // is_valid = !(x.is_zero() /\ sign_flag) - ( !(x.is_zero() & sign_flag), x.is_zero()) - }; - + new_curve_impl!(($($privacy)*), $name, $name_affine, $base, $scalar, $generator, $constant_a, $constant_b, $curve_id, $hash_to_curve, $flag_config); + }; - CtOption::new( - Self::identity(), - is_identity) - .or_else(|| { - // Computes corresponding y coordinate. - $name_affine::y2(x).sqrt().and_then(|y| { - // Get sign of obtained solution. sign = y % 2. - let sign = Choice::from(y.to_bytes()[0] & 1); - // Adjust sign if necessary. - let y = $base::conditional_select(&y, &-y, sign_flag ^ sign); - CtOption::new( - $name_affine { - x, - y, - }, - is_valid, - ) - }) - }) - }) - } + (($($privacy:tt)*), + $name:ident, + $name_affine:ident, + $base:ident, + $scalar:ident, + $generator:expr, + $constant_a:expr, + $constant_b:expr, + $curve_id:literal, + $hash_to_curve:expr, + $flag_config:expr + ) => { - fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { - // In compressed form we cannot skip the curve check. - Self::from_bytes(bytes) - } - fn to_bytes(&self) -> Self::Repr { - let mut res = [0; [< $name _COMPRESSED_SIZE >]]; + paste::paste! { + const [< $name:upper _COMPRESSED_SIZE >]: usize = $base::SIZE + if $flag_config.has_extra_byte() { 1 } else { 0 }; - let x_bytes = $base::conditional_select(&self.x, &$base::zero(), self.is_identity()).to_bytes(); - res[..$base::SIZE].copy_from_slice(&x_bytes); + pub type [<$name:upper Compressed >] = $crate::serde::Repr<[< $name:upper _COMPRESSED_SIZE >]>; + pub type [<$name Uncompressed >] = $crate::serde::Repr<{ 2*$base::SIZE }>; - // Set identity flag if necessary. - res[ [< $name _FLAG_BYTE_INDEX>]] |= u8::conditional_select(&0u8, &IDENTITY_MASK, self.is_identity()); + impl GroupEncoding for $name_affine { + type Repr = [<$name:upper Compressed >]; - // Set sign flag if point is not identity, and has negative sign. - res[ [< $name _FLAG_BYTE_INDEX>]] |= u8::conditional_select(&0u8, &SIGN_MASK, !self.is_identity() & Choice::from(self.y.to_bytes()[0] & 1)); - [< $name Compressed >](res) - } + fn from_bytes(bytes: &Self::Repr) -> CtOption { + >::decode(bytes.clone()) } - + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + >::decode(bytes.clone()) } - }; - } - - - // **Uncompressed format** - // The encoding of the x-coordinate and y-coordinate can be Little Endian or Big Endian - // (inherited from the field encoding). - // - // There are no flag bits, the spare bits must be 0. - // `BS` is the base size: the number of bytes required to encode a coordinate. - // - // According to the number of spare bits: - // 1 Spare bit: - // - // | | 0 | x-coordinate | 0 | y-coordinate | - // | Byte pos. (LE) | BS-1 .. 0 | 2*BS-1 .. BS | - // | Byte pos. (BE) | 0 .. BS-1 | BS .. 2*BS-1 | - // | Bit pos. | 7 | | 7 | | - // | ---------------- | - | ------------ | - | ------------ | - // | Identity | 0 | 0 | 0 | 0 | - // | Non-identity $P$ | 0 | $P.x$ | 0 | $P.y$ | - // - // ---- - // 2 Spare bits: - // - // | | 0 | 0 | x-coordinate | 0 | 0 | y-coordinate | - // | Byte pos. (LE) | BS-1 .. 0 | 2*BS-1 .. BS | - // | Byte pos. (BE) | 0 .. BS-1 | BS .. 2*BS-1 | - // | Bit pos. | 7 | 6 | | 7 | 6 | | - // | ---------------- | - | - | ------------ | - | - | ------------ | - // | Identity | 0 | 0 | 0 | 0 | 0 | 0 | - // | Non-identity $P$ | 0 | 0 | $P.x$ | 0 | 0 | $P.y$ | - // - // ---- - // 0 Spare bits: - // - // | | x-coordinate | y-coordinate | - // | Byte pos. (LE) | BS-1 .. 0 | 2*BS-1 .. BS | - // | Byte pos. (BE) | 0 .. BS-1 | BS .. 2*BS-1 | - // | ---------------- | ------------ | ------------ | - // | Identity | 0 | 0 | - // | Non-identity $P$ | $P.x$ | $P.y$ | - // - - macro_rules! impl_uncompressed { - ($spare_bits: expr) => { - paste::paste! { - - #[derive(Copy, Clone)] - pub struct [< $name Uncompressed >]([u8; 2*$base::SIZE]); - impl std::fmt::Debug for [< $name Uncompressed >] { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0[..].fmt(f) - } - } - - impl Default for [< $name Uncompressed >] { - fn default() -> Self { - [< $name Uncompressed >]([0; 2*$base::SIZE ]) - } - } - - impl AsRef<[u8]> for [< $name Uncompressed >] { - fn as_ref(&self) -> &[u8] { - &self.0 - } - } - - impl AsMut<[u8]> for [< $name Uncompressed >] { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } - } - - impl ConstantTimeEq for [< $name Uncompressed >] { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } - } - - impl Eq for [< $name Uncompressed >] {} - - impl PartialEq for [< $name Uncompressed >] { - #[inline] - fn eq(&self, other: &Self) -> bool { - bool::from(self.ct_eq(other)) - } - } - - impl group::UncompressedEncoding for $name_affine{ - type Uncompressed = [< $name Uncompressed >]; - fn from_uncompressed(bytes: &Self::Uncompressed) -> CtOption { - Self::from_uncompressed_unchecked(bytes).and_then(|p| CtOption::new(p, p.is_on_curve())) - } + fn to_bytes(&self) -> Self::Repr { + >::encode(&self) + } + } - fn from_uncompressed_unchecked(bytes: &Self::Uncompressed) -> CtOption { - let mut bytes = bytes.0; + impl GroupEncoding for $name { + type Repr = [<$name:upper Compressed >]; - let flag_idx_x = $base::SIZE -1; - let flag_idx_y = 2* $base::SIZE -1; + fn from_bytes(bytes: &Self::Repr) -> CtOption { + >::decode(bytes.clone()).map(Self::from) + } - // In the uncompressed format, the spare bits in both coordinates must be 0. - let mut any_flag_set = Choice::from(0u8); + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + >::decode(bytes.clone()).map(Self::from) + } - // Get sign flag to check they are set to 0. - if $spare_bits == 2 || $spare_bits == 1 { - any_flag_set |= Choice::from( (bytes[ flag_idx_x ] & SIGN_MASK) >> SIGN_SHIFT | - (bytes[ flag_idx_y ] & SIGN_MASK) >> SIGN_SHIFT ) - } + fn to_bytes(&self) -> Self::Repr { + >::encode(&self.to_affine()) + } + } - // Get identity flag to check they are set to 0. - if $spare_bits == 2 { - any_flag_set |= Choice::from( (( bytes[ flag_idx_x ] & IDENTITY_MASK) >> IDENTITY_SHIFT) | (( bytes[ flag_idx_y ] & IDENTITY_MASK) >> IDENTITY_SHIFT) ); - } + impl group::UncompressedEncoding for $name_affine { + type Uncompressed = [<$name Uncompressed >]; - // Clear spare bits. - if $spare_bits == 2 || $spare_bits == 1 { - bytes[flag_idx_x] &= ![< $name _FLAG_BITS >]; - bytes[flag_idx_y] &= ![< $name _FLAG_BITS >]; - } + fn from_uncompressed(bytes: &Self::Uncompressed) -> CtOption { + Self::from_uncompressed_unchecked(bytes).and_then(|p| CtOption::new(p, p.is_on_curve())) + } + fn from_uncompressed_unchecked(bytes: &Self::Uncompressed) -> CtOption { + let mut repr = [0u8; $base::SIZE]; - // Get x, y coordinates. - let mut repr = [0u8; $base::SIZE]; - let x = { - repr.copy_from_slice(&bytes[0..$base::SIZE]); - $base::from_bytes(&repr) - }; + let x = { + repr.copy_from_slice(&bytes[0..$base::SIZE]); + $base::from_bytes(&repr) + }; - let y = { - repr.copy_from_slice(&bytes[$base::SIZE..2*$base::SIZE]); - $base::from_bytes(&repr) - }; + let y = { + repr.copy_from_slice(&bytes[$base::SIZE..2*$base::SIZE]); + $base::from_bytes(&repr) + }; + x.and_then(|x| { + y.and_then(|y| { + let is_identity = x.is_zero() & y.is_zero(); + let unchecked = Self { x, y }; + subtle::CtOption::new($name_affine::identity(), is_identity) + .or_else(|| subtle::CtOption::new(unchecked, Choice::from(1u8))) + }) + }) + } - x.and_then(|x| { - y.and_then(|y| { - let zero_coords = x.is_zero() & y.is_zero(); - - // Check identity condition and encoding validity: - // The point is the identity if both coordinates are zero. - // The encoding is valid if both coordinates represent valid field elements and - // the spare bits are all zero. - let (is_valid, is_identity) = - ( !any_flag_set, zero_coords); - - - let p = $name_affine::conditional_select( - &$name_affine{ - x, - y, - }, - &$name_affine::identity(), - is_identity, - ); - - CtOption::new( - p, - is_valid - ) - }) - }) - } + fn to_uncompressed(&self) -> Self::Uncompressed { + let mut res = Self::Uncompressed::default(); - fn to_uncompressed(&self) -> Self::Uncompressed { - let mut res = [0; 2*$base::SIZE]; + res[0..$base::SIZE].copy_from_slice( + &$base::conditional_select(&self.x, &$base::zero(), self.is_identity()).to_bytes()[..], + ); - res[0..$base::SIZE].copy_from_slice( - &$base::conditional_select(&self.x, &$base::zero(), self.is_identity()).to_bytes()[..], - ); - res[$base::SIZE.. 2*$base::SIZE].copy_from_slice( - &$base::conditional_select(&self.y, &$base::zero(), self.is_identity()).to_bytes()[..], - ); + res[$base::SIZE.. 2*$base::SIZE].copy_from_slice( + &$base::conditional_select(&self.y, &$base::zero(), self.is_identity()).to_bytes()[..], + ); - [< $name Uncompressed >](res) - } - } + res } - }; + } } /// A macro to help define point serialization using the [`group::GroupEncoding`] trait @@ -485,9 +191,9 @@ macro_rules! new_curve_impl { fn serialize(&self, serializer: S) -> Result { let bytes = &self.to_bytes(); if serializer.is_human_readable() { - ::hex::serde::serialize(&bytes.0, serializer) + ::hex::serde::serialize(&bytes, serializer) } else { - ::serde_arrays::serialize(&bytes.0, serializer) + ::serde_arrays::serialize(bytes.inner(), serializer) } } } @@ -501,9 +207,9 @@ macro_rules! new_curve_impl { let bytes = if deserializer.is_human_readable() { ::hex::serde::deserialize(deserializer)? } else { - ::serde_arrays::deserialize::<_, u8, [< $name _COMPRESSED_SIZE >]>(deserializer)? + ::serde_arrays::deserialize::<_, u8, [< $name:upper _COMPRESSED_SIZE >]>(deserializer)? }; - Option::from(Self::from_bytes(&[< $name Compressed >](bytes))).ok_or_else(|| { + Option::from(Self::from_bytes(&bytes.into())).ok_or_else(|| { D::Error::custom("deserialized bytes don't encode a valid field element") }) } @@ -514,9 +220,9 @@ macro_rules! new_curve_impl { fn serialize(&self, serializer: S) -> Result { let bytes = &self.to_bytes(); if serializer.is_human_readable() { - ::hex::serde::serialize(&bytes.0, serializer) + ::hex::serde::serialize(&bytes, serializer) } else { - ::serde_arrays::serialize(&bytes.0, serializer) + ::serde_arrays::serialize(bytes.inner(), serializer) } } } @@ -530,9 +236,9 @@ macro_rules! new_curve_impl { let bytes = if deserializer.is_human_readable() { ::hex::serde::deserialize(deserializer)? } else { - ::serde_arrays::deserialize::<_, u8, [< $name _COMPRESSED_SIZE >]>(deserializer)? + ::serde_arrays::deserialize::<_, u8, [< $name:upper _COMPRESSED_SIZE >]>(deserializer)? }; - Option::from(Self::from_bytes(&[< $name Compressed >](bytes))).ok_or_else(|| { + Option::from(Self::from_bytes(&bytes.into())).ok_or_else(|| { D::Error::custom("deserialized bytes don't encode a valid field element") }) } @@ -557,11 +263,6 @@ macro_rules! new_curve_impl { #[cfg(feature = "derive_serde")] serialize_deserialize_to_from_bytes!(); - // Base's num_bits is the number of bits for the base prime field, - // so the computation of spare bits is correct for extensions as well. - impl_compressed!((($base::NUM_BITS-1) / 8 +1) * 8 - $base::NUM_BITS); - impl_uncompressed!((($base::NUM_BITS-1) / 8 +1) * 8 - $base::NUM_BITS); - impl $name { @@ -617,7 +318,7 @@ macro_rules! new_curve_impl { let y2 = $name_affine::y2(x); if let Some(y) = Option::<$base>::from(y2.sqrt()) { - let sign = y.to_bytes()[0] & 1; + let sign = y.to_repr()[0] & 1; let y = if ysign ^ sign == 0 { y } else { -y }; let p = $name_affine { diff --git a/src/derive/field/tower.rs b/src/derive/field/tower.rs index f20c15c5..bbdd1324 100644 --- a/src/derive/field/tower.rs +++ b/src/derive/field/tower.rs @@ -61,6 +61,20 @@ macro_rules! impl_tower2 { res[$base::SIZE..$base::SIZE * 2].copy_from_slice(&c1_bytes[..]); res } + + #[inline] + /// Returns whether or not this element is strictly lexicographically + /// larger than its negation. + pub fn lexicographically_largest(&self) -> Choice { + // If this element's c1 coefficient is lexicographically largest + // then it is lexicographically largest. Otherwise, in the event + // the c1 coefficient is zero and the c0 coefficient is + // lexicographically largest, then this element is lexicographically + // largest. + + self.c1.lexicographically_largest() + | (self.c1.is_zero() & self.c0.lexicographically_largest()) + } } impl WithSmallOrderMulGroup<3> for $field { diff --git a/src/grumpkin/curve.rs b/src/grumpkin/curve.rs index 3d956490..e212ad83 100644 --- a/src/grumpkin/curve.rs +++ b/src/grumpkin/curve.rs @@ -2,7 +2,6 @@ use crate::arithmetic::mul_512; use crate::arithmetic::sbb; use crate::arithmetic::CurveEndo; use crate::arithmetic::EndoParameters; -use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::Curve; @@ -21,9 +20,6 @@ use core::ops::{Add, Mul, Neg, Sub}; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -#[cfg(feature = "derive_serde")] -use serde::{Deserialize, Serialize}; - new_curve_impl!( (pub), G1, @@ -35,6 +31,8 @@ new_curve_impl!( G1_B, "grumpkin_g1", |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, G1::default_hash_to_curve_suite()), + crate::serde::CompressedFlagConfig::TwoSpare, + standard_sign ); // Parameters in montgomery form taken from diff --git a/src/lib.rs b/src/lib.rs index 1f2efc0c..d2b929e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod hash_to_curve; pub mod msm; pub mod serde; +pub mod bls12381; pub mod bn256; pub mod grumpkin; pub mod pasta; diff --git a/src/pluto_eris/curve.rs b/src/pluto_eris/curve.rs index 23acac24..e0452a94 100644 --- a/src/pluto_eris/curve.rs +++ b/src/pluto_eris/curve.rs @@ -1,5 +1,4 @@ use super::{fp::Fp, fp2::Fp2, fq::Fq}; -use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; @@ -12,9 +11,6 @@ use group::cofactor::CofactorGroup; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -#[cfg(feature = "derive_serde")] -use serde::{Deserialize, Serialize}; - use crate::{ impl_binops_additive, impl_binops_additive_specify_output, impl_binops_multiplicative, impl_binops_multiplicative_mixed, new_curve_impl, @@ -130,6 +126,8 @@ new_curve_impl!( PLUTO_B, "pluto", |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, G1::default_hash_to_curve_suite()), + crate::serde::CompressedFlagConfig::TwoSpare, + standard_sign ); impl group::cofactor::CofactorGroup for Eris { @@ -173,6 +171,8 @@ new_curve_impl!( ERIS_B, "eris", |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, Eris::default_hash_to_curve_suite()), + crate::serde::CompressedFlagConfig::TwoSpare, + standard_sign ); impl CofactorGroup for G2 { @@ -242,6 +242,18 @@ impl Eris { } } +impl crate::serde::endian::EndianRepr for Fp2 { + const ENDIAN: crate::serde::endian::Endian = Fq::ENDIAN; + + fn to_bytes(&self) -> Vec { + self.to_bytes().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> subtle::CtOption { + Fp2::from_bytes(bytes[..Fp2::SIZE].try_into().unwrap()) + } +} + new_curve_impl!( (pub), G2, @@ -253,6 +265,8 @@ new_curve_impl!( TRITON_B, "triton", |_| unimplemented!(), + crate::serde::CompressedFlagConfig::TwoSpare, + standard_sign ); #[cfg(test)] diff --git a/src/secp256k1/curve.rs b/src/secp256k1/curve.rs index 4b56d1aa..6214cad8 100644 --- a/src/secp256k1/curve.rs +++ b/src/secp256k1/curve.rs @@ -1,4 +1,3 @@ -use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; @@ -17,9 +16,6 @@ use crate::{ impl_binops_multiplicative_mixed, new_curve_impl, }; -#[cfg(feature = "derive_serde")] -use serde::{Deserialize, Serialize}; - impl group::cofactor::CofactorGroup for Secp256k1 { type Subgroup = Secp256k1; @@ -64,6 +60,8 @@ new_curve_impl!( SECP_B, "secp256k1", |domain_prefix| hash_to_curve(domain_prefix, hash_to_curve_suite(b"secp256k1_XMD:SHA-256_SSWU_RO_")), + crate::serde::CompressedFlagConfig::Extra, + standard_sign ); fn hash_to_curve_suite(domain: &[u8]) -> crate::hash_to_curve::Suite { diff --git a/src/secp256r1/curve.rs b/src/secp256r1/curve.rs index 8acdd1c2..44486c1d 100644 --- a/src/secp256r1/curve.rs +++ b/src/secp256r1/curve.rs @@ -1,4 +1,3 @@ -use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; @@ -12,9 +11,6 @@ use core::ops::{Add, Mul, Neg, Sub}; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -#[cfg(feature = "derive_serde")] -use serde::{Deserialize, Serialize}; - impl group::cofactor::CofactorGroup for Secp256r1 { type Subgroup = Secp256r1; @@ -75,6 +71,8 @@ new_curve_impl!( SECP_B, "secp256r1", |domain_prefix| hash_to_curve(domain_prefix, hash_to_curve_suite(b"P256_XMD:SHA-256_SSWU_RO_")), + crate::serde::CompressedFlagConfig::Extra, + standard_sign ); fn hash_to_curve_suite(domain: &[u8]) -> crate::hash_to_curve::Suite { diff --git a/src/secq256k1/curve.rs b/src/secq256k1/curve.rs index 675f25b8..f08b515d 100644 --- a/src/secq256k1/curve.rs +++ b/src/secq256k1/curve.rs @@ -1,4 +1,3 @@ -use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::Curve; @@ -16,9 +15,6 @@ use core::ops::{Add, Mul, Neg, Sub}; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -#[cfg(feature = "derive_serde")] -use serde::{Deserialize, Serialize}; - const SECQ_GENERATOR_X: Fq = Fq::from_raw([ 0xA24288E37702EDA6, 0x3134E45A097781A6, @@ -47,6 +43,8 @@ new_curve_impl!( SECQ_B, "secq256k1", |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, Secq256k1::default_hash_to_curve_suite()), + crate::serde::CompressedFlagConfig::Extra, + standard_sign ); impl group::cofactor::CofactorGroup for Secq256k1 { diff --git a/src/serde.rs b/src/serde.rs index 74b9a3e3..13779934 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -3,8 +3,20 @@ use std::{ io::{self, Read, Write}, }; +#[cfg(feature = "derive_serde")] +use serde::{Deserialize, Serialize}; + #[derive(Clone, Copy, Debug)] -pub struct Repr([u8; T]); +#[cfg_attr(feature = "derive_serde", derive(Serialize, Deserialize))] +pub struct Repr( + #[cfg_attr(feature = "derive_serde", serde(with = "serde_arrays"))] [u8; T], +); + +impl Repr { + pub fn inner(&self) -> &[u8; T] { + &self.0 + } +} impl From<[u8; T]> for Repr { fn from(bytes: [u8; T]) -> Self { @@ -12,6 +24,12 @@ impl From<[u8; T]> for Repr { } } +impl<'a, const T: usize> From<&'a [u8]> for Repr { + fn from(bytes: &[u8]) -> Self { + Self(bytes.try_into().unwrap()) + } +} + impl From> for [u8; T] { fn from(repr: Repr) -> Self { repr.0 @@ -98,44 +116,254 @@ pub trait SerdeObject: Sized { fn write_raw(&self, writer: &mut W) -> io::Result<()>; } -pub(crate) mod endian { +pub mod endian { + + pub trait EndianRepr: Sized { + const ENDIAN: Endian; - pub trait Endian { - fn to_bytes(res: &mut [u8], el: &[u64]); - fn from_bytes(res: &[u8], el: &mut [u64]); + fn to_bytes(&self) -> Vec; + + fn from_bytes(x: &[u8]) -> subtle::CtOption; } - pub struct LE; - pub struct BE; + pub enum Endian { + LE, + BE, + } - impl Endian for LE { - fn to_bytes(res: &mut [u8], el: &[u64]) { - el.iter().enumerate().for_each(|(i, limb)| { - let off = i * 8; - res[off..off + 8].copy_from_slice(&limb.to_le_bytes()); - }); + impl Endian { + pub fn to_bytes(&self, res: &mut [u8], el: &[u64]) { + match self { + Endian::LE => { + el.iter().enumerate().for_each(|(i, limb)| { + let off = i * 8; + res[off..off + 8].copy_from_slice(&limb.to_le_bytes()); + }); + } + Endian::BE => { + el.iter().rev().enumerate().for_each(|(i, limb)| { + let off = i * 8; + res[off..off + 8].copy_from_slice(&limb.to_be_bytes()); + }); + } + } } - fn from_bytes(res: &[u8], el: &mut [u64]) { - el.iter_mut().enumerate().for_each(|(i, limb)| { - let off = i * 8; - *limb = u64::from_le_bytes(res[off..off + 8].try_into().unwrap()); - }); + pub fn from_bytes(&self, res: &[u8], el: &mut [u64]) { + match self { + Endian::LE => { + el.iter_mut().enumerate().for_each(|(i, limb)| { + let off = i * 8; + *limb = u64::from_le_bytes(res[off..off + 8].try_into().unwrap()); + }); + } + Endian::BE => { + el.iter_mut().rev().enumerate().for_each(|(i, limb)| { + let off = i * 8; + *limb = u64::from_be_bytes(res[off..off + 8].try_into().unwrap()); + }); + } + } } } - impl Endian for BE { - fn to_bytes(res: &mut [u8], el: &[u64]) { - el.iter().rev().enumerate().for_each(|(i, limb)| { - let off = i * 8; - res[off..off + 8].copy_from_slice(&limb.to_be_bytes()); - }); +} + +pub(crate) enum CompressedFlagConfig { + // NOTE: if needed we can add fields for bit positions + + // Secp256k1, Secp256r1 curves should be encoded with + Extra, // sign: 0 identity: 1 + + // Pasta curves should be encoded with + // SingleSpare, // sign: 0 + + // BN254 curve should be encoded with + TwoSpare, // sign: 0, identity: 1 + + // BLS12-{381, 377} curves should be encoded with + ThreeSpare, // is_compressed: 0, sign: 1, identity: 2 +} + +impl CompressedFlagConfig { + pub(crate) const fn has_extra_byte(&self) -> bool { + matches!(self, CompressedFlagConfig::Extra) + } +} + +pub(crate) struct Flag {} + +impl Flag { + fn flag(pos: u8) -> u8 { + 1 << 7u8.checked_sub(pos).unwrap() + } + + fn set(pos: u8, value: bool, flag_byte: &mut u8) { + value.then(|| *flag_byte |= Self::flag(pos)); + } + + fn get(pos: u8, flag_byte: &mut u8) -> subtle::Choice { + let flag = Self::flag(pos); + let value = (*flag_byte & flag) != 0; + *flag_byte &= !flag; // clear flag + subtle::Choice::from(value as u8) + } +} + +pub(crate) trait Compressed: + Debug + Copy + Default + AsRef<[u8]> + AsMut<[u8]> + Send + Sync + 'static +where + C::Base: crate::serde::endian::EndianRepr, +{ + const CONFIG: CompressedFlagConfig; + + fn flag_byte(&mut self) -> &mut u8 { + use crate::serde::endian::EndianRepr; + match Self::CONFIG { + // Most sig byte is always the flag byte when extra byte flag is used + CompressedFlagConfig::Extra => self.as_mut().first_mut().unwrap(), + _ => match C::Base::ENDIAN { + // Least sig byte is the flag byte + crate::serde::endian::Endian::LE => self.as_mut().last_mut().unwrap(), + // Most sig byte is the flag byte + crate::serde::endian::Endian::BE => self.as_mut().first_mut().unwrap(), + }, } + } - fn from_bytes(res: &[u8], el: &mut [u64]) { - el.iter_mut().rev().enumerate().for_each(|(i, limb)| { - let off = i * 8; - *limb = u64::from_be_bytes(res[off..off + 8].try_into().unwrap()); - }); + fn sign(y: &C) -> subtle::Choice; + + fn resolve(x: C::Base, sign: subtle::Choice) -> subtle::CtOption; + + fn pos_sign() -> u8 { + match Self::CONFIG { + CompressedFlagConfig::Extra => 0, + CompressedFlagConfig::TwoSpare => 0, + CompressedFlagConfig::ThreeSpare => 2, } } + + fn pos_compressed() -> Option { + match Self::CONFIG { + CompressedFlagConfig::ThreeSpare => Some(0), + _ => None, + } + } + + fn pos_idetity() -> Option { + match Self::CONFIG { + CompressedFlagConfig::Extra => Some(1), + CompressedFlagConfig::TwoSpare => Some(1), + CompressedFlagConfig::ThreeSpare => Some(1), + } + } + + fn set_sign(&mut self, c: &C) { + let sign = bool::from(Self::sign(c)); + let pos = Self::pos_sign(); + Flag::set(pos, sign, self.flag_byte()); + } + + fn set_compressed(&mut self) { + if let Some(pos) = Self::pos_compressed() { + Flag::set(pos, true, self.flag_byte()) + } + } + + fn set_identity(&mut self, c: &C) { + if let Some(pos) = Self::pos_idetity() { + Flag::set(pos, bool::from(c.is_identity()), self.flag_byte()); + }; + } + + fn get_sign(&mut self) -> subtle::Choice { + Flag::get(Self::pos_sign(), self.flag_byte()) + } + + fn get_is_compressed(&mut self) -> Option { + Self::pos_compressed().map(|pos| Flag::get(pos, self.flag_byte())) + } + + fn get_is_identity(&mut self) -> Option { + Self::pos_idetity().map(|pos| Flag::get(pos, self.flag_byte())) + } + + fn set_flags(&mut self, c: &C) { + self.set_identity(c); + self.set_sign(c); + self.set_compressed(); + } + + fn encode(c: &C) -> Self { + use crate::serde::endian::EndianRepr; + let mut this = Self::default(); + let coordinates = c.coordinates().unwrap(); + let x = coordinates.x(); + let x_bytes = x.to_bytes(); + match Self::CONFIG { + CompressedFlagConfig::Extra => { + // Most sig byte is always the flag byte when extra byte flag is used + this.as_mut()[1..1 + x_bytes.len()].copy_from_slice(&x_bytes) + } + _ => this.as_mut()[..x_bytes.len()].copy_from_slice(&x_bytes), + }; + this.set_identity(c); + this.set_sign(c); + this.set_compressed(); + this + } + + fn decode(mut self) -> subtle::CtOption { + let is_compressed = self.get_is_compressed(); + // if is compressed set then it should be set one + let is_valid_0: subtle::Choice = is_compressed.unwrap_or(subtle::Choice::from(1u8)); + + let is_identity = self.get_is_identity(); + + let sign = self.get_sign(); + + // with extra byte config expect it goes to zero after it is read + // otherwise `from_byte` checks if flag or rest unused bytes are zero + let is_valid_1 = match Self::CONFIG { + CompressedFlagConfig::Extra => *self.flag_byte() == 0, + _ => true, + }; + let is_valid_1: subtle::Choice = (is_valid_1 as u8).into(); + + let x = match Self::CONFIG { + CompressedFlagConfig::Extra => { + // Most sig byte is always the flag byte when extra byte flag is used + ::from_bytes(&self.as_ref()[1..]) + } + _ => ::from_bytes(self.as_ref()), + }; + + x.and_then(|x| -> subtle::CtOption { + use ff::Field; + let is_zero = x.is_zero(); + + let (is_valid_2, is_identity) = match is_identity { + // identity flag active + Some(is_identity) => { + // identity flag set: + // * x must be zero + // * sign must not be set + + // identity flag not set: + // * x must not be zero + + let is_valid = (is_identity & is_zero & !sign) ^ (!is_identity & !is_zero); + + (is_valid, is_identity) + } + + // identitity flag inactive + None => (subtle::Choice::from(1u8), is_zero), + }; + + let is_valid = is_valid_0 & is_valid_1 & is_valid_2; + + subtle::CtOption::new(C::identity(), is_valid & is_identity) + .or_else(|| Self::resolve(x, sign).and_then(|c| subtle::CtOption::new(c, is_valid))) + }) + } }