├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── comparison.rs └── fftree.rs ├── examples ├── find_curve.rs ├── interp_eval.rs └── schoofs.rs ├── rustfmt.toml └── src ├── ec.rs ├── fftree.rs ├── find_curve.rs ├── lib.rs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ecfft" 3 | description = "Elliptic curve fast fourier transform for all prime fields" 4 | repository = "https://github.com/andrewmilson/ecfft" 5 | authors = ["Andrew Milson "] 6 | keywords = ["cryptography", "finite-fields", "elliptic-curves", "fft"] 7 | categories = ["cryptography"] 8 | version = "0.1.0" 9 | license = "MIT" 10 | edition = "2021" 11 | 12 | [[bench]] 13 | name = "fftree" 14 | path = "benches/fftree.rs" 15 | harness = false 16 | 17 | [[bench]] 18 | name = "compareison" 19 | path = "benches/comparison.rs" 20 | harness = false 21 | 22 | [dependencies] 23 | ark-ff = "0.4" 24 | ark-serialize = { version = "0.4", features = [ "std" ] } 25 | ark-poly = "0.4" 26 | ark-ff-optimized = "0.4" 27 | num-bigint = "0.4" 28 | num-integer = "0.1" 29 | rand = "0.8" 30 | 31 | [dev-dependencies] 32 | criterion = "0.4" 33 | rayon = "1.7" 34 | 35 | [profile.bench] 36 | codegen-units = 1 37 | lto = true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECFFT 2 | 3 | This library enables fast polynomial arithmetic over any finite field by implementing all the algorithms outlined in [Elliptic Curve Fast Fourier Transform (ECFFT) Part I](https://arxiv.org/pdf/2107.08473.pdf): 4 | 5 | |Algorithm|Description|Runtime| 6 | |:-|:-|:-| 7 | |ENTER|Coefficients to evaluations (fft analogue)|$\mathcal{O}(n\log^2{n})$| 8 | |EXIT|Evaluations to coefficients (ifft analogue)|$\mathcal{O}(n\log^2{n})$| 9 | |DEGREE|Computes a polynomial's degree|$\mathcal{O}(n\log{n})$| 10 | |EXTEND|Extends evaluations from one set to another|$\mathcal{O}(n\log{n})$| 11 | |MEXTEND|EXTEND for special monic polynomials|$\mathcal{O}(n\log{n})$| 12 | |MOD|Calculates the remainder of polynomial division|$\mathcal{O}(n\log{n})$| 13 | |REDC|Computes polynomial analogue of Montgomery's REDC|$\mathcal{O}(n\log{n})$| 14 | |VANISH|Generates a vanishing polynomial ([from section 7.1](https://arxiv.org/pdf/2107.08473.pdf))|$\mathcal{O}(n\log^2{n})$| 15 | 16 | There are also some relevant algorithms implemented from [ECFFT Part II](https://www.math.toronto.edu/swastik/ECFFT2.pdf): 17 | 18 | |Algorithm|Description|Runtime| 19 | |:-|:-|:-| 20 | |FIND_CURVE|Finds a curve over $\mathbb{F}_q$ with a cyclic subgroup of order $2^k$ |$\mathcal{O}(2^k\log{q})$| 21 | 22 | ## Build FFTrees at compile time 23 | 24 | FFTrees are the core data structure that the ECFFT algorithms are built upon. FFTrees can be generated and serialized at compile time and then be deserialized and used at runtime. This can be preferable since generating FFTrees involves a significant amount of computation. While this approach improves runtime it will significantly blow up a binary's size. Generating a FFTree capable of evaluating/interpolating degree $n$ polynomials takes $\mathcal{O}(n\log^3{n})$ - the space complexity of this FFTree is $\mathcal{O}(n)$. 25 | 26 | ```rust 27 | // build.rs 28 | 29 | use ark_serialize::CanonicalSerialize; 30 | use ecfft::{secp256k1::Fp, FftreeField}; 31 | use std::{env, fs::File, io, path::Path}; 32 | 33 | fn main() -> io::Result<()> { 34 | let fftree = Fp::build_fftree(1 << 16).unwrap(); 35 | let out_dir = env::var_os("OUT_DIR").unwrap(); 36 | let path = Path::new(&out_dir).join("fftree"); 37 | fftree.serialize_compressed(File::create(path)?).unwrap(); 38 | println!("cargo:rerun-if-changed=build.rs"); 39 | Ok(()) 40 | } 41 | ``` 42 | 43 | ```rust 44 | // src/main.rs 45 | 46 | use ark_ff::One; 47 | use ark_serialize::CanonicalDeserialize; 48 | use ecfft::{ecfft::FFTree, secp256k1::Fp}; 49 | use std::sync::OnceLock; 50 | 51 | static FFTREE: OnceLock> = OnceLock::new(); 52 | 53 | fn get_fftree() -> &'static FFTree { 54 | const BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fftree")); 55 | FFTREE.get_or_init(|| FFTree::deserialize_compressed(BYTES).unwrap()) 56 | } 57 | 58 | fn main() { 59 | let fftree = get_fftree(); 60 | // = x^65535 + x^65534 + ... + x + 1 61 | let poly = vec![Fp::one(); 1 << 16]; 62 | let evals = fftree.enter(&poly); 63 | let coeffs = fftree.exit(&evals); 64 | assert_eq!(poly, coeffs); 65 | } 66 | ``` 67 | 68 | ## References 69 | 70 | - [Elliptic Curve Fast Fourier Transform (ECFFT) Part I](https://arxiv.org/pdf/2107.08473.pdf) 71 | - [Elliptic Curve Fast Fourier Transform (ECFFT) Part II](https://www.math.toronto.edu/swastik/ECFFT2.pdf) 72 | - [The ECFFT algorithm](https://solvable.group/posts/ecfft/) blogpost by [wborgeaud](https://github.com/wborgeaud) -------------------------------------------------------------------------------- /benches/comparison.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::Fp256; 2 | use ark_ff::MontBackend; 3 | use ark_ff::MontConfig; 4 | use ark_ff::UniformRand; 5 | use ark_poly::EvaluationDomain; 6 | use ark_poly::Radix2EvaluationDomain; 7 | use criterion::criterion_group; 8 | use criterion::criterion_main; 9 | use criterion::BenchmarkId; 10 | use criterion::Criterion; 11 | use ecfft::secp256k1::Fp as EcfftField; 12 | use ecfft::FftreeField; 13 | use rand::rngs::StdRng; 14 | use rand::SeedableRng; 15 | 16 | const BENCHMARK_INPUT_SIZES: [usize; 1] = [8192]; 17 | 18 | #[derive(MontConfig)] 19 | #[modulus = "3618502788666131213697322783095070105623107215331596699973092056135872020481"] 20 | #[generator = "3"] 21 | pub struct FpMontConfig; 22 | 23 | pub type FftField = Fp256>; 24 | 25 | // TODO: naive evaluation/interpolation 26 | fn ecfft_vs_fft_benches(c: &mut Criterion) { 27 | let mut rng = StdRng::from_seed([1; 32]); 28 | let mut group = c.benchmark_group("ECFFT vs FFT"); 29 | group.sample_size(10); 30 | 31 | for n in BENCHMARK_INPUT_SIZES { 32 | let ecfft_vals: Vec = (0..n).map(|_| EcfftField::rand(&mut rng)).collect(); 33 | let fft_vals: Vec = (0..n).map(|_| FftField::rand(&mut rng)).collect(); 34 | let domain = Radix2EvaluationDomain::::new(n).unwrap(); 35 | let fftree = EcfftField::build_fftree(n * 2).unwrap(); 36 | 37 | group.bench_with_input(BenchmarkId::new("evaluate/ECFFT", n), &n, |b, _| { 38 | b.iter(|| fftree.enter(&ecfft_vals)) 39 | }); 40 | 41 | group.bench_with_input(BenchmarkId::new("interpolate/ECFFT", n), &n, |b, _| { 42 | b.iter(|| fftree.exit(&ecfft_vals)) 43 | }); 44 | 45 | group.bench_with_input(BenchmarkId::new("evaluate/FFT", n), &n, |b, _| { 46 | b.iter(|| domain.fft(&fft_vals)) 47 | }); 48 | 49 | group.bench_with_input(BenchmarkId::new("interpolate/FFT", n), &n, |b, _| { 50 | b.iter(|| domain.ifft(&fft_vals)) 51 | }); 52 | } 53 | 54 | group.finish(); 55 | } 56 | 57 | criterion_group!(benches, ecfft_vs_fft_benches); 58 | criterion_main!(benches); 59 | -------------------------------------------------------------------------------- /benches/fftree.rs: -------------------------------------------------------------------------------- 1 | use ark_serialize::CanonicalDeserialize; 2 | use ark_serialize::CanonicalSerialize; 3 | use criterion::criterion_group; 4 | use criterion::criterion_main; 5 | use criterion::BenchmarkId; 6 | use criterion::Criterion; 7 | use ecfft::m31; 8 | use ecfft::secp256k1; 9 | use ecfft::FFTree; 10 | use ecfft::FftreeField; 11 | use rand::rngs::StdRng; 12 | use rand::SeedableRng; 13 | 14 | const BENCHMARK_INPUT_SIZES: [usize; 1] = [2048]; 15 | 16 | const FIELD_DESCRIPTION_M31: &str = "31 bit Mersenne prime field"; 17 | const FIELD_DESCRIPTION_SECP256K1: &str = "secp256k1's prime field"; 18 | 19 | fn bench_ecfft_algorithms(c: &mut Criterion, field_description: &str) { 20 | let mut rng = StdRng::from_seed([1; 32]); 21 | let mut group = c.benchmark_group(format!("ECFFT algorithms ({field_description})")); 22 | group.sample_size(10); 23 | 24 | for n in BENCHMARK_INPUT_SIZES { 25 | let vals: Vec = (0..n).map(|_| F::rand(&mut rng)).collect(); 26 | let fftree = F::build_fftree(n * 2).unwrap(); 27 | 28 | group.bench_with_input(BenchmarkId::new("ENTER", n), &n, |b, _| { 29 | b.iter(|| fftree.enter(&vals)) 30 | }); 31 | 32 | group.bench_with_input(BenchmarkId::new("EXIT", n), &n, |b, _| { 33 | b.iter(|| fftree.exit(&vals)) 34 | }); 35 | 36 | group.bench_with_input(BenchmarkId::new("DEGREE", n), &n, |b, _| { 37 | b.iter(|| fftree.degree(&vals)) 38 | }); 39 | 40 | group.bench_with_input(BenchmarkId::new("EXTEND", n), &n, |b, _| { 41 | b.iter(|| fftree.extend(&vals, ecfft::Moiety::S1)) 42 | }); 43 | 44 | group.bench_with_input(BenchmarkId::new("MEXTEND", n), &n, |b, _| { 45 | b.iter(|| fftree.mextend(&vals, ecfft::Moiety::S1)) 46 | }); 47 | 48 | group.bench_with_input(BenchmarkId::new("MOD", n), &n, |b, _| { 49 | b.iter(|| fftree.modular_reduce(&vals, &fftree.xnn_s, &fftree.z0z0_rem_xnn_s)) 50 | }); 51 | 52 | group.bench_with_input(BenchmarkId::new("REDC", n), &n, |b, _| { 53 | b.iter(|| fftree.redc_z0(&vals, &fftree.xnn_s)) 54 | }); 55 | 56 | group.bench_with_input(BenchmarkId::new("VANISH", n), &n, |b, _| { 57 | b.iter(|| fftree.vanish(&vals)) 58 | }); 59 | } 60 | 61 | group.finish(); 62 | } 63 | 64 | fn bench_fftree(c: &mut Criterion, field_description: &str) { 65 | let mut group = c.benchmark_group(format!("FFTree ({field_description})")); 66 | group.sample_size(10); 67 | 68 | for n in BENCHMARK_INPUT_SIZES { 69 | let fftree = F::build_fftree(n).unwrap(); 70 | 71 | group.bench_with_input(BenchmarkId::new("generate", n), &n, |b, _| { 72 | b.iter(|| F::build_fftree(n).unwrap()) 73 | }); 74 | 75 | group.bench_with_input(BenchmarkId::new("serialize compressed", n), &n, |b, _| { 76 | b.iter(|| { 77 | let mut bytes = Vec::new(); 78 | fftree.serialize_compressed(&mut bytes).unwrap(); 79 | bytes 80 | }) 81 | }); 82 | 83 | group.bench_with_input(BenchmarkId::new("serialize uncompressed", n), &n, |b, _| { 84 | b.iter(|| { 85 | let mut bytes = Vec::new(); 86 | fftree.serialize_uncompressed(&mut bytes).unwrap(); 87 | bytes 88 | }) 89 | }); 90 | 91 | group.bench_with_input(BenchmarkId::new("deserialize compressed", n), &n, |b, _| { 92 | let mut bytes = Vec::new(); 93 | fftree.serialize_compressed(&mut bytes).unwrap(); 94 | b.iter(|| FFTree::::deserialize_compressed(&*bytes).unwrap()) 95 | }); 96 | 97 | group.bench_with_input( 98 | BenchmarkId::new("deserialize uncompressed", n), 99 | &n, 100 | |b, _| { 101 | let mut bytes = Vec::new(); 102 | fftree.serialize_uncompressed(&mut bytes).unwrap(); 103 | b.iter(|| FFTree::::deserialize_uncompressed(&*bytes).unwrap()) 104 | }, 105 | ); 106 | } 107 | 108 | group.finish(); 109 | } 110 | 111 | fn ecfft_algorithm_benches(c: &mut Criterion) { 112 | bench_ecfft_algorithms::(c, FIELD_DESCRIPTION_M31); 113 | bench_ecfft_algorithms::(c, FIELD_DESCRIPTION_SECP256K1); 114 | } 115 | 116 | fn fftree_benches(c: &mut Criterion) { 117 | bench_fftree::(c, FIELD_DESCRIPTION_M31); 118 | bench_fftree::(c, FIELD_DESCRIPTION_SECP256K1); 119 | } 120 | 121 | criterion_group!(fftree_group, ecfft_algorithm_benches, fftree_benches); 122 | criterion_main!(fftree_group); 123 | -------------------------------------------------------------------------------- /examples/find_curve.rs: -------------------------------------------------------------------------------- 1 | #![feature(let_chains)] 2 | 3 | use ark_ff::Field; 4 | use ecfft::ec::GoodCurve; 5 | use ecfft::ec::Point; 6 | use ecfft::find_curve::find_curve; 7 | use ecfft::secp256k1::Fp; 8 | use std::sync::atomic::AtomicU32; 9 | use std::sync::atomic::Ordering; 10 | 11 | fn main() { 12 | let max_n = AtomicU32::new(10); 13 | 14 | rayon::scope(|s| { 15 | for _ in 0..10 { 16 | s.spawn(|_| { 17 | let mut rng = rand::thread_rng(); 18 | loop { 19 | let (n, g) = find_curve::(&mut rng, max_n.load(Ordering::Relaxed) + 1); 20 | if n > max_n.load(Ordering::Relaxed) { 21 | max_n.store(n, Ordering::Relaxed); 22 | let curve = g.curve.unwrap(); 23 | match curve { 24 | GoodCurve::Odd { a, b } => { 25 | let bb = b.square(); 26 | let Point { x, y, .. } = g; 27 | println!("n={n}, a = {a}, bb = {bb}, p=({x}, {y})",) 28 | } 29 | GoodCurve::Even { b: _ } => todo!(), 30 | } 31 | } 32 | } 33 | }); 34 | } 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /examples/interp_eval.rs: -------------------------------------------------------------------------------- 1 | use ark_poly::univariate::DensePolynomial; 2 | use ark_poly::DenseUVPolynomial; 3 | use ark_poly::Polynomial; 4 | use ecfft::secp256k1::Fp; 5 | use ecfft::FftreeField; 6 | use std::ops::Deref; 7 | use std::time::Instant; 8 | 9 | fn main() { 10 | let n = 1 << 14; 11 | let mut rng = rand::thread_rng(); 12 | 13 | let now = Instant::now(); 14 | let fftree = Fp::build_fftree(n).unwrap(); 15 | println!("FFTree generation time: {:?}", now.elapsed()); 16 | 17 | let poly = DensePolynomial::rand(n - 1, &mut rng); 18 | 19 | let now = Instant::now(); 20 | let ecfft_evals = fftree.enter(&poly); 21 | println!("evaluation time (fft): {:?}", now.elapsed()); 22 | 23 | let now = Instant::now(); 24 | let xs = fftree.subtree_with_size(n).f.get_layer(0); 25 | let ys = xs.iter().map(|x| poly.evaluate(x)).collect::>(); 26 | assert_eq!(ecfft_evals, ys); 27 | println!("naive O(n^2) eval: {:?}", now.elapsed()); 28 | 29 | let now = Instant::now(); 30 | let ecfft_coeffs = fftree.exit(&ecfft_evals); 31 | println!("interpolation time (ifft): {:?}", now.elapsed()); 32 | 33 | assert_eq!(poly.deref(), ecfft_coeffs); 34 | } 35 | -------------------------------------------------------------------------------- /examples/schoofs.rs: -------------------------------------------------------------------------------- 1 | use ark_ff::One; 2 | use ark_ff::PrimeField; 3 | use ark_ff::Zero; 4 | use ark_poly::univariate::DensePolynomial; 5 | use ark_poly::DenseUVPolynomial; 6 | use ark_poly::Polynomial; 7 | use ecfft::ec::ShortWeierstrassCurve; 8 | use ecfft::m31::Fp; 9 | // use ecfft::secp256k1::Fp; 10 | use ecfft::utils::div_rem; 11 | use ecfft::utils::gcd; 12 | use ecfft::utils::pow_mod; 13 | use ecfft::utils::xgcd; 14 | use num_bigint::BigInt; 15 | use num_bigint::BigUint; 16 | use num_integer::ExtendedGcd; 17 | use num_integer::Integer; 18 | use std::ops::Add; 19 | use std::ops::Mul; 20 | 21 | fn main() { 22 | let curve = ShortWeierstrassCurve::new(Fp::from(8), Fp::from(81)); 23 | println!("Cardinality is: {}", cardinality(curve)); 24 | } 25 | 26 | /// Returns the cardinality of a curve using Schoofs Algorithm 27 | /// Implementation based on Algorithm 4.5 from "Elliptic Curves" book by LCW and 28 | /// https://math.mit.edu/classes/18.783/2015/LectureNotes9.pdf. 29 | // TODO: fix bug. a=8, b=81 gives 2147478255 but should be 2147489041 30 | pub fn cardinality(curve: ShortWeierstrassCurve) -> BigUint { 31 | let p = F::MODULUS.into(); 32 | let hasse_interval_len = BigInt::from(p.sqrt() * 4u32); 33 | let mut small_primes = SMALL_PRIMES.into_iter(); 34 | let mut a_mod_m = BigInt::zero(); 35 | let mut m = BigInt::one(); 36 | while m < hasse_interval_len { 37 | let l = small_primes.next().unwrap(); 38 | let a_mod_l = if l == 2 { 39 | // p + 1 + a ≡ 0 (mod 2) then a = 0 (mod 2) otherwise a = 1 (mod 2) 40 | if has_even_order(curve) { 41 | println!("is true"); 42 | 0 43 | } else { 44 | 1 45 | } 46 | } else { 47 | // handle odd primes using Schoofs algorithm 48 | let psi = division_polynomial(l, &curve); 49 | frobenius_trace_mod_l(l, curve, &psi.into()) 50 | }; 51 | 52 | // Incremental Chinese Remainder Theorem. From algorithm 9.1 in: 53 | // https://math.mit.edu/classes/18.783/2015/LectureNotes9.pdf 54 | // calculates the frobenius trace mod `m` where m = l_0 * l_1 * ... * l_n 55 | let ExtendedGcd { 56 | x: m_inv_l, 57 | y: l_inv_m, 58 | .. 59 | } = m.extended_gcd(&l.into()); 60 | a_mod_m = (m_inv_l * &m * a_mod_l + l_inv_m * l * a_mod_m) % (&m * l); 61 | m *= l; 62 | } 63 | 64 | let a_mod_m = BigUint::try_from((&m + a_mod_m) % &m).unwrap(); 65 | let m = BigUint::try_from(m).unwrap(); 66 | if a_mod_m > &m / 2u8 { 67 | p + 1u8 + a_mod_m - m 68 | } else { 69 | p + 1u8 + a_mod_m 70 | } 71 | } 72 | 73 | /// Calculates the trace of the frobenius mod `l` using Schoof's algorithm 74 | /// `modulus` can only be made up of factors of the l-th division polynomial 75 | /// Panics if `l` is not an odd prime or if modulus has degree 0 76 | fn frobenius_trace_mod_l( 77 | l: usize, 78 | curve: ShortWeierstrassCurve, 79 | ring: &QuotientRing, 80 | ) -> usize { 81 | assert!(l.is_odd(), "odd primes only"); 82 | assert!(!ring.modulus.degree().is_zero()); 83 | let p = F::MODULUS.into(); 84 | let p_mod_l = usize::try_from(&p % l).unwrap(); 85 | 86 | if ring.modulus.degree() == 1 { 87 | // Modulus is only made up of factors of the l-th division polynomial. 88 | // If modulus is a degree 1 polynomial i.e. (x - c) for some constant c 89 | // then we've found a roots of the l-th division polynomial equal to c. 90 | // The roots of the l-th division polynomial correspond to the 91 | // x-coordinates of the points of the l-torsion group. Ok, so we found a 92 | // root and now know there is a point of order l. Therefore #E(F) = q + 1 + a = 93 | // 0 (mod l) and a = -q - 1 (mod l) 94 | return l - p_mod_l - 1; 95 | } 96 | 97 | let x = DensePolynomial::from_coefficients_vec(vec![F::zero(), F::one()]); 98 | let x3_ax_b = curve.x3_ax_b(); 99 | 100 | // Compute π = (x^p, y^p) and π^2 = π ∘ π - note endomorphisms are multiplied by 101 | // composition. Also note y^p = (x^3 + A*x + B)^((p - 1) / 2) * y = b(x)*y 102 | // therefore π = (x^p, y^p) = (a(x), b(x)*y). Now we want to compute the 103 | // composition of π_1 ∘ π_2. To do this, we apply π_1 to the input 104 | // coordinates (x, y), and then apply π_2 to the result. (1) Apply π_2: 105 | // (a_2(x), b_2(x)*y) (2) Apply π_1 to the result of π_2: 106 | // * the x-coord transform: a_1(a_2(x)) 107 | // * the y-coord transform: b_1(x') * y' (where x' = a_2(x) and y' = b_2(x)*y) 108 | let pi_a = ring.pow(&x, p.clone()); 109 | let pi_b = ring.pow(&x3_ax_b, (p - 1u8) / 2u8); 110 | let pi = Endomorphism::new(pi_a, pi_b, curve, ring); 111 | let pi_pi = pi.clone() * pi.clone(); 112 | 113 | // (a_pl, b_pl * y) = (p mod l) * (x, y) 114 | let pl = match Endomorphism::identity(curve, ring) * BigUint::from(p_mod_l) { 115 | Err(Uninvertable(gcd)) => return frobenius_trace_mod_l(l, curve, &gcd.into()), 116 | Ok(res) => res, 117 | }; 118 | 119 | // (x', y') = (a', b' * y) = π^2 + pl 120 | let e_prime = match pi_pi + pl { 121 | Err(Uninvertable(gcd)) => return frobenius_trace_mod_l(l, curve, &gcd.into()), 122 | Ok(res) => res, 123 | }; 124 | 125 | for j in 1..l { 126 | // (a_j, b_j * y) = j * π 127 | let pi_j = match pi.clone() * BigUint::from(j) { 128 | Err(Uninvertable(gcd)) => return frobenius_trace_mod_l(l, curve, &gcd.into()), 129 | Ok(res) => res, 130 | }; 131 | 132 | if pi_j == e_prime { 133 | return j; 134 | } 135 | } 136 | 137 | 0 138 | } 139 | 140 | /// Stores an elliptic curve endomorphism in a quotient ring 141 | /// (x, y) = (a(x), b(x) * y) 142 | #[derive(Debug, Clone, PartialEq, Eq)] 143 | struct Endomorphism<'a, F: PrimeField> { 144 | a: DensePolynomial, 145 | b: DensePolynomial, 146 | curve: ShortWeierstrassCurve, 147 | ring: &'a QuotientRing, 148 | } 149 | 150 | impl<'a, F: PrimeField> Endomorphism<'a, F> { 151 | fn new( 152 | a: DensePolynomial, 153 | b: DensePolynomial, 154 | curve: ShortWeierstrassCurve, 155 | ring: &'a QuotientRing, 156 | ) -> Self { 157 | Self { 158 | a: ring.reduce_poly(a), 159 | b: ring.reduce_poly(b), 160 | curve, 161 | ring, 162 | } 163 | } 164 | 165 | fn identity(curve: ShortWeierstrassCurve, ring: &'a QuotientRing) -> Self { 166 | // TODO: identity endomorphism 167 | Self { 168 | a: DensePolynomial::from_coefficients_vec(vec![F::zero(), F::one()]), 169 | b: DensePolynomial::from_coefficients_vec(vec![F::one()]), 170 | curve, 171 | ring, 172 | } 173 | } 174 | } 175 | 176 | impl Mul for Endomorphism<'_, F> { 177 | type Output = Result>; 178 | 179 | /// Computes scalar multiplication of an endomorphism using double-and-add 180 | fn mul(self, mut n: BigUint) -> Self::Output { 181 | let mut res = None; 182 | let mut acc = self; 183 | while !n.is_zero() { 184 | if n.is_odd() { 185 | res = Some(match res.clone() { 186 | Some(res) => (res + acc.clone())?, 187 | None => acc.clone(), 188 | }); 189 | } 190 | acc = (acc.clone() + acc)?; 191 | n >>= 1; 192 | } 193 | Ok(res.unwrap()) 194 | } 195 | } 196 | 197 | impl Mul for Endomorphism<'_, F> { 198 | type Output = Self; 199 | 200 | /// Multiplies two endomorphisms by composition 201 | fn mul(self, rhs: Self) -> Self { 202 | assert_eq!(self.ring, rhs.ring); 203 | assert_eq!(self.curve, rhs.curve); 204 | let ring = self.ring; 205 | let curve = self.curve; 206 | let Self { a: a1, b: b1, .. } = self; 207 | let Self { a: a2, b: b2, .. } = rhs; 208 | // compose a_1 with a_2 to obtain a_3 = (a_1(a_2(x)), b_1(a_2(x)) * b_2(x) * y) 209 | let mut a1_a2 = DensePolynomial::zero(); 210 | let mut b1_a2 = DensePolynomial::zero(); 211 | let mut acc = QuotientRing::one(); 212 | for i in 0..a1.len().max(b1.len()) { 213 | if let Some(&coeff) = a1.get(i) { 214 | a1_a2 += &(&acc * coeff); 215 | } 216 | if let Some(&coeff) = b1.get(i) { 217 | b1_a2 += &(&acc * coeff); 218 | } 219 | acc = ring.mul(&acc, &a2); 220 | } 221 | // TODO: remove https://github.com/arkworks-rs/algebra/pull/638 222 | while a1_a2.coeffs.last().map_or(false, |c| c.is_zero()) { 223 | a1_a2.coeffs.pop(); 224 | } 225 | while b1_a2.coeffs.last().map_or(false, |c| c.is_zero()) { 226 | b1_a2.coeffs.pop(); 227 | } 228 | Self { 229 | a: a1_a2, 230 | b: ring.mul(&b1_a2, &b2), 231 | ring, 232 | curve, 233 | } 234 | } 235 | } 236 | 237 | impl Add for Endomorphism<'_, F> { 238 | type Output = Result>; 239 | 240 | /// Applies elliptic curve addition operation to endomorphisms 241 | fn add(self, rhs: Self) -> Self::Output { 242 | assert_eq!(self.ring, rhs.ring); 243 | assert_eq!(self.curve, rhs.curve); 244 | let ring = self.ring; 245 | let curve = self.curve; 246 | let x3_ax_b = curve.x3_ax_b(); 247 | 248 | let Self { a: a1, b: b1, .. } = self; 249 | let Self { a: a2, b: b2, .. } = rhs; 250 | 251 | // m = y*(b_1 - b_2)/(a_1 - a_2) if a_1 != a_2 252 | // m = (3*a_1^2 + A)/(2*b_1*y) if a_1 == a_2 253 | // m = r(x) * y 254 | let r = if a1 == a2 { 255 | // calculate tangent line 256 | let a = DensePolynomial::from_coefficients_vec(vec![curve.a]); 257 | let numerator = &(&ring.mul(&a1, &a1) * F::from(3u8)) + &a; 258 | let denominator = &x3_ax_b.naive_mul(&b1) * F::from(2u8); 259 | ring.div(&numerator, &denominator)? 260 | } else { 261 | // calculate slope 262 | ring.div(&(&b1 - &b2), &(&a1 - &a2))? 263 | }; 264 | 265 | // note that y^2 = x^3 + A*x + B 266 | // a_3 = r^2 * y^2 - a_1 - a_2 267 | // b_3 = r * (a_1 - a_3) - b_1 268 | let rr = ring.pow(&r, 2u8.into()); 269 | let a3 = &ring.mul(&rr, &x3_ax_b) - &(&a1 + &a2); 270 | let b3 = &ring.mul(&r, &(&a1 - &a3)) - &b1; 271 | Ok(Self::new(a3, b3, curve, ring)) 272 | } 273 | } 274 | 275 | /// Quotient ring with modulus f(x) 276 | #[derive(Debug, Clone, PartialEq, Eq)] 277 | struct QuotientRing { 278 | modulus: DensePolynomial, 279 | } 280 | 281 | impl QuotientRing { 282 | fn new(modulus: DensePolynomial) -> Self { 283 | assert!(!modulus.is_zero()); 284 | Self { modulus } 285 | } 286 | 287 | fn one() -> DensePolynomial { 288 | DensePolynomial::from_coefficients_vec(vec![F::one()]) 289 | } 290 | 291 | fn mul(&self, a: &DensePolynomial, b: &DensePolynomial) -> DensePolynomial { 292 | self.reduce_poly(a.naive_mul(b)) 293 | } 294 | 295 | fn pow(&self, a: &DensePolynomial, exp: BigUint) -> DensePolynomial { 296 | if self.modulus.is_zero() { 297 | unimplemented!() 298 | } 299 | 300 | pow_mod(a, exp, &self.modulus) 301 | } 302 | 303 | /// If the denominator is invertible, the method returns a `Result` 304 | /// containing the division polynomial. Otherwise, it returns an error 305 | /// indicating that the denominator is not invertible. 306 | fn div( 307 | &self, 308 | numerator: &DensePolynomial, 309 | denominator: &DensePolynomial, 310 | ) -> Result, Uninvertable> { 311 | let denominator_inv = self.try_inverse(denominator)?; 312 | Ok(self.reduce_poly(numerator.naive_mul(&denominator_inv))) 313 | } 314 | 315 | /// Reduces a polynomial into the quotient ring, returning its canonical 316 | /// representative. 317 | fn reduce_poly(&self, a: DensePolynomial) -> DensePolynomial { 318 | if self.modulus.is_zero() { 319 | a 320 | } else { 321 | div_rem(&a, &self.modulus) 322 | } 323 | } 324 | 325 | fn try_inverse(&self, a: &DensePolynomial) -> Result, Uninvertable> { 326 | let (a_inverse, _, gcd) = xgcd(a, &self.modulus); 327 | if gcd == Self::one() { 328 | Ok(a_inverse) 329 | } else { 330 | Err(Uninvertable(gcd)) 331 | } 332 | } 333 | } 334 | 335 | impl From> for QuotientRing { 336 | fn from(modulus: DensePolynomial) -> Self { 337 | Self::new(modulus) 338 | } 339 | } 340 | 341 | /// Holds the GCD of the polynomial and the modulus 342 | struct Uninvertable(DensePolynomial); 343 | 344 | /// Returns true if there is an even number of points on the curve. 345 | fn has_even_order(curve: ShortWeierstrassCurve) -> bool { 346 | // "if x^3 + A*x + B has a root e ∈ Fp, then (e, 0) ∈ E[2] and (e, 0) ∈ E(Fp), 347 | // so E(Fp) has even order ... if x^3 + A*x + B has no roots in Fp, then E(Fp) 348 | // has no points of order 2" - Elliptic Curves, LCW. 349 | // 350 | // This can be determined by trying all values of x in the field but there is a 351 | // faster way. Note that x^p − x = (x - 1)(x - 2)...(x - (p - 1)) therefore 352 | // if gcd(x^p − x, x^3 + A*x + B) != 1 then a root exists and therefore 353 | // there is a point with order 2. 354 | // 355 | // Since "p" can be large we first compute xp ≡ x^p (mod x^3 + A*x + B) by 356 | // successive squaring. Then we just need to compute gcd(xp − x, x^3 + Ax + B). 357 | let p: BigUint = F::MODULUS.into(); 358 | let x3_ax_b = curve.x3_ax_b(); 359 | let one = DensePolynomial::from_coefficients_vec(vec![F::one()]); 360 | let x = DensePolynomial::from_coefficients_vec(vec![F::zero(), F::one()]); 361 | let xp = pow_mod(&x, p, &x3_ax_b); 362 | 363 | // Compute gcd(xp - x, x^3 + A*x + B). If the gcd is 1, then there's no root and 364 | // the order is odd. 365 | gcd(&(&xp - &x), &x3_ax_b) != one 366 | } 367 | 368 | /// Returns the nth division polynomial 369 | /// The division polynomial for even values of n are missing a factor of 2y 370 | pub fn division_polynomial( 371 | n: usize, 372 | curve: &ShortWeierstrassCurve, 373 | ) -> DensePolynomial { 374 | if n == 0 { 375 | // ψ_0 = 0 376 | DensePolynomial::default() 377 | } else if n == 1 || n == 2 { 378 | // ψ_1 = 1; ψ_2 = 2*y (2*y factor removed see above) 379 | DensePolynomial::from_coefficients_vec(vec![F::one()]) 380 | } else if n == 3 { 381 | // ψ_3 = 3*x^4 + 6*A*x^2 + 12*B*x - A^2 382 | DensePolynomial::from_coefficients_vec(vec![ 383 | -curve.a.square(), 384 | curve.b * F::from(12u8), 385 | curve.a * F::from(6u8), 386 | F::zero(), 387 | F::from(3u8), 388 | ]) 389 | } else if n == 4 { 390 | // ψ_4 = 4*y*(x^6 + 5*A*x^4 + 20*B*x^3 − 5*A*A*x^2 − 4*A*B*x − 8*B^2 − A^3) 391 | // = 2*y*(2*x^6 + 10*A*x^4 + 40*B*x^3 − 10*AA*x^2 − 8*AB*x − 16*B^2 − 2*A^3) 392 | DensePolynomial::from_coefficients_vec(vec![ 393 | -curve.a.pow([3]) * F::from(2u8) - curve.b.square() * F::from(16u8), 394 | -curve.a * curve.b * F::from(8u8), 395 | -curve.a.square() * F::from(10u8), 396 | curve.b * F::from(40u8), 397 | curve.a * F::from(10u8), 398 | F::zero(), 399 | F::from(2u32), 400 | ]) 401 | } else { 402 | let m = n / 2; 403 | let psi_m = division_polynomial(m, curve); 404 | let psi_ms1 = division_polynomial(m - 1, curve); 405 | let psi_mp1 = division_polynomial(m + 1, curve); 406 | let psi_mp2 = division_polynomial(m + 2, curve); 407 | if n.is_even() { 408 | // t0 = ψ_(m+2) * ψ_(m-1)^2 409 | // t1 = ψ_(m-2) * ψ_(m+1)^2 410 | let psi_ms2 = division_polynomial(m - 2, curve); 411 | let t0 = psi_ms1.naive_mul(&psi_ms1).naive_mul(&psi_mp2); 412 | let t1 = psi_mp1.naive_mul(&psi_mp1).naive_mul(&psi_ms2); 413 | (&t0 - &t1).naive_mul(&psi_m) 414 | } else { 415 | // = (2 * y)^4 416 | // = 16 * (y^2)^2 417 | // = 16 * (x^3 + A*x + B)^2 418 | let yy = curve.x3_ax_b(); 419 | let yyyy16 = &yy.naive_mul(&yy) * F::from(16u8); 420 | // t0 = ψ_(m+2) * ψ_m^3 421 | // t1 = ψ_(m-1) * ψ_(m+1)^3 422 | let t0 = psi_mp2.naive_mul(&psi_m.naive_mul(&psi_m).naive_mul(&psi_m)); 423 | let t1 = psi_ms1.naive_mul(&psi_mp1.naive_mul(&psi_mp1).naive_mul(&psi_mp1)); 424 | if m.is_even() { 425 | &yyyy16.naive_mul(&t0) - &t1 426 | } else { 427 | &t0 - &yyyy16.naive_mul(&t1) 428 | } 429 | } 430 | } 431 | } 432 | 433 | // first 46 primes 434 | const SMALL_PRIMES: [usize; 46] = [ 435 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 436 | 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 437 | 197, 199, 438 | ]; 439 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # See: https://github.com/rust-lang/rustfmt/blob/master/Configurations.md 2 | edition = "2021" 3 | imports_granularity = "Item" 4 | group_imports = "One" 5 | wrap_comments = true 6 | format_macro_matchers = true 7 | format_macro_bodies = true 8 | use_field_init_shorthand = true -------------------------------------------------------------------------------- /src/ec.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::find_roots; 2 | use crate::utils::is_odd; 3 | use crate::utils::two_adicity; 4 | use crate::utils::RationalMap; 5 | use crate::FFTree; 6 | use ark_ff::vec; 7 | use ark_ff::vec::Vec; 8 | use ark_ff::Field; 9 | use ark_ff::PrimeField; 10 | use ark_ff::Zero; 11 | use ark_poly::univariate::DensePolynomial; 12 | use ark_poly::DenseUVPolynomial; 13 | use ark_serialize::CanonicalDeserialize; 14 | use ark_serialize::CanonicalSerialize; 15 | use ark_serialize::Valid; 16 | use core::fmt::Debug; 17 | use core::ops::Add; 18 | use core::ops::AddAssign; 19 | use core::ops::Mul; 20 | use core::ops::Neg; 21 | use num_bigint::BigUint; 22 | use num_integer::Integer; 23 | 24 | /// Good curve from ECFFT Part II 25 | /// 26 | /// All Good Curves share the same 2-torsion point (0, 0) 27 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 28 | pub enum GoodCurve { 29 | /// Even sized fields: 30 | /// E_B: y^2 + x*y = x^3 + B*x, B = b^2 31 | Even { b: F }, 32 | /// Odd sized fields: 33 | /// E_{a, B}: y^2 = x^3 + a*x^2 + B*x, B = b^2 34 | Odd { a: F, b: F }, 35 | } 36 | 37 | impl GoodCurve { 38 | pub fn new_odd(a: F, bb: F) -> Self { 39 | assert!(is_odd::()); 40 | // requirement for the curve to be non-singluar 41 | assert!(!bb.is_zero() && !(a.square() - bb.double().double()).is_zero()); 42 | let b = bb.sqrt().expect("must be a quadratic residue"); 43 | assert!((a + b + b).sqrt().is_some()); 44 | Self::Odd { a, b } 45 | } 46 | 47 | pub fn new_even(bb: F) -> Self { 48 | assert!(!is_odd::()); 49 | assert!(!bb.is_zero()); 50 | let b = bb.sqrt().expect("must be a quadratic residue"); 51 | Self::Even { b } 52 | } 53 | 54 | pub fn good_point(self) -> Point { 55 | match self { 56 | Self::Even { b } => Point::new(b, b, self), 57 | Self::Odd { a, b } => Point::new(a, b.square(), self), 58 | } 59 | } 60 | 61 | pub fn good_isogeny(self) -> Isogeny { 62 | match self { 63 | GoodCurve::Even { b } => { 64 | let domain = self; 65 | let codomain = Self::new_even(b); 66 | 67 | let bb = b.square(); 68 | let one = F::one(); 69 | let zero = F::zero(); 70 | let r = RationalMap::new(&[bb, zero, one], &[zero, one]); 71 | let g = RationalMap::new(&[bb, b], &[zero, one]); 72 | let h = RationalMap::new(&[bb, zero, one], &[zero, zero, one]); 73 | Isogeny::new(domain, codomain, r, g, h) 74 | } 75 | GoodCurve::Odd { a, b } => { 76 | let domain = self; 77 | let bb = b.square(); 78 | let a_prime = a + b.double().double() + b.double(); 79 | let b_prime = (a * b).double().double() + bb.double().double().double(); 80 | let codomain = Self::new_odd(a_prime, b_prime); 81 | 82 | let one = F::one(); 83 | let zero = F::zero(); 84 | let r = RationalMap::new(&[bb, -b.double(), one], &[zero, one]); 85 | let g = RationalMap::zero(); 86 | let h = RationalMap::new(&[-bb, zero, one], &[zero, zero, one]); 87 | Isogeny::new(domain, codomain, r, g, h) 88 | } 89 | } 90 | } 91 | } 92 | 93 | impl CanonicalSerialize for GoodCurve { 94 | fn serialize_with_mode( 95 | &self, 96 | mut writer: W, 97 | compress: ark_serialize::Compress, 98 | ) -> Result<(), ark_serialize::SerializationError> { 99 | match self { 100 | GoodCurve::Even { b } => b.serialize_with_mode(writer, compress), 101 | GoodCurve::Odd { a, b } => { 102 | a.serialize_with_mode(&mut writer, compress)?; 103 | b.serialize_with_mode(writer, compress) 104 | } 105 | } 106 | } 107 | 108 | fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { 109 | match self { 110 | GoodCurve::Even { b } => b.serialized_size(compress), 111 | GoodCurve::Odd { a, b } => a.serialized_size(compress) + b.serialized_size(compress), 112 | } 113 | } 114 | } 115 | 116 | impl Valid for GoodCurve { 117 | #[inline] 118 | fn check(&self) -> Result<(), ark_serialize::SerializationError> { 119 | Ok(()) 120 | } 121 | } 122 | 123 | impl CanonicalDeserialize for GoodCurve { 124 | fn deserialize_with_mode( 125 | mut reader: R, 126 | compress: ark_serialize::Compress, 127 | validate: ark_serialize::Validate, 128 | ) -> Result { 129 | Ok(if is_odd::() { 130 | Self::Odd { 131 | a: F::deserialize_with_mode(&mut reader, compress, validate)?, 132 | b: F::deserialize_with_mode(reader, compress, validate)?, 133 | } 134 | } else { 135 | Self::Even { 136 | b: F::deserialize_with_mode(reader, compress, validate)?, 137 | } 138 | }) 139 | } 140 | } 141 | 142 | impl WeierstrassCurve for GoodCurve { 143 | type F = F; 144 | 145 | fn a1(&self) -> Self::F { 146 | match self { 147 | GoodCurve::Even { b: _ } => F::one(), 148 | GoodCurve::Odd { a: _, b: _ } => F::zero(), 149 | } 150 | } 151 | 152 | fn a2(&self) -> Self::F { 153 | match self { 154 | GoodCurve::Even { b: _ } => F::zero(), 155 | GoodCurve::Odd { a, b: _ } => *a, 156 | } 157 | } 158 | 159 | fn a3(&self) -> Self::F { 160 | F::zero() 161 | } 162 | 163 | fn a4(&self) -> Self::F { 164 | match self { 165 | GoodCurve::Even { b } => b.square(), 166 | GoodCurve::Odd { a: _, b } => b.square(), 167 | } 168 | } 169 | 170 | fn a6(&self) -> Self::F { 171 | F::zero() 172 | } 173 | } 174 | 175 | // Finds a chain of isogenies where all curves and all isogenies are good 176 | // TODO: could implement this on Point!? 177 | pub fn find_isogeny_chain(generator: Point>) -> Vec>> { 178 | let k = two_adicity(generator).expect("not a point of order 2^k"); 179 | let mut good_isogenies = Vec::new(); 180 | let mut g = generator; 181 | for _ in 0..k { 182 | let good_isogeny = g.curve.unwrap().good_isogeny(); 183 | let g_prime = good_isogeny.map(&g); 184 | assert_eq!(two_adicity(g).unwrap(), two_adicity(g_prime).unwrap() + 1); 185 | good_isogenies.push(good_isogeny); 186 | g = g_prime; 187 | } 188 | good_isogenies 189 | } 190 | 191 | /// Short Weierstrass curve of the form y^2 = x^3 + a*x + b 192 | #[derive( 193 | Clone, 194 | Copy, 195 | PartialEq, 196 | Eq, 197 | PartialOrd, 198 | Ord, 199 | Debug, 200 | Default, 201 | CanonicalDeserialize, 202 | CanonicalSerialize, 203 | )] 204 | pub struct ShortWeierstrassCurve { 205 | pub a: F, 206 | pub b: F, 207 | } 208 | 209 | impl ShortWeierstrassCurve { 210 | pub const fn new(a: F, b: F) -> Self { 211 | Self { a, b } 212 | } 213 | 214 | pub fn two_isogenies(&self) -> Vec> 215 | where 216 | F: PrimeField, 217 | { 218 | // Find all the points of order two and then use Velu's formula to find all 219 | // the 2-isogenies. Velu's formula: https://math.mit.edu/~drew/CTNT2018.pdf 220 | self.two_torsion_points() 221 | .into_iter() 222 | .map(|point| { 223 | let x0 = point.x; 224 | let t = F::from(3u8) * x0 * x0 + self.a; 225 | 226 | // Our domain E(F) and codomain E'(F) 227 | let domain = *self; 228 | let codomain = Self::new(self.a - F::from(5u8) * t, self.b - F::from(7u8) * x0 * t); 229 | 230 | // ϕ: E -> E' := ((x^2 - x0*x + t)/(x - x0), ((x - x0)^2 - t)/(x - x0)^2 * y) 231 | let x_map_numerator = &[t, -x0, F::one()]; 232 | let x_map_denominator = &[-x0, F::one()]; 233 | let y_map_numerator = &[x0 * x0 - t, -(x0 + x0), F::one()]; 234 | let y_map_denominator = &[x0 * x0, -(x0 + x0), F::one()]; 235 | 236 | let r = RationalMap::new(x_map_numerator, x_map_denominator); 237 | let g = RationalMap::zero(); 238 | let h = RationalMap::new(y_map_numerator, y_map_denominator); 239 | Isogeny::new(domain, codomain, r, g, h) 240 | }) 241 | .collect() 242 | } 243 | 244 | /// Returns all non-zero points on the curve that have order 2. 245 | fn two_torsion_points(&self) -> Vec> 246 | where 247 | F: PrimeField, 248 | { 249 | // The two torsion points have a vertical tangent since 2*P = 0. 250 | // The points with vertical tangent are those with y = 0. 251 | // We can find the points if we find the values of x satisfy 0 = x^3 + a*x + b. 252 | // TODO: can use Cantor–Zassenhaus algorithm for degree 3 polynomials 253 | // https://en.wikipedia.org/wiki/Cantor%E2%80%93Zassenhaus_algorithm 254 | let roots = find_roots(&self.x3_ax_b()); 255 | roots 256 | .into_iter() 257 | .map(|root| Point::new(root, F::zero(), *self)) 258 | .collect() 259 | } 260 | 261 | // Returns the polynomial x^3 + A*x + B 262 | pub fn x3_ax_b(&self) -> DensePolynomial { 263 | DensePolynomial::from_coefficients_vec(vec![self.b, self.a, F::zero(), F::one()]) 264 | } 265 | } 266 | 267 | impl WeierstrassCurve for ShortWeierstrassCurve { 268 | type F = F; 269 | 270 | fn a1(&self) -> F { 271 | F::zero() 272 | } 273 | 274 | fn a2(&self) -> F { 275 | F::zero() 276 | } 277 | 278 | fn a3(&self) -> F { 279 | F::zero() 280 | } 281 | 282 | fn a4(&self) -> F { 283 | self.a 284 | } 285 | 286 | fn a6(&self) -> F { 287 | self.b 288 | } 289 | } 290 | 291 | /// General Weierstrass curve of the form: 292 | /// `y^2 + a1*x*y + a3*y = x^3 + a2*x^2 + a4*x + a6` 293 | pub trait WeierstrassCurve: 294 | Clone + Copy + Debug + CanonicalDeserialize + CanonicalSerialize + Eq + PartialEq + PartialOrd + Ord 295 | { 296 | type F: Field; 297 | 298 | /// Returns Weierstrass equation coefficient `a1` 299 | fn a1(&self) -> Self::F; 300 | 301 | /// Returns Weierstrass equation coefficient `a2` 302 | fn a2(&self) -> Self::F; 303 | 304 | /// Returns Weierstrass equation coefficient `a3` 305 | fn a3(&self) -> Self::F; 306 | 307 | /// Returns Weierstrass equation coefficient `a4` 308 | fn a4(&self) -> Self::F; 309 | 310 | /// Returns Weierstrass equation coefficient `a6` 311 | fn a6(&self) -> Self::F; 312 | } 313 | 314 | /// Defines an isogeny between curves: 315 | /// ϕ(x, y) = (r(x), g(x) + h(x) * y) 316 | // TODO: r, g and h are pretty confusing symbols 317 | #[derive(Clone, Debug, CanonicalDeserialize, CanonicalSerialize)] 318 | pub struct Isogeny { 319 | pub domain: C, 320 | pub codomain: C, 321 | pub r: RationalMap, 322 | pub g: RationalMap, 323 | pub h: RationalMap, 324 | } 325 | 326 | impl Isogeny { 327 | /// Isogeny ϕ(x, y) = (r(x), g(x) + h(x) * y) 328 | pub fn new( 329 | domain: C, 330 | codomain: C, 331 | r: RationalMap, 332 | g: RationalMap, 333 | h: RationalMap, 334 | ) -> Self { 335 | Self { 336 | domain, 337 | codomain, 338 | r, 339 | g, 340 | h, 341 | } 342 | } 343 | 344 | pub fn map(&self, p: &Point) -> Point { 345 | if p.is_zero() { 346 | // TODO: should this be handled differently? 347 | return Point::zero(); 348 | } 349 | 350 | assert_eq!(self.domain, p.curve.unwrap()); 351 | let rx = self.r.map(&p.x); 352 | let gx = self.g.map(&p.x); 353 | let hx = self.h.map(&p.x); 354 | match (rx, gx, hx) { 355 | (Some(rx), Some(gx), Some(hx)) => Point::new(rx, gx + hx * p.y, self.codomain), 356 | _ => Point::zero(), 357 | } 358 | } 359 | } 360 | 361 | /// Point on an elliptic curve 362 | #[derive(Clone, Copy, Debug)] 363 | pub struct Point { 364 | pub x: C::F, 365 | pub y: C::F, 366 | pub curve: Option, 367 | } 368 | 369 | impl Point { 370 | pub const fn new(x: C::F, y: C::F, curve: C) -> Self { 371 | let curve = Some(curve); 372 | Self { x, y, curve } 373 | } 374 | } 375 | 376 | impl Add for Point { 377 | type Output = Self; 378 | 379 | fn add(self, rhs: Self) -> Self { 380 | // Addition law for elliptic curve groups 381 | // Source: "The arithmetic of elliptic curves, 2nd ed." Silverman, III.2.3 382 | if self.is_zero() { 383 | rhs 384 | } else if rhs.is_zero() { 385 | self 386 | } else if self.curve != rhs.curve { 387 | panic!("points belong to different curves"); 388 | } else { 389 | let curve = self.curve.unwrap(); 390 | let a1 = curve.a1(); 391 | let a2 = curve.a2(); 392 | let a3 = curve.a3(); 393 | let a4 = curve.a4(); 394 | let a6 = curve.a6(); 395 | let x1 = self.x; 396 | let y1 = self.y; 397 | let x2 = rhs.x; 398 | let y2 = rhs.y; 399 | 400 | if x1 == x2 && (y1 + y2 + a1 * x2 + a3).is_zero() { 401 | Self::zero() 402 | } else { 403 | let lambda: C::F; 404 | let nu: C::F; 405 | if x1 == x2 { 406 | // tangent line 407 | let x1x1 = x1.square(); 408 | let a2x1 = a2 * x1; 409 | let a1x1 = a1 * x1; 410 | lambda = 411 | (x1x1 + x1x1 + x1x1 + a2x1 + a2x1 + a4 - a1 * y1) / (y1 + y1 + a1x1 + a3); 412 | nu = (-(x1x1 * x1) + a4 * x1 + a6 + a6 - a3 * y1) / (y1 + y1 + a1 * x1 + a3); 413 | } else { 414 | // slope through the points 415 | lambda = (y2 - y1) / (x2 - x1); 416 | nu = (y1 * x2 - y2 * x1) / (x2 - x1); 417 | } 418 | let x3 = lambda.square() + a1 * lambda - a2 - x1 - x2; 419 | let y3 = -(lambda + a1) * x3 - nu - a3; 420 | Self::new(x3, y3, curve) 421 | } 422 | } 423 | } 424 | } 425 | 426 | impl AddAssign for Point { 427 | fn add_assign(&mut self, rhs: Self) { 428 | *self = *self + rhs 429 | } 430 | } 431 | 432 | impl Mul for Point { 433 | type Output = Self; 434 | 435 | fn mul(self, mut rhs: BigUint) -> Self { 436 | let mut res = Self::zero(); 437 | let mut acc = self; 438 | while !rhs.is_zero() { 439 | if rhs.is_odd() { 440 | res += acc; 441 | } 442 | acc += acc; 443 | rhs >>= 1; 444 | } 445 | res 446 | } 447 | } 448 | 449 | impl Neg for Point { 450 | type Output = Self; 451 | 452 | fn neg(self) -> Self { 453 | // Source: "The arithmetic of elliptic curves, 2nd ed." Silverman, III.2.3 454 | if self.is_zero() { 455 | return self; 456 | } 457 | 458 | let curve = self.curve.unwrap(); 459 | Self { 460 | y: -self.y - curve.a1() * self.x - curve.a3(), 461 | ..self 462 | } 463 | } 464 | } 465 | 466 | impl PartialEq for Point { 467 | fn eq(&self, other: &Self) -> bool { 468 | if self.is_zero() && other.is_zero() { 469 | true 470 | } else { 471 | assert_eq!(self.curve, other.curve); 472 | self.x == other.x && self.y == other.y 473 | } 474 | } 475 | } 476 | 477 | impl Zero for Point { 478 | fn zero() -> Self { 479 | Self { 480 | x: C::F::zero(), 481 | y: C::F::zero(), 482 | curve: None, 483 | } 484 | } 485 | 486 | fn is_zero(&self) -> bool { 487 | self.curve.is_none() 488 | } 489 | } 490 | 491 | /// Builds an FFTree (from a elliptic curve point) that's capable of evaluating 492 | /// degree n-1 polynomials. Returns None if no such FFTree exists. 493 | /// 494 | /// `subgroup_generator` must generate a cyclic subgroup of order 2^k. 495 | /// `subgroup_order` is the order of the subgroup generated by the generator. 496 | /// `coset_offset` must be disjoint from the subgroup defined by the generator. 497 | /// `n` is the power-of-2 size of the FFTree. 498 | pub fn build_ec_fftree( 499 | subgroup_generator: Point>, 500 | subgroup_order: usize, 501 | coset_offset: Point>, 502 | n: usize, 503 | ) -> Option> { 504 | assert_ne!(coset_offset, subgroup_generator); 505 | assert_eq!(coset_offset.curve, subgroup_generator.curve); 506 | assert!(n.is_power_of_two()); 507 | assert!(subgroup_order.is_power_of_two()); 508 | let subgroup_two_addicity = subgroup_order.ilog2(); 509 | let log_n = n.ilog2(); 510 | assert!(log_n < 32); 511 | 512 | // FFTree size is too large for our generator 513 | if log_n > subgroup_two_addicity { 514 | return None; 515 | } 516 | 517 | // get a generator of a subgroup with order `n` 518 | let mut generator = subgroup_generator; 519 | for _ in 0..subgroup_two_addicity - log_n { 520 | generator += generator; 521 | } 522 | 523 | // find our rational maps 524 | let mut rational_maps = Vec::new(); 525 | let mut g = generator; 526 | for _ in 0..log_n { 527 | let isogeny = g 528 | .curve 529 | .unwrap() 530 | .two_isogenies() 531 | .into_iter() 532 | .find_map(|isogeny| { 533 | let g_prime = isogeny.map(&g); 534 | if two_adicity(g)? == two_adicity(g_prime)? + 1 { 535 | g = g_prime; 536 | Some(isogeny) 537 | } else { 538 | None 539 | } 540 | }) 541 | .expect("cannot find a suitable isogeny"); 542 | rational_maps.push(isogeny.r) 543 | } 544 | 545 | // generate the FFTree leaf nodes 546 | let mut leaves = vec![F::zero(); n]; 547 | let mut acc = Point::zero(); 548 | for x in &mut leaves { 549 | *x = (coset_offset + acc).x; 550 | acc += generator; 551 | } 552 | 553 | Some(FFTree::new(leaves, rational_maps)) 554 | } 555 | 556 | #[cfg(test)] 557 | mod tests { 558 | use super::*; 559 | use ark_ff::One; 560 | use ark_ff_optimized::fp31::Fp; 561 | 562 | #[test] 563 | fn two_torsion_points_have_order_two() { 564 | let curve = ShortWeierstrassCurve::new(Fp::one(), Fp::zero()); 565 | 566 | let two_torsion_points = curve.two_torsion_points(); 567 | 568 | for p in two_torsion_points { 569 | assert!(!p.is_zero()); 570 | assert!((p + p).is_zero()); 571 | } 572 | } 573 | 574 | #[test] 575 | fn two_isogenies_map_to_identity() { 576 | let curve = ShortWeierstrassCurve::new(Fp::one(), Fp::zero()); 577 | let two_torsion_points = curve.two_torsion_points(); 578 | 579 | let two_isogenies = curve.two_isogenies(); 580 | 581 | for p in two_torsion_points { 582 | for isogeny in &two_isogenies { 583 | assert!(isogeny.r.map(&p.x).is_none()); 584 | } 585 | } 586 | } 587 | } 588 | -------------------------------------------------------------------------------- /src/fftree.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::BinaryTree; 2 | use crate::utils::Mat2x2; 3 | use crate::utils::RationalMap; 4 | use alloc::boxed::Box; 5 | use ark_ff::batch_inversion; 6 | use ark_ff::vec; 7 | use ark_ff::vec::Vec; 8 | use ark_ff::Field; 9 | use ark_poly::Polynomial; 10 | use ark_serialize::CanonicalDeserialize; 11 | use ark_serialize::CanonicalSerialize; 12 | use ark_serialize::Compress; 13 | use ark_serialize::Valid; 14 | use core::cmp::Ordering; 15 | use core::iter::zip; 16 | 17 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 18 | pub enum Moiety { 19 | S0, 20 | S1, 21 | } 22 | 23 | #[derive(Clone, Debug)] 24 | pub struct FFTree { 25 | pub f: BinaryTree, 26 | pub recombine_matrices: BinaryTree>, 27 | pub decompose_matrices: BinaryTree>, 28 | pub rational_maps: Vec>, 29 | pub subtree: Option>, 30 | pub xnn_s: Vec, // = 31 | pub xnn_s_inv: Vec, // = <1/X^(n/2) ≀ S> 32 | pub z0_s1: Vec, // = 33 | pub z1_s0: Vec, // = 34 | pub z0_inv_s1: Vec, // = <1/Z_0 ≀ S_1> 35 | pub z1_inv_s0: Vec, // = <1/Z_1 ≀ S_0> 36 | pub z0z0_rem_xnn_s: Vec, // = 37 | pub z1z1_rem_xnn_s: Vec, // = 38 | } 39 | 40 | // TODO: errors 41 | impl FFTree { 42 | pub fn new(leaves: Vec, rational_maps: Vec>) -> Self { 43 | let n = leaves.len(); 44 | assert!(n.is_power_of_two()); 45 | let log_n = n.ilog2(); 46 | assert_eq!(log_n, rational_maps.len() as u32); 47 | let n = 1 << log_n; 48 | 49 | // copy leaf nodes 50 | let mut f = BinaryTree::from(vec![F::zero(); 2 * n]); 51 | f[n..].copy_from_slice(&leaves); 52 | 53 | // generate internal nodes 54 | // TODO: would be cool to use array_windows_mut 55 | let mut f_layers = f.get_layers_mut(); 56 | for (i, rational_map) in rational_maps.iter().enumerate() { 57 | let (prev_layer, layer) = { 58 | let (prev_layers, layers) = f_layers.split_at_mut(i + 1); 59 | (prev_layers.last_mut().unwrap(), layers.first_mut().unwrap()) 60 | }; 61 | 62 | let layer_size = layer.len(); 63 | for (i, s) in layer.iter_mut().enumerate() { 64 | *s = rational_map.map(&prev_layer[i]).unwrap(); 65 | debug_assert_eq!(*s, rational_map.map(&prev_layer[i + layer_size]).unwrap()); 66 | } 67 | } 68 | 69 | Self::from_tree(f, rational_maps) 70 | } 71 | 72 | fn extend_impl(&self, evals: &[F], moiety: Moiety) -> Vec { 73 | let n = evals.len(); 74 | if n == 1 { 75 | return evals.to_vec(); 76 | } 77 | 78 | let layer = (self.f.num_layers() - 2 - n.ilog2()) as usize; 79 | 80 | // π_0 and π_1 81 | let mut evals0 = vec![F::zero(); n / 2]; 82 | let mut evals1 = vec![F::zero(); n / 2]; 83 | for (i, m) in self 84 | .decompose_matrices 85 | .get_layer(layer) 86 | .iter() 87 | .skip(match moiety { 88 | Moiety::S0 => 1, 89 | Moiety::S1 => 0, 90 | }) 91 | .step_by(2) 92 | .enumerate() 93 | { 94 | let [v0, v1] = m * &[evals[i], evals[i + n / 2]]; 95 | evals0[i] = v0; 96 | evals1[i] = v1; 97 | } 98 | 99 | // π_0' and π_1' 100 | let evals0_prime = self.extend_impl(&evals0, moiety); 101 | let evals1_prime = self.extend_impl(&evals1, moiety); 102 | 103 | let mut res = vec![F::zero(); n]; 104 | for (i, m) in self 105 | .recombine_matrices 106 | .get_layer(layer) 107 | .iter() 108 | .skip(match moiety { 109 | Moiety::S0 => 0, 110 | Moiety::S1 => 1, 111 | }) 112 | .step_by(2) 113 | .enumerate() 114 | { 115 | let [v0, v1] = m * &[evals0_prime[i], evals1_prime[i]]; 116 | res[i] = v0; 117 | res[i + n / 2] = v1; 118 | } 119 | res 120 | } 121 | 122 | /// Extends evals on the chosen moiety 123 | pub fn extend(&self, evals: &[F], moiety: Moiety) -> Vec { 124 | let tree = self.subtree_with_size(evals.len() * 2); 125 | tree.extend_impl(evals, moiety) 126 | } 127 | 128 | fn mextend_impl(&self, evals: &[F], moiety: Moiety) -> Vec { 129 | let e = self.extend_impl(evals, moiety); 130 | let z = match moiety { 131 | Moiety::S1 => &self.z0_s1, 132 | Moiety::S0 => &self.z1_s0, 133 | }; 134 | zip(e, z).map(|(e, z)| e + z).collect() 135 | } 136 | 137 | /// Extends special monic polynomials 138 | pub fn mextend(&self, evals: &[F], moiety: Moiety) -> Vec { 139 | let tree = self.subtree_with_size(evals.len() * 2); 140 | tree.mextend_impl(evals, moiety) 141 | } 142 | 143 | fn enter_impl(&self, coeffs: &[F]) -> Vec { 144 | let n = coeffs.len(); 145 | if n == 1 { 146 | return coeffs.to_vec(); 147 | } 148 | 149 | let subtree = self.subtree().unwrap(); 150 | let u0 = subtree.enter(&coeffs[0..n / 2]); 151 | let v0 = subtree.enter(&coeffs[n / 2..]); 152 | let u1 = self.extend(&u0, Moiety::S1); 153 | let v1 = self.extend(&v0, Moiety::S1); 154 | 155 | let mut res = Vec::new(); 156 | for i in 0..n / 2 { 157 | res.push(u0[i] + v0[i] * self.xnn_s[i * 2]); 158 | res.push(u1[i] + v1[i] * self.xnn_s[i * 2 + 1]); 159 | } 160 | res 161 | } 162 | 163 | /// Converts from coefficient to evaluation representation of a polynomial 164 | pub fn enter(&self, coeffs: &[F]) -> Vec { 165 | let tree = self.subtree_with_size(coeffs.len()); 166 | tree.enter_impl(coeffs) 167 | } 168 | 169 | fn degree_impl(&self, evals: &[F]) -> usize { 170 | let n = evals.len(); 171 | if n == 1 { 172 | return 0; 173 | } 174 | 175 | let subtree = self.subtree().unwrap(); 176 | let (e0, e1): (Vec, Vec) = evals.chunks(2).map(|e| (e[0], e[1])).unzip(); 177 | 178 | // The intuition is that if `degree < n/2` then all coefficients on the RHS 179 | // will be 0. Therefore if `degree < n/2` then e1 == extend(e0) 180 | let g1 = self.extend_impl(&e0, Moiety::S1); 181 | if g1 == e1 { 182 | return subtree.degree_impl(&e0); 183 | } 184 | 185 | // compute `<(π-g)/Z_0 ≀ S1>` 186 | // isolate the evaluations of the coefficients on the RHS 187 | let t1: Vec = zip(zip(e1, g1), &self.z0_inv_s1) 188 | .map(|((e1, g1), z0_inv)| (e1 - g1) * z0_inv) 189 | .collect(); 190 | let t0 = self.extend_impl(&t1, Moiety::S0); 191 | n / 2 + subtree.degree_impl(&t0) 192 | } 193 | 194 | /// Evaluates the degree of an evaluation table in `O(n log n)` 195 | pub fn degree(&self, evals: &[F]) -> usize { 196 | let tree = self.subtree_with_size(evals.len()); 197 | tree.degree_impl(evals) 198 | } 199 | 200 | pub fn exit_impl(&self, evals: &[F]) -> Vec { 201 | let n = evals.len(); 202 | if n == 1 { 203 | return evals.to_vec(); 204 | } 205 | 206 | let u0: Vec = self 207 | .modular_reduce_impl(evals, &self.xnn_s, &self.z0z0_rem_xnn_s) 208 | .into_iter() 209 | .step_by(2) 210 | .collect(); 211 | 212 | let subtree = self.subtree().unwrap(); 213 | let mut a = subtree.exit_impl(&u0); 214 | 215 | let xnn0_inv = self.xnn_s_inv.iter().step_by(2); 216 | let e0 = evals.iter().step_by(2); 217 | let v0: Vec = zip(zip(e0, u0), xnn0_inv) 218 | .map(|((&e0, u0), xnn0_inv)| (e0 - u0) * xnn0_inv) 219 | .collect(); 220 | let mut b = subtree.exit_impl(&v0); 221 | 222 | a.append(&mut b); 223 | a 224 | } 225 | 226 | /// Converts from evaluation to coefficient representation of a polynomial 227 | pub fn exit(&self, evals: &[F]) -> Vec { 228 | let tree = self.subtree_with_size(evals.len()); 229 | tree.exit_impl(evals) 230 | } 231 | 232 | fn redc_impl(&self, evals: &[F], a: &[F], moiety: Moiety) -> Vec { 233 | let (e0, e1): (Vec, Vec) = evals.chunks(2).map(|e| (e[0], e[1])).unzip(); 234 | let (mut a0_inv, a1): (Vec, Vec) = a.chunks(2).map(|a| (a[0], a[1])).unzip(); 235 | batch_inversion(&mut a0_inv); 236 | 237 | // compute <π/a ≀ S> 238 | let t0: Vec = zip(e0, a0_inv).map(|(e0, a0_inv)| e0 * a0_inv).collect(); 239 | let g1 = self.extend_impl( 240 | &t0, 241 | match moiety { 242 | Moiety::S1 => Moiety::S0, 243 | Moiety::S0 => Moiety::S1, 244 | }, 245 | ); 246 | 247 | let z_inv = match moiety { 248 | Moiety::S0 => &self.z0_inv_s1, 249 | Moiety::S1 => &self.z1_inv_s0, 250 | }; 251 | 252 | // compute `<(π - a*g)/Z ≀ S'>` 253 | let h1: Vec = zip(zip(e1, g1), zip(a1, z_inv)) 254 | .map(|((e1, g1), (a1, z0_inv))| (e1 - g1 * a1) * z0_inv) 255 | .collect(); 256 | let h0 = self.extend_impl(&h1, moiety); 257 | 258 | zip(h0, h1).flat_map(|h| [h.0, h.1]).collect() 259 | } 260 | 261 | /// Computes 262 | /// Z_0 is the vanishing polynomial of S_0 263 | /// `a` must be a polynomial of max degree `n/2` having no zeroes in `S_0` 264 | pub fn redc_z0(&self, evals: &[F], a: &[F]) -> Vec { 265 | let tree = self.subtree_with_size(evals.len()); 266 | tree.redc_impl(evals, a, Moiety::S0) 267 | } 268 | 269 | /// Computes `` 270 | /// `Z_1` is the vanishing polynomial of `S_1` 271 | /// `A` must be a polynomial of max degree `n/2` having no zeroes in `S_1` 272 | pub fn redc_z1(&self, evals: &[F], a: &[F]) -> Vec { 273 | let tree = self.subtree_with_size(evals.len()); 274 | tree.redc_impl(evals, a, Moiety::S1) 275 | } 276 | 277 | fn modular_reduce_impl(&self, evals: &[F], a: &[F], c: &[F]) -> Vec { 278 | let h = self.redc_impl(evals, a, Moiety::S0); 279 | let hc: Vec = zip(h, c).map(|(h, c)| h * c).collect(); 280 | self.redc_impl(&hc, a, Moiety::S0) 281 | } 282 | 283 | /// Computes MOD algorithm 284 | /// `a` must be a polynomial of max degree `n/2` having no zeroes in `S_0` 285 | /// `c` must be the evaluation table `` 286 | pub fn modular_reduce(&self, evals: &[F], a: &[F], c: &[F]) -> Vec { 287 | let tree = self.subtree_with_size(evals.len()); 288 | tree.modular_reduce_impl(evals, a, c) 289 | } 290 | 291 | fn vanish_impl(&self, vanish_domain: &[F]) -> Vec { 292 | let n = vanish_domain.len(); 293 | if n == 1 { 294 | let l = self.f.leaves(); 295 | assert_eq!(2, l.len()); 296 | let alpha = vanish_domain[0]; 297 | return vec![alpha - l[0], alpha - l[1]]; 298 | } 299 | 300 | let subtree = self.subtree().unwrap(); 301 | let qp = subtree.vanish_impl(&vanish_domain[0..n / 2]); 302 | let qpp = subtree.vanish_impl(&vanish_domain[n / 2..n]); 303 | let q_s0: Vec = zip(qp, qpp).map(|(qp, qpp)| qp * qpp).collect(); 304 | let q_s1 = self.mextend(&q_s0, Moiety::S1); 305 | zip(q_s0, q_s1) 306 | .flat_map(|(q_s0, q_s1)| [q_s0, q_s1]) 307 | .collect() 308 | } 309 | 310 | /// Returns an evaluation of the vanishing polynomial `Z(x) = ∏ (x - a_i)` 311 | /// Runtime `O(n log^2 n)`. `vanishi_domain = [a_0, a_1, ..., a_(n - 1)]` 312 | /// Section 7.1 https://arxiv.org/pdf/2107.08473.pdf 313 | pub fn vanish(&self, vanish_domain: &[F]) -> Vec { 314 | let tree = self.subtree_with_size(vanish_domain.len() * 2); 315 | tree.vanish_impl(vanish_domain) 316 | } 317 | 318 | fn from_tree(f: BinaryTree, rational_maps: Vec>) -> Self { 319 | let subtree = Self::derive_subtree(&f, &rational_maps).map(Box::new); 320 | let f_layers = f.get_layers(); 321 | let n = f.leaves().len(); 322 | let nn = n as u64 / 2; 323 | let nnnn = n as u64 / 4; 324 | let s = f_layers[0]; 325 | 326 | // Precompute eval table 327 | // TODO: compute xnn from xnnnn for n != 2 328 | let xnnnn_s: Vec = f_layers[0].iter().map(|x| x.pow([nnnn])).collect(); 329 | let mut xnnnn_s_inv = xnnnn_s.clone(); 330 | batch_inversion(&mut xnnnn_s_inv); 331 | let xnn_s: Vec = f_layers[0].iter().map(|x| x.pow([nn])).collect(); 332 | let mut xnn_s_inv = xnn_s.clone(); 333 | batch_inversion(&mut xnn_s_inv); 334 | 335 | // Split S into its two moieties S0 and S1 336 | let (s0, s1): (Vec, Vec) = s.chunks_exact(2).map(|s| (s[0], s[1])).unzip(); 337 | 338 | // Generate polynomial decomposition matrices 339 | // Lemma 3.2 (M_t) https://arxiv.org/abs/2107.08473 340 | // TODO: change notation 341 | let mut recombine_matrices = BinaryTree::from(vec![Mat2x2::identity(); n]); 342 | let mut decompose_matrices = BinaryTree::from(vec![Mat2x2::identity(); n]); 343 | let recombine_layers = recombine_matrices.get_layers_mut(); 344 | let decompose_layers = decompose_matrices.get_layers_mut(); 345 | for ((recombine_layer, decompose_layer), (l, map)) in zip( 346 | zip(recombine_layers, decompose_layers), 347 | zip(f_layers, &rational_maps), 348 | ) { 349 | let d = l.len() / 2; 350 | if d == 1 { 351 | continue; 352 | } 353 | 354 | let v = &map.denominator; 355 | for (i, (rmat, dmat)) in zip(recombine_layer, decompose_layer).enumerate() { 356 | let s0 = l[i]; 357 | let s1 = l[i + d]; 358 | let v0 = v.evaluate(&s0).pow([(d / 2 - 1) as u64]); 359 | let v1 = v.evaluate(&s1).pow([(d / 2 - 1) as u64]); 360 | *rmat = Mat2x2([[v0, s0 * v0], [v1, s1 * v1]]); 361 | *dmat = rmat.inverse().unwrap(); 362 | } 363 | } 364 | 365 | let mut tree = Self { 366 | f, 367 | recombine_matrices, 368 | decompose_matrices, 369 | rational_maps, 370 | subtree, 371 | xnn_s, 372 | xnn_s_inv, 373 | z0_s1: Vec::new(), 374 | z1_s0: Vec::new(), 375 | z1_inv_s0: Vec::new(), 376 | z0_inv_s1: Vec::new(), 377 | z0z0_rem_xnn_s: Vec::new(), 378 | z1z1_rem_xnn_s: Vec::new(), 379 | }; 380 | 381 | // Precompute eval tables and using our partial FFTree 382 | // Z_0 is the vanishing polynomial of S_0 i.e. Z_0(x) = Π(x - s0_i) 383 | // TODO: this code is a little brittle, might be nice to find a cleaner solution 384 | match n.cmp(&2) { 385 | Ordering::Greater => { 386 | // compute z0_s1 in O(n log n) using the subtree's vanishing polynomials 387 | let zero = F::zero(); 388 | let st = tree.subtree.as_ref().unwrap(); 389 | let st_z0_s0: Vec = st.z0_s1.iter().flat_map(|&y| [zero, y]).collect(); 390 | let st_z1_s0: Vec = st.z1_s0.iter().flat_map(|&y| [y, zero]).collect(); 391 | let st_z0_s1 = tree.extend(&st_z0_s0, Moiety::S1); 392 | let st_z1_s1 = tree.extend(&st_z1_s0, Moiety::S1); 393 | tree.z0_s1 = zip(st_z0_s1, st_z1_s1).map(|(z0, z1)| z0 * z1).collect(); 394 | 395 | // compute z1_s in O(n log^2 n) - .vanish() uses z0_s1 396 | let z1_s = tree.vanish(&s1); 397 | tree.z1_s0 = z1_s.into_iter().step_by(2).collect(); 398 | } 399 | Ordering::Equal => { 400 | // base cases 401 | tree.z0_s1 = vec![s1[0] - s0[0]]; 402 | tree.z1_s0 = vec![s0[0] - s1[0]]; 403 | } 404 | Ordering::Less => {} 405 | } 406 | 407 | tree.z0_inv_s1 = tree.z0_s1.clone(); 408 | tree.z1_inv_s0 = tree.z1_s0.clone(); 409 | batch_inversion(&mut tree.z0_inv_s1); 410 | batch_inversion(&mut tree.z1_inv_s0); 411 | 412 | // Precompute evaluation tables and 413 | // using our partial FFTree. 414 | // TODO: alternative approach - https://www.math.toronto.edu/swastik/ECFFT2.pdf 415 | // section 5.1 equation 11 gives an algorithm for the vanishing polynomial. 416 | // Might be nice for a O(log n) verifier vanishing polynomial evaluation. 417 | match n.cmp(&2) { 418 | Ordering::Greater => { 419 | // compute z0z0_rem_xnn_s in O(n log n) 420 | let st = tree.subtree.as_ref().unwrap(); 421 | let z0_rem_xnnnn_sq_s0 = zip(&st.z0z0_rem_xnn_s, &st.z1z1_rem_xnn_s) 422 | .map(|(y0, y1)| *y0 * y1) 423 | .collect::>(); 424 | let z0z0_rem_xnnnn_s0 = 425 | st.modular_reduce(&z0_rem_xnnnn_sq_s0, &st.xnn_s, &st.z0z0_rem_xnn_s); 426 | let z0z0_rem_xnnnn_s1 = tree.extend(&z0z0_rem_xnnnn_s0, Moiety::S1); 427 | let z0z0_rem_xnnnn_s = zip(z0z0_rem_xnnnn_s0, z0z0_rem_xnnnn_s1) 428 | .flat_map(|(y0, y1)| [y0, y1]) 429 | .collect::>(); 430 | let z0_s = tree.z0_s1.iter().flat_map(|&y1| [F::zero(), y1]); 431 | let z0_rem_xnn_s = zip(z0_s, &tree.xnn_s).map(|(z0, xnn)| z0 - xnn); 432 | let z0_rem_xnn_sq_s = z0_rem_xnn_s.map(|y| y.square()).collect::>(); 433 | let z0_rem_xnn_sq_div_xnnnn_s = 434 | zip(&z0_rem_xnn_sq_s, zip(&z0z0_rem_xnnnn_s, &xnnnn_s_inv)) 435 | .map(|(z0_rem_xnn_sq, (z0z0_rem_xnnnn, xnnnn_inv))| { 436 | (*z0_rem_xnn_sq - z0z0_rem_xnnnn) * xnnnn_inv 437 | }) 438 | .collect::>(); 439 | let z0z0_div_xnnnn_rem_xnnnn_s = 440 | tree.modular_reduce(&z0_rem_xnn_sq_div_xnnnn_s, &xnnnn_s, &z0z0_rem_xnnnn_s); 441 | tree.z0z0_rem_xnn_s = 442 | zip(z0z0_rem_xnnnn_s, zip(z0z0_div_xnnnn_rem_xnnnn_s, xnnnn_s)) 443 | .map(|(z0z0_rem_xnnnn, (z0z0_div_xnnnn_rem_xnnnn, xnnnn))| { 444 | z0z0_rem_xnnnn + xnnnn * z0z0_div_xnnnn_rem_xnnnn 445 | }) 446 | .collect(); 447 | 448 | // compute z1z1_rem_xnn_s in O(n log n) 449 | let z1_s = tree.z1_s0.iter().flat_map(|&y0| [y0, F::zero()]); 450 | let z1_rem_xnn_s = zip(z1_s, &tree.xnn_s).map(|(z1, xnn)| z1 - xnn); 451 | let z1z1 = z1_rem_xnn_s.map(|y| y.square()).collect::>(); 452 | tree.z1z1_rem_xnn_s = tree.modular_reduce(&z1z1, &tree.xnn_s, &tree.z0z0_rem_xnn_s); 453 | } 454 | Ordering::Equal => { 455 | // base cases 456 | tree.z0z0_rem_xnn_s = vec![s0[0].square(); 2]; 457 | tree.z1z1_rem_xnn_s = vec![s1[0].square(); 2]; 458 | } 459 | Ordering::Less => {} 460 | } 461 | 462 | tree 463 | } 464 | 465 | fn derive_subtree(f: &BinaryTree, rational_maps: &[RationalMap]) -> Option { 466 | let n = f.leaves().len() / 2; 467 | if n == 0 { 468 | return None; 469 | } 470 | 471 | let mut f_prime = BinaryTree::from(vec![F::zero(); n * 2]); 472 | let f_prime_layers = f_prime.get_layers_mut(); 473 | let f_layers = f.get_layers(); 474 | for (l_prime, l) in zip(f_prime_layers, f_layers) { 475 | for (s_prime, s) in l_prime.iter_mut().zip(l.iter().step_by(2)) { 476 | *s_prime = *s; 477 | } 478 | } 479 | 480 | let rational_maps = rational_maps.split_last().map_or(vec![], |i| i.1.to_vec()); 481 | Some(Self::from_tree(f_prime, rational_maps)) 482 | } 483 | 484 | pub(crate) fn subtree(&self) -> Option<&Self> { 485 | Some(self.subtree.as_ref()?) 486 | } 487 | 488 | /// Returns a FFTree with `n` leaves 489 | pub fn subtree_with_size(&self, n: usize) -> &Self { 490 | assert!(n.is_power_of_two()); 491 | match usize::cmp(&n, &self.f.leaves().len()) { 492 | Ordering::Less => self.subtree().unwrap().subtree_with_size(n), 493 | Ordering::Equal => self, 494 | Ordering::Greater => panic!("FFTree is too small"), 495 | } 496 | } 497 | } 498 | 499 | #[cfg(test)] 500 | impl FFTree { 501 | // TODO: maybe remove 502 | pub(crate) fn eval_domain(&self) -> &[F] { 503 | self.f.leaves() 504 | } 505 | } 506 | 507 | // TODO: implement CanonicalSerialize for Box in arkworks 508 | // TODO: Derive bug "error[E0275]" with recursive field subtree 509 | // TODO: add compress version (with flags perhaps) 510 | impl CanonicalSerialize for FFTree { 511 | fn serialize_with_mode( 512 | &self, 513 | mut writer: W, 514 | compress: ark_serialize::Compress, 515 | ) -> Result<(), ark_serialize::SerializationError> { 516 | #[deny(unused_variables)] 517 | let Self { 518 | f, 519 | recombine_matrices, 520 | decompose_matrices, 521 | rational_maps, 522 | xnn_s, 523 | xnn_s_inv, 524 | z0_s1, 525 | z1_s0, 526 | z0_inv_s1, 527 | z1_inv_s0, 528 | z0z0_rem_xnn_s, 529 | z1z1_rem_xnn_s, 530 | subtree, 531 | } = self; 532 | f.serialize_with_mode(&mut writer, compress)?; 533 | recombine_matrices.serialize_with_mode(&mut writer, compress)?; 534 | decompose_matrices.serialize_with_mode(&mut writer, compress)?; 535 | rational_maps.serialize_with_mode(&mut writer, compress)?; 536 | xnn_s.serialize_with_mode(&mut writer, compress)?; 537 | z0_s1.serialize_with_mode(&mut writer, compress)?; 538 | z1_s0.serialize_with_mode(&mut writer, compress)?; 539 | if compress == ark_serialize::Compress::No { 540 | // Inverses are the cheapest to regenerate (1 mul per element) 541 | xnn_s_inv.serialize_with_mode(&mut writer, compress)?; 542 | z0_inv_s1.serialize_with_mode(&mut writer, compress)?; 543 | z1_inv_s0.serialize_with_mode(&mut writer, compress)?; 544 | } 545 | z0z0_rem_xnn_s.serialize_with_mode(&mut writer, compress)?; 546 | z1z1_rem_xnn_s.serialize_with_mode(&mut writer, compress)?; 547 | // TODO: get "error[E0275]: overflow evaluating the requirement" for: 548 | // subtree.as_ref().map(Box::as_ref).serialize_with_mode(...) 549 | (subtree.is_some()).serialize_with_mode(&mut writer, compress)?; 550 | if let Some(subtree) = subtree.as_ref().map(Box::as_ref) { 551 | subtree.serialize_with_mode(writer, compress)?; 552 | } 553 | Ok(()) 554 | } 555 | 556 | fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { 557 | #[deny(unused_variables)] 558 | let Self { 559 | f, 560 | recombine_matrices, 561 | decompose_matrices, 562 | rational_maps, 563 | xnn_s, 564 | xnn_s_inv, 565 | z0_s1, 566 | z1_s0, 567 | z0_inv_s1, 568 | z1_inv_s0, 569 | z0z0_rem_xnn_s, 570 | z1z1_rem_xnn_s, 571 | subtree, 572 | } = self; 573 | let mut size = f.serialized_size(compress) 574 | + recombine_matrices.serialized_size(compress) 575 | + decompose_matrices.serialized_size(compress) 576 | + rational_maps.serialized_size(compress) 577 | + xnn_s.serialized_size(compress) 578 | + z0_s1.serialized_size(compress) 579 | + z1_s0.serialized_size(compress) 580 | + z0z0_rem_xnn_s.serialized_size(compress) 581 | + z1z1_rem_xnn_s.serialized_size(compress) 582 | // subtree: 1 (for Option state) + subtree size 583 | + 1 + subtree.as_ref().map_or(0, |v| v.as_ref().serialized_size(compress)); 584 | if compress == Compress::No { 585 | size += xnn_s_inv.serialized_size(compress) 586 | + z0_inv_s1.serialized_size(compress) 587 | + z1_inv_s0.serialized_size(compress); 588 | } 589 | size 590 | } 591 | } 592 | 593 | impl Valid for FFTree { 594 | #[inline] 595 | fn check(&self) -> Result<(), ark_serialize::SerializationError> { 596 | Ok(()) 597 | } 598 | } 599 | 600 | // TODO: implement CanonicalDeserialize for Box in arkworks 601 | // TODO: Derive bug "error[E0275]" with recursive field subtree 602 | impl CanonicalDeserialize for FFTree { 603 | fn deserialize_with_mode( 604 | mut reader: R, 605 | compress: ark_serialize::Compress, 606 | validate: ark_serialize::Validate, 607 | ) -> Result { 608 | let f = BinaryTree::deserialize_with_mode(&mut reader, compress, validate)?; 609 | let recombine_matrices = 610 | BinaryTree::deserialize_with_mode(&mut reader, compress, validate)?; 611 | let decompose_matrices = 612 | BinaryTree::deserialize_with_mode(&mut reader, compress, validate)?; 613 | let rational_maps = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 614 | let xnn_s = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 615 | let z0_s1 = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 616 | let z1_s0 = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 617 | let mut xnn_s_inv: Vec; 618 | let mut z0_inv_s1: Vec; 619 | let mut z1_inv_s0: Vec; 620 | match compress { 621 | Compress::Yes => { 622 | xnn_s_inv = xnn_s.clone(); 623 | z0_inv_s1 = z0_s1.clone(); 624 | z1_inv_s0 = z1_s0.clone(); 625 | batch_inversion(&mut xnn_s_inv); 626 | batch_inversion(&mut z0_inv_s1); 627 | batch_inversion(&mut z1_inv_s0); 628 | } 629 | Compress::No => { 630 | xnn_s_inv = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 631 | z0_inv_s1 = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 632 | z1_inv_s0 = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 633 | } 634 | } 635 | let z0z0_rem_xnn_s = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 636 | let z1z1_rem_xnn_s = Vec::deserialize_with_mode(&mut reader, compress, validate)?; 637 | let subtree = if bool::deserialize_with_mode(&mut reader, compress, validate)? { 638 | Some(Box::new(Self::deserialize_with_mode( 639 | reader, compress, validate, 640 | )?)) 641 | } else { 642 | None 643 | }; 644 | Ok(Self { 645 | f, 646 | recombine_matrices, 647 | decompose_matrices, 648 | rational_maps, 649 | subtree, 650 | xnn_s, 651 | xnn_s_inv, 652 | z0_s1, 653 | z1_s0, 654 | z0_inv_s1, 655 | z1_inv_s0, 656 | z0z0_rem_xnn_s, 657 | z1z1_rem_xnn_s, 658 | }) 659 | } 660 | } 661 | -------------------------------------------------------------------------------- /src/find_curve.rs: -------------------------------------------------------------------------------- 1 | use crate::ec::GoodCurve; 2 | use crate::ec::Point; 3 | use crate::utils::is_odd; 4 | use ark_ff::Field; 5 | use rand::Rng; 6 | 7 | /// Returns the x-coordinate of Q if P is a half point of Q. Otherwise returns 8 | /// None. y^2 = x(x^2 + a*x + B), B = b^2. `F` must be an odd order field. 9 | /// 10 | /// "If Q = 2P, we say that Q has a half point P". 11 | fn double_point_x(px: F, a: F, bb: F) -> Option { 12 | assert!(is_odd::()); 13 | let pxpx = px.square(); 14 | let pypy = px * (pxpx + a * px + bb); 15 | if pypy.is_zero() { 16 | return None; 17 | } 18 | Some((pxpx - bb).square() / pypy.double().double()) 19 | } 20 | 21 | /// Returns the x-coordinate of P if P is a half point of Q. Otherwise returns 22 | /// None. y^2 = x(x^2 + a*x + B), B = b^2. `F` must be an odd order field. 23 | /// 24 | /// "If Q = 2P, we say that Q has a half point P". 25 | fn half_point_x(qx: F, a: F, bb: F) -> Option { 26 | assert!(is_odd::()); 27 | let roots = fi_roots(1, qx, a, bb).or_else(|| fi_roots(2, qx, a, bb))?; 28 | roots 29 | .into_iter() 30 | .find(|&x| (x * (x.square() + a * x + bb)).sqrt().is_some()) 31 | } 32 | 33 | /// Finds the roots of a quadratic equation a*x^2 + b*x + c 34 | /// F must be an odd order field 35 | fn roots(a: F, b: F, c: F) -> Option<[F; 2]> { 36 | assert!(is_odd::()); 37 | let discriminant = b.square() - (a * c).double().double(); 38 | let discriminant_sqrt = discriminant.sqrt()?; 39 | let two_inv = a.double().inverse()?; 40 | Some([ 41 | (-b + discriminant_sqrt) * two_inv, 42 | (-b - discriminant_sqrt) * two_inv, 43 | ]) 44 | } 45 | 46 | /// Finds the roots of f_{1, ξ} or f_{2, ξ} if they exists 47 | /// Let Q be a point such that x(Q) = ξ 48 | /// F must be an odd order field 49 | fn fi_roots(i: usize, qx: F, a: F, bb: F) -> Option<[F; 2]> { 50 | assert!(i == 1 || i == 2); 51 | let delta = qx.square() + a * qx + bb; 52 | let delta_sqrt = delta.sqrt()?; 53 | let one = F::one(); 54 | let x_coeff = -(qx.double() + (-one).pow([i as u64]) * delta_sqrt.double()); 55 | roots(one, x_coeff, bb) 56 | } 57 | 58 | /// Determines the 2-sylow subgroup of curve y^2 = x(x^2 + a*x + B), B = b^2 59 | /// Algorithm from: https://www.ams.org/journals/mcom/2005-74-249/S0025-5718-04-01640-0/S0025-5718-04-01640-0.pdf 60 | /// Output is of the form (n, r, x1, x2) with n, r such that Sylow_2(E(Fq)) is 61 | /// isomorphic to Z/2^nZ × Z/2^rZ and x-coordinates x1, x2 of points of order 62 | /// 2^n and 2^r, respectively, generating this Sylow subgroup. Note that None 63 | /// refers to infinity. Panics if `F` is not a field with odd order. 64 | // TODO: finish this implementation 65 | fn _find_two_sylow_subgroup(a: F, bb: F) -> (u32, u32, Option, Option) { 66 | assert!(is_odd::()); 67 | let discriminant = a.square() - bb.double().double(); 68 | assert!(!discriminant.is_zero()); 69 | let one = F::one(); 70 | let zero = F::zero(); 71 | 72 | if discriminant.sqrt().is_some() { 73 | // == non-cyclic case == 74 | 75 | // the roots (r0, r1, r2) of our curve equation give us our 2-torsion points 76 | // x^3 + a*x^2 + B = x(x^2 + a*x + B), B = b^2 77 | let Some([r1, r2]) = roots(F::one(), a, bb) else { unreachable!() }; 78 | let r0 = zero; 79 | 80 | // check if rational points of order 4 exist (lemma 3) 81 | let p4_cond1 = bb.sqrt().and_then(|b| (a - b.double()).sqrt()).is_some(); 82 | let p4_cond2 = r1.sqrt().and_then(|_| (r1.double() + a).sqrt()).is_some(); 83 | let p4_cond3 = r2.sqrt().and_then(|_| (r2.double() + a).sqrt()).is_some(); 84 | 85 | if !p4_cond1 && !p4_cond2 && !p4_cond3 { 86 | // there is no point with order 4 87 | return (1, 1, Some(zero), Some(r1)); 88 | } 89 | 90 | // find an x-coordinate of points of order 4 (lemma 3) 91 | // safe to unwrap since we've already checked if points of order 4 exist 92 | let p4_x0 = roots(one, r0, -bb).map(|[x, _]| x).unwrap(); 93 | let p4_x1 = roots(one, -r1.double(), bb).map(|[x, _]| x).unwrap(); 94 | let p4_x2 = roots(one, -r2.double(), bb).map(|[x, _]| x).unwrap(); 95 | 96 | let has_half_point = |x: F| -> bool { 97 | // halving conditions (Proposition 2) 98 | if x.sqrt().is_none() { 99 | return false; 100 | } 101 | let delta = x.square() + a * x + bb; 102 | let delta_sqrt = match delta.sqrt() { 103 | Some(sqrt) => sqrt, 104 | None => return false, 105 | }; 106 | (x.double() + a + delta_sqrt.double()).sqrt().is_some() 107 | }; 108 | 109 | let mut k = 2; 110 | let mut h = 1; 111 | let mut acc0 = p4_x0; 112 | let mut acc1 = p4_x1; 113 | let mut acc2 = p4_x2; 114 | loop { 115 | let acc0_has_half_point = has_half_point(acc0); 116 | let acc1_has_half_point = has_half_point(acc1); 117 | let acc2_has_half_point = has_half_point(acc2); 118 | 119 | if !acc0_has_half_point && !acc1_has_half_point && !acc2_has_half_point { 120 | // no accumulators have a half point 121 | return (h, h, Some(acc0), Some(acc1)); 122 | } 123 | 124 | if acc0_has_half_point && acc1_has_half_point && acc2_has_half_point { 125 | // all accumulators have a half point 126 | acc0 = half_point_x(acc0, a, bb).unwrap(); 127 | acc1 = half_point_x(acc1, a, bb).unwrap(); 128 | acc2 = half_point_x(acc2, a, bb).unwrap(); 129 | k += 1; 130 | h = k; 131 | continue; 132 | } 133 | 134 | if h == 1 { 135 | // `x2` is the x-coord of a point of order 2 that does not have a half point 136 | // `acc` is TODO 137 | let _x2 = match ( 138 | acc0_has_half_point, 139 | acc1_has_half_point, 140 | acc2_has_half_point, 141 | ) { 142 | (false, _, _) => r0, 143 | (_, false, _) => r1, 144 | (_, _, false) => r2, 145 | _ => unreachable!(), 146 | }; 147 | 148 | todo!() 149 | } 150 | 151 | todo!() 152 | } 153 | 154 | // TODO 155 | } else if let Some(b) = bb.sqrt() { 156 | // == cyclic case == 157 | let p4_x = match ((a + b.double()).sqrt(), (a - b.double()).sqrt()) { 158 | (Some(_), _) => b, 159 | (_, Some(_)) => -b, 160 | _ => unreachable!(), 161 | }; 162 | 163 | if double_point_x(p4_x, a, bb).is_none() { 164 | return (1, 0, Some(zero), None); 165 | } 166 | 167 | let mut k = 2; 168 | let mut acc = p4_x; 169 | while let Some(x) = half_point_x(acc, a, bb) { 170 | k += 1; 171 | acc = x; 172 | } 173 | 174 | (k, 0, Some(acc), None) 175 | } else { 176 | (0, 0, None, None) 177 | } 178 | } 179 | 180 | /// Determines the 2-sylow subgroup of curve y^2 = x(x^2 + a*x + B), B = b^2 181 | /// Output is of the form (n, x) with n such that Sylow_2(E(Fq)) is 182 | /// isomorphic to Z/2^nZ and x-coordinates x, the x-coordinate of the point 183 | /// of order 2^n generating this Sylow subgroup. Note that None refers to 184 | /// infinity. Panics if `F` is not a field with odd order. 185 | /// 186 | /// Algorithm from: https://www.ams.org/journals/mcom/2005-74-249/S0025-5718-04-01640-0/S0025-5718-04-01640-0.pdf 187 | /// note that the algorithm in the paper finds the 2-slyow subgoup of non-cyclic 188 | /// curves as well. This has been ommited since we are only interested in cyclic 189 | /// subgroups for ECFFT. 190 | fn cyclic_two_sylow_subgroup(a: F, bb: F) -> (u32, Option) { 191 | assert!(is_odd::()); 192 | let discriminant = a.square() - bb.double().double(); 193 | assert!(!discriminant.is_zero()); 194 | 195 | if let (Some(b), None) = (bb.sqrt(), discriminant.sqrt()) { 196 | // 2-sylow subgroup is cyclic 197 | let p4_x = match ((a + b.double()).sqrt(), (a - b.double()).sqrt()) { 198 | (Some(_), _) => b, 199 | (_, Some(_)) => -b, 200 | _ => unreachable!(), 201 | }; 202 | 203 | if double_point_x(p4_x, a, bb).is_none() { 204 | return (1, Some(F::zero())); 205 | } 206 | 207 | let mut k = 2; 208 | let mut acc = p4_x; 209 | while let Some(x) = half_point_x(acc, a, bb) { 210 | k += 1; 211 | acc = x; 212 | } 213 | 214 | (k, Some(acc)) 215 | } else { 216 | (0, None) 217 | } 218 | } 219 | 220 | /// Returns a point with order 2^n on a Good Curve such that n >= k. 221 | /// Output is of the form (n, subgroup_generator_point) 222 | /// Based on `find_curve` algorithm from "ECFFT part II": 223 | /// 224 | pub fn find_curve(mut rng: impl Rng, k: u32) -> (u32, Point>) { 225 | let k = k.max(2); 226 | if is_odd::() { 227 | loop { 228 | // curve: y^2 = x*(x^2 + a*x + b) 229 | let a = F::rand(&mut rng); 230 | let bb = F::rand(&mut rng); 231 | // TODO: may be faster this way but would be nice to 232 | // finish the generic two sylow algorithm. 233 | let (n, x) = cyclic_two_sylow_subgroup(a, bb); 234 | if n >= k { 235 | let x = x.unwrap(); 236 | let yy = x * (x.square() + a * x + bb); 237 | let y = yy.sqrt().unwrap(); 238 | let good_curve = GoodCurve::new_odd(a, bb); 239 | let p = Point::new(x, y, good_curve); 240 | return (n, p); 241 | } 242 | } 243 | } else { 244 | todo!() 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | pub mod ec; 4 | pub mod fftree; 5 | pub mod find_curve; 6 | pub mod utils; 7 | 8 | use ark_ff::PrimeField; 9 | use ec::Point; 10 | pub use fftree::FFTree; 11 | pub use fftree::Moiety; 12 | 13 | /// The interface for fields to build FFTrees. 14 | pub trait FftreeField: PrimeField { 15 | fn build_fftree(n: usize) -> Option>; 16 | } 17 | 18 | pub mod secp256k1 { 19 | use super::FFTree; 20 | use super::FftreeField; 21 | use super::Point; 22 | use crate::ec::find_isogeny_chain; 23 | use crate::ec::GoodCurve; 24 | use ark_ff::Fp256; 25 | use ark_ff::MontBackend; 26 | use ark_ff::MontConfig; 27 | use ark_ff::MontFp as F; 28 | use ark_ff::Zero; 29 | 30 | /// Secp256k1's field 31 | #[derive(MontConfig)] 32 | #[modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663"] 33 | #[generator = "3"] 34 | #[small_subgroup_base = "3"] 35 | #[small_subgroup_power = "1"] 36 | pub struct FqConfig; 37 | pub type Fp = Fp256>; 38 | 39 | impl FftreeField for Fp { 40 | fn build_fftree(n: usize) -> Option> { 41 | assert!(n.is_power_of_two()); 42 | let log_n = n.ilog2(); 43 | 44 | // Curve with 2^36 | #E 45 | let curve = GoodCurve::new_odd( 46 | F!("31172306031375832341232376275243462303334845584808513005362718476441963632613"), 47 | F!("45508371059383884471556188660911097844526467659576498497548207627741160623272"), 48 | ); 49 | let coset_offset = Point::new( 50 | F!("105623886150579165427389078198493427091405550492761682382732004625374789850161"), 51 | F!("7709812624542158994629670452026922591039826164720902911013234773380889499231"), 52 | curve, 53 | ); 54 | let subgroup_generator = Point::new( 55 | F!("41293412487153066667050767300223451435019201659857889215769525847559135483332"), 56 | F!("73754924733368840065089190002333366411120578552679996887076912271884749237510"), 57 | curve, 58 | ); 59 | let subgroup_two_addicity = 36; 60 | 61 | // FFTree size is too large for our generator 62 | if log_n >= subgroup_two_addicity { 63 | return None; 64 | } 65 | 66 | // get a generator of a subgroup with order `n` 67 | let mut generator = subgroup_generator; 68 | for _ in 0..subgroup_two_addicity - log_n { 69 | generator += generator; 70 | } 71 | 72 | // generate the FFTree leaf nodes 73 | let mut leaves = vec![Self::zero(); n]; 74 | let mut acc = Point::zero(); 75 | for x in &mut leaves { 76 | *x = (coset_offset + acc).x; 77 | acc += generator; 78 | } 79 | 80 | let isogenies = find_isogeny_chain(generator); 81 | let rational_maps = isogenies.into_iter().map(|isogeny| isogeny.r).collect(); 82 | 83 | Some(FFTree::new(leaves, rational_maps)) 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::FftreeField; 90 | use super::Fp; 91 | use crate::FFTree; 92 | use crate::Moiety; 93 | use ark_poly::univariate::DensePolynomial; 94 | use ark_poly::DenseUVPolynomial; 95 | use ark_poly::Polynomial; 96 | use ark_serialize::CanonicalDeserialize; 97 | use ark_serialize::CanonicalSerialize; 98 | use rand::rngs::StdRng; 99 | use rand::SeedableRng; 100 | use std::sync::OnceLock; 101 | 102 | static FFTREE: OnceLock> = OnceLock::new(); 103 | 104 | fn get_fftree() -> &'static FFTree { 105 | FFTREE.get_or_init(|| Fp::build_fftree(64).unwrap()) 106 | } 107 | 108 | #[test] 109 | fn evaluates_polynomial() { 110 | let n = 64; 111 | let fftree = get_fftree(); 112 | let mut rng = StdRng::from_seed([1; 32]); 113 | let poly = DensePolynomial::rand(n - 1, &mut rng); 114 | let eval_domain = fftree.subtree_with_size(n).eval_domain(); 115 | 116 | let ecfft_evals = fftree.enter(&poly); 117 | 118 | let expected_evals: Vec = eval_domain.iter().map(|x| poly.evaluate(x)).collect(); 119 | assert_eq!(expected_evals, ecfft_evals); 120 | } 121 | 122 | #[test] 123 | fn extends_evaluations_from_s0_to_s1() { 124 | let n = 64; 125 | let fftree = get_fftree(); 126 | let eval_domain = fftree.subtree_with_size(n).eval_domain(); 127 | let mut rng = StdRng::from_seed([1; 32]); 128 | let poly = DensePolynomial::rand(n / 2 - 1, &mut rng); 129 | let (s0, s1): (Vec, Vec) = eval_domain.chunks(2).map(|s| (s[0], s[1])).unzip(); 130 | let s0_evals: Vec = s0.iter().map(|x| poly.evaluate(x)).collect(); 131 | 132 | let s1_evals_actual = fftree.extend(&s0_evals, Moiety::S1); 133 | 134 | let s1_evals_expected: Vec = s1.iter().map(|x| poly.evaluate(x)).collect(); 135 | assert_eq!(s1_evals_expected, s1_evals_actual) 136 | } 137 | 138 | #[test] 139 | fn extends_evaluations_from_s1_to_s0() { 140 | let n = 64; 141 | let fftree = get_fftree(); 142 | let eval_domain = fftree.subtree_with_size(n).eval_domain(); 143 | let mut rng = StdRng::from_seed([1; 32]); 144 | let poly = DensePolynomial::rand(n / 2 - 1, &mut rng); 145 | let (s0, s1): (Vec, Vec) = eval_domain.chunks(2).map(|c| (c[0], c[1])).unzip(); 146 | let s1_evals: Vec = s1.iter().map(|x| poly.evaluate(x)).collect(); 147 | 148 | let s0_evals_actual = fftree.extend(&s1_evals, Moiety::S0); 149 | 150 | let s0_evals_expected: Vec = s0.iter().map(|x| poly.evaluate(x)).collect(); 151 | assert_eq!(s0_evals_expected, s0_evals_actual) 152 | } 153 | 154 | #[test] 155 | fn deserialized_uncompressed_tree_works() { 156 | let n = 64; 157 | let fftree = get_fftree(); 158 | let mut rng = StdRng::from_seed([1; 32]); 159 | let poly = DensePolynomial::rand(n - 1, &mut rng); 160 | let eval_domain = fftree.subtree_with_size(n).eval_domain(); 161 | 162 | let mut fftree_bytes = Vec::new(); 163 | fftree.serialize_uncompressed(&mut fftree_bytes).unwrap(); 164 | let fftree = FFTree::deserialize_uncompressed(&*fftree_bytes).unwrap(); 165 | let ecfft_evals = fftree.enter(&poly); 166 | 167 | let expected_evals: Vec = eval_domain.iter().map(|x| poly.evaluate(x)).collect(); 168 | assert_eq!(expected_evals, ecfft_evals); 169 | } 170 | 171 | #[test] 172 | fn deserialized_compressed_tree_works() { 173 | let n = 64; 174 | let fftree = get_fftree(); 175 | let mut rng = StdRng::from_seed([1; 32]); 176 | let poly = DensePolynomial::rand(n - 1, &mut rng); 177 | let eval_domain = fftree.subtree_with_size(n).eval_domain(); 178 | 179 | let mut fftree_bytes = Vec::new(); 180 | fftree.serialize_compressed(&mut fftree_bytes).unwrap(); 181 | let fftree = FFTree::deserialize_compressed(&*fftree_bytes).unwrap(); 182 | let ecfft_evals = fftree.enter(&poly); 183 | 184 | let expected_evals: Vec = eval_domain.iter().map(|x| poly.evaluate(x)).collect(); 185 | assert_eq!(expected_evals, ecfft_evals); 186 | } 187 | } 188 | } 189 | 190 | pub mod m31 { 191 | use super::FFTree; 192 | use super::FftreeField; 193 | use super::Point; 194 | use crate::ec::build_ec_fftree; 195 | use crate::ec::ShortWeierstrassCurve; 196 | pub use ark_ff_optimized::fp31::Fp; 197 | 198 | impl FftreeField for Fp { 199 | fn build_fftree(n: usize) -> Option> { 200 | /// Supersingular curve with 2^31 | #E 201 | const CURVE: ShortWeierstrassCurve = ShortWeierstrassCurve::new(Fp(1), Fp(0)); 202 | const COSET_OFFSET: Point> = 203 | Point::new(Fp(1048755163), Fp(279503108), CURVE); 204 | const SUBGROUP_GENERATOR: Point> = 205 | Point::new(Fp(1273083559), Fp(804329170), CURVE); 206 | const SUBGORUP_TWO_ADDICITY: u32 = 28; 207 | 208 | build_ec_fftree( 209 | SUBGROUP_GENERATOR, 210 | 1 << SUBGORUP_TWO_ADDICITY, 211 | COSET_OFFSET, 212 | n, 213 | ) 214 | } 215 | } 216 | 217 | // TODO: there's a lot of repetition between field tests. Should implement macro 218 | // or loop at solutions to remove duplication of test logic. 219 | #[cfg(test)] 220 | mod tests { 221 | use super::Fp; 222 | use crate::fftree::FFTree; 223 | use crate::FftreeField; 224 | use ark_ff::One; 225 | use ark_ff::Zero; 226 | use ark_poly::univariate::DensePolynomial; 227 | use ark_poly::DenseUVPolynomial; 228 | use ark_poly::Polynomial; 229 | use rand::rngs::StdRng; 230 | use rand::SeedableRng; 231 | use std::sync::OnceLock; 232 | 233 | static FFTREE: OnceLock> = OnceLock::new(); 234 | 235 | fn get_fftree() -> &'static FFTree { 236 | FFTREE.get_or_init(|| Fp::build_fftree(64).unwrap()) 237 | } 238 | 239 | #[test] 240 | fn evaluates_polynomial() { 241 | let n = 64; 242 | let fftree = get_fftree(); 243 | let mut rng = StdRng::from_seed([1; 32]); 244 | let poly = DensePolynomial::rand(n - 1, &mut rng); 245 | let eval_domain = fftree.subtree_with_size(n).eval_domain(); 246 | 247 | let ecfft_evals = fftree.enter(&poly); 248 | 249 | let expected_evals: Vec = eval_domain.iter().map(|x| poly.evaluate(x)).collect(); 250 | assert_eq!(expected_evals, ecfft_evals); 251 | } 252 | 253 | #[test] 254 | fn interpolates_evaluations() { 255 | let fftree = get_fftree(); 256 | let one = Fp::one(); 257 | let zero = Fp::zero(); 258 | let coeffs: &[Fp] = &[one, one, Fp::from(5u8), zero, zero, one, zero, zero]; 259 | let evals = fftree.enter(coeffs); 260 | 261 | let exit_coeffs = fftree.exit(&evals); 262 | 263 | assert_eq!(coeffs, &exit_coeffs); 264 | } 265 | 266 | #[test] 267 | fn determines_degree() { 268 | let fftree = get_fftree(); 269 | let one = Fp::one(); 270 | let zero = Fp::zero(); 271 | let coeffs = &[one, one, one, zero, zero, one, zero, zero]; 272 | let evals = fftree.enter(coeffs); 273 | 274 | let degree = fftree.degree(&evals); 275 | 276 | let poly = DensePolynomial::from_coefficients_slice(coeffs); 277 | assert_eq!(poly.degree(), degree); 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::ec::Point; 2 | use crate::ec::WeierstrassCurve; 3 | use alloc::collections::BTreeMap; 4 | use alloc::vec; 5 | use alloc::vec::Vec; 6 | use ark_ff::Field; 7 | use ark_ff::PrimeField; 8 | use ark_ff::Zero; 9 | use ark_poly::univariate::DenseOrSparsePolynomial; 10 | use ark_poly::univariate::DensePolynomial; 11 | use ark_poly::DenseUVPolynomial; 12 | use ark_poly::Polynomial; 13 | use ark_serialize::CanonicalDeserialize; 14 | use ark_serialize::CanonicalSerialize; 15 | use core::ops::Deref; 16 | use core::ops::DerefMut; 17 | use core::ops::Mul; 18 | use num_bigint::BigUint; 19 | use num_integer::Integer; 20 | use rand::Rng; 21 | 22 | type Degree = usize; 23 | 24 | /// Finds all unique roots in the field F 25 | pub fn find_roots(poly: &DensePolynomial) -> Vec { 26 | let f = square_free_factors(poly); 27 | let ddf = distinct_degree_factors(&f); 28 | if let Some(d1) = ddf.get(&1) { 29 | let degree_1_factors = equal_degree_factorization(d1, 1); 30 | let mut roots: Vec = degree_1_factors 31 | .into_iter() 32 | .map(|factor| { 33 | // factor = x + c 34 | // root = -c 35 | assert_eq!(1, factor.degree()); 36 | -factor[0] 37 | }) 38 | .collect(); 39 | roots.sort(); 40 | roots 41 | } else { 42 | Vec::new() 43 | } 44 | } 45 | 46 | /// Returns a mapping of degrees d to the factors of the polynomial, f, 47 | /// such that each factor is the product of all monic irreducible factors 48 | /// of f of degree d. 49 | /// 50 | /// The input must be a squarefree polynomial. 51 | /// https://en.wikipedia.org/wiki/Factorization_of_polynomials_over_finite_fields 52 | pub(crate) fn distinct_degree_factors( 53 | f: &DensePolynomial, 54 | ) -> BTreeMap> { 55 | let x = DensePolynomial::from_coefficients_slice(&[F::zero(), F::one()]); 56 | let mut res = BTreeMap::new(); 57 | let mut f_star = f.clone(); 58 | let mut i = 1; 59 | while f_star.degree() >= 2 * i { 60 | // TODO: only works for prime fields. Won't work for extension fields 61 | let p: BigUint = F::BasePrimeField::MODULUS.into(); 62 | let xp = pow_mod(&x, p, &f_star); 63 | let xpi = pow_mod(&xp, i.into(), &f_star); 64 | let g = gcd(&f_star, &(&xpi - &x)); 65 | if g.degree() != 0 { 66 | f_star = &f_star / &g; 67 | let prev = res.insert(i, g); 68 | assert!(prev.is_none()); 69 | } 70 | i += 1; 71 | } 72 | if f_star.degree() != 0 { 73 | res.insert(f_star.degree(), f_star); 74 | } else if res.is_empty() { 75 | res.insert(1, f_star); 76 | } 77 | res 78 | } 79 | 80 | /// Takes as input a polynomial which is known the be the product of irreducible 81 | /// polynomials of degree d. https://en.wikipedia.org/wiki/Factorization_of_polynomials_over_finite_fields 82 | fn equal_degree_factorization( 83 | f: &DensePolynomial, 84 | d: Degree, 85 | ) -> Vec> { 86 | if d == 0 { 87 | return vec![]; 88 | } 89 | let f = f.clone(); 90 | let n = f.degree(); 91 | let r = n / d; 92 | let one = DensePolynomial::from_coefficients_slice(&[F::one()]); 93 | let mut factors = vec![f.clone()]; 94 | let mut rng = rand::thread_rng(); 95 | while factors.len() < r { 96 | let h = rand_poly(n - 1, &mut rng); 97 | // TODO: only works for prime fields. Won't work for extension fields 98 | let p: BigUint = F::BasePrimeField::MODULUS.into().pow(d as u32); 99 | let g = &pow_mod(&h, (p - 1u32) / 2u32, &f) - &one; 100 | factors = factors 101 | .into_iter() 102 | .flat_map(|factor| { 103 | let gcd_res = gcd(&g, &factor); 104 | if gcd_res.degree() != 0 && gcd_res != factor { 105 | vec![&factor / &gcd_res, gcd_res] 106 | } else { 107 | vec![factor] 108 | } 109 | }) 110 | .collect(); 111 | } 112 | factors 113 | } 114 | 115 | /// Returns the polynomial's square free factorization. 116 | /// https://mathrefresher.blogspot.com/2009/01/greatest-common-divisor-of-polynomial.html 117 | /// https://en.wikipedia.org/wiki/Factorization_of_polynomials_over_finite_fields 118 | pub(crate) fn square_free_factors(f: &DensePolynomial) -> DensePolynomial { 119 | let f_prime = derivative(f); 120 | if f_prime.is_zero() { 121 | // f is already square free 122 | f.clone() 123 | } else { 124 | // divide out gcd(f, f') to get square free factors 125 | f / &gcd(f, &f_prime) 126 | } 127 | } 128 | 129 | /// Returns the GCD of two polynomials. 130 | /// The GCD is normalized to a monic polynomial. 131 | /// https://math.stackexchange.com/a/3009888/393151 132 | pub fn gcd(a: &DensePolynomial, b: &DensePolynomial) -> DensePolynomial { 133 | if a.is_zero() { 134 | DensePolynomial::zero() 135 | } else if b.is_zero() { 136 | let leading_coeff = a.last().unwrap(); 137 | a * leading_coeff.inverse().unwrap() 138 | } else { 139 | gcd(b, &div_rem(a, b)) 140 | } 141 | } 142 | 143 | /// Computes the extended GCD (a * s + b * t = gcd) 144 | /// The GCD is normalized to a monic polynomial. 145 | /// Output is of the form (s, t, gcd) where s and t are Bezout coefficients 146 | /// https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm 147 | pub fn xgcd( 148 | a: &DensePolynomial, 149 | b: &DensePolynomial, 150 | ) -> (DensePolynomial, DensePolynomial, DensePolynomial) { 151 | let zero = DensePolynomial::zero(); 152 | let one = DensePolynomial::from_coefficients_vec(vec![F::one()]); 153 | let mut s = zero.clone(); 154 | let mut old_s = one; 155 | let mut r = b.clone(); 156 | let mut old_r = a.clone(); 157 | 158 | while !r.is_zero() { 159 | let (quotient, _) = 160 | DenseOrSparsePolynomial::divide_with_q_and_r(&(&old_r).into(), &(&r).into()).unwrap(); 161 | (r, old_r) = (&old_r - "ient.naive_mul(&r), r); 162 | (s, old_s) = (&old_s - "ient.naive_mul(&s), s); 163 | } 164 | 165 | let bezout_t = if !b.is_zero() { 166 | let numerator = (&old_r - &old_s.naive_mul(a)).into(); 167 | let denominator = b.clone().into(); 168 | DenseOrSparsePolynomial::divide_with_q_and_r(&numerator, &denominator) 169 | .unwrap() 170 | .0 171 | } else { 172 | zero 173 | }; 174 | 175 | // Normalize the GCD to a monic polynomial 176 | let leading_coeff_inv = old_r.last().map_or(F::one(), |c| c.inverse().unwrap()); 177 | ( 178 | &old_s * leading_coeff_inv, 179 | &bezout_t * leading_coeff_inv, 180 | &old_r * leading_coeff_inv, 181 | ) 182 | } 183 | 184 | /// Returns numerator % denominator 185 | pub fn div_rem( 186 | numerator: &DensePolynomial, 187 | denominator: &DensePolynomial, 188 | ) -> DensePolynomial { 189 | let numerator = DenseOrSparsePolynomial::from(numerator); 190 | let denominator = DenseOrSparsePolynomial::from(denominator); 191 | numerator.divide_with_q_and_r(&denominator).unwrap().1 192 | } 193 | 194 | /// Calculates (a^exp) % modulus 195 | pub fn pow_mod( 196 | a: &DensePolynomial, 197 | mut exp: BigUint, 198 | modulus: &DensePolynomial, 199 | ) -> DensePolynomial { 200 | let one = DensePolynomial::from_coefficients_slice(&[F::one()]); 201 | let mut res = one; 202 | let mut acc = a.clone(); 203 | while !exp.is_zero() { 204 | if exp.is_odd() { 205 | res = div_rem(&res.naive_mul(&acc), modulus); 206 | } 207 | acc = div_rem(&acc.naive_mul(&acc), modulus); 208 | exp >>= 1; 209 | } 210 | res 211 | } 212 | 213 | /// Calculates and returns the derivative f' of f 214 | fn derivative(f: &DensePolynomial) -> DensePolynomial { 215 | DensePolynomial::from_coefficients_vec( 216 | f.iter() 217 | .enumerate() 218 | .skip(1) 219 | .map(|(pow, coeff)| F::from(pow as u32) * coeff) 220 | .collect(), 221 | ) 222 | } 223 | 224 | fn rand_poly(d: Degree, rng: &mut R) -> DensePolynomial { 225 | DensePolynomial::from_coefficients_vec((0..=d).map(|_| F::rand(rng)).collect()) 226 | } 227 | 228 | #[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] 229 | pub struct BinaryTree(Vec); 230 | 231 | impl BinaryTree { 232 | pub fn depth(&self) -> u32 { 233 | if self.0.is_empty() { 234 | 0 235 | } else { 236 | self.0.len().ilog2() 237 | } 238 | } 239 | 240 | pub fn leaves(&self) -> &[T] { 241 | &self.0[self.0.len() / 2..] 242 | } 243 | 244 | pub fn num_layers(&self) -> u32 { 245 | self.0.len().ilog2() 246 | } 247 | 248 | pub fn get_layer(&self, i: usize) -> &[T] { 249 | let num_leaves = self.0.len() / 2; 250 | let layer_size = num_leaves >> i; 251 | &self.0[layer_size..layer_size * 2] 252 | } 253 | 254 | /// Returns all layers of a binary tree. 255 | /// ```text 256 | /// a <- layers[d-1] = [a] 257 | /// / \ 258 | /// b c <- layers[d-2] = [b, c] 259 | /// / \ / \ 260 | /// ... ... ... 261 | /// / \ / \ 262 | /// w x ... y z <- layers[0] = [w, x, ..., y, z] 263 | /// ``` 264 | pub fn get_layers_mut(&mut self) -> Vec<&mut [T]> { 265 | let mut res = Vec::new(); 266 | (0..self.depth()).rev().fold(&mut *self.0, |rem, i| { 267 | let (lhs, rhs) = rem.split_at_mut(1 << i); 268 | res.push(rhs); 269 | lhs 270 | }); 271 | res 272 | } 273 | 274 | /// Returns all layers of a binary tree. 275 | /// ```text 276 | /// a <- layers[d-1] = [a] 277 | /// / \ 278 | /// b c <- layers[d-2] = [b, c] 279 | /// / \ / \ 280 | /// ... ... ... 281 | /// / \ / \ 282 | /// w x ... y z <- layers[0] = [w, x, ..., y, z] 283 | /// ``` 284 | pub fn get_layers(&self) -> Vec<&[T]> { 285 | let mut res = Vec::new(); 286 | (0..self.depth()).rev().fold(&*self.0, |rem, i| { 287 | let (lhs, rhs) = rem.split_at(1 << i); 288 | res.push(rhs); 289 | lhs 290 | }); 291 | res 292 | } 293 | } 294 | 295 | impl From> for BinaryTree { 296 | fn from(tree: Vec) -> Self { 297 | let n = tree.len(); 298 | assert!(n.is_power_of_two() || n == 0); 299 | Self(tree) 300 | } 301 | } 302 | 303 | impl Deref for BinaryTree { 304 | type Target = as Deref>::Target; 305 | 306 | fn deref(&self) -> &Self::Target { 307 | &self.0 308 | } 309 | } 310 | 311 | impl DerefMut for BinaryTree { 312 | fn deref_mut(&mut self) -> &mut Self::Target { 313 | &mut self.0 314 | } 315 | } 316 | 317 | #[derive(Clone, Copy, Default, Debug, CanonicalSerialize, CanonicalDeserialize)] 318 | pub struct Mat2x2(pub [[F; 2]; 2]); 319 | 320 | impl Mat2x2 { 321 | pub fn identity() -> Mat2x2 { 322 | Self([[F::one(), F::zero()], [F::zero(), F::one()]]) 323 | } 324 | 325 | pub fn inverse(&self) -> Option { 326 | let det_inv = self.determinant().inverse()?; 327 | Some(Self([ 328 | [self.0[1][1] * det_inv, -self.0[0][1] * det_inv], 329 | [-self.0[1][0] * det_inv, self.0[0][0] * det_inv], 330 | ])) 331 | } 332 | 333 | pub fn determinant(&self) -> F { 334 | self.0[0][0] * self.0[1][1] - self.0[0][1] * self.0[1][0] 335 | } 336 | } 337 | 338 | impl Mul<&[F; 2]> for &Mat2x2 { 339 | type Output = [F; 2]; 340 | 341 | fn mul(self, rhs: &[F; 2]) -> Self::Output { 342 | [ 343 | self.0[0][0] * rhs[0] + self.0[0][1] * rhs[1], 344 | self.0[1][0] * rhs[0] + self.0[1][1] * rhs[1], 345 | ] 346 | } 347 | } 348 | 349 | /// Returns `true` if `F` is an odd order field, otherwise returns `false`. 350 | pub fn is_odd() -> bool { 351 | F::characteristic()[0].is_odd() 352 | } 353 | 354 | /// Returns the two adicity of a point i.e. returns `k` such that 2^k * p = 0. 355 | /// Returns `None` if `p` isn't a point of order 2^k. 356 | pub fn two_adicity(p: Point) -> Option { 357 | let mut acc = p; 358 | for i in 0..2048 { 359 | if acc.is_zero() { 360 | return Some(i); 361 | } 362 | acc += acc; 363 | } 364 | None 365 | } 366 | 367 | #[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] 368 | pub struct RationalMap { 369 | pub numerator: DensePolynomial, 370 | pub denominator: DensePolynomial, 371 | } 372 | 373 | impl RationalMap { 374 | pub fn new(numerator: &[F], denominator: &[F]) -> Self { 375 | let numerator = DensePolynomial::from_coefficients_slice(numerator); 376 | let denominator = DensePolynomial::from_coefficients_slice(denominator); 377 | Self { 378 | numerator, 379 | denominator, 380 | } 381 | } 382 | 383 | pub fn map(&self, x: &F) -> Option { 384 | Some(self.numerator.evaluate(x) * self.denominator.evaluate(x).inverse()?) 385 | } 386 | 387 | pub fn zero() -> Self { 388 | Self::new(&[], &[F::one()]) 389 | } 390 | } 391 | 392 | #[cfg(test)] 393 | mod tests { 394 | use super::*; 395 | use ark_ff::One; 396 | use ark_ff_optimized::fp31::Fp; 397 | use rand::rngs::StdRng; 398 | use rand::SeedableRng; 399 | 400 | #[test] 401 | fn finds_roots_of_cubic() { 402 | // = x^3 + 16*x 403 | let f = DensePolynomial::from_coefficients_slice(&[ 404 | Fp::zero(), 405 | -Fp::from(4u8), 406 | Fp::zero(), 407 | Fp::one(), 408 | ]); 409 | 410 | let actual = find_roots(&f); 411 | 412 | let expected = vec![Fp::zero(), Fp::from(2u32), Fp::from(2147483645u32)]; 413 | assert_eq!(expected, actual); 414 | } 415 | 416 | #[test] 417 | fn test_xgcd() { 418 | let mut rng = StdRng::seed_from_u64(0); 419 | let a = DensePolynomial::::rand(5, &mut rng); 420 | let b = DensePolynomial::::rand(5, &mut rng); 421 | 422 | let (x, y, gcd) = xgcd(&a, &b); 423 | 424 | let ax = a.naive_mul(&x); 425 | let by = b.naive_mul(&y); 426 | assert_eq!(&ax + &by, gcd); 427 | } 428 | 429 | #[test] 430 | fn test_xgcd_with_linear_gcd() { 431 | // a = (x + 1)(x - 1) = x^2 - 1 432 | // b = (x + 1)(x + 0) = x^2 + x + 1 433 | let a = DensePolynomial::from_coefficients_vec(vec![-Fp::one(), Fp::zero(), Fp::one()]); 434 | let b = DensePolynomial::from_coefficients_vec(vec![Fp::one(), Fp::one(), Fp::one()]); 435 | 436 | let (s, t, gcd) = xgcd(&a, &b); 437 | 438 | let a_s = a.naive_mul(&s); 439 | let b_t = b.naive_mul(&t); 440 | assert_eq!(&a_s + &b_t, gcd); 441 | // TODO: should be a factor 442 | // assert_eq!(&[Fp::one(), Fp::one()], &*gcd); 443 | } 444 | 445 | #[test] 446 | fn test_xgcd_with_zero_polynomial() { 447 | let mut rng = StdRng::seed_from_u64(0); 448 | let zero = DensePolynomial::::zero(); 449 | let b = DensePolynomial::::rand(5, &mut rng); 450 | 451 | let (s, t, gcd) = xgcd(&zero, &b); 452 | 453 | assert_eq!(s, zero, "x should be zero polynomial"); 454 | assert_eq!(b.naive_mul(&t), gcd); 455 | assert!(!gcd.is_zero()); 456 | } 457 | } 458 | --------------------------------------------------------------------------------