diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6cdec81..2794275 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -23,5 +23,7 @@ jobs: run: cargo build --verbose - name: Run tests run: cargo test --verbose + env: + RUSTFLAGS: "-D warnings" - name: Doc builds run: cargo ourdoc diff --git a/ec/src/curve_weierstrass.rs b/ec/src/curve_weierstrass.rs index ba5f0f0..88bd070 100644 --- a/ec/src/curve_weierstrass.rs +++ b/ec/src/curve_weierstrass.rs @@ -8,17 +8,56 @@ //! y^2 + a_1 xy + a_3 y = x^3 + a_2 x^2 + a_4 x + a_6 //! $$ //! -//! This form is valid in *any* characteristic, including characteristic $2$. +//! This form is valid in *any* characteristic (provided the +//! discriminant is nonzero), including characteristic $2$. This is +//! enabled via the constructor [`WeierstrassCurve::new`]. //! //! # Short Weierstrass specialisation //! -//! When $\mathrm{char}(F) \ne 2, 3$ the curve can be brought to the simpler +//! When $\mathrm{char}(F) \ne 2, 3$ the curve can be brought to the +//! simpler //! //! $$ -//! y^2 = x^3 + ax + b \quad (a_1 = a_2 = a_3 = 0,\; a_4 = a,\; a_6 = b) +//! y^2 = x^3 + ax + b //! $$ //! -//! via the convenience constructor [`WeierstrassCurve::new_short`]. +//! so that $a_1 = a_2 = a_3 = 0$, $a_4 = a$ and a_6 = b$. This is +//! enabled via the constructor [`WeierstrassCurve::new_short`]. +//! +//! # Examples +//! +//! ``` +//! use crypto_bigint::{Uint, const_prime_monty_params}; +//! use ec::curve_weierstrass::WeierstrassCurve; +//! use fp::field_ops::FieldOps; +//! use fp::fp_element::FpElement; +//! +//! const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); +//! type F19 = FpElement; +//! +//! fn fp(n: u64) -> F19 { +//! F19::from_u64(n) +//! } +//! +//! /* y^2 = x^3 + 2*x + 3 */ +//! let c = WeierstrassCurve::new_short(fp(2), fp(3)); +//! let c_too = WeierstrassCurve::new(fp(0), fp(0), fp(0), fp(2), fp(3)); +//! +//! /* They're the same curve */ +//! assert_eq!(c, c_too); +//! +//! /* The a-invariants are correct */ +//! assert_eq!(c.a4, fp(2)); +//! assert_eq!(c.a6, fp(3)); +//! +//! /* Curve is smooth */ +//! assert!(!bool::from(c.discriminant().is_zero())); +//! +//! /* (1,5) is a point on c */ +//! assert!(c.contains(&fp(1), &fp(5))); +//! +//! /* (0,0) is not a point on c */ +//! assert!(!c.contains(&fp(0), &fp(0))); use core::fmt; use fp::field_ops::{FieldOps, FieldRandom}; @@ -38,15 +77,15 @@ use crate::point_weierstrass::AffinePoint; /// case simply has $a_1 = a_2 = a_3 = 0$. #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct WeierstrassCurve { - /// a-invariant + /// $a_1$-invariant pub a1: F, - /// a-invariant + /// $a_2$-invariant pub a2: F, - /// a-invariant + /// $a_3$-invariant pub a3: F, - /// a-invariant + /// $a_4$-invariant pub a4: F, - /// a-invariant + /// $a_6$-invariant pub a6: F, } @@ -130,6 +169,7 @@ ref_field_impl! { // ------------------------------------------------------------------- /// Returns the five $a$-invariants $[a_1, a_2, a_3, a_4, a_6]$. + #[allow(clippy::clone_on_copy)] // want to signal that the a_invariants are copied pub fn a_invariants(&self) -> [F; 5] { [ self.a1.clone(), diff --git a/ec/tests/curves_legendre_tests.rs b/ec/tests/curves_legendre_tests.rs index 7f17be0..a9ba59a 100644 --- a/ec/tests/curves_legendre_tests.rs +++ b/ec/tests/curves_legendre_tests.rs @@ -1,4 +1,4 @@ -use crypto_bigint::{Uint, const_prime_monty_params}; +use crypto_bigint::{const_prime_monty_params, Uint}; use ec::curve_legendre::LegendreCurve; use ec::curve_ops::Curve; use ec::point_legendre::LegendrePoint; @@ -119,6 +119,7 @@ fn legendre_short_weierstrass_coordinate_shift_works() { let jc = c.j_invariant(); let w = c.to_short_weierstrass(); let jw = w.j_invariant(); + assert_eq!(jc, jw); let p = LegendrePoint::new(fp(2), fp(6)); assert!(c.is_on_curve(&p)); diff --git a/ec/tests/hessian_tests.rs b/ec/tests/hessian_tests.rs index 23be843..c2752fa 100644 --- a/ec/tests/hessian_tests.rs +++ b/ec/tests/hessian_tests.rs @@ -1,6 +1,6 @@ //! Integration tests for generalized Hessian curves. -use crypto_bigint::{Uint, const_prime_monty_params}; +use crypto_bigint::{const_prime_monty_params, Uint}; use ec::curve_hessian::HessianCurve; use ec::curve_ops::Curve; @@ -181,6 +181,8 @@ fn hessian_weierstrass_birational_roundtrip() { let wc = curve.to_weierstrass_curve_with_zeta(zeta).unwrap(); let jw = wc.j_invariant(); + assert_eq!(jc, jw); + let wp = curve .map_point_to_weierstrass_with_zeta(&p, zeta) .expect("forward birational map"); diff --git a/ec/tests/twisted_hessian_tests.rs b/ec/tests/twisted_hessian_tests.rs index d11fed3..bf10c62 100644 --- a/ec/tests/twisted_hessian_tests.rs +++ b/ec/tests/twisted_hessian_tests.rs @@ -1,10 +1,9 @@ //! Integration tests for twisted Hessian curves. -use crypto_bigint::{Uint, const_prime_monty_params}; +use crypto_bigint::{const_prime_monty_params, Uint}; use ec::curve_ops::Curve; use ec::curve_twisted_hessian::TwistedHessianCurve; -use ec::point_ops::{PointAdd, PointOps}; use ec::point_twisted_hessian::TwistedHessianPoint; use fp::field_ops::FieldOps; use fp::fp_element::FpElement; diff --git a/fp/src/_doctest_support.rs b/fp/src/_doctest_support.rs new file mode 100644 index 0000000..6a3238d --- /dev/null +++ b/fp/src/_doctest_support.rs @@ -0,0 +1,34 @@ +//! Hidded modules only used for testing in the documentation + +pub mod _doctest_fp_ext { + use crate::field_ops::FieldOps; + use crate::fp_element::FpElement; + use crate::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; + use crypto_bigint::{const_prime_monty_params, Uint}; + + const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); + pub type Fp19 = FpElement; + + pub struct QuadPoly; + pub struct TSQuad; + + impl IrreduciblePoly for QuadPoly { + fn modulus() -> [Fp19; 2] { + [Fp19::one(), Fp19::zero()] + } + } + + impl TonelliShanksConstants for TSQuad { + const ORDER: Uint<1> = Uint::<1>::from_u64(360); + const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180); + const S: u64 = 3; + const T: Uint<1> = Uint::<1>::from_u64(45); + const PROJENATOR_EXP: Uint<1> = Uint::<1>::from_u64(22); + const TWOSM1: Uint<1> = Uint::<1>::from_u64(4); + fn root_of_unity() -> [FpElement; 2] { + [Fp19::from_u64(3), Fp19::from_u64(3)] + } + } + + pub type F19_2 = FpExt; +} diff --git a/fp/src/fp_element.rs b/fp/src/fp_element.rs index 32b80d8..60e6b00 100644 --- a/fp/src/fp_element.rs +++ b/fp/src/fp_element.rs @@ -6,8 +6,8 @@ use std::fmt; use crate::field_ops::{FieldFromRepr, FieldOps, FieldRandom}; use crypto_bigint::{ - NonZero, RandomMod, Uint, modular::{ConstMontyForm, ConstPrimeMontyParams}, + NonZero, RandomMod, Uint, }; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; diff --git a/fp/src/fp_ext.rs b/fp/src/fp_ext.rs index 875df5e..65913eb 100644 --- a/fp/src/fp_ext.rs +++ b/fp/src/fp_ext.rs @@ -25,10 +25,6 @@ //! const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); //! type Fp19 = FpElement; //! -//! fn fp(n: u64) -> Fp19 { -//! Fp19::from_u64(n) -//! } -//! //! /* Setput the irreducible polynomial */ //! struct QuadPoly; //! impl IrreduciblePoly for QuadPoly { @@ -61,9 +57,9 @@ //! type F19_2 = FpExt; //! //! /* Some standard tests */ -//! let a = F19_2::new([fp(3), fp(2)]); -//! let ainv = F19_2::new([fp(9), fp(13)]); -//! let asquare = F19_2::new([fp(5), fp(12)]); +//! let a = F19_2::from_u64_vec([3, 2]); +//! let ainv = F19_2::from_u64_vec([9, 13]); +//! let asquare = F19_2::from_u64_vec([5, 12]); //! assert_eq!(a.invert().unwrap(), ainv); //! assert_eq!(a.square(), asquare); //! assert_eq!(asquare.sqrt().unwrap().square(), asquare); @@ -118,12 +114,16 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; /// /// The polynomial $f(x) = x^2 + 1$ is irreducible over $\mathbb{F}_{19}$. /// ``` -/// # use crypto_bigint::{const_prime_monty_params, Uint}; -/// # use fp::fp_element::FpElement; -/// # use fp::field_ops::FieldOps; -/// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; +/// use crypto_bigint::{const_prime_monty_params, Uint}; +/// use fp::fp_element::FpElement; +/// use fp::field_ops::FieldOps; +/// use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; +/// +/// /* Setup the underlying prime field */ /// const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); /// type Fp19 = FpElement; +/// +/// /* Define an irreducible polynomial */ /// struct MyQuadPoly; /// impl IrreduciblePoly for MyQuadPoly { /// fn modulus() -> [FpElement; 2] { @@ -137,11 +137,15 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; /// /// Note that $f(x) = x^3 + 17$ is irreducible over $\mathbb{F}_{19}$. /// ``` -/// # use crypto_bigint::{const_prime_monty_params, Uint}; -/// # use fp::fp_element::FpElement; -/// # use fp::field_ops::FieldOps; -/// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; +/// use crypto_bigint::{const_prime_monty_params, Uint}; +/// use fp::fp_element::FpElement; +/// use fp::field_ops::FieldOps; +/// use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; +/// +/// /* Setup the underlying prime field */ /// const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); +/// +/// /* Define an irreducible polynomial */ /// type Fp19 = FpElement; /// struct MyCubicPoly; /// impl IrreduciblePoly for MyCubicPoly { @@ -182,11 +186,15 @@ implement for a new field /// # Example: $\mathbb{F}_{19^2}$ /// Note that we have a factorisation $19^2 - 1 = 2^3 \cdot 45$ /// ``` -/// # use crypto_bigint::{const_prime_monty_params, Uint}; -/// # use fp::fp_element::FpElement; -/// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; +/// use crypto_bigint::{const_prime_monty_params, Uint}; +/// use fp::fp_element::FpElement; +/// use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; +/// +/// /* Setup the underlying prime field */ /// const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); /// type Fp19 = FpElement; +/// +/// /* Write out the precomputed Tonelli--Shanks constants */ /// struct TSQuad; /// impl TonelliShanksConstants for TSQuad { /// // p^2 - 1 @@ -270,18 +278,20 @@ where /// use fp::fp_element::FpElement; /// use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; /// +/// /* Setup the underlying prime field */ /// const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); /// type Fp19 = FpElement; /// +/// /* Setup the irreducible poynomial */ /// struct QuadPoly; -/// struct TSQuad; -/// /// impl IrreduciblePoly for QuadPoly { /// fn modulus() -> [Fp19; 2] { /// [Fp19::one(), Fp19::zero()] /// } /// } /// +/// /* Write out the precomputed Tonelli--Shanks constants */ +/// struct TSQuad; /// impl TonelliShanksConstants for TSQuad { /// const ORDER: Uint<1> = Uint::<1>::from_u64(360); /// const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180); @@ -336,35 +346,7 @@ where /// # Examples /// /// ``` - /// # use crypto_bigint::{const_prime_monty_params, Uint}; - /// # use fp::field_ops::{FieldOps, FieldRandom}; - /// # use fp::fp_element::FpElement; - /// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; - /// # const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); - /// # type Fp19 = FpElement; - /// # - /// # struct QuadPoly; - /// # struct TSQuad; - /// # - /// # impl IrreduciblePoly for QuadPoly { - /// # fn modulus() -> [Fp19; 2] { - /// # [Fp19::one(), Fp19::zero()] - /// # } - /// # } - /// # - /// # impl TonelliShanksConstants for TSQuad { - /// # const ORDER: Uint<1> = Uint::<1>::from_u64(360); - /// # const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180); - /// # const S: u64 = 3; - /// # const T: Uint<1> = Uint::<1>::from_u64(45); - /// # const PROJENATOR_EXP: Uint<1> = Uint::<1>::from_u64(22); - /// # const TWOSM1: Uint<1> = Uint::<1>::from_u64(4); - /// # fn root_of_unity() -> [FpElement; 2] { - /// # [Fp19::from_u64(3), Fp19::from_u64(3)] - /// # } - /// # } - /// # - /// # type F19_2 = FpExt; + /// # use fp::_doctest_support::_doctest_fp_ext::*; /// let a = F19_2::new([Fp19::from_u64(3), Fp19::from_u64(5)]); // a = 3 + 5 x /// ``` pub fn new(coeffs: [FpElement; M]) -> Self { @@ -391,40 +373,12 @@ where /// # Examples /// /// ``` - /// # use crypto_bigint::{const_prime_monty_params, Uint}; - /// # use fp::field_ops::{FieldOps, FieldRandom}; - /// # use fp::fp_element::FpElement; - /// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; - /// # const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); - /// # type Fp19 = FpElement; - /// # - /// # struct QuadPoly; - /// # struct TSQuad; - /// # - /// # impl IrreduciblePoly for QuadPoly { - /// # fn modulus() -> [Fp19; 2] { - /// # [Fp19::one(), Fp19::zero()] - /// # } - /// # } - /// # - /// # impl TonelliShanksConstants for TSQuad { - /// # const ORDER: Uint<1> = Uint::<1>::from_u64(360); - /// # const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180); - /// # const S: u64 = 3; - /// # const T: Uint<1> = Uint::<1>::from_u64(45); - /// # const PROJENATOR_EXP: Uint<1> = Uint::<1>::from_u64(22); - /// # const TWOSM1: Uint<1> = Uint::<1>::from_u64(4); - /// # fn root_of_unity() -> [FpElement; 2] { - /// # [Fp19::from_u64(3), Fp19::from_u64(3)] - /// # } - /// # } - /// # - /// # type F19_2 = FpExt; + /// # use fp::_doctest_support::_doctest_fp_ext::*; /// let a = F19_2::new([Fp19::from_u64(3), Fp19::from_u64(5)]); - /// let b = F19_2::from_u64([3, 5]); + /// let b = F19_2::from_u64_vec([3, 5]); /// assert_eq!(a, b); /// ``` - pub fn from_u64(coeffs: [u64; M]) -> Self { + pub fn from_u64_vec(coeffs: [u64; M]) -> Self { let coeffs_fp = std::array::from_fn(|i| FpElement::from_u64(coeffs[i])); Self::new(coeffs_fp) } @@ -446,36 +400,9 @@ where /// # Examples /// /// ``` - /// # use crypto_bigint::{const_prime_monty_params, Uint}; - /// # use fp::field_ops::{FieldOps, FieldRandom}; - /// # use fp::fp_element::FpElement; - /// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; - /// # const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); - /// # type Fp19 = FpElement; - /// # - /// # struct QuadPoly; - /// # struct TSQuad; - /// # - /// # impl IrreduciblePoly for QuadPoly { - /// # fn modulus() -> [Fp19; 2] { - /// # [Fp19::one(), Fp19::zero()] - /// # } - /// # } - /// # - /// # impl TonelliShanksConstants for TSQuad { - /// # const ORDER: Uint<1> = Uint::<1>::from_u64(360); - /// # const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180); - /// # const S: u64 = 3; - /// # const T: Uint<1> = Uint::<1>::from_u64(45); - /// # const PROJENATOR_EXP: Uint<1> = Uint::<1>::from_u64(22); - /// # const TWOSM1: Uint<1> = Uint::<1>::from_u64(4); - /// # fn root_of_unity() -> [FpElement; 2] { - /// # [Fp19::from_u64(3), Fp19::from_u64(3)] - /// # } - /// # } - /// # - /// # type F19_2 = FpExt; - /// let a = F19_2::from_u64([3, 5]); + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use crypto_bigint::Uint; // unclear why this is needed, it should be imported above + /// let a = F19_2::from_u64_vec([3, 5]); /// let b = F19_2::from_uint([Uint::<1>::from_u64(3), Uint::<1>::from_u64(5)]); /// assert_eq!(a, b); /// ``` @@ -499,37 +426,9 @@ where /// # Examples /// /// ``` - /// # use crypto_bigint::{const_prime_monty_params, Uint}; - /// # use fp::field_ops::{FieldOps, FieldRandom}; - /// # use fp::fp_element::FpElement; - /// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; - /// # const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); - /// # type Fp19 = FpElement; - /// # - /// # struct QuadPoly; - /// # struct TSQuad; - /// # - /// # impl IrreduciblePoly for QuadPoly { - /// # fn modulus() -> [Fp19; 2] { - /// # [Fp19::one(), Fp19::zero()] - /// # } - /// # } - /// # - /// # impl TonelliShanksConstants for TSQuad { - /// # const ORDER: Uint<1> = Uint::<1>::from_u64(360); - /// # const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180); - /// # const S: u64 = 3; - /// # const T: Uint<1> = Uint::<1>::from_u64(45); - /// # const PROJENATOR_EXP: Uint<1> = Uint::<1>::from_u64(22); - /// # const TWOSM1: Uint<1> = Uint::<1>::from_u64(4); - /// # fn root_of_unity() -> [FpElement; 2] { - /// # [Fp19::from_u64(3), Fp19::from_u64(3)] - /// # } - /// # } - /// # - /// # type F19_2 = FpExt; + /// # use fp::_doctest_support::_doctest_fp_ext::*; /// let a = F19_2::from_base(Fp19::from_u64(3)); - /// let b = F19_2::from_u64([3, 0]); + /// let b = F19_2::from_u64_vec([3, 0]); /// assert_eq!(a, b); // a = 3 + 0 x /// ``` pub fn from_base(a: FpElement) -> Self { @@ -553,36 +452,8 @@ where /// # Examples /// /// ``` - /// # use crypto_bigint::{const_prime_monty_params, Uint}; - /// # use fp::field_ops::{FieldOps, FieldRandom}; - /// # use fp::fp_element::FpElement; - /// # use fp::fp_ext::{FpExt, IrreduciblePoly, TonelliShanksConstants}; - /// # const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); - /// # type Fp19 = FpElement; - /// # - /// # struct QuadPoly; - /// # struct TSQuad; - /// # - /// # impl IrreduciblePoly for QuadPoly { - /// # fn modulus() -> [Fp19; 2] { - /// # [Fp19::one(), Fp19::zero()] - /// # } - /// # } - /// # - /// # impl TonelliShanksConstants for TSQuad { - /// # const ORDER: Uint<1> = Uint::<1>::from_u64(360); - /// # const HALF_ORDER: Uint<1> = Uint::<1>::from_u64(180); - /// # const S: u64 = 3; - /// # const T: Uint<1> = Uint::<1>::from_u64(45); - /// # const PROJENATOR_EXP: Uint<1> = Uint::<1>::from_u64(22); - /// # const TWOSM1: Uint<1> = Uint::<1>::from_u64(4); - /// # fn root_of_unity() -> [FpElement; 2] { - /// # [Fp19::from_u64(3), Fp19::from_u64(3)] - /// # } - /// # } - /// # - /// # type F19_2 = FpExt; - /// let a = F19_2::from_u64([3, 5]); + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// let a = F19_2::from_u64_vec([3, 5]); /// assert_eq!(*a.coeff(0), Fp19::from_u64(3)); /// assert_eq!(*a.coeff(1), Fp19::from_u64(5)); /// ``` @@ -1166,8 +1037,31 @@ where })) } + /// /// Schoolbook multiplication followed by reduction modulo $f(x)$. /// + /// # Arguments + /// + /// * `rhs` - An element of $\mathbb{F}\_{p^M}$ to multiply by + /// (type: `&Self`). + /// + /// # Returns + /// + /// The product `self * rhs` (type: `Self`). + /// + /// # Examples + /// + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 5]); + /// let b = F19_2::from_u64_vec([7, 11]); + /// let prod = F19_2::from_u64_vec([4, 11]); + /// assert_eq!(a.mul(&b), prod); + /// ``` + /// + /// # Note + /// /// Product has degree $\leq 2M−2$, then each high-degree term is /// replaced using $x^M \equiv −\sum_j \texttt{modulus}\[j\] x^j$ /// until all degrees are below $M$. @@ -1190,6 +1084,23 @@ where /// Inversion via polynomial extended GCD. /// + /// # Returns + /// + /// Either the inverse of `self` or `none` if `self == 0` (type: + /// `CtOption`). + /// + /// # Examples + /// + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 5]); + /// let inv = F19_2::from_u64_vec([4, 6]); + /// assert_eq!(a.invert().unwrap(), inv); + /// ``` + /// + /// # Note + /// /// Finds $s$ such that $\texttt{self} \times s \equiv 1 \pmod{f}$ /// by computing $\gcd(\texttt{self}, f) = g$ (a nonzero constant /// if `self` is nonzero) and then returning $s g^{-1} \pmod{f}$. @@ -1218,21 +1129,60 @@ where // --- Frobenius ---------------------------------------------------------- - /// `φ_p(a) = a^p` — the p-power Frobenius endomorphism. + /// Computes the $p$-power Frobenius endomorphism $\phi_p(a) = a^p$. + /// + /// # Returns /// - /// Computed via square-and-multiply using the characteristic p retrieved - /// from the base field. + /// The $p$-power of `self` (type: Self) + /// + /// # Examples + /// + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 5]); + /// let frob_a = F19_2::from_u64_vec([3, 14]); + /// assert_eq!(a.frobenius(), frob_a); + /// ``` + /// + /// # Note + /// + /// Computed via square-and-multiply using the characteristic $p$ + /// retrieved from the base field. fn frobenius(&self) -> Self { let p = FpElement::::characteristic(); - ::pow(self, &p) + // can use pow_vartime since the exponent is always the same + ::pow_vartime(self, &p) } // --- Norm and trace ----------------------------------------------------- - /// `N_{Fp^M/Fp}(a) = ∏_{i=0}^{M-1} φ_p^i(a)` — product of all Galois conjugates. + /// Computes the norm of `self` to $\mathbb{F}\_{p}$ embedded as an element + /// of $\mathbb{F}\_{p^M}$. + /// + /// As a reminder the norm is defined as the product over all the + /// Galois conjugates that is + /// $$ N\_{\mathbb{F}\_{p^M}/\mathbb{F}\_p}(a) = \prod\_{i=0}^{M-1} \phi\_p^i(a) $$ + /// + /// # Returns + /// + /// The norm of self (type: `Self`). + /// + /// # Examples + /// + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 5]); + /// let nm_a = F19_2::from_u64_vec([15, 0]); + /// assert_eq!(a.norm(), nm_a); + /// ``` /// - /// The result lies in Fp (all higher coefficients are 0), but is returned - /// embedded in Fp^M for uniformity with the [`FieldOps`] signature. + /// # Note + /// + /// The result lies in $\mathbb{F}\_p$ (all higher coefficients + /// are 0), but is returned embedded in $\mathbb{F}\_{p^M}$ for + /// uniformity with the [`FieldOps`] signature. fn norm(&self) -> Self { let mut result = self.clone(); let mut conj = self.frobenius(); @@ -1243,9 +1193,32 @@ where result } - /// `Tr_{Fp^M/Fp}(a) = Σ_{i=0}^{M-1} φ_p^i(a)` — sum of all Galois conjugates. + /// Computes the trace of `self` to $\mathbb{F}\_{p}$ embedded as an element + /// of $\mathbb{F}\_{p^M}$. + /// + /// As a reminder the trace is defined as the sum over all the + /// Galois conjugates that is + /// $$ \mathrm{tr}\_{\mathbb{F}\_{p^M}/\mathbb{F}\_p}(a) = \sum\_{i=0}^{M-1} \phi\_p^i(a) $$ + /// + /// # Returns /// - /// Like `norm`, the result lies in Fp but is returned embedded in Fp^M. + /// The trace of self (type: `Self`). + /// + /// # Examples + /// + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 5]); + /// let tr_a = F19_2::from_u64_vec([6, 0]); + /// assert_eq!(a.trace(), tr_a); + /// ``` + /// + /// # Note + /// + /// The result lies in $\mathbb{F}\_p$ (all higher coefficients + /// are 0), but is returned embedded in $\mathbb{F}\_{p^M}$ for + /// uniformity with the [`FieldOps`] signature. fn trace(&self) -> Self { let mut result = self.clone(); let mut conj = self.frobenius(); @@ -1258,25 +1231,37 @@ where // --- Square root -------------------------------------------------------- - /// Tonelli--Shanks squareroot algorithm + /// Computes the square root of `self` /// - /// Implementation of the Tonelli--Shanks square root algorithm. Requires - /// only a factorisation as $p^M - 1 = 2^K N$ so can compute this at - /// compile time by truncating zeros. + /// # Returns /// - /// # Arguments + /// $\sqrt{\texttt{self}}$ a choice of squareroot (type: `Self`) /// - /// * `&self` - An element of Fp^M (type: &self) + /// # Examples /// - /// # Returns + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 11]); + /// let sqrt_a = a.sqrt().unwrap(); + /// assert_eq!(sqrt_a.mul(&sqrt_a), a); + /// ``` /// - /// `self^(1/2)` a choice of squareroot (type: Self) + /// # Note + /// + /// This is an implementation of the Tonelli--Shanks squareroot + /// algorithm. + /// + /// # TODO + /// + /// - [ ] Generate the addition chain for the projenator exponent + /// this is constant time since the projenator exponent is + /// always(!) the same (for a fixed field). fn sqrt(&self) -> CtOption { let mut x = *self; let exp = TSCONSTS::PROJENATOR_EXP; let exp_limbs = exp.as_limbs().map(|limb| limb.0); - // TODO: generate the addition chain for this specific constant - // this is constant time since exp_limbs is always(!) the same + let w = x.pow_vartime(&exp_limbs); ts_loop(&mut x, &w); CtOption::new( @@ -1285,19 +1270,32 @@ where ) } - /// Inverse and sqrt in one exponentiation + /// Returns both the inverse and sqrt of `self` /// - /// Computes the inverse and squareroot of `self` in one - /// exponentiation using the tricks in Scott's article + /// # Returns /// - /// # Arguments + /// $(1/\texttt{self}, \sqrt{\texttt{self}})$ which is + /// `self.invert()` and `self.sqrt()` (type: `CtOption`, + /// `CtOption`) /// - /// * `&self` - An element of Fp^M (type: &self) + /// # Examples /// - /// # Returns + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 11]); + /// let (inv_a, sqrt_a) = a.inverse_and_sqrt(); + /// let inv_a = inv_a.unwrap(); + /// let sqrt_a = sqrt_a.unwrap(); + /// assert!(bool::from(inv_a.mul(&a).is_one())); + /// assert_eq!(sqrt_a.mul(&sqrt_a), a); + /// ``` /// - /// `(myinv, mysqrt)` which is `self.invert()` and `self.sqrt()` - /// (type: `CtOption`, `CtOption`) + /// # Note + /// + /// Computes the inverse and squareroot of `self` in one + /// exponentiation using the tricks in Scott's article + /// . fn inverse_and_sqrt(&self) -> (CtOption, CtOption) { let is_invertible = !self.is_zero(); @@ -1315,7 +1313,6 @@ where let e1_limbs = e1.as_limbs().map(|limb| limb.0); // Compute x^(2^(S - 1) - 1) * (x * w^4)^(2^(S - 1)) - // pow_vartime? let first_term = self.pow_vartime(&e1_limbs); let xw4 = self * &w.pow_vartime(&[4 as u64]); @@ -1332,36 +1329,65 @@ where ) } - /// Inverse of squareroot of `self` in 1 exponentiation + /// Computes the inverse and squareroot of `self`. /// - /// Computes 1/sqrt(self) using the trick from Mike Scott's - /// "Tricks of the trade" article Section 2 - /// + /// # Returns /// - /// # Arguments + /// $1 / \sqrt{\texttt{self}}$, the inverse of the squareroot of + /// `self` (type: `CtOption`) /// - /// * `&self` - Description of &self (type: self) + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 11]); + /// let inv_sqrt_a = a.inv_sqrt(); + /// let inv_sqrt_a = inv_sqrt_a.unwrap(); + /// assert_eq!(inv_sqrt_a.mul(&inv_sqrt_a), a.invert().unwrap()); + /// ``` /// - /// # Returns + /// # Note /// - /// The inverse of the squareroot of `self` (type: `CtOption`) + /// Computes $1/\sqrt{\texttt{self}$ using the trick from Scott's + /// "Tricks of the trade" article Section 2 + /// fn inv_sqrt(&self) -> CtOption { let (inv, sqrt) = self.inverse_and_sqrt(); inv.and_then(|a| sqrt.map(|b| &a * &b)) } - /// Inverse of `self` and squareroot of `rhs` in 1 exponentiation + /// Inverse of `self` and squareroot of `rhs`. /// - /// Computes `1/self` and `rhs.sqrt()` simulaineously using the - /// trick from Mike Scott's "Tricks of the trade" article Section - /// 2 + /// # Arguments + /// + /// * `rhs` - element of $\mathbb{F}\_{p^M}$ (type: `&Self`) /// /// # Returns /// - /// The inverse of `self` and square root fo `rhs`. Theq former is - /// none if and only if `self` is nonzero and the latter is not - /// none if and only if there exists a squareroot of `rhs` in FpM - /// (type: (`CtOption`, `CtOption`)) + /// The pair $(1 / \texttt{self}, \sqrt{\texttt{rhs}})$. The + /// former is none if and only if `self` is nonzero and the latter + /// is none if and only if there is no squareroot of `rhs` in + /// $\mathbb{F}\_{p^M}$ (type: (`CtOption`, + /// `CtOption`)) + /// + /// # Examples + /// + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 11]); + /// let b = F19_2::from_u64_vec([2, 11]); + /// let (inv_a, sqrt_b) = a.invertme_sqrtother(&b); + /// let inv_a = inv_a.unwrap(); + /// let sqrt_b = sqrt_b.unwrap(); + /// assert!(bool::from(inv_a.mul(&a).is_one())); + /// assert_eq!(sqrt_b.square(), b); + /// ``` + /// + /// # Note + /// + /// Computes `1/self` and `rhs.sqrt()` simulaineously using the + /// trick from Scott's article Section 2 + /// fn invertme_sqrtother(&self, rhs: &Self) -> (CtOption, CtOption) { let is_invertible = !self.is_zero(); @@ -1386,20 +1412,33 @@ where /// Computes the squareroot of a ratio `self/rhs` /// - /// Computes `sqrt(self/rhs)` in one exponentiation using the - /// trick from Mike Scott's "Tricks of the trade" article Section - /// 2 - /// /// # Arguments - /// - /// * `&self` - Element of FpM (type: self) - /// * `rhs` - Element of FpM (type: &Self) + /// * `rhs` - Element of $\mathbb{F}\_{p^M}$ (type: `&Self`) /// /// # Returns /// - /// The squareroot of the ratio `self/rhs` is not none if and only - /// if `rhs` is invertible and the ratio has an FpM squareroot - /// (type: `CtOption`) + /// $\sqrt{\texttt{self}/\texttt{rhs}}$, the squareroot of the + /// ratio which is not none if and only if `rhs` is invertible and + /// the ratio has an $\mathbb{F}\_{p^M}$ rational squareroot (type: + /// `CtOption`) + /// + /// # Examples + /// + /// ``` + /// # use fp::_doctest_support::_doctest_fp_ext::*; + /// # use fp::field_ops::FieldOps; + /// let a = F19_2::from_u64_vec([3, 11]); + /// let b = F19_2::from_u64_vec([2, 11]); + /// let sqrt_a_over_b = a.sqrt_ratio(&b); + /// let sqrt_a_over_b = sqrt_a_over_b.unwrap(); + /// assert_eq!(sqrt_a_over_b.square().mul(&b), a); + /// ``` + /// + /// # Note + /// + /// Computes $\sqrt{\texttt{self}/\texttt{rhs}}$ in one + /// exponentiation using the trick from Scott's "Tricks of the + /// trade" article Section 2 fn sqrt_ratio(&self, rhs: &Self) -> CtOption { let x = self * &(&(self.square()) * rhs); let (myinv, mysqrt) = x.inverse_and_sqrt(); @@ -1414,20 +1453,25 @@ where CtOption::new(ans_value, ans_is_some) } - /// a is a QR in Fp^M iff a^{(p^M-1)/2} = 1. + /// Implements the "Legendre symbol" which is 1 if and only if + /// `self` is a quadratic residue in $\mathbb{F}\_{p^M}$. /// - /// Implements the "Legendre symbol" which is 1 if and only if we - /// have a quadratic residue in FpM - /// WARNING: Not constant time if `self` is zero + /// As a reminder the "Legendre symbol" for $a \in \mathbb{F}\_{p^M}$ is defined as + /// $$ \begin{cases} 0 & \text{ if $a = 0$,} \\\\ 1 & \text{if $a$ + /// is a QR,} \\\\ -1 & \text{if $a$ is not a QR.} \end{cases} $$ /// - /// # Arguments + /// # Returns /// - /// * `&self` - Element of FpM (type: self) + /// The Legendre symbol of `self` (type: `i8`) /// - /// # Returns + /// # WARNING + /// + /// Not constant time if `self` is zero + /// + /// # Note /// - /// Either `0` if `&self` is `0`, `1` if `&self` is a QR or `-1` if - /// `&self` is not a QR. (type: i8) + /// Implemented using the fact that $a$ is a QR in + /// $\mathbb{F}\_{p^M}$ if and only if $a^{(p^M-1)/2} = 1$. fn legendre(&self) -> i8 { let exp = TSCONSTS::HALF_ORDER; let exp_limbs = exp.as_limbs().map(|limb| limb.0); diff --git a/fp/src/lib.rs b/fp/src/lib.rs index c787936..045d797 100644 --- a/fp/src/lib.rs +++ b/fp/src/lib.rs @@ -22,3 +22,6 @@ pub mod field_ops; pub mod fp_element; /// Prime-field extension towers and related helper traits. pub mod fp_ext; + +#[doc(hidden)] +pub mod _doctest_support; diff --git a/isogeny/src/isogeny_ops.rs b/isogeny/src/isogeny_ops.rs index d6760a1..7f1ba3d 100644 --- a/isogeny/src/isogeny_ops.rs +++ b/isogeny/src/isogeny_ops.rs @@ -1,14 +1,12 @@ +#![allow(dead_code)] // TODO: Make the code not dead... //! Generic isogeny abstraction. //! //! # Overview //! -//! An isogeny is a morphism of elliptic curves -//! -//! ```text -//! φ : E -> E' -//! ``` -//! -//! that is also a group homomorphism. +//! An isogeny is a finite morphism of curves +//! $$\phi : E -> E'$$ +//! for which $\phi(O\_E) = O\_{E'}$. This implies that $\phi$ is also +//! a group homomorphism. //! //! This trait is meant to capture the **minimal common interface** //! that any concrete isogeny implementation should expose: @@ -28,7 +26,7 @@ //! For example: //! - the domain could use affine Weierstrass points, //! - the codomain could use projective points, -//! - or an x-only Montgomery/Kummer representation. +//! - or an $x$-only Montgomery/Kummer representation. //! //! So we keep both associated types explicit instead of assuming that the //! same point type is used on both sides. @@ -41,24 +39,23 @@ use subtle::Choice; /// Generic interface for an isogeny. /// /// An implementation of this trait represents a map -/// -/// ```text -/// φ : E -> E' -/// ``` -/// +/// $$\phi : E \to E'$$ /// together with the information needed to evaluate it on points. /// +/// # Note +/// /// The trait is intentionally small: it only describes the common /// functionality of isogenies, without forcing a particular internal -/// representation (kernel-based, Vélu formulas, x-only formulas, chains, etc.). +/// representation (kernel-based, Vélu formulas, $x$-only formulas, +/// chains, etc.). pub trait IsogenyOps: Clone { /// Base field over which both the domain and codomain curves are defined. type BaseField: FieldOps; - /// Domain curve `E` of the isogeny `φ : E -> E'`. + /// Domain curve $E$ of the isogeny $\phi : E \to E'$. type DomainCurve: Curve; - /// Codomain curve `E'` of the isogeny `φ : E -> E'`. + /// Codomain curve $E'$ of the isogeny $\phi : E \to E'$. type CodomainCurve: Curve; /// Native point representation used when evaluating points on the domain. @@ -75,16 +72,13 @@ pub trait IsogenyOps: Clone { /// Return the degree of the isogeny. /// - /// For a composition `ψ ∘ φ`, the degree should be - /// - /// ```text - /// deg(ψ ∘ φ) = deg(ψ) deg(φ). - /// ``` + /// For a composition $\phi \circ \phi$, the degree should be + /// $$ \deg(\psi \circ \phi) = \deg(\psi) $deg(\phi)$$ fn degree(&self) -> u64; /// Evaluate the isogeny at a point of the domain curve. /// - /// If `φ : E -> E'` and `P ∈ E`, this returns `φ(P) ∈ E'`. + /// If $\phi : E \to E'$ and $P \in E$, this returns $\phi(P) \in E'$. fn evaluate(&self, p: &Self::DomainPoint) -> Self::CodomainPoint; /// Return whether the isogeny is separable. @@ -97,17 +91,9 @@ pub trait IsogenyOps: Clone { /// Extension trait for isogenies whose dual isogeny is available. /// /// If -/// -/// ```text -/// φ : E -> E' -/// ``` -/// +/// $$\phi : E \to E'$$ /// is an isogeny, then its dual -/// -/// ```text -/// φ^∨ : E' -> E -/// ``` -/// +/// $$\phi^\vee : E \to E'$$ /// goes in the opposite direction, has the same degree, and satisfies /// the standard duality relations. pub trait DualIsogenyOps: IsogenyOps { @@ -116,12 +102,12 @@ pub trait DualIsogenyOps: IsogenyOps { /// Note how domain and codomain are swapped, and likewise the point /// representations on the source and target. type Dual: IsogenyOps< - BaseField = Self::BaseField, - DomainCurve = Self::CodomainCurve, - CodomainCurve = Self::DomainCurve, - DomainPoint = Self::CodomainPoint, - CodomainPoint = Self::DomainPoint, - >; + BaseField = Self::BaseField, + DomainCurve = Self::CodomainCurve, + CodomainCurve = Self::DomainCurve, + DomainPoint = Self::CodomainPoint, + CodomainPoint = Self::DomainPoint, + >; /// Return the dual isogeny. fn dual(&self) -> Self::Dual; @@ -130,23 +116,19 @@ pub trait DualIsogenyOps: IsogenyOps { /// A wrapper representing the composition of two isogenies. /// /// If -/// -/// ```text -/// first : E -> E' -/// second : E' -> E'', -/// ``` +/// $$\texttt{first} : E \to E'$$ +/// $$\texttt{second} : E' \to E''$$ /// /// then `CompositeIsogeny { first, second }` represents /// -/// ```text -/// second ∘ first : E -> E''. -/// ``` +/// $$ \texttt{second} \circ \texttt{first} : E -> E''.$$ /// -/// # Why a wrapper? +/// # Note /// -/// This is a very convenient generic representation of composition: -/// instead of trying to "simplify" the composite into a single special -/// formula, we just store the two maps and evaluate them one after the other. +/// Why a wrapper? This is a very convenient generic representation of +/// composition: instead of trying to "simplify" the composite into a +/// single special formula, we just store the two maps and evaluate +/// them one after the other. /// /// This is often the cleanest first design, especially when different /// concrete isogeny families may be composed together. @@ -175,18 +157,18 @@ where // - domain curve of `second` = codomain curve of `first`, // - domain point type of `second` = codomain point type of `first`. I2: IsogenyOps< - BaseField = I1::BaseField, - DomainCurve = I1::CodomainCurve, - DomainPoint = I1::CodomainPoint, - >, + BaseField = I1::BaseField, + DomainCurve = I1::CodomainCurve, + DomainPoint = I1::CodomainPoint, + >, { /// The composite map is still defined over the same base field. type BaseField = I1::BaseField; - /// The domain of `second ∘ first` is the domain of `first`. + /// The domain of $\texttt{second} \circ \texttt{first}$ is the domain of `first`. type DomainCurve = I1::DomainCurve; - /// The codomain of `second ∘ first` is the codomain of `second`. + /// The codomain of \texttt{second} \circ \texttt{first} is the codomain of `second`. type CodomainCurve = I2::CodomainCurve; /// Points fed into the composite are points of the domain of `first`.