diff --git a/Cargo.toml b/Cargo.toml index 9678e8eb..5361ec61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,13 @@ serde = { version = "1.0", default-features = false, optional = true } serde_arrays = { version = "0.1.0", optional = true } hex = { version = "0.4", optional = true, default-features = false, features = ["alloc", "serde"] } blake2b_simd = "1" +bls12_381 = { git = "https://github.com/dragan2234/bls12_381", features = ["groups", "basefield"], branch = "add-FromUniformBytes-trait" } rayon = "1.8" +[dependencies.bitvec] +version = "1" +default-features = false + [features] default = ["bits"] asm = [] diff --git a/src/bandersnatch/curve.rs b/src/bandersnatch/curve.rs new file mode 100644 index 00000000..944ee850 --- /dev/null +++ b/src/bandersnatch/curve.rs @@ -0,0 +1,181 @@ +use crate::bandersnatch::Fp; +use crate::bandersnatch::Fr; +use crate::ff::WithSmallOrderMulGroup; +use crate::ff::{Field, PrimeField}; +use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; +use crate::hash_to_curve::sswu_hash_to_curve; +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}; + +#[cfg(feature = "derive_serde")] +use serde::{Deserialize, Serialize}; + +impl group::cofactor::CofactorGroup for Bandersnatch { + type Subgroup = Bandersnatch; + + fn clear_cofactor(&self) -> Self { + self * Fr::from(4) + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, 1.into()) + } + + fn is_torsion_free(&self) -> Choice { + 1.into() + } +} + +// Reference: https://eprint.iacr.org/2021/1152.pdf +// SW x: a76451786f95a802c0982bbd0abd68e41b92adc86c8859b4f44679b21658710 +const BANDERSNATCH_GENERATOR_X: Fp = Fp::from_raw([ + 0x4f44679b21658710, + 0x41b92adc86c8859b, + 0x2c0982bbd0abd68e, + 0xa76451786f95a80, +]); + +// SW y: 44d150c8b4bd14f79720d021a839e7b7eb4ee43844b30243126a72ac2375490a +const BANDERSNATCH_GENERATOR_Y: Fp = Fp::from_raw([ + 0x126a72ac2375490a, + 0xeb4ee43844b30243, + 0x9720d021a839e7b7, + 0x44d150c8b4bd14f7, +]); + +// − 3763200000 +// 73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFE1FB22001 +const BANDERSNATCH_A: Fp = Fp::from_raw([ + 0xFFFFFFFE1FB22001, + 0x53BDA402FFFE5BFE, + 0x3339D80809A1D805, + 0x73EDA753299D7D48, +]); + +// − 78675968000000 +// 73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFB870D2E00001 +const BANDERSNATCH_B: Fp = Fp::from_raw([ + 0xFFFFB870D2E00001, + 0x53BDA402FFFE5BFE, + 0x3339D80809A1D805, + 0x73EDA753299D7D48, +]); + +use crate::{ + impl_add_binop_specify_output, impl_binops_additive, impl_binops_additive_specify_output, + impl_binops_multiplicative, impl_binops_multiplicative_mixed, impl_sub_binop_specify_output, + new_curve_impl, +}; + +new_curve_impl!( + (pub), + Bandersnatch, + BandersnatchAffine, + true, + Fp, + Fr, + (BANDERSNATCH_GENERATOR_X,BANDERSNATCH_GENERATOR_Y), + BANDERSNATCH_A, + BANDERSNATCH_B, + "bandersnatch", + |curve_id, domain_prefix| sswu_hash_to_curve(curve_id, domain_prefix, Bandersnatch::SSVDW_Z), +); + +impl Bandersnatch { + // TODO: switch to bandersnatch param + // Optimal Z with: + // Script: https://github.com/privacy-scaling-explorations/halo2curves/pull/139#issuecomment-1965257028 + // Z = 7 (reference: ) + const SSVDW_Z: Fp = Fp::from_raw([ + 0x0000000000000007, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + ]); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bandersnatch::{Fp, Fr}; + use crate::group::Curve; + use ff::FromUniformBytes; + use rand_core::OsRng; + + #[test] + fn test_hash_to_curve() { + crate::tests::curve::hash_to_curve_test::(); + } + + #[test] + fn test_curve() { + crate::tests::curve::curve_tests::(); + } + + #[test] + fn test_serialization() { + crate::tests::curve::random_serialization_test::(); + #[cfg(feature = "derive_serde")] + crate::tests::curve::random_serde_test::(); + } + + #[test] + fn ecdsa_example() { + fn mod_n(x: Fp) -> Fr { + let mut x_repr = [0u8; 32]; + x_repr.copy_from_slice(x.to_repr().as_ref()); + let mut x_bytes = [0u8; 64]; + x_bytes[..32].copy_from_slice(&x_repr[..]); + Fr::from_uniform_bytes(&x_bytes) + } + + let g = Bandersnatch::generator(); + + for _ in 0..1000 { + // Generate a key pair + let sk = Fr::random(OsRng); + let pk = (g * sk).to_affine(); + + // Generate a valid signature + // Suppose `m_hash` is the message hash + let msg_hash = Fr::random(OsRng); + + let (r, s) = { + // Draw arandomness + let k = Fr::random(OsRng); + let k_inv = k.invert().unwrap(); + + // Calculate `r` + let r_point = (g * k).to_affine().coordinates().unwrap(); + let x = r_point.x(); + let r = mod_n(*x); + + // Calculate `s` + let s = k_inv * (msg_hash + (r * sk)); + + (r, s) + }; + + { + // Verify + let s_inv = s.invert().unwrap(); + let u_1 = msg_hash * s_inv; + let u_2 = r * s_inv; + + let v_1 = g * u_1; + let v_2 = pk * u_2; + + let r_point = (v_1 + v_2).to_affine().coordinates().unwrap(); + let x_candidate = r_point.x(); + let r_candidate = mod_n(*x_candidate); + + assert_eq!(r, r_candidate); + } + } + } +} diff --git a/src/bandersnatch/fr.rs b/src/bandersnatch/fr.rs new file mode 100644 index 00000000..41fbc1bd --- /dev/null +++ b/src/bandersnatch/fr.rs @@ -0,0 +1,379 @@ +use crate::arithmetic::{adc, mac, macx, sbb}; +use crate::extend_field_legendre; +use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; +use core::fmt; +use core::ops::{Add, Mul, Neg, Sub}; +use rand::RngCore; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +/// This represents an element of $\mathbb{F}_r$ where +/// +/// `r = 0x1cfb69d4ca675f520cce760202687600ff8f87007419047174fd06b52876e7e1` +/// +/// is the scalar field of the bandersnatch curve. +// The internal representation of this type is four 64-bit unsigned +// integers in little-endian order. `Fr` values are always in +// Montgomery form; i.e., Fr(a) = aR mod q, with R = 2^256. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct Fr(pub(crate) [u64; 4]); + +#[cfg(feature = "derive_serde")] +crate::serialize_deserialize_32_byte_primefield!(Fr); + +/// Constant representing the modulus +/// r = 0x1cfb69d4ca675f520cce760202687600ff8f87007419047174fd06b52876e7e1 +const MODULUS: Fr = Fr([ + 0x74fd06b52876e7e1, + 0xff8f870074190471, + 0x0cce760202687600, + 0x1cfb69d4ca675f52, +]); + +/// The modulus as u32 limbs. +#[cfg(not(target_pointer_width = "64"))] +const MODULUS_LIMBS_32: [u32; 8] = [ + 0x2876_e7e1, + 0x74fd_06b5, + 0x7419_0471, + 0xff8f_8700, + 0x0268_7600, + 0x0cce_7602, + 0xca67_5f52, + 0x1cfb_69d4, +]; + +///Constant representing the modulus as static str +const MODULUS_STR: &str = "0x1cfb69d4ca675f520cce760202687600ff8f87007419047174fd06b52876e7e1"; + +/// INV = -(r^{-1} mod 2^64) mod 2^64 +const INV: u64 = 0xF19F22295CC063DF; + +/// R = 2^256 mod r +/// 0x1824B159ACC5056F998C4FEFECBC4FF80383C7FC5F37DC745817CA56BC48C0F8 +const R: Fr = Fr([ + 0x5817CA56BC48C0F8, + 0x0383C7FC5F37DC74, + 0x998C4FEFECBC4FF8, + 0x1824B159ACC5056F, +]); + +/// R^2 = 2^512 mod r +/// 0xAE793DDB14AEC7DAA9E6DAEC0055CEA40FA7CA27FECB938DBB4F5D658DB47CB +const R2: Fr = Fr([ + 0xDBB4F5D658DB47CB, + 0x40FA7CA27FECB938, + 0xAA9E6DAEC0055CEA, + 0xAE793DDB14AEC7D, +]); + +/// R^3 = 2^768 mod r +/// 0x53A3B49F57751131126587105341936B0CE06DAEDDD77691EB6E3EB79377BD1 +const R3: Fr = Fr([ + 0x1EB6E3EB79377BD1, + 0xB0CE06DAEDDD7769, + 0x1126587105341936, + 0x53A3B49F5775113, +]); + +/// `GENERATOR = 7 mod r` is a generator of the `r - 1` order multiplicative +/// subgroup, or in other words a primitive root of the field. +/// It's derived with SageMath with: `GF(MODULUS).primitive_element()`. +const GENERATOR: Fr = Fr::from_raw([0x07, 0x00, 0x00, 0x00]); + +/// GENERATOR^t where t * 2^s + 1 = r with t odd. In other words, this is a 2^s root of unity. +/// t = 409655274805673363120685472720202858103411121670017820368325103335302739775 +/// `19470B7EFE802F9B36B6675F52C7008234BB3E0CB7ED22AEC65A62A1234BD960` +const ROOT_OF_UNITY: Fr = Fr::from_raw([ + 0xC65A62A1234BD960, + 0x34BB3E0CB7ED22AE, + 0x36B6675F52C70082, + 0x19470B7EFE802F9B, +]); + +/// 1 / ROOT_OF_UNITY mod r +/// `12BD24C544CCEE9E935FC01BF5106936E62D68F4CC287C26DE5B1F3C2F8A9200` +const ROOT_OF_UNITY_INV: Fr = Fr::from_raw([ + 0xDE5B1F3C2F8A9200, + 0xE62D68F4CC287C26, + 0x935FC01BF5106936, + 0x12BD24C544CCEE9E, +]); + +/// 1 / 2 mod r +/// E7DB4EA6533AFA906673B0101343B007FC7C3803A0C8238BA7E835A943B73F1 +// 0x1a8321a98684c180beebb2019390cad6e0d54b593ee3286311383e6c85936f08 +const TWO_INV: Fr = Fr::from_raw([ + 0xba7e835a943b73f1, + 0x7fc7c3803a0c8238, + 0x06673b0101343b00, + 0xe7db4ea6533afa9, +]); + +// C6A4BE1AB577A673E9D28FF76D10E05F34C2C9AC1E5639B32882FC4D84C3E8E +const ZETA: Fr = Fr::from_raw([ + 0x32882FC4D84C3E8E, + 0xF34C2C9AC1E5639B, + 0x3E9D28FF76D10E05, + 0xC6A4BE1AB577A67, +]); + +/// Generator of the t-order multiplicative subgroup. +/// Computed by exponentiating Self::MULTIPLICATIVE_GENERATOR by 2^s, where s is Self::S. +const DELTA: Fr = Fr::from_raw([0x303C33586E913B01, 0x3918FA8, 0, 0]); + +use crate::{ + field_arithmetic, field_bits, field_common, field_specific, impl_add_binop_specify_output, + impl_binops_additive, impl_binops_additive_specify_output, impl_binops_multiplicative, + impl_binops_multiplicative_mixed, impl_from_u64, impl_sub_binop_specify_output, impl_sum_prod, +}; +impl_binops_additive!(Fr, Fr); +impl_binops_multiplicative!(Fr, Fr); +field_common!( + Fr, + MODULUS, + INV, + MODULUS_STR, + TWO_INV, + ROOT_OF_UNITY_INV, + DELTA, + ZETA, + R, + R2, + R3 +); +impl_from_u64!(Fr, R2); +field_arithmetic!(Fr, MODULUS, INV, dense); +impl_sum_prod!(Fr); + +#[cfg(target_pointer_width = "64")] +field_bits!(Fr, MODULUS); +#[cfg(not(target_pointer_width = "64"))] +field_bits!(Fr, MODULUS, MODULUS_LIMBS_32); + +impl Fr { + pub const fn size() -> usize { + 32 + } +} + +impl ff::Field for Fr { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + + fn random(mut rng: impl RngCore) -> Self { + Self::from_u512([ + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + ]) + } + + fn double(&self) -> Self { + self.double() + } + + #[inline(always)] + fn square(&self) -> Self { + self.square() + } + + /// Returns the multiplicative inverse of the + /// element. If it is zero, the method fails. + fn invert(&self) -> CtOption { + self.invert() + } + + fn pow_vartime>(&self, exp: S) -> Self { + let mut res = Self::one(); + let mut found_one = false; + for e in exp.as_ref().iter().rev() { + for i in (0..64).rev() { + if found_one { + res = res.square(); + } + + if ((*e >> i) & 1) == 1 { + found_one = true; + res *= self; + } + } + } + res + } + + fn sqrt(&self) -> CtOption { + // 73EDA753299D7D483339D80809A1D803FE3E1C01D06411C5D3F41AD4A1DB9F + let tm1d2 = [ + 0xC5D3F41AD4A1DB9F, + 0x03FE3E1C01D06411, + 0x483339D80809A1D8, + 0x73EDA753299D7D, + ]; + + ff::helpers::sqrt_tonelli_shanks(self, tm1d2) + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + ff::helpers::sqrt_ratio_generic(num, div) + } +} + +impl ff::PrimeField for Fr { + type Repr = [u8; 32]; + + const NUM_BITS: u32 = 253; + const CAPACITY: u32 = 252; + const MODULUS: &'static str = MODULUS_STR; + const MULTIPLICATIVE_GENERATOR: Self = GENERATOR; + const ROOT_OF_UNITY: Self = ROOT_OF_UNITY; + const ROOT_OF_UNITY_INV: Self = ROOT_OF_UNITY_INV; + const TWO_INV: Self = TWO_INV; + const DELTA: Self = DELTA; + const S: u32 = 5; + + fn from_repr(repr: Self::Repr) -> CtOption { + let mut tmp = Fr([0, 0, 0, 0]); + + tmp.0[0] = u64::from_le_bytes(repr[0..8].try_into().unwrap()); + tmp.0[1] = u64::from_le_bytes(repr[8..16].try_into().unwrap()); + tmp.0[2] = u64::from_le_bytes(repr[16..24].try_into().unwrap()); + tmp.0[3] = u64::from_le_bytes(repr[24..32].try_into().unwrap()); + + // Try to subtract the modulus + let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0); + let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow); + let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow); + let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow); + + // If the element is smaller than MODULUS then the + // subtraction will underflow, producing a borrow value + // of 0xffff...ffff. Otherwise, it'll be zero. + let is_some = (borrow as u8) & 1; + + // Convert to Montgomery form by computi + // (a.R^0 * R^2) / R = a.R + tmp *= &R2; + + CtOption::new(tmp, Choice::from(is_some)) + } + + fn to_repr(&self) -> Self::Repr { + let tmp: [u64; 4] = (*self).into(); + let mut res = [0; 32]; + res[0..8].copy_from_slice(&tmp[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp[3].to_le_bytes()); + + res + } + + fn is_odd(&self) -> Choice { + Choice::from(self.to_repr()[0] & 1) + } +} + +impl FromUniformBytes<64> for Fr { + /// Converts a 512-bit little endian integer into + /// an `Fr` by reducing by the modulus. + fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { + Self::from_u512([ + u64::from_le_bytes(bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), + u64::from_le_bytes(bytes[16..24].try_into().unwrap()), + u64::from_le_bytes(bytes[24..32].try_into().unwrap()), + u64::from_le_bytes(bytes[32..40].try_into().unwrap()), + u64::from_le_bytes(bytes[40..48].try_into().unwrap()), + u64::from_le_bytes(bytes[48..56].try_into().unwrap()), + u64::from_le_bytes(bytes[56..64].try_into().unwrap()), + ]) + } +} + +impl WithSmallOrderMulGroup<3> for Fr { + const ZETA: Self = ZETA; +} + +extend_field_legendre!(Fr); + +#[cfg(test)] +mod test { + use super::*; + use ff::Field; + use rand_core::OsRng; + + #[test] + fn test_zeta() { + assert_eq!(Fr::ZETA * Fr::ZETA * Fr::ZETA, Fr::ONE); + assert_ne!(Fr::ZETA * Fr::ZETA, Fr::ONE); + } + + #[test] + fn test_sqrt() { + // NB: TWO_INV is standing in as a "random" field element + // let v = (Fr::TWO_INV).square().sqrt().unwrap(); + // assert!(v == Fr::TWO_INV || (-v) == Fr::TWO_INV); + + for _ in 0..10000 { + let a = Fr::random(OsRng); + let mut b = a; + b = b.square(); + + let b = b.sqrt().unwrap(); + let mut negb = b; + negb = negb.neg(); + + assert!(a == b || a == negb); + } + } + + #[test] + fn test_constants() { + assert_eq!( + Fr::MODULUS, + "0x1cfb69d4ca675f520cce760202687600ff8f87007419047174fd06b52876e7e1", + ); + } + + #[test] + fn test_delta() { + assert_eq!(Fr::DELTA, Fr::MULTIPLICATIVE_GENERATOR.pow([1u64 << Fr::S])); + } + + #[test] + fn test_root_of_unity() { + assert_eq!(Fr::ROOT_OF_UNITY.pow_vartime([1 << Fr::S]), Fr::one()); + } + + #[test] + fn test_inv_root_of_unity() { + assert_eq!(Fr::ROOT_OF_UNITY_INV, Fr::ROOT_OF_UNITY.invert().unwrap()); + } + + #[test] + fn test_field() { + crate::tests::field::random_field_tests::("bandersnatch scalar".to_string()); + } + + #[test] + fn test_conversion() { + crate::tests::field::random_conversion_tests::("bandersnatch scalar".to_string()); + } + + #[test] + fn test_serialization() { + crate::tests::field::random_serialization_test::("bandersnatch scalar".to_string()); + #[cfg(feature = "derive_serde")] + crate::tests::field::random_serde_test::("bandersnatch scalar".to_string()); + } + + #[test] + fn test_quadratic_residue() { + crate::tests::field::random_quadratic_residue_test::(); + } +} diff --git a/src/bandersnatch/mod.rs b/src/bandersnatch/mod.rs new file mode 100644 index 00000000..ab9cd125 --- /dev/null +++ b/src/bandersnatch/mod.rs @@ -0,0 +1,8 @@ +mod curve; +mod fr; +mod te_curve; + +pub use crate::bls12_381::Scalar as Fp; +pub use curve::*; +pub use fr::*; +pub use te_curve::*; diff --git a/src/bandersnatch/te_curve.rs b/src/bandersnatch/te_curve.rs new file mode 100644 index 00000000..ab8f5140 --- /dev/null +++ b/src/bandersnatch/te_curve.rs @@ -0,0 +1,1095 @@ +use crate::bandersnatch::Fp; +use crate::bandersnatch::Fr; +use crate::{Coordinates, CurveAffine, CurveExt}; +use core::cmp; +use core::fmt::Debug; +use core::iter::Sum; +use core::ops::{Add, Mul, Neg, Sub}; +use ff::{BatchInverter, Field, PrimeField}; +use group::{self, Curve}; +use group::{prime::PrimeCurveAffine, GroupEncoding}; +use rand::RngCore; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +#[cfg(feature = "derive_serde")] +use serde::{Deserialize, Serialize}; + +// `bandersnatch` is an incomplete twisted Edwards curve. These curves have +// equations of the form: ax² + y² = 1 + dx²y². +// over some base finite field Fp. +// +// Bandersnatch's curve equation: -5x² + y² = 1 + dx²y² +// +// q = 52435875175126190479447740508185965837690552500527637822603658699938581184513. +// +// a = -5. +// d = (138827208126141220649022263972958607803/ +// 171449701953573178309673572579671231137) mod q +// = 45022363124591815672509500913686876175488063829319466900776701791074614335719. +// +// Sage script to calculate these: +// +// ```text +// q = 52435875175126190479447740508185965837690552500527637822603658699938581184513 +// Fp = GF(q) +// d = (Fp(138827208126141220649022263972958607803)/Fp(171449701953573178309673572579671231137)) +// ``` +// These parameters and the sage script obtained from: +// + +// The TE form generator is generated following Zcash's fashion: +// "The generators of G1 and G2 are computed by finding the lexicographically +// smallest valid x-coordinate, and its lexicographically smallest +// y-coordinate and scaling it by the cofactor such that the result is not +// the point at infinity." +// The SW form generator is the same TE generator converted into SW form, +// obtained from the scripts: +// + +// Reference: https://eprint.iacr.org/2021/1152.pdf + +// TE x: 29c132cc2c0b34c5743711777bbe42f32b79c022ad998465e1e71866a252ae18 + +const TE_BANDERSNATCH_GENERATOR_X: Fp = Fp::from_raw([ + 0xe1e71866a252ae18, + 0x2b79c022ad998465, + 0x743711777bbe42f3, + 0x29c132cc2c0b34c5, +]); + +// TE y: 2a6c669eda123e0f157d8b50badcd586358cad81eee464605e3167b6cc974166 +const TE_BANDERSNATCH_GENERATOR_Y: Fp = Fp::from_raw([ + 0x5e3167b6cc974166, + 0x358cad81eee46460, + 0x157d8b50badcd586, + 0x2a6c669eda123e0f, +]); + +// 73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFEFFFFFFFC +const TE_A_PARAMETER: Fp = Fp::from_raw([ + 0xFFFFFFFEFFFFFFFC, + 0x53BDA402FFFE5BFE, + 0x3339D80809A1D805, + 0x73EDA753299D7D48, +]); + +// 6389C12633C267CBC66E3BF86BE3B6D8CB66677177E54F92B369F2F5188D58E7 +const TE_D_PARAMETER: Fp = Fp::from_raw([ + 0xB369F2F5188D58E7, + 0xCB66677177E54F92, + 0xC66E3BF86BE3B6D8, + 0x6389C12633C267CB, +]); + +const FR_MODULUS_BYTES: [u8; 32] = [ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, 1, 1, 59, + 103, 6, 169, 175, 51, 101, 234, 180, 125, 14, +]; + +use crate::{ + impl_add_binop_specify_output, impl_binops_additive, impl_binops_additive_specify_output, + impl_binops_multiplicative, impl_binops_multiplicative_mixed, impl_sub_binop_specify_output, +}; + +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "derive_serde", derive(Serialize, Deserialize))] +pub struct BandersnatchTE { + pub x: Fp, + pub y: Fp, + pub z: Fp, + pub t: Fp, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "derive_serde", derive(Serialize, Deserialize))] +pub struct BandersnatchTEAffine { + pub x: Fp, + pub y: Fp, +} + +#[derive(Copy, Clone, Hash, Default)] +pub struct BandersnatchTECompressed([u8; 32]); + +impl BandersnatchTE { + /// Constructs an extended point from the neutral element `(0, 1)`. + pub const fn identity() -> Self { + BandersnatchTE { + x: Fp::zero(), + y: Fp::one(), + z: Fp::one(), + t: Fp::zero(), + } + } + + /// Determines if this point is the identity. + pub fn is_identity(&self) -> Choice { + // If this point is the identity, then + // u = 0 * z = 0 + // and v = 1 * z = z + self.x.ct_eq(&Fp::zero()) & self.y.ct_eq(&self.z) + } + + /// Determines if this point is torsion free and so is contained + /// in the prime order subgroup. + pub fn is_torsion_free(&self) -> Choice { + self.multiply(&FR_MODULUS_BYTES).is_identity() + } + + #[inline] + fn multiply(&self, by: &[u8; 32]) -> BandersnatchTE { + let zero = BandersnatchTE::identity(); + let mut acc = BandersnatchTE::identity(); + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading three bits because they're always + // unset for Fr. + for bit in by + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(3) + { + acc = acc.double(); + acc += BandersnatchTE::conditional_select(&zero, self, bit); + } + + acc + } + + /// Multiplies this element by the cofactor `4`. + pub fn mul_by_cofactor(&self) -> BandersnatchTE { + self.double().double() + } + + pub fn generator() -> Self { + let generator = BandersnatchTEAffine::generator(); + Self { + x: generator.x, + y: generator.y, + z: Fp::one(), + t: generator.x * generator.y, + } + } + + pub fn double(&self) -> BandersnatchTE { + // Doubling is more efficient (three multiplications, four squarings) + // when we work within the projective coordinate space (U:Z, V:Z). We + // rely on the most efficient formula, link: https://hyperelliptic.org/EFD/g1p/data/twisted/extended/doubling/dbl-2008-hwcd. + + // A = X1^2 + // B = Y1^2 + // C = 2*Z1^2 + // D = a*A + // E = (X1+Y1)^2-A-B + // G = D+B + // F = G-C + // H = D-B + // X3 = E*F + // Y3 = G*H + // T3 = E*H + // Z3 = F*G + + let a = self.x.square(); + let b = self.y.square(); + let c = self.z.square().double(); + let d = TE_A_PARAMETER * a; + let e = (self.x + self.y).square() - a - b; + let g = d + b; + let f = g - c; + let h = d - b; + + BandersnatchTE { + x: e * f, + y: g * h, + z: f * g, + t: e * h, + } + } +} + +impl BandersnatchTEAffine { + /// Constructs the neutral element `(0, 1)`. + pub const fn identity() -> Self { + BandersnatchTEAffine { + x: Fp::zero(), + y: Fp::one(), + } + } + + /// Determines if this point is the identity. + pub fn is_identity(&self) -> Choice { + self.x.is_zero() & self.y.is_zero() + } + + pub fn generator() -> Self { + Self { + x: TE_BANDERSNATCH_GENERATOR_X, + y: TE_BANDERSNATCH_GENERATOR_Y, + } + } + + pub fn to_extended(&self) -> BandersnatchTE { + BandersnatchTE { + x: self.x, + y: self.y, + z: Fp::one(), + t: self.x * self.y, + } + } + + // TODO: fix this to work with BandersnatchTE + pub fn random(mut rng: impl RngCore) -> Self { + loop { + let y = Fp::random(&mut rng); + let flip_sign = rng.next_u32() % 2 != 0; + + let y2 = y.square(); + let p = ((y2 - Fp::one()) + * ((Fp::one() + TE_D_PARAMETER * y2) + .invert() + .unwrap_or(Fp::zero()))) + .sqrt() + .map(|x| BandersnatchTEAffine { + x: if flip_sign { -x } else { x }, + y, + }); + + if p.is_some().into() { + use crate::group::cofactor::CofactorGroup; + let p = p.unwrap().to_curve(); + + if bool::from(!p.is_identity()) { + return p.clear_cofactor().to_affine(); + } + } + } + } + + /// Converts this element into its byte representation. + pub fn to_bytes(&self) -> [u8; 32] { + let mut tmp = self.y.to_bytes(); + let u = self.x.to_bytes(); + + // Encode the sign of the u-coordinate in the most + // significant bit. + tmp[31] |= u[0] << 7; + + tmp + } + + /// Attempts to interpret a byte representation of an + /// affine point, failing if the element is not on + /// the curve or non-canonical. + pub fn from_bytes(b: [u8; 32]) -> CtOption { + Self::from_bytes_inner(b, 1.into()) + } + + fn from_bytes_inner(mut b: [u8; 32], zip_216_enabled: Choice) -> CtOption { + // Grab the sign bit from the representation + let sign = b[31] >> 7; + + // Mask away the sign bit + b[31] &= 0b0111_1111; + + // Interpret what remains as the v-coordinate + Fp::from_bytes(&b).and_then(|v| { + // -u^2 + v^2 = 1 + d.u^2.v^2 + // -u^2 = 1 + d.u^2.v^2 - v^2 (rearrange) + // -u^2 - d.u^2.v^2 = 1 - v^2 (rearrange) + // u^2 + d.u^2.v^2 = v^2 - 1 (flip signs) + // u^2 (1 + d.v^2) = v^2 - 1 (factor) + // u^2 = (v^2 - 1) / (1 + d.v^2) (isolate u^2) + // We know that (1 + d.v^2) is nonzero for all v: + // (1 + d.v^2) = 0 + // d.v^2 = -1 + // v^2 = -(1 / d) No solutions, as -(1 / d) is not a square + + let v2 = v.square(); + + ((v2 - Fp::one()) + * ((Fp::one() + TE_D_PARAMETER * v2) + .invert() + .unwrap_or(Fp::zero()))) + .sqrt() + .and_then(|u| { + // Fix the sign of `u` if necessary + let flip_sign = Choice::from((u.to_bytes()[0] ^ sign) & 1); + let u_negated = -u; + let final_u = Fp::conditional_select(&u, &u_negated, flip_sign); + + // If u == 0, flip_sign == sign_bit. We therefore want to reject the + // encoding as non-canonical if all of the following occur: + // - ZIP 216 is enabled + // - u == 0 + // - flip_sign == true + let u_is_zero = u.ct_eq(&Fp::zero()); + CtOption::new( + BandersnatchTEAffine { x: final_u, y: v }, + !(zip_216_enabled & u_is_zero & flip_sign), + ) + }) + }) + } +} + +// Compressed +impl std::fmt::Debug for BandersnatchTECompressed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0[..].fmt(f) + } +} + +impl AsRef<[u8]> for BandersnatchTECompressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for BandersnatchTECompressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +// Jacobian implementations +impl<'a> From<&'a BandersnatchTEAffine> for BandersnatchTE { + fn from(p: &'a BandersnatchTEAffine) -> BandersnatchTE { + p.to_curve() + } +} + +impl From for BandersnatchTE { + fn from(p: BandersnatchTEAffine) -> BandersnatchTE { + p.to_curve() + } +} + +impl Default for BandersnatchTE { + fn default() -> BandersnatchTE { + BandersnatchTE::identity() + } +} + +impl subtle::ConstantTimeEq for BandersnatchTE { + fn ct_eq(&self, other: &Self) -> Choice { + (self.x * other.z).ct_eq(&(other.x * self.z)) + & (self.y * other.z).ct_eq(&(other.y * self.z)) + } +} + +impl subtle::ConditionallySelectable for BandersnatchTE { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + BandersnatchTE { + x: Fp::conditional_select(&a.x, &b.x, choice), + y: Fp::conditional_select(&a.y, &b.y, choice), + z: Fp::conditional_select(&a.z, &b.z, choice), + t: Fp::conditional_select(&a.t, &b.t, choice), + } + } +} + +impl PartialEq for BandersnatchTE { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl cmp::Eq for BandersnatchTE {} + +impl CurveExt for BandersnatchTE { + type ScalarExt = Fr; + type Base = Fp; + type AffineExt = BandersnatchTEAffine; + + const CURVE_ID: &'static str = "BandersnatchTE"; + + fn is_on_curve(&self) -> Choice { + let affine = BandersnatchTEAffine::from(*self); + !self.z.is_zero() & affine.is_on_curve() & (affine.x * affine.y * self.z).ct_eq(&self.t) + } + + fn endo(&self) -> Self { + unimplemented!(); + } + + fn jacobian_coordinates(&self) -> (Fp, Fp, Fp) { + unimplemented!(); + } + + fn hash_to_curve<'a>(_: &'a str) -> Box Self + 'a> { + unimplemented!(); + } + + fn a() -> Self::Base { + unimplemented!() + } + + fn b() -> Self::Base { + unimplemented!() + } + + fn new_jacobian(_x: Self::Base, _y: Self::Base, _z: Self::Base) -> CtOption { + unimplemented!(); + } +} + +impl group::Curve for BandersnatchTE { + type AffineRepr = BandersnatchTEAffine; + + fn batch_normalize(p: &[Self], q: &mut [Self::AffineRepr]) { + assert_eq!(p.len(), q.len()); + + for (p, q) in p.iter().zip(q.iter_mut()) { + // We use the `u` field of `AffinePoint` to store the z-coordinate being + // inverted, and the `v` field for scratch space. + q.x = p.z; + } + + BatchInverter::invert_with_internal_scratch(q, |q| &mut q.x, |q| &mut q.y); + + for (p, q) in p.iter().zip(q.iter_mut()).rev() { + let tmp = q.x; + + // Set the coordinates to the correct value + q.x = p.x * tmp; // Multiply by 1/z + q.y = p.y * tmp; // Multiply by 1/z + } + } + + fn to_affine(&self) -> Self::AffineRepr { + // Z coordinate is always nonzero, so this is + // its inverse. + let zinv = self.z.invert().unwrap(); + + BandersnatchTEAffine { + x: self.x * zinv, + y: self.y * zinv, + } + } +} + +impl group::Group for BandersnatchTE { + type Scalar = Fr; + + fn random(mut rng: impl RngCore) -> Self { + BandersnatchTEAffine::random(&mut rng).to_curve() + } + + fn generator() -> Self { + BandersnatchTE::generator() + } + + fn identity() -> Self { + Self::identity() + } + + fn is_identity(&self) -> Choice { + self.is_identity() + } + + #[must_use] + fn double(&self) -> Self { + self.double() + } +} + +impl GroupEncoding for BandersnatchTE { + type Repr = BandersnatchTECompressed; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + BandersnatchTEAffine::from_bytes(bytes.0).map(Self::from) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + BandersnatchTEAffine::from_bytes(bytes.0).map(Self::from) + } + + fn to_bytes(&self) -> Self::Repr { + BandersnatchTECompressed(BandersnatchTEAffine::from(self).to_bytes()) + } +} + +impl crate::serde::SerdeObject for BandersnatchTE { + fn from_raw_bytes_unchecked(bytes: &[u8]) -> Self { + debug_assert_eq!(bytes.len(), 4 * Fp::size()); + let [x, y, z, t] = [0, 1, 2, 3] + .map(|i| Fp::from_raw_bytes_unchecked(&bytes[i * Fp::size()..(i + 1) * Fp::size()])); + Self { x, y, z, t } + } + fn from_raw_bytes(bytes: &[u8]) -> Option { + if bytes.len() != 4 * Fp::size() { + return None; + } + let [x, y, z, t] = + [0, 1, 2, 3].map(|i| Fp::from_raw_bytes(&bytes[i * Fp::size()..(i + 1) * Fp::size()])); + x.zip(y).zip(z).zip(t).and_then(|(((x, y), z), t)| { + let res = Self { x, y, z, t }; + // Check that the point is on the curve. + bool::from(res.is_on_curve()).then_some(res) + }) + } + fn to_raw_bytes(&self) -> Vec { + let mut res = Vec::with_capacity(4 * Fp::size()); + Self::write_raw(self, &mut res).unwrap(); + res + } + fn read_raw_unchecked(reader: &mut R) -> Self { + let [x, y, z, t] = [(); 4].map(|_| Fp::read_raw_unchecked(reader)); + Self { x, y, z, t } + } + fn read_raw(reader: &mut R) -> std::io::Result { + let x = Fp::read_raw(reader)?; + let y = Fp::read_raw(reader)?; + let z = Fp::read_raw(reader)?; + let t = Fp::read_raw(reader)?; + Ok(Self { x, y, z, t }) + } + fn write_raw(&self, writer: &mut W) -> std::io::Result<()> { + self.x.write_raw(writer)?; + self.y.write_raw(writer)?; + self.z.write_raw(writer)?; + self.t.write_raw(writer) + } +} + +impl group::prime::PrimeGroup for BandersnatchTE {} + +impl group::prime::PrimeCurve for BandersnatchTE { + type Affine = BandersnatchTEAffine; +} + +impl group::cofactor::CofactorCurve for BandersnatchTE { + type Affine = BandersnatchTEAffine; +} + +impl group::cofactor::CofactorGroup for BandersnatchTE { + type Subgroup = BandersnatchTE; + + fn clear_cofactor(&self) -> Self { + self.mul_by_cofactor() + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, self.is_torsion_free()) + } + + fn is_torsion_free(&self) -> Choice { + self.is_torsion_free() + } +} + +impl<'a> From<&'a BandersnatchTE> for BandersnatchTEAffine { + fn from(p: &'a BandersnatchTE) -> BandersnatchTEAffine { + p.to_affine() + } +} + +impl From for BandersnatchTEAffine { + fn from(p: BandersnatchTE) -> BandersnatchTEAffine { + p.to_affine() + } +} + +impl Default for BandersnatchTEAffine { + fn default() -> BandersnatchTEAffine { + BandersnatchTEAffine::identity() + } +} + +impl subtle::ConstantTimeEq for BandersnatchTEAffine { + fn ct_eq(&self, other: &Self) -> Choice { + self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y) + } +} + +impl subtle::ConditionallySelectable for BandersnatchTEAffine { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + BandersnatchTEAffine { + x: Fp::conditional_select(&a.x, &b.x, choice), + y: Fp::conditional_select(&a.y, &b.y, choice), + } + } +} + +impl cmp::Eq for BandersnatchTEAffine {} + +impl group::GroupEncoding for BandersnatchTEAffine { + type Repr = [u8; 32]; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + Self::from_bytes(*bytes) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + Self::from_bytes(*bytes) + } + + fn to_bytes(&self) -> Self::Repr { + self.to_bytes() + } +} + +impl crate::serde::SerdeObject for BandersnatchTEAffine { + fn from_raw_bytes_unchecked(bytes: &[u8]) -> Self { + debug_assert_eq!(bytes.len(), 2 * Fp::size()); + let [x, y] = + [0, Fp::size()].map(|i| Fp::from_raw_bytes_unchecked(&bytes[i..i + Fp::size()])); + Self { x, y } + } + fn from_raw_bytes(bytes: &[u8]) -> Option { + if bytes.len() != 2 * Fp::size() { + return None; + } + let [x, y] = [0, Fp::size()].map(|i| Fp::from_raw_bytes(&bytes[i..i + Fp::size()])); + x.zip(y).and_then(|(x, y)| { + let res = Self { x, y }; + // Check that the point is on the curve. + bool::from(res.is_on_curve()).then_some(res) + }) + } + fn to_raw_bytes(&self) -> Vec { + let mut res = Vec::with_capacity(2 * Fp::size()); + Self::write_raw(self, &mut res).unwrap(); + res + } + fn read_raw_unchecked(reader: &mut R) -> Self { + let [x, y] = [(); 2].map(|_| Fp::read_raw_unchecked(reader)); + Self { x, y } + } + fn read_raw(reader: &mut R) -> std::io::Result { + let x = Fp::read_raw(reader)?; + let y = Fp::read_raw(reader)?; + Ok(Self { x, y }) + } + fn write_raw(&self, writer: &mut W) -> std::io::Result<()> { + self.x.write_raw(writer)?; + self.y.write_raw(writer) + } +} + +impl group::prime::PrimeCurveAffine for BandersnatchTEAffine { + type Curve = BandersnatchTE; + type Scalar = Fr; + + fn generator() -> Self { + BandersnatchTEAffine::generator() + } + + fn identity() -> Self { + BandersnatchTEAffine::identity() + } + + fn is_identity(&self) -> Choice { + self.is_identity() + } + + fn to_curve(&self) -> Self::Curve { + BandersnatchTE { + x: self.x, + y: self.y, + z: Fp::one(), + t: self.x * self.y, + } + } +} + +impl group::cofactor::CofactorCurveAffine for BandersnatchTEAffine { + type Curve = BandersnatchTE; + type Scalar = Fr; + + fn identity() -> Self { + ::identity() + } + + fn generator() -> Self { + ::generator() + } + + fn is_identity(&self) -> Choice { + ::is_identity(self) + } + + fn to_curve(&self) -> Self::Curve { + ::to_curve(self) + } +} + +impl CurveAffine for BandersnatchTEAffine { + type ScalarExt = Fr; + type Base = Fp; + type CurveExt = BandersnatchTE; + + fn is_on_curve(&self) -> Choice { + let x2 = self.x.square(); + let ax2 = self.x.square() * TE_A_PARAMETER; + let y2 = self.y.square(); + + (ax2 + y2).ct_eq(&(Fp::one() + TE_D_PARAMETER * x2 * y2)) | self.is_identity() + } + + fn coordinates(&self) -> CtOption> { + Coordinates::from_xy(self.x, self.y) + } + + fn from_xy(x: Self::Base, y: Self::Base) -> CtOption { + let p = BandersnatchTEAffine { x, y }; + CtOption::new(p, p.is_on_curve()) + } + + fn a() -> Self::Base { + unimplemented!() + } + + fn b() -> Self::Base { + unimplemented!() + } +} + +impl_binops_additive!(BandersnatchTE, BandersnatchTE); +impl_binops_additive!(BandersnatchTE, BandersnatchTEAffine); +impl_binops_additive_specify_output!(BandersnatchTEAffine, BandersnatchTEAffine, BandersnatchTE); +impl_binops_additive_specify_output!(BandersnatchTEAffine, BandersnatchTE, BandersnatchTE); +impl_binops_multiplicative!(BandersnatchTE, Fr); +impl_binops_multiplicative_mixed!(BandersnatchTEAffine, Fr, BandersnatchTE); + +impl<'a> Neg for &'a BandersnatchTE { + type Output = BandersnatchTE; + + fn neg(self) -> BandersnatchTE { + BandersnatchTE { + x: -self.x, + y: self.y, + z: self.z, + t: -self.t, + } + } +} + +impl Neg for BandersnatchTE { + type Output = BandersnatchTE; + + fn neg(self) -> BandersnatchTE { + -&self + } +} + +impl Sum for BandersnatchTE +where + T: core::borrow::Borrow, +{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Self::identity(), |acc, item| acc + item.borrow()) + } +} + +impl<'a, 'b> Add<&'a BandersnatchTE> for &'b BandersnatchTE { + type Output = BandersnatchTE; + + fn add(self, rhs: &'a BandersnatchTE) -> BandersnatchTE { + // formula: https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd + + // A = X1*X2 + // B = Y1*Y2 + // C = T1*d*T2 + // D = Z1*Z2 + // E = (X1+Y1)*(X2+Y2)-A-B + // F = D-C + // G = D+C + // H = B-a*A + // X3 = E*F + // Y3 = G*H + // T3 = E*H + // Z3 = F*G + + let a = self.x * rhs.x; + let b = self.y * rhs.y; + let c = self.t * TE_D_PARAMETER * rhs.t; + let d = self.z * rhs.z; + let e = (self.x + self.y) * (rhs.x + rhs.y) - a - b; + let f = d - c; + let g = d + c; + let h = b - TE_A_PARAMETER * a; + + let _u_r = e * f; + + BandersnatchTE { + x: e * f, + y: g * h, + z: f * g, + t: e * h, + } + } +} + +impl<'a, 'b> Add<&'a BandersnatchTEAffine> for &'b BandersnatchTE { + type Output = BandersnatchTE; + + fn add(self, rhs: &'a BandersnatchTEAffine) -> BandersnatchTE { + self + rhs.to_extended() + } +} + +impl<'a, 'b> Sub<&'a BandersnatchTE> for &'b BandersnatchTE { + type Output = BandersnatchTE; + + fn sub(self, other: &'a BandersnatchTE) -> BandersnatchTE { + self + (-other) + } +} + +impl<'a, 'b> Sub<&'a BandersnatchTEAffine> for &'b BandersnatchTE { + type Output = BandersnatchTE; + + fn sub(self, other: &'a BandersnatchTEAffine) -> BandersnatchTE { + self + (-other) + } +} + +#[allow(clippy::suspicious_arithmetic_impl)] +impl<'a, 'b> Mul<&'b Fr> for &'a BandersnatchTE { + type Output = BandersnatchTE; + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading three bits because they're always + // unset for Fr. + fn mul(self, other: &'b Fr) -> Self::Output { + let mut acc = BandersnatchTE::identity(); + for bit in other + .to_repr() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(3) + { + acc = acc.double(); + acc = BandersnatchTE::conditional_select(&acc, &(acc + self), bit); + } + + acc + } +} + +impl<'a> Neg for &'a BandersnatchTEAffine { + type Output = BandersnatchTEAffine; + + fn neg(self) -> BandersnatchTEAffine { + BandersnatchTEAffine { + x: -self.x, + y: self.y, + } + } +} + +impl Neg for BandersnatchTEAffine { + type Output = BandersnatchTEAffine; + + fn neg(self) -> BandersnatchTEAffine { + -&self + } +} + +impl<'a, 'b> Add<&'a BandersnatchTE> for &'b BandersnatchTEAffine { + type Output = BandersnatchTE; + + fn add(self, rhs: &'a BandersnatchTE) -> BandersnatchTE { + rhs + self + } +} + +impl<'a, 'b> Add<&'a BandersnatchTEAffine> for &'b BandersnatchTEAffine { + type Output = BandersnatchTE; + + fn add(self, rhs: &'a BandersnatchTEAffine) -> BandersnatchTE { + self.to_extended() + rhs.to_extended() + } +} + +impl<'a, 'b> Sub<&'a BandersnatchTEAffine> for &'b BandersnatchTEAffine { + type Output = BandersnatchTE; + + fn sub(self, other: &'a BandersnatchTEAffine) -> BandersnatchTE { + self + (-other) + } +} + +impl<'a, 'b> Sub<&'a BandersnatchTE> for &'b BandersnatchTEAffine { + type Output = BandersnatchTE; + + fn sub(self, other: &'a BandersnatchTE) -> BandersnatchTE { + self + (-other) + } +} + +#[allow(clippy::suspicious_arithmetic_impl)] +impl<'a, 'b> Mul<&'b Fr> for &'a BandersnatchTEAffine { + type Output = BandersnatchTE; + + fn mul(self, other: &'b Fr) -> Self::Output { + let mut acc = BandersnatchTE::identity(); + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading three bits because they're always + // unset for Fr. + for bit in other + .to_repr() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + { + acc = acc.double(); + acc = BandersnatchTE::conditional_select(&acc, &(acc + self), bit); + } + + acc + } +} + +pub trait CurveAffineExt: pasta_curves::arithmetic::CurveAffine { + /// Unlike the `Coordinates` trait, this just returns the raw affine coordinates without checking `is_on_curve` + fn into_coordinates(self) -> (Self::Base, Self::Base) { + // fallback implementation + let coordinates = self.coordinates().unwrap(); + (*coordinates.x(), *coordinates.y()) + } +} + +impl CurveAffineExt for BandersnatchTEAffine { + fn into_coordinates(self) -> (Self::Base, Self::Base) { + (self.x, self.y) + } +} + +pub trait TwistedEdwardsCurveExt: CurveExt { + fn a() -> ::Base; + fn d() -> ::Base; +} + +impl TwistedEdwardsCurveExt for BandersnatchTE { + fn a() -> Fp { + TE_A_PARAMETER + } + + fn d() -> Fp { + TE_D_PARAMETER + } +} + +pub trait TwistedEdwardsCurveAffineExt: CurveAffineExt { + fn a() -> ::Base; + fn d() -> ::Base; +} + +impl TwistedEdwardsCurveAffineExt for BandersnatchTEAffine { + fn a() -> Fp { + TE_A_PARAMETER + } + + fn d() -> Fp { + TE_D_PARAMETER + } +} +#[cfg(test)] +mod tests { + use crate::bandersnatch::te_curve::TE_D_PARAMETER; + use crate::bandersnatch::BandersnatchTE; + use crate::bandersnatch::BandersnatchTEAffine; + use crate::bandersnatch::Fr; + use ff::Field; + use ff::PrimeField; + use pasta_curves::arithmetic::CurveAffine; + use pasta_curves::arithmetic::CurveExt; + + #[test] + fn test_is_on_curve() { + assert!(bool::from(BandersnatchTEAffine::identity().is_on_curve())); + assert!(bool::from(BandersnatchTEAffine::generator().is_on_curve())); + } + + #[test] + fn test_d_is_non_quadratic_residue() { + assert!(bool::from(TE_D_PARAMETER.sqrt().is_none())); + assert!(bool::from((-TE_D_PARAMETER).sqrt().is_none())); + assert!(bool::from( + (-TE_D_PARAMETER).invert().unwrap().sqrt().is_none() + )); + } + + #[test] + fn test_double() { + let p = BandersnatchTE::generator(); + + assert_eq!(p.double(), p + p); + + let double_g = p.double(); + + // Value is taken from arworks implementation of bandersnatch. + assert_eq!( + double_g.x, + bls12_381::Scalar::from_str_vartime( + // in this context this is Bandersnatch base field although struct name is Scalar. + "47509778783496412982820807418084268119503941123460587829794679458985081388520" + ) + .unwrap() + ); + } + + #[test] + fn test_assoc() { + let p = BandersnatchTE::generator().mul_by_cofactor(); + assert!(bool::from(p.is_on_curve())); + + assert_eq!( + (p * Fr::from(1000u64)) * Fr::from(3938u64), + p * (Fr::from(1000u64) * Fr::from(3938u64)), + ); + } + #[test] + fn test_equality_scalar_mul_double_addition() { + let generator = BandersnatchTEAffine::generator(); + + let proj_generator = generator.to_extended(); + + let double_g = proj_generator + proj_generator; + + let double_g_double = proj_generator.double(); + + let scalar_mul = proj_generator * Fr::from(2); + + assert!(double_g.eq(&double_g_double)); + assert!(double_g.eq(&scalar_mul)); + + let minus = double_g - proj_generator; + + assert!(proj_generator.eq(&minus)); + + let quadruple_g = proj_generator.double().double(); + + let scalar_mul_4 = proj_generator * Fr::from(4); + + assert!(quadruple_g.eq(&scalar_mul_4)); + } + + // tests failing because of random() is wrong + // #[test] + // fn test_curve() { + // crate::tests::curve::curve_tests::(); + // } + + // tests failing + // #[test] + // fn test_serialization() { + // crate::tests::curve::random_serialization_test::(); + // } +} diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs new file mode 100644 index 00000000..ea4c9905 --- /dev/null +++ b/src/bls12_381/mod.rs @@ -0,0 +1,134 @@ +use crate::arithmetic::mul_512; +use crate::arithmetic::sbb; +use crate::{ + arithmetic::{CurveEndo, EndoParameters}, + endo, +}; +pub use bls12_381::{Fp, G1Projective, Scalar}; +use ff::PrimeField; +use ff::WithSmallOrderMulGroup; +use std::convert::TryInto; +use std::io::{self, Read, Write}; + + +pub use bls12_381::*; + + +#[cfg(feature = "derive_serde")] +use serde::{Deserialize, Serialize}; + + +// Obtained from https://github.com/ConsenSys/gnark-crypto/blob/master/ecc/utils.go +// See https://github.com/demining/Endomorphism-Secp256k1/blob/main/README.md +// to have more details about the endomorphism. +const ENDO_PARAMS_BLS: 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], +}; + +endo!(G1Projective, Scalar, ENDO_PARAMS_BLS); + + +impl crate::serde::SerdeObject for G1Affine { + /// The purpose of unchecked functions is to read the internal memory representation + /// of a type from bytes as quickly as possible. No sanitization checks are performed + /// to ensure the bytes represent a valid object. As such this function should only be + /// used internally as an extension of machine memory. It should not be used to deserialize + /// externally provided data. + fn from_raw_bytes_unchecked(bytes: &[u8]) -> Self { + G1Affine::from_compressed(bytes.try_into().unwrap()).unwrap() + } + fn from_raw_bytes(bytes: &[u8]) -> Option { + Some(G1Affine::from_compressed(bytes.try_into().unwrap()).unwrap()) + } + + fn to_raw_bytes(&self) -> Vec { + self.to_compressed().into() + } + + /// The purpose of unchecked functions is to read the internal memory representation + /// of a type from disk as quickly as possible. No sanitization checks are performed + /// to ensure the bytes represent a valid object. This function should only be used + /// internally when some machine state cannot be kept in memory (e.g., between runs) + /// and needs to be reloaded as quickly as possible. + fn read_raw_unchecked(reader: &mut R) -> Self { + let mut buf = [0; 48]; + reader.read_exact(&mut buf).unwrap(); + G1Affine::from_compressed(&buf).unwrap() + + } + fn read_raw(reader: &mut R) -> io::Result { + let mut buf = [0; 48]; + reader.read_exact(&mut buf).unwrap(); + Ok(G1Affine::from_compressed(&buf).unwrap()) + } + + fn write_raw(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&self.to_compressed())?; + Ok(()) + + } +} + + +#[test] +fn test_endo() { + use ff::Field; + use rand_core::OsRng; + + for _ in 0..100000 { + let k = Scalar::random(OsRng); + let (k1, k1_neg, k2, k2_neg) = G1Projective::decompose_scalar(&k); + if k1_neg & k2_neg { + assert_eq!( + k, + -Scalar::from_u128(k1) + Scalar::ZETA * Scalar::from_u128(k2) + ) + } else if k1_neg { + assert_eq!( + k, + -Scalar::from_u128(k1) - Scalar::ZETA * Scalar::from_u128(k2) + ) + } else if k2_neg { + assert_eq!( + k, + Scalar::from_u128(k1) + Scalar::ZETA * Scalar::from_u128(k2) + ) + } else { + assert_eq!( + k, + Scalar::from_u128(k1) - Scalar::ZETA * Scalar::from_u128(k2) + ) + } + } + + for _ in 0..100000 { + let k = Scalar::random(OsRng); + let (k1, k1_neg, k2, k2_neg) = G1Projective::decompose_scalar(&k); + if k1_neg & k2_neg { + assert_eq!( + k, + -Scalar::from_u128(k1) + Scalar::ZETA * Scalar::from_u128(k2) + ) + } else if k1_neg { + assert_eq!( + k, + -Scalar::from_u128(k1) - Scalar::ZETA * Scalar::from_u128(k2) + ) + } else if k2_neg { + assert_eq!( + k, + Scalar::from_u128(k1) + Scalar::ZETA * Scalar::from_u128(k2) + ) + } else { + assert_eq!( + k, + Scalar::from_u128(k1) - Scalar::ZETA * Scalar::from_u128(k2) + ) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b2396d7b..e23b1e75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,8 @@ pub mod hash_to_curve; pub mod msm; pub mod serde; +pub mod bandersnatch; +pub mod bls12_381; pub mod bn256; pub mod grumpkin; pub mod pasta;