diff --git a/.github/disabled/cross.yml b/.github/disabled/cross.yml new file mode 100644 index 00000000..40e89f40 --- /dev/null +++ b/.github/disabled/cross.yml @@ -0,0 +1,44 @@ +name: Cross + +on: + push: + branches: [ '**' ] + pull_request: + branches: [ '**' ] + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: '-D warnings' + +jobs: + + test-cross: + name: Test + strategy: + matrix: + include: + # ARM32 + - target: armv7-unknown-linux-gnueabihf + rust: stable + + # ARM64 + - target: aarch64-unknown-linux-gnu + rust: stable + + # PPC32 + - target: powerpc-unknown-linux-gnu + rust: stable + + # TODO: We only test x/ed/curve for cross as derive is platform specifics + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: ${{ matrix.deps }} + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - uses: RustCrypto/actions/cross-install@master + - run: cross test -p curve25519-dalek --release --target ${{ matrix.target }} + - run: cross test -p ed25519-dalek --release --target ${{ matrix.target }} + - run: cross test -p x25519-dalek --release --target ${{ matrix.target }} diff --git a/.github/workflows/ed25519-dalek.yml b/.github/disabled/ed25519-dalek.yml similarity index 100% rename from .github/workflows/ed25519-dalek.yml rename to .github/disabled/ed25519-dalek.yml diff --git a/.github/disabled/workspace.yml b/.github/disabled/workspace.yml new file mode 100644 index 00000000..b8e44dc5 --- /dev/null +++ b/.github/disabled/workspace.yml @@ -0,0 +1,111 @@ +name: All + +on: + push: + branches: [ '**' ] + pull_request: + branches: [ '**' ] + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: '-D warnings' + +jobs: + test-stable: + name: Test 32/64 bit stable + runs-on: ubuntu-latest + strategy: + matrix: + include: + # 32-bit target + - target: i686-unknown-linux-gnu + deps: sudo apt update && sudo apt install gcc-multilib + + # 64-bit target + - target: x86_64-unknown-linux-gnu + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - run: rustup target add ${{ matrix.target }} + - run: ${{ matrix.deps }} + - run: cargo test --target ${{ matrix.target }} --no-default-features + - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --all-features + + test-nightly: + name: Test Nightly + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo test + + bench: + name: Check that benchmarks compile + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - name: Build u32 bench + env: + RUSTFLAGS: '--cfg curve25519_dalek_bits="32"' + run: cargo build --benches + - name: Build u64 bench + env: + RUSTFLAGS: '--cfg curve25519_dalek_bits="64"' + run: cargo build --benches + - name: Build default (host native) bench + run: cargo build --benches + + # Test no_std with serial (default) + build-nostd-serial: + name: Build serial on no_std target (thumbv7em-none-eabi) + runs-on: ubuntu-latest + strategy: + matrix: + include: + - crate: curve25519-dalek + - crate: ed25519-dalek + - crate: x25519-dalek + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + targets: thumbv7em-none-eabi + - uses: taiki-e/install-action@cargo-hack + # No default features build + - name: no_std / no feat ${{ matrix.crate }} + run: cargo build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --no-default-features + - name: no_std / cargo hack ${{ matrix.crate }} + run: cargo hack build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --each-feature --exclude-features default,std,getrandom + + clippy: + name: Check that clippy is happy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@1.73.0 + with: + components: clippy + - run: cargo clippy --target x86_64-unknown-linux-gnu --all-features + + rustfmt: + name: Check formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all -- --check + + doc: + name: Check docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - run: cargo doc --all-features diff --git a/.github/workflows/x25519-dalek.yml b/.github/disabled/x25519-dalek.yml similarity index 100% rename from .github/workflows/x25519-dalek.yml rename to .github/disabled/x25519-dalek.yml diff --git a/.github/workflows/cross.yml b/.github/workflows/cross.yml index 40e89f40..1ac710a8 100644 --- a/.github/workflows/cross.yml +++ b/.github/workflows/cross.yml @@ -32,13 +32,11 @@ jobs: # TODO: We only test x/ed/curve for cross as derive is platform specifics runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: ${{ matrix.deps }} - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} targets: ${{ matrix.target }} - uses: RustCrypto/actions/cross-install@master - - run: cross test -p curve25519-dalek --release --target ${{ matrix.target }} - - run: cross test -p ed25519-dalek --release --target ${{ matrix.target }} - - run: cross test -p x25519-dalek --release --target ${{ matrix.target }} + - run: cross test -p curve25519-elligator2 --release --target ${{ matrix.target }} diff --git a/.github/workflows/curve25519-dalek.yml b/.github/workflows/curve25519-elligator2.yml similarity index 89% rename from .github/workflows/curve25519-dalek.yml rename to .github/workflows/curve25519-elligator2.yml index 1fb13aa3..f2673fc1 100644 --- a/.github/workflows/curve25519-dalek.yml +++ b/.github/workflows/curve25519-elligator2.yml @@ -4,17 +4,17 @@ on: push: branches: [ '**' ] paths: - - 'curve25519-dalek/**' - - '.github/workflows/curve25519-dalek.yml' + - 'curve25519-elligator2/**' + - '.github/workflows/curve25519-elligator2.yml' pull_request: branches: [ '**' ] paths: - - 'curve25519-dalek/**' - - '.github/workflows/curve25519-dalek.yml' + - 'curve25519-elligator2/**' + - '.github/workflows/curve25519-elligator2.yml' defaults: run: - working-directory: curve25519-dalek + working-directory: curve25519-elligator2 env: CARGO_TERM_COLOR: always @@ -35,7 +35,7 @@ jobs: # 64-bit target - target: x86_64-unknown-linux-gnu steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: rustup target add ${{ matrix.target }} - run: ${{ matrix.deps }} @@ -50,9 +50,9 @@ jobs: strategy: matrix: include: - - crate: curve25519-dalek + - crate: curve25519-elligator2 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -81,7 +81,7 @@ jobs: # 64-bit target - target: x86_64-unknown-linux-gnu steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: rustup target add ${{ matrix.target }} - run: ${{ matrix.deps }} @@ -93,7 +93,7 @@ jobs: name: Test Build Script runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -104,7 +104,7 @@ jobs: name: Test simd backend (nightly) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - env: # This will: @@ -119,7 +119,7 @@ jobs: name: Test simd backend (stable) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - env: # This will run AVX2-specific tests and run all of the normal tests @@ -133,7 +133,7 @@ jobs: name: Current MSRV is 1.60.0 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Re-resolve Cargo.lock with minimal versions - uses: dtolnay/rust-toolchain@nightly - run: cargo update -Z minimal-versions diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index b8e44dc5..0af52b68 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -11,51 +11,51 @@ env: RUSTFLAGS: '-D warnings' jobs: - test-stable: - name: Test 32/64 bit stable - runs-on: ubuntu-latest - strategy: - matrix: - include: - # 32-bit target - - target: i686-unknown-linux-gnu - deps: sudo apt update && sudo apt install gcc-multilib - - # 64-bit target - - target: x86_64-unknown-linux-gnu - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - run: rustup target add ${{ matrix.target }} - - run: ${{ matrix.deps }} - - run: cargo test --target ${{ matrix.target }} --no-default-features - - run: cargo test --target ${{ matrix.target }} - - run: cargo test --target ${{ matrix.target }} --all-features - - test-nightly: - name: Test Nightly - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - - run: cargo test - - bench: - name: Check that benchmarks compile - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - name: Build u32 bench - env: - RUSTFLAGS: '--cfg curve25519_dalek_bits="32"' - run: cargo build --benches - - name: Build u64 bench - env: - RUSTFLAGS: '--cfg curve25519_dalek_bits="64"' - run: cargo build --benches - - name: Build default (host native) bench - run: cargo build --benches + # test-stable: + # name: Test 32/64 bit stable + # runs-on: ubuntu-latest + # strategy: + # matrix: + # include: + # # 32-bit target + # - target: i686-unknown-linux-gnu + # deps: sudo apt update && sudo apt install gcc-multilib + # + # # 64-bit target + # - target: x86_64-unknown-linux-gnu + # steps: + # - uses: actions/checkout@v3 + # - uses: dtolnay/rust-toolchain@stable + # - run: rustup target add ${{ matrix.target }} + # - run: ${{ matrix.deps }} + # - run: cargo test --target ${{ matrix.target }} --no-default-features + # - run: cargo test --target ${{ matrix.target }} + # - run: cargo test --target ${{ matrix.target }} --all-features + # + # test-nightly: + # name: Test Nightly + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - uses: dtolnay/rust-toolchain@nightly + # - run: cargo test + # + # bench: + # name: Check that benchmarks compile + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - uses: dtolnay/rust-toolchain@stable + # - name: Build u32 bench + # env: + # RUSTFLAGS: '--cfg curve25519_dalek_bits="32"' + # run: cargo build --benches + # - name: Build u64 bench + # env: + # RUSTFLAGS: '--cfg curve25519_dalek_bits="64"' + # run: cargo build --benches + # - name: Build default (host native) bench + # run: cargo build --benches # Test no_std with serial (default) build-nostd-serial: @@ -64,9 +64,7 @@ jobs: strategy: matrix: include: - - crate: curve25519-dalek - - crate: ed25519-dalek - - crate: x25519-dalek + - crate: curve25519-elligator2 steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master @@ -88,7 +86,7 @@ jobs: - uses: dtolnay/rust-toolchain@1.73.0 with: components: clippy - - run: cargo clippy --target x86_64-unknown-linux-gnu --all-features + - run: cargo clippy --target x86_64-unknown-linux-gnu --all-features -p curve25519-elligator2 rustfmt: name: Check formatting @@ -98,7 +96,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - - run: cargo fmt --all -- --check + - run: cargo fmt --all -p curve25519-elligator2 -- --check doc: name: Check docs @@ -108,4 +106,4 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: toolchain: stable - - run: cargo doc --all-features + - run: cargo doc --all-features -p curve25519-elligator2 diff --git a/Cargo.toml b/Cargo.toml index a891c670..b73b39b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "curve25519-dalek", + "curve25519-elligator2", "curve25519-dalek-derive", "ed25519-dalek", "x25519-dalek" diff --git a/README.md b/README.md index aced3739..ab6c2685 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ # Dalek elliptic curve cryptography +## THIS IS A FORK FOR CONVENIENCE + +This fork contains an implementation of `elligator2` that is awaiting potential +inclusion in the mainstream curve25519-dalek crates. This crate exists for +use in the interim period. + + This repo contains pure-Rust crates for elliptic curve cryptography: | Crate | Description | Crates.io | Docs | CI | diff --git a/curve25519-elligator2/CHANGELOG.md b/curve25519-elligator2/CHANGELOG.md new file mode 100644 index 00000000..a4c8452c --- /dev/null +++ b/curve25519-elligator2/CHANGELOG.md @@ -0,0 +1,263 @@ +# Changelog + +Entries are listed in reverse chronological order per undeprecated +major series. + +## 4.x series + +### 4.1.2 + +* Fix nightly SIMD build + +### 4.1.1 + +* Mark `constants::BASEPOINT_ORDER` deprecated from pub API +* Add implementation for `PrimeFieldBits`, behind the `group-bits` feature flag. + +### 4.1.0 + +* Add arbitrary integer multiplication with `MontgomeryPoint::mul_bits_be` +* Add implementations of the `ff` and `group` traits, behind the `group` feature flag +* Adapt to new types introduced in `fiat-crypto` 0.2 in `fiat` backend +* Fix `no_std` for `fiat` backend +* Mark `Scalar::clamp_integer` as `#[must_use]` +* Various documentation fixes + +### 4.0.0 + +#### Breaking changes + +* Update the MSRV from 1.41 to 1.60 +* Provide SemVer policy +* Make `digest` an optional feature +* Make `rand_core` an optional feature +* Remove `std` feature flag +* Remove `nightly` feature flag +* Automatic serial backend selection between `u32` and `u64` over the default `u32` +* Backend `simd` is now automatically selected over `serial` when a supported CPU is detected +* Backend override is now via cfg(curve25519_dalek_backend) over additive features +* Provide override to select `u32` or `u64` backend via cfg(curve25519_dalek_bits) +* Replace methods `Scalar::{zero, one}` with constants `Scalar::{ZERO, ONE}` +* Deprecate `EdwardsPoint::hash_from_bytes` and rename it `EdwardsPoint::nonspec_map_to_curve` +* Require including a new trait, `use curve25519_dalek::traits::BasepointTable` + whenever using `EdwardsBasepointTable` or `RistrettoBasepointTable` +* `Scalar::from_canonical_bytes` now returns `CtOption` +* `Scalar::is_canonical` now returns `Choice` +* Remove `Scalar::from_bytes_clamped` and `Scalar::reduce` +* Deprecate and feature-gate `Scalar::from_bits` behind `legacy_compatibility` + +#### Other changes + +* Add `EdwardsPoint::{mul_base, mul_base_clamped}`, `MontgomeryPoint::{mul_base, mul_base_clamped}`, and `BasepointTable::mul_base_clamped` +* Add `precomputed-tables` feature +* Update Maintenance Policies for SemVer +* Migrate documentation to docs.rs hosted +* Fix backend documentation generation +* Fix panic when `Ristretto::double_and_compress_batch` receives the identity point +* Remove `byteorder` dependency +* Update the `criterion` dependency to 0.4.0 +* Include README.md into crate Documentation +* Update the `rand_core` dependency version and the `rand` dev-dependency + version. +* Relax the `zeroize` dependency to `^1` +* Update the edition from 2015 to 2021 + +## 3.x series + +### 3.2.0 + +* Add support for getting the identity element for the Montgomery + form of curve25519, which is useful in certain protocols for + checking contributory behaviour in derivation of shared secrets. + +### 3.1.2 + +* Revert a commit which mistakenly removed support for `zeroize` traits + for some point types, as well as elligator2 support for Edwards points. + +### 3.1.1 + +* Fix documentation builds on nightly due to syntax changes to + `#![cfg_attr(feature = "nightly", doc = include_str!("../README.md"))]`. + +### 3.1.0 + +* Add support for the Elligator2 encoding for Edwards points. +* Add two optional formally-verified field arithmetic backends which + use the Fiat Crypto project's Rust code, which is generated from + proofs of functional correctness checked by the Coq theorem proving + system. +* Add support for additional sizes of precomputed tables for basepoint + scalar multiplication. +* Fix an unused import. +* Add support for using the `zeroize` traits with all point types. + Note that points are not automatically zeroized on Drop, but that + consumers of `curve25519-dalek` should call these methods manually + when needed. + +### 3.0.3 + +* Fix documentation builds on nightly due to syntax changes to + `#![cfg_attr(feature = "nightly", doc = include_str!("../README.md"))]`. + +### 3.0.2 + +* Multiple documentation typo fixes. +* Fixes to make using `alloc`+`no_std` possible for stable Rust. + +### 3.0.1 + +* Update the optional `packed-simd` dependency to rely on a newer, + maintained version of the `packed-simd-2` crate. + +### 3.0.0 + +#### Breaking changes + +* Update the `digest` dependency to `0.9`. This requires a major version + because the `digest` traits are part of the public API, but there are + otherwise no changes to the API. + +## 2.x series + +### 2.1.3 + +* Fix documentation builds on nightly due to syntax changes to + `#![fg_attr(feature = "nightly", doc = include_str!("../README.md"))]`. + +### 2.1.2 + +* Multiple documentation typo fixes. +* Fix `alloc` feature working with stable rust. + +### 2.1.1 + +* Update the optional `packed-simd` dependency to rely on a newer, + maintained version of the `packed-simd-2` crate. + +### 2.1.0 + +* Make `Scalar::from_bits` a `const fn`, allowing its use in `const` contexts. + +### 2.0.0 + +The only significant change is the data model change to the `serde` feature; +besides the `rand_core` version bump, there are no other user-visible changes. + +#### Breaking changes + +* Fix a data modeling error in the `serde` feature pointed out by Trevor Perrin + which caused points and scalars to be serialized with length fields rather + than as fixed-size 32-byte arrays. This is a breaking change, but it fixes + compatibility with `serde-json` and ensures that the `serde-bincode` encoding + matches the conventional encoding for X/Ed25519. +* Update `rand_core` to `0.5`, allowing use with new `rand` versions. + +#### Other changes + +* Switch from `clear_on_drop` to `zeroize` (by Tony Arcieri). +* Require `subtle = ^2.2.1` and remove the note advising nightly Rust, which is + no longer required as of that version of `subtle`. See the `subtle` + changelog for more details. +* Update `README.md` for `2.x` series. +* Remove the `build.rs` hack which loaded the entire crate into its own + `build.rs` to generate constants, and keep the constants in the source code. + +## 1.x series + +### 1.2.6 + +* Fixes to make using alloc+no_std possible for stable Rust. + +### 1.2.5 + +* Update the optional `packed-simd` dependency to rely on a newer, + maintained version of the `packed-simd-2` crate. + +### 1.2.4 + +* Specify a semver bound for `clear_on_drop` rather than an exact version, + addressing an issue where changes to inline assembly in rustc prevented + `clear_on_drop` from working without an update. + +### 1.2.3 + +* Fix an issue identified by a Quarkslab audit (and Jack Grigg), where manually + constructing unreduced `Scalar` values, as needed for X/Ed25519, and then + performing scalar/scalar arithmetic could compute incorrect results. +* Switch to upstream Rust intrinsics for the IFMA backend now that they exist in + Rust and don't need to be defined locally. +* Ensure that the NAF computation works correctly, even for parameters never + used elsewhere in the codebase. +* Minor refactoring to EdwardsPoint decompression. +* Fix broken links in documentation. +* Fix compilation on nightly broken due to changes to the `#[doc(include)]` path + root (not quite correctly done in 1.2.2). + +### 1.2.2 + +* Fix a typo in an internal doc-comment. +* Add the "crypto" tag to crate metadata. +* Fix compilation on nightly broken due to changes to the `#[doc(include)]` path + root. + +### 1.2.1 + +* Fix a bug in bucket index calculations in the Pippenger multiscalar algorithm + for very large input sizes. +* Add a more extensive randomized multiscalar multiplication consistency check + to the test suite to prevent regressions. +* Ensure that that multiscalar and NAF computations work correctly on extremal + `Scalar` values constructed via `from_bits`. + +### 1.2.0 + +* New multiscalar multiplication algorithm with better performance for + large problem sizes. The backend algorithm is selected + transparently using the size hints of the input iterators, so no + changes are required for client crates to start using it. +* Equality of Edwards points is now checked in projective coordinates. +* Serde can now be used with `no_std`. + +### 1.1.4 + +* Fix typos in documentation comments. +* Remove unnecessary `Default` bound on `Scalar::from_hash`. + +### 1.1.3 + +* Reverts the change in 1.1.0 to allow owned and borrowed RNGs, which caused a breakage due to a subtle interaction with ownership rules. (The `RngCore` change is retained). + +### 1.1.2 + +* Disabled KaTeX on `docs.rs` pending proper [support upstream](https://github.com/rust-lang/docs.rs/issues/302). + +## 1.1.1 + +* Fixed an issue related to `#[cfg(rustdoc)]` which prevented documenting multiple backends. + +### 1.1.0 + +* Adds support for precomputation for multiscalar multiplication. +* Restructures the internal source tree into `serial` and `vector` backends (no change to external API). +* Adds a new IFMA backend which sets speed records. +* The `avx2_backend` feature is now an alias for the `simd_backend` feature, which autoselects an appropriate vector backend (currently AVX2 or IFMA). +* Replaces the `rand` dependency with `rand_core`. +* Generalizes trait bounds on `RistrettoPoint::random()` and `Scalar::random()` to allow owned and borrowed RNGs and to allow `RngCore` instead of `Rng`. + +### 1.0.3 + +* Adds `ConstantTimeEq` implementation for compressed points. + +### 1.0.2 + +* Fixes a typo in the naming of variables in Ristretto formulas (no change to functionality). + +### 1.0.1 + +* Depends on the stable `2.0` version of `subtle` instead of `2.0.0-pre.0`. + +### 1.0.0 + +Initial stable release. Yanked due to a dependency mistake (see above). + diff --git a/curve25519-elligator2/Cargo.toml b/curve25519-elligator2/Cargo.toml new file mode 100644 index 00000000..e848807e --- /dev/null +++ b/curve25519-elligator2/Cargo.toml @@ -0,0 +1,79 @@ +[package] +name = "curve25519-elligator2" +# Before incrementing: +# - update CHANGELOG +# - update README if required by semver +# - if README was updated, also update module documentation in src/lib.rs +version = "0.1.0-alpha.0" +edition = "2021" +rust-version = "1.60.0" +authors = ["Isis Lovecruft ", + "Henry de Valence ", + "jmwample"] +readme = "README.md" +license = "BSD-3-Clause" +repository = "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/curve25519-dalek" +homepage = "https://github.com/dalek-cryptography/curve25519-dalek" +documentation = "https://docs.rs/curve25519-dalek" +categories = ["cryptography", "no-std"] +keywords = ["cryptography", "crypto", "ristretto", "curve25519", "ristretto255"] +description = "A pure-Rust implementation of group operations on ristretto255 and Curve25519" +exclude = [ + "**/.gitignore", + ".gitignore", +] + +[package.metadata.docs.rs] +rustdoc-args = [ + "--html-in-header", "docs/assets/rustdoc-include-katex-header.html", + "--cfg", "docsrs", +] +features = ["serde", "rand_core", "elligator2", "digest", "legacy_compatibility", "group-bits"] + +[dev-dependencies] +sha2 = { version = "0.10", default-features = false } +bincode = "1" +criterion = { version = "0.5", features = ["html_reports"] } +hex = "0.4.2" +json = "0.12.4" +rand = "0.8" +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +rand_distr = "0.4.3" +kolmogorov_smirnov = "1.1.0" + +[build-dependencies] +rustc_version = "0.4.0" + +[[bench]] +name = "dalek_benchmarks" +harness = false +required-features = ["alloc", "rand_core"] + +[dependencies] +cfg-if = "1" +ff = { version = "0.13", default-features = false, optional = true } +group = { version = "0.13", default-features = false, optional = true } +rand_core = { version = "0.6.4", default-features = false, optional = true } +digest = { version = "0.10", default-features = false, optional = true } +subtle = { version = "2.3.0", default-features = false } +serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] } +zeroize = { version = "1", default-features = false, optional = true } + +[target.'cfg(target_arch = "x86_64")'.dependencies] +cpufeatures = "0.2.6" + +[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies] +fiat-crypto = { version = "0.2.1", default-features = false } + +[features] +default = ["alloc", "precomputed-tables", "zeroize", "elligator2"] +alloc = ["zeroize?/alloc"] +precomputed-tables = [] +legacy_compatibility = [] +group = ["dep:group", "rand_core"] +group-bits = ["group", "ff/bits"] +elligator2 = [] +digest = ["dep:digest", "elligator2"] + +[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies] +curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" } diff --git a/curve25519-elligator2/LICENSE b/curve25519-elligator2/LICENSE new file mode 100644 index 00000000..ff347574 --- /dev/null +++ b/curve25519-elligator2/LICENSE @@ -0,0 +1,65 @@ +Copyright (c) 2016-2021 isis agora lovecruft. All rights reserved. +Copyright (c) 2016-2021 Henry de Valence. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +======================================================================== + +Portions of curve25519-dalek were originally derived from Adam Langley's +Go ed25519 implementation, found at , +under the following licence: + +======================================================================== + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/curve25519-elligator2/Makefile b/curve25519-elligator2/Makefile new file mode 100644 index 00000000..bb61cc84 --- /dev/null +++ b/curve25519-elligator2/Makefile @@ -0,0 +1,11 @@ +FEATURES := serde rand_core digest legacy_compatibility + +export RUSTDOCFLAGS := \ + --cfg docsrs \ + --html-in-header docs/assets/rustdoc-include-katex-header.html + +doc: + cargo +nightly rustdoc --features "$(FEATURES)" + +doc-internal: + cargo +nightly rustdoc --features "$(FEATURES)" -- --document-private-items diff --git a/curve25519-elligator2/README.md b/curve25519-elligator2/README.md new file mode 100644 index 00000000..970baa22 --- /dev/null +++ b/curve25519-elligator2/README.md @@ -0,0 +1,354 @@ + +# curve25519-elligator2 [![](https://buildstats.info/crate/curve25519-elligator2)](https://crates.io/crates/curve25519-elligator2) [![](https://img.shields.io/docsrs/curve25519-elligator2)](https://docs.rs/curve25519-elligator2) [![CI](https://github.com/jmwample/curve25519-dalek/actions/workflows/curve25519-elligator2.yml/badge.svg?branch=main)](https://github.com/jmwample/curve25519-dalek/actions/workflows/curve25519-elligator2.yml) + +## THIS IS A FORK FOR CONVENIENCE + +This fork contains an implementation of elligator2 that is awaiting potential +inclusion in the mainstream `curve25519-dalek` crate. This crate exists for +use in the interim period. As such, this crate: +1. may change its API to conform to needs of the upstream source +2. will be yanked if the feature is merged into the upstream source. + +# Use + +To import `curve25519-dalek`, add the following to the dependencies section of +your project's `Cargo.toml`: +```toml +curve25519-elligator2 = "4" +``` + +If opting into [SemVer-exempted features](#public-api-semver-exemptions) a range +can be used to scope the tested compatible version range e.g.: +```toml +curve25519-elligator2 = ">= 4.0, < 4.2" +``` + + +Below is the documentation for the underlying `curve25519-dalek` crate. + +--- +--- +# curve25519-dalek [![](https://buildstats.info/crate/curve25519-dalek)](https://crates.io/crates/curve25519-dalek) [![](https://img.shields.io/docsrs/curve25519-dalek)](https://docs.rs/curve25519-dalek) [![CI](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/curve25519-dalek.yml/badge.svg?branch=main)](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/curve25519-dalek.yml) + + +

+dalek-cryptography logo: a dalek with edwards curves as sparkles coming out of its radar-schnozzley blaster thingies +

+ +**A pure-Rust implementation of group operations on Ristretto and Curve25519.** + +`curve25519-dalek` is a library providing group operations on the Edwards and +Montgomery forms of Curve25519, and on the prime-order Ristretto group. + +`curve25519-dalek` is not intended to provide implementations of any particular +crypto protocol. Rather, implementations of those protocols (such as +[`x25519-dalek`][x25519-dalek] and [`ed25519-dalek`][ed25519-dalek]) should use +`curve25519-dalek` as a library. + +`curve25519-dalek` is intended to provide a clean and safe _mid-level_ API for use +implementing a wide range of ECC-based crypto protocols, such as key agreement, +signatures, anonymous credentials, rangeproofs, and zero-knowledge proof +systems. + +In particular, `curve25519-dalek` implements Ristretto, which constructs a +prime-order group from a non-prime-order Edwards curve. This provides the +speed and safety benefits of Edwards curve arithmetic, without the pitfalls of +cofactor-related abstraction mismatches. + +# Use + +## Stable + +To import `curve25519-dalek`, add the following to the dependencies section of +your project's `Cargo.toml`: +```toml +curve25519-dalek = "4" +``` + +If opting into [SemVer-exempted features](#public-api-semver-exemptions) a range +can be used to scope the tested compatible version range e.g.: +```toml +curve25519-dalek = ">= 4.0, < 4.2" +``` + +## Feature Flags + +| Feature | Default? | Description | +| :--- | :---: | :--- | +| `alloc` | ✓ | Enables Edwards and Ristretto multiscalar multiplication, batch scalar inversion, and batch Ristretto double-and-compress. Also enables `zeroize`. | +| `zeroize` | ✓ | Enables [`Zeroize`][zeroize-trait] for all scalar and curve point types. | +| `precomputed-tables` | ✓ | Includes precomputed basepoint multiplication tables. This speeds up `EdwardsPoint::mul_base` and `RistrettoPoint::mul_base` by ~4x, at the cost of ~30KB added to the code size. | +| `rand_core` | | Enables `Scalar::random` and `RistrettoPoint::random`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | +| `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | +| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. | +| `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. | +| `group` | | Enables external `group` and `ff` crate traits | +| `elligator2` | | Enables elligator2 functionality for supported types. This allows curve points to be encoded to uniform random representatives, and 32 byte values to be mapped (back) to curve points. | + +To disable the default features when using `curve25519-dalek` as a dependency, +add `default-features = false` to the dependency in your `Cargo.toml`. To +disable it when running `cargo`, add the `--no-default-features` CLI flag. + +## Major Version API Changes + +Breaking changes for each major version release can be found in +[`CHANGELOG.md`](CHANGELOG.md), under the "Breaking changes" subheader. The +latest breaking changes in high level are below: + +### Breaking changes in 4.0.0 + +* Update the MSRV from 1.41 to 1.60 +* Provide SemVer policy +* Make `digest` and `rand_core` optional features +* Remove `std` and `nightly` features +* Replace backend selection - See [CHANGELOG.md](CHANGELOG.md) and [backends](#backends) +* Replace methods `Scalar::{zero, one}` with constants `Scalar::{ZERO, ONE}` +* `Scalar::from_canonical_bytes` now returns `CtOption` +* `Scalar::is_canonical` now returns `Choice` +* Remove `Scalar::from_bytes_clamped` and `Scalar::reduce` +* Deprecate and feature-gate `Scalar::from_bits` behind `legacy_compatibility` +* Deprecate `EdwardsPoint::hash_from_bytes` and rename it + `EdwardsPoint::nonspec_map_to_curve` +* Require including a new trait, `use curve25519_dalek::traits::BasepointTable` + whenever using `EdwardsBasepointTable` or `RistrettoBasepointTable` + +This release also does a lot of dependency updates and relaxations to unblock upstream build issues. + +# Backends + +Curve arithmetic is implemented and used by one of the following backends: + +| Backend | Selection | Implementation | Bits / Word sizes | +| :--- | :--- | :--- | :--- | +| `serial` | Automatic | An optimized, non-parllel implementation | `32` and `64` | +| `fiat` | Manual | Formally verified field arithmetic from [fiat-crypto] | `32` and `64` | +| `simd` | Automatic | Intel AVX2 / AVX512 IFMA accelerated backend | `64` only | + +At runtime, `curve25519-dalek` selects an arithmetic backend from the set of backends it was compiled to support. For Intel x86-64 targets, unless otherwise specified, it will build itself with `simd` support, and default to `serial` at runtime if the appropriate CPU features aren't detected. See [SIMD backend] for more details. + +In the future, `simd` backend may be extended to cover more instruction sets. This change will be non-breaking as this is considered as implementation detail. + +## Manual Backend Override + +You can force the crate to compile with specific backend support, e.g., `serial` for x86-64 targets to save code size, or `fiat` to force the runtime to use verified code. To do this, set the environment variable: +```sh +RUSTFLAGS='--cfg curve25519_dalek_backend="BACKEND"' +``` +Equivalently, you can write to +`~/.cargo/config`: +```toml +[build] +rustflags = ['--cfg=curve25519_dalek_backend="BACKEND"'] +``` +More info [here](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags). + +Note for contributors: The target backends are not entirely independent of each +other. The [SIMD backend] directly depends on parts of the serial backend to +function. + +## Bits / Word size + +`curve25519-dalek` will automatically choose the word size for the `fiat` and +`serial` backends, based on the build target. +For example, building for a 64-bit machine, the default 64 bit word size is +automatically chosen when either the `serial` or `fiat` backend is selected. + +In some targets it might be required to override the word size for better +performance. +Backend word size can be overridden for `serial` and `fiat` by setting the +environment variable: +```sh +RUSTFLAGS='--cfg curve25519_dalek_bits="SIZE"' +``` +`SIZE` is `32` or `64`. As in the above section, this can also be placed +in `~/.cargo/config`. + +Note: The [SIMD backend] requires a word size of 64 bits. Attempting to set bits=32 and backend=`simd` will yield a compile error. + +### Cross-compilation + +Because backend selection is done by target, cross-compiling will select the correct word size automatically. For example, if a x86-64 Linux machine runs the following commands, `curve25519-dalek` will be compiled with the 32-bit `serial` backend. +```console +$ sudo apt install gcc-multilib # (or whatever package manager you use) +$ rustup target add i686-unknown-linux-gnu +$ cargo build --target i686-unknown-linux-gnu +``` + +## SIMD backend + +The specific SIMD backend (AVX512 / AVX2 / `serial` default) is selected automatically at runtime, depending on the currently available CPU features, and whether Rust nightly is being used for compilation. The precise conditions are specified below. + +For a given CPU feature, you can also specify an appropriate `-C target_feature` to build a binary which assumes the required SIMD instructions are always available. Don't do this if you don't have a good reason. + +| Backend | `RUSTFLAGS` | Requires nightly? | +| :--- | :--- | :--- | +| avx2 | `-C target_feature=+avx2` | no | +| avx512 | `-C target_feature=+avx512ifma,+avx512vl` | yes | + +If compiled on a non-nightly compiler, `curve25519-dalek` will not include AVX512 code, and therefore will never select it at runtime. + +# Documentation + +The semver-stable, public-facing `curve25519-dalek` API is documented [here][docs]. + +## Building Docs Locally + +The `curve25519-dalek` documentation requires a custom HTML header to include +KaTeX for math support. Unfortunately `cargo doc` does not currently support +this, but docs can be built using +```sh +make doc +``` +for regular docs, and +```sh +make doc-internal +``` +for docs that include private items. + +# Maintenance Policies + +All on-by-default features of this library are covered by +[semantic versioning][semver] (SemVer). SemVer exemptions are outlined below +for MSRV and public API. + +## Minimum Supported Rust Version + +| Releases | MSRV | +| :--- |:-------| +| 4.x | 1.60.0 | +| 3.x | 1.41.0 | + +From 4.x and on, MSRV changes will be accompanied by a minor version bump. + +## Public API SemVer Exemptions + +Breaking changes to SemVer-exempted components affecting the public API will be accompanied by +_some_ version bump. Below are the specific policies: + +| Releases | Public API Component(s) | Policy | +| :--- | :--- | :--- | +| 4.x | Dependencies `group`, `digest` and `rand_core` | Minor SemVer bump | + +# Safety + +The `curve25519-dalek` types are designed to make illegal states +unrepresentable. For example, any instance of an `EdwardsPoint` is +guaranteed to hold a point on the Edwards curve, and any instance of a +`RistrettoPoint` is guaranteed to hold a valid point in the Ristretto +group. + +All operations are implemented using constant-time logic (no +secret-dependent branches, no secret-dependent memory accesses), +unless specifically marked as being variable-time code. +We believe that our constant-time logic is lowered to constant-time +assembly, at least on `x86_64` targets. + +As an additional guard against possible future compiler optimizations, +the `subtle` crate places an optimization barrier before every +conditional move or assignment. More details can be found in [the +documentation for the `subtle` crate][subtle_doc]. + +Some functionality (e.g., multiscalar multiplication or batch +inversion) requires heap allocation for temporary buffers. All +heap-allocated buffers of potentially secret data are explicitly +zeroed before release. + +However, we do not attempt to zero stack data, for two reasons. +First, it's not possible to do so correctly: we don't have control +over stack allocations, so there's no way to know how much data to +wipe. Second, because `curve25519-dalek` provides a mid-level API, +the correct place to start zeroing stack data is likely not at the +entrypoints of `curve25519-dalek` functions, but at the entrypoints of +functions in other crates. + +The implementation is memory-safe, and contains no significant +`unsafe` code. The SIMD backend uses `unsafe` internally to call SIMD +intrinsics. These are marked `unsafe` only because invoking them on an +inappropriate CPU would cause `SIGILL`, but the entire backend is only +invoked when the appropriate CPU features are detected at runtime, or +when the whole program is compiled with the appropriate `target_feature`s. + +# Performance + +Benchmarks are run using [`criterion.rs`][criterion]: + +```sh +cargo bench --features "rand_core" +export RUSTFLAGS='-C target_cpu=native' +cargo +nightly bench --features "rand_core" +``` + +Performance is a secondary goal behind correctness, safety, and +clarity, but we aim to be competitive with other implementations. + +# FFI + +Unfortunately, we have no plans to add FFI to `curve25519-dalek` directly. The +reason is that we use Rust features to provide an API that maintains safety +invariants, which are not possible to maintain across an FFI boundary. For +instance, as described in the _Safety_ section above, invalid points are +impossible to construct, and this would not be the case if we exposed point +operations over FFI. + +However, `curve25519-dalek` is designed as a *mid-level* API, aimed at +implementing other, higher-level primitives. Instead of providing FFI at the +mid-level, our suggestion is to implement the higher-level primitive (a +signature, PAKE, ZKP, etc) in Rust, using `curve25519-dalek` as a dependency, +and have that crate provide a minimal, byte-buffer-oriented FFI specific to +that primitive. + +# Contributing + +Please see [CONTRIBUTING.md][contributing]. + +# About + +**SPOILER ALERT:** *The Twelfth Doctor's first encounter with the Daleks is in +his second full episode, "Into the Dalek". A beleaguered ship of the "Combined +Galactic Resistance" has discovered a broken Dalek that has turned "good", +desiring to kill all other Daleks. The Doctor, Clara and a team of soldiers +are miniaturized and enter the Dalek, which the Doctor names Rusty. They +repair the damage, but accidentally restore it to its original nature, causing +it to go on the rampage and alert the Dalek fleet to the whereabouts of the +rebel ship. However, the Doctor manages to return Rusty to its previous state +by linking his mind with the Dalek's: Rusty shares the Doctor's view of the +universe's beauty, but also his deep hatred of the Daleks. Rusty destroys the +other Daleks and departs the ship, determined to track down and bring an end +to the Dalek race.* + +`curve25519-dalek` is authored by Isis Agora Lovecruft and Henry de Valence. + +Portions of this library were originally a port of [Adam Langley's +Golang ed25519 library](https://github.com/agl/ed25519), which was in +turn a port of the reference `ref10` implementation. Most of this code, +including the 32-bit field arithmetic, has since been rewritten. + +The fast `u32` and `u64` scalar arithmetic was implemented by Andrew Moon, and +the addition chain for scalar inversion was provided by Brian Smith. The +optimised batch inversion was contributed by Sean Bowe and Daira Hopwood. + +The `no_std` and `zeroize` support was contributed by Tony Arcieri. + +The formally verified `fiat_backend` integrates Rust code generated by the +[Fiat Crypto project](https://github.com/mit-plv/fiat-crypto) and was +contributed by François Garillot. + +Thanks also to Ashley Hauck, Lucas Salibian, Manish Goregaokar, Jack Grigg, +Pratyush Mishra, Michael Rosenberg, @pinkforest, and countless others for their +contributions. + +[ed25519-dalek]: https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek +[x25519-dalek]: https://github.com/dalek-cryptography/curve25519-dalek/tree/main/x25519-dalek +[docs]: https://docs.rs/curve25519-dalek/ +[contributing]: https://github.com/dalek-cryptography/curve25519-dalek/blob/master/CONTRIBUTING.md +[criterion]: https://github.com/japaric/criterion.rs +[parallel_doc]: https://docs.rs/curve25519-dalek/latest/curve25519_dalek/backend/vector/index.html +[subtle_doc]: https://docs.rs/subtle +[fiat-crypto]: https://github.com/mit-plv/fiat-crypto +[semver]: https://semver.org/spec/v2.0.0.html +[rngcorestd]: https://github.com/rust-random/rand/tree/7aa25d577e2df84a5156f824077bb7f6bdf28d97/rand_core#crate-features +[zeroize-trait]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html +[SIMD backend]: #simd-backend diff --git a/curve25519-elligator2/benches/dalek_benchmarks.rs b/curve25519-elligator2/benches/dalek_benchmarks.rs new file mode 100644 index 00000000..c330dd35 --- /dev/null +++ b/curve25519-elligator2/benches/dalek_benchmarks.rs @@ -0,0 +1,366 @@ +#![allow(non_snake_case)] + +use rand::{rngs::OsRng, thread_rng}; + +use criterion::{ + criterion_main, measurement::Measurement, BatchSize, BenchmarkGroup, BenchmarkId, Criterion, +}; + +use curve25519_elligator2::constants; +use curve25519_elligator2::scalar::Scalar; + +static BATCH_SIZES: [usize; 5] = [1, 2, 4, 8, 16]; +static MULTISCALAR_SIZES: [usize; 13] = [1, 2, 4, 8, 16, 32, 64, 128, 256, 384, 512, 768, 1024]; + +mod edwards_benches { + use super::*; + + use curve25519_elligator2::edwards::EdwardsPoint; + + fn compress(c: &mut BenchmarkGroup) { + let B = &constants::ED25519_BASEPOINT_POINT; + c.bench_function("EdwardsPoint compression", move |b| b.iter(|| B.compress())); + } + + fn decompress(c: &mut BenchmarkGroup) { + let B_comp = &constants::ED25519_BASEPOINT_COMPRESSED; + c.bench_function("EdwardsPoint decompression", move |b| { + b.iter(|| B_comp.decompress().unwrap()) + }); + } + + fn consttime_fixed_base_scalar_mul(c: &mut BenchmarkGroup) { + let s = Scalar::from(897987897u64).invert(); + c.bench_function("Constant-time fixed-base scalar mul", move |b| { + b.iter(|| EdwardsPoint::mul_base(&s)) + }); + } + + fn consttime_variable_base_scalar_mul(c: &mut BenchmarkGroup) { + let B = &constants::ED25519_BASEPOINT_POINT; + let s = Scalar::from(897987897u64).invert(); + c.bench_function("Constant-time variable-base scalar mul", move |b| { + b.iter(|| B * s) + }); + } + + fn vartime_double_base_scalar_mul(c: &mut BenchmarkGroup) { + c.bench_function("Variable-time aA+bB, A variable, B fixed", |bench| { + let mut rng = thread_rng(); + let A = EdwardsPoint::mul_base(&Scalar::random(&mut rng)); + bench.iter_batched( + || (Scalar::random(&mut rng), Scalar::random(&mut rng)), + |(a, b)| EdwardsPoint::vartime_double_scalar_mul_basepoint(&a, &A, &b), + BatchSize::SmallInput, + ); + }); + } + + pub(crate) fn edwards_benches() { + let mut c = Criterion::default(); + let mut g = c.benchmark_group("edwards benches"); + + compress(&mut g); + decompress(&mut g); + consttime_fixed_base_scalar_mul(&mut g); + consttime_variable_base_scalar_mul(&mut g); + vartime_double_base_scalar_mul(&mut g); + } +} + +mod multiscalar_benches { + use super::*; + + use curve25519_elligator2::edwards::EdwardsPoint; + use curve25519_elligator2::edwards::VartimeEdwardsPrecomputation; + use curve25519_elligator2::traits::MultiscalarMul; + use curve25519_elligator2::traits::VartimeMultiscalarMul; + use curve25519_elligator2::traits::VartimePrecomputedMultiscalarMul; + + fn construct_scalars(n: usize) -> Vec { + let mut rng = thread_rng(); + (0..n).map(|_| Scalar::random(&mut rng)).collect() + } + + fn construct_points(n: usize) -> Vec { + let mut rng = thread_rng(); + (0..n) + .map(|_| EdwardsPoint::mul_base(&Scalar::random(&mut rng))) + .collect() + } + + fn consttime_multiscalar_mul(c: &mut BenchmarkGroup) { + for multiscalar_size in &MULTISCALAR_SIZES { + c.bench_with_input( + BenchmarkId::new( + "Constant-time variable-base multiscalar multiplication", + *multiscalar_size, + ), + &multiscalar_size, + |b, &&size| { + let points = construct_points(size); + // This is supposed to be constant-time, but we might as well + // rerandomize the scalars for every call just in case. + b.iter_batched( + || construct_scalars(size), + |scalars| EdwardsPoint::multiscalar_mul(&scalars, &points), + BatchSize::SmallInput, + ); + }, + ); + } + } + + fn vartime_multiscalar_mul(c: &mut BenchmarkGroup) { + for multiscalar_size in &MULTISCALAR_SIZES { + c.bench_with_input( + BenchmarkId::new( + "Variable-time variable-base multiscalar multiplication", + *multiscalar_size, + ), + &multiscalar_size, + |b, &&size| { + let points = construct_points(size); + // Rerandomize the scalars for every call to prevent + // false timings from better caching (e.g., the CPU + // cache lifts exactly the right table entries for the + // benchmark into the highest cache levels). + b.iter_batched( + || construct_scalars(size), + |scalars| EdwardsPoint::vartime_multiscalar_mul(&scalars, &points), + BatchSize::SmallInput, + ); + }, + ); + } + } + + fn vartime_precomputed_pure_static(c: &mut BenchmarkGroup) { + for multiscalar_size in &MULTISCALAR_SIZES { + c.bench_with_input( + BenchmarkId::new( + "Variable-time fixed-base multiscalar multiplication", + multiscalar_size, + ), + &multiscalar_size, + move |b, &&total_size| { + let static_size = total_size; + + let static_points = construct_points(static_size); + let precomp = VartimeEdwardsPrecomputation::new(static_points); + // Rerandomize the scalars for every call to prevent + // false timings from better caching (e.g., the CPU + // cache lifts exactly the right table entries for the + // benchmark into the highest cache levels). + b.iter_batched( + || construct_scalars(static_size), + |scalars| precomp.vartime_multiscalar_mul(scalars), + BatchSize::SmallInput, + ); + }, + ); + } + } + + fn vartime_precomputed_helper( + c: &mut BenchmarkGroup, + dynamic_fraction: f64, + ) { + for multiscalar_size in &MULTISCALAR_SIZES { + let bench_id = BenchmarkId::new( + "Variable-time mixed-base", + format!( + "(size: {:?}), ({:.0}pct dyn)", + multiscalar_size, + 100.0 * dynamic_fraction + ), + ); + + c.bench_with_input(bench_id, &multiscalar_size, move |b, &&total_size| { + let dynamic_size = ((total_size as f64) * dynamic_fraction) as usize; + let static_size = total_size - dynamic_size; + + let static_points = construct_points(static_size); + let dynamic_points = construct_points(dynamic_size); + let precomp = VartimeEdwardsPrecomputation::new(static_points); + // Rerandomize the scalars for every call to prevent + // false timings from better caching (e.g., the CPU + // cache lifts exactly the right table entries for the + // benchmark into the highest cache levels). Timings + // should be independent of points so we don't + // randomize them. + b.iter_batched( + || { + ( + construct_scalars(static_size), + construct_scalars(dynamic_size), + ) + }, + |(static_scalars, dynamic_scalars)| { + precomp.vartime_mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ) + }, + BatchSize::SmallInput, + ); + }); + } + } + + pub(crate) fn multiscalar_benches() { + let mut c = Criterion::default(); + let mut g = c.benchmark_group("multiscalar benches"); + + consttime_multiscalar_mul(&mut g); + vartime_multiscalar_mul(&mut g); + vartime_precomputed_pure_static(&mut g); + + let dynamic_fracs = [0.0, 0.2, 0.5]; + + for frac in dynamic_fracs.iter() { + vartime_precomputed_helper(&mut g, *frac); + } + } +} + +mod ristretto_benches { + use super::*; + use curve25519_elligator2::ristretto::RistrettoPoint; + + fn compress(c: &mut BenchmarkGroup) { + c.bench_function("RistrettoPoint compression", |b| { + let B = &constants::RISTRETTO_BASEPOINT_POINT; + b.iter(|| B.compress()) + }); + } + + fn decompress(c: &mut BenchmarkGroup) { + c.bench_function("RistrettoPoint decompression", |b| { + let B_comp = &constants::RISTRETTO_BASEPOINT_COMPRESSED; + b.iter(|| B_comp.decompress().unwrap()) + }); + } + + fn double_and_compress_batch(c: &mut BenchmarkGroup) { + for batch_size in &BATCH_SIZES { + c.bench_with_input( + BenchmarkId::new("Batch Ristretto double-and-encode", *batch_size), + &batch_size, + |b, &&size| { + let mut rng = OsRng; + let points: Vec = (0..size) + .map(|_| RistrettoPoint::random(&mut rng)) + .collect(); + b.iter(|| RistrettoPoint::double_and_compress_batch(&points)); + }, + ); + } + } + + pub(crate) fn ristretto_benches() { + let mut c = Criterion::default(); + let mut g = c.benchmark_group("ristretto benches"); + + compress(&mut g); + decompress(&mut g); + double_and_compress_batch(&mut g); + } +} + +mod montgomery_benches { + use super::*; + use curve25519_elligator2::montgomery::MontgomeryPoint; + + fn montgomery_ladder(c: &mut BenchmarkGroup) { + c.bench_function("Montgomery pseudomultiplication", |b| { + let B = constants::X25519_BASEPOINT; + let s = Scalar::from(897987897u64).invert(); + b.iter(|| B * s); + }); + } + + fn consttime_fixed_base_scalar_mul(c: &mut BenchmarkGroup) { + let s = Scalar::from(897987897u64).invert(); + c.bench_function("Constant-time fixed-base scalar mul", move |b| { + b.iter(|| MontgomeryPoint::mul_base(&s)) + }); + } + + pub(crate) fn montgomery_benches() { + let mut c = Criterion::default(); + let mut g = c.benchmark_group("montgomery benches"); + + montgomery_ladder(&mut g); + consttime_fixed_base_scalar_mul(&mut g); + } +} + +mod scalar_benches { + use super::*; + + fn scalar_arith(c: &mut BenchmarkGroup) { + let mut rng = thread_rng(); + + c.bench_function("Scalar inversion", |b| { + let s = Scalar::from(897987897u64).invert(); + b.iter(|| s.invert()); + }); + c.bench_function("Scalar addition", |b| { + b.iter_batched( + || (Scalar::random(&mut rng), Scalar::random(&mut rng)), + |(a, b)| a + b, + BatchSize::SmallInput, + ); + }); + c.bench_function("Scalar subtraction", |b| { + b.iter_batched( + || (Scalar::random(&mut rng), Scalar::random(&mut rng)), + |(a, b)| a - b, + BatchSize::SmallInput, + ); + }); + c.bench_function("Scalar multiplication", |b| { + b.iter_batched( + || (Scalar::random(&mut rng), Scalar::random(&mut rng)), + |(a, b)| a * b, + BatchSize::SmallInput, + ); + }); + } + + fn batch_scalar_inversion(c: &mut BenchmarkGroup) { + for batch_size in &BATCH_SIZES { + c.bench_with_input( + BenchmarkId::new("Batch scalar inversion", *batch_size), + &batch_size, + |b, &&size| { + let mut rng = OsRng; + let scalars: Vec = + (0..size).map(|_| Scalar::random(&mut rng)).collect(); + b.iter(|| { + let mut s = scalars.clone(); + Scalar::batch_invert(&mut s); + }); + }, + ); + } + } + + pub(crate) fn scalar_benches() { + let mut c = Criterion::default(); + let mut g = c.benchmark_group("scalar benches"); + + scalar_arith(&mut g); + batch_scalar_inversion(&mut g); + } +} + +criterion_main!( + scalar_benches::scalar_benches, + montgomery_benches::montgomery_benches, + ristretto_benches::ristretto_benches, + edwards_benches::edwards_benches, + multiscalar_benches::multiscalar_benches, +); diff --git a/curve25519-elligator2/build.rs b/curve25519-elligator2/build.rs new file mode 100644 index 00000000..97fa2852 --- /dev/null +++ b/curve25519-elligator2/build.rs @@ -0,0 +1,126 @@ +//! This selects the curve25519_dalek_bits either by default from target_pointer_width or explicitly set + +#![deny(clippy::unwrap_used, dead_code)] + +#[allow(non_camel_case_types)] +#[derive(PartialEq, Debug)] +enum DalekBits { + Dalek32, + Dalek64, +} + +use std::fmt::Formatter; + +impl std::fmt::Display for DalekBits { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + let w_bits = match self { + DalekBits::Dalek32 => "32", + DalekBits::Dalek64 => "64", + }; + write!(f, "{}", w_bits) + } +} + +fn main() { + let target_arch = match std::env::var("CARGO_CFG_TARGET_ARCH") { + Ok(arch) => arch, + _ => "".to_string(), + }; + + let curve25519_dalek_bits = match std::env::var("CARGO_CFG_CURVE25519_DALEK_BITS").as_deref() { + Ok("32") => DalekBits::Dalek32, + Ok("64") => DalekBits::Dalek64, + _ => deterministic::determine_curve25519_dalek_bits(&target_arch), + }; + + println!("cargo:rustc-cfg=curve25519_dalek_bits=\"{curve25519_dalek_bits}\""); + + if rustc_version::version_meta() + .expect("failed to detect rustc version") + .channel + == rustc_version::Channel::Nightly + { + println!("cargo:rustc-cfg=nightly"); + } + + let rustc_version = rustc_version::version().expect("failed to detect rustc version"); + if rustc_version.major == 1 && rustc_version.minor <= 64 { + // Old versions of Rust complain when you have an `unsafe fn` and you use `unsafe {}` inside, + // so for those we want to apply the `#[allow(unused_unsafe)]` attribute to get rid of that warning. + println!("cargo:rustc-cfg=allow_unused_unsafe"); + } + + // Backend overrides / defaults + let curve25519_dalek_backend = + match std::env::var("CARGO_CFG_CURVE25519_DALEK_BACKEND").as_deref() { + Ok("fiat") => "fiat", + Ok("serial") => "serial", + Ok("simd") => { + // simd can only be enabled on x86_64 & 64bit target_pointer_width + match is_capable_simd(&target_arch, curve25519_dalek_bits) { + true => "simd", + // If override is not possible this must result to compile error + // See: issues/532 + false => panic!("Could not override curve25519_dalek_backend to simd"), + } + } + // default between serial / simd (if potentially capable) + _ => match is_capable_simd(&target_arch, curve25519_dalek_bits) { + true => "simd", + false => "serial", + }, + }; + println!("cargo:rustc-cfg=curve25519_dalek_backend=\"{curve25519_dalek_backend}\""); +} + +// Is the target arch & curve25519_dalek_bits potentially simd capable ? +fn is_capable_simd(arch: &str, bits: DalekBits) -> bool { + arch == "x86_64" && bits == DalekBits::Dalek64 +} + +// Deterministic cfg(curve25519_dalek_bits) when this is not explicitly set. +mod deterministic { + + use super::*; + + // Custom Rust non-cargo build tooling needs to set CARGO_CFG_TARGET_POINTER_WIDTH + static ERR_MSG_NO_POINTER_WIDTH: &str = + "Standard Cargo TARGET_POINTER_WIDTH environment variable is not set."; + + // When either non-32 or 64 TARGET_POINTER_WIDTH detected + static ERR_MSG_UNKNOWN_POINTER_WIDTH: &str = "Unknown TARGET_POINTER_WIDTH detected."; + + // Warning when the curve25519_dalek_bits cannot be determined + fn determine_curve25519_dalek_bits_warning(cause: &str) { + println!("cargo:warning=\"Defaulting to curve25519_dalek_bits=32: {cause}\""); + } + + // Determine the curve25519_dalek_bits based on Rust standard TARGET triplet + pub(super) fn determine_curve25519_dalek_bits(target_arch: &String) -> DalekBits { + let target_pointer_width = match std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH") { + Ok(pw) => pw, + Err(_) => { + determine_curve25519_dalek_bits_warning(ERR_MSG_NO_POINTER_WIDTH); + return DalekBits::Dalek32; + } + }; + + #[allow(clippy::match_single_binding)] + match &target_arch { + //Issues: 449 and 456 + //TODO: When adding arch defaults use proper types not String match + //TODO(Arm): Needs tests + benchmarks to back this up + //TODO(Wasm32): Needs tests + benchmarks to back this up + _ => match target_pointer_width.as_ref() { + "64" => DalekBits::Dalek64, + "32" => DalekBits::Dalek32, + // Intended default solely for non-32/64 target pointer widths + // Otherwise known target platforms only. + _ => { + determine_curve25519_dalek_bits_warning(ERR_MSG_UNKNOWN_POINTER_WIDTH); + DalekBits::Dalek32 + } + }, + } + } +} diff --git a/curve25519-elligator2/docs/assets/rustdoc-include-katex-header.html b/curve25519-elligator2/docs/assets/rustdoc-include-katex-header.html new file mode 100644 index 00000000..d240432a --- /dev/null +++ b/curve25519-elligator2/docs/assets/rustdoc-include-katex-header.html @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/curve25519-elligator2/docs/avx2-notes.md b/curve25519-elligator2/docs/avx2-notes.md new file mode 100644 index 00000000..ccb50223 --- /dev/null +++ b/curve25519-elligator2/docs/avx2-notes.md @@ -0,0 +1,140 @@ +An AVX2 implementation of the vectorized point operation strategy. + +# Field element representation + +Our strategy is to implement 4-wide multiplication and squaring by +wordslicing, using one 64-bit AVX2 lane for each field element. Field +elements are represented in the usual way as 10 `u32` limbs in radix +\\(25.5\\) (i.e., alternating between \\(2\^{26}\\) for even limbs and +\\(2\^{25}\\) for odd limbs). This has the effect that passing between +the parallel 32-bit AVX2 representation and the serial 64-bit +representation (which uses radix \\(2^{51}\\)) amounts to regrouping +digits. + +The field element representation is oriented around the AVX2 +`vpmuludq` instruction, which multiplies the low 32 bits of each +64-bit lane of each operand to produce a 64-bit result. + +```text,no_run +(a1 ?? b1 ?? c1 ?? d1 ??) +(a2 ?? b2 ?? c2 ?? d2 ??) + +(a1*a2 b1*b2 c1*c2 d1*d2) +``` + +To unpack 32-bit values into 64-bit lanes for use in multiplication +it would be convenient to use the `vpunpck[lh]dq` instructions, +which unpack and interleave the low and high 32-bit lanes of two +source vectors. +However, the AVX2 versions of these instructions are designed to +operate only within 128-bit lanes of the 256-bit vectors, so that +interleaving the low lanes of `(a0 b0 c0 d0 a1 b1 c1 d1)` with zero +gives `(a0 00 b0 00 a1 00 b1 00)`. Instead, we pre-shuffle the data +layout as `(a0 b0 a1 b1 c0 d0 c1 d1)` so that we can unpack the +"low" and "high" parts as + +```text,no_run +(a0 00 b0 00 c0 00 d0 00) +(a1 00 b1 00 c1 00 d1 00) +``` + +The data layout for a vector of four field elements \\( (a,b,c,d) +\\) with limbs \\( a_0, a_1, \ldots, a_9 \\) is as `[u32x8; 5]` in +the form + +```text,no_run +(a0 b0 a1 b1 c0 d0 c1 d1) +(a2 b2 a3 b3 c2 d2 c3 d3) +(a4 b4 a5 b5 c4 d4 c5 d5) +(a6 b6 a7 b7 c6 d6 c7 d7) +(a8 b8 a9 b9 c8 d8 c9 d9) +``` + +Since this breaks cleanly into two 128-bit lanes, it may be possible +to adapt it to 128-bit vector instructions such as NEON without too +much difficulty. + +# Avoiding Overflow in Doubling + +To analyze the size of the field element coefficients during the +computations, we can parameterize the bounds on the limbs of each +field element by \\( b \in \mathbb R \\) representing the excess bits +above that limb's radix, so that each limb is bounded by either +\\(2\^{25+b} \\) or \\( 2\^{26+b} \\), as appropriate. + +The multiplication routine requires that its inputs are bounded with +\\( b < 1.75 \\), in order to fit a multiplication by \\( 19 \\) +into 32 bits. Since \\( \lg 19 < 4.25 \\), \\( 19x < 2\^{32} \\) +when \\( x < 2\^{27.75} = 2\^{26 + 1.75} \\). However, this is only +required for one of the inputs; the other can grow up to \\( b < 2.5 +\\). + +In addition, the multiplication and squaring routines do not +canonically reduce their outputs, but can leave some small uncarried +excesses, so that their reduced outputs are bounded with +\\( b < 0.007 \\). + +The non-parallel portion of the doubling formulas is +$$ +\begin{aligned} +(S\_5 &&,&& S\_6 &&,&& S\_8 &&,&& S\_9 ) +&\gets +(S\_1 + S\_2 &&,&& S\_1 - S\_2 &&,&& S\_1 + 2S\_3 - S\_2 &&,&& S\_1 + S\_2 - S\_4) +\end{aligned} +$$ + +Computing \\( (S\_5, S\_6, S\_8, S\_9 ) \\) as +$$ +\begin{matrix} + & S\_1 & S\_1 & S\_1 & S\_1 \\\\ ++& S\_2 & & & S\_2 \\\\ ++& & & S\_3 & \\\\ ++& & & S\_3 & \\\\ ++& & 2p & 2p & 2p \\\\ +-& & S\_2 & S\_2 & \\\\ +-& & & & S\_4 \\\\ +=& S\_5 & S\_6 & S\_8 & S\_9 +\end{matrix} +$$ +results in bit-excesses \\( < (1.01, 1.60, 2.33, 2.01)\\) for +\\( (S\_5, S\_6, S\_8, S\_9 ) \\). The products we want to compute +are then +$$ +\begin{aligned} +X\_3 &\gets S\_8 S\_9 \leftrightarrow (2.33, 2.01) \\\\ +Y\_3 &\gets S\_5 S\_6 \leftrightarrow (1.01, 1.60) \\\\ +Z\_3 &\gets S\_8 S\_6 \leftrightarrow (2.33, 1.60) \\\\ +T\_3 &\gets S\_5 S\_9 \leftrightarrow (1.01, 2.01) +\end{aligned} +$$ +which are too large: it's not possible to arrange the multiplicands so +that one vector has \\(b < 2.5\\) and the other has \\( b < 1.75 \\). +However, if we flip the sign of \\( S\_4 = S\_0\^2 \\) during +squaring, so that we output \\(S\_4' = -S\_4 \pmod p\\), then we can +compute +$$ +\begin{matrix} + & S\_1 & S\_1 & S\_1 & S\_1 \\\\ ++& S\_2 & & & S\_2 \\\\ ++& & & S\_3 & \\\\ ++& & & S\_3 & \\\\ ++& & & & S\_4' \\\\ ++& & 2p & 2p & \\\\ +-& & S\_2 & S\_2 & \\\\ +=& S\_5 & S\_6 & S\_8 & S\_9 +\end{matrix} +$$ +resulting in bit-excesses \\( < (1.01, 1.60, 2.33, 1.60)\\) for +\\( (S\_5, S\_6, S\_8, S\_9 ) \\). The products we want to compute +are then +$$ +\begin{aligned} +X\_3 &\gets S\_8 S\_9 \leftrightarrow (2.33, 1.60) \\\\ +Y\_3 &\gets S\_5 S\_6 \leftrightarrow (1.01, 1.60) \\\\ +Z\_3 &\gets S\_8 S\_6 \leftrightarrow (2.33, 1.60) \\\\ +T\_3 &\gets S\_5 S\_9 \leftrightarrow (1.01, 1.60) +\end{aligned} +$$ +whose right-hand sides are all bounded with \\( b < 1.75 \\) and +whose left-hand sides are all bounded with \\( b < 2.5 \\), +so that we can avoid any intermediate reductions. diff --git a/curve25519-elligator2/docs/ifma-notes.md b/curve25519-elligator2/docs/ifma-notes.md new file mode 100644 index 00000000..faf89280 --- /dev/null +++ b/curve25519-elligator2/docs/ifma-notes.md @@ -0,0 +1,580 @@ +An AVX512-IFMA implementation of the vectorized point operation +strategy. + +# IFMA instructions + +AVX512-IFMA is an extension to AVX-512 consisting of two instructions: + +* `vpmadd52luq`: packed multiply of unsigned 52-bit integers and add + the low 52 product bits to 64-bit accumulators; +* `vpmadd52huq`: packed multiply of unsigned 52-bit integers and add + the high 52 product bits to 64-bit accumulators; + +These operate on 64-bit lanes of their source vectors, taking the low +52 bits of each lane of each source vector, computing the 104-bit +products of each pair, and then adding either the high or low 52 bits +of the 104-bit products to the 64-bit lanes of the destination vector. +The multiplication is performed internally by reusing circuitry for +floating-point arithmetic. Although these instructions are part of +AVX512, the AVX512VL (vector length) extension (present whenever IFMA +is) allows using them with 512, 256, or 128-bit operands. + +This provides a major advantage to vectorized integer operations: +previously, vector operations could only use a \\(32 \times 32 +\rightarrow 64\\)-bit multiplier, while serial code could use a +\\(64\times 64 \rightarrow 128\\)-bit multiplier. + +## IFMA for big-integer multiplications + +A detailed example of the intended use of the IFMA instructions can be +found in a 2016 paper by Gueron and Krasnov, [_Accelerating Big +Integer Arithmetic Using Intel IFMA Extensions_][2016_gueron_krasnov]. +The basic idea is that multiplication of large integers (such as 1024, +2048, or more bits) can be performed as follows. + +First, convert a “packed” 64-bit representation +\\[ +\begin{aligned} +x &= x'_0 + x'_1 2^{64} + x'_2 2^{128} + \cdots \\\\ +y &= y'_0 + y'_1 2^{64} + y'_2 2^{128} + \cdots +\end{aligned} +\\] +into a “redundant” 52-bit representation +\\[ +\begin{aligned} +x &= x_0 + x_1 2^{52} + x_2 2^{104} + \cdots \\\\ +y &= y_0 + y_1 2^{52} + y_2 2^{104} + \cdots +\end{aligned} +\\] +with each \\(x_i, y_j\\) in a 64-bit lane. + +Writing the product as \\(z = z_0 + z_1 2^{52} + z_2 2^{104} + \cdots\\), +the “schoolbook” multiplication strategy gives +\\[ +\begin{aligned} +&z_0 &&=& x_0 & y_0 & & & & & & & & \\\\ +&z_1 &&=& x_1 & y_0 &+ x_0 & y_1 & & & & & & \\\\ +&z_2 &&=& x_2 & y_0 &+ x_1 & y_1 &+ x_0 & y_2 & & & & \\\\ +&z_3 &&=& x_3 & y_0 &+ x_2 & y_1 &+ x_1 & y_2 &+ x_0 & y_3 & & \\\\ +&z_4 &&=& \vdots\\;&\\;\vdots &+ x_3 & y_1 &+ x_2 & y_2 &+ x_1 & y_3 &+ \cdots& \\\\ +&z_5 &&=& & & \vdots\\;&\\;\vdots &+ x_3 & y_2 &+ x_2 & y_3 &+ \cdots& \\\\ +&z_6 &&=& & & & & \vdots\\;&\\;\vdots &+ x_3 & y_3 &+ \cdots& \\\\ +&z_7 &&=& & & & & & & \vdots\\;&\\;\vdots &+ \cdots& \\\\ +&\vdots&&=& & & & & & & & & \ddots& \\\\ +\end{aligned} +\\] +Notice that the product coefficient \\(z_k\\), representing the value +\\(z_k 2^{52k}\\), is the sum of all product terms +\\( +(x_i 2^{52 i}) (y_j 2^{52 j}) +\\) +with \\(k = i + j\\). +Write the IFMA operators \\(\mathrm{lo}(a,b)\\), denoting the low +\\(52\\) bits of \\(ab\\), and +\\(\mathrm{hi}(a,b)\\), denoting the high \\(52\\) bits of +\\(ab\\). +Now we can rewrite the product terms as +\\[ +\begin{aligned} +(x_i 2^{52 i}) (y_j 2^{52 j}) +&= +2^{52 (i+j)}( +\mathrm{lo}(x_i, y_j) + +\mathrm{hi}(x_i, y_j) 2^{52} +) +\\\\ +&= +\mathrm{lo}(x_i, y_j) 2^{52 (i+j)} + +\mathrm{hi}(x_i, y_j) 2^{52 (i+j+1)}. +\end{aligned} +\\] +This means that the low half of \\(x_i y_j\\) can be accumulated onto +the product limb \\(z_{i+j}\\) and the high half can be directly +accumulated onto the next-higher product limb \\(z_{i+j+1}\\) with no +additional operations. This allows rewriting the schoolbook +multiplication into the form +\\[ +\begin{aligned} +&z_0 &&=& \mathrm{lo}(x_0,&y_0) & & & & & & & & & & \\\\ +&z_1 &&=& \mathrm{lo}(x_1,&y_0) &+\mathrm{hi}(x_0,&y_0) &+\mathrm{lo}(x_0,&y_1) & & & & & & \\\\ +&z_2 &&=& \mathrm{lo}(x_2,&y_0) &+\mathrm{hi}(x_1,&y_0) &+\mathrm{lo}(x_1,&y_1) &+\mathrm{hi}(x_0,&y_1) &+\mathrm{lo}(x_0,&y_2) & & \\\\ +&z_3 &&=& \mathrm{lo}(x_3,&y_0) &+\mathrm{hi}(x_2,&y_0) &+\mathrm{lo}(x_2,&y_1) &+\mathrm{hi}(x_1,&y_1) &+\mathrm{lo}(x_1,&y_2) &+ \cdots& \\\\ +&z_4 &&=& \vdots\\;&\\;\vdots &+\mathrm{hi}(x_3,&y_0) &+\mathrm{lo}(x_3,&y_1) &+\mathrm{hi}(x_2,&y_1) &+\mathrm{lo}(x_2,&y_2) &+ \cdots& \\\\ +&z_5 &&=& & & \vdots\\;&\\;\vdots & \vdots\\;&\\;\vdots &+\mathrm{hi}(x_3,&y_1) &+\mathrm{lo}(x_3,&y_2) &+ \cdots& \\\\ +&z_6 &&=& & & & & & & \vdots\\;&\\;\vdots & \vdots\\;&\\;\vdots &+ \cdots& \\\\ +&\vdots&&=& & & & & & & & & & & \ddots& \\\\ +\end{aligned} +\\] +Gueron and Krasnov implement multiplication by constructing vectors +out of the columns of this diagram, so that the source operands for +the IFMA instructions are of the form \\((x_0, x_1, x_2, \ldots)\\) +and \\((y_i, y_i, y_i, \ldots)\\). +After performing the multiplication, +the product terms \\(z_i\\) are then repacked into a 64-bit representation. + +## An alternative strategy + +The strategy described above is aimed at big-integer multiplications, +such as 1024, 2048, or 4096 bits, which would be used for applications +like RSA. However, elliptic curve cryptography uses much smaller field +sizes, such as 256 or 384 bits, so a different strategy is needed. + +The parallel Edwards formulas provide parallelism at the level of the +formulas for curve operations. This means that instead of scanning +through the terms of the source operands and parallelizing *within* a +field element (as described above), we can arrange the computation in +product-scanning form and parallelize *across* field elements (as +described below). + +The parallel Edwards +formulas provide 4-way parallelism, so they can be implemented using +256-bit vectors using a single 64-bit lane for each element, or using +512-bit vectors using two 64-bit lanes. +The only available CPU supporting IFMA (the +i3-8121U) executes 512-bit IFMA instructions at half rate compared to +256-bit instructions, so for now there's no throughput advantage to +using 512-bit IFMA instructions, and this implementation uses 256-bit +vectors. + +To extend this to 512-bit vectors, it's only only necessary to achieve +2-way parallelism, and it's possible (with a small amount of overhead) +to create a hybrid strategy that operates entirely within 128-bit +lanes. This means that cross-lane operations can use the faster +`vpshufd` (1c latency) instead of a general shuffle instruction (3c +latency). + +# Choice of radix + +The inputs to IFMA instructions are 52 bits wide, so the radix \\(r\\) +used to represent a multiprecision integer must be \\( r \leq 52 \\). +The obvious choice is the "native" radix \\(r = 52\\). + +As described above, this choice +has the advantage that for \\(x_i, y_j \in [0,2^{52})\\), the product term +\\[ +\begin{aligned} +(x_i 2^{52 i}) (y_j 2^{52 j}) +&= +2^{52 (i+j)}( +\mathrm{lo}(x_i, y_j) + +\mathrm{hi}(x_i, y_j) 2^{52} +) +\\\\ +&= +\mathrm{lo}(x_i, y_j) 2^{52 (i+j)} + +\mathrm{hi}(x_i, y_j) 2^{52 (i+j+1)}, +\end{aligned} +\\] +so that the low and high halves of the product can be directly accumulated +onto the product limbs. +In contrast, when using a smaller radix \\(r = 52 - k\\), +the product term has the form +\\[ +\begin{aligned} +(x_i 2^{r i}) (y_j 2^{r j}) +&= +2^{r (i+j)}( +\mathrm{lo}(x_i, y_j) + +\mathrm{hi}(x_i, y_j) 2^{52} +) +\\\\ +&= +\mathrm{lo}(x_i, y_j) 2^{r (i+j)} + +( +\mathrm{hi}(x_i, y_j) 2^k +) +2^{r (i+j+1)}. +\end{aligned} +\\] +What's happening is that the product \\(x_i y_j\\) of size \\(2r\\) +bits is split not at \\(r\\) but at \\(52\\), so \\(k\\) product bits +are placed into the low half instead of the high half. This means +that the high half of the product cannot be directly accumulated onto +\\(z_{i+j+1}\\), but must first be multiplied by \\(2^k\\) (i.e., left +shifted by \\(k\\)). In addition, the low half of the product is +\\(52\\) bits large instead of \\(r\\) bits. + +## Handling offset product terms + +[Drucker and Gueron][2018_drucker_gueron] analyze the choice of radix +in the context of big-integer squaring, outlining three ways to handle +the offset product terms, before concluding that all of them are +suboptimal: + +1. Shift the results after accumulation; +2. Shift the input operands before multiplication; +3. Split the MAC operation, accumulating into a zeroed register, + shifting the result, and then adding. + +The first option is rejected because it could double-shift some +previously accumulated terms, the second doesn't work because the +inputs could become larger than \\(52\\) bits, and the third requires +additional instructions to handle the shifting and adding. + +Based on an analysis of total number of instructions, they suggest an +addition to the instruction set, which they call `FMSA` (fused +multiply-shift-add). This would shift the result according to an 8-bit +immediate value before accumulating it into the destination register. + +However, this change to the instruction set doesn't seem to be +necessary. Instead, the product terms can be grouped according to +their coefficients, accumulated together, then shifted once before +adding them to the final sum. This uses an extra register, shift, and +add, but only once per product term (accumulation target), not once +per source term (as in the Drucker-Gueron paper). + +Moreover, because IFMA instructions execute only on two ports +(presumably 0 and 1), while adds and shifts can execute on three ports +(0, 1, and 5), the adds and shifts can execute independently of the +IFMA operations, as long as there is not too much pressure on port 5. +This means that, although the total number of instructions increases, +the shifts and adds do not necessarily increase the execution time, as +long as throughput is limited by IFMA operations. + +Finally, because IFMA instructions have 4 cycle latency and 0.5/1 +cycle throughput (for 256/512 bit vectors), maximizing IFMA throughput +requires either 8 (for 256) or 4 (for 512) independent operations. So +accumulating groups of terms independently before adding them at the +end may be necessary anyways, in order to prevent long chains of +dependent instructions. + +## Advantages of a smaller radix + +Using a smaller radix has other advantages. Although radix \\(52\\) +is an unsaturated representation from the point of view of the +\\(64\\)-bit accumulators (because up to 4096 product terms can be +accumulated without carries), it's a saturated representation from the +point of view of the multiplier (since \\(52\\)-bit values are the +maximum input size). + +Because the inputs to a multiplication must have all of their limbs +bounded by \\(2^{52}\\), limbs in excess of \\(2^{52}\\) must be +reduced before they can be used as an input. The +[Gueron-Krasnov][2016_gueron_krasnov] paper suggests normalizing +values using a standard, sequential carry chain: for each limb, add +the carryin from reducing the previous limb, compute the carryout and +reduce the current limb, then move to the next limb. + +However, when using a smaller radix, such as \\(51\\), each limb can +store a carry bit and still be used as the input to a multiplication. +This means that the inputs do not need to be normalized, and instead +of using a sequential carry chain, we can compute all carryouts in +parallel, reduce all limbs in parallel, and then add the carryins in +parallel (possibly growing the limb values by one bit). + +Because the output of this partial reduction is an acceptable +multiplication input, we can "close the loop" using partial reductions +and never have to normalize to a canonical representation through the +entire computation, in contrast to the Gueron-Krasnov approach, which +converts back to a packed representation after every operation. (This +idea seems to trace back to at least as early as [this 1999 +paper][1999_walter]). + +Using \\(r = 51\\) is enough to keep a carry bit in each limb and +avoid normalizations. What about an even smaller radix? One reason +to choose a smaller radix would be to align the limb boundaries with +an inline reduction (for instance, choosing \\(r = 43\\) for the +Mersenne field \\(p = 2^{127} - 1\\)), but for \\(p = 2^{255 - 19}\\), +\\(r = 51 = 255/5\\) is the natural choice. + +# Multiplication + +The inputs to a multiplication are two field elements +\\[ +\begin{aligned} +x &= x_0 + x_1 2^{51} + x_2 2^{102} + x_3 2^{153} + x_4 2^{204} \\\\ +y &= y_0 + y_1 2^{51} + y_2 2^{102} + y_3 2^{153} + y_4 2^{204}, +\end{aligned} +\\] +with limbs in range \\([0,2^{52})\\). + +Writing the product terms as +\\[ +\begin{aligned} +z &= z_0 + z_1 2^{51} + z_2 2^{102} + z_3 2^{153} + z_4 2^{204} \\\\ + &+ z_5 2^{255} + z_6 2^{306} + z_7 2^{357} + z_8 2^{408} + z_9 2^{459}, +\end{aligned} +\\] +a schoolbook multiplication in product scanning form takes the form +\\[ +\begin{aligned} +z_0 &= x_0 y_0 \\\\ +z_1 &= x_1 y_0 + x_0 y_1 \\\\ +z_2 &= x_2 y_0 + x_1 y_1 + x_0 y_2 \\\\ +z_3 &= x_3 y_0 + x_2 y_1 + x_1 y_2 + x_0 y_3 \\\\ +z_4 &= x_4 y_0 + x_3 y_1 + x_2 y_2 + x_1 y_3 + x_0 y_4 \\\\ +z_5 &= x_4 y_1 + x_3 y_2 + x_2 y_3 + x_1 y_4 \\\\ +z_6 &= x_4 y_2 + x_3 y_3 + x_2 y_4 \\\\ +z_7 &= x_4 y_3 + x_3 y_4 \\\\ +z_8 &= x_4 y_4 \\\\ +z_9 &= 0 \\\\ +\end{aligned} +\\] +Each term \\(x_i y_j\\) can be written in terms of IFMA operations as +\\[ +x_i y_j = \mathrm{lo}(x_i,y_j) + 2\mathrm{hi}(x_i,y_j)2^{51}. +\\] +Substituting this equation into the schoolbook multiplication, then +moving terms to eliminate the \\(2^{51}\\) factors gives +\\[ +\begin{aligned} +z_0 &= \mathrm{lo}(x_0, y_0) \\\\ + &+ \qquad 0 \\\\ +z_1 &= \mathrm{lo}(x_1, y_0) + \mathrm{lo}(x_0, y_1) \\\\ + &+ \qquad 2( \mathrm{hi}(x_0, y_0) )\\\\ +z_2 &= \mathrm{lo}(x_2, y_0) + \mathrm{lo}(x_1, y_1) + \mathrm{lo}(x_0, y_2) \\\\ + &+ \qquad 2( \mathrm{hi}(x_1, y_0) + \mathrm{hi}(x_0, y_1) )\\\\ +z_3 &= \mathrm{lo}(x_3, y_0) + \mathrm{lo}(x_2, y_1) + \mathrm{lo}(x_1, y_2) + \mathrm{lo}(x_0, y_3) \\\\ + &+ \qquad 2( \mathrm{hi}(x_2, y_0) + \mathrm{hi}(x_1, y_1) + \mathrm{hi}(x_0, y_2) )\\\\ +z_4 &= \mathrm{lo}(x_4, y_0) + \mathrm{lo}(x_3, y_1) + \mathrm{lo}(x_2, y_2) + \mathrm{lo}(x_1, y_3) + \mathrm{lo}(x_0, y_4) \\\\ + &+ \qquad 2( \mathrm{hi}(x_3, y_0) + \mathrm{hi}(x_2, y_1) + \mathrm{hi}(x_1, y_2) + \mathrm{hi}(x_0, y_3) )\\\\ +z_5 &= \mathrm{lo}(x_4, y_1) + \mathrm{lo}(x_3, y_2) + \mathrm{lo}(x_2, y_3) + \mathrm{lo}(x_1, y_4) \\\\ + &+ \qquad 2( \mathrm{hi}(x_4, y_0) + \mathrm{hi}(x_3, y_1) + \mathrm{hi}(x_2, y_2) + \mathrm{hi}(x_1, y_3) + \mathrm{hi}(x_0, y_4) )\\\\ +z_6 &= \mathrm{lo}(x_4, y_2) + \mathrm{lo}(x_3, y_3) + \mathrm{lo}(x_2, y_4) \\\\ + &+ \qquad 2( \mathrm{hi}(x_4, y_1) + \mathrm{hi}(x_3, y_2) + \mathrm{hi}(x_2, y_3) + \mathrm{hi}(x_1, y_4) )\\\\ +z_7 &= \mathrm{lo}(x_4, y_3) + \mathrm{lo}(x_3, y_4) \\\\ + &+ \qquad 2( \mathrm{hi}(x_4, y_2) + \mathrm{hi}(x_3, y_3) + \mathrm{hi}(x_2, y_4) )\\\\ +z_8 &= \mathrm{lo}(x_4, y_4) \\\\ + &+ \qquad 2( \mathrm{hi}(x_4, y_3) + \mathrm{hi}(x_3, y_4) )\\\\ +z_9 &= 0 \\\\ + &+ \qquad 2( \mathrm{hi}(x_4, y_4) )\\\\ +\end{aligned} +\\] +As noted above, our strategy will be to multiply and accumulate the +terms with coefficient \\(2\\) separately from those with coefficient +\\(1\\), before combining them at the end. This can alternately be +thought of as accumulating product terms into a *doubly-redundant* +representation, with two limbs for each digit, before collapsing +the doubly-redundant representation by shifts and adds. + +This computation requires 25 `vpmadd52luq` and 25 `vpmadd52huq` +operations. For 256-bit vectors, IFMA operations execute on an +i3-8121U with latency 4 cycles, throughput 0.5 cycles, so executing 50 +instructions requires 25 cycles' worth of throughput. Accumulating +terms with coefficient \\(1\\) and \\(2\\) separately means that the +longest dependency chain has length 5, so the critical path has length +20 cycles and the bottleneck is throughput. + +# Reduction modulo \\(p\\) + +The next question is how to handle the reduction modulo \\(p\\). +Because \\(p = 2^{255} - 19\\), \\(2^{255} = 19 \pmod p\\), so we can +alternately write +\\[ +\begin{aligned} +z &= z_0 + z_1 2^{51} + z_2 2^{102} + z_3 2^{153} + z_4 2^{204} \\\\ + &+ z_5 2^{255} + z_6 2^{306} + z_7 2^{357} + z_8 2^{408} + z_9 2^{459} +\end{aligned} +\\] +as +\\[ +\begin{aligned} +z &= (z_0 + 19z_5) + (z_1 + 19z_6) 2^{51} + (z_2 + 19z_7) 2^{102} + (z_3 + 19z_8) 2^{153} + (z_4 + 19z_9) 2^{204}. +\end{aligned} +\\] +When using a \\(64 \times 64 \rightarrow 128\\)-bit multiplier, this +can be handled (as in [Ed25519][ed25519_paper]) by premultiplying +source terms by \\(19\\). Since \\(\lg(19) < 4.25\\), this increases +their size by less than \\(4.25\\) bits, and the rest of the +multiplication can be shown to work out. + +Here, we have at most \\(1\\) bit of headroom. In order to allow +premultiplication, we would need to use radix \\(2^{47}\\), which +would require six limbs instead of five. Instead, we compute the high +terms \\(z_5, \ldots, z_9\\), each using two chains of IFMA +operations, then multiply by \\(19\\) and combine with the lower terms +\\(z_0, \ldots, z_4\\). There are two ways to perform the +multiplication by \\(19\\): using more IFMA operations, or using the +`vpmullq` instruction, which computes the low \\(64\\) bits of a \\(64 +\times 64\\)-bit product. However, `vpmullq` has 15c/1.5c +latency/throughput, in contrast to the 4c/0.5c latency/throughput of +IFMA operations, so it seems like a worse choice. + +The high terms \\(z_5, \ldots, z_9\\) are sums of \\(52\\)-bit terms, +so they are larger than \\(52\\) bits. Write these terms in radix \\(52\\) as +\\[ +z_{5+i} = z_{5+i}' + z_{5+i}'' 2^{52}, \qquad z_{5+i}' < 2^{52}. +\\] +Then the contribution of \\(z_{5+i}\\), taken modulo \\(p\\), is +\\[ +\begin{aligned} +z_{5+i} 2^{255} 2^{51 i} +&= +19 (z_{5+i}' + z_{5+i}'' 2^{52}) 2^{51 i} +\\\\ +&= +19 z_{5+i}' 2^{51 i} + 2 \cdot 19 z_{5+i}'' 2^{51 (i+1)} +\\\\ +\end{aligned} +\\] +The products \\(19 z_{5+i}', 19 z_{5+i}''\\) can be written in terms of IFMA operations as +\\[ +\begin{aligned} +19 z_{5+i}' &= \mathrm{lo}(19, z_{5+i}') + 2 \mathrm{hi}(19, z_{5+i}') 2^{51}, \\\\ +19 z_{5+i}'' &= \mathrm{lo}(19, z_{5+i}'') + 2 \mathrm{hi}(19, z_{5+i}'') 2^{51}. \\\\ +\end{aligned} +\\] +Because \\(z_{5+i} < 2^{64}\\), \\(z_{5+i}'' < 2^{12} \\), so \\(19 +z_{5+i}'' < 2^{17} < 2^{52} \\) and \\(\mathrm{hi}(19, z_{5+i}'') = 0\\). +Because IFMA operations ignore the high bits of their source +operands, we do not need to compute \\(z\_{5+i}'\\) explicitly: +the high bits will be ignored. +Combining these observations, we can write +\\[ +\begin{aligned} +z_{5+i} 2^{255} 2^{51 i} +&= +19 z_{5+i}' 2^{51 i} + 2 \cdot 19 z_{5+i}'' 2^{51 (i+1)} +\\\\ +&= +\mathrm{lo}(19, z_{5+i}) 2^{51 i} +\+ 2 \mathrm{hi}(19, z_{5+i}) 2^{51 (i+1)} +\+ 2 \mathrm{lo}(19, z_{5+i}/2^{52}) 2^{51 (i+1)}. +\end{aligned} +\\] + +For \\(i = 0,1,2,3\\), this allows reducing \\(z_{5+i}\\) onto +\\(z_{i}, z_{i+1}\\), and if the low terms are computed using a +doubly-redundant representation, no additional shifts are needed to +handle the \\(2\\) coefficients. For \\(i = 4\\), there's a +complication: the contribution becomes +\\[ +\begin{aligned} +z_{9} 2^{255} 2^{204} +&= +\mathrm{lo}(19, z_{9}) 2^{204} +\+ 2 \mathrm{hi}(19, z_{9}) 2^{255} +\+ 2 \mathrm{lo}(19, z_{9}/2^{52}) 2^{255} +\\\\ +&= +\mathrm{lo}(19, z_{9}) 2^{204} +\+ 2 \mathrm{hi}(19, z_{9}) 19 +\+ 2 \mathrm{lo}(19, z_{9}/2^{52}) 19 +\\\\ +&= +\mathrm{lo}(19, z_{9}) 2^{204} +\+ 2 +\mathrm{lo}(19, \mathrm{hi}(19, z_{9}) + \mathrm{lo}(19, z_{9}/2^{52})). +\\\\ +\end{aligned} +\\] + +It would be possible to cut the number of multiplications from 3 to 2 +by carrying the high part of each \\(z_i\\) onto \\(z_{i+1}\\). This +would eliminate 5 multiplications, clearing 2.5 cycles of port +pressure, at the cost of 5 additions, adding 1.66 cycles of port +pressure. But doing this would create a dependency between terms +(e.g., \\(z_{5}\\) must be computed before the reduction of +\\(z_{6}\\) can begin), whereas with the approach above, all +contributions to all terms are computed independently, to maximize ILP +and flexibility for the processor to schedule instructions. + +This strategy performs 16 IFMA operations, adding two IFMA operations +to each of the \\(2\\)-coefficient terms and one to each of the +\\(1\\)-coefficient terms. Considering the multiplication and +reduction together, we use 66 IFMA operations, requiring 33 cycles' +throughput, while the longest chain of IFMA operations is in the +reduction of \\(z_5\\) onto \\(z_1\\), of length 7 (so 28 cycles, plus +2 cycles to combine the two parts of \\(z_5\\), and the bottleneck is +again throughput. + +Once this is done, we have computed the product terms +\\[ +z = z_0 + z_1 2^{51} + z_2 2^{102} + z_3 2^{153} + z_4 2^{204}, +\\] +without reducing the \\(z_i\\) to fit in \\(52\\) bits. Because the +overall flow of operations alternates multiplications and additions or +subtractions, we would have to perform a reduction after an addition +but before the next multiplication anyways, so there's no benefit to +fully reducing the limbs at the end of a multiplication. Instead, we +leave them unreduced, and track the reduction state using the type +system to ensure that unreduced limbs are not accidentally used as an +input to a multiplication. + +# Squaring + +Squaring operates similarly to multiplication, but with the +possibility to combine identical terms. +As before, we write the input as +\\[ +\begin{aligned} +x &= x_0 + x_1 2^{51} + x_2 2^{102} + x_3 2^{153} + x_4 2^{204} +\end{aligned} +\\] +with limbs in range \\([0,2^{52})\\). +Writing the product terms as +\\[ +\begin{aligned} +z &= z_0 + z_1 2^{51} + z_2 2^{102} + z_3 2^{153} + z_4 2^{204} \\\\ + &+ z_5 2^{255} + z_6 2^{306} + z_7 2^{357} + z_8 2^{408} + z_9 2^{459}, +\end{aligned} +\\] +a schoolbook squaring in product scanning form takes the form +\\[ +\begin{aligned} +z_0 &= x_0 x_0 \\\\ +z_1 &= 2 x_1 x_0 \\\\ +z_2 &= 2 x_2 x_0 + x_1 x_1 \\\\ +z_3 &= 2 x_3 x_0 + 2 x_2 x_1 \\\\ +z_4 &= 2 x_4 x_0 + 2 x_3 x_1 + x_2 x_2 \\\\ +z_5 &= 2 x_4 x_1 + 2 x_3 x_2 \\\\ +z_6 &= 2 x_4 x_2 + x_3 x_3 \\\\ +z_7 &= 2 x_4 x_3 \\\\ +z_8 &= x_4 x_4 \\\\ +z_9 &= 0 \\\\ +\end{aligned} +\\] +As before, we write \\(x_i x_j\\) as +\\[ +x_i x_j = \mathrm{lo}(x_i,x_j) + 2\mathrm{hi}(x_i,x_j)2^{51}, +\\] +and substitute to obtain +\\[ +\begin{aligned} +z_0 &= \mathrm{lo}(x_0, x_0) + 0 \\\\ +z_1 &= 2 \mathrm{lo}(x_1, x_0) + 2 \mathrm{hi}(x_0, x_0) \\\\ +z_2 &= 2 \mathrm{lo}(x_2, x_0) + \mathrm{lo}(x_1, x_1) + 4 \mathrm{hi}(x_1, x_0) \\\\ +z_3 &= 2 \mathrm{lo}(x_3, x_0) + 2 \mathrm{lo}(x_2, x_1) + 4 \mathrm{hi}(x_2, x_0) + 2 \mathrm{hi}(x_1, x_1) \\\\ +z_4 &= 2 \mathrm{lo}(x_4, x_0) + 2 \mathrm{lo}(x_3, x_1) + \mathrm{lo}(x_2, x_2) + 4 \mathrm{hi}(x_3, x_0) + 4 \mathrm{hi}(x_2, x_1) \\\\ +z_5 &= 2 \mathrm{lo}(x_4, x_1) + 2 \mathrm{lo}(x_3, x_2) + 4 \mathrm{hi}(x_4, x_0) + 4 \mathrm{hi}(x_3, x_1) + 2 \mathrm{hi}(x_2, x_2) \\\\ +z_6 &= 2 \mathrm{lo}(x_4, x_2) + \mathrm{lo}(x_3, x_3) + 4 \mathrm{hi}(x_4, x_1) + 4 \mathrm{hi}(x_3, x_2) \\\\ +z_7 &= 2 \mathrm{lo}(x_4, x_3) + 4 \mathrm{hi}(x_4, x_2) + 2 \mathrm{hi}(x_3, x_3) \\\\ +z_8 &= \mathrm{lo}(x_4, x_4) + 4 \mathrm{hi}(x_4, x_3) \\\\ +z_9 &= 0 + 2 \mathrm{hi}(x_4, x_4) \\\\ +\end{aligned} +\\] +To implement these, we group terms by their coefficient, computing +those with coefficient \\(2\\) on set of IFMA chains, and on another +set of chains, we begin with coefficient-\\(4\\) terms, then shift +left before continuing with the coefficient-\\(1\\) terms. +The reduction strategy is the same as for multiplication. + +# Future improvements + +LLVM won't use blend operations on [256-bit vectors yet][llvm_blend], +so there's a bunch of blend instructions that could be omitted. + +Although the multiplications and squarings are much faster, there's no +speedup to the additions and subtractions, so there are diminishing +returns. In fact, the complications in the doubling formulas mean +that doubling is actually slower than readdition. This also suggests +that moving to 512-bit vectors won't be much help for a strategy aimed +at parallelism within a group operation, so to extract performance +gains from 512-bit vectors it will probably be necessary to create a +parallel-friendly multiscalar multiplication algorithm. This could +also help with reducing shuffle pressure. + +The squaring implementation could probably be optimized, but without +`perf` support on Cannonlake it's difficult to make actual +measurements. + +Another improvement would be to implement vectorized square root +computations, which would allow creating an iterator adaptor for point +decompression that bunched decompression operations and executed them +in parallel. This would accelerate batch verification. + +[2016_gueron_krasnov]: https://ieeexplore.ieee.org/document/7563269 +[2018_drucker_gueron]: https://eprint.iacr.org/2018/335 +[1999_walter]: https://pdfs.semanticscholar.org/0e6a/3e8f30b63b556679f5dff2cbfdfe9523f4fa.pdf +[ed25519_paper]: https://ed25519.cr.yp.to/ed25519-20110926.pdf +[llvm_blend]: https://bugs.llvm.org/show_bug.cgi?id=38343 diff --git a/curve25519-elligator2/docs/parallel-formulas.md b/curve25519-elligator2/docs/parallel-formulas.md new file mode 100644 index 00000000..70aadc38 --- /dev/null +++ b/curve25519-elligator2/docs/parallel-formulas.md @@ -0,0 +1,333 @@ +Vectorized implementations of field and point operations, using a +modification of the 4-way parallel formulas of Hisil, Wong, Carter, +and Dawson. + +These notes explain the parallel formulas and our strategy for using +them with SIMD operations. There are two backend implementations: one +using AVX2, and the other using AVX512-IFMA. + +# Overview + +The 2008 paper [_Twisted Edwards Curves Revisited_][hwcd08] by Hisil, +Wong, Carter, and Dawson (HWCD) introduced the “extended coordinates” +and mixed-model representations which are used by most Edwards curve +implementations. + +However, they also describe 4-way parallel formulas for point addition +and doubling: a unified addition algorithm taking an effective +\\(2\mathbf M + 1\mathbf D\\), a doubling algorithm taking an +effective \\(1\mathbf M + 1\mathbf S\\), and a dedicated (i.e., for +distinct points) addition algorithm taking an effective \\(2 \mathbf M +\\). They compare these formulas with a 2-way parallel variant of the +Montgomery ladder. + +Unlike their serial formulas, which are used widely, their parallel +formulas do not seem to have been implemented in software before. The +2-way parallel Montgomery ladder was used in 2015 by Tung Chou's +`sandy2x` implementation. Curiously, however, although the [`sandy2x` +paper][sandy2x] also implements Edwards arithmetic, and cites HWCD08, +it doesn't mention their parallel Edwards formulas. +A 2015 paper by Hernández and López describes an AVX2 implementation +of X25519. Neither the paper nor the code are publicly available, but +it apparently gives only a [slight speedup][avx2trac], suggesting that +it uses a 4-way parallel Montgomery ladder rather than parallel +Edwards formulas. + +The reason may be that HWCD08 describe their formulas as operating on +four independent processors, which would make a software +implementation impractical: all of the operations are too low-latency +to effectively synchronize. But a closer inspection reveals that the +(more expensive) multiplication and squaring steps are uniform, while +the instruction divergence occurs in the (much cheaper) addition and +subtraction steps. This means that a SIMD implementation can perform +the expensive steps uniformly, and handle divergence in the +inexpensive steps using masking. + +These notes describe modifications to the original parallel formulas +to allow a SIMD implementation, and this module contains +implementations of the modified formulas targeting either AVX2 or +AVX512-IFMA. + +# Parallel formulas in HWCD'08 + +The doubling formula is presented in the HWCD paper as follows: + +| Cost | Processor 1 | Processor 2 | Processor 3 | Processor 4 | +|------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------| +| | idle | idle | idle | \\( R\_1 \gets X\_1 + Y\_1 \\) | +| \\(1\mathbf S\\) | \\( R\_2 \gets X\_1\^2 \\) | \\( R\_3 \gets Y\_1\^2 \\) | \\( R\_4 \gets Z\_1\^2 \\) | \\( R\_5 \gets R\_1\^2 \\) | +| | \\( R\_6 \gets R\_2 + R\_3 \\) | \\( R\_7 \gets R\_2 - R\_3 \\) | \\( R\_4 \gets 2 R\_4 \\) | idle | +| | idle | \\( R\_1 \gets R\_4 + R\_7 \\) | idle | \\( R\_2 \gets R\_6 - R\_5 \\) | +| \\(1\mathbf M\\) | \\( X\_3 \gets R\_1 R\_2 \\) | \\( Y\_3 \gets R\_6 R\_7 \\) | \\( T\_3 \gets R\_2 R\_6 \\) | \\( Z\_3 \gets R\_1 R\_7 \\) | + +and the unified addition algorithm is presented as follows: + +| Cost | Processor 1 | Processor 2 | Processor 3 | Processor 4 | +|------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------| +| | \\( R\_1 \gets Y\_1 - X\_1 \\) | \\( R\_2 \gets Y\_2 - X\_2 \\) | \\( R\_3 \gets Y\_1 + X\_1 \\) | \\( R\_4 \gets Y\_2 + X\_2 \\) | +| \\(1\mathbf M\\) | \\( R\_5 \gets R\_1 R\_2 \\) | \\( R\_6 \gets R\_3 R\_4 \\) | \\( R\_7 \gets T\_1 T\_2 \\) | \\( R\_8 \gets Z\_1 Z\_2 \\) | +| \\(1\mathbf D\\) | idle | idle | \\( R\_7 \gets k R\_7 \\) | \\( R\_8 \gets 2 R\_8 \\) | +| | \\( R\_1 \gets R\_6 - R\_5 \\) | \\( R\_2 \gets R\_8 - R\_7 \\) | \\( R\_3 \gets R\_8 + R\_7 \\) | \\( R\_4 \gets R\_6 + R\_5 \\) | +| \\(1\mathbf M\\) | \\( X\_3 \gets R\_1 R\_2 \\) | \\( Y\_3 \gets R\_3 R\_4 \\) | \\( T\_3 \gets R\_1 R\_4 \\) | \\( Z\_3 \gets R\_2 R\_3 \\) | + +Here \\(\mathbf M\\) and \\(\mathbf S\\) represent the cost of +multiplication and squaring of generic field elements, \\(\mathbf D\\) +represents the cost of multiplication by a curve constant (in this +case \\( k = 2d \\)). + +Notice that the \\(1\mathbf M\\) and \\(1\mathbf S\\) steps are +uniform. The non-uniform steps are all inexpensive additions or +subtractions, with the exception of the multiplication by the curve +constant \\(k = 2d\\): +$$ +R\_7 \gets 2 d R\_7. +$$ + +HWCD suggest parallelising this step by breaking \\(k = 2d\\) into four +parts as \\(k = k_0 + 2\^n k_1 + 2\^{2n} k_2 + 2\^{3n} k_3 \\) and +computing \\(k_i R_7 \\) in parallel. This is quite awkward, but if +the curve constant is a ratio \\( d = d\_1/d\_2 \\), then projective +coordinates allow us to instead compute +$$ +(R\_5, R\_6, R\_7, R\_8) \gets (d\_2 R\_5, d\_2 R\_6, 2d\_1 R\_7, d\_2 R\_8). +$$ +This can be performed as a uniform multiplication by a vector of +constants, and if \\(d\_1, d\_2\\) are small, it is relatively +inexpensive. (This trick was suggested by Mike Hamburg). +In the Curve25519 case, we have +$$ +d = \frac{d\_1}{d\_2} = \frac{-121665}{121666}; +$$ +Since \\(2 \cdot 121666 < 2\^{18}\\), all the constants above fit (up +to sign) in 32 bits, so this can be done in parallel as four +multiplications by small constants \\( (121666, 121666, 2\cdot 121665, +2\cdot 121666) \\), followed by a negation to compute \\( - 2\cdot 121665\\). + +# Modified parallel formulas + +Using the modifications sketched above, we can write SIMD-friendly +versions of the parallel formulas as follows. To avoid confusion with +the original formulas, temporary variables are named \\(S\\) instead +of \\(R\\) and are in static single-assignment form. + +## Addition + +To add points +\\(P_1 = (X_1 : Y_1 : Z_1 : T_1) \\) +and +\\(P_2 = (X_2 : Y_2 : Z_2 : T_2 ) \\), +we compute +$$ +\begin{aligned} +(S\_0 &&,&& S\_1 &&,&& S\_2 &&,&& S\_3 ) +&\gets +(Y\_1 - X\_1&&,&& Y\_1 + X\_1&&,&& Y\_2 - X\_2&&,&& Y\_2 + X\_2) +\\\\ +(S\_4 &&,&& S\_5 &&,&& S\_6 &&,&& S\_7 ) +&\gets +(S\_0 \cdot S\_2&&,&& S\_1 \cdot S\_3&&,&& Z\_1 \cdot Z\_2&&,&& T\_1 \cdot T\_2) +\\\\ +(S\_8 &&,&& S\_9 &&,&& S\_{10} &&,&& S\_{11} ) +&\gets +(d\_2 \cdot S\_4 &&,&& d\_2 \cdot S\_5 &&,&& 2 d\_2 \cdot S\_6 &&,&& 2 d\_1 \cdot S\_7 ) +\\\\ +(S\_{12} &&,&& S\_{13} &&,&& S\_{14} &&,&& S\_{15}) +&\gets +(S\_9 - S\_8&&,&& S\_9 + S\_8&&,&& S\_{10} - S\_{11}&&,&& S\_{10} + S\_{11}) +\\\\ +(X\_3&&,&& Y\_3&&,&& Z\_3&&,&& T\_3) +&\gets +(S\_{12} \cdot S\_{14}&&,&& S\_{15} \cdot S\_{13}&&,&& S\_{15} \cdot S\_{14}&&,&& S\_{12} \cdot S\_{13}) +\end{aligned} +$$ +to obtain \\( P\_3 = (X\_3 : Y\_3 : Z\_3 : T\_3) = P\_1 + P\_2 \\). +This costs \\( 2\mathbf M + 1 \mathbf D\\). + +## Readdition + +If the point \\( P\_2 = (X\_2 : Y\_2 : Z\_2 : T\_2) \\) is fixed, we +can cache the multiplication of the curve constants by computing +$$ +\begin{aligned} +(S\_2\' &&,&& S\_3\' &&,&& Z\_2\' &&,&& T\_2\' ) +&\gets +(d\_2 \cdot (Y\_2 - X\_2)&&,&& d\_2 \cdot (Y\_1 + X\_1)&&,&& 2d\_2 \cdot Z\_2 &&,&& 2d\_1 \cdot T\_2). +\end{aligned} +$$ +This costs \\( 1\mathbf D\\); with \\( (S\_2\', S\_3\', Z\_2\', T\_2\')\\) +in hand, the addition formulas above become +$$ +\begin{aligned} +(S\_0 &&,&& S\_1 &&,&& Z\_1 &&,&& T\_1 ) +&\gets +(Y\_1 - X\_1&&,&& Y\_1 + X\_1&&,&& Z\_1 &&,&& T\_1) +\\\\ +(S\_8 &&,&& S\_9 &&,&& S\_{10} &&,&& S\_{11} ) +&\gets +(S\_0 \cdot S\_2\' &&,&& S\_1 \cdot S\_3\'&&,&& Z\_1 \cdot Z\_2\' &&,&& T\_1 \cdot T\_2\') +\\\\ +(S\_{12} &&,&& S\_{13} &&,&& S\_{14} &&,&& S\_{15}) +&\gets +(S\_9 - S\_8&&,&& S\_9 + S\_8&&,&& S\_{10} - S\_{11}&&,&& S\_{10} + S\_{11}) +\\\\ +(X\_3&&,&& Y\_3&&,&& Z\_3&&,&& T\_3) +&\gets +(S\_{12} \cdot S\_{14}&&,&& S\_{15} \cdot S\_{13}&&,&& S\_{15} \cdot S\_{14}&&,&& S\_{12} \cdot S\_{13}) +\end{aligned} +$$ +which costs only \\( 2\mathbf M \\). This precomputation is +essentially similar to the precomputation that HWCD suggest for their +serial formulas. Because the cost of precomputation and then +readdition is the same as addition, it's sufficient to only +implement caching and readdition. + +## Doubling + +The non-uniform portions of the (re)addition formulas have a fairly +regular structure. Unfortunately, this is not the case for the +doubling formulas, which are much less nice. + +To double a point \\( P = (X\_1 : Y\_1 : Z\_1 : T\_1) \\), we compute +$$ +\begin{aligned} +(X\_1 &&,&& Y\_1 &&,&& Z\_1 &&,&& S\_0) +&\gets +(X\_1 &&,&& Y\_1 &&,&& Z\_1 &&,&& X\_1 + Y\_1) +\\\\ +(S\_1 &&,&& S\_2 &&,&& S\_3 &&,&& S\_4 ) +&\gets +(X\_1\^2 &&,&& Y\_1\^2&&,&& Z\_1\^2 &&,&& S\_0\^2) +\\\\ +(S\_5 &&,&& S\_6 &&,&& S\_8 &&,&& S\_9 ) +&\gets +(S\_1 + S\_2 &&,&& S\_1 - S\_2 &&,&& S\_1 + 2S\_3 - S\_2 &&,&& S\_1 + S\_2 - S\_4) +\\\\ +(X\_3 &&,&& Y\_3 &&,&& Z\_3 &&,&& T\_3 ) +&\gets +(S\_8 \cdot S\_9 &&,&& S\_5 \cdot S\_6 &&,&& S\_8 \cdot S\_6 &&,&& S\_5 \cdot S\_9) +\end{aligned} +$$ +to obtain \\( P\_3 = (X\_3 : Y\_3 : Z\_3 : T\_3) = \[2\]P\_1 \\). + +The intermediate step between the squaring and multiplication requires +a long chain of additions. For the IFMA-based implementation, this is not a problem; for the AVX2-based implementation, it is, but with some care and finesse, it's possible to arrange the computation without requiring an intermediate reduction. + +# Implementation + +These formulas aren't specific to a particular representation of field +element vectors, whose optimum choice is determined by the details of +the instruction set. However, it's not possible to perfectly separate +the implementation of the field element vectors from the +implementation of the point operations. Instead, the [`avx2`] and +[`ifma`] backends provide `ExtendedPoint` and `CachedPoint` types, and +the [`scalar_mul`] code uses one of the backend types by a type alias. + +# Comparison to non-vectorized formulas + +In theory, the parallel Edwards formulas seem to allow a \\(4\\)-way +speedup from parallelism. However, an actual vectorized +implementation has several slowdowns that cut into this speedup. + +First, the parallel formulas can only use the available vector +multiplier. For AVX2, this is a \\( 32 \times 32 \rightarrow 64 +\\)-bit integer multiplier, so the speedup from vectorization must +overcome the disadvantage of losing the \\( 64 \times 64 \rightarrow +128\\)-bit (serial) integer multiplier. The effect of this slowdown +is microarchitecture-dependent, since it requires accounting for the +total number of multiplications and additions and their relative +costs. IFMA allows using a \\( 52 \times 52 \rightarrow 104 \\)-bit +multiplier, but the high and low halves need to be computed +separately, and the reduction requires extra work because it's not +possible to pre-multiply by \\(19\\). + +Second, the parallel doubling formulas incur both a theoretical and +practical slowdown. The parallel formulas described above work on the +\\( \mathbb P\^3 \\) “extended” coordinates. The \\( \mathbb P\^2 \\) +model introduced earlier by [Bernstein, Birkner, Joye, Lange, and +Peters][bbjlp08] allows slightly faster doublings, so HWCD suggest +mixing coordinate systems while performing scalar multiplication +(attributing the idea to [a 1998 paper][cmo98] by Cohen, Miyagi, and +Ono). The \\( T \\) coordinate is not required for doublings, so when +doublings are followed by doublings, its computation can be skipped. +More details on this approach and the different coordinate systems can +be found in the [`curve_models` module documentation][curve_models]. + +Unfortunately, this optimization is not compatible with the parallel +formulas, which cannot save time by skipping a single variable, so the +parallel doubling formulas do slightly more work when counting the +total number of field multiplications and squarings. + +In addition, the parallel doubling formulas have a less regular +pattern of additions and subtractions than the parallel addition +formulas, so the vectorization overhead is proportionately greater. +Both the parallel addition and parallel doubling formulas also require +some shuffling to rearrange data within the vectors, which places more +pressure on the shuffle unit than is desirable. + +This means that the speedup from using a vectorized implementation of +parallel Edwards formulas is likely to be greatest in applications +that do fewer doublings and more additions (like a large multiscalar +multiplication) rather than applications that do fewer additions and +more doublings (like a double-base scalar multiplication). + +Third, Amdahl's law says that the speedup is limited to the portion +which can be parallelized. Normally, the field multiplications +dominate the cost of point operations, but with the IFMA backend, the +multiplications are so fast that the non-parallel additions end up as +a significant portion of the total time. + +Fourth, current Intel CPUs perform thermal throttling when using wide +vector instructions. A detailed description can be found in §15.26 of +[the Intel Optimization Manual][intel], but using wide vector +instructions prevents the core from operating at higher frequencies. +The core can return to the higher-frequency state after 2 +milliseconds, but this timer is reset every time high-power +instructions are used. + +Any speedup from vectorization therefore has to be weighed against a +slowdown for the next few million instructions. For a mixed workload, +where point operations are interspersed with other tasks, this can +reduce overall performance. This implementation is therefore probably +not suitable for basic applications, like signatures, but is +worthwhile for complex applications, like zero-knowledge proofs, which +do sustained work. + +# Future work + +There are several directions for future improvement: + +* Using the vectorized field arithmetic code to parallelize across + point operations rather than within a single point operation. This + is less flexible, but would give a speedup both from allowing use of + the faster mixed-model arithmetic and from reducing shuffle + pressure. One approach in this direction would be to implement + batched scalar-point operations using vectors of points (AoSoA + layout). This less generally useful but would give a speedup for + Bulletproofs. + +* Extending the IFMA implementation to use the full width of AVX512, + either handling the extra parallelism internally to a single point + operation (by using a 2-way parallel implementation of field + arithmetic instead of a wordsliced one), or externally, + parallelizing across point operations. Internal parallelism would + be preferable but might require too much shuffle pressure. For now, + the only available CPU which runs IFMA operations executes them at + 256-bits wide anyways, so this isn't yet important. + +* Generalizing the implementation to NEON instructions. The current + point arithmetic code is written in terms of field element vectors, + which are in turn implemented using platform SIMD vectors. It + should be possible to write an alternate implementation of the + `FieldElement2625x4` using NEON without changing the point + arithmetic. NEON has 128-bit vectors rather than 256-bit vectors, + but this may still be worthwhile compared to a serial + implementation. + + +[sandy2x]: https://eprint.iacr.org/2015/943.pdf +[avx2trac]: https://trac.torproject.org/projects/tor/ticket/8897#comment:28 +[hwcd08]: https://www.iacr.org/archive/asiacrypt2008/53500329/53500329.pdf +[curve_models]: https://docs.rs/curve25519-dalek/latest/curve25519-dalek/backend/serial/curve_models/index.html +[bbjlp08]: https://eprint.iacr.org/2008/013 +[cmo98]: https://link.springer.com/content/pdf/10.1007%2F3-540-49649-1_6.pdf +[intel]: https://software.intel.com/sites/default/files/managed/9e/bc/64-ia-32-architectures-optimization-manual.pdf diff --git a/curve25519-elligator2/src/backend/mod.rs b/curve25519-elligator2/src/backend/mod.rs new file mode 100644 index 00000000..e809e6b7 --- /dev/null +++ b/curve25519-elligator2/src/backend/mod.rs @@ -0,0 +1,251 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! **INTERNALS:** Pluggable implementations for different architectures. +//! +//! The backend code is split into two parts: a serial backend, +//! and a vector backend. +//! +//! The [`serial`] backend contains 32- and 64-bit implementations of +//! field arithmetic and scalar arithmetic, as well as implementations +//! of point operations using the mixed-model strategy (passing +//! between different curve models depending on the operation). +//! +//! The [`vector`] backend contains implementations of vectorized +//! field arithmetic, used to implement point operations using a novel +//! implementation strategy derived from parallel formulas of Hisil, +//! Wong, Carter, and Dawson. +//! +//! Because the two strategies give rise to different curve models, +//! it's not possible to reuse exactly the same scalar multiplication +//! code (or to write it generically), so both serial and vector +//! backends contain matching implementations of scalar multiplication +//! algorithms. These are intended to be selected by a `#[cfg]`-based +//! type alias. +//! +//! The [`vector`] backend is selected by the `simd_backend` cargo +//! feature; it uses the [`serial`] backend for non-vectorized operations. + +use crate::EdwardsPoint; +use crate::Scalar; + +pub mod serial; + +#[cfg(curve25519_dalek_backend = "simd")] +pub mod vector; + +#[derive(Copy, Clone)] +enum BackendKind { + #[cfg(curve25519_dalek_backend = "simd")] + Avx2, + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + Avx512, + Serial, +} + +#[inline] +fn get_selected_backend() -> BackendKind { + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + { + cpufeatures::new!(cpuid_avx512, "avx512ifma", "avx512vl"); + let token_avx512: cpuid_avx512::InitToken = cpuid_avx512::init(); + if token_avx512.get() { + return BackendKind::Avx512; + } + } + + #[cfg(curve25519_dalek_backend = "simd")] + { + cpufeatures::new!(cpuid_avx2, "avx2"); + let token_avx2: cpuid_avx2::InitToken = cpuid_avx2::init(); + if token_avx2.get() { + return BackendKind::Avx2; + } + } + + BackendKind::Serial +} + +#[allow(missing_docs)] +#[cfg(feature = "alloc")] +pub fn pippenger_optional_multiscalar_mul(scalars: I, points: J) -> Option +where + I: IntoIterator, + I::Item: core::borrow::Borrow, + J: IntoIterator>, +{ + use crate::traits::VartimeMultiscalarMul; + + match get_selected_backend() { + #[cfg(curve25519_dalek_backend = "simd")] + BackendKind::Avx2 => + vector::scalar_mul::pippenger::spec_avx2::Pippenger::optional_multiscalar_mul::(scalars, points), + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + BackendKind::Avx512 => + vector::scalar_mul::pippenger::spec_avx512ifma_avx512vl::Pippenger::optional_multiscalar_mul::(scalars, points), + BackendKind::Serial => + serial::scalar_mul::pippenger::Pippenger::optional_multiscalar_mul::(scalars, points), + } +} + +#[cfg(feature = "alloc")] +pub(crate) enum VartimePrecomputedStraus { + #[cfg(curve25519_dalek_backend = "simd")] + Avx2(vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus), + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + Avx512ifma( + vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus, + ), + Scalar(serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus), +} + +#[cfg(feature = "alloc")] +impl VartimePrecomputedStraus { + pub fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: core::borrow::Borrow, + { + use crate::traits::VartimePrecomputedMultiscalarMul; + + match get_selected_backend() { + #[cfg(curve25519_dalek_backend = "simd")] + BackendKind::Avx2 => + VartimePrecomputedStraus::Avx2(vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus::new(static_points)), + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + BackendKind::Avx512 => + VartimePrecomputedStraus::Avx512ifma(vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus::new(static_points)), + BackendKind::Serial => + VartimePrecomputedStraus::Scalar(serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(static_points)) + } + } + + pub fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: core::borrow::Borrow, + J: IntoIterator, + J::Item: core::borrow::Borrow, + K: IntoIterator>, + { + use crate::traits::VartimePrecomputedMultiscalarMul; + + match self { + #[cfg(curve25519_dalek_backend = "simd")] + VartimePrecomputedStraus::Avx2(inner) => inner.optional_mixed_multiscalar_mul( + static_scalars, + dynamic_scalars, + dynamic_points, + ), + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + VartimePrecomputedStraus::Avx512ifma(inner) => inner.optional_mixed_multiscalar_mul( + static_scalars, + dynamic_scalars, + dynamic_points, + ), + VartimePrecomputedStraus::Scalar(inner) => inner.optional_mixed_multiscalar_mul( + static_scalars, + dynamic_scalars, + dynamic_points, + ), + } + } +} + +#[allow(missing_docs)] +#[cfg(feature = "alloc")] +pub fn straus_multiscalar_mul(scalars: I, points: J) -> EdwardsPoint +where + I: IntoIterator, + I::Item: core::borrow::Borrow, + J: IntoIterator, + J::Item: core::borrow::Borrow, +{ + use crate::traits::MultiscalarMul; + + match get_selected_backend() { + #[cfg(curve25519_dalek_backend = "simd")] + BackendKind::Avx2 => { + vector::scalar_mul::straus::spec_avx2::Straus::multiscalar_mul::(scalars, points) + } + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + BackendKind::Avx512 => { + vector::scalar_mul::straus::spec_avx512ifma_avx512vl::Straus::multiscalar_mul::( + scalars, points, + ) + } + BackendKind::Serial => { + serial::scalar_mul::straus::Straus::multiscalar_mul::(scalars, points) + } + } +} + +#[allow(missing_docs)] +#[cfg(feature = "alloc")] +pub fn straus_optional_multiscalar_mul(scalars: I, points: J) -> Option +where + I: IntoIterator, + I::Item: core::borrow::Borrow, + J: IntoIterator>, +{ + use crate::traits::VartimeMultiscalarMul; + + match get_selected_backend() { + #[cfg(curve25519_dalek_backend = "simd")] + BackendKind::Avx2 => { + vector::scalar_mul::straus::spec_avx2::Straus::optional_multiscalar_mul::( + scalars, points, + ) + } + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + BackendKind::Avx512 => { + vector::scalar_mul::straus::spec_avx512ifma_avx512vl::Straus::optional_multiscalar_mul::< + I, + J, + >(scalars, points) + } + BackendKind::Serial => { + serial::scalar_mul::straus::Straus::optional_multiscalar_mul::(scalars, points) + } + } +} + +/// Perform constant-time, variable-base scalar multiplication. +pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint { + match get_selected_backend() { + #[cfg(curve25519_dalek_backend = "simd")] + BackendKind::Avx2 => vector::scalar_mul::variable_base::spec_avx2::mul(point, scalar), + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + BackendKind::Avx512 => { + vector::scalar_mul::variable_base::spec_avx512ifma_avx512vl::mul(point, scalar) + } + BackendKind::Serial => serial::scalar_mul::variable_base::mul(point, scalar), + } +} + +/// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint. +#[allow(non_snake_case)] +pub fn vartime_double_base_mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint { + match get_selected_backend() { + #[cfg(curve25519_dalek_backend = "simd")] + BackendKind::Avx2 => vector::scalar_mul::vartime_double_base::spec_avx2::mul(a, A, b), + #[cfg(all(curve25519_dalek_backend = "simd", nightly))] + BackendKind::Avx512 => { + vector::scalar_mul::vartime_double_base::spec_avx512ifma_avx512vl::mul(a, A, b) + } + BackendKind::Serial => serial::scalar_mul::vartime_double_base::mul(a, A, b), + } +} diff --git a/curve25519-elligator2/src/backend/serial/curve_models/mod.rs b/curve25519-elligator2/src/backend/serial/curve_models/mod.rs new file mode 100644 index 00000000..8e697892 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/curve_models/mod.rs @@ -0,0 +1,564 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Internal curve representations which are not part of the public API. +//! +//! # Curve representations +//! +//! Internally, we use several different models for the curve. Here +//! is a sketch of the relationship between the models, following [a +//! post][smith-moderncrypto] +//! by Ben Smith on the `moderncrypto` mailing list. This is also briefly +//! discussed in section 2.5 of [_Montgomery curves and their +//! arithmetic_][costello-smith-2017] by Costello and Smith. +//! +//! Begin with the affine equation for the curve, +//! $$ +//! -x\^2 + y\^2 = 1 + dx\^2y\^2. +//! $$ +//! Next, pass to the projective closure \\(\mathbb P\^1 \times \mathbb +//! P\^1 \\) by setting \\(x=X/Z\\), \\(y=Y/T.\\) Clearing denominators +//! gives the model +//! $$ +//! -X\^2T\^2 + Y\^2Z\^2 = Z\^2T\^2 + dX\^2Y\^2. +//! $$ +//! In `curve25519-elligator2`, this is represented as the `CompletedPoint` +//! struct. +//! To map from \\(\mathbb P\^1 \times \mathbb P\^1 \\), a product of +//! two lines, to \\(\mathbb P\^3\\), we use the [Segre +//! embedding](https://en.wikipedia.org/wiki/Segre_embedding) +//! $$ +//! \sigma : ((X:Z),(Y:T)) \mapsto (XY:XT:ZY:ZT). +//! $$ +//! Using coordinates \\( (W_0:W_1:W_2:W_3) \\) for \\(\mathbb P\^3\\), +//! the image \\(\sigma (\mathbb P\^1 \times \mathbb P\^1) \\) is the +//! surface defined by \\( W_0 W_3 = W_1 W_2 \\), and under \\( +//! \sigma\\), the equation above becomes +//! $$ +//! -W\_1\^2 + W\_2\^2 = W\_3\^2 + dW\_0\^2, +//! $$ +//! so that the curve is given by the pair of equations +//! $$ +//! \begin{aligned} +//! -W\_1\^2 + W\_2\^2 &= W\_3\^2 + dW\_0\^2, \\\\ W_0 W_3 &= W_1 W_2. +//! \end{aligned} +//! $$ +//! Up to variable naming, this is exactly the "extended" curve model +//! introduced in [_Twisted Edwards Curves +//! Revisited_][hisil-wong-carter-dawson-2008] by Hisil, Wong, Carter, +//! and Dawson. In `curve25519-elligator2`, it is represented as the +//! `EdwardsPoint` struct. We can map from \\(\mathbb P\^3 \\) to +//! \\(\mathbb P\^2 \\) by sending \\( (W\_0:W\_1:W\_2:W\_3) \\) to \\( +//! (W\_1:W\_2:W\_3) \\). Notice that +//! $$ +//! \frac {W\_1} {W\_3} = \frac {XT} {ZT} = \frac X Z = x, +//! $$ +//! and +//! $$ +//! \frac {W\_2} {W\_3} = \frac {YZ} {ZT} = \frac Y T = y, +//! $$ +//! so this is the same as if we had started with the affine model +//! and passed to \\( \mathbb P\^2 \\) by setting \\( x = W\_1 / W\_3 +//! \\), \\(y = W\_2 / W\_3 \\). +//! Up to variable naming, this is the projective representation +//! introduced in in [_Twisted Edwards +//! Curves_][bernstein-birkner-joye-lange-peters-2008] by Bernstein, +//! Birkner, Joye, Lange, and Peters. In `curve25519-elligator2`, it is +//! represented by the `ProjectivePoint` struct. +//! +//! # Passing between curve models +//! +//! Although the \\( \mathbb P\^3 \\) model provides faster addition +//! formulas, the \\( \mathbb P\^2 \\) model provides faster doubling +//! formulas. Hisil, Wong, Carter, and Dawson therefore suggest mixing +//! coordinate systems for scalar multiplication, attributing the idea +//! to [a 1998 paper][cohen-miyaji-ono-1998] of Cohen, Miyagi, and Ono. +//! +//! Their suggestion is to vary the formulas used by context, using a +//! \\( \mathbb P\^2 \rightarrow \mathbb P\^2 \\) doubling formula when +//! a doubling is followed +//! by another doubling, a \\( \mathbb P\^2 \rightarrow \mathbb P\^3 \\) +//! doubling formula when a doubling is followed by an addition, and +//! computing point additions using a \\( \mathbb P\^3 \times \mathbb P\^3 +//! \rightarrow \mathbb P\^2 \\) formula. +//! +//! The `ref10` reference implementation of [Ed25519][ed25519], by +//! Bernstein, Duif, Lange, Schwabe, and Yang, tweaks +//! this strategy, factoring the addition formulas through the +//! completion \\( \mathbb P\^1 \times \mathbb P\^1 \\), so that the +//! output of an addition or doubling always lies in \\( \mathbb P\^1 \times +//! \mathbb P\^1\\), and the choice of which formula to use is replaced +//! by a choice of whether to convert the result to \\( \mathbb P\^2 \\) +//! or \\(\mathbb P\^3 \\). However, this tweak is not described in +//! their paper, only in their software. +//! +//! Our naming for the `CompletedPoint` (\\(\mathbb P\^1 \times \mathbb +//! P\^1 \\)), `ProjectivePoint` (\\(\mathbb P\^2 \\)), and +//! `EdwardsPoint` (\\(\mathbb P\^3 \\)) structs follows the naming in +//! Adam Langley's [Golang ed25519][agl-ed25519] implementation, which +//! `curve25519-elligator2` was originally derived from. +//! +//! Finally, to accelerate readditions, we use two cached point formats +//! in "Niels coordinates", named for Niels Duif, +//! one for the affine model and one for the \\( \mathbb P\^3 \\) model: +//! +//! * `AffineNielsPoint`: \\( (y+x, y-x, 2dxy) \\) +//! * `ProjectiveNielsPoint`: \\( (Y+X, Y-X, Z, 2dXY) \\) +//! +//! [smith-moderncrypto]: https://moderncrypto.org/mail-archive/curves/2016/000807.html +//! [costello-smith-2017]: https://eprint.iacr.org/2017/212 +//! [hisil-wong-carter-dawson-2008]: https://www.iacr.org/archive/asiacrypt2008/53500329/53500329.pdf +//! [bernstein-birkner-joye-lange-peters-2008]: https://eprint.iacr.org/2008/013 +//! [cohen-miyaji-ono-1998]: https://link.springer.com/content/pdf/10.1007%2F3-540-49649-1_6.pdf +//! [ed25519]: https://eprint.iacr.org/2011/368 +//! [agl-ed25519]: https://github.com/agl/ed25519 + +#![allow(non_snake_case)] + +use core::fmt::Debug; +use core::ops::{Add, Neg, Sub}; + +use subtle::Choice; +use subtle::ConditionallySelectable; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +use crate::constants; + +use crate::edwards::EdwardsPoint; +use crate::field::FieldElement; +use crate::traits::ValidityCheck; + +// ------------------------------------------------------------------------ +// Internal point representations +// ------------------------------------------------------------------------ + +/// A `ProjectivePoint` is a point \\((X:Y:Z)\\) on the \\(\mathbb +/// P\^2\\) model of the curve. +/// A point \\((x,y)\\) in the affine model corresponds to +/// \\((x:y:1)\\). +/// +/// More details on the relationships between the different curve models +/// can be found in the module-level documentation. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub struct ProjectivePoint { + pub X: FieldElement, + pub Y: FieldElement, + pub Z: FieldElement, +} + +/// A `CompletedPoint` is a point \\(((X:Z), (Y:T))\\) on the \\(\mathbb +/// P\^1 \times \mathbb P\^1 \\) model of the curve. +/// A point (x,y) in the affine model corresponds to \\( ((x:1),(y:1)) +/// \\). +/// +/// More details on the relationships between the different curve models +/// can be found in the module-level documentation. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub struct CompletedPoint { + pub X: FieldElement, + pub Y: FieldElement, + pub Z: FieldElement, + pub T: FieldElement, +} + +/// A pre-computed point in the affine model for the curve, represented as +/// \\((y+x, y-x, 2dxy)\\) in "Niels coordinates". +/// +/// More details on the relationships between the different curve models +/// can be found in the module-level documentation. +// Safe to derive Eq because affine coordinates. +#[derive(Copy, Clone, Eq, PartialEq)] +#[allow(missing_docs)] +pub struct AffineNielsPoint { + pub y_plus_x: FieldElement, + pub y_minus_x: FieldElement, + pub xy2d: FieldElement, +} + +#[cfg(feature = "zeroize")] +impl Zeroize for AffineNielsPoint { + fn zeroize(&mut self) { + self.y_plus_x.zeroize(); + self.y_minus_x.zeroize(); + self.xy2d.zeroize(); + } +} + +/// A pre-computed point on the \\( \mathbb P\^3 \\) model for the +/// curve, represented as \\((Y+X, Y-X, Z, 2dXY)\\) in "Niels coordinates". +/// +/// More details on the relationships between the different curve models +/// can be found in the module-level documentation. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub struct ProjectiveNielsPoint { + pub Y_plus_X: FieldElement, + pub Y_minus_X: FieldElement, + pub Z: FieldElement, + pub T2d: FieldElement, +} + +#[cfg(feature = "zeroize")] +impl Zeroize for ProjectiveNielsPoint { + fn zeroize(&mut self) { + self.Y_plus_X.zeroize(); + self.Y_minus_X.zeroize(); + self.Z.zeroize(); + self.T2d.zeroize(); + } +} + +// ------------------------------------------------------------------------ +// Constructors +// ------------------------------------------------------------------------ + +use crate::traits::Identity; + +impl Identity for ProjectivePoint { + fn identity() -> ProjectivePoint { + ProjectivePoint { + X: FieldElement::ZERO, + Y: FieldElement::ONE, + Z: FieldElement::ONE, + } + } +} + +impl Identity for ProjectiveNielsPoint { + fn identity() -> ProjectiveNielsPoint { + ProjectiveNielsPoint { + Y_plus_X: FieldElement::ONE, + Y_minus_X: FieldElement::ONE, + Z: FieldElement::ONE, + T2d: FieldElement::ZERO, + } + } +} + +impl Default for ProjectiveNielsPoint { + fn default() -> ProjectiveNielsPoint { + ProjectiveNielsPoint::identity() + } +} + +impl Identity for AffineNielsPoint { + fn identity() -> AffineNielsPoint { + AffineNielsPoint { + y_plus_x: FieldElement::ONE, + y_minus_x: FieldElement::ONE, + xy2d: FieldElement::ZERO, + } + } +} + +impl Default for AffineNielsPoint { + fn default() -> AffineNielsPoint { + AffineNielsPoint::identity() + } +} + +// ------------------------------------------------------------------------ +// Validity checks (for debugging, not CT) +// ------------------------------------------------------------------------ + +impl ValidityCheck for ProjectivePoint { + fn is_valid(&self) -> bool { + // Curve equation is -x^2 + y^2 = 1 + d*x^2*y^2, + // homogenized as (-X^2 + Y^2)*Z^2 = Z^4 + d*X^2*Y^2 + let XX = self.X.square(); + let YY = self.Y.square(); + let ZZ = self.Z.square(); + let ZZZZ = ZZ.square(); + let lhs = &(&YY - &XX) * &ZZ; + let rhs = &ZZZZ + &(&constants::EDWARDS_D * &(&XX * &YY)); + + lhs == rhs + } +} + +// ------------------------------------------------------------------------ +// Constant-time assignment +// ------------------------------------------------------------------------ + +impl ConditionallySelectable for ProjectiveNielsPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + ProjectiveNielsPoint { + Y_plus_X: FieldElement::conditional_select(&a.Y_plus_X, &b.Y_plus_X, choice), + Y_minus_X: FieldElement::conditional_select(&a.Y_minus_X, &b.Y_minus_X, choice), + Z: FieldElement::conditional_select(&a.Z, &b.Z, choice), + T2d: FieldElement::conditional_select(&a.T2d, &b.T2d, choice), + } + } + + fn conditional_assign(&mut self, other: &Self, choice: Choice) { + self.Y_plus_X.conditional_assign(&other.Y_plus_X, choice); + self.Y_minus_X.conditional_assign(&other.Y_minus_X, choice); + self.Z.conditional_assign(&other.Z, choice); + self.T2d.conditional_assign(&other.T2d, choice); + } +} + +impl ConditionallySelectable for AffineNielsPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + AffineNielsPoint { + y_plus_x: FieldElement::conditional_select(&a.y_plus_x, &b.y_plus_x, choice), + y_minus_x: FieldElement::conditional_select(&a.y_minus_x, &b.y_minus_x, choice), + xy2d: FieldElement::conditional_select(&a.xy2d, &b.xy2d, choice), + } + } + + fn conditional_assign(&mut self, other: &Self, choice: Choice) { + self.y_plus_x.conditional_assign(&other.y_plus_x, choice); + self.y_minus_x.conditional_assign(&other.y_minus_x, choice); + self.xy2d.conditional_assign(&other.xy2d, choice); + } +} + +// ------------------------------------------------------------------------ +// Point conversions +// ------------------------------------------------------------------------ + +impl ProjectivePoint { + /// Convert this point from the \\( \mathbb P\^2 \\) model to the + /// \\( \mathbb P\^3 \\) model. + /// + /// This costs \\(3 \mathrm M + 1 \mathrm S\\). + pub fn as_extended(&self) -> EdwardsPoint { + EdwardsPoint { + X: &self.X * &self.Z, + Y: &self.Y * &self.Z, + Z: self.Z.square(), + T: &self.X * &self.Y, + } + } +} + +impl CompletedPoint { + /// Convert this point from the \\( \mathbb P\^1 \times \mathbb P\^1 + /// \\) model to the \\( \mathbb P\^2 \\) model. + /// + /// This costs \\(3 \mathrm M \\). + pub fn as_projective(&self) -> ProjectivePoint { + ProjectivePoint { + X: &self.X * &self.T, + Y: &self.Y * &self.Z, + Z: &self.Z * &self.T, + } + } + + /// Convert this point from the \\( \mathbb P\^1 \times \mathbb P\^1 + /// \\) model to the \\( \mathbb P\^3 \\) model. + /// + /// This costs \\(4 \mathrm M \\). + pub fn as_extended(&self) -> EdwardsPoint { + EdwardsPoint { + X: &self.X * &self.T, + Y: &self.Y * &self.Z, + Z: &self.Z * &self.T, + T: &self.X * &self.Y, + } + } +} + +// ------------------------------------------------------------------------ +// Doubling +// ------------------------------------------------------------------------ + +impl ProjectivePoint { + /// Double this point: return self + self + pub fn double(&self) -> CompletedPoint { + // Double() + let XX = self.X.square(); + let YY = self.Y.square(); + let ZZ2 = self.Z.square2(); + let X_plus_Y = &self.X + &self.Y; + let X_plus_Y_sq = X_plus_Y.square(); + let YY_plus_XX = &YY + &XX; + let YY_minus_XX = &YY - &XX; + + CompletedPoint { + X: &X_plus_Y_sq - &YY_plus_XX, + Y: YY_plus_XX, + Z: YY_minus_XX, + T: &ZZ2 - &YY_minus_XX, + } + } +} + +// ------------------------------------------------------------------------ +// Addition and Subtraction +// ------------------------------------------------------------------------ + +// XXX(hdevalence) These were doc(hidden) so they don't appear in the +// public API docs. +// However, that prevents them being used with --document-private-items, +// so comment out the doc(hidden) for now until this is resolved +// +// upstream rust issue: https://github.com/rust-lang/rust/issues/46380 +//#[doc(hidden)] +impl<'a, 'b> Add<&'b ProjectiveNielsPoint> for &'a EdwardsPoint { + type Output = CompletedPoint; + + fn add(self, other: &'b ProjectiveNielsPoint) -> CompletedPoint { + let Y_plus_X = &self.Y + &self.X; + let Y_minus_X = &self.Y - &self.X; + let PP = &Y_plus_X * &other.Y_plus_X; + let MM = &Y_minus_X * &other.Y_minus_X; + let TT2d = &self.T * &other.T2d; + let ZZ = &self.Z * &other.Z; + let ZZ2 = &ZZ + &ZZ; + + CompletedPoint { + X: &PP - &MM, + Y: &PP + &MM, + Z: &ZZ2 + &TT2d, + T: &ZZ2 - &TT2d, + } + } +} + +//#[doc(hidden)] +impl<'a, 'b> Sub<&'b ProjectiveNielsPoint> for &'a EdwardsPoint { + type Output = CompletedPoint; + + fn sub(self, other: &'b ProjectiveNielsPoint) -> CompletedPoint { + let Y_plus_X = &self.Y + &self.X; + let Y_minus_X = &self.Y - &self.X; + let PM = &Y_plus_X * &other.Y_minus_X; + let MP = &Y_minus_X * &other.Y_plus_X; + let TT2d = &self.T * &other.T2d; + let ZZ = &self.Z * &other.Z; + let ZZ2 = &ZZ + &ZZ; + + CompletedPoint { + X: &PM - &MP, + Y: &PM + &MP, + Z: &ZZ2 - &TT2d, + T: &ZZ2 + &TT2d, + } + } +} + +//#[doc(hidden)] +impl<'a, 'b> Add<&'b AffineNielsPoint> for &'a EdwardsPoint { + type Output = CompletedPoint; + + fn add(self, other: &'b AffineNielsPoint) -> CompletedPoint { + let Y_plus_X = &self.Y + &self.X; + let Y_minus_X = &self.Y - &self.X; + let PP = &Y_plus_X * &other.y_plus_x; + let MM = &Y_minus_X * &other.y_minus_x; + let Txy2d = &self.T * &other.xy2d; + let Z2 = &self.Z + &self.Z; + + CompletedPoint { + X: &PP - &MM, + Y: &PP + &MM, + Z: &Z2 + &Txy2d, + T: &Z2 - &Txy2d, + } + } +} + +//#[doc(hidden)] +impl<'a, 'b> Sub<&'b AffineNielsPoint> for &'a EdwardsPoint { + type Output = CompletedPoint; + + fn sub(self, other: &'b AffineNielsPoint) -> CompletedPoint { + let Y_plus_X = &self.Y + &self.X; + let Y_minus_X = &self.Y - &self.X; + let PM = &Y_plus_X * &other.y_minus_x; + let MP = &Y_minus_X * &other.y_plus_x; + let Txy2d = &self.T * &other.xy2d; + let Z2 = &self.Z + &self.Z; + + CompletedPoint { + X: &PM - &MP, + Y: &PM + &MP, + Z: &Z2 - &Txy2d, + T: &Z2 + &Txy2d, + } + } +} + +// ------------------------------------------------------------------------ +// Negation +// ------------------------------------------------------------------------ + +impl<'a> Neg for &'a ProjectiveNielsPoint { + type Output = ProjectiveNielsPoint; + + fn neg(self) -> ProjectiveNielsPoint { + ProjectiveNielsPoint { + Y_plus_X: self.Y_minus_X, + Y_minus_X: self.Y_plus_X, + Z: self.Z, + T2d: -(&self.T2d), + } + } +} + +impl<'a> Neg for &'a AffineNielsPoint { + type Output = AffineNielsPoint; + + fn neg(self) -> AffineNielsPoint { + AffineNielsPoint { + y_plus_x: self.y_minus_x, + y_minus_x: self.y_plus_x, + xy2d: -(&self.xy2d), + } + } +} + +// ------------------------------------------------------------------------ +// Debug traits +// ------------------------------------------------------------------------ + +impl Debug for ProjectivePoint { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "ProjectivePoint{{\n\tX: {:?},\n\tY: {:?},\n\tZ: {:?}\n}}", + &self.X, &self.Y, &self.Z + ) + } +} + +impl Debug for CompletedPoint { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "CompletedPoint{{\n\tX: {:?},\n\tY: {:?},\n\tZ: {:?},\n\tT: {:?}\n}}", + &self.X, &self.Y, &self.Z, &self.T + ) + } +} + +impl Debug for AffineNielsPoint { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "AffineNielsPoint{{\n\ty_plus_x: {:?},\n\ty_minus_x: {:?},\n\txy2d: {:?}\n}}", + &self.y_plus_x, &self.y_minus_x, &self.xy2d + ) + } +} + +impl Debug for ProjectiveNielsPoint { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "ProjectiveNielsPoint{{\n\tY_plus_X: {:?},\n\tY_minus_X: {:?},\n\tZ: {:?},\n\tT2d: {:?}\n}}", + &self.Y_plus_X, &self.Y_minus_X, &self.Z, &self.T2d) + } +} diff --git a/curve25519-elligator2/src/backend/serial/fiat_u32/field.rs b/curve25519-elligator2/src/backend/serial/fiat_u32/field.rs new file mode 100644 index 00000000..548e40de --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/fiat_u32/field.rs @@ -0,0 +1,291 @@ +// -*- mode: rust; coding: utf-8; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Isis Agora Lovecruft +// - Henry de Valence + +//! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(32\\)-bit +//! limbs with \\(64\\)-bit products. +//! +//! This code was originally derived from Adam Langley's Golang ed25519 +//! implementation, and was then rewritten to use unsigned limbs instead +//! of signed limbs. +//! +//! This uses the formally-verified field arithmetic generated by the +//! [fiat-crypto project](https://github.com/mit-plv/fiat-crypto) + +use core::fmt::Debug; +use core::ops::Neg; +use core::ops::{Add, AddAssign}; +use core::ops::{Mul, MulAssign}; +use core::ops::{Sub, SubAssign}; + +use subtle::Choice; +use subtle::ConditionallySelectable; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +use fiat_crypto::curve25519_32::*; + +/// A `FieldElement2625` represents an element of the field +/// \\( \mathbb Z / (2\^{255} - 19)\\). +/// +/// In the 32-bit implementation, a `FieldElement` is represented in +/// radix \\(2\^{25.5}\\) as ten `u32`s. This means that a field +/// element \\(x\\) is represented as +/// $$ +/// x = \sum\_{i=0}\^9 x\_i 2\^{\lceil i \frac {51} 2 \rceil} +/// = x\_0 + x\_1 2\^{26} + x\_2 2\^{51} + x\_3 2\^{77} + \cdots + x\_9 2\^{230}; +/// $$ +/// the coefficients are alternately bounded by \\(2\^{25}\\) and +/// \\(2\^{26}\\). The limbs are allowed to grow between reductions up +/// to \\(2\^{25+b}\\) or \\(2\^{26+b}\\), where \\(b = 1.75\\). +/// +/// # Note +/// +/// The `curve25519_elligator2::field` module provides a type alias +/// `curve25519_elligator2::field::FieldElement` to either `FieldElement51` +/// or `FieldElement2625`. +/// +/// The backend-specific type `FieldElement2625` should not be used +/// outside of the `curve25519_elligator2::field` module. +#[derive(Copy, Clone)] +pub struct FieldElement2625(pub(crate) fiat_25519_tight_field_element); + +impl Debug for FieldElement2625 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "FieldElement2625({:?})", &(self.0).0[..]) + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for FieldElement2625 { + fn zeroize(&mut self) { + (self.0).0.zeroize(); + } +} + +impl<'b> AddAssign<&'b FieldElement2625> for FieldElement2625 { + fn add_assign(&mut self, rhs: &'b FieldElement2625) { + let mut result_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_add(&mut result_loose, &self.0, &rhs.0); + fiat_25519_carry(&mut self.0, &result_loose); + } +} + +impl<'a, 'b> Add<&'b FieldElement2625> for &'a FieldElement2625 { + type Output = FieldElement2625; + fn add(self, rhs: &'b FieldElement2625) -> FieldElement2625 { + let mut result_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_add(&mut result_loose, &self.0, &rhs.0); + let mut output = FieldElement2625::ZERO; + fiat_25519_carry(&mut output.0, &result_loose); + output + } +} + +impl<'b> SubAssign<&'b FieldElement2625> for FieldElement2625 { + fn sub_assign(&mut self, rhs: &'b FieldElement2625) { + let mut result_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_sub(&mut result_loose, &self.0, &rhs.0); + fiat_25519_carry(&mut self.0, &result_loose); + } +} + +impl<'a, 'b> Sub<&'b FieldElement2625> for &'a FieldElement2625 { + type Output = FieldElement2625; + fn sub(self, rhs: &'b FieldElement2625) -> FieldElement2625 { + let mut result_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_sub(&mut result_loose, &self.0, &rhs.0); + let mut output = FieldElement2625::ZERO; + fiat_25519_carry(&mut output.0, &result_loose); + output + } +} + +impl<'b> MulAssign<&'b FieldElement2625> for FieldElement2625 { + fn mul_assign(&mut self, rhs: &'b FieldElement2625) { + let mut self_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_relax(&mut self_loose, &self.0); + let mut rhs_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_relax(&mut rhs_loose, &rhs.0); + fiat_25519_carry_mul(&mut self.0, &self_loose, &rhs_loose); + } +} + +impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 { + type Output = FieldElement2625; + fn mul(self, rhs: &'b FieldElement2625) -> FieldElement2625 { + let mut self_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_relax(&mut self_loose, &self.0); + let mut rhs_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_relax(&mut rhs_loose, &rhs.0); + let mut output = FieldElement2625::ZERO; + fiat_25519_carry_mul(&mut output.0, &self_loose, &rhs_loose); + output + } +} + +impl<'a> Neg for &'a FieldElement2625 { + type Output = FieldElement2625; + fn neg(self) -> FieldElement2625 { + let mut output_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_opp(&mut output_loose, &self.0); + let mut output = FieldElement2625::ZERO; + fiat_25519_carry(&mut output.0, &output_loose); + output + } +} + +impl ConditionallySelectable for FieldElement2625 { + fn conditional_select( + a: &FieldElement2625, + b: &FieldElement2625, + choice: Choice, + ) -> FieldElement2625 { + let mut output = fiat_25519_tight_field_element([0u32; 10]); + fiat_25519_selectznz( + &mut output.0, + choice.unwrap_u8() as fiat_25519_u1, + &(a.0).0, + &(b.0).0, + ); + FieldElement2625(output) + } + + fn conditional_assign(&mut self, other: &FieldElement2625, choice: Choice) { + let mut output = [0u32; 10]; + let choicebit = choice.unwrap_u8() as fiat_25519_u1; + fiat_25519_cmovznz_u32(&mut output[0], choicebit, self.0[0], other.0[0]); + fiat_25519_cmovznz_u32(&mut output[1], choicebit, self.0[1], other.0[1]); + fiat_25519_cmovznz_u32(&mut output[2], choicebit, self.0[2], other.0[2]); + fiat_25519_cmovznz_u32(&mut output[3], choicebit, self.0[3], other.0[3]); + fiat_25519_cmovznz_u32(&mut output[4], choicebit, self.0[4], other.0[4]); + fiat_25519_cmovznz_u32(&mut output[5], choicebit, self.0[5], other.0[5]); + fiat_25519_cmovznz_u32(&mut output[6], choicebit, self.0[6], other.0[6]); + fiat_25519_cmovznz_u32(&mut output[7], choicebit, self.0[7], other.0[7]); + fiat_25519_cmovznz_u32(&mut output[8], choicebit, self.0[8], other.0[8]); + fiat_25519_cmovznz_u32(&mut output[9], choicebit, self.0[9], other.0[9]); + *self = FieldElement2625::from_limbs(output); + } + + fn conditional_swap(a: &mut FieldElement2625, b: &mut FieldElement2625, choice: Choice) { + u32::conditional_swap(&mut a.0[0], &mut b.0[0], choice); + u32::conditional_swap(&mut a.0[1], &mut b.0[1], choice); + u32::conditional_swap(&mut a.0[2], &mut b.0[2], choice); + u32::conditional_swap(&mut a.0[3], &mut b.0[3], choice); + u32::conditional_swap(&mut a.0[4], &mut b.0[4], choice); + u32::conditional_swap(&mut a.0[5], &mut b.0[5], choice); + u32::conditional_swap(&mut a.0[6], &mut b.0[6], choice); + u32::conditional_swap(&mut a.0[7], &mut b.0[7], choice); + u32::conditional_swap(&mut a.0[8], &mut b.0[8], choice); + u32::conditional_swap(&mut a.0[9], &mut b.0[9], choice); + } +} + +impl FieldElement2625 { + pub(crate) const fn from_limbs(limbs: [u32; 10]) -> FieldElement2625 { + FieldElement2625(fiat_25519_tight_field_element(limbs)) + } + + /// The scalar \\( 0 \\). + pub const ZERO: FieldElement2625 = FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// The scalar \\( 1 \\). + pub const ONE: FieldElement2625 = FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// The scalar \\( -1 \\). + pub const MINUS_ONE: FieldElement2625 = FieldElement2625::from_limbs([ + 0x3ffffec, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, + 0x3ffffff, 0x1ffffff, + ]); + + /// Invert the sign of this field element + pub fn negate(&mut self) { + let neg = self.neg(); + self.0 = neg.0; + } + + /// Given `k > 0`, return `self^(2^k)`. + pub fn pow2k(&self, k: u32) -> FieldElement2625 { + debug_assert!(k > 0); + let mut z = self.square(); + for _ in 1..k { + z = z.square(); + } + z + } + + /// Load a `FieldElement2625` from the low 255 bits of a 256-bit + /// input. + /// + /// # Warning + /// + /// This function does not check that the input used the canonical + /// representative. It masks the high bit, but it will happily + /// decode 2^255 - 18 to 1. Applications that require a canonical + /// encoding of every field element should decode, re-encode to + /// the canonical encoding, and check that the input was + /// canonical. + pub fn from_bytes(data: &[u8; 32]) -> FieldElement2625 { + let mut temp = [0u8; 32]; + temp.copy_from_slice(data); + temp[31] &= 127u8; + let mut output = fiat_25519_tight_field_element([0u32; 10]); + fiat_25519_from_bytes(&mut output, &temp); + FieldElement2625(output) + } + + /// Serialize this `FieldElement51` to a 32-byte array. The + /// encoding is canonical. + pub fn as_bytes(&self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + fiat_25519_to_bytes(&mut bytes, &self.0); + bytes + } + + /// Compute `self^2`. + pub fn square(&self) -> FieldElement2625 { + let mut self_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_relax(&mut self_loose, &self.0); + let mut output = FieldElement2625::ZERO; + fiat_25519_carry_square(&mut output.0, &self_loose); + output + } + + /// Compute `2*self^2`. + pub fn square2(&self) -> FieldElement2625 { + let mut self_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_relax(&mut self_loose, &self.0); + let mut square = fiat_25519_tight_field_element([0; 10]); + fiat_25519_carry_square(&mut square, &self_loose); + let mut output_loose = fiat_25519_loose_field_element([0; 10]); + fiat_25519_add(&mut output_loose, &square, &square); + let mut output = FieldElement2625::ZERO; + fiat_25519_carry(&mut output.0, &output_loose); + output + } + + /// Returns 1 if self is greater than the other and 0 otherwise + // implementation based on C libgmp -> mpn_sub_n + pub(crate) fn gt(&self, other: &Self) -> Choice { + let mut _ul = 0_u32; + let mut _vl = 0_u32; + let mut _rl = 0_u32; + + let mut cy = 0_u32; + for i in 0..10 { + _ul = self.0[i]; + _vl = other.0[i]; + + let (_sl, _cy1) = _ul.overflowing_sub(_vl); + let (_rl, _cy2) = _sl.overflowing_sub(cy); + cy = _cy1 as u32 | _cy2 as u32; + } + + Choice::from((cy != 0_u32) as u8) + } +} diff --git a/curve25519-elligator2/src/backend/serial/fiat_u32/mod.rs b/curve25519-elligator2/src/backend/serial/fiat_u32/mod.rs new file mode 100644 index 00000000..700f0bc1 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/fiat_u32/mod.rs @@ -0,0 +1,26 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Isis Agora Lovecruft +// - Henry de Valence + +//! The `u32` backend uses `u32`s and a `(u32, u32) -> u64` multiplier. +//! +//! This code is intended to be portable, but it requires that +//! multiplication of two \\(32\\)-bit values to a \\(64\\)-bit result +//! is constant-time on the target platform. +//! +//! This uses the formally-verified field arithmetic generated by the +//! [fiat-crypto project](https://github.com/mit-plv/fiat-crypto) + +#[path = "../u32/scalar.rs"] +pub mod scalar; + +pub mod field; + +#[path = "../u32/constants.rs"] +pub mod constants; diff --git a/curve25519-elligator2/src/backend/serial/fiat_u64/field.rs b/curve25519-elligator2/src/backend/serial/fiat_u64/field.rs new file mode 100644 index 00000000..05728c06 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/fiat_u64/field.rs @@ -0,0 +1,282 @@ +// -*- mode: rust; coding: utf-8; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Isis Agora Lovecruft +// - Henry de Valence + +//! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(64\\)-bit +//! limbs with \\(128\\)-bit products. +//! +//! This uses the formally-verified field arithmetic generated by the +//! [fiat-crypto project](https://github.com/mit-plv/fiat-crypto) + +use core::fmt::Debug; +use core::ops::Neg; +use core::ops::{Add, AddAssign}; +use core::ops::{Mul, MulAssign}; +use core::ops::{Sub, SubAssign}; + +use subtle::Choice; +use subtle::ConditionallySelectable; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +use fiat_crypto::curve25519_64::*; + +/// A `FieldElement51` represents an element of the field +/// \\( \mathbb Z / (2\^{255} - 19)\\). +/// +/// In the 64-bit implementation, a `FieldElement` is represented in +/// radix \\(2\^{51}\\) as five `u64`s; the coefficients are allowed to +/// grow up to \\(2\^{54}\\) between reductions modulo \\(p\\). +/// +/// # Note +/// +/// The `curve25519_elligator2::field` module provides a type alias +/// `curve25519_elligator2::field::FieldElement` to either `FieldElement51` +/// or `FieldElement2625`. +/// +/// The backend-specific type `FieldElement51` should not be used +/// outside of the `curve25519_elligator2::field` module. +#[derive(Copy, Clone)] +pub struct FieldElement51(pub(crate) fiat_25519_tight_field_element); + +impl Debug for FieldElement51 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "FieldElement51({:?})", &(self.0).0[..]) + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for FieldElement51 { + fn zeroize(&mut self) { + (self.0).0.zeroize(); + } +} + +impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 { + fn add_assign(&mut self, rhs: &'b FieldElement51) { + let mut result_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_add(&mut result_loose, &self.0, &rhs.0); + fiat_25519_carry(&mut self.0, &result_loose); + } +} + +impl<'a, 'b> Add<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + fn add(self, rhs: &'b FieldElement51) -> FieldElement51 { + let mut result_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_add(&mut result_loose, &self.0, &rhs.0); + let mut output = FieldElement51::ZERO; + fiat_25519_carry(&mut output.0, &result_loose); + output + } +} + +impl<'b> SubAssign<&'b FieldElement51> for FieldElement51 { + fn sub_assign(&mut self, rhs: &'b FieldElement51) { + let mut result_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_sub(&mut result_loose, &self.0, &rhs.0); + fiat_25519_carry(&mut self.0, &result_loose); + } +} + +impl<'a, 'b> Sub<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + fn sub(self, rhs: &'b FieldElement51) -> FieldElement51 { + let mut result_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_sub(&mut result_loose, &self.0, &rhs.0); + let mut output = FieldElement51::ZERO; + fiat_25519_carry(&mut output.0, &result_loose); + output + } +} + +impl<'b> MulAssign<&'b FieldElement51> for FieldElement51 { + fn mul_assign(&mut self, rhs: &'b FieldElement51) { + let mut self_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_relax(&mut self_loose, &self.0); + let mut rhs_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_relax(&mut rhs_loose, &rhs.0); + fiat_25519_carry_mul(&mut self.0, &self_loose, &rhs_loose); + } +} + +impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + fn mul(self, rhs: &'b FieldElement51) -> FieldElement51 { + let mut self_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_relax(&mut self_loose, &self.0); + let mut rhs_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_relax(&mut rhs_loose, &rhs.0); + let mut output = FieldElement51::ZERO; + fiat_25519_carry_mul(&mut output.0, &self_loose, &rhs_loose); + output + } +} + +impl<'a> Neg for &'a FieldElement51 { + type Output = FieldElement51; + fn neg(self) -> FieldElement51 { + let mut output_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_opp(&mut output_loose, &self.0); + let mut output = FieldElement51::ZERO; + fiat_25519_carry(&mut output.0, &output_loose); + output + } +} + +impl ConditionallySelectable for FieldElement51 { + fn conditional_select( + a: &FieldElement51, + b: &FieldElement51, + choice: Choice, + ) -> FieldElement51 { + let mut output = fiat_25519_tight_field_element([0u64; 5]); + fiat_25519_selectznz( + &mut output.0, + choice.unwrap_u8() as fiat_25519_u1, + &(a.0).0, + &(b.0).0, + ); + FieldElement51(output) + } + + fn conditional_swap(a: &mut FieldElement51, b: &mut FieldElement51, choice: Choice) { + u64::conditional_swap(&mut a.0[0], &mut b.0[0], choice); + u64::conditional_swap(&mut a.0[1], &mut b.0[1], choice); + u64::conditional_swap(&mut a.0[2], &mut b.0[2], choice); + u64::conditional_swap(&mut a.0[3], &mut b.0[3], choice); + u64::conditional_swap(&mut a.0[4], &mut b.0[4], choice); + } + + fn conditional_assign(&mut self, rhs: &FieldElement51, choice: Choice) { + let mut output = [0u64; 5]; + let choicebit = choice.unwrap_u8() as fiat_25519_u1; + fiat_25519_cmovznz_u64(&mut output[0], choicebit, self.0[0], rhs.0[0]); + fiat_25519_cmovznz_u64(&mut output[1], choicebit, self.0[1], rhs.0[1]); + fiat_25519_cmovznz_u64(&mut output[2], choicebit, self.0[2], rhs.0[2]); + fiat_25519_cmovznz_u64(&mut output[3], choicebit, self.0[3], rhs.0[3]); + fiat_25519_cmovznz_u64(&mut output[4], choicebit, self.0[4], rhs.0[4]); + *self = FieldElement51::from_limbs(output); + } +} + +impl FieldElement51 { + pub(crate) const fn from_limbs(limbs: [u64; 5]) -> FieldElement51 { + FieldElement51(fiat_25519_tight_field_element(limbs)) + } + + /// The scalar \\( 0 \\). + pub const ZERO: FieldElement51 = FieldElement51::from_limbs([0, 0, 0, 0, 0]); + /// The scalar \\( 1 \\). + pub const ONE: FieldElement51 = FieldElement51::from_limbs([1, 0, 0, 0, 0]); + /// The scalar \\( -1 \\). + pub const MINUS_ONE: FieldElement51 = FieldElement51::from_limbs([ + 2251799813685228, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, + ]); + + /// Given 64-bit input limbs, reduce to enforce the bound 2^(51 + epsilon). + #[inline(always)] + #[allow(dead_code)] // Need this to not complain about reduce not being used + fn reduce(limbs: [u64; 5]) -> FieldElement51 { + let input = fiat_25519_loose_field_element(limbs); + let mut output = fiat_25519_tight_field_element([0; 5]); + fiat_25519_carry(&mut output, &input); + FieldElement51(output) + } + + /// Load a `FieldElement51` from the low 255 bits of a 256-bit + /// input. + /// + /// # Warning + /// + /// This function does not check that the input used the canonical + /// representative. It masks the high bit, but it will happily + /// decode 2^255 - 18 to 1. Applications that require a canonical + /// encoding of every field element should decode, re-encode to + /// the canonical encoding, and check that the input was + /// canonical. + /// + pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 { + let mut temp = [0u8; 32]; + temp.copy_from_slice(bytes); + temp[31] &= 127u8; + let mut output = fiat_25519_tight_field_element([0u64; 5]); + fiat_25519_from_bytes(&mut output, &temp); + FieldElement51(output) + } + + /// Serialize this `FieldElement51` to a 32-byte array. The + /// encoding is canonical. + pub fn as_bytes(&self) -> [u8; 32] { + let mut bytes = [0u8; 32]; + fiat_25519_to_bytes(&mut bytes, &self.0); + bytes + } + + /// Given `k > 0`, return `self^(2^k)`. + pub fn pow2k(&self, mut k: u32) -> FieldElement51 { + let mut output = *self; + loop { + let mut input = fiat_25519_loose_field_element([0; 5]); + fiat_25519_relax(&mut input, &output.0); + fiat_25519_carry_square(&mut output.0, &input); + k -= 1; + if k == 0 { + return output; + } + } + } + + /// Returns the square of this field element. + pub fn square(&self) -> FieldElement51 { + let mut self_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_relax(&mut self_loose, &self.0); + let mut output = FieldElement51::ZERO; + fiat_25519_carry_square(&mut output.0, &self_loose); + output + } + + /// Returns 2 times the square of this field element. + pub fn square2(&self) -> FieldElement51 { + let mut self_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_relax(&mut self_loose, &self.0); + let mut square = fiat_25519_tight_field_element([0; 5]); + fiat_25519_carry_square(&mut square, &self_loose); + let mut output_loose = fiat_25519_loose_field_element([0; 5]); + fiat_25519_add(&mut output_loose, &square, &square); + let mut output = FieldElement51::ZERO; + fiat_25519_carry(&mut output.0, &output_loose); + output + } + + /// Returns 1 if self is greater than the other and 0 otherwise + // implementation based on C libgmp -> mpn_sub_n + pub(crate) fn gt(&self, other: &Self) -> Choice { + let mut _ul = 0_u64; + let mut _vl = 0_u64; + let mut _rl = 0_u64; + + let mut cy = 0_u64; + for i in 0..5 { + _ul = self.0[i]; + _vl = other.0[i]; + + let (_sl, _cy1) = _ul.overflowing_sub(_vl); + let (_rl, _cy2) = _sl.overflowing_sub(cy); + cy = _cy1 as u64 | _cy2 as u64; + } + + Choice::from((cy != 0_u64) as u8) + } +} diff --git a/curve25519-elligator2/src/backend/serial/fiat_u64/mod.rs b/curve25519-elligator2/src/backend/serial/fiat_u64/mod.rs new file mode 100644 index 00000000..054e38cf --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/fiat_u64/mod.rs @@ -0,0 +1,28 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Isis Agora Lovecruft +// - Henry de Valence + +//! The `u64` backend uses `u64`s and a `(u64, u64) -> u128` multiplier. +//! +//! On x86_64, the idiom `(x as u128) * (y as u128)` lowers to `MUL` +//! instructions taking 64-bit inputs and producing 128-bit outputs. On +//! other platforms, this implementation is not recommended. +//! +//! On Haswell and newer, the BMI2 extension provides `MULX`, and on +//! Broadwell and newer, the ADX extension provides `ADCX` and `ADOX` +//! (allowing the CPU to compute two carry chains in parallel). These +//! will be used if available. + +#[path = "../u64/scalar.rs"] +pub mod scalar; + +pub mod field; + +#[path = "../u64/constants.rs"] +pub mod constants; diff --git a/curve25519-elligator2/src/backend/serial/mod.rs b/curve25519-elligator2/src/backend/serial/mod.rs new file mode 100644 index 00000000..336dd7e2 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/mod.rs @@ -0,0 +1,49 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Serial implementations of field, scalar, point arithmetic. +//! +//! When the vector backend is disabled, the crate uses the mixed-model strategy +//! for implementing point operations and scalar multiplication; see the +//! [`curve_models`] and [`scalar_mul`] documentation for more information. +//! +//! When the vector backend is enabled, the field and scalar +//! implementations are still used for non-vectorized operations. + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(curve25519_dalek_backend = "fiat")] { + + #[cfg(curve25519_dalek_bits = "32")] + #[doc(hidden)] + pub mod fiat_u32; + + #[cfg(curve25519_dalek_bits = "64")] + #[doc(hidden)] + pub mod fiat_u64; + + } else { + + #[cfg(curve25519_dalek_bits = "32")] + #[doc(hidden)] + pub mod u32; + + #[cfg(curve25519_dalek_bits = "64")] + #[doc(hidden)] + pub mod u64; + + } +} + +pub mod curve_models; + +pub mod scalar_mul; diff --git a/curve25519-elligator2/src/backend/serial/scalar_mul/mod.rs b/curve25519-elligator2/src/backend/serial/scalar_mul/mod.rs new file mode 100644 index 00000000..71635372 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/scalar_mul/mod.rs @@ -0,0 +1,33 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Implementations of various scalar multiplication algorithms. +//! +//! Note that all of these implementations use serial code for field +//! arithmetic with the multi-model strategy described in the +//! `curve_models` module. The vectorized AVX2 backend has its own +//! scalar multiplication implementations, since it only uses one +//! curve model. + +#[allow(missing_docs)] +pub mod variable_base; + +#[allow(missing_docs)] +pub mod vartime_double_base; + +#[cfg(feature = "alloc")] +pub mod straus; + +#[cfg(feature = "alloc")] +pub mod precomputed_straus; + +#[cfg(feature = "alloc")] +pub mod pippenger; diff --git a/curve25519-elligator2/src/backend/serial/scalar_mul/pippenger.rs b/curve25519-elligator2/src/backend/serial/scalar_mul/pippenger.rs new file mode 100644 index 00000000..d3c13a17 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/scalar_mul/pippenger.rs @@ -0,0 +1,199 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2019 Oleg Andreev +// See LICENSE for licensing information. +// +// Authors: +// - Oleg Andreev + +//! Implementation of a variant of Pippenger's algorithm. + +#![allow(non_snake_case)] + +use alloc::vec::Vec; + +use core::borrow::Borrow; +use core::cmp::Ordering; + +use crate::edwards::EdwardsPoint; +use crate::scalar::Scalar; +use crate::traits::VartimeMultiscalarMul; + +/// Implements a version of Pippenger's algorithm. +/// +/// The algorithm works as follows: +/// +/// Let `n` be a number of point-scalar pairs. +/// Let `w` be a window of bits (6..8, chosen based on `n`, see cost factor). +/// +/// 1. Prepare `2^(w-1) - 1` buckets with indices `[1..2^(w-1))` initialized with identity points. +/// Bucket 0 is not needed as it would contain points multiplied by 0. +/// 2. Convert scalars to a radix-`2^w` representation with signed digits in `[-2^w/2, 2^w/2]`. +/// Note: only the last digit may equal `2^w/2`. +/// 3. Starting with the last window, for each point `i=[0..n)` add it to a a bucket indexed by +/// the point's scalar's value in the window. +/// 4. Once all points in a window are sorted into buckets, add buckets by multiplying each +/// by their index. Efficient way of doing it is to start with the last bucket and compute two sums: +/// intermediate sum from the last to the first, and the full sum made of all intermediate sums. +/// 5. Shift the resulting sum of buckets by `w` bits by using `w` doublings. +/// 6. Add to the return value. +/// 7. Repeat the loop. +/// +/// Approximate cost w/o wNAF optimizations (A = addition, D = doubling): +/// +/// ```ascii +/// cost = (n*A + 2*(2^w/2)*A + w*D + A)*256/w +/// | | | | | +/// | | | | looping over 256/w windows +/// | | | adding to the result +/// sorting points | shifting the sum by w bits (to the next window, starting from last window) +/// one by one | +/// into buckets adding/subtracting all buckets +/// multiplied by their indexes +/// using a sum of intermediate sums +/// ``` +/// +/// For large `n`, dominant factor is (n*256/w) additions. +/// However, if `w` is too big and `n` is not too big, then `(2^w/2)*A` could dominate. +/// Therefore, the optimal choice of `w` grows slowly as `n` grows. +/// +/// This algorithm is adapted from section 4 of . +pub struct Pippenger; + +impl VartimeMultiscalarMul for Pippenger { + type Point = EdwardsPoint; + + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>, + { + use crate::traits::Identity; + + let mut scalars = scalars.into_iter(); + let size = scalars.by_ref().size_hint().0; + + // Digit width in bits. As digit width grows, + // number of point additions goes down, but amount of + // buckets and bucket additions grows exponentially. + let w = if size < 500 { + 6 + } else if size < 800 { + 7 + } else { + 8 + }; + + let max_digit: usize = 1 << w; + let digits_count: usize = Scalar::to_radix_2w_size_hint(w); + let buckets_count: usize = max_digit / 2; // digits are signed+centered hence 2^w/2, excluding 0-th bucket + + // Collect optimized scalars and points in buffers for repeated access + // (scanning the whole set per digit position). + let scalars = scalars.map(|s| s.borrow().as_radix_2w(w)); + + let points = points + .into_iter() + .map(|p| p.map(|P| P.as_projective_niels())); + + let scalars_points = scalars + .zip(points) + .map(|(s, maybe_p)| maybe_p.map(|p| (s, p))) + .collect::>>()?; + + // Prepare 2^w/2 buckets. + // buckets[i] corresponds to a multiplication factor (i+1). + let mut buckets: Vec<_> = (0..buckets_count) + .map(|_| EdwardsPoint::identity()) + .collect(); + + let mut columns = (0..digits_count).rev().map(|digit_index| { + // Clear the buckets when processing another digit. + for bucket in &mut buckets { + *bucket = EdwardsPoint::identity(); + } + + // Iterate over pairs of (point, scalar) + // and add/sub the point to the corresponding bucket. + // Note: if we add support for precomputed lookup tables, + // we'll be adding/subtracting point premultiplied by `digits[i]` to buckets[0]. + for (digits, pt) in scalars_points.iter() { + // Widen digit so that we don't run into edge cases when w=8. + let digit = digits[digit_index] as i16; + match digit.cmp(&0) { + Ordering::Greater => { + let b = (digit - 1) as usize; + buckets[b] = (&buckets[b] + pt).as_extended(); + } + Ordering::Less => { + let b = (-digit - 1) as usize; + buckets[b] = (&buckets[b] - pt).as_extended(); + } + Ordering::Equal => {} + } + } + + // Add the buckets applying the multiplication factor to each bucket. + // The most efficient way to do that is to have a single sum with two running sums: + // an intermediate sum from last bucket to the first, and a sum of intermediate sums. + // + // For example, to add buckets 1*A, 2*B, 3*C we need to add these points: + // C + // C B + // C B A Sum = C + (C+B) + (C+B+A) + let mut buckets_intermediate_sum = buckets[buckets_count - 1]; + let mut buckets_sum = buckets[buckets_count - 1]; + for i in (0..(buckets_count - 1)).rev() { + buckets_intermediate_sum += buckets[i]; + buckets_sum += buckets_intermediate_sum; + } + + buckets_sum + }); + + // Take the high column as an initial value to avoid wasting time doubling the identity element in `fold()`. + let hi_column = columns.next().expect("should have more than zero digits"); + + Some(columns.fold(hi_column, |total, p| total.mul_by_pow_2(w as u32) + p)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::constants; + + #[test] + fn test_vartime_pippenger() { + // Reuse points across different tests + let mut n = 512; + let x = Scalar::from(2128506u64).invert(); + let y = Scalar::from(4443282u64).invert(); + let points: Vec<_> = (0..n) + .map(|i| constants::ED25519_BASEPOINT_POINT * Scalar::from(1 + i as u64)) + .collect(); + let scalars: Vec<_> = (0..n) + .map(|i| x + (Scalar::from(i as u64) * y)) // fast way to make ~random but deterministic scalars + .collect(); + + let premultiplied: Vec = scalars + .iter() + .zip(points.iter()) + .map(|(sc, pt)| sc * pt) + .collect(); + + while n > 0 { + let scalars = &scalars[0..n].to_vec(); + let points = &points[0..n].to_vec(); + let control: EdwardsPoint = premultiplied[0..n].iter().sum(); + + let subject = Pippenger::vartime_multiscalar_mul(scalars.clone(), points.clone()); + + assert_eq!(subject.compress(), control.compress()); + + n /= 2; + } + } +} diff --git a/curve25519-elligator2/src/backend/serial/scalar_mul/precomputed_straus.rs b/curve25519-elligator2/src/backend/serial/scalar_mul/precomputed_straus.rs new file mode 100644 index 00000000..2bc94b9b --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/scalar_mul/precomputed_straus.rs @@ -0,0 +1,120 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2019 Henry de Valence. +// See LICENSE for licensing information. +// +// Authors: +// - Henry de Valence + +//! Precomputation for Straus's method. + +#![allow(non_snake_case)] + +use alloc::vec::Vec; + +use core::borrow::Borrow; +use core::cmp::Ordering; + +use crate::backend::serial::curve_models::{ + AffineNielsPoint, CompletedPoint, ProjectiveNielsPoint, ProjectivePoint, +}; +use crate::edwards::EdwardsPoint; +use crate::scalar::Scalar; +use crate::traits::Identity; +use crate::traits::VartimePrecomputedMultiscalarMul; +use crate::window::{NafLookupTable5, NafLookupTable8}; + +#[allow(missing_docs)] +pub struct VartimePrecomputedStraus { + static_lookup_tables: Vec>, +} + +impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self { + static_lookup_tables: static_points + .into_iter() + .map(|P| NafLookupTable8::::from(P.borrow())) + .collect(), + } + } + + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>, + { + let static_nafs = static_scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect::>(); + let dynamic_nafs: Vec<_> = dynamic_scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect::>(); + + let dynamic_lookup_tables = dynamic_points + .into_iter() + .map(|P_opt| P_opt.map(|P| NafLookupTable5::::from(&P))) + .collect::>>()?; + + let sp = self.static_lookup_tables.len(); + let dp = dynamic_lookup_tables.len(); + assert_eq!(sp, static_nafs.len()); + assert_eq!(dp, dynamic_nafs.len()); + + // We could save some doublings by looking for the highest + // nonzero NAF coefficient, but since we might have a lot of + // them to search, it's not clear it's worthwhile to check. + let mut S = ProjectivePoint::identity(); + for j in (0..256).rev() { + let mut R: CompletedPoint = S.double(); + + for i in 0..dp { + let t_ij = dynamic_nafs[i][j]; + match t_ij.cmp(&0) { + Ordering::Greater => { + R = &R.as_extended() + &dynamic_lookup_tables[i].select(t_ij as usize) + } + Ordering::Less => { + R = &R.as_extended() - &dynamic_lookup_tables[i].select(-t_ij as usize) + } + Ordering::Equal => {} + } + } + + #[allow(clippy::needless_range_loop)] + for i in 0..sp { + let t_ij = static_nafs[i][j]; + match t_ij.cmp(&0) { + Ordering::Greater => { + R = &R.as_extended() + &self.static_lookup_tables[i].select(t_ij as usize) + } + Ordering::Less => { + R = &R.as_extended() - &self.static_lookup_tables[i].select(-t_ij as usize) + } + Ordering::Equal => {} + } + } + + S = R.as_projective(); + } + + Some(S.as_extended()) + } +} diff --git a/curve25519-elligator2/src/backend/serial/scalar_mul/straus.rs b/curve25519-elligator2/src/backend/serial/scalar_mul/straus.rs new file mode 100644 index 00000000..14fbf848 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/scalar_mul/straus.rs @@ -0,0 +1,201 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Implementation of the interleaved window method, also known as Straus' method. + +#![allow(non_snake_case)] + +use alloc::vec::Vec; + +use core::borrow::Borrow; +use core::cmp::Ordering; + +use crate::edwards::EdwardsPoint; +use crate::scalar::Scalar; +use crate::traits::MultiscalarMul; +use crate::traits::VartimeMultiscalarMul; + +/// Perform multiscalar multiplication by the interleaved window +/// method, also known as Straus' method (since it was apparently +/// [first published][solution] by Straus in 1964, as a solution to [a +/// problem][problem] posted in the American Mathematical Monthly in +/// 1963). +/// +/// It is easy enough to reinvent, and has been repeatedly. The basic +/// idea is that when computing +/// \\[ +/// Q = s_1 P_1 + \cdots + s_n P_n +/// \\] +/// by means of additions and doublings, the doublings can be shared +/// across the \\( P_i \\\). +/// +/// We implement two versions, a constant-time algorithm using fixed +/// windows and a variable-time algorithm using sliding windows. They +/// are slight variations on the same idea, and are described in more +/// detail in the respective implementations. +/// +/// [solution]: https://www.jstor.org/stable/2310929 +/// [problem]: https://www.jstor.org/stable/2312273 +pub struct Straus {} + +impl MultiscalarMul for Straus { + type Point = EdwardsPoint; + + /// Constant-time Straus using a fixed window of size \\(4\\). + /// + /// Our goal is to compute + /// \\[ + /// Q = s_1 P_1 + \cdots + s_n P_n. + /// \\] + /// + /// For each point \\( P_i \\), precompute a lookup table of + /// \\[ + /// P_i, 2P_i, 3P_i, 4P_i, 5P_i, 6P_i, 7P_i, 8P_i. + /// \\] + /// + /// For each scalar \\( s_i \\), compute its radix-\\(2^4\\) + /// signed digits \\( s_{i,j} \\), i.e., + /// \\[ + /// s_i = s_{i,0} + s_{i,1} 16^1 + ... + s_{i,63} 16^{63}, + /// \\] + /// with \\( -8 \leq s_{i,j} < 8 \\). Since \\( 0 \leq |s_{i,j}| + /// \leq 8 \\), we can retrieve \\( s_{i,j} P_i \\) from the + /// lookup table with a conditional negation: using signed + /// digits halves the required table size. + /// + /// Then as in the single-base fixed window case, we have + /// \\[ + /// \begin{aligned} + /// s_i P_i &= P_i (s_{i,0} + s_{i,1} 16^1 + \cdots + s_{i,63} 16^{63}) \\\\ + /// s_i P_i &= P_i s_{i,0} + P_i s_{i,1} 16^1 + \cdots + P_i s_{i,63} 16^{63} \\\\ + /// s_i P_i &= P_i s_{i,0} + 16(P_i s_{i,1} + 16( \cdots +16P_i s_{i,63})\cdots ) + /// \end{aligned} + /// \\] + /// so each \\( s_i P_i \\) can be computed by alternately adding + /// a precomputed multiple \\( P_i s_{i,j} \\) of \\( P_i \\) and + /// repeatedly doubling. + /// + /// Now consider the two-dimensional sum + /// \\[ + /// \begin{aligned} + /// s\_1 P\_1 &=& P\_1 s\_{1,0} &+& 16 (P\_1 s\_{1,1} &+& 16 ( \cdots &+& 16 P\_1 s\_{1,63}&) \cdots ) \\\\ + /// + & & + & & + & & & & + & \\\\ + /// s\_2 P\_2 &=& P\_2 s\_{2,0} &+& 16 (P\_2 s\_{2,1} &+& 16 ( \cdots &+& 16 P\_2 s\_{2,63}&) \cdots ) \\\\ + /// + & & + & & + & & & & + & \\\\ + /// \vdots & & \vdots & & \vdots & & & & \vdots & \\\\ + /// + & & + & & + & & & & + & \\\\ + /// s\_n P\_n &=& P\_n s\_{n,0} &+& 16 (P\_n s\_{n,1} &+& 16 ( \cdots &+& 16 P\_n s\_{n,63}&) \cdots ) + /// \end{aligned} + /// \\] + /// The sum of the left-hand column is the result \\( Q \\); by + /// computing the two-dimensional sum on the right column-wise, + /// top-to-bottom, then right-to-left, we need to multiply by \\( + /// 16\\) only once per column, sharing the doublings across all + /// of the input points. + fn multiscalar_mul(scalars: I, points: J) -> EdwardsPoint + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + { + use crate::backend::serial::curve_models::ProjectiveNielsPoint; + use crate::traits::Identity; + use crate::window::LookupTable; + + let lookup_tables: Vec<_> = points + .into_iter() + .map(|point| LookupTable::::from(point.borrow())) + .collect(); + + // This puts the scalar digits into a heap-allocated Vec. + // To ensure that these are erased, pass ownership of the Vec into a + // Zeroizing wrapper. + #[cfg_attr(not(feature = "zeroize"), allow(unused_mut))] + let mut scalar_digits: Vec<_> = scalars + .into_iter() + .map(|s| s.borrow().as_radix_16()) + .collect(); + + let mut Q = EdwardsPoint::identity(); + for j in (0..64).rev() { + Q = Q.mul_by_pow_2(4); + let it = scalar_digits.iter().zip(lookup_tables.iter()); + for (s_i, lookup_table_i) in it { + // R_i = s_{i,j} * P_i + let R_i = lookup_table_i.select(s_i[j]); + // Q = Q + R_i + Q = (&Q + &R_i).as_extended(); + } + } + + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(&mut scalar_digits); + + Q + } +} + +impl VartimeMultiscalarMul for Straus { + type Point = EdwardsPoint; + + /// Variable-time Straus using a non-adjacent form of width \\(5\\). + /// + /// This is completely similar to the constant-time code, but we + /// use a non-adjacent form for the scalar, and do not do table + /// lookups in constant time. + /// + /// The non-adjacent form has signed, odd digits. Using only odd + /// digits halves the table size (since we only need odd + /// multiples), or gives fewer additions for the same table size. + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>, + { + use crate::backend::serial::curve_models::{ + CompletedPoint, ProjectiveNielsPoint, ProjectivePoint, + }; + use crate::traits::Identity; + use crate::window::NafLookupTable5; + + let nafs: Vec<_> = scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect(); + + let lookup_tables = points + .into_iter() + .map(|P_opt| P_opt.map(|P| NafLookupTable5::::from(&P))) + .collect::>>()?; + + let mut r = ProjectivePoint::identity(); + + for i in (0..256).rev() { + let mut t: CompletedPoint = r.double(); + + for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) { + match naf[i].cmp(&0) { + Ordering::Greater => { + t = &t.as_extended() + &lookup_table.select(naf[i] as usize) + } + Ordering::Less => t = &t.as_extended() - &lookup_table.select(-naf[i] as usize), + Ordering::Equal => {} + } + } + + r = t.as_projective(); + } + + Some(r.as_extended()) + } +} diff --git a/curve25519-elligator2/src/backend/serial/scalar_mul/variable_base.rs b/curve25519-elligator2/src/backend/serial/scalar_mul/variable_base.rs new file mode 100644 index 00000000..1de84bc4 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/scalar_mul/variable_base.rs @@ -0,0 +1,48 @@ +#![allow(non_snake_case)] + +use crate::backend::serial::curve_models::ProjectiveNielsPoint; +use crate::edwards::EdwardsPoint; +use crate::scalar::Scalar; +use crate::traits::Identity; +use crate::window::LookupTable; + +/// Perform constant-time, variable-base scalar multiplication. +#[rustfmt::skip] // keep alignment of explanatory comments +pub(crate) fn mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint { + // Construct a lookup table of [P,2P,3P,4P,5P,6P,7P,8P] + let lookup_table = LookupTable::::from(point); + // Setting s = scalar, compute + // + // s = s_0 + s_1*16^1 + ... + s_63*16^63, + // + // with `-8 ≤ s_i < 8` for `0 ≤ i < 63` and `-8 ≤ s_63 ≤ 8`. + // This decomposition requires s < 2^255, which is guaranteed by Scalar invariant #1. + let scalar_digits = scalar.as_radix_16(); + // Compute s*P as + // + // s*P = P*(s_0 + s_1*16^1 + s_2*16^2 + ... + s_63*16^63) + // s*P = P*s_0 + P*s_1*16^1 + P*s_2*16^2 + ... + P*s_63*16^63 + // s*P = P*s_0 + 16*(P*s_1 + 16*(P*s_2 + 16*( ... + P*s_63)...)) + // + // We sum right-to-left. + + // Unwrap first loop iteration to save computing 16*identity + let mut tmp2; + let mut tmp3 = EdwardsPoint::identity(); + let mut tmp1 = &tmp3 + &lookup_table.select(scalar_digits[63]); + // Now tmp1 = s_63*P in P1xP1 coords + for i in (0..63).rev() { + tmp2 = tmp1.as_projective(); // tmp2 = (prev) in P2 coords + tmp1 = tmp2.double(); // tmp1 = 2*(prev) in P1xP1 coords + tmp2 = tmp1.as_projective(); // tmp2 = 2*(prev) in P2 coords + tmp1 = tmp2.double(); // tmp1 = 4*(prev) in P1xP1 coords + tmp2 = tmp1.as_projective(); // tmp2 = 4*(prev) in P2 coords + tmp1 = tmp2.double(); // tmp1 = 8*(prev) in P1xP1 coords + tmp2 = tmp1.as_projective(); // tmp2 = 8*(prev) in P2 coords + tmp1 = tmp2.double(); // tmp1 = 16*(prev) in P1xP1 coords + tmp3 = tmp1.as_extended(); // tmp3 = 16*(prev) in P3 coords + tmp1 = &tmp3 + &lookup_table.select(scalar_digits[i]); + // Now tmp1 = s_i*P + 16*(prev) in P1xP1 coords + } + tmp1.as_extended() +} diff --git a/curve25519-elligator2/src/backend/serial/scalar_mul/vartime_double_base.rs b/curve25519-elligator2/src/backend/serial/scalar_mul/vartime_double_base.rs new file mode 100644 index 00000000..ffb983a2 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/scalar_mul/vartime_double_base.rs @@ -0,0 +1,72 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence +#![allow(non_snake_case)] + +use core::cmp::Ordering; + +use crate::backend::serial::curve_models::{ProjectiveNielsPoint, ProjectivePoint}; +use crate::constants; +use crate::edwards::EdwardsPoint; +use crate::scalar::Scalar; +use crate::traits::Identity; +use crate::window::NafLookupTable5; + +/// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint. +pub fn mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint { + let a_naf = a.non_adjacent_form(5); + + #[cfg(feature = "precomputed-tables")] + let b_naf = b.non_adjacent_form(8); + #[cfg(not(feature = "precomputed-tables"))] + let b_naf = b.non_adjacent_form(5); + + // Find starting index + let mut i: usize = 255; + for j in (0..256).rev() { + i = j; + if a_naf[i] != 0 || b_naf[i] != 0 { + break; + } + } + + let table_A = NafLookupTable5::::from(A); + #[cfg(feature = "precomputed-tables")] + let table_B = &constants::AFFINE_ODD_MULTIPLES_OF_BASEPOINT; + #[cfg(not(feature = "precomputed-tables"))] + let table_B = + &NafLookupTable5::::from(&constants::ED25519_BASEPOINT_POINT); + + let mut r = ProjectivePoint::identity(); + loop { + let mut t = r.double(); + + match a_naf[i].cmp(&0) { + Ordering::Greater => t = &t.as_extended() + &table_A.select(a_naf[i] as usize), + Ordering::Less => t = &t.as_extended() - &table_A.select(-a_naf[i] as usize), + Ordering::Equal => {} + } + + match b_naf[i].cmp(&0) { + Ordering::Greater => t = &t.as_extended() + &table_B.select(b_naf[i] as usize), + Ordering::Less => t = &t.as_extended() - &table_B.select(-b_naf[i] as usize), + Ordering::Equal => {} + } + + r = t.as_projective(); + + if i == 0 { + break; + } + i -= 1; + } + + r.as_extended() +} diff --git a/curve25519-elligator2/src/backend/serial/u32/constants.rs b/curve25519-elligator2/src/backend/serial/u32/constants.rs new file mode 100644 index 00000000..6f8a2363 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/u32/constants.rs @@ -0,0 +1,4803 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! This module contains various constants (such as curve parameters +//! and useful field elements like `sqrt(-1)`), as well as +//! lookup tables of pre-computed points. + +use super::field::FieldElement2625; +use super::scalar::Scalar29; +use crate::edwards::EdwardsPoint; + +#[cfg(feature = "precomputed-tables")] +use crate::{ + backend::serial::curve_models::AffineNielsPoint, + edwards::EdwardsBasepointTable, + window::{LookupTable, NafLookupTable8}, +}; + +/// The value of minus one, equal to `-&FieldElement::ONE` +pub(crate) const MINUS_ONE: FieldElement2625 = FieldElement2625::from_limbs([ + 67108844, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, + 33554431, +]); + +/// Edwards `d` value, equal to `-121665/121666 mod p`. +pub(crate) const EDWARDS_D: FieldElement2625 = FieldElement2625::from_limbs([ + 56195235, 13857412, 51736253, 6949390, 114729, 24766616, 60832955, 30306712, 48412415, 21499315, +]); + +/// Edwards `2*d` value, equal to `2*(-121665/121666) mod p`. +pub(crate) const EDWARDS_D2: FieldElement2625 = FieldElement2625::from_limbs([ + 45281625, 27714825, 36363642, 13898781, 229458, 15978800, 54557047, 27058993, 29715967, 9444199, +]); + +/// One minus edwards `d` value squared, equal to `(1 - (-121665/121666) mod p) pow 2` +pub(crate) const ONE_MINUS_EDWARDS_D_SQUARED: FieldElement2625 = FieldElement2625::from_limbs([ + 6275446, 16937061, 44170319, 29780721, 11667076, 7397348, 39186143, 1766194, 42675006, 672202, +]); + +/// Edwards `d` value minus one squared, equal to `(((-121665/121666) mod p) - 1) pow 2` +pub(crate) const EDWARDS_D_MINUS_ONE_SQUARED: FieldElement2625 = FieldElement2625::from_limbs([ + 15551776, 22456977, 53683765, 23429360, 55212328, 10178283, 40474537, 4729243, 61826754, + 23438029, +]); + +/// `= sqrt(a*d - 1)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters. +pub(crate) const SQRT_AD_MINUS_ONE: FieldElement2625 = FieldElement2625::from_limbs([ + 24849947, 33400850, 43495378, 6347714, 46036536, 32887293, 41837720, 18186727, 66238516, + 14525638, +]); + +/// `= 1/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters. +pub(crate) const INVSQRT_A_MINUS_D: FieldElement2625 = FieldElement2625::from_limbs([ + 6111466, 4156064, 39310137, 12243467, 41204824, 120896, 20826367, 26493656, 6093567, 31568420, +]); + +/// Precomputed value of one of the square roots of -1 (mod p) +pub(crate) const SQRT_M1: FieldElement2625 = FieldElement2625::from_limbs([ + 34513072, 25610706, 9377949, 3500415, 12389472, 33281959, 41962654, 31548777, 326685, 11406482, +]); + +/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within the Montgomery ladder.) +pub(crate) const APLUS2_OVER_FOUR: FieldElement2625 = + FieldElement2625::from_limbs([121666, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + +#[cfg(feature = "elligator2")] +/// `MONTGOMERY_A` is equal to 486662, which is a constant of the curve equation +/// for Curve25519 in its Montgomery form. (This is used internally within the +/// Elligator map.) +pub(crate) const MONTGOMERY_A: FieldElement2625 = + FieldElement2625::from_limbs([486662, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + +#[cfg(feature = "elligator2")] +/// `MONTGOMERY_A_NEG` is equal to -486662. (This is used internally within the +/// Elligator map.) +pub(crate) const MONTGOMERY_A_NEG: FieldElement2625 = FieldElement2625::from_limbs([ + 66622183, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, + 33554431, +]); + +/// `L` is the order of base point, i.e. 2^252 + +/// 27742317777372353535851937790883648493 +pub(crate) const L: Scalar29 = Scalar29([ + 0x1cf5d3ed, 0x009318d2, 0x1de73596, 0x1df3bd45, 0x0000014d, 0x00000000, 0x00000000, 0x00000000, + 0x00100000, +]); + +/// `L` * `LFACTOR` = -1 (mod 2^29) +pub(crate) const LFACTOR: u32 = 0x12547e1b; + +/// `R` = R % L where R = 2^261 +pub(crate) const R: Scalar29 = Scalar29([ + 0x114df9ed, 0x1a617303, 0x0f7c098c, 0x16793167, 0x1ffd656e, 0x1fffffff, 0x1fffffff, 0x1fffffff, + 0x000fffff, +]); + +/// `RR` = (R^2) % L where R = 2^261 +pub(crate) const RR: Scalar29 = Scalar29([ + 0x0b5f9d12, 0x1e141b17, 0x158d7f3d, 0x143f3757, 0x1972d781, 0x042feb7c, 0x1ceec73d, 0x1e184d1e, + 0x0005046d, +]); + +/// The Ed25519 basepoint, as an `EdwardsPoint`. +/// +/// This is called `_POINT` to distinguish it from +/// `ED25519_BASEPOINT_TABLE`, which should be used for scalar +/// multiplication (it's much faster). +pub const ED25519_BASEPOINT_POINT: EdwardsPoint = EdwardsPoint { + X: FieldElement2625::from_limbs([ + 52811034, 25909283, 16144682, 17082669, 27570973, 30858332, 40966398, 8378388, 20764389, + 8758491, + ]), + Y: FieldElement2625::from_limbs([ + 40265304, 26843545, 13421772, 20132659, 26843545, 6710886, 53687091, 13421772, 40265318, + 26843545, + ]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([ + 28827043, 27438313, 39759291, 244362, 8635006, 11264893, 19351346, 13413597, 16611511, + 27139452, + ]), +}; + +/// The 8-torsion subgroup \\(\mathcal E \[8\]\\). +/// +/// In the case of Curve25519, it is cyclic; the \\(i\\)-th element of +/// the array is \\([i]P\\), where \\(P\\) is a point of order \\(8\\) +/// generating \\(\mathcal E\[8\]\\). +/// +/// Thus \\(\mathcal E\[4\]\\) is the points indexed by `0,2,4,6`, and +/// \\(\mathcal E\[2\]\\) is the points indexed by `0,4`. +/// The Ed25519 basepoint has y = 4/5. This is called `_POINT` to +/// distinguish it from `_TABLE`, which should be used for scalar +/// multiplication (it's much faster). +pub const EIGHT_TORSION: [EdwardsPoint; 8] = EIGHT_TORSION_INNER_DOC_HIDDEN; + +/// Inner item used to hide limb constants from cargo doc output. +#[doc(hidden)] +pub const EIGHT_TORSION_INNER_DOC_HIDDEN: [EdwardsPoint; 8] = [ + EdwardsPoint { + X: FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Y: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + }, + EdwardsPoint { + X: FieldElement2625::from_limbs([ + 21352778, 5345713, 4660180, 25206575, 24143089, 14568123, 30185756, 21306662, 33579924, + 8345318, + ]), + Y: FieldElement2625::from_limbs([ + 6952903, 1265500, 60246523, 7057497, 4037696, 5447722, 35427965, 15325401, 19365852, + 31985330, + ]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([ + 41846657, 21581751, 11716001, 27684820, 48915701, 16297738, 20670665, 24995334, + 3541542, 28543251, + ]), + }, + EdwardsPoint { + X: FieldElement2625::from_limbs([ + 32595773, 7943725, 57730914, 30054016, 54719391, 272472, 25146209, 2005654, 66782178, + 22147949, + ]), + Y: FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + }, + EdwardsPoint { + X: FieldElement2625::from_limbs([ + 21352778, 5345713, 4660180, 25206575, 24143089, 14568123, 30185756, 21306662, 33579924, + 8345318, + ]), + Y: FieldElement2625::from_limbs([ + 60155942, 32288931, 6862340, 26496934, 63071167, 28106709, 31680898, 18229030, + 47743011, 1569101, + ]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([ + 25262188, 11972680, 55392862, 5869611, 18193162, 17256693, 46438198, 8559097, 63567321, + 5011180, + ]), + }, + EdwardsPoint { + X: FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Y: FieldElement2625::from_limbs([ + 67108844, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, 33554431, + 67108863, 33554431, + ]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + }, + EdwardsPoint { + X: FieldElement2625::from_limbs([ + 45756067, 28208718, 62448683, 8347856, 42965774, 18986308, 36923107, 12247769, + 33528939, 25209113, + ]), + Y: FieldElement2625::from_limbs([ + 60155942, 32288931, 6862340, 26496934, 63071167, 28106709, 31680898, 18229030, + 47743011, 1569101, + ]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([ + 41846657, 21581751, 11716001, 27684820, 48915701, 16297738, 20670665, 24995334, + 3541542, 28543251, + ]), + }, + EdwardsPoint { + X: FieldElement2625::from_limbs([ + 34513072, 25610706, 9377949, 3500415, 12389472, 33281959, 41962654, 31548777, 326685, + 11406482, + ]), + Y: FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + }, + EdwardsPoint { + X: FieldElement2625::from_limbs([ + 45756067, 28208718, 62448683, 8347856, 42965774, 18986308, 36923107, 12247769, + 33528939, 25209113, + ]), + Y: FieldElement2625::from_limbs([ + 6952903, 1265500, 60246523, 7057497, 4037696, 5447722, 35427965, 15325401, 19365852, + 31985330, + ]), + Z: FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + T: FieldElement2625::from_limbs([ + 25262188, 11972680, 55392862, 5869611, 18193162, 17256693, 46438198, 8559097, 63567321, + 5011180, + ]), + }, +]; + +/// Table containing precomputed multiples of the Ed25519 basepoint \\(B = (x, 4/5)\\). +#[cfg(feature = "precomputed-tables")] +pub static ED25519_BASEPOINT_TABLE: &'static EdwardsBasepointTable = + &ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN; + +/// Inner constant, used to avoid filling the docs with precomputed points. +#[doc(hidden)] +#[cfg(feature = "precomputed-tables")] +static ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN: EdwardsBasepointTable = EdwardsBasepointTable([ + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 93076338, 52752828, 29566454, 37215328, 54414518, 37569218, 94653489, 21800160, + 61029707, 35602036, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 54563134, 934261, 64385954, 3049989, 66381436, 9406985, 12720692, 5043384, + 19500929, 18085054, + ]), + xy2d: FieldElement2625::from_limbs([ + 58370664, 4489569, 9688441, 18769238, 10184608, 21191052, 29287918, 11864899, + 42594502, 29115885, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54292951, 54132516, 45527619, 11784319, 41753206, 30803714, 55390960, 29739860, + 66750418, 23343128, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 45405608, 6903824, 27185491, 6451973, 37531140, 24000426, 51492312, 11189267, + 40279186, 28235350, + ]), + xy2d: FieldElement2625::from_limbs([ + 26966623, 11152617, 32442495, 15396054, 14353839, 20802097, 63980037, 24013313, + 51636816, 29387734, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 82745136, 23865874, 24204772, 25642034, 67725840, 16869169, 94896463, 52336674, + 28944398, 32004408, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 16568933, 4717097, 55552716, 32452109, 15682895, 21747389, 16354576, 21778470, + 7689661, 11199574, + ]), + xy2d: FieldElement2625::from_limbs([ + 30464137, 27578307, 55329429, 17883566, 23220364, 15915852, 7512774, 10017326, + 49359771, 23634074, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 50071967, 13921891, 78054670, 27521000, 27105051, 17470053, 105291517, 15006021, + 70393432, 27277891, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 23599295, 25248385, 55915199, 25867015, 13236773, 10506355, 7464579, 9656445, + 13059162, 10374397, + ]), + xy2d: FieldElement2625::from_limbs([ + 7798537, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, 29715387, + 66467155, 33453106, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77970208, 11473153, 27284546, 35535607, 37044514, 46132292, 99976748, 48069538, + 118779423, 44373810, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 4708026, 6336745, 20377586, 9066809, 55836755, 6594695, 41455196, 12483687, + 54440373, 5581305, + ]), + xy2d: FieldElement2625::from_limbs([ + 19563141, 16186464, 37722007, 4097518, 10237984, 29206317, 28542349, 13850243, + 43430843, 17738489, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 51736881, 20691677, 32573249, 4720197, 107781206, 39429941, 115029100, 18329611, + 124398787, 21468653, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 58559652, 109982, 15149363, 2178705, 22900618, 4543417, 3044240, 17864545, 1762327, + 14866737, + ]), + xy2d: FieldElement2625::from_limbs([ + 48909169, 17603008, 56635573, 1707277, 49922944, 3916100, 38872452, 3959420, + 27914454, 4383652, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 72262591, 43463716, 68832610, 30776557, 97632468, 39071304, 86589715, 38784565, + 43156424, 18378665, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 36839857, 30090922, 7665485, 10083793, 28475525, 1649722, 20654025, 16520125, + 30598449, 7715701, + ]), + xy2d: FieldElement2625::from_limbs([ + 28881826, 14381568, 9657904, 3680757, 46927229, 7843315, 35708204, 1370707, + 29794553, 32145132, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 14499452, 64379265, 33917749, 62854211, 95603724, 14271266, 97399599, 10876453, + 33954766, 35936157, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 59913433, 30899068, 52378708, 462250, 39384538, 3941371, 60872247, 3696004, + 34808032, 15351954, + ]), + xy2d: FieldElement2625::from_limbs([ + 27431194, 8222322, 16448760, 29646437, 48401861, 11938354, 34147463, 30583916, + 29551812, 10109425, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 53451805, 20399000, 102933977, 45331528, 88556249, 40073815, 64730579, 31926875, + 77201646, 28790260, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 27939166, 14210322, 4677035, 16277044, 44144402, 21156292, 34600109, 12005537, + 49298737, 12803509, + ]), + xy2d: FieldElement2625::from_limbs([ + 17228999, 17892808, 65875336, 300139, 65883994, 21839654, 30364212, 24516238, + 18016356, 4397660, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 56150002, 25864224, 4776340, 18600194, 27850027, 17952220, 40489757, 14544524, + 49631360, 34537070, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 29253598, 15796703, 64244882, 23645547, 10057022, 3163536, 7332899, 29434304, + 46061167, 9934962, + ]), + xy2d: FieldElement2625::from_limbs([ + 5793284, 16271923, 42977250, 23438027, 29188559, 1206517, 52360934, 4559894, + 36984942, 22656481, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 39464893, 55615857, 83391519, 22517938, 28414020, 52096600, 24191032, 38096129, + 53770554, 39054999, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 12650548, 32057319, 9052870, 11355358, 49428827, 25154267, 49678271, 12264342, + 10874051, 13524335, + ]), + xy2d: FieldElement2625::from_limbs([ + 25556948, 30508442, 714650, 2510400, 23394682, 23139102, 33119037, 5080568, + 44580805, 5376627, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 108129445, 29543378, 50095164, 30016803, 60382070, 35475328, 44787558, 57661420, + 71644630, 35123438, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 64853442, 14606629, 45416424, 25514613, 28430648, 8775819, 36614302, 3044289, + 31848280, 12543772, + ]), + xy2d: FieldElement2625::from_limbs([ + 45080285, 2943892, 35251351, 6777305, 13784462, 29262229, 39731668, 31491700, + 7718481, 14474653, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 69494160, 36008644, 44477543, 33601034, 62670928, 51428448, 67765827, 26317766, + 91425031, 28300864, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 13741529, 10911568, 33875447, 24950694, 46931033, 32521134, 33040650, 20129900, + 46379407, 8321685, + ]), + xy2d: FieldElement2625::from_limbs([ + 21060490, 31341688, 15712756, 29218333, 1639039, 10656336, 23845965, 21679594, + 57124405, 608371, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 53436113, 18466845, 56219170, 25997372, 61071954, 11305546, 68232832, 60328286, + 94338261, 33578318, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 43864724, 33260226, 55364135, 14712570, 37643165, 31524814, 12797023, 27114124, + 65475458, 16678953, + ]), + xy2d: FieldElement2625::from_limbs([ + 37608244, 4770661, 51054477, 14001337, 7830047, 9564805, 65600720, 28759386, + 49939598, 4904952, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 91168402, 48171434, 86146020, 18514523, 86874956, 18648002, 72278074, 16191879, + 69237100, 29227598, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 50127693, 4124965, 58568254, 22900634, 30336521, 19449185, 37302527, 916032, + 60226322, 30567899, + ]), + xy2d: FieldElement2625::from_limbs([ + 44477957, 12419371, 59974635, 26081060, 50629959, 16739174, 285431, 2763829, + 15736322, 4143876, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 69488197, 11839344, 62998462, 27565766, 78383161, 34349388, 67321664, 18959768, + 23527083, 17096164, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 33431108, 22423954, 49269897, 17927531, 8909498, 8376530, 34483524, 4087880, + 51919953, 19138217, + ]), + xy2d: FieldElement2625::from_limbs([ + 1767664, 7197987, 53903638, 31531796, 54017513, 448825, 5799055, 4357868, 62334673, + 17231393, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 6721947, 47388255, 43585475, 32003117, 93463156, 21691110, 90474010, 29604699, + 74499753, 36314231, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 4409022, 2052381, 23373853, 10530217, 7676779, 20668478, 21302352, 29290375, + 1244379, 20634787, + ]), + xy2d: FieldElement2625::from_limbs([ + 62687625, 7169618, 4982368, 30596842, 30256824, 30776892, 14086412, 9208236, + 15886429, 16489664, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 69104920, 43930080, 81455230, 46865633, 60234728, 17116020, 120524529, 33952799, + 36502408, 32841498, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 41801399, 9795879, 64331450, 14878808, 33577029, 14780362, 13348553, 12076947, + 36272402, 5113181, + ]), + xy2d: FieldElement2625::from_limbs([ + 49338080, 11797795, 31950843, 13929123, 41220562, 12288343, 36767763, 26218045, + 13847710, 5387222, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 48526682, 30138214, 84933706, 64767897, 89853205, 56666252, 75871923, 37172217, + 47508201, 43925422, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 20246567, 19185054, 22358228, 33010720, 18507282, 23140436, 14554436, 24808340, + 32232923, 16763880, + ]), + xy2d: FieldElement2625::from_limbs([ + 9648486, 10094563, 26416693, 14745928, 36734546, 27081810, 11094160, 15689506, + 3140038, 17044340, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 50948773, 39027126, 31895587, 38299426, 75932378, 43920116, 39884063, 43003044, + 38334409, 33920726, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 19153450, 11523972, 56012374, 27051289, 42461232, 5420646, 28344573, 8041113, + 719605, 11671788, + ]), + xy2d: FieldElement2625::from_limbs([ + 8678006, 2694440, 60300850, 2517371, 4964326, 11152271, 51675948, 18287915, + 27000812, 23358879, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 119059805, 40688742, 75748150, 30739554, 59873175, 43976173, 67672928, 38890528, + 73859840, 19033405, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 11836410, 29574944, 26297893, 16080799, 23455045, 15735944, 1695823, 24735310, + 8169719, 16220347, + ]), + xy2d: FieldElement2625::from_limbs([ + 48993007, 8653646, 17578566, 27461813, 59083086, 17541668, 55964556, 30926767, + 61118155, 19388398, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 43800347, 22586119, 82322091, 23473217, 36255258, 22504427, 27884328, 36401716, + 69764724, 35292826, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 39571412, 19301410, 41772562, 25551651, 57738101, 8129820, 21651608, 30315096, + 48021414, 22549153, + ]), + xy2d: FieldElement2625::from_limbs([ + 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, + 10478196, 8544890, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 32173083, 50979553, 24896205, 37475929, 22579055, 63698010, 19270447, 45771905, + 84897880, 63712868, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 36555903, 31326030, 51530034, 23407230, 13243888, 517024, 15479401, 29701199, + 30460519, 1052596, + ]), + xy2d: FieldElement2625::from_limbs([ + 55493970, 13323617, 32618793, 8175907, 51878691, 12596686, 27491595, 28942073, + 3179267, 24075541, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 99055914, 52742212, 62468279, 18214510, 51982886, 27514722, 52352086, 17142691, + 19072639, 24043372, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 11685058, 11822410, 3158003, 19601838, 33402193, 29389366, 5977895, 28339415, + 473098, 5040608, + ]), + xy2d: FieldElement2625::from_limbs([ + 46817982, 8198641, 39698732, 11602122, 1290375, 30754672, 28326861, 1721092, + 47550222, 30422825, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 74990396, 10687936, 74687587, 7738377, 48157852, 31000479, 88929649, 8076148, + 39240368, 11538388, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 47173198, 3899860, 18283497, 26752864, 51380203, 22305220, 8754524, 7446702, + 61432810, 5797015, + ]), + xy2d: FieldElement2625::from_limbs([ + 55813245, 29760862, 51326753, 25589858, 12708868, 25098233, 2014098, 24503858, + 64739691, 27677090, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 111745333, 55540121, 106535706, 34700805, 86065554, 50194990, 68301593, 29840232, + 82232482, 44365936, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 14352079, 30134717, 48166819, 10822654, 32750596, 4699007, 67038501, 15776355, + 38222085, 21579878, + ]), + xy2d: FieldElement2625::from_limbs([ + 38867681, 25481956, 62129901, 28239114, 29416930, 1847569, 46454691, 17069576, + 4714546, 23953777, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 15200313, 41923004, 86787964, 15970073, 35236190, 35513882, 24611598, 29010600, + 55362987, 45894651, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 12876937, 23074376, 33134380, 6590940, 60801088, 14872439, 9613953, 8241152, + 15370987, 9608631, + ]), + xy2d: FieldElement2625::from_limbs([ + 62965568, 21540023, 8446280, 33162829, 4407737, 13629032, 59383996, 15866073, + 38898243, 24740332, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 26660609, 51431209, 75502596, 33912478, 59707572, 34547419, 43204630, 34413128, + 87680086, 41974987, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 14620696, 13067227, 51661590, 8264466, 14106269, 15080814, 33531827, 12516406, + 45534429, 21077682, + ]), + xy2d: FieldElement2625::from_limbs([ + 236881, 10476226, 57258, 18877408, 6472997, 2466984, 17258519, 7256740, 8791136, + 15069930, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 68385255, 24182513, 90058498, 17231624, 43615824, 61406677, 81820737, 38428660, + 36445723, 31223040, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 5855666, 4990204, 53397016, 7294283, 59304582, 1924646, 65685689, 25642053, + 34039526, 9234252, + ]), + xy2d: FieldElement2625::from_limbs([ + 20590503, 24535444, 31529743, 26201766, 64402029, 10650547, 31559055, 21944845, + 18979185, 13396066, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 24474268, 38522535, 22267081, 37961786, 91172745, 25229251, 48291976, 13594781, + 33514650, 40576390, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 55541958, 26988926, 45743778, 15928891, 40950559, 4315420, 41160136, 29637754, + 45628383, 12868081, + ]), + xy2d: FieldElement2625::from_limbs([ + 38473832, 13504660, 19988037, 31421671, 21078224, 6443208, 45662757, 2244499, + 54653067, 25465048, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 36513317, 13793478, 61256044, 33873567, 41385691, 60844964, 100195408, 8957936, + 51875216, 39094952, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 55478669, 22050529, 58989363, 25911358, 2620055, 1022908, 43398120, 31985447, + 50980335, 18591624, + ]), + xy2d: FieldElement2625::from_limbs([ + 23152952, 775386, 27395463, 14006635, 57407746, 4649511, 1689819, 892185, 55595587, + 18348483, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 76878974, 43141169, 93604957, 37878551, 68665374, 30004407, 94562682, 38317558, + 47929249, 39421565, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 34343820, 1927589, 31726409, 28801137, 23962433, 17534932, 27846558, 5931263, + 37359161, 17445976, + ]), + xy2d: FieldElement2625::from_limbs([ + 27461885, 30576896, 22380809, 1815854, 44075111, 30522493, 7283489, 18406359, + 47582163, 7734628, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 59098581, 57518046, 55988459, 39750469, 29344157, 20123547, 74694158, 30377805, + 85658360, 48856500, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 34450527, 27383209, 59436070, 22502750, 6258877, 13504381, 10458790, 27135971, + 58236621, 8424745, + ]), + xy2d: FieldElement2625::from_limbs([ + 24687186, 8613276, 36441818, 30320886, 1863891, 31723888, 19206233, 7134917, + 55824382, 32725512, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 11334880, 24336410, 75134156, 46261950, 84632755, 23078360, 77352601, 18868970, + 62042829, 50053268, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 8911542, 6887158, 57524604, 26595841, 11145640, 24010752, 17303924, 19430194, + 6536640, 10543906, + ]), + xy2d: FieldElement2625::from_limbs([ + 38162480, 15479762, 49642029, 568875, 65611181, 11223453, 64439674, 16928857, + 39873154, 8876770, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 41365946, 54541999, 118567760, 32707823, 101191041, 32758142, 33627041, 15824473, + 66504438, 24514614, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10330056, 70051, 7957388, 24551765, 9764901, 15609756, 27698697, 28664395, 1657393, + 3084098, + ]), + xy2d: FieldElement2625::from_limbs([ + 10477963, 26084172, 12119565, 20303627, 29016246, 28188843, 31280318, 14396151, + 36875289, 15272408, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54820536, 36723894, 28813182, 16658753, 92225296, 27923965, 109043770, 54472724, + 42094105, 35504935, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 40928506, 9489186, 11053416, 18808271, 36055143, 5825629, 58724558, 24786899, + 15341278, 8373727, + ]), + xy2d: FieldElement2625::from_limbs([ + 28685821, 7759505, 52730348, 21551571, 35137043, 4079241, 298136, 23321830, + 64230656, 15190419, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 34175950, 47360767, 52771378, 51314432, 110213106, 10940926, 75778582, 36296824, + 108184414, 60233859, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 65528476, 21825014, 41129205, 22109408, 49696989, 22641577, 9291593, 17306653, + 54954121, 6048604, + ]), + xy2d: FieldElement2625::from_limbs([ + 36803549, 14843443, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, + 11213262, 9168384, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 40828313, 44562278, 19408959, 32613674, 115624762, 29225850, 62020803, 22449281, + 20470156, 50710163, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 43972811, 9282191, 14855179, 18164354, 59746048, 19145871, 44324911, 14461607, + 14042978, 5230683, + ]), + xy2d: FieldElement2625::from_limbs([ + 29969548, 30812838, 50396996, 25001989, 9175485, 31085458, 21556950, 3506042, + 61174973, 21104723, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 63964099, 42299092, 19704002, 38135710, 46678177, 6830682, 45824694, 42525944, + 38569674, 48880994, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 47644235, 10110287, 49846336, 30050539, 43608476, 1355668, 51585814, 15300987, + 46594746, 9168259, + ]), + xy2d: FieldElement2625::from_limbs([ + 61755510, 4488612, 43305616, 16314346, 7780487, 17915493, 38160505, 9601604, + 33087103, 24543045, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 47665675, 18041531, 46311396, 21109108, 104393280, 43783891, 39664534, 52108332, + 61111992, 49219103, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 23294591, 16921819, 44458082, 25083453, 27844203, 11461195, 13099750, 31094076, + 18151675, 13417686, + ]), + xy2d: FieldElement2625::from_limbs([ + 42385932, 29377914, 35958184, 5988918, 40250079, 6685064, 1661597, 21002991, + 15271675, 18101767, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 78541887, 20325766, 75348494, 28274914, 65123427, 32828713, 48410099, 35721975, + 60187562, 20114249, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 35672693, 15575145, 30436815, 12192228, 44645511, 9395378, 57191156, 24915434, + 12215109, 12028277, + ]), + xy2d: FieldElement2625::from_limbs([ + 14098381, 6555944, 23007258, 5757252, 51681032, 20603929, 30123439, 4617780, + 50208775, 32898803, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 63082644, 51868028, 79002030, 47273095, 52299401, 35401816, 51288864, 43708440, + 91082124, 20869957, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 40577025, 29858441, 65199965, 2534300, 35238307, 17004076, 18341389, 22134481, + 32013173, 23450893, + ]), + xy2d: FieldElement2625::from_limbs([ + 41629544, 10876442, 55337778, 18929291, 54739296, 1838103, 21911214, 6354752, + 4425632, 32716610, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 56675456, 18941465, 89338721, 30463384, 53917697, 34331160, 116802352, 55088400, + 71833867, 47599401, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 19268631, 26250011, 1555348, 8692754, 45634805, 23643767, 6347389, 32142648, + 47586572, 17444675, + ]), + xy2d: FieldElement2625::from_limbs([ + 42244775, 12986007, 56209986, 27995847, 55796492, 33405905, 19541417, 8180106, + 9282262, 10282508, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 108012627, 37982977, 58447667, 20360168, 71207265, 52943606, 15522533, 8372215, + 72651459, 22851748, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56546323, 14895632, 26814552, 16880582, 49628109, 31065071, 64326972, 6993760, + 49014979, 10114654, + ]), + xy2d: FieldElement2625::from_limbs([ + 47001790, 32625013, 31422703, 10427861, 59998115, 6150668, 38017109, 22025285, + 25953724, 33448274, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 62874448, 59069571, 57989737, 36600431, 69210472, 54501569, 86498882, 39648727, + 63793584, 46385556, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 51110167, 7578151, 5310217, 14408357, 33560244, 33329692, 31575953, 6326196, + 7381791, 31132593, + ]), + xy2d: FieldElement2625::from_limbs([ + 46206085, 3296810, 24736065, 17226043, 18374253, 7318640, 6295303, 8082724, + 51746375, 12339663, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 27724736, 35845589, 73197064, 19369633, 68901590, 39412065, 80957277, 15768921, + 92200031, 14856293, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 48242193, 8331042, 24373479, 8541013, 66406866, 24284974, 12927299, 20858939, + 44926390, 24541532, + ]), + xy2d: FieldElement2625::from_limbs([ + 55685435, 28132841, 11632844, 3405020, 30536730, 21880393, 39848098, 13866389, + 30146206, 9142070, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 71032974, 18246915, 120400605, 23499470, 79400683, 32886065, 39406089, 9326383, + 58871006, 37725725, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 51186905, 16037936, 6713787, 16606682, 45496729, 2790943, 26396185, 3731949, + 345228, 28091483, + ]), + xy2d: FieldElement2625::from_limbs([ + 45781307, 13448258, 25284571, 1143661, 20614966, 24705045, 2031538, 21163201, + 50855680, 19972348, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 98125037, 16832002, 93480255, 52657630, 62081513, 14854136, 17477601, 37397089, + 28012649, 50703444, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 62033029, 9368965, 58546785, 28953529, 51858910, 6970559, 57918991, 16292056, + 58241707, 3507939, + ]), + xy2d: FieldElement2625::from_limbs([ + 29439664, 3537914, 23333589, 6997794, 49553303, 22536363, 51899661, 18503164, + 57943934, 6580395, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54922984, 59429075, 83547131, 10826159, 58412047, 27318820, 84969307, 24280585, + 65013061, 42858998, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 20714545, 29217521, 29088194, 7406487, 11426967, 28458727, 14792666, 18945815, + 5289420, 33077305, + ]), + xy2d: FieldElement2625::from_limbs([ + 50443312, 22903641, 60948518, 20248671, 9192019, 31751970, 17271489, 12349094, + 26939669, 29802138, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54218947, 9373457, 98704712, 16374214, 21471720, 13221525, 39825369, 54760304, + 63410056, 33672318, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 22263325, 26994382, 3984569, 22379786, 51994855, 32987646, 28311252, 5358056, + 43789084, 541963, + ]), + xy2d: FieldElement2625::from_limbs([ + 16259200, 3261970, 2309254, 18019958, 50223152, 28972515, 24134069, 16848603, + 53771797, 20002236, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 76487005, 20414245, 111371745, 20809166, 95307144, 59864765, 64709178, 32837080, + 67799289, 48430675, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 24977353, 33240048, 58884894, 20089345, 28432342, 32378079, 54040059, 21257083, + 44727879, 6618998, + ]), + xy2d: FieldElement2625::from_limbs([ + 65570671, 11685645, 12944378, 13682314, 42719353, 19141238, 8044828, 19737104, + 32239828, 27901670, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 48505798, 38317421, 66182613, 42439735, 105805247, 30367115, 76890510, 23204372, + 32779358, 5095274, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 34100715, 28339925, 34843976, 29869215, 9460460, 24227009, 42507207, 14506723, + 21639561, 30924196, + ]), + xy2d: FieldElement2625::from_limbs([ + 50707921, 20442216, 25239337, 15531969, 3987758, 29055114, 65819361, 26690896, + 17874573, 558605, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 53508716, 10240080, 76280747, 16131052, 46239610, 43154131, 100608350, 38634582, + 69194755, 38674192, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 44903700, 31034903, 50727262, 414690, 42089314, 2170429, 30634760, 25190818, + 35108870, 27794547, + ]), + xy2d: FieldElement2625::from_limbs([ + 60263160, 15791201, 8550074, 32241778, 29928808, 21462176, 27534429, 26362287, + 44757485, 12961481, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 42616785, 57538092, 10368192, 11582341, 110820435, 31309143, 83642793, 8206995, + 104023076, 28394792, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 55987368, 30172197, 2307365, 6362031, 66973409, 8868176, 50273234, 7031274, + 7589640, 8945490, + ]), + xy2d: FieldElement2625::from_limbs([ + 34956097, 8917966, 6661220, 21876816, 65916803, 17761038, 7251488, 22372252, + 24099108, 19098262, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 72128384, 25646961, 71352990, 18840075, 107284455, 40007595, 47990681, 20265406, + 127985831, 56828126, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10853575, 10721687, 26480089, 5861829, 44113045, 1972174, 65242217, 22996533, + 63745412, 27113307, + ]), + xy2d: FieldElement2625::from_limbs([ + 50106456, 5906789, 221599, 26991285, 7828207, 20305514, 24362660, 31546264, + 53242455, 7421391, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 75248772, 27007934, 99366509, 27663885, 97484582, 1886180, 113042620, 48995682, + 95935221, 29431402, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 6267067, 9695052, 7709135, 16950835, 34239795, 31668296, 14795159, 25714308, + 13746020, 31812384, + ]), + xy2d: FieldElement2625::from_limbs([ + 28584883, 7787108, 60375922, 18503702, 22846040, 25983196, 63926927, 33190907, + 4771361, 25134474, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 92058101, 6376278, 39642383, 25379823, 48462709, 23623825, 100652432, 54967168, + 70678489, 44897024, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 26514970, 4740088, 27912651, 3697550, 19331575, 22082093, 6809885, 4608608, + 7325975, 18753361, + ]), + xy2d: FieldElement2625::from_limbs([ + 55490446, 19000001, 42787651, 7655127, 65739590, 5214311, 39708324, 10258389, + 49462170, 25367739, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 11431185, 49377439, 93679108, 47883555, 85138853, 38350513, 35662684, 49135095, + 76389221, 29580744, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 66948081, 23228174, 44253547, 29249434, 46247496, 19933429, 34297962, 22372809, + 51563772, 4387440, + ]), + xy2d: FieldElement2625::from_limbs([ + 46309467, 12194511, 3937617, 27748540, 39954043, 9340369, 42594872, 8548136, + 20617071, 26072431, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 66170039, 29623845, 58394552, 49679149, 91711988, 27329038, 53333511, 55233041, + 91454545, 10325459, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 47253587, 31985546, 44906155, 8714033, 14007766, 6928528, 16318175, 32543743, + 4766742, 3552007, + ]), + xy2d: FieldElement2625::from_limbs([ + 45357481, 16823515, 1351762, 32751011, 63099193, 3950934, 3217514, 14481909, + 10988822, 29559670, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 15564288, 19242862, 70210106, 39238579, 97555643, 25503075, 79785990, 27049088, + 58813011, 46850436, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 57666574, 6624295, 36809900, 21640754, 62437882, 31497052, 31521203, 9614054, + 37108040, 12074673, + ]), + xy2d: FieldElement2625::from_limbs([ + 4771172, 33419193, 14290748, 20464580, 27992297, 14998318, 65694928, 31997715, + 29832612, 17163397, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 7064865, 59567690, 115055764, 62041325, 48217593, 30641695, 92934105, 38847728, + 39986203, 46656021, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 64810282, 2439669, 59642254, 1719964, 39841323, 17225986, 32512468, 28236839, + 36752793, 29363474, + ]), + xy2d: FieldElement2625::from_limbs([ + 37102324, 10162315, 33928688, 3981722, 50626726, 20484387, 14413973, 9515896, + 19568978, 9628812, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 33053784, 33753789, 83003454, 35137490, 94489106, 28973996, 49269969, 61002024, + 60817076, 36992171, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 48129987, 3884492, 19469877, 12726490, 15913552, 13614290, 44147131, 70103, + 7463304, 4176122, + ]), + xy2d: FieldElement2625::from_limbs([ + 39984863, 10659916, 11482427, 17484051, 12771466, 26919315, 34389459, 28231680, + 24216881, 5944158, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 76002989, 41005405, 64444714, 57343111, 106137209, 21165315, 19345745, 48235228, + 78741856, 5847884, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 26942781, 31239115, 9129563, 28647825, 26024104, 11769399, 55590027, 6367193, + 57381634, 4782139, + ]), + xy2d: FieldElement2625::from_limbs([ + 19916442, 28726022, 44198159, 22140040, 25606323, 27581991, 33253852, 8220911, + 6358847, 31680575, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 67910273, 31472729, 16569427, 44619599, 29875703, 33651059, 75017251, 29073951, + 53570360, 34941586, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 19646058, 5720633, 55692158, 12814208, 11607948, 12749789, 14147075, 15156355, + 45242033, 11835259, + ]), + xy2d: FieldElement2625::from_limbs([ + 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, + 40548314, 5052482, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 64091413, 43612637, 69089700, 37518674, 22160965, 12322533, 60677741, 20936246, + 12228556, 26550755, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 32944382, 14922211, 44263970, 5188527, 21913450, 24834489, 4001464, 13238564, + 60994061, 8653814, + ]), + xy2d: FieldElement2625::from_limbs([ + 22865569, 28901697, 27603667, 21009037, 14348957, 8234005, 24808405, 5719875, + 28483275, 2841751, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 117796741, 32441125, 66781144, 21446575, 21886281, 51556090, 65220896, 33238773, + 87040921, 20815228, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 55452759, 10087520, 58243976, 28018288, 47830290, 30498519, 3999227, 13239134, + 62331395, 19644223, + ]), + xy2d: FieldElement2625::from_limbs([ + 1382174, 21859713, 17266789, 9194690, 53784508, 9720080, 20403944, 11284705, + 53095046, 3093229, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 83759766, 56070931, 66044684, 35125060, 58779117, 40907184, 66806439, 16271224, + 43059443, 26862581, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 45197768, 27626490, 62497547, 27994275, 35364760, 22769138, 24123613, 15193618, + 45456747, 16815042, + ]), + xy2d: FieldElement2625::from_limbs([ + 57172930, 29264984, 41829040, 4372841, 2087473, 10399484, 31870908, 14690798, + 17361620, 11864968, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 55801216, 39764803, 80315437, 39360751, 105200035, 19587230, 54777658, 26067830, + 41530403, 50868174, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 14668443, 21284197, 26039038, 15305210, 25515617, 4542480, 10453892, 6577524, + 9145645, 27110552, + ]), + xy2d: FieldElement2625::from_limbs([ + 5974855, 3053895, 57675815, 23169240, 35243739, 3225008, 59136222, 3936127, + 61456591, 30504127, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 97734231, 28825031, 41552902, 20761565, 46624288, 41249530, 17097187, 50805368, + 106217947, 35358062, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 63555773, 9865098, 61880298, 4272700, 61435032, 16864731, 14911343, 12196514, + 45703375, 7047411, + ]), + xy2d: FieldElement2625::from_limbs([ + 20093258, 9920966, 55970670, 28210574, 13161586, 12044805, 34252013, 4124600, + 34765036, 23296865, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 46320021, 14084653, 53577151, 41396578, 19119037, 19731827, 71861240, 24839791, + 45429205, 35842469, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 40289628, 30270716, 29965058, 3039786, 52635099, 2540456, 29457502, 14625692, + 42289247, 12570231, + ]), + xy2d: FieldElement2625::from_limbs([ + 66045306, 22002608, 16920317, 12494842, 1278292, 27685323, 45948920, 30055751, + 55134159, 4724942, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 85069815, 21778897, 62967895, 23851901, 58232301, 32143814, 54201480, 24894499, + 104641427, 35458286, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 23134274, 19275300, 56426866, 31942495, 20684484, 15770816, 54119114, 3190295, + 26955097, 14109738, + ]), + xy2d: FieldElement2625::from_limbs([ + 15308788, 5320727, 36995055, 19235554, 22902007, 7767164, 29425325, 22276870, + 31960941, 11934971, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 39713134, 41990227, 71218507, 12222638, 109589860, 14818667, 87747037, 38429459, + 77600255, 34934149, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 53949449, 9197840, 3875503, 24618324, 65725151, 27674630, 33518458, 16176658, + 21432314, 12180697, + ]), + xy2d: FieldElement2625::from_limbs([ + 55321537, 11500837, 13787581, 19721842, 44678184, 10140204, 1465425, 12689540, + 56807545, 19681548, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 72522936, 18168390, 46101199, 43198001, 79943833, 34740580, 64485947, 32212200, + 26128230, 39587344, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 40771450, 19788269, 32496024, 19900513, 17847800, 20885276, 3604024, 8316894, + 41233830, 23117073, + ]), + xy2d: FieldElement2625::from_limbs([ + 3296484, 6223048, 24680646, 21307972, 44056843, 5903204, 58246567, 28915267, + 12376616, 3188849, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 29190450, 18895386, 27549112, 32370916, 70628929, 22857130, 32049514, 26245319, + 50999629, 57256556, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 52364359, 24245275, 735817, 32955454, 46701176, 28496527, 25246077, 17758763, + 18640740, 32593455, + ]), + xy2d: FieldElement2625::from_limbs([ + 60180029, 17123636, 10361373, 5642961, 4910474, 12345252, 35470478, 33060001, + 10530746, 1053335, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 104951742, 52922057, 120679510, 54991489, 47651803, 56453479, 102755357, 30605445, + 24018830, 48581076, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 44516310, 30409154, 64819587, 5953842, 53668675, 9425630, 25310643, 13003497, + 64794073, 18408815, + ]), + xy2d: FieldElement2625::from_limbs([ + 39688860, 32951110, 59064879, 31885314, 41016598, 13987818, 39811242, 187898, + 43942445, 31022696, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 45364447, 19743956, 68953703, 38575859, 123783328, 17642957, 76825530, 49821353, + 62038646, 34280530, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 29370903, 27500434, 7334070, 18212173, 9385286, 2247707, 53446902, 28714970, + 30007387, 17731091, + ]), + xy2d: FieldElement2625::from_limbs([ + 66172485, 16086690, 23751945, 33011114, 65941325, 28365395, 9137108, 730663, + 9835848, 4555336, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 43732410, 34964877, 44855110, 54209249, 97976497, 49381408, 17693929, 34099128, + 55123565, 45977077, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 31117226, 21338698, 53606025, 6561946, 57231997, 20796761, 61990178, 29457725, + 29120152, 13924425, + ]), + xy2d: FieldElement2625::from_limbs([ + 49707966, 19321222, 19675798, 30819676, 56101901, 27695611, 57724924, 22236731, + 7240930, 33317044, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 35747087, 22207651, 119210280, 27698212, 111764387, 54956091, 68331198, 37943914, + 70402500, 51557120, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 50424044, 19110186, 11038543, 11054958, 53307689, 30215898, 42789283, 7733546, + 12796905, 27218610, + ]), + xy2d: FieldElement2625::from_limbs([ + 58349431, 22736595, 41689999, 10783768, 36493307, 23807620, 38855524, 3647835, + 3222231, 22393970, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 85714958, 35247531, 108769341, 51938590, 71221215, 43599452, 23603892, 31506198, + 59558087, 36039416, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 9255298, 30423235, 54952701, 32550175, 13098012, 24339566, 16377219, 31451620, + 47306788, 30519729, + ]), + xy2d: FieldElement2625::from_limbs([ + 44379556, 7496159, 61366665, 11329248, 19991973, 30206930, 35390715, 9936965, + 37011176, 22935634, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 88987435, 28553134, 71447199, 47198328, 64071998, 13160959, 86817760, 5415496, + 59748361, 29445138, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 27736842, 10103576, 12500508, 8502413, 63695848, 23920873, 10436917, 32004156, + 43449720, 25422331, + ]), + xy2d: FieldElement2625::from_limbs([ + 19492550, 21450067, 37426887, 32701801, 63900692, 12403436, 30066266, 8367329, + 13243957, 8709688, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 79123950, 36355692, 95306994, 10151020, 91926984, 28811298, 55914672, 27908697, + 72259831, 40828617, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 2831347, 21062286, 1478974, 6122054, 23825128, 20820846, 31097298, 6083058, + 31021603, 23760822, + ]), + xy2d: FieldElement2625::from_limbs([ + 64578913, 31324785, 445612, 10720828, 53259337, 22048494, 43601132, 16354464, + 15067285, 19406725, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 74949787, 47592304, 100852864, 49488446, 66380650, 29911725, 88512851, 34612017, + 47729401, 21151211, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 915865, 17085158, 15608284, 24765302, 42751837, 6060029, 49737545, 8410996, + 59888403, 16527024, + ]), + xy2d: FieldElement2625::from_limbs([ + 32922597, 32997445, 20336073, 17369864, 10903704, 28169945, 16957573, 52992, + 23834301, 6588044, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 32752011, 44787382, 70490858, 24839565, 22652987, 22810329, 17159698, 50243539, + 46794283, 32248439, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 62419196, 9166775, 41398568, 22707125, 11576751, 12733943, 7924251, 30802151, + 1976122, 26305405, + ]), + xy2d: FieldElement2625::from_limbs([ + 21251203, 16309901, 64125849, 26771309, 30810596, 12967303, 156041, 30183180, + 12331344, 25317235, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 75760459, 29077399, 118132091, 28557436, 80111370, 36505236, 96163290, 28447461, + 77116999, 28886530, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 31486061, 15114593, 52847614, 12951353, 14369431, 26166587, 16347320, 19892343, + 8684154, 23021480, + ]), + xy2d: FieldElement2625::from_limbs([ + 19443825, 11385320, 24468943, 23895364, 43189605, 2187568, 40845657, 27467510, + 31316347, 14219878, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 38514355, 1193784, 99354083, 11392484, 31092169, 49277233, 94254877, 40546840, + 29126554, 42761822, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 32382916, 1110093, 18477781, 11028262, 39697101, 26006320, 62128346, 10843781, + 59151264, 19118701, + ]), + xy2d: FieldElement2625::from_limbs([ + 2814918, 7836403, 27519878, 25686276, 46214848, 22000742, 45614304, 8550129, + 28346258, 1994730, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 47530546, 41639976, 53108344, 29605809, 69894701, 17323124, 47591912, 40729325, + 22628101, 41669612, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 36703732, 955510, 55975026, 18476362, 34661776, 20276352, 41457285, 3317159, + 57165847, 930271, + ]), + xy2d: FieldElement2625::from_limbs([ + 51805164, 26720662, 28856489, 1357446, 23421993, 1057177, 24091212, 32165462, + 44343487, 22903716, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 44357614, 28250434, 54201256, 54339997, 51297351, 25757378, 52269845, 50554643, + 65241844, 41953401, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 35139535, 2106402, 62372504, 1362500, 12813763, 16200670, 22981545, 27263159, + 18009407, 17781660, + ]), + xy2d: FieldElement2625::from_limbs([ + 49887941, 24009210, 39324209, 14166834, 29815394, 7444469, 29551787, 29827013, + 19288548, 1325865, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 82209002, 51273111, 110293748, 32549332, 107767535, 49063838, 79485593, 30075285, + 100274970, 25511681, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 20909212, 13023121, 57899112, 16251777, 61330449, 25459517, 12412150, 10018715, + 2213263, 19676059, + ]), + xy2d: FieldElement2625::from_limbs([ + 32529814, 22479743, 30361438, 16864679, 57972923, 1513225, 22922121, 6382134, + 61341936, 8371347, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77032307, 44825931, 79725657, 37099153, 104219359, 31832804, 12891686, 25361300, + 40665920, 44040575, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 44511638, 26541766, 8587002, 25296571, 4084308, 20584370, 361725, 2610596, + 43187334, 22099236, + ]), + xy2d: FieldElement2625::from_limbs([ + 5408392, 32417741, 62139741, 10561667, 24145918, 14240566, 31319731, 29318891, + 19985174, 30118346, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 53114388, 50171252, 81658109, 36895530, 99264821, 13648975, 49531796, 8849296, + 67173894, 41925115, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 58787919, 21504805, 31204562, 5839400, 46481576, 32497154, 47665921, 6922163, + 12743482, 23753914, + ]), + xy2d: FieldElement2625::from_limbs([ + 64747493, 12678784, 28815050, 4759974, 43215817, 4884716, 23783145, 11038569, + 18800704, 255233, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 61839168, 31780545, 13957885, 41545147, 23132994, 34283205, 80502710, 42621388, + 86367551, 52355070, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 64172210, 22726896, 56676774, 14516792, 63468078, 4372540, 35173943, 2209389, + 65584811, 2055793, + ]), + xy2d: FieldElement2625::from_limbs([ + 580882, 16705327, 5468415, 30871414, 36182444, 18858431, 59905517, 24560042, + 37087844, 7394434, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 90947654, 35377159, 118479284, 48797157, 75426955, 29821327, 45436683, 30062226, + 62287122, 48354352, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 13345610, 9759151, 3371034, 17416641, 16353038, 8577942, 31129804, 13496856, + 58052846, 7402517, + ]), + xy2d: FieldElement2625::from_limbs([ + 2286874, 29118501, 47066405, 31546095, 53412636, 5038121, 11006906, 17794080, + 8205060, 1607563, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 81522931, 25552299, 70440693, 63900646, 89358013, 27960243, 85473524, 30647473, + 30019586, 24525154, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 39420813, 1585952, 56333811, 931068, 37988643, 22552112, 52698034, 12029092, + 9944378, 8024, + ]), + xy2d: FieldElement2625::from_limbs([ + 4368715, 29844802, 29874199, 18531449, 46878477, 22143727, 50994269, 32555346, + 58966475, 5640029, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77408455, 13746482, 11661824, 16234854, 74739102, 5998373, 76918751, 16859867, + 82328661, 19226648, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 27425505, 27835351, 3055005, 10660664, 23458024, 595578, 51710259, 32381236, + 48766680, 9742716, + ]), + xy2d: FieldElement2625::from_limbs([ + 6744077, 2427284, 26042789, 2720740, 66260958, 1118973, 32324614, 7406442, + 12420155, 1994844, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 81121366, 62084143, 115833273, 23975961, 107732385, 29617991, 121184249, 22644627, + 91428792, 27108098, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 16412671, 29047065, 10772640, 15929391, 50040076, 28895810, 10555944, 23070383, + 37006495, 28815383, + ]), + xy2d: FieldElement2625::from_limbs([ + 22397363, 25786748, 57815702, 20761563, 17166286, 23799296, 39775798, 6199365, + 21880021, 21303672, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 62825538, 5368522, 35991846, 41717820, 103894664, 36763558, 83666014, 42445160, + 75949308, 38512191, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 51661137, 709326, 60189418, 22684253, 37330941, 6522331, 45388683, 12130071, + 52312361, 5005756, + ]), + xy2d: FieldElement2625::from_limbs([ + 64994094, 19246303, 23019041, 15765735, 41839181, 6002751, 10183197, 20315106, + 50713577, 31378319, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 115191953, 35186435, 80575154, 59113763, 110577275, 16573535, 35094956, 30497327, + 22208661, 35554900, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 3065054, 32141671, 41510189, 33192999, 49425798, 27851016, 58944651, 11248526, + 63417650, 26140247, + ]), + xy2d: FieldElement2625::from_limbs([ + 10379208, 27508878, 8877318, 1473647, 37817580, 21046851, 16690914, 2553332, + 63976176, 16400288, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 82825513, 34808697, 115745037, 41000704, 58659945, 6344163, 45011593, 26268851, + 26894936, 42686498, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 24158868, 12938817, 11085297, 25376834, 39045385, 29097348, 36532400, 64451, + 60291780, 30861549, + ]), + xy2d: FieldElement2625::from_limbs([ + 13488534, 7794716, 22236231, 5989356, 25426474, 20976224, 2350709, 30135921, + 62420857, 2364225, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 83443897, 9132433, 92749446, 40233319, 68834491, 42072368, 55301839, 21856974, + 15445874, 25756331, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 29004188, 25687351, 28661401, 32914020, 54314860, 25611345, 31863254, 29418892, + 66830813, 17795152, + ]), + xy2d: FieldElement2625::from_limbs([ + 60986784, 18687766, 38493958, 14569918, 56250865, 29962602, 10343411, 26578142, + 37280576, 22738620, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 94190495, 37018415, 14099041, 29036828, 68725166, 27348827, 96651499, 15372178, + 84402661, 34515140, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 20263915, 11434237, 61343429, 11236809, 13505955, 22697330, 50997518, 6493121, + 47724353, 7639713, + ]), + xy2d: FieldElement2625::from_limbs([ + 64278047, 18715199, 25403037, 25339236, 58791851, 17380732, 18006286, 17510682, + 29994676, 17746311, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 76878673, 38757082, 110060329, 19923038, 106166724, 21992806, 42495722, 53248081, + 35924287, 34263895, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 12286395, 13076066, 45333675, 32377809, 42105665, 4057651, 35090736, 24663557, + 16102006, 13205847, + ]), + xy2d: FieldElement2625::from_limbs([ + 13733362, 5599946, 10557076, 3195751, 61550873, 8536969, 41568694, 8525971, + 10151379, 10394400, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 71133505, 17416880, 89545125, 12276533, 58009849, 64422764, 86807091, 11743038, + 100915394, 42488844, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 51229064, 29029191, 58528116, 30620370, 14634844, 32856154, 57659786, 3137093, + 55571978, 11721157, + ]), + xy2d: FieldElement2625::from_limbs([ + 17555920, 28540494, 8268605, 2331751, 44370049, 9761012, 9319229, 8835153, + 57903375, 32274386, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 66647436, 25724417, 87722981, 16688287, 59594098, 28747312, 89409167, 34059860, + 73217325, 27371016, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 62038564, 12367916, 36445330, 3234472, 32617080, 25131790, 29880582, 20071101, + 40210373, 25686972, + ]), + xy2d: FieldElement2625::from_limbs([ + 35133562, 5726538, 26934134, 10237677, 63935147, 32949378, 24199303, 3795095, + 7592688, 18562353, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 21594413, 18590204, 84575271, 63031641, 32537082, 36294330, 73516586, 12018832, + 38852812, 37852843, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 46458361, 21592935, 39872588, 570497, 3767144, 31836892, 13891941, 31985238, + 13717173, 10805743, + ]), + xy2d: FieldElement2625::from_limbs([ + 52432215, 17910135, 15287173, 11927123, 24177847, 25378864, 66312432, 14860608, + 40169934, 27690595, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 80071405, 38866230, 57048095, 45212711, 85964149, 25600230, 80395126, 54300159, + 62727806, 9882021, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 18512060, 11319350, 46985740, 15090308, 18818594, 5271736, 44380960, 3666878, + 43141434, 30255002, + ]), + xy2d: FieldElement2625::from_limbs([ + 60319844, 30408388, 16192428, 13241070, 15898607, 19348318, 57023983, 26893321, + 64705764, 5276064, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 97278672, 28236783, 93415069, 55358004, 94923826, 40623698, 74261714, 37239413, + 68558087, 13082860, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10342807, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, + 46092426, 25352431, + ]), + xy2d: FieldElement2625::from_limbs([ + 33958735, 3261607, 22745853, 7948688, 19370557, 18376767, 40936887, 6482813, + 56808784, 22494330, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 32869439, 61700319, 25609741, 49233102, 56421094, 51637792, 26112419, 36075440, + 44444575, 40459246, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 29506904, 4457497, 3377935, 23757988, 36598817, 12935079, 1561737, 3841096, + 38105225, 26896789, + ]), + xy2d: FieldElement2625::from_limbs([ + 10340844, 26924055, 48452231, 31276001, 12621150, 20215377, 30878496, 21730062, + 41524312, 5181965, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 25940096, 20896407, 17324187, 56801490, 58437394, 15029093, 91505116, 17103509, + 64786011, 21165857, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 45343161, 9916822, 65808455, 4079497, 66080518, 11909558, 1782390, 12641087, + 20603771, 26992690, + ]), + xy2d: FieldElement2625::from_limbs([ + 48226577, 21881051, 24849421, 11501709, 13161720, 28785558, 1925522, 11914390, + 4662781, 7820689, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 79349895, 33128449, 75241554, 42948365, 32846759, 31954812, 29749455, 45727356, + 83245615, 48818451, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56758909, 18873868, 58896884, 2330219, 49446315, 19008651, 10658212, 6671822, + 19012087, 3772772, + ]), + xy2d: FieldElement2625::from_limbs([ + 3753511, 30133366, 10617073, 2028709, 14841030, 26832768, 28718731, 17791548, + 20527770, 12988982, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 52286341, 27757162, 63400876, 12689772, 66209881, 22639565, 110034681, 56543919, + 70408527, 54683910, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 50331161, 18301130, 57466446, 4978982, 3308785, 8755439, 6943197, 6461331, + 41525717, 8991217, + ]), + xy2d: FieldElement2625::from_limbs([ + 49882601, 1816361, 65435576, 27467992, 31783887, 25378441, 34160718, 7417949, + 36866577, 1507264, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 29692644, 40384323, 56610063, 37889327, 88054838, 21647935, 38221255, 41763822, + 14606361, 22907359, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 63627275, 8707080, 32188102, 5672294, 22096700, 1711240, 34088169, 9761486, + 4170404, 31469107, + ]), + xy2d: FieldElement2625::from_limbs([ + 55521375, 14855944, 62981086, 32022574, 40459774, 15084045, 22186522, 16002000, + 52832027, 25153633, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 62297389, 47315460, 35404986, 31070512, 63796392, 41423478, 59995291, 23934339, + 80349708, 44520301, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 59366301, 25297669, 52340529, 19898171, 43876480, 12387165, 4498947, 14147411, + 29514390, 4302863, + ]), + xy2d: FieldElement2625::from_limbs([ + 53695440, 21146572, 20757301, 19752600, 14785142, 8976368, 62047588, 31410058, + 17846987, 19582505, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 64864393, 32799703, 62511833, 32488122, 60861691, 35009730, 112569999, 24339641, + 61886162, 46204698, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 57202067, 17484121, 21134159, 12198166, 40044289, 708125, 387813, 13770293, + 47974538, 10958662, + ]), + xy2d: FieldElement2625::from_limbs([ + 22470984, 12369526, 23446014, 28113323, 45588061, 23855708, 55336367, 21979976, + 42025033, 4271861, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 109048144, 57055220, 47199530, 48916026, 61124505, 35713623, 67184238, 62830334, + 101691505, 42024103, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 15854951, 4148314, 58214974, 7259001, 11666551, 13824734, 36577666, 2697371, + 24154791, 24093489, + ]), + xy2d: FieldElement2625::from_limbs([ + 15446137, 17747788, 29759746, 14019369, 30811221, 23944241, 35526855, 12840103, + 24913809, 9815020, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 62399559, 27940162, 35267365, 21265538, 52665326, 44353845, 125114051, 46993199, + 85843991, 43020669, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 11933045, 9281483, 5081055, 28370608, 64480701, 28648802, 59381042, 22658328, + 44380208, 16199063, + ]), + xy2d: FieldElement2625::from_limbs([ + 14576810, 379472, 40322331, 25237195, 37682355, 22741457, 67006097, 1876698, + 30801119, 2164795, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 15995067, 36754305, 13672554, 13712240, 47730029, 62461217, 121136116, 51612593, + 53616055, 34822483, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56818250, 29895392, 63822271, 10948817, 23037027, 3794475, 63638526, 20954210, + 50053494, 3565903, + ]), + xy2d: FieldElement2625::from_limbs([ + 29210069, 24135095, 61189071, 28601646, 10834810, 20226706, 50596761, 22733718, + 39946641, 19523900, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 121055819, 49063018, 83772567, 25398281, 38758921, 42573554, 37925442, 29785008, + 69352974, 19552452, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 61955989, 29753495, 57802388, 27482848, 16243068, 14684434, 41435776, 17373631, + 13491505, 4641841, + ]), + xy2d: FieldElement2625::from_limbs([ + 10813398, 643330, 47920349, 32825515, 30292061, 16954354, 27548446, 25833190, + 14476988, 20787001, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77400943, 9984944, 73590300, 41834336, 59857349, 40587174, 27282936, 31910173, + 106304917, 12651322, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 35923332, 32741048, 22271203, 11835308, 10201545, 15351028, 17099662, 3988035, + 21721536, 30405492, + ]), + xy2d: FieldElement2625::from_limbs([ + 10202177, 27008593, 35735631, 23979793, 34958221, 25434748, 54202543, 3852693, + 13216206, 14842320, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 51293205, 22953365, 60569911, 26295436, 60124204, 26972653, 35608016, 47320255, + 106783330, 43454614, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 14465486, 19721101, 34974879, 18815558, 39665676, 12990491, 33046193, 15796406, + 60056998, 25514317, + ]), + xy2d: FieldElement2625::from_limbs([ + 30924398, 25274812, 6359015, 20738097, 16508376, 9071735, 41620263, 15413634, + 9524356, 26535554, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 12274182, 20378885, 99736504, 65323537, 73845487, 13267304, 72346523, 28444948, + 82772379, 37590215, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 64157555, 8903984, 17349946, 601635, 50676049, 28941875, 53376124, 17665097, + 44850385, 4659090, + ]), + xy2d: FieldElement2625::from_limbs([ + 50192582, 28601458, 36715152, 18395610, 20774811, 15897498, 5736189, 15026997, + 64930608, 20098846, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 58249865, 31335375, 28571665, 56953346, 66634395, 23448733, 63307367, 33832526, + 23440561, 33264224, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10226222, 27625730, 15139955, 120818, 52241171, 5218602, 32937275, 11551483, + 50536904, 26111567, + ]), + xy2d: FieldElement2625::from_limbs([ + 17932739, 21117156, 43069306, 10749059, 11316803, 7535897, 22503767, 5561594, + 63462240, 3898660, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 74858752, 32584864, 50769132, 33537967, 42090752, 15122142, 65535333, 40706961, + 88940025, 34799664, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 26958440, 18896406, 4314585, 8346991, 61431100, 11960071, 34519569, 32934396, + 36706772, 16838219, + ]), + xy2d: FieldElement2625::from_limbs([ + 54942968, 9166946, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, + 44770839, 13987524, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 109867800, 7778773, 88224864, 49127028, 62275597, 28196653, 62807965, 28429792, + 59639082, 30696363, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 9681908, 26817309, 35157219, 13591837, 60225043, 386949, 31622781, 6439245, + 52527852, 4091396, + ]), + xy2d: FieldElement2625::from_limbs([ + 58682418, 1470726, 38999185, 31957441, 3978626, 28430809, 47486180, 12092162, + 29077877, 18812444, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 72378032, 26694705, 120987516, 25533715, 25932562, 35317984, 61502753, 28048550, + 47091016, 2357888, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 32264008, 18146780, 61721128, 32394338, 65017541, 29607531, 23104803, 20684524, + 5727337, 189038, + ]), + xy2d: FieldElement2625::from_limbs([ + 14609104, 24599962, 61108297, 16931650, 52531476, 25810533, 40363694, 10942114, + 41219933, 18669734, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 87622345, 39112362, 51504250, 41383962, 93522806, 31535027, 45729895, 41026212, + 13913676, 28416557, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 41534488, 11967825, 29233242, 12948236, 60354399, 4713226, 58167894, 14059179, + 12878652, 8511905, + ]), + xy2d: FieldElement2625::from_limbs([ + 41452044, 3393630, 64153449, 26478905, 64858154, 9366907, 36885446, 6812973, + 5568676, 30426776, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 78738868, 12144453, 69225203, 47160468, 94487748, 49231348, 49700110, 20050058, + 119822531, 8070816, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 27117677, 23547054, 35826092, 27984343, 1127281, 12772488, 37262958, 10483305, + 55556115, 32525717, + ]), + xy2d: FieldElement2625::from_limbs([ + 10637467, 27866368, 5674780, 1072708, 40765276, 26572129, 65424888, 9177852, + 39615702, 15431202, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 87633990, 44446997, 121475255, 12779441, 104724694, 16150073, 105977209, 14943140, + 52052074, 25618500, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 37084402, 5626925, 66557297, 23573344, 753597, 11981191, 25244767, 30314666, + 63752313, 9594023, + ]), + xy2d: FieldElement2625::from_limbs([ + 43356201, 2636869, 61944954, 23450613, 585133, 7877383, 11345683, 27062142, + 13352334, 22577348, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 65177046, 28146973, 70413512, 54223994, 84124668, 62231772, 104433876, 25801948, + 53893326, 33235227, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 20239939, 6607058, 6203985, 3483793, 48721888, 32775202, 46385121, 15077869, + 44358105, 14523816, + ]), + xy2d: FieldElement2625::from_limbs([ + 27406023, 27512775, 27423595, 29057038, 4996213, 10002360, 38266833, 29008937, + 36936121, 28748764, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 78483087, 12660714, 17861383, 21013599, 78044431, 34653658, 53222787, 24462691, + 106490683, 44912934, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 54378055, 10311866, 1510375, 10778093, 64989409, 24408729, 32676002, 11149336, + 40985213, 4985767, + ]), + xy2d: FieldElement2625::from_limbs([ + 48012542, 341146, 60911379, 33315398, 15756972, 24757770, 66125820, 13794113, + 47694557, 17933176, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 73598907, 45494717, 25495922, 59382504, 75777235, 24803115, 70476466, 40524436, + 65417798, 58104073, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 1656478, 13457317, 15370807, 6364910, 13605745, 8362338, 47934242, 28078708, + 50312267, 28522993, + ]), + xy2d: FieldElement2625::from_limbs([ + 44835530, 20030007, 67044178, 29220208, 48503227, 22632463, 46537798, 26546453, + 67009010, 23317098, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 84856310, 43593691, 86477162, 29503840, 46478228, 51067577, 99101545, 17696455, + 104957364, 28042459, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 31932008, 28568291, 47496481, 16366579, 22023614, 88450, 11371999, 29810185, + 4882241, 22927527, + ]), + xy2d: FieldElement2625::from_limbs([ + 29796488, 37186, 19818052, 10115756, 55279832, 3352735, 18551198, 3272828, + 61917932, 29392022, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 12501267, 4044383, 58495907, 53716478, 101787674, 38691029, 47878485, 30024734, + 330069, 29895023, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 6384877, 2899513, 17807477, 7663917, 64749976, 12363164, 25366522, 24980540, + 66837568, 12071498, + ]), + xy2d: FieldElement2625::from_limbs([ + 58743349, 29511910, 25133447, 29037077, 60897836, 2265926, 34339246, 1936674, + 61949167, 3829362, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 28425947, 27718999, 66531773, 28857233, 120000172, 40425360, 75030413, 26986644, + 26333139, 47822096, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56041645, 11871230, 27385719, 22994888, 62522949, 22365119, 10004785, 24844944, + 45347639, 8930323, + ]), + xy2d: FieldElement2625::from_limbs([ + 45911060, 17158396, 25654215, 31829035, 12282011, 11008919, 1541940, 4757911, + 40617363, 17145491, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 80646107, 25794941, 113612887, 44516357, 61186043, 20336366, 53952279, 39771685, + 118274028, 47369420, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 49686272, 15157789, 18705543, 29619, 24409717, 33293956, 27361680, 9257833, + 65152338, 31777517, + ]), + xy2d: FieldElement2625::from_limbs([ + 42063564, 23362465, 15366584, 15166509, 54003778, 8423555, 37937324, 12361134, + 48422886, 4578289, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 91688613, 3711569, 68451186, 22374305, 107212592, 47679386, 44564334, 14074918, + 21964432, 41789689, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 60580251, 31142934, 9442965, 27628844, 12025639, 32067012, 64127349, 31885225, + 13006805, 2355433, + ]), + xy2d: FieldElement2625::from_limbs([ + 50803946, 19949172, 60476436, 28412082, 16974358, 22643349, 27202043, 1719366, + 1141648, 20758196, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54244901, 53888877, 58790596, 56090772, 60298717, 28710537, 13475065, 30420460, + 32674894, 47269477, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 11423316, 28086373, 32344215, 8962751, 24989809, 9241752, 53843611, 16086211, + 38367983, 17912338, + ]), + xy2d: FieldElement2625::from_limbs([ + 65699196, 12530727, 60740138, 10847386, 19531186, 19422272, 55399715, 7791793, + 39862921, 4383346, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 38137947, 38825878, 65842854, 23817442, 121762491, 50287029, 62246456, 62202414, + 27193555, 39799623, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 51914908, 5362277, 65324971, 2695833, 4960227, 12840725, 23061898, 3260492, + 22510453, 8577507, + ]), + xy2d: FieldElement2625::from_limbs([ + 54476394, 11257345, 34415870, 13548176, 66387860, 10879010, 31168030, 13952092, + 37537372, 29918525, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 70986166, 23981692, 99525555, 38959755, 56104456, 19897796, 70868632, 45489751, + 72720723, 41718449, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 50833043, 14667796, 15906460, 12155291, 44997715, 24514713, 32003001, 24722143, + 5773084, 25132323, + ]), + xy2d: FieldElement2625::from_limbs([ + 43320746, 25300131, 1950874, 8937633, 18686727, 16459170, 66203139, 12376319, + 31632953, 190926, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 109624102, 17415545, 58684872, 13378745, 81271271, 6901327, 58820115, 38062995, + 41767308, 29926903, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 8884438, 27670423, 6023973, 10104341, 60227295, 28612898, 18722940, 18768427, + 65436375, 827624, + ]), + xy2d: FieldElement2625::from_limbs([ + 34388281, 17265135, 34605316, 7101209, 13354605, 2659080, 65308289, 19446395, + 42230385, 1541285, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 70010192, 32436744, 70989239, 57049475, 116596786, 29941649, 45306746, 29986950, + 87565708, 31669398, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 27019610, 12299467, 53450576, 31951197, 54247203, 28692960, 47568713, 28538373, + 29439640, 15138866, + ]), + xy2d: FieldElement2625::from_limbs([ + 21536104, 26928012, 34661045, 22864223, 44700786, 5175813, 61688824, 17193268, + 7779327, 109896, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 97388589, 48203181, 59063992, 39979989, 80748484, 32810922, 28698389, 45734550, + 23177718, 33000357, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 26572828, 3405927, 35407164, 12890904, 47843196, 5335865, 60615096, 2378491, + 4439158, 20275085, + ]), + xy2d: FieldElement2625::from_limbs([ + 44392139, 3489069, 57883598, 33221678, 18875721, 32414337, 14819433, 20822905, + 49391106, 28092994, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 62052362, 50120982, 83062524, 37322183, 56672364, 49181491, 66287909, 35731656, + 75658945, 18440266, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 48635543, 16596774, 66727204, 15663610, 22860960, 15585581, 39264755, 29971692, + 43848403, 25125843, + ]), + xy2d: FieldElement2625::from_limbs([ + 34628313, 15707274, 58902952, 27902350, 29464557, 2713815, 44383727, 15860481, + 45206294, 1494192, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 47546754, 53021470, 41524990, 24254879, 80236705, 34314140, 21923481, 16529112, + 75851568, 46521448, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 38643965, 1553204, 32536856, 23080703, 42417258, 33148257, 58194238, 30620535, + 37205105, 15553882, + ]), + xy2d: FieldElement2625::from_limbs([ + 21877890, 3230008, 9881174, 10539357, 62311749, 2841331, 11543572, 14513274, + 19375923, 20906471, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 75941133, 52613378, 80362373, 38692006, 72146734, 37633208, 24880817, 60886148, + 69971515, 9455042, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 29306751, 5123106, 20245049, 19404543, 9592565, 8447059, 65031740, 30564351, + 15511448, 4789663, + ]), + xy2d: FieldElement2625::from_limbs([ + 46429108, 7004546, 8824831, 24119455, 63063159, 29803695, 61354101, 108892, + 23513200, 16652362, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 100961536, 37699212, 62632834, 26975308, 77878902, 26398889, 60458447, 54172563, + 115898528, 43767290, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 2756062, 8598110, 7383731, 26694540, 22312758, 32449420, 21179800, 2600940, + 57120566, 21047965, + ]), + xy2d: FieldElement2625::from_limbs([ + 42463153, 13317461, 36659605, 17900503, 21365573, 22684775, 11344423, 864440, + 64609187, 16844368, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 107784906, 6148327, 49924452, 19080277, 85891792, 33278434, 44547329, 33765731, + 69828620, 38495428, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 65784982, 3911312, 60160120, 14759764, 37081714, 7851206, 21690126, 8518463, + 26699843, 5276295, + ]), + xy2d: FieldElement2625::from_limbs([ + 53958991, 27125364, 9396248, 365013, 24703301, 23065493, 1321585, 149635, 51656090, + 7159368, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77096625, 30149672, 84616825, 43059961, 76840398, 31388917, 89464872, 41866607, + 89586081, 25151046, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 18155857, 17049442, 19744715, 9006923, 15154154, 23015456, 24256459, 28689437, + 44560690, 9334108, + ]), + xy2d: FieldElement2625::from_limbs([ + 2986088, 28642539, 10776627, 30080588, 10620589, 26471229, 45695018, 14253544, + 44521715, 536905, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 71486582, 41670267, 91675941, 15495313, 78733938, 46619030, 74499414, 44144056, + 77946923, 51688439, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 47766460, 867879, 9277171, 30335973, 52677291, 31567988, 19295825, 17757482, + 6378259, 699185, + ]), + xy2d: FieldElement2625::from_limbs([ + 7895007, 4057113, 60027092, 20476675, 49222032, 33231305, 66392824, 15693154, + 62063800, 20180469, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 59371282, 27685029, 119651408, 26147511, 78494517, 46756047, 31730677, 22591592, + 63190227, 23885106, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10188286, 17783598, 59772502, 13427542, 22223443, 14896287, 30743455, 7116568, + 45322357, 5427592, + ]), + xy2d: FieldElement2625::from_limbs([ + 696102, 13206899, 27047647, 22922350, 15285304, 23701253, 10798489, 28975712, + 19236242, 12477404, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 55879406, 44798227, 50054593, 25513566, 66320635, 58940896, 63211193, 44734935, + 43939347, 41288075, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 17800790, 19518253, 40108434, 21787760, 23887826, 3149671, 23466177, 23016261, + 10322026, 15313801, + ]), + xy2d: FieldElement2625::from_limbs([ + 26246234, 11968874, 32263343, 28085704, 6830754, 20231401, 51314159, 33452449, + 42659621, 10890803, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 35743198, 43825794, 54448238, 27287163, 83799070, 54046319, 119235514, 50039361, + 92289660, 28219547, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 66522290, 10376443, 34522450, 22268075, 19801892, 10997610, 2276632, 9482883, + 316878, 13820577, + ]), + xy2d: FieldElement2625::from_limbs([ + 57226037, 29044064, 64993357, 16457135, 56008783, 11674995, 30756178, 26039378, + 30696929, 29841583, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 100097781, 23951019, 12499365, 41465219, 56491606, 21622917, 59766047, 57123466, + 34759345, 7392472, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 58253184, 15927860, 9866406, 29905021, 64711949, 16898650, 36699387, 24419436, + 25112946, 30627788, + ]), + xy2d: FieldElement2625::from_limbs([ + 64604801, 33117465, 25621773, 27875660, 15085041, 28074555, 42223985, 20028237, + 5537437, 19640113, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 55883261, 2320284, 57524584, 10149186, 100773065, 5808646, 119341477, 31824763, + 98343453, 39645030, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 57475529, 116425, 26083934, 2897444, 60744427, 30866345, 609720, 15878753, + 60138459, 24519663, + ]), + xy2d: FieldElement2625::from_limbs([ + 39351007, 247743, 51914090, 24551880, 23288160, 23542496, 43239268, 6503645, + 20650474, 1804084, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 106627923, 49010854, 76081380, 42024039, 82749485, 37994278, 70230858, 56779150, + 94951478, 33352103, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 51801891, 2839643, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, + 55733782, 12714368, + ]), + xy2d: FieldElement2625::from_limbs([ + 20807691, 26283607, 29286140, 11421711, 39232341, 19686201, 45881388, 1035545, + 47375635, 12796919, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 79185725, 52807577, 58323861, 21705509, 42096072, 49955115, 49517368, 20654993, + 70589528, 51926048, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 34747315, 5457596, 28548107, 7833186, 7303070, 21600887, 42745799, 17632556, + 33734809, 2771024, + ]), + xy2d: FieldElement2625::from_limbs([ + 45719598, 421931, 26597266, 6860826, 22486084, 26817260, 49971378, 29344205, + 42556581, 15673396, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 46924223, 35892647, 19788684, 57487908, 63107597, 24813538, 46837679, 38287685, + 70836007, 20619983, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 6120100, 814863, 55314462, 32931715, 6812204, 17806661, 2019593, 7975683, 31123697, + 22595451, + ]), + xy2d: FieldElement2625::from_limbs([ + 30069250, 22119100, 30434653, 2958439, 18399564, 32578143, 12296868, 9204260, + 50676426, 9648164, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 32705413, 32003455, 97814521, 41005496, 55303257, 43186244, 70414129, 38803035, + 108209395, 22176929, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 17219846, 2375039, 35537917, 27978816, 47649184, 9219902, 294711, 15298639, + 2662509, 17257359, + ]), + xy2d: FieldElement2625::from_limbs([ + 65935918, 25995736, 62742093, 29266687, 45762450, 25120105, 32087528, 32331655, + 32247247, 19164571, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 14312609, 34775988, 17395389, 58408721, 62163121, 58424228, 106019982, 23916613, + 51081240, 20175586, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 65680039, 23875441, 57873182, 6549686, 59725795, 33085767, 23046501, 9803137, + 17597934, 2346211, + ]), + xy2d: FieldElement2625::from_limbs([ + 18510781, 15337574, 26171504, 981392, 44867312, 7827555, 43617730, 22231079, + 3059832, 21771562, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77250443, 39637338, 84938156, 31606788, 76938955, 13613135, 41552228, 28009845, + 33606651, 37146527, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 33114149, 17665080, 40583177, 20211034, 33076704, 8716171, 1151462, 1521897, + 66126199, 26716628, + ]), + xy2d: FieldElement2625::from_limbs([ + 34169699, 29298616, 23947180, 33230254, 34035889, 21248794, 50471177, 3891703, + 26353178, 693168, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 97483084, 35150011, 117333688, 46741361, 71709207, 33961335, 76694157, 33153763, + 31375463, 47924397, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 52738210, 25781902, 1510300, 6434173, 48324075, 27291703, 32732229, 20445593, + 17901440, 16011505, + ]), + xy2d: FieldElement2625::from_limbs([ + 18171223, 21619806, 54608461, 15197121, 56070717, 18324396, 47936623, 17508055, + 8764034, 12309598, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 73084753, 28311243, 47649501, 23872684, 55567586, 14015781, 110551971, 34782749, + 17544095, 22960650, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 5811932, 31839139, 3442886, 31285122, 48741515, 25194890, 49064820, 18144304, + 61543482, 12348899, + ]), + xy2d: FieldElement2625::from_limbs([ + 35709185, 11407554, 25755363, 6891399, 63851926, 14872273, 42259511, 8141294, + 56476330, 32968952, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 121542424, 34248456, 62032718, 46854775, 81124121, 19103037, 124519055, 22225380, + 30944592, 1130208, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 8247747, 26843490, 40546482, 25845122, 52706924, 18905521, 4652151, 2488540, + 23550156, 33283200, + ]), + xy2d: FieldElement2625::from_limbs([ + 17294297, 29765994, 7026747, 15626851, 22990044, 113481, 2267737, 27646286, + 66700045, 33416712, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 83199930, 17300505, 85708115, 40895109, 69246500, 32332774, 63744702, 48105367, + 70369388, 26388160, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 62198760, 20221544, 18550886, 10864893, 50649539, 26262835, 44079994, 20349526, + 54360141, 2701325, + ]), + xy2d: FieldElement2625::from_limbs([ + 58534169, 16099414, 4629974, 17213908, 46322650, 27548999, 57090500, 9276970, + 11329923, 1862132, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 14763057, 17650824, 103299457, 3689865, 70620756, 43867957, 45157775, 45773662, + 58070900, 32614131, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 8894987, 30108338, 6150752, 3013931, 301220, 15693451, 35127648, 30644714, + 51670695, 11595569, + ]), + xy2d: FieldElement2625::from_limbs([ + 15214943, 3537601, 40870142, 19495559, 4418656, 18323671, 13947275, 10730794, + 53619402, 29190761, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 64570539, 41237224, 99867876, 33817540, 104232996, 25598978, 111885603, 23365795, + 68085971, 34254425, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 54642373, 4195083, 57897332, 550903, 51543527, 12917919, 19118110, 33114591, + 36574330, 19216518, + ]), + xy2d: FieldElement2625::from_limbs([ + 31788442, 19046775, 4799988, 7372237, 8808585, 18806489, 9408236, 23502657, + 12493931, 28145115, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 41428258, 5260743, 47873055, 27269961, 63412921, 16566086, 94327144, 36161552, + 29375954, 6024730, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 842132, 30759739, 62345482, 24831616, 26332017, 21148791, 11831879, 6985184, + 57168503, 2854095, + ]), + xy2d: FieldElement2625::from_limbs([ + 62261602, 25585100, 2516241, 27706719, 9695690, 26333246, 16512644, 960770, + 12121869, 16648078, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 51890193, 48221527, 53772634, 35568148, 97707150, 33090294, 35603941, 25672367, + 20237805, 36392843, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 47820798, 4453151, 15298546, 17376044, 22115042, 17581828, 12544293, 20083975, + 1068880, 21054527, + ]), + xy2d: FieldElement2625::from_limbs([ + 57549981, 17035596, 33238497, 13506958, 30505848, 32439836, 58621956, 30924378, + 12521377, 4845654, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 106019188, 44298538, 64150483, 43754095, 74868174, 54020263, 70518210, 32681031, + 127735421, 20668560, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 43547042, 6230155, 46726851, 10655313, 43068279, 21933259, 10477733, 32314216, + 63995636, 13974497, + ]), + xy2d: FieldElement2625::from_limbs([ + 12966261, 15550616, 35069916, 31939085, 21025979, 32924988, 5642324, 7188737, + 18895762, 12629579, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 14741860, 18607545, 89286071, 21833194, 68388604, 41613031, 11758139, 34343875, + 32195180, 37450109, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10758205, 15755439, 62598914, 9243697, 62229442, 6879878, 64904289, 29988312, + 58126794, 4429646, + ]), + xy2d: FieldElement2625::from_limbs([ + 64654951, 15725972, 46672522, 23143759, 61304955, 22514211, 59972993, 21911536, + 18047435, 18272689, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 41935825, 55801698, 29759954, 45331216, 111955344, 51288407, 78101976, 54258026, + 49488161, 57700395, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 21987233, 700364, 42603816, 14972007, 59334599, 27836036, 32155025, 2581431, + 37149879, 8773374, + ]), + xy2d: FieldElement2625::from_limbs([ + 41540495, 454462, 53896929, 16126714, 25240068, 8594567, 20656846, 12017935, + 59234475, 19634276, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 73137027, 39817509, 103205921, 55807152, 66289943, 36016203, 102376553, 61640820, + 65387074, 30777706, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 54829870, 16624276, 987579, 27631834, 32908202, 1248608, 7719845, 29387734, + 28408819, 6816612, + ]), + xy2d: FieldElement2625::from_limbs([ + 56750770, 25316602, 19549650, 21385210, 22082622, 16147817, 20613181, 13982702, + 56769294, 5067942, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 36602859, 29732664, 79183544, 13582411, 47230892, 35998382, 47389577, 12746131, + 72440074, 57002919, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 30528792, 3601899, 65151774, 4619784, 39747042, 18118043, 24180792, 20984038, + 27679907, 31905504, + ]), + xy2d: FieldElement2625::from_limbs([ + 9402385, 19597367, 32834042, 10838634, 40528714, 20317236, 26653273, 24868867, + 22611443, 20839026, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 89299435, 34672460, 22736440, 48684895, 103757035, 27563109, 86298488, 62459921, + 71963721, 40176570, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 58798126, 30600981, 58846284, 30166382, 56707132, 33282502, 13424425, 29987205, + 26404408, 13001963, + ]), + xy2d: FieldElement2625::from_limbs([ + 35867026, 18138731, 64114613, 8939345, 11562230, 20713762, 41044498, 21932711, + 51703708, 11020692, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 68974887, 59159374, 59210213, 23253421, 12483314, 47031979, 70284499, 21130268, + 28761761, 34961166, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 66660290, 31776765, 13018550, 3194501, 57528444, 22392694, 24760584, 29207344, + 25577410, 20175752, + ]), + xy2d: FieldElement2625::from_limbs([ + 42818486, 4759344, 66418211, 31701615, 2066746, 10693769, 37513074, 9884935, + 57739938, 4745409, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 57967561, 39604145, 47577802, 29213020, 102956929, 43498706, 51646855, 55797011, + 78040786, 21622500, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 50547351, 14112679, 59096219, 4817317, 59068400, 22139825, 44255434, 10856640, + 46638094, 13434653, + ]), + xy2d: FieldElement2625::from_limbs([ + 22759470, 23480998, 50342599, 31683009, 13637441, 23386341, 1765143, 20900106, + 28445306, 28189722, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 29875044, 46048045, 69904399, 63322533, 68819482, 48735613, 56913146, 24765756, + 9074233, 34721612, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 40903181, 11014232, 57266213, 30918946, 40200743, 7532293, 48391976, 24018933, + 3843902, 9367684, + ]), + xy2d: FieldElement2625::from_limbs([ + 56139269, 27150720, 9591133, 9582310, 11349256, 108879, 16235123, 8601684, + 66969667, 4242894, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 89201818, 53917740, 65066069, 21585919, 99295616, 55591475, 60534521, 36025091, + 106800361, 16625499, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56051142, 3042015, 13770083, 24296510, 584235, 33009577, 59338006, 2602724, + 39757248, 14247412, + ]), + xy2d: FieldElement2625::from_limbs([ + 6314156, 23289540, 34336361, 15957556, 56951134, 168749, 58490057, 14290060, + 27108877, 32373552, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 58522248, 26383465, 80350645, 44514587, 34117848, 19759835, 100656839, 22495542, + 107069276, 34536304, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 22833421, 9293594, 34459416, 19935764, 57971897, 14756818, 44180005, 19583651, + 56629059, 17356469, + ]), + xy2d: FieldElement2625::from_limbs([ + 59340277, 3326785, 38997067, 10783823, 19178761, 14905060, 22680049, 13906969, + 51175174, 3797898, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 88830182, 29341685, 54902740, 42864613, 63226624, 19901321, 90849087, 30845199, + 87600846, 59066711, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 9209251, 18419377, 53852306, 27386633, 66377847, 15289672, 25947805, 15286587, + 30997318, 26851369, + ]), + xy2d: FieldElement2625::from_limbs([ + 7392013, 16618386, 23946583, 25514540, 53843699, 32020573, 52911418, 31232855, + 17649997, 33304352, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 57807757, 52915036, 97718388, 30504888, 41933794, 32270679, 51867297, 24028707, + 64875610, 41216577, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 49550191, 1763593, 33994528, 15908609, 37067994, 21380136, 7335079, 25082233, + 63934189, 3440182, + ]), + xy2d: FieldElement2625::from_limbs([ + 47219164, 27577423, 42997570, 23865561, 10799742, 16982475, 40449, 29122597, + 4862399, 1133, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 34252636, 25680474, 61686474, 48415381, 50789832, 41510573, 74366924, 33866292, + 36513872, 26175010, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 63335436, 31988495, 28985339, 7499440, 24445838, 9325937, 29727763, 16527196, + 18278453, 15405622, + ]), + xy2d: FieldElement2625::from_limbs([ + 62726958, 8508651, 47210498, 29880007, 61124410, 15149969, 53795266, 843522, + 45233802, 13626196, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 69390312, 20067376, 56193445, 30944521, 68988221, 49718638, 56324981, 37508223, + 80449702, 15928662, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 31727126, 26374577, 48671360, 25270779, 2875792, 17164102, 41838969, 26539605, + 43656557, 5964752, + ]), + xy2d: FieldElement2625::from_limbs([ + 4100401, 27594980, 49929526, 6017713, 48403027, 12227140, 40424029, 11344143, + 2538215, 25983677, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 57675240, 6123112, 78268667, 31397823, 97125143, 48520672, 46633880, 35039852, + 66479607, 17595569, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 40304287, 4260918, 11851389, 9658551, 35091757, 16367491, 46903439, 20363143, + 11659921, 22439314, + ]), + xy2d: FieldElement2625::from_limbs([ + 26180377, 10015009, 36264640, 24973138, 5418196, 9480663, 2231568, 23384352, + 33100371, 32248261, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 82229958, 28352560, 56718958, 48982252, 39598926, 17561924, 88779810, 38041106, + 61177053, 19088051, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 16166467, 24070699, 56004733, 6023907, 35182066, 32189508, 2340059, 17299464, + 56373093, 23514607, + ]), + xy2d: FieldElement2625::from_limbs([ + 28042865, 29997343, 54982337, 12259705, 63391366, 26608532, 6766452, 24864833, + 18036435, 5803270, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 66291264, 40318343, 78912424, 35140016, 78067310, 30883266, 23855390, 4598332, + 60949433, 19436993, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 36077558, 19298237, 17332028, 31170912, 31312681, 27587249, 696308, 50292, + 47013125, 11763583, + ]), + xy2d: FieldElement2625::from_limbs([ + 66514282, 31040148, 34874710, 12643979, 12650761, 14811489, 665117, 20940800, + 47335652, 22840869, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 97573435, 55845991, 62981386, 20819953, 86944190, 60003250, 109821551, 35630203, + 50088706, 34546902, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 18357166, 26559999, 7766381, 16342475, 37783946, 411173, 14578841, 8080033, + 55534529, 22952821, + ]), + xy2d: FieldElement2625::from_limbs([ + 19598397, 10334610, 12555054, 2555664, 18821899, 23214652, 21873262, 16014234, + 26224780, 16452269, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 36884920, 5145195, 73053412, 49940397, 71085598, 35564328, 122839923, 25936244, + 46575034, 37253081, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 14187449, 3448569, 56472628, 22743496, 44444983, 30120835, 7268409, 22663988, + 27394300, 12015369, + ]), + xy2d: FieldElement2625::from_limbs([ + 19695742, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, 32241655, + 53849736, 30151970, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 97968948, 12735207, 65220619, 28854697, 50133957, 35811371, 126051714, 45852742, + 58558339, 23160969, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 61389038, 22309106, 65198214, 15569034, 26642876, 25966672, 61319509, 18435777, + 62132699, 12651792, + ]), + xy2d: FieldElement2625::from_limbs([ + 64260450, 9953420, 11531313, 28271553, 26895122, 20857343, 53990043, 17036529, + 9768697, 31021214, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 109498250, 35449081, 66821165, 28850346, 82457582, 25397901, 32767512, 46319882, + 72048958, 44232657, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 18860224, 15980149, 48121624, 31991861, 40875851, 22482575, 59264981, 13944023, + 42736516, 16582018, + ]), + xy2d: FieldElement2625::from_limbs([ + 51604604, 4970267, 37215820, 4175592, 46115652, 31354675, 55404809, 15444559, + 56105103, 7989036, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 98599278, 39122492, 64696060, 35736814, 34772016, 38086117, 35030594, 39754637, + 47422750, 52308692, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 49800177, 17674491, 35586086, 33551600, 34221481, 16375548, 8680158, 17182719, + 28550067, 26697300, + ]), + xy2d: FieldElement2625::from_limbs([ + 38981977, 27866340, 16837844, 31733974, 60258182, 12700015, 37068883, 4364037, + 1155602, 5988841, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 88999280, 20281524, 121593716, 12154347, 59276991, 48854927, 90257846, 29083950, + 91727270, 41837612, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 33972757, 23041680, 9975415, 6841041, 35549071, 16356535, 3070187, 26528504, + 1466168, 10740210, + ]), + xy2d: FieldElement2625::from_limbs([ + 65599446, 18066246, 53605478, 22898515, 32799043, 909394, 53169961, 27774712, + 34944214, 18227391, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 71069668, 19286628, 39082773, 51190812, 47704004, 46701299, 82676190, 34505938, + 63848542, 32980496, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 24740822, 5052253, 37014733, 8961360, 25877428, 6165135, 42740684, 14397371, + 59728495, 27410326, + ]), + xy2d: FieldElement2625::from_limbs([ + 38220480, 3510802, 39005586, 32395953, 55870735, 22922977, 51667400, 19101303, + 65483377, 27059617, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 67902144, 24323953, 75945165, 27318724, 39747955, 31184838, 100261706, 62223612, + 57202662, 32932579, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 5666214, 525582, 20782575, 25516013, 42570364, 14657739, 16099374, 1468826, + 60937436, 18367850, + ]), + xy2d: FieldElement2625::from_limbs([ + 62249590, 29775088, 64191105, 26806412, 7778749, 11688288, 36704511, 23683193, + 65549940, 23690785, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 10896313, 25834728, 67933138, 34027032, 114757419, 36564017, 25248957, 48337770, + 36527387, 17796587, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10566929, 12612572, 35164652, 11118702, 54475488, 12362878, 21752402, 8822496, + 24003793, 14264025, + ]), + xy2d: FieldElement2625::from_limbs([ + 27713843, 26198459, 56100623, 9227529, 27050101, 2504721, 23886875, 20436907, + 13958494, 27821979, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 110736080, 38421656, 39861735, 37454952, 29838368, 25342141, 102328328, 23512649, + 74449384, 51698795, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 4646495, 25543308, 44342840, 22021777, 23184552, 8566613, 31366726, 32173371, + 52042079, 23179239, + ]), + xy2d: FieldElement2625::from_limbs([ + 49838347, 12723031, 50115803, 14878793, 21619651, 27356856, 27584816, 3093888, + 58265170, 3849920, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 58043933, 35657603, 92670503, 51983125, 61869038, 43137389, 99585908, 24536476, + 72111157, 18004172, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 55051311, 22376525, 21115584, 20189277, 8808711, 21523724, 16489529, 13378448, + 41263148, 12741425, + ]), + xy2d: FieldElement2625::from_limbs([ + 61162478, 10645102, 36197278, 15390283, 63821882, 26435754, 24306471, 15852464, + 28834118, 25908360, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 49773097, 24447374, 109686448, 42989383, 58636779, 32971069, 54018092, 34010272, + 87570721, 39045736, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 13669229, 17458950, 54626889, 23351392, 52539093, 21661233, 42112877, 11293806, + 38520660, 24132599, + ]), + xy2d: FieldElement2625::from_limbs([ + 28497909, 6272777, 34085870, 14470569, 8906179, 32328802, 18504673, 19389266, + 29867744, 24758489, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 50901822, 47071627, 39309233, 19856633, 24009063, 60734973, 60741262, 53933471, + 22853427, 29542421, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 24191359, 16712145, 53177067, 15217830, 14542237, 1646131, 18603514, 22516545, + 12876622, 31441985, + ]), + xy2d: FieldElement2625::from_limbs([ + 17902668, 4518229, 66697162, 30725184, 26878216, 5258055, 54248111, 608396, + 16031844, 3723494, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 105584936, 12763726, 46662418, 41131935, 33001347, 54091119, 17558840, 59235974, + 23896952, 29240187, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 47103464, 21542479, 31520463, 605201, 2543521, 5991821, 64163800, 7229063, + 57189218, 24727572, + ]), + xy2d: FieldElement2625::from_limbs([ + 28816026, 298879, 38943848, 17633493, 19000927, 31888542, 54428030, 30605106, + 49057085, 31471516, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 16000882, 33209536, 70601955, 55661665, 37604267, 20394642, 79686603, 49595699, + 47393623, 7847706, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, 34252933, 27035413, + 57088296, 3852847, + ]), + xy2d: FieldElement2625::from_limbs([ + 55678375, 15697595, 45987307, 29133784, 5386313, 15063598, 16514493, 17622322, + 29330898, 18478208, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 41609110, 29175637, 51885955, 26653220, 83724594, 35606215, 70412565, 33569921, + 106668931, 45868821, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 15683501, 27551389, 18109119, 23573784, 15337967, 27556609, 50391428, 15921865, + 16103996, 29823217, + ]), + xy2d: FieldElement2625::from_limbs([ + 43939021, 22773182, 13588191, 31925625, 63310306, 32479502, 47835256, 5402698, + 37293151, 23713330, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 90299521, 35939014, 34394523, 37016585, 104314072, 32025298, 55842007, 8911516, + 109011869, 36294143, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 21374101, 30000182, 33584214, 9874410, 15377179, 11831242, 33578960, 6134906, + 4931255, 11987849, + ]), + xy2d: FieldElement2625::from_limbs([ + 67101132, 30575573, 50885377, 7277596, 105524, 33232381, 35628324, 13861387, + 37032554, 10117929, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 37607694, 22809559, 40945095, 13051538, 41483300, 38644074, 127892224, 40258509, + 79998882, 15728939, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 45136504, 21783052, 66157804, 29135591, 14704839, 2695116, 903376, 23126293, + 12885166, 8311031, + ]), + xy2d: FieldElement2625::from_limbs([ + 49592363, 5352193, 10384213, 19742774, 7506450, 13453191, 26423267, 4384730, + 1888765, 28119028, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 108400371, 64001550, 120723127, 30371924, 98005322, 19632702, 101966083, 20846561, + 47644429, 30214188, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 43500868, 30888657, 66582772, 4651135, 5765089, 4618330, 6092245, 14845197, + 17151279, 23700316, + ]), + xy2d: FieldElement2625::from_limbs([ + 42278406, 20820711, 51942885, 10367249, 37577956, 33289075, 22825804, 26467153, + 50242379, 16176524, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 43525570, 40119392, 87172552, 37352659, 129477549, 40913655, 69115045, 23191005, + 38362610, 56911354, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56482264, 29068029, 53788301, 28429114, 3432135, 27161203, 23632036, 31613822, + 32808309, 1099883, + ]), + xy2d: FieldElement2625::from_limbs([ + 15030958, 5768825, 39657628, 30667132, 60681485, 18193060, 51830967, 26745081, + 2051440, 18328567, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 63746522, 26315059, 74626753, 43379423, 90664713, 33849800, 72257261, 52954675, + 44422508, 50188091, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 4577067, 16802144, 13249840, 18250104, 19958762, 19017158, 18559669, 22794883, + 8402477, 23690159, + ]), + xy2d: FieldElement2625::from_limbs([ + 38702534, 32502850, 40318708, 32646733, 49896449, 22523642, 9453450, 18574360, + 17983009, 9967138, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 41346351, 40079153, 93694351, 43523701, 24709297, 34774792, 65430873, 7806336, + 84616260, 37205991, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56688388, 29436320, 14584638, 15971087, 51340543, 8861009, 26556809, 27979875, + 48555541, 22197296, + ]), + xy2d: FieldElement2625::from_limbs([ + 2839082, 14284142, 4029895, 3472686, 14402957, 12689363, 40466743, 8459446, + 61503401, 25932490, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 62269556, 30018987, 76853824, 2871047, 92222842, 36741449, 109106914, 32705364, + 84366947, 25576692, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 18164541, 22959256, 49953981, 32012014, 19237077, 23809137, 23357532, 18337424, + 26908269, 12150756, + ]), + xy2d: FieldElement2625::from_limbs([ + 36843994, 25906566, 5112248, 26517760, 65609056, 26580174, 43167, 28016731, + 34806789, 16215818, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 60209940, 43378825, 54804084, 29153342, 102820586, 27277595, 99683352, 46087336, + 59605791, 24879084, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 39765323, 17038963, 39957339, 22831480, 946345, 16291093, 254968, 7168080, + 21676107, 31611404, + ]), + xy2d: FieldElement2625::from_limbs([ + 21260942, 25129680, 50276977, 21633609, 43430902, 3968120, 63456915, 27338965, + 63552672, 25641356, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 16544735, 46804798, 50304435, 49100673, 62525860, 46311689, 64646555, 24874095, + 48201831, 23891632, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 64693606, 17976703, 18312302, 4964443, 51836334, 20900867, 26820650, 16690659, + 25459437, 28989823, + ]), + xy2d: FieldElement2625::from_limbs([ + 41964155, 11425019, 28423002, 22533875, 60963942, 17728207, 9142794, 31162830, + 60676445, 31909614, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 44004193, 39807907, 16964146, 29785560, 109103755, 54812425, 39651637, 50764205, + 73444554, 40804420, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 36775618, 13979674, 7503222, 21186118, 55152142, 28932738, 36836594, 2682241, + 25993170, 21075909, + ]), + xy2d: FieldElement2625::from_limbs([ + 4364628, 5930691, 32304656, 23509878, 59054082, 15091130, 22857016, 22955477, + 31820367, 15075278, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 98987979, 24635738, 84367624, 33645057, 126175891, 28636721, 91271651, 23903545, + 116247489, 46387475, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 19073683, 14851414, 42705695, 21694263, 7625277, 11091125, 47489674, 2074448, + 57694925, 14905376, + ]), + xy2d: FieldElement2625::from_limbs([ + 24483648, 21618865, 64589997, 22007013, 65555733, 15355505, 41826784, 9253128, + 27628530, 25998952, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 84706452, 41895034, 86464480, 34106618, 26198469, 30377849, 71702187, 24396849, + 120106852, 48851446, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 510886, 14337390, 35323607, 16638631, 6328095, 2713355, 46891447, 21690211, + 8683220, 2921426, + ]), + xy2d: FieldElement2625::from_limbs([ + 18606791, 11874196, 27155355, 28272950, 43077121, 6265445, 41930624, 32275507, + 4674689, 13890525, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 13609605, 13069022, 106845367, 20498522, 91469449, 43147405, 82086020, 43389536, + 71498550, 33842827, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 9922506, 33035038, 13613106, 5883594, 48350519, 33120168, 54804801, 8317627, + 23388070, 16052080, + ]), + xy2d: FieldElement2625::from_limbs([ + 12719997, 11937594, 35138804, 28525742, 26900119, 8561328, 46953177, 21921452, + 52354592, 22741539, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 83070703, 47704840, 93825794, 32888599, 111423399, 47157999, 78938436, 41022275, + 38286735, 34483706, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 11038231, 21972036, 39798381, 26237869, 56610336, 17246600, 43629330, 24182562, + 45715720, 2465073, + ]), + xy2d: FieldElement2625::from_limbs([ + 20017144, 29231206, 27915241, 1529148, 12396362, 15675764, 13817261, 23896366, + 2463390, 28932292, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 50749967, 20890520, 122152544, 38550884, 65852441, 34628003, 76692421, 12851106, + 71112760, 46228148, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 65377275, 18398561, 63845933, 16143081, 19294135, 13385325, 14741514, 24450706, + 7903885, 2348101, + ]), + xy2d: FieldElement2625::from_limbs([ + 24536016, 17039225, 12715591, 29692277, 1511292, 10047386, 63266518, 26425272, + 38731325, 10048126, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54486638, 27349611, 97827688, 2591311, 56491836, 12192839, 85982162, 59811773, + 34811106, 15221631, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 40630742, 22450567, 11546243, 31701949, 9180879, 7656409, 45764914, 2095754, + 29769758, 6593415, + ]), + xy2d: FieldElement2625::from_limbs([ + 35114656, 30646970, 4176911, 3264766, 12538965, 32686321, 26312344, 27435754, + 30958053, 8292160, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 98538667, 53149747, 96282394, 15632447, 12174511, 64348770, 99917693, 37531617, + 93251999, 30405555, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 22648882, 1402143, 44308880, 13746058, 7936347, 365344, 58440231, 31879998, + 63350620, 31249806, + ]), + xy2d: FieldElement2625::from_limbs([ + 51616947, 8012312, 64594134, 20851969, 43143017, 23300402, 65496150, 32018862, + 50444388, 8194477, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 27338047, 26047012, 59694639, 10140404, 48082437, 26964542, 94386054, 42409807, + 95681149, 36559595, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 26287105, 4821776, 25476601, 29408529, 63344350, 17765447, 49100281, 1182478, + 41014043, 20474836, + ]), + xy2d: FieldElement2625::from_limbs([ + 59937691, 3178079, 23970071, 6201893, 49913287, 29065239, 45232588, 19571804, + 32208682, 32356184, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 50451143, 36372074, 56822501, 14811297, 73133531, 46903936, 39793359, 56611021, + 39436277, 22014573, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 15941010, 24148500, 45741813, 8062054, 31876073, 33315803, 51830470, 32110002, + 15397330, 29424239, + ]), + xy2d: FieldElement2625::from_limbs([ + 8934485, 20068965, 43822466, 20131190, 34662773, 14047985, 31170398, 32113411, + 39603297, 15087183, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 115860466, 31397939, 24524912, 16876564, 82629290, 27193655, 118715321, 11461894, + 83897392, 27685489, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 65161459, 16013772, 21750665, 3714552, 49707082, 17498998, 63338576, 23231111, + 31322513, 21938797, + ]), + xy2d: FieldElement2625::from_limbs([ + 21426636, 27904214, 53460576, 28206894, 38296674, 28633461, 48833472, 18933017, + 13040861, 21441484, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 78402740, 46032517, 107081326, 48638180, 104910306, 14748870, 14555558, 20137329, + 68722574, 38451366, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 41213962, 15323293, 58619073, 25496531, 25967125, 20128972, 2825959, 28657387, + 43137087, 22287016, + ]), + xy2d: FieldElement2625::from_limbs([ + 51184079, 28324551, 49665331, 6410663, 3622847, 10243618, 20615400, 12405433, + 43355834, 25118015, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 127126414, 46110638, 114026375, 9025185, 50036385, 4333800, 71487300, 35986461, + 23097948, 32988414, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 4565804, 17528778, 20084411, 25711615, 1724998, 189254, 24767264, 10103221, + 48596551, 2424777, + ]), + xy2d: FieldElement2625::from_limbs([ + 366633, 21577626, 8173089, 26664313, 30788633, 5745705, 59940186, 1344108, + 63466311, 12412658, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 110215918, 41244716, 82038279, 33386174, 102006892, 53695876, 91271559, 51782359, + 63967361, 44733816, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 18289503, 18829478, 8056944, 16430056, 45379140, 7842513, 61107423, 32067534, + 48424218, 22110928, + ]), + xy2d: FieldElement2625::from_limbs([ + 476239, 6601091, 60956074, 23831056, 17503544, 28690532, 27672958, 13403813, + 11052904, 5219329, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 87787372, 25178693, 34436965, 42403554, 129207969, 48129182, 98295834, 29580701, + 9014761, 58529808, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 53464795, 23204192, 51146355, 5075807, 65594203, 22019831, 34006363, 9160279, + 8473550, 30297594, + ]), + xy2d: FieldElement2625::from_limbs([ + 24900749, 14435722, 17209120, 18261891, 44516588, 9878982, 59419555, 17218610, + 42540382, 11788947, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 63990690, 22159237, 53306774, 48351872, 76761311, 26708527, 47071426, 43965164, + 42540393, 32095740, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 51449703, 16736705, 44641714, 10215877, 58011687, 7563910, 11871841, 21049238, + 48595538, 8464117, + ]), + xy2d: FieldElement2625::from_limbs([ + 43708233, 8348506, 52522913, 32692717, 63158658, 27181012, 14325288, 8628612, + 33313881, 25183915, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 46921853, 28586496, 89476219, 38825978, 66011746, 28765593, 109412060, 23317576, + 58168128, 61290594, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 60160060, 31759219, 34483180, 17533252, 32635413, 26180187, 15989196, 20716244, + 28358191, 29300528, + ]), + xy2d: FieldElement2625::from_limbs([ + 43547083, 30755372, 34757181, 31892468, 57961144, 10429266, 50471180, 4072015, + 61757200, 5596588, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 105981130, 30164382, 79421759, 39767609, 3117141, 49632997, 29266238, 36111653, + 68877164, 15373192, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 59865506, 30307471, 62515396, 26001078, 66980936, 32642186, 66017961, 29049440, + 42448372, 3442909, + ]), + xy2d: FieldElement2625::from_limbs([ + 36898293, 5124042, 14181784, 8197961, 18964734, 21615339, 22597930, 7176455, + 48523386, 13365929, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 59231455, 32054473, 75433536, 38244510, 73370723, 34444877, 24538106, 24984246, + 57419264, 30522764, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 25008885, 22782833, 62803832, 23916421, 16265035, 15721635, 683793, 21730648, + 15723478, 18390951, + ]), + xy2d: FieldElement2625::from_limbs([ + 57448220, 12374378, 40101865, 26528283, 59384749, 21239917, 11879681, 5400171, + 519526, 32318556, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 22258378, 50776631, 59239045, 14613015, 44588609, 30603508, 46754982, 40870398, + 16648396, 41160072, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 59027556, 25089834, 58885552, 9719709, 19259459, 18206220, 23994941, 28272877, + 57640015, 4763277, + ]), + xy2d: FieldElement2625::from_limbs([ + 45409620, 9220968, 51378240, 1084136, 41632757, 30702041, 31088446, 25789909, + 55752334, 728111, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 26047201, 55357393, 127317403, 50587064, 91200930, 9158118, 62835319, 20998873, + 104852291, 28056158, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 17510331, 33231575, 5854288, 8403524, 17133918, 30441820, 38997856, 12327944, + 10750447, 10014012, + ]), + xy2d: FieldElement2625::from_limbs([ + 56796096, 3936951, 9156313, 24656749, 16498691, 32559785, 39627812, 32887699, + 3424690, 7540221, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 97431206, 26590321, 78469868, 29411114, 74542167, 4989747, 127146306, 50791643, + 57864597, 48812477, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 13054543, 30774935, 19155473, 469045, 54626067, 4566041, 5631406, 2711395, 1062915, + 28418087, + ]), + xy2d: FieldElement2625::from_limbs([ + 47868616, 22299832, 37599834, 26054466, 61273100, 13005410, 61042375, 12194496, + 32960380, 1459310, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 86960860, 40582355, 90778216, 43574797, 75695366, 26896524, 67503060, 27452546, + 85746866, 55933926, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 31395515, 15098109, 26581030, 8030562, 50580950, 28547297, 9012485, 25970078, + 60465776, 28111795, + ]), + xy2d: FieldElement2625::from_limbs([ + 57916680, 31207054, 65111764, 4529533, 25766844, 607986, 67095642, 9677542, + 34813975, 27098423, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 64664330, 33404494, 96457765, 8186664, 68982624, 12489862, 103283149, 25714738, + 59256019, 58970434, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 51872508, 18120922, 7766469, 746860, 26346930, 23332670, 39775412, 10754587, + 57677388, 5203575, + ]), + xy2d: FieldElement2625::from_limbs([ + 31834314, 14135496, 66338857, 5159117, 20917671, 16786336, 59640890, 26216907, + 31809242, 7347066, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 57502122, 21680191, 87523322, 46588417, 80825387, 21862550, 86906833, 21343176, + 82301739, 31466941, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 54445282, 31372712, 1168161, 29749623, 26747876, 19416341, 10609329, 12694420, + 33473243, 20172328, + ]), + xy2d: FieldElement2625::from_limbs([ + 33184999, 11180355, 15832085, 22169002, 65475192, 225883, 15089336, 22530529, + 60973201, 14480052, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 98417562, 27934433, 98139703, 31657332, 82783410, 26971548, 72605071, 13685226, + 27595050, 42291707, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 46790012, 18404192, 10933842, 17376410, 8335351, 26008410, 36100512, 20943827, + 26498113, 66511, + ]), + xy2d: FieldElement2625::from_limbs([ + 22644435, 24792703, 50437087, 4884561, 64003250, 19995065, 30540765, 29267685, + 53781076, 26039336, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 106199862, 9834843, 85726071, 30873119, 63706907, 53801357, 75314402, 13585436, + 117090263, 48669869, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 23711543, 32881517, 31206560, 25191721, 6164646, 23844445, 33572981, 32128335, + 8236920, 16492939, + ]), + xy2d: FieldElement2625::from_limbs([ + 43198286, 20038905, 40809380, 29050590, 25005589, 25867162, 19574901, 10071562, + 6708380, 27332008, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 69210217, 28624377, 86811594, 35922006, 118790560, 34602105, 72409880, 42883131, + 29955600, 55430554, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 3096359, 9271816, 45488000, 18032587, 52260867, 25961494, 41216721, 20918836, + 57191288, 6216607, + ]), + xy2d: FieldElement2625::from_limbs([ + 34493015, 338662, 41913253, 2510421, 37895298, 19734218, 24822829, 27407865, + 40341383, 7525078, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 44042196, 53123240, 83242349, 25658253, 130828162, 34333218, 66198527, 30771936, + 47722230, 45548532, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 21691500, 19929806, 66467532, 19187410, 3285880, 30070836, 42044197, 9718257, + 59631427, 13381417, + ]), + xy2d: FieldElement2625::from_limbs([ + 18445390, 29352196, 14979845, 11622458, 65381754, 29971451, 23111647, 27179185, + 28535281, 15779576, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 30098034, 36644094, 124983340, 16662133, 45801924, 44862842, 53040409, 12021729, + 77064149, 17251075, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 9734894, 18977602, 59635230, 24415696, 2060391, 11313496, 48682835, 9924398, + 20194861, 13380996, + ]), + xy2d: FieldElement2625::from_limbs([ + 40730762, 25589224, 44941042, 15789296, 49053522, 27385639, 65123949, 15707770, + 26342023, 10146099, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 41091971, 33334488, 88448054, 33513043, 86854119, 30675731, 37471583, 35781471, + 21612325, 33008704, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 54031477, 1184227, 23562814, 27583990, 46757619, 27205717, 25764460, 12243797, + 46252298, 11649657, + ]), + xy2d: FieldElement2625::from_limbs([ + 57077370, 11262625, 27384172, 2271902, 26947504, 17556661, 39943, 6114064, + 33514190, 2333242, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 112784121, 54687041, 75228644, 40774344, 45278341, 58092729, 60429112, 54438225, + 91459440, 20104430, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 62992557, 22282898, 43222677, 4843614, 37020525, 690622, 35572776, 23147595, + 8317859, 12352766, + ]), + xy2d: FieldElement2625::from_limbs([ + 18200138, 19078521, 34021104, 30857812, 43406342, 24451920, 43556767, 31266881, + 20712162, 6719373, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 26656189, 39629685, 59250307, 35440503, 105873684, 37816756, 78226393, 29791221, + 26224234, 30256974, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 49939907, 18700334, 63713187, 17184554, 47154818, 14050419, 21728352, 9493610, + 18620611, 17125804, + ]), + xy2d: FieldElement2625::from_limbs([ + 53785524, 13325348, 11432106, 5964811, 18609221, 6062965, 61839393, 23828875, + 36407290, 17074774, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 43248307, 55875704, 94070219, 35195292, 34695751, 16816491, 79357372, 28313792, + 80844205, 35488493, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 25089769, 6742589, 17081145, 20148166, 21909292, 17486451, 51972569, 29789085, + 45830866, 5473615, + ]), + xy2d: FieldElement2625::from_limbs([ + 31883658, 25593331, 1083431, 21982029, 22828470, 13290673, 59983779, 12469655, + 29111212, 28103418, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 91353792, 52058456, 107954750, 36345970, 52111264, 50221109, 91476329, 39943270, + 56813276, 34006814, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 41468082, 30136590, 5217915, 16224624, 19987036, 29472163, 42872612, 27639183, + 15766061, 8407814, + ]), + xy2d: FieldElement2625::from_limbs([ + 46701865, 13990230, 15495425, 16395525, 5377168, 15166495, 58191841, 29165478, + 59040954, 2276717, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 30157899, 46478498, 116505677, 42800183, 87003891, 36922573, 43281276, 38650650, + 89849239, 26251014, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 2041139, 19298082, 7783686, 13876377, 41161879, 20201972, 24051123, 13742383, + 51471265, 13295221, + ]), + xy2d: FieldElement2625::from_limbs([ + 33338218, 25048699, 12532112, 7977527, 9106186, 31839181, 49388668, 28941459, + 62657506, 18884987, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 47063564, 39008528, 52762315, 40001577, 28862070, 35438083, 64639597, 29412551, + 74879432, 43175028, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 23208049, 7979712, 33071466, 8149229, 1758231, 22719437, 30945527, 31860109, + 33606523, 18786461, + ]), + xy2d: FieldElement2625::from_limbs([ + 1439939, 17283952, 66028874, 32760649, 4625401, 10647766, 62065063, 1220117, + 30494170, 22113633, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 62071265, 20526136, 64138304, 30492664, 82749837, 26852765, 40369837, 34480481, + 65424524, 20220784, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 13908495, 30005160, 30919927, 27280607, 45587000, 7989038, 9021034, 9078865, + 3353509, 4033511, + ]), + xy2d: FieldElement2625::from_limbs([ + 37445433, 18440821, 32259990, 33209950, 24295848, 20642309, 23161162, 8839127, + 27485041, 7356032, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 76769853, 34259874, 79088928, 28184277, 65480320, 14661172, 60762722, 36179446, + 95539899, 50337029, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 43269631, 25243016, 41163352, 7480957, 49427195, 25200248, 44562891, 14150564, + 15970762, 4099461, + ]), + xy2d: FieldElement2625::from_limbs([ + 29262576, 16756590, 26350592, 24760869, 8529670, 22346382, 13617292, 23617289, + 11465738, 8317062, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 41615764, 26591503, 99609063, 24135380, 44070139, 31252209, 82007500, 37402886, + 88078197, 28396915, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 46724414, 19206718, 48772458, 13884721, 34069410, 2842113, 45498038, 29904543, + 11177094, 14989547, + ]), + xy2d: FieldElement2625::from_limbs([ + 42612143, 21838415, 16959895, 2278463, 12066309, 10137771, 13515641, 2581286, + 38621356, 9930239, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 49357223, 31456605, 83653163, 54099563, 118302919, 18605349, 18345766, 53705111, + 83400343, 28240393, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 33879670, 2553287, 32678213, 9875984, 8534129, 6889387, 57432090, 6957616, 4368891, + 9788741, + ]), + xy2d: FieldElement2625::from_limbs([ + 16660737, 7281060, 56278106, 12911819, 20108584, 25452756, 45386327, 24941283, + 16250551, 22443329, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 47343357, 35944957, 117666696, 14161978, 69014150, 39969338, 71798447, 10604806, + 104027325, 4782745, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 65754325, 14736940, 59741422, 20261545, 7710541, 19398842, 57127292, 4383044, + 22546403, 437323, + ]), + xy2d: FieldElement2625::from_limbs([ + 31665558, 21373968, 50922033, 1491338, 48740239, 3294681, 27343084, 2786261, + 36475274, 19457415, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 52641566, 32870716, 33734756, 41002983, 19294359, 14334329, 47418233, 35909750, + 47824192, 27440058, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 15121312, 17758270, 6377019, 27523071, 56310752, 20596586, 18952176, 15496498, + 37728731, 11754227, + ]), + xy2d: FieldElement2625::from_limbs([ + 64471568, 20071356, 8488726, 19250536, 12728760, 31931939, 7141595, 11724556, + 22761615, 23420291, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 16918416, 11729663, 49025285, 36577418, 103201995, 53769203, 38367677, 21327038, + 32851221, 11717399, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 11166615, 7338049, 60386341, 4531519, 37640192, 26252376, 31474878, 3483633, + 65915689, 29523600, + ]), + xy2d: FieldElement2625::from_limbs([ + 66923210, 9921304, 31456609, 20017994, 55095045, 13348922, 33142652, 6546660, + 47123585, 29606055, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 101757113, 44821142, 55911756, 25655328, 31703693, 37410335, 58571732, 20721383, + 36336829, 18068118, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 49102387, 12709067, 3991746, 27075244, 45617340, 23004006, 35973516, 17504552, + 10928916, 3011958, + ]), + xy2d: FieldElement2625::from_limbs([ + 60151107, 17960094, 31696058, 334240, 29576716, 14796075, 36277808, 20749251, + 18008030, 10258577, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 44660220, 49210000, 74127342, 29144428, 36794597, 32352840, 65255398, 34921551, + 92236737, 6671742, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 29701166, 19180498, 56230743, 9279287, 67091296, 13127209, 21382910, 11042292, + 25838796, 4642684, + ]), + xy2d: FieldElement2625::from_limbs([ + 46678630, 14955536, 42982517, 8124618, 61739576, 27563961, 30468146, 19653792, + 18423288, 4177476, + ]), + }, + ]), +]); + +/// Odd multiples of the basepoint `[B, 3B, 5B, 7B, 9B, 11B, 13B, 15B, ..., 127B]`. +#[cfg(feature = "precomputed-tables")] +#[allow(dead_code)] +pub(crate) const AFFINE_ODD_MULTIPLES_OF_BASEPOINT: NafLookupTable8 = + NafLookupTable8([ + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 93076338, 52752828, 29566454, 37215328, 54414518, 37569218, 94653489, 21800160, + 61029707, 35602036, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 54563134, 934261, 64385954, 3049989, 66381436, 9406985, 12720692, 5043384, + 19500929, 18085054, + ]), + xy2d: FieldElement2625::from_limbs([ + 58370664, 4489569, 9688441, 18769238, 10184608, 21191052, 29287918, 11864899, + 42594502, 29115885, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 82745136, 23865874, 24204772, 25642034, 67725840, 16869169, 94896463, 52336674, + 28944398, 32004408, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 16568933, 4717097, 55552716, 32452109, 15682895, 21747389, 16354576, 21778470, + 7689661, 11199574, + ]), + xy2d: FieldElement2625::from_limbs([ + 30464137, 27578307, 55329429, 17883566, 23220364, 15915852, 7512774, 10017326, + 49359771, 23634074, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77970208, 11473153, 27284546, 35535607, 37044514, 46132292, 99976748, 48069538, + 118779423, 44373810, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 4708026, 6336745, 20377586, 9066809, 55836755, 6594695, 41455196, 12483687, + 54440373, 5581305, + ]), + xy2d: FieldElement2625::from_limbs([ + 19563141, 16186464, 37722007, 4097518, 10237984, 29206317, 28542349, 13850243, + 43430843, 17738489, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 72262591, 43463716, 68832610, 30776557, 97632468, 39071304, 86589715, 38784565, + 43156424, 18378665, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 36839857, 30090922, 7665485, 10083793, 28475525, 1649722, 20654025, 16520125, + 30598449, 7715701, + ]), + xy2d: FieldElement2625::from_limbs([ + 28881826, 14381568, 9657904, 3680757, 46927229, 7843315, 35708204, 1370707, + 29794553, 32145132, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 44589852, 26862249, 14201701, 24808930, 43598457, 42399157, 85583074, 32192981, + 54046167, 47376308, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 60653668, 25714560, 3374701, 28813570, 40010246, 22982724, 31655027, 26342105, + 18853321, 19333481, + ]), + xy2d: FieldElement2625::from_limbs([ + 4566811, 20590564, 38133974, 21313742, 59506191, 30723862, 58594505, 23123294, + 2207752, 30344648, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 41954014, 62923042, 96790006, 41423232, 60254202, 24130566, 121780363, 32891430, + 103106264, 17421994, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 25576264, 30851218, 7349803, 21739588, 16472781, 9300885, 3844789, 15725684, + 171356, 6466918, + ]), + xy2d: FieldElement2625::from_limbs([ + 23103977, 13316479, 9739013, 17404951, 817874, 18515490, 8965338, 19466374, + 36393951, 16193876, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 100695917, 36735143, 64714733, 47558118, 50205389, 17283591, 84347261, 38283886, + 49034350, 9256799, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 41926547, 29380300, 32336397, 5036987, 45872047, 11360616, 22616405, 9761698, + 47281666, 630304, + ]), + xy2d: FieldElement2625::from_limbs([ + 53388152, 2639452, 42871404, 26147950, 9494426, 27780403, 60554312, 17593437, + 64659607, 19263131, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 63957664, 28508356, 76391577, 40420576, 102310665, 32691407, 48168288, 15033783, + 92213982, 25659555, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 42782475, 15950225, 35307649, 18961608, 55446126, 28463506, 1573891, 30928545, + 2198789, 17749813, + ]), + xy2d: FieldElement2625::from_limbs([ + 64009494, 10324966, 64867251, 7453182, 61661885, 30818928, 53296841, 17317989, + 34647629, 21263748, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 17735022, 27114469, 76149336, 40765111, 43325570, 26153544, 26948151, 45905235, + 38656900, 62179684, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 2154119, 14782993, 28737794, 11906199, 36205504, 26488101, 19338132, 16910143, + 50209922, 29794297, + ]), + xy2d: FieldElement2625::from_limbs([ + 29935700, 6336041, 20999566, 30405369, 13628497, 24612108, 61639745, 22359641, + 56973806, 18684690, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 29792811, 31379227, 113441390, 20675662, 58452680, 54138549, 42892249, 32958636, + 31674345, 24275271, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 7606599, 22131225, 17376912, 15235046, 32822971, 7512882, 30227203, 14344178, + 9952094, 8804749, + ]), + xy2d: FieldElement2625::from_limbs([ + 32575079, 3961822, 36404898, 17773250, 67073898, 1319543, 30641032, 7823672, + 63309858, 18878784, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77823924, 52933642, 26572931, 18690221, 109143683, 23989794, 79129572, 53326100, + 38888709, 55889506, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 37146997, 554126, 63326061, 20925660, 49205290, 8620615, 53375504, 25938867, + 8752612, 31225894, + ]), + xy2d: FieldElement2625::from_limbs([ + 4529887, 12416158, 60388162, 30157900, 15427957, 27628808, 61150927, 12724463, + 23658330, 23690055, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 102043267, 54823614, 45810225, 19657305, 54297192, 7413280, 66851983, 39718512, + 25005048, 18002658, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 5403481, 24654166, 61855580, 13522652, 14989680, 1879017, 43913069, 25724172, + 20315901, 421248, + ]), + xy2d: FieldElement2625::from_limbs([ + 34818947, 1705239, 25347020, 7938434, 51632025, 1720023, 54809726, 32655885, + 64907986, 5517607, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 88543525, 16557377, 80359887, 30047148, 91602876, 27723948, 62710290, 52707861, + 7715736, 61648232, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 14461032, 6393639, 22681353, 14533514, 52493587, 3544717, 57780998, 24657863, + 59891807, 31628125, + ]), + xy2d: FieldElement2625::from_limbs([ + 60864886, 31199953, 18524951, 11247802, 43517645, 21165456, 26204394, 27268421, + 63221077, 29979135, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 97491378, 10077555, 94805128, 42472719, 30231379, 17961119, 76201413, 41182329, + 41405214, 31798052, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 13670592, 720327, 7131696, 19360499, 66651570, 16947532, 3061924, 22871019, + 39814495, 20141336, + ]), + xy2d: FieldElement2625::from_limbs([ + 44847187, 28379568, 38472030, 23697331, 49441718, 3215393, 1669253, 30451034, + 62323912, 29368533, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 74923758, 35244493, 27222384, 30715870, 48444195, 28125622, 116052444, 32330148, + 92609232, 35372537, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 39340596, 15199968, 52787715, 18781603, 18787729, 5464578, 11652644, 8722118, + 57056621, 5153960, + ]), + xy2d: FieldElement2625::from_limbs([ + 5733861, 14534448, 59480402, 15892910, 30737296, 188529, 491756, 17646733, + 33071791, 15771063, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 85239571, 21331573, 119690709, 30172286, 44350959, 55826224, 68258766, 16209406, + 20222151, 32139086, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 52372801, 13847470, 52690845, 3802477, 48387139, 10595589, 13745896, 3112846, + 50361463, 2761905, + ]), + xy2d: FieldElement2625::from_limbs([ + 45982696, 12273933, 15897066, 704320, 31367969, 3120352, 11710867, 16405685, + 19410991, 10591627, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 82008850, 34439758, 89319886, 49124188, 34309215, 29866047, 80308709, 27738519, + 71739865, 46909287, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 36631997, 23300851, 59535242, 27474493, 59924914, 29067704, 17551261, 13583017, + 37580567, 31071178, + ]), + xy2d: FieldElement2625::from_limbs([ + 22641770, 21277083, 10843473, 1582748, 37504588, 634914, 15612385, 18139122, + 59415250, 22563863, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 76721854, 52814714, 41722368, 35285867, 53022548, 38255176, 93163883, 27627617, + 87963092, 33729456, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 61915349, 11733561, 59403492, 31381562, 29521830, 16845409, 54973419, 26057054, + 49464700, 796779, + ]), + xy2d: FieldElement2625::from_limbs([ + 3855018, 8248512, 12652406, 88331, 2948262, 971326, 15614761, 9441028, 29507685, + 8583792, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 76968870, 14808584, 76708906, 57649718, 23400175, 24077237, 63783137, 37471119, + 56750251, 30681804, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 33709664, 3740344, 52888604, 25059045, 46197996, 22678812, 45207164, 6431243, + 21300862, 27646257, + ]), + xy2d: FieldElement2625::from_limbs([ + 49811511, 9216232, 25043921, 18738174, 29145960, 3024227, 65580502, 530149, + 66809973, 22275500, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 23499366, 24936714, 38355445, 35908587, 82540167, 39280880, 46809413, 41143783, + 72530804, 49676198, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 45162189, 23851397, 9380591, 15192763, 36034862, 15525765, 5277811, 25040629, + 33286237, 31693326, + ]), + xy2d: FieldElement2625::from_limbs([ + 62424427, 13336013, 49368582, 1581264, 30884213, 15048226, 66823504, 4736577, + 53805192, 29608355, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 25190215, 26304748, 58928336, 42665707, 64280342, 38580230, 61299598, 20659504, + 30387592, 32519377, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 14480213, 17057820, 2286692, 32980967, 14693157, 22197912, 49247898, 9909859, + 236428, 16857435, + ]), + xy2d: FieldElement2625::from_limbs([ + 7877514, 29872867, 45886243, 25902853, 41998762, 6241604, 35694938, 15657879, + 56797932, 8609105, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54245189, 32562161, 57887697, 19509733, 45323534, 37472546, 27606727, 59528498, + 74398957, 44973176, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 28964163, 20950093, 44929966, 26145892, 34786807, 18058153, 18187179, 27016486, + 42438836, 14869174, + ]), + xy2d: FieldElement2625::from_limbs([ + 55703901, 1222455, 64329400, 24533246, 11330890, 9135834, 3589529, 19555234, + 53275553, 1207212, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 33323313, 35603165, 79328585, 6017848, 71286345, 23804207, 86644124, 44008367, + 55775078, 31816581, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 64814718, 27217688, 29891310, 4504619, 8548709, 21986323, 62140656, 12555980, + 34377058, 21436823, + ]), + xy2d: FieldElement2625::from_limbs([ + 49069441, 9880212, 33350825, 24576421, 24446077, 15616561, 19302117, 9370836, + 55172180, 28526191, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 95404934, 26757208, 123864063, 4572839, 69249194, 43584425, 53559055, 41742046, + 41167331, 24643278, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 35101859, 30958612, 66105296, 3168612, 22836264, 10055966, 22893634, 13045780, + 28576558, 30704591, + ]), + xy2d: FieldElement2625::from_limbs([ + 59987873, 21166324, 43296694, 15387892, 39447987, 19996270, 5059183, 19972934, + 30207804, 29631666, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 67444156, 16132892, 88330413, 37924284, 68147855, 57949418, 91481571, 24889160, + 62329722, 50712214, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56922508, 1347520, 23300731, 27393371, 42651667, 8512932, 27610931, 24436993, + 3998295, 3835244, + ]), + xy2d: FieldElement2625::from_limbs([ + 16327050, 22776956, 14746360, 22599650, 23700920, 11727222, 25900154, 21823218, + 34907363, 25105813, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 59807886, 12089757, 115624210, 41476837, 67589715, 26361580, 71355762, 44268661, + 67753061, 13128476, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 7174885, 26592113, 59892333, 6465478, 4145835, 17673606, 38764952, 22293290, + 1360980, 25805937, + ]), + xy2d: FieldElement2625::from_limbs([ + 40179568, 6331649, 42386021, 20205884, 15635073, 6103612, 56391180, 6789942, + 7597240, 24095312, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54776568, 36935932, 18757261, 41429535, 67215081, 34700142, 86560976, 61204154, + 26496794, 19612129, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 46701540, 24101444, 49515651, 25946994, 45338156, 9941093, 55509371, 31298943, + 1347425, 15381335, + ]), + xy2d: FieldElement2625::from_limbs([ + 53576449, 26135856, 17092785, 3684747, 57829121, 27109516, 2987881, 10987137, + 52269096, 15465522, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 80033010, 26264316, 72380996, 10039544, 94605936, 30615493, 60406855, 30400829, + 120765849, 45301372, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 35668062, 24246990, 47788280, 25128298, 37456967, 19518969, 43459670, 10724644, + 7294162, 4471290, + ]), + xy2d: FieldElement2625::from_limbs([ + 33813988, 3549109, 101112, 21464449, 4858392, 3029943, 59999440, 21424738, + 34313875, 1512799, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 29494960, 28240930, 51093230, 28823678, 92791151, 54796794, 77571888, 37795542, + 75765856, 10649531, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 63536751, 7572551, 62249759, 25202639, 32046232, 32318941, 29315141, 15424555, + 24706712, 28857648, + ]), + xy2d: FieldElement2625::from_limbs([ + 47618751, 5819839, 19528172, 20715950, 40655763, 20611047, 4960954, 6496879, + 2790858, 28045273, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 85174457, 55843901, 111946683, 31021158, 32797785, 48944265, 78338887, 31144772, + 82688001, 38470222, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 49664705, 3638040, 57888693, 19234931, 40104182, 28143840, 28667142, 18386877, + 18584835, 3592929, + ]), + xy2d: FieldElement2625::from_limbs([ + 12065039, 18867394, 6430594, 17107159, 1727094, 13096957, 61520237, 27056604, + 27026997, 13543966, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 68512926, 37577278, 94695528, 14209106, 95849194, 30038709, 51818051, 20241476, + 68980056, 42251074, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 17325298, 33376175, 65271265, 4931225, 31708266, 6292284, 23064744, 22072792, + 43945505, 9236924, + ]), + xy2d: FieldElement2625::from_limbs([ + 51955585, 20268063, 61151838, 26383348, 4766519, 20788033, 21173534, 27030753, + 9509140, 7790046, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 24124086, 38918775, 28620390, 10538620, 59433851, 19581010, 60862718, 43500219, + 77600721, 32213801, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 7062127, 13930079, 2259902, 6463144, 32137099, 24748848, 41557343, 29331342, + 47345194, 13022814, + ]), + xy2d: FieldElement2625::from_limbs([ + 18921826, 392002, 55817981, 6420686, 8000611, 22415972, 14722962, 26246290, + 20604450, 8079345, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 67710253, 26257798, 51499391, 46550521, 30228769, 53940987, 76234206, 43362242, + 77953697, 21034392, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 25817710, 8020883, 50134679, 21244805, 47057788, 8766556, 29308546, 22307963, + 49449920, 23874253, + ]), + xy2d: FieldElement2625::from_limbs([ + 11081015, 13522660, 12474691, 29260223, 48687631, 9341946, 16850694, 18637605, + 6199839, 14303642, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 64518173, 19894035, 117213833, 43031641, 79641718, 39533880, 66531934, 41205092, + 117735515, 13989682, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 6921800, 4421166, 59739491, 30510778, 43106355, 30941531, 9363541, 3394240, + 50874187, 23872585, + ]), + xy2d: FieldElement2625::from_limbs([ + 54293979, 23466866, 47184247, 20627378, 8313211, 5865878, 5948507, 32290343, + 52583140, 23139870, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 111574723, 24134616, 49842442, 23485580, 34844037, 45228427, 67103167, 25858409, + 38508586, 35097070, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 19879846, 15259900, 25020018, 14261729, 22075205, 25189303, 787540, 31325033, + 62422289, 16131171, + ]), + xy2d: FieldElement2625::from_limbs([ + 39487053, 27893575, 34654176, 25620816, 60209846, 23603919, 8931189, 12275052, + 38626469, 33438928, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 105416367, 9568747, 62672739, 49685015, 106242995, 4547918, 18403901, 38581738, + 60829966, 33150322, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 7950033, 25841033, 47276506, 3884935, 62418883, 2342083, 50269031, 14194015, + 27013685, 3320257, + ]), + xy2d: FieldElement2625::from_limbs([ + 35270691, 18076829, 46994271, 4273335, 43595882, 31742297, 58328702, 4594760, + 49180851, 18144010, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 30194115, 50068680, 49746331, 27470090, 40428285, 23271051, 70252167, 16153483, + 123511881, 27809602, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 27113466, 6865046, 4512771, 29327742, 29021084, 7405965, 33302911, 9322435, + 4307527, 32438240, + ]), + xy2d: FieldElement2625::from_limbs([ + 29337813, 24673346, 10359233, 30347534, 57709483, 9930840, 60607771, 24076133, + 20985293, 22480923, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 14579237, 33467236, 85745988, 15769997, 101228358, 21649866, 82685456, 59023858, + 86175344, 24337101, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 4472119, 14702190, 10432042, 22460027, 708461, 18783996, 34234374, 30870323, + 63796457, 10370850, + ]), + xy2d: FieldElement2625::from_limbs([ + 36957127, 19555637, 16244231, 24367549, 58999881, 13440043, 35147632, 8718974, + 43101064, 18487380, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 21818223, 34477173, 23913863, 22441963, 129271975, 14842154, 43035020, 9485973, + 53819529, 22318987, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 10874834, 4351765, 66252340, 17269436, 64427034, 30735311, 5883785, 28998531, + 44403022, 26064601, + ]), + xy2d: FieldElement2625::from_limbs([ + 64017630, 9755550, 37507935, 22752543, 4031638, 29903925, 47267417, 32706846, + 39147952, 21635901, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 81365001, 44927611, 97395185, 43985591, 66242539, 38517499, 52937891, 37374973, + 73352483, 38476849, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 43460763, 24260930, 21493330, 30888969, 23329454, 24545577, 58286855, 12750266, + 22391140, 26198125, + ]), + xy2d: FieldElement2625::from_limbs([ + 20477567, 24078713, 1674568, 4102219, 25208396, 13972305, 30389482, 19572626, + 1485666, 17679765, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 100511110, 23887606, 116505658, 30877106, 45483774, 25222431, 67931340, 37154158, + 32618865, 18610785, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 48647066, 166413, 55454758, 8889513, 21027475, 32728181, 43100067, 4690060, + 7520989, 16421303, + ]), + xy2d: FieldElement2625::from_limbs([ + 14868391, 20996450, 64836606, 1042490, 27060176, 10253541, 53431276, 19516737, + 41808946, 2239538, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 50228416, 29594943, 62030348, 10307368, 70970997, 20292574, 126292474, 51543890, + 67827181, 15848795, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 5548701, 17911007, 33137864, 32764443, 31146554, 17931096, 64023370, 7290289, + 6361313, 32861205, + ]), + xy2d: FieldElement2625::from_limbs([ + 63374742, 30320053, 4091667, 30955480, 44819449, 2212055, 52638826, 22391938, + 38484599, 7051029, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 50485560, 7033600, 57711425, 10740562, 72347547, 42328739, 7593987, 46950560, + 85560721, 41970063, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 40930651, 3776911, 39108529, 2508077, 19371703, 7626128, 4092943, 15778278, + 42044145, 24540103, + ]), + xy2d: FieldElement2625::from_limbs([ + 44128555, 8867576, 8645499, 22222278, 11497130, 4344907, 10788462, 23382703, + 3547104, 15368835, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 81786515, 51902785, 74560130, 22753403, 52379722, 41395524, 57994925, 6818020, + 57707296, 16352835, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 21622574, 18581624, 36511951, 1212467, 36930308, 7910192, 20622927, 2438677, + 52628762, 29068327, + ]), + xy2d: FieldElement2625::from_limbs([ + 6797431, 2854059, 4269865, 8037366, 32016522, 15223213, 34765784, 15297582, + 3559197, 26425254, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 107761639, 61759660, 79235166, 8794359, 48418924, 60111631, 87862210, 33613219, + 68436482, 40229362, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 52388944, 32880897, 37676257, 8253690, 32826330, 2707379, 25088512, 17182878, + 15053907, 11601568, + ]), + xy2d: FieldElement2625::from_limbs([ + 43894091, 25425955, 50962615, 28097648, 30129084, 13258436, 39364589, 8197601, + 58181660, 15003422, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 13470722, 47835674, 31012390, 30525035, 89789519, 50713267, 39648035, 13815677, + 94028755, 62582101, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 54478677, 14782829, 56712503, 7094748, 41775828, 29409658, 9084386, 30179063, + 64014926, 32519086, + ]), + xy2d: FieldElement2625::from_limbs([ + 6314429, 20018828, 12535891, 19610611, 10074031, 28087963, 50489447, 26314252, + 24553876, 32746308, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 105768482, 46629424, 103418946, 65789027, 85765355, 28316167, 56299027, 22780838, + 122676432, 32376204, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 5654403, 26425050, 39347935, 963424, 5032477, 19850195, 30011537, 11153401, + 63182039, 13343989, + ]), + xy2d: FieldElement2625::from_limbs([ + 1130444, 29814849, 40569426, 8144467, 24179188, 6267924, 63847147, 2912740, + 63870704, 29186744, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 49722534, 11073633, 52865263, 50829611, 33921405, 38614719, 32360242, 35465390, + 50107050, 45035301, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 2003571, 2472803, 46902183, 1716406, 58609069, 15922982, 43766122, 27456369, + 33468339, 29346282, + ]), + xy2d: FieldElement2625::from_limbs([ + 18834217, 8245144, 29896065, 3490830, 62967493, 7220277, 146130, 18459164, + 57533060, 30070422, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 77805507, 38474121, 73459597, 18553340, 107508318, 52705654, 33655873, 27331956, + 44498407, 13768350, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 23652128, 27647291, 43351590, 13262712, 65238054, 26296349, 11902126, 2949002, + 34445239, 25602117, + ]), + xy2d: FieldElement2625::from_limbs([ + 55906958, 19046111, 28501158, 28224561, 14495533, 14714956, 32929972, 2643566, + 17034893, 11645825, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 38181639, 29751709, 73650473, 17760526, 80753587, 17992258, 72670209, 41214427, + 87524152, 37630124, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 6498441, 12053607, 10375600, 14764370, 24795955, 16159258, 57849421, 16071837, + 31008329, 3792564, + ]), + xy2d: FieldElement2625::from_limbs([ + 47930485, 9176956, 54248931, 8732776, 58000258, 10333519, 96092, 29273884, + 13051277, 20121493, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 54190492, 49837594, 61282066, 10734597, 67926686, 36967416, 115462142, 30339271, + 37200685, 30036936, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 21193614, 19929501, 18841215, 29565554, 64002173, 11123558, 14111648, 6069945, + 30307604, 25935103, + ]), + xy2d: FieldElement2625::from_limbs([ + 58539773, 2098685, 38301131, 15844175, 41633654, 16934366, 15145895, 5543861, + 64050790, 6595361, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 34107945, 34731353, 51956038, 5614778, 79079051, 30288154, 47460410, 22186730, + 30689695, 19628976, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 25043248, 19224237, 46048097, 32289319, 29339134, 12397721, 37385860, 12978240, + 57951631, 31419653, + ]), + xy2d: FieldElement2625::from_limbs([ + 46038439, 28501736, 62566522, 12609283, 35236982, 30457796, 64113609, 14800343, + 6412849, 6276813, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 124528774, 39505727, 83050803, 41361190, 116071796, 37845759, 61633481, 38385016, + 71255100, 31629488, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 249426, 17196749, 35434953, 13884216, 11701636, 24553269, 51821986, 12900910, + 34844073, 16150118, + ]), + xy2d: FieldElement2625::from_limbs([ + 2520516, 14697628, 15319213, 22684490, 62866663, 29666431, 13872507, 7473319, + 12419515, 2958466, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 101517167, 22298305, 98222207, 59471046, 61547444, 50370568, 97111094, 42539051, + 14298448, 49873561, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 19427905, 12004555, 9971383, 28189868, 32306269, 23648270, 34176633, 10760437, + 53354280, 5634974, + ]), + xy2d: FieldElement2625::from_limbs([ + 30044319, 23677863, 60273406, 14563839, 9734978, 19808149, 30899064, 30835691, + 22828539, 23633348, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 25513026, 37111929, 37113703, 29589233, 77394412, 34745965, 95889446, 61766763, + 92876242, 37566563, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 42139852, 9176396, 16274786, 33467453, 52558621, 7190768, 1490604, 31312359, + 44767199, 18491072, + ]), + xy2d: FieldElement2625::from_limbs([ + 4272877, 21431483, 45594743, 13027605, 59232641, 24151956, 38390319, 12906718, + 45915869, 15503563, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 29874396, 35808736, 25494239, 37976524, 43036007, 37144111, 18198811, 35141252, + 53490316, 47742788, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 59518553, 28520621, 59946871, 29462027, 3630300, 29398589, 60425462, 24588735, + 53129947, 28399367, + ]), + xy2d: FieldElement2625::from_limbs([ + 18192774, 12787801, 32021061, 9158184, 48389348, 16385092, 11799402, 9492011, + 43154220, 15950102, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 68768204, 54638026, 33464925, 53430209, 66037964, 35360373, 22565155, 39168685, + 46605438, 51897954, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 57660336, 29715319, 64414626, 32753338, 16894121, 935644, 53848937, 22684138, + 10541713, 14174330, + ]), + xy2d: FieldElement2625::from_limbs([ + 22888141, 12700209, 40301697, 6435658, 56329485, 5524686, 56715961, 6520808, + 15754965, 9355803, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 79549820, 26746924, 54931884, 38547877, 49672847, 19708985, 52599424, 12757151, + 93328625, 39524327, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 33888606, 13911610, 18921581, 1162763, 46616901, 13799218, 29525142, 21929286, + 59295464, 503508, + ]), + xy2d: FieldElement2625::from_limbs([ + 57865531, 22043577, 17998312, 3038439, 52838371, 9832208, 43311531, 660991, + 25265267, 18977724, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 64010269, 23727746, 42277281, 48089313, 102316973, 34946803, 127880577, 38411468, + 114816699, 43712746, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56859315, 32558245, 41017090, 22610758, 13704990, 23215119, 2475037, 32344984, + 12799418, 11135856, + ]), + xy2d: FieldElement2625::from_limbs([ + 1867214, 27167702, 19772099, 16925005, 15366693, 25797692, 10829276, 15372827, + 26582557, 31642714, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 57265197, 20059797, 107314987, 30587501, 60553812, 25602102, 29690666, 37127097, + 103070929, 51772159, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56432653, 6329655, 42770975, 4187982, 30677076, 9335071, 60103332, 14755050, + 9451294, 574767, + ]), + xy2d: FieldElement2625::from_limbs([ + 52859018, 2867107, 56258365, 15719081, 5959372, 8703738, 29137781, 21575537, + 20249840, 31808689, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 74749335, 47235127, 9995910, 52200224, 92069015, 8964515, 33248715, 21201554, + 57573145, 31605506, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 56307055, 23891752, 3613811, 30787942, 49031222, 26667524, 26985478, 31973510, + 26785294, 29587427, + ]), + xy2d: FieldElement2625::from_limbs([ + 30891460, 5254655, 47414930, 12769216, 42912782, 11830405, 7411958, 1394027, + 18778535, 18209370, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 61227949, 26179350, 57501473, 13585864, 102855675, 40344975, 54134826, 59707765, + 74122694, 12256219, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 5975515, 16302413, 24341148, 28270615, 18786096, 22405501, 28243950, 28328004, + 53412289, 4381960, + ]), + xy2d: FieldElement2625::from_limbs([ + 9394648, 8758552, 26189703, 16642536, 35993528, 5117040, 5977877, 13955594, + 19244020, 24493735, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 111388362, 51822507, 30193028, 3993472, 110736308, 44014764, 107346699, 48464072, + 92830877, 56442511, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 7236795, 30433657, 63588571, 620817, 11118384, 24979014, 66780154, 19877679, + 16217590, 26311105, + ]), + xy2d: FieldElement2625::from_limbs([ + 42540794, 21657271, 16455973, 23630199, 3992015, 21894417, 44876052, 19291718, + 55429803, 30442389, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement2625::from_limbs([ + 69421833, 26972132, 58859271, 20240912, 119664007, 29643940, 93968457, 34515112, + 110902491, 44996669, + ]), + y_minus_x: FieldElement2625::from_limbs([ + 3428668, 27807272, 41139948, 24786894, 4167808, 21423270, 52199622, 8021269, + 53172251, 18070808, + ]), + xy2d: FieldElement2625::from_limbs([ + 30631113, 26363656, 21279866, 23275794, 18311406, 466071, 42527968, 7989982, + 29641567, 29446694, + ]), + }, + ]); diff --git a/curve25519-elligator2/src/backend/serial/u32/field.rs b/curve25519-elligator2/src/backend/serial/u32/field.rs new file mode 100644 index 00000000..ad2d47d2 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/u32/field.rs @@ -0,0 +1,624 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(32\\)-bit +//! limbs with \\(64\\)-bit products. +//! +//! This code was originally derived from Adam Langley's Golang ed25519 +//! implementation, and was then rewritten to use unsigned limbs instead +//! of signed limbs. + +use core::fmt::Debug; +use core::ops::Neg; +use core::ops::{Add, AddAssign}; +use core::ops::{Mul, MulAssign}; +use core::ops::{Sub, SubAssign}; + +use subtle::Choice; +use subtle::ConditionallySelectable; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +/// A `FieldElement2625` represents an element of the field +/// \\( \mathbb Z / (2\^{255} - 19)\\). +/// +/// In the 32-bit implementation, a `FieldElement` is represented in +/// radix \\(2\^{25.5}\\) as ten `u32`s. This means that a field +/// element \\(x\\) is represented as +/// $$ +/// x = \sum\_{i=0}\^9 x\_i 2\^{\lceil i \frac {51} 2 \rceil} +/// = x\_0 + x\_1 2\^{26} + x\_2 2\^{51} + x\_3 2\^{77} + \cdots + x\_9 2\^{230}; +/// $$ +/// the coefficients are alternately bounded by \\(2\^{25}\\) and +/// \\(2\^{26}\\). The limbs are allowed to grow between reductions up +/// to \\(2\^{25+b}\\) or \\(2\^{26+b}\\), where \\(b = 1.75\\). +/// +/// # Note +/// +/// The `curve25519_elligator2::field` module provides a type alias +/// `curve25519_elligator2::field::FieldElement` to either `FieldElement51` +/// or `FieldElement2625`. +/// +/// The backend-specific type `FieldElement2625` should not be used +/// outside of the `curve25519_elligator2::field` module. +#[derive(Copy, Clone)] +pub struct FieldElement2625(pub(crate) [u32; 10]); + +impl Debug for FieldElement2625 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "FieldElement2625({:?})", &self.0[..]) + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for FieldElement2625 { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl<'b> AddAssign<&'b FieldElement2625> for FieldElement2625 { + fn add_assign(&mut self, _rhs: &'b FieldElement2625) { + for i in 0..10 { + self.0[i] += _rhs.0[i]; + } + } +} + +impl<'a, 'b> Add<&'b FieldElement2625> for &'a FieldElement2625 { + type Output = FieldElement2625; + fn add(self, _rhs: &'b FieldElement2625) -> FieldElement2625 { + let mut output = *self; + output += _rhs; + output + } +} + +impl<'b> SubAssign<&'b FieldElement2625> for FieldElement2625 { + fn sub_assign(&mut self, _rhs: &'b FieldElement2625) { + // See comment in FieldElement51::Sub + // + // Compute a - b as ((a + 2^4 * p) - b) to avoid underflow. + let b = &_rhs.0; + self.0 = FieldElement2625::reduce([ + ((self.0[0] + (0x3ffffed << 4)) - b[0]) as u64, + ((self.0[1] + (0x1ffffff << 4)) - b[1]) as u64, + ((self.0[2] + (0x3ffffff << 4)) - b[2]) as u64, + ((self.0[3] + (0x1ffffff << 4)) - b[3]) as u64, + ((self.0[4] + (0x3ffffff << 4)) - b[4]) as u64, + ((self.0[5] + (0x1ffffff << 4)) - b[5]) as u64, + ((self.0[6] + (0x3ffffff << 4)) - b[6]) as u64, + ((self.0[7] + (0x1ffffff << 4)) - b[7]) as u64, + ((self.0[8] + (0x3ffffff << 4)) - b[8]) as u64, + ((self.0[9] + (0x1ffffff << 4)) - b[9]) as u64, + ]) + .0; + } +} + +impl<'a, 'b> Sub<&'b FieldElement2625> for &'a FieldElement2625 { + type Output = FieldElement2625; + fn sub(self, _rhs: &'b FieldElement2625) -> FieldElement2625 { + let mut output = *self; + output -= _rhs; + output + } +} + +impl<'b> MulAssign<&'b FieldElement2625> for FieldElement2625 { + fn mul_assign(&mut self, _rhs: &'b FieldElement2625) { + let result = (self as &FieldElement2625) * _rhs; + self.0 = result.0; + } +} + +impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 { + type Output = FieldElement2625; + + #[rustfmt::skip] // keep alignment of z* calculations + fn mul(self, _rhs: &'b FieldElement2625) -> FieldElement2625 { + /// Helper function to multiply two 32-bit integers with 64 bits + /// of output. + #[inline(always)] + fn m(x: u32, y: u32) -> u64 { + (x as u64) * (y as u64) + } + + // Alias self, _rhs for more readable formulas + let x: &[u32; 10] = &self.0; + let y: &[u32; 10] = &_rhs.0; + + // We assume that the input limbs x[i], y[i] are bounded by: + // + // x[i], y[i] < 2^(26 + b) if i even + // x[i], y[i] < 2^(25 + b) if i odd + // + // where b is a (real) parameter representing the excess bits of + // the limbs. We track the bitsizes of all variables through + // the computation and solve at the end for the allowable + // headroom bitsize b (which determines how many additions we + // can perform between reductions or multiplications). + + let y1_19 = 19 * y[1]; // This fits in a u32 + let y2_19 = 19 * y[2]; // iff 26 + b + lg(19) < 32 + let y3_19 = 19 * y[3]; // if b < 32 - 26 - 4.248 = 1.752 + let y4_19 = 19 * y[4]; + let y5_19 = 19 * y[5]; // below, b<2.5: this is a bottleneck, + let y6_19 = 19 * y[6]; // could be avoided by promoting to + let y7_19 = 19 * y[7]; // u64 here instead of in m() + let y8_19 = 19 * y[8]; + let y9_19 = 19 * y[9]; + + // What happens when we multiply x[i] with y[j] and place the + // result into the (i+j)-th limb? + // + // x[i] represents the value x[i]*2^ceil(i*51/2) + // y[j] represents the value y[j]*2^ceil(j*51/2) + // z[i+j] represents the value z[i+j]*2^ceil((i+j)*51/2) + // x[i]*y[j] represents the value x[i]*y[i]*2^(ceil(i*51/2)+ceil(j*51/2)) + // + // Since the radix is already accounted for, the result placed + // into the (i+j)-th limb should be + // + // x[i]*y[i]*2^(ceil(i*51/2)+ceil(j*51/2) - ceil((i+j)*51/2)). + // + // The value of ceil(i*51/2)+ceil(j*51/2) - ceil((i+j)*51/2) is + // 1 when both i and j are odd, and 0 otherwise. So we add + // + // x[i]*y[j] if either i or j is even + // 2*x[i]*y[j] if i and j are both odd + // + // by using precomputed multiples of x[i] for odd i: + + let x1_2 = 2 * x[1]; // This fits in a u32 iff 25 + b + 1 < 32 + let x3_2 = 2 * x[3]; // iff b < 6 + let x5_2 = 2 * x[5]; + let x7_2 = 2 * x[7]; + let x9_2 = 2 * x[9]; + + let z0 = m(x[0], y[0]) + m(x1_2, y9_19) + m(x[2], y8_19) + m(x3_2, y7_19) + m(x[4], y6_19) + m(x5_2, y5_19) + m(x[6], y4_19) + m(x7_2, y3_19) + m(x[8], y2_19) + m(x9_2, y1_19); + let z1 = m(x[0], y[1]) + m(x[1], y[0]) + m(x[2], y9_19) + m(x[3], y8_19) + m(x[4], y7_19) + m(x[5], y6_19) + m(x[6], y5_19) + m(x[7], y4_19) + m(x[8], y3_19) + m(x[9], y2_19); + let z2 = m(x[0], y[2]) + m(x1_2, y[1]) + m(x[2], y[0]) + m(x3_2, y9_19) + m(x[4], y8_19) + m(x5_2, y7_19) + m(x[6], y6_19) + m(x7_2, y5_19) + m(x[8], y4_19) + m(x9_2, y3_19); + let z3 = m(x[0], y[3]) + m(x[1], y[2]) + m(x[2], y[1]) + m(x[3], y[0]) + m(x[4], y9_19) + m(x[5], y8_19) + m(x[6], y7_19) + m(x[7], y6_19) + m(x[8], y5_19) + m(x[9], y4_19); + let z4 = m(x[0], y[4]) + m(x1_2, y[3]) + m(x[2], y[2]) + m(x3_2, y[1]) + m(x[4], y[0]) + m(x5_2, y9_19) + m(x[6], y8_19) + m(x7_2, y7_19) + m(x[8], y6_19) + m(x9_2, y5_19); + let z5 = m(x[0], y[5]) + m(x[1], y[4]) + m(x[2], y[3]) + m(x[3], y[2]) + m(x[4], y[1]) + m(x[5], y[0]) + m(x[6], y9_19) + m(x[7], y8_19) + m(x[8], y7_19) + m(x[9], y6_19); + let z6 = m(x[0], y[6]) + m(x1_2, y[5]) + m(x[2], y[4]) + m(x3_2, y[3]) + m(x[4], y[2]) + m(x5_2, y[1]) + m(x[6], y[0]) + m(x7_2, y9_19) + m(x[8], y8_19) + m(x9_2, y7_19); + let z7 = m(x[0], y[7]) + m(x[1], y[6]) + m(x[2], y[5]) + m(x[3], y[4]) + m(x[4], y[3]) + m(x[5], y[2]) + m(x[6], y[1]) + m(x[7], y[0]) + m(x[8], y9_19) + m(x[9], y8_19); + let z8 = m(x[0], y[8]) + m(x1_2, y[7]) + m(x[2], y[6]) + m(x3_2, y[5]) + m(x[4], y[4]) + m(x5_2, y[3]) + m(x[6], y[2]) + m(x7_2, y[1]) + m(x[8], y[0]) + m(x9_2, y9_19); + let z9 = m(x[0], y[9]) + m(x[1], y[8]) + m(x[2], y[7]) + m(x[3], y[6]) + m(x[4], y[5]) + m(x[5], y[4]) + m(x[6], y[3]) + m(x[7], y[2]) + m(x[8], y[1]) + m(x[9], y[0]); + + // How big is the contribution to z[i+j] from x[i], y[j]? + // + // Using the bounds above, we get: + // + // i even, j even: x[i]*y[j] < 2^(26+b)*2^(26+b) = 2*2^(51+2*b) + // i odd, j even: x[i]*y[j] < 2^(25+b)*2^(26+b) = 1*2^(51+2*b) + // i even, j odd: x[i]*y[j] < 2^(26+b)*2^(25+b) = 1*2^(51+2*b) + // i odd, j odd: 2*x[i]*y[j] < 2*2^(25+b)*2^(25+b) = 1*2^(51+2*b) + // + // We perform inline reduction mod p by replacing 2^255 by 19 + // (since 2^255 - 19 = 0 mod p). This adds a factor of 19, so + // we get the bounds (z0 is the biggest one, but calculated for + // posterity here in case finer estimation is needed later): + // + // z0 < ( 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 249*2^(51 + 2*b) + // z1 < ( 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 154*2^(51 + 2*b) + // z2 < ( 2 + 1 + 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 195*2^(51 + 2*b) + // z3 < ( 1 + 1 + 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 118*2^(51 + 2*b) + // z4 < ( 2 + 1 + 2 + 1 + 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 141*2^(51 + 2*b) + // z5 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 82*2^(51 + 2*b) + // z6 < ( 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 87*2^(51 + 2*b) + // z7 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1*19 + 1*19 )*2^(51 + 2b) = 46*2^(51 + 2*b) + // z6 < ( 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1*19 )*2^(51 + 2b) = 33*2^(51 + 2*b) + // z7 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 )*2^(51 + 2b) = 10*2^(51 + 2*b) + // + // So z[0] fits into a u64 if 51 + 2*b + lg(249) < 64 + // if b < 2.5. + FieldElement2625::reduce([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9]) + } +} + +impl<'a> Neg for &'a FieldElement2625 { + type Output = FieldElement2625; + fn neg(self) -> FieldElement2625 { + let mut output = *self; + output.negate(); + output + } +} + +impl ConditionallySelectable for FieldElement2625 { + fn conditional_select( + a: &FieldElement2625, + b: &FieldElement2625, + choice: Choice, + ) -> FieldElement2625 { + FieldElement2625([ + u32::conditional_select(&a.0[0], &b.0[0], choice), + u32::conditional_select(&a.0[1], &b.0[1], choice), + u32::conditional_select(&a.0[2], &b.0[2], choice), + u32::conditional_select(&a.0[3], &b.0[3], choice), + u32::conditional_select(&a.0[4], &b.0[4], choice), + u32::conditional_select(&a.0[5], &b.0[5], choice), + u32::conditional_select(&a.0[6], &b.0[6], choice), + u32::conditional_select(&a.0[7], &b.0[7], choice), + u32::conditional_select(&a.0[8], &b.0[8], choice), + u32::conditional_select(&a.0[9], &b.0[9], choice), + ]) + } + + fn conditional_assign(&mut self, other: &FieldElement2625, choice: Choice) { + self.0[0].conditional_assign(&other.0[0], choice); + self.0[1].conditional_assign(&other.0[1], choice); + self.0[2].conditional_assign(&other.0[2], choice); + self.0[3].conditional_assign(&other.0[3], choice); + self.0[4].conditional_assign(&other.0[4], choice); + self.0[5].conditional_assign(&other.0[5], choice); + self.0[6].conditional_assign(&other.0[6], choice); + self.0[7].conditional_assign(&other.0[7], choice); + self.0[8].conditional_assign(&other.0[8], choice); + self.0[9].conditional_assign(&other.0[9], choice); + } + + fn conditional_swap(a: &mut FieldElement2625, b: &mut FieldElement2625, choice: Choice) { + u32::conditional_swap(&mut a.0[0], &mut b.0[0], choice); + u32::conditional_swap(&mut a.0[1], &mut b.0[1], choice); + u32::conditional_swap(&mut a.0[2], &mut b.0[2], choice); + u32::conditional_swap(&mut a.0[3], &mut b.0[3], choice); + u32::conditional_swap(&mut a.0[4], &mut b.0[4], choice); + u32::conditional_swap(&mut a.0[5], &mut b.0[5], choice); + u32::conditional_swap(&mut a.0[6], &mut b.0[6], choice); + u32::conditional_swap(&mut a.0[7], &mut b.0[7], choice); + u32::conditional_swap(&mut a.0[8], &mut b.0[8], choice); + u32::conditional_swap(&mut a.0[9], &mut b.0[9], choice); + } +} + +impl FieldElement2625 { + pub(crate) const fn from_limbs(limbs: [u32; 10]) -> FieldElement2625 { + FieldElement2625(limbs) + } + + /// The scalar \\( 0 \\). + pub const ZERO: FieldElement2625 = FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// The scalar \\( 1 \\). + pub const ONE: FieldElement2625 = FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// The scalar \\( -1 \\). + pub const MINUS_ONE: FieldElement2625 = FieldElement2625::from_limbs([ + 0x3ffffec, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, + 0x3ffffff, 0x1ffffff, + ]); + + /// Invert the sign of this field element + pub fn negate(&mut self) { + // Compute -b as ((2^4 * p) - b) to avoid underflow. + let neg = FieldElement2625::reduce([ + ((0x3ffffed << 4) - self.0[0]) as u64, + ((0x1ffffff << 4) - self.0[1]) as u64, + ((0x3ffffff << 4) - self.0[2]) as u64, + ((0x1ffffff << 4) - self.0[3]) as u64, + ((0x3ffffff << 4) - self.0[4]) as u64, + ((0x1ffffff << 4) - self.0[5]) as u64, + ((0x3ffffff << 4) - self.0[6]) as u64, + ((0x1ffffff << 4) - self.0[7]) as u64, + ((0x3ffffff << 4) - self.0[8]) as u64, + ((0x1ffffff << 4) - self.0[9]) as u64, + ]); + self.0 = neg.0; + } + + /// Given `k > 0`, return `self^(2^k)`. + pub fn pow2k(&self, k: u32) -> FieldElement2625 { + debug_assert!(k > 0); + let mut z = self.square(); + for _ in 1..k { + z = z.square(); + } + z + } + + /// Given unreduced coefficients `z[0], ..., z[9]` of any size, + /// carry and reduce them mod p to obtain a `FieldElement2625` + /// whose coefficients have excess `b < 0.007`. + /// + /// In other words, each coefficient of the result is bounded by + /// either `2^(25 + 0.007)` or `2^(26 + 0.007)`, as appropriate. + #[rustfmt::skip] // keep alignment of carry chain + fn reduce(mut z: [u64; 10]) -> FieldElement2625 { + + const LOW_25_BITS: u64 = (1 << 25) - 1; + const LOW_26_BITS: u64 = (1 << 26) - 1; + + /// Carry the value from limb i = 0..8 to limb i+1 + #[inline(always)] + fn carry(z: &mut [u64; 10], i: usize) { + debug_assert!(i < 9); + if i % 2 == 0 { + // Even limbs have 26 bits + z[i + 1] += z[i] >> 26; + z[i] &= LOW_26_BITS; + } else { + // Odd limbs have 25 bits + z[i + 1] += z[i] >> 25; + z[i] &= LOW_25_BITS; + } + } + + // Perform two halves of the carry chain in parallel. + carry(&mut z, 0); carry(&mut z, 4); + carry(&mut z, 1); carry(&mut z, 5); + carry(&mut z, 2); carry(&mut z, 6); + carry(&mut z, 3); carry(&mut z, 7); + // Since z[3] < 2^64, c < 2^(64-25) = 2^39, + // so z[4] < 2^26 + 2^39 < 2^39.0002 + carry(&mut z, 4); carry(&mut z, 8); + // Now z[4] < 2^26 + // and z[5] < 2^25 + 2^13.0002 < 2^25.0004 (good enough) + + // Last carry has a multiplication by 19: + z[0] += 19 * (z[9] >> 25); + z[9] &= LOW_25_BITS; + + // Since z[9] < 2^64, c < 2^(64-25) = 2^39, + // so z[0] + 19*c < 2^26 + 2^43.248 < 2^43.249. + carry(&mut z, 0); + // Now z[1] < 2^25 - 2^(43.249 - 26) + // < 2^25.007 (good enough) + // and we're done. + + FieldElement2625([ + z[0] as u32, + z[1] as u32, + z[2] as u32, + z[3] as u32, + z[4] as u32, + z[5] as u32, + z[6] as u32, + z[7] as u32, + z[8] as u32, + z[9] as u32, + ]) + } + + /// Load a `FieldElement51` from the low 255 bits of a 256-bit + /// input. + /// + /// # Warning + /// + /// This function does not check that the input used the canonical + /// representative. It masks the high bit, but it will happily + /// decode 2^255 - 18 to 1. Applications that require a canonical + /// encoding of every field element should decode, re-encode to + /// the canonical encoding, and check that the input was + /// canonical. + #[rustfmt::skip] // keep alignment of h[*] values + pub fn from_bytes(data: &[u8; 32]) -> FieldElement2625 { + #[inline] + fn load3(b: &[u8]) -> u64 { + (b[0] as u64) | ((b[1] as u64) << 8) | ((b[2] as u64) << 16) + } + + #[inline] + fn load4(b: &[u8]) -> u64 { + (b[0] as u64) | ((b[1] as u64) << 8) | ((b[2] as u64) << 16) | ((b[3] as u64) << 24) + } + + let mut h = [0u64;10]; + const LOW_23_BITS: u64 = (1 << 23) - 1; + h[0] = load4(&data[ 0..]); + h[1] = load3(&data[ 4..]) << 6; + h[2] = load3(&data[ 7..]) << 5; + h[3] = load3(&data[10..]) << 3; + h[4] = load3(&data[13..]) << 2; + h[5] = load4(&data[16..]); + h[6] = load3(&data[20..]) << 7; + h[7] = load3(&data[23..]) << 5; + h[8] = load3(&data[26..]) << 4; + h[9] = (load3(&data[29..]) & LOW_23_BITS) << 2; + + FieldElement2625::reduce(h) + } + + /// Serialize this `FieldElement51` to a 32-byte array. The + /// encoding is canonical. + #[allow(clippy::identity_op)] + pub fn as_bytes(&self) -> [u8; 32] { + let inp = &self.0; + // Reduce the value represented by `in` to the range [0,2*p) + let mut h: [u32; 10] = FieldElement2625::reduce([ + // XXX this cast is annoying + inp[0] as u64, + inp[1] as u64, + inp[2] as u64, + inp[3] as u64, + inp[4] as u64, + inp[5] as u64, + inp[6] as u64, + inp[7] as u64, + inp[8] as u64, + inp[9] as u64, + ]) + .0; + + // Let h be the value to encode. + // + // Write h = pq + r with 0 <= r < p. We want to compute r = h mod p. + // + // Since h < 2*p, q = 0 or 1, with q = 0 when h < p and q = 1 when h >= p. + // + // Notice that h >= p <==> h + 19 >= p + 19 <==> h + 19 >= 2^255. + // Therefore q can be computed as the carry bit of h + 19. + + let mut q: u32 = (h[0] + 19) >> 26; + q = (h[1] + q) >> 25; + q = (h[2] + q) >> 26; + q = (h[3] + q) >> 25; + q = (h[4] + q) >> 26; + q = (h[5] + q) >> 25; + q = (h[6] + q) >> 26; + q = (h[7] + q) >> 25; + q = (h[8] + q) >> 26; + q = (h[9] + q) >> 25; + + debug_assert!(q == 0 || q == 1); + + // Now we can compute r as r = h - pq = r - (2^255-19)q = r + 19q - 2^255q + + const LOW_25_BITS: u32 = (1 << 25) - 1; + const LOW_26_BITS: u32 = (1 << 26) - 1; + + h[0] += 19 * q; + + // Now carry the result to compute r + 19q... + h[1] += h[0] >> 26; + h[0] &= LOW_26_BITS; + h[2] += h[1] >> 25; + h[1] &= LOW_25_BITS; + h[3] += h[2] >> 26; + h[2] &= LOW_26_BITS; + h[4] += h[3] >> 25; + h[3] &= LOW_25_BITS; + h[5] += h[4] >> 26; + h[4] &= LOW_26_BITS; + h[6] += h[5] >> 25; + h[5] &= LOW_25_BITS; + h[7] += h[6] >> 26; + h[6] &= LOW_26_BITS; + h[8] += h[7] >> 25; + h[7] &= LOW_25_BITS; + h[9] += h[8] >> 26; + h[8] &= LOW_26_BITS; + + // ... but instead of carrying the value + // (h[9] >> 25) = q*2^255 into another limb, + // discard it, subtracting the value from h. + debug_assert!((h[9] >> 25) == 0 || (h[9] >> 25) == 1); + h[9] &= LOW_25_BITS; + + let mut s = [0u8; 32]; + s[0] = (h[0] >> 0) as u8; + s[1] = (h[0] >> 8) as u8; + s[2] = (h[0] >> 16) as u8; + s[3] = ((h[0] >> 24) | (h[1] << 2)) as u8; + s[4] = (h[1] >> 6) as u8; + s[5] = (h[1] >> 14) as u8; + s[6] = ((h[1] >> 22) | (h[2] << 3)) as u8; + s[7] = (h[2] >> 5) as u8; + s[8] = (h[2] >> 13) as u8; + s[9] = ((h[2] >> 21) | (h[3] << 5)) as u8; + s[10] = (h[3] >> 3) as u8; + s[11] = (h[3] >> 11) as u8; + s[12] = ((h[3] >> 19) | (h[4] << 6)) as u8; + s[13] = (h[4] >> 2) as u8; + s[14] = (h[4] >> 10) as u8; + s[15] = (h[4] >> 18) as u8; + s[16] = (h[5] >> 0) as u8; + s[17] = (h[5] >> 8) as u8; + s[18] = (h[5] >> 16) as u8; + s[19] = ((h[5] >> 24) | (h[6] << 1)) as u8; + s[20] = (h[6] >> 7) as u8; + s[21] = (h[6] >> 15) as u8; + s[22] = ((h[6] >> 23) | (h[7] << 3)) as u8; + s[23] = (h[7] >> 5) as u8; + s[24] = (h[7] >> 13) as u8; + s[25] = ((h[7] >> 21) | (h[8] << 4)) as u8; + s[26] = (h[8] >> 4) as u8; + s[27] = (h[8] >> 12) as u8; + s[28] = ((h[8] >> 20) | (h[9] << 6)) as u8; + s[29] = (h[9] >> 2) as u8; + s[30] = (h[9] >> 10) as u8; + s[31] = (h[9] >> 18) as u8; + + // Check that high bit is cleared + debug_assert!((s[31] & 0b1000_0000u8) == 0u8); + + s + } + + #[rustfmt::skip] // keep alignment of z* calculations + fn square_inner(&self) -> [u64; 10] { + // Optimized version of multiplication for the case of squaring. + // Pre- and post- conditions identical to multiplication function. + let x = &self.0; + let x0_2 = 2 * x[0]; + let x1_2 = 2 * x[1]; + let x2_2 = 2 * x[2]; + let x3_2 = 2 * x[3]; + let x4_2 = 2 * x[4]; + let x5_2 = 2 * x[5]; + let x6_2 = 2 * x[6]; + let x7_2 = 2 * x[7]; + let x5_19 = 19 * x[5]; + let x6_19 = 19 * x[6]; + let x7_19 = 19 * x[7]; + let x8_19 = 19 * x[8]; + let x9_19 = 19 * x[9]; + + /// Helper function to multiply two 32-bit integers with 64 bits + /// of output. + #[inline(always)] + fn m(x: u32, y: u32) -> u64 { + (x as u64) * (y as u64) + } + + // This block is rearranged so that instead of doing a 32-bit multiplication by 38, we do a + // 64-bit multiplication by 2 on the results. This is because lg(38) is too big: we would + // have less than 1 bit of headroom left, which is too little. + let mut z = [0u64; 10]; + z[0] = m(x[0], x[0]) + m(x2_2, x8_19) + m(x4_2, x6_19) + (m(x1_2, x9_19) + m(x3_2, x7_19) + m(x[5], x5_19)) * 2; + z[1] = m(x0_2, x[1]) + m(x3_2, x8_19) + m(x5_2, x6_19) + (m(x[2], x9_19) + m(x[4], x7_19) ) * 2; + z[2] = m(x0_2, x[2]) + m(x1_2, x[1]) + m(x4_2, x8_19) + m(x[6], x6_19) + (m(x3_2, x9_19) + m(x5_2, x7_19)) * 2; + z[3] = m(x0_2, x[3]) + m(x1_2, x[2]) + m(x5_2, x8_19) + (m(x[4], x9_19) + m(x[6], x7_19) ) * 2; + z[4] = m(x0_2, x[4]) + m(x1_2, x3_2) + m(x[2], x[2]) + m(x6_2, x8_19) + (m(x5_2, x9_19) + m(x[7], x7_19)) * 2; + z[5] = m(x0_2, x[5]) + m(x1_2, x[4]) + m(x2_2, x[3]) + m(x7_2, x8_19) + m(x[6], x9_19) * 2; + z[6] = m(x0_2, x[6]) + m(x1_2, x5_2) + m(x2_2, x[4]) + m(x3_2, x[3]) + m(x[8], x8_19) + m(x7_2, x9_19) * 2; + z[7] = m(x0_2, x[7]) + m(x1_2, x[6]) + m(x2_2, x[5]) + m(x3_2, x[4]) + m(x[8], x9_19) * 2; + z[8] = m(x0_2, x[8]) + m(x1_2, x7_2) + m(x2_2, x[6]) + m(x3_2, x5_2) + m(x[4], x[4]) + m(x[9], x9_19) * 2; + z[9] = m(x0_2, x[9]) + m(x1_2, x[8]) + m(x2_2, x[7]) + m(x3_2, x[6]) + m(x4_2, x[5]) ; + + z + } + + /// Compute `self^2`. + pub fn square(&self) -> FieldElement2625 { + FieldElement2625::reduce(self.square_inner()) + } + + /// Compute `2*self^2`. + pub fn square2(&self) -> FieldElement2625 { + let mut coeffs = self.square_inner(); + for coeff in &mut coeffs { + *coeff += *coeff; + } + FieldElement2625::reduce(coeffs) + } + + /// Returns 1 if self is greater than the other and 0 otherwise + // implementation based on C libgmp -> mpn_sub_n + pub(crate) fn gt(&self, other: &Self) -> Choice { + let mut _ul = 0_u32; + let mut _vl = 0_u32; + let mut _rl = 0_u32; + + let mut cy = 0_u32; + for i in 0..10 { + _ul = self.0[i]; + _vl = other.0[i]; + + let (_sl, _cy1) = _ul.overflowing_sub(_vl); + let (_rl, _cy2) = _sl.overflowing_sub(cy); + cy = _cy1 as u32 | _cy2 as u32; + } + + Choice::from((cy != 0_u32) as u8) + } +} diff --git a/curve25519-elligator2/src/backend/serial/u32/mod.rs b/curve25519-elligator2/src/backend/serial/u32/mod.rs new file mode 100644 index 00000000..b374f72f --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/u32/mod.rs @@ -0,0 +1,22 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! The `u32` backend uses `u32`s and a `(u32, u32) -> u64` multiplier. +//! +//! This code is intended to be portable, but it requires that +//! multiplication of two \\(32\\)-bit values to a \\(64\\)-bit result +//! is constant-time on the target platform. + +pub mod field; + +pub mod scalar; + +pub mod constants; diff --git a/curve25519-elligator2/src/backend/serial/u32/scalar.rs b/curve25519-elligator2/src/backend/serial/u32/scalar.rs new file mode 100644 index 00000000..2d135d1d --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/u32/scalar.rs @@ -0,0 +1,538 @@ +//! Arithmetic mod 2^252 + 27742317777372353535851937790883648493 +//! with 9 29-bit unsigned limbs +//! +//! To see that this is safe for intermediate results, note that +//! the largest limb in a 9 by 9 product of 29-bit limbs will be +//! (0x1fffffff^2) * 9 = 0x23fffffdc0000009 (62 bits). +//! +//! For a one level Karatsuba decomposition, the specific ranges +//! depend on how the limbs are combined, but will stay within +//! -0x1ffffffe00000008 (62 bits with sign bit) to +//! 0x43fffffbc0000011 (63 bits), which is still safe. + +use core::fmt::Debug; +use core::ops::{Index, IndexMut}; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +use crate::constants; + +/// The `Scalar29` struct represents an element in \\(\mathbb{Z} / \ell\mathbb{Z}\\) as 9 29-bit +/// limbs +#[derive(Copy, Clone)] +pub struct Scalar29(pub [u32; 9]); + +impl Debug for Scalar29 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Scalar29: {:?}", &self.0[..]) + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for Scalar29 { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl Index for Scalar29 { + type Output = u32; + fn index(&self, _index: usize) -> &u32 { + &(self.0[_index]) + } +} + +impl IndexMut for Scalar29 { + fn index_mut(&mut self, _index: usize) -> &mut u32 { + &mut (self.0[_index]) + } +} + +/// u32 * u32 = u64 multiply helper +#[inline(always)] +fn m(x: u32, y: u32) -> u64 { + (x as u64) * (y as u64) +} + +impl Scalar29 { + /// The scalar \\( 0 \\). + pub const ZERO: Scalar29 = Scalar29([0, 0, 0, 0, 0, 0, 0, 0, 0]); + + /// Unpack a 32 byte / 256 bit scalar into 9 29-bit limbs. + #[rustfmt::skip] // keep alignment of s[*] calculations + pub fn from_bytes(bytes: &[u8; 32]) -> Scalar29 { + let mut words = [0u32; 8]; + for i in 0..8 { + for j in 0..4 { + words[i] |= (bytes[(i * 4) + j] as u32) << (j * 8); + } + } + + let mask = (1u32 << 29) - 1; + let top_mask = (1u32 << 24) - 1; + let mut s = Scalar29::ZERO; + + s[0] = words[0] & mask; + s[1] = ((words[0] >> 29) | (words[1] << 3)) & mask; + s[2] = ((words[1] >> 26) | (words[2] << 6)) & mask; + s[3] = ((words[2] >> 23) | (words[3] << 9)) & mask; + s[4] = ((words[3] >> 20) | (words[4] << 12)) & mask; + s[5] = ((words[4] >> 17) | (words[5] << 15)) & mask; + s[6] = ((words[5] >> 14) | (words[6] << 18)) & mask; + s[7] = ((words[6] >> 11) | (words[7] << 21)) & mask; + s[8] = (words[7] >> 8) & top_mask; + + s + } + + /// Reduce a 64 byte / 512 bit scalar mod l. + #[rustfmt::skip] // keep alignment of lo[*] calculations + pub fn from_bytes_wide(bytes: &[u8; 64]) -> Scalar29 { + let mut words = [0u32; 16]; + for i in 0..16 { + for j in 0..4 { + words[i] |= (bytes[(i * 4) + j] as u32) << (j * 8); + } + } + + let mask = (1u32 << 29) - 1; + let mut lo = Scalar29::ZERO; + let mut hi = Scalar29::ZERO; + + lo[0] = words[ 0] & mask; + lo[1] = ((words[ 0] >> 29) | (words[ 1] << 3)) & mask; + lo[2] = ((words[ 1] >> 26) | (words[ 2] << 6)) & mask; + lo[3] = ((words[ 2] >> 23) | (words[ 3] << 9)) & mask; + lo[4] = ((words[ 3] >> 20) | (words[ 4] << 12)) & mask; + lo[5] = ((words[ 4] >> 17) | (words[ 5] << 15)) & mask; + lo[6] = ((words[ 5] >> 14) | (words[ 6] << 18)) & mask; + lo[7] = ((words[ 6] >> 11) | (words[ 7] << 21)) & mask; + lo[8] = ((words[ 7] >> 8) | (words[ 8] << 24)) & mask; + hi[0] = ((words[ 8] >> 5) | (words[ 9] << 27)) & mask; + hi[1] = (words[ 9] >> 2) & mask; + hi[2] = ((words[ 9] >> 31) | (words[10] << 1)) & mask; + hi[3] = ((words[10] >> 28) | (words[11] << 4)) & mask; + hi[4] = ((words[11] >> 25) | (words[12] << 7)) & mask; + hi[5] = ((words[12] >> 22) | (words[13] << 10)) & mask; + hi[6] = ((words[13] >> 19) | (words[14] << 13)) & mask; + hi[7] = ((words[14] >> 16) | (words[15] << 16)) & mask; + hi[8] = words[15] >> 13 ; + + lo = Scalar29::montgomery_mul(&lo, &constants::R); // (lo * R) / R = lo + hi = Scalar29::montgomery_mul(&hi, &constants::RR); // (hi * R^2) / R = hi * R + + Scalar29::add(&hi, &lo) // (hi * R) + lo + } + + /// Pack the limbs of this `Scalar29` into 32 bytes. + #[rustfmt::skip] // keep alignment of s[*] calculations + #[allow(clippy::identity_op)] + pub fn as_bytes(&self) -> [u8; 32] { + let mut s = [0u8; 32]; + + s[ 0] = (self.0[0] >> 0) as u8; + s[ 1] = (self.0[0] >> 8) as u8; + s[ 2] = (self.0[0] >> 16) as u8; + s[ 3] = ((self.0[0] >> 24) | (self.0[1] << 5)) as u8; + s[ 4] = (self.0[1] >> 3) as u8; + s[ 5] = (self.0[1] >> 11) as u8; + s[ 6] = (self.0[1] >> 19) as u8; + s[ 7] = ((self.0[1] >> 27) | (self.0[2] << 2)) as u8; + s[ 8] = (self.0[2] >> 6) as u8; + s[ 9] = (self.0[2] >> 14) as u8; + s[10] = ((self.0[2] >> 22) | (self.0[3] << 7)) as u8; + s[11] = (self.0[3] >> 1) as u8; + s[12] = (self.0[3] >> 9) as u8; + s[13] = (self.0[3] >> 17) as u8; + s[14] = ((self.0[3] >> 25) | (self.0[4] << 4)) as u8; + s[15] = (self.0[4] >> 4) as u8; + s[16] = (self.0[4] >> 12) as u8; + s[17] = (self.0[4] >> 20) as u8; + s[18] = ((self.0[4] >> 28) | (self.0[5] << 1)) as u8; + s[19] = (self.0[5] >> 7) as u8; + s[20] = (self.0[5] >> 15) as u8; + s[21] = ((self.0[5] >> 23) | (self.0[6] << 6)) as u8; + s[22] = (self.0[6] >> 2) as u8; + s[23] = (self.0[6] >> 10) as u8; + s[24] = (self.0[6] >> 18) as u8; + s[25] = ((self.0[6] >> 26) | (self.0[7] << 3)) as u8; + s[26] = (self.0[7] >> 5) as u8; + s[27] = (self.0[7] >> 13) as u8; + s[28] = (self.0[7] >> 21) as u8; + s[29] = (self.0[8] >> 0) as u8; + s[30] = (self.0[8] >> 8) as u8; + s[31] = (self.0[8] >> 16) as u8; + + s + } + + /// Compute `a + b` (mod l). + pub fn add(a: &Scalar29, b: &Scalar29) -> Scalar29 { + let mut sum = Scalar29::ZERO; + let mask = (1u32 << 29) - 1; + + // a + b + let mut carry: u32 = 0; + for i in 0..9 { + carry = a[i] + b[i] + (carry >> 29); + sum[i] = carry & mask; + } + + // subtract l if the sum is >= l + Scalar29::sub(&sum, &constants::L) + } + + /// Compute `a - b` (mod l). + pub fn sub(a: &Scalar29, b: &Scalar29) -> Scalar29 { + let mut difference = Scalar29::ZERO; + let mask = (1u32 << 29) - 1; + + // a - b + let mut borrow: u32 = 0; + for i in 0..9 { + borrow = a[i].wrapping_sub(b[i] + (borrow >> 31)); + difference[i] = borrow & mask; + } + + // conditionally add l if the difference is negative + let underflow_mask = ((borrow >> 31) ^ 1).wrapping_sub(1); + let mut carry: u32 = 0; + for i in 0..9 { + carry = (carry >> 29) + difference[i] + (constants::L[i] & underflow_mask); + difference[i] = carry & mask; + } + + difference + } + + /// Compute `a * b`. + /// + /// This is implemented with a one-level refined Karatsuba decomposition + #[inline(always)] + #[rustfmt::skip] // keep alignment of z[*] calculations + pub (crate) fn mul_internal(a: &Scalar29, b: &Scalar29) -> [u64; 17] { + let mut z = [0u64; 17]; + + z[0] = m(a[0], b[0]); // c00 + z[1] = m(a[0], b[1]) + m(a[1], b[0]); // c01 + z[2] = m(a[0], b[2]) + m(a[1], b[1]) + m(a[2], b[0]); // c02 + z[3] = m(a[0], b[3]) + m(a[1], b[2]) + m(a[2], b[1]) + m(a[3], b[0]); // c03 + z[4] = m(a[0], b[4]) + m(a[1], b[3]) + m(a[2], b[2]) + m(a[3], b[1]) + m(a[4], b[0]); // c04 + z[5] = m(a[1], b[4]) + m(a[2], b[3]) + m(a[3], b[2]) + m(a[4], b[1]); // c05 + z[6] = m(a[2], b[4]) + m(a[3], b[3]) + m(a[4], b[2]); // c06 + z[7] = m(a[3], b[4]) + m(a[4], b[3]); // c07 + z[8] = (m(a[4], b[4])).wrapping_sub(z[3]); // c08 - c03 + + z[10] = z[5].wrapping_sub(m(a[5], b[5])); // c05mc10 + z[11] = z[6].wrapping_sub(m(a[5], b[6]) + m(a[6], b[5])); // c06mc11 + z[12] = z[7].wrapping_sub(m(a[5], b[7]) + m(a[6], b[6]) + m(a[7], b[5])); // c07mc12 + z[13] = m(a[5], b[8]) + m(a[6], b[7]) + m(a[7], b[6]) + m(a[8], b[5]); // c13 + z[14] = m(a[6], b[8]) + m(a[7], b[7]) + m(a[8], b[6]); // c14 + z[15] = m(a[7], b[8]) + m(a[8], b[7]); // c15 + z[16] = m(a[8], b[8]); // c16 + + z[ 5] = z[10].wrapping_sub(z[ 0]); // c05mc10 - c00 + z[ 6] = z[11].wrapping_sub(z[ 1]); // c06mc11 - c01 + z[ 7] = z[12].wrapping_sub(z[ 2]); // c07mc12 - c02 + z[ 8] = z[ 8].wrapping_sub(z[13]); // c08mc13 - c03 + z[ 9] = z[14].wrapping_add(z[ 4]); // c14 + c04 + z[10] = z[15].wrapping_add(z[10]); // c15 + c05mc10 + z[11] = z[16].wrapping_add(z[11]); // c16 + c06mc11 + + let aa = [ + a[0] + a[5], + a[1] + a[6], + a[2] + a[7], + a[3] + a[8] + ]; + + let bb = [ + b[0] + b[5], + b[1] + b[6], + b[2] + b[7], + b[3] + b[8] + ]; + + z[ 5] = (m(aa[0], bb[0])) .wrapping_add(z[ 5]); // c20 + c05mc10 - c00 + z[ 6] = (m(aa[0], bb[1]) + m(aa[1], bb[0])) .wrapping_add(z[ 6]); // c21 + c06mc11 - c01 + z[ 7] = (m(aa[0], bb[2]) + m(aa[1], bb[1]) + m(aa[2], bb[0])) .wrapping_add(z[ 7]); // c22 + c07mc12 - c02 + z[ 8] = (m(aa[0], bb[3]) + m(aa[1], bb[2]) + m(aa[2], bb[1]) + m(aa[3], bb[0])) .wrapping_add(z[ 8]); // c23 + c08mc13 - c03 + z[ 9] = (m(aa[0], b[4]) + m(aa[1], bb[3]) + m(aa[2], bb[2]) + m(aa[3], bb[1]) + m(a[4], bb[0])).wrapping_sub(z[ 9]); // c24 - c14 - c04 + z[10] = ( m(aa[1], b[4]) + m(aa[2], bb[3]) + m(aa[3], bb[2]) + m(a[4], bb[1])).wrapping_sub(z[10]); // c25 - c15 - c05mc10 + z[11] = ( m(aa[2], b[4]) + m(aa[3], bb[3]) + m(a[4], bb[2])).wrapping_sub(z[11]); // c26 - c16 - c06mc11 + z[12] = ( m(aa[3], b[4]) + m(a[4], bb[3])).wrapping_sub(z[12]); // c27 - c07mc12 + + z + } + + /// Compute `a^2`. + #[inline(always)] + #[rustfmt::skip] // keep alignment of calculations + fn square_internal(a: &Scalar29) -> [u64; 17] { + let aa = [ + a[0] * 2, + a[1] * 2, + a[2] * 2, + a[3] * 2, + a[4] * 2, + a[5] * 2, + a[6] * 2, + a[7] * 2 + ]; + + [ + m( a[0], a[0]), + m(aa[0], a[1]), + m(aa[0], a[2]) + m( a[1], a[1]), + m(aa[0], a[3]) + m(aa[1], a[2]), + m(aa[0], a[4]) + m(aa[1], a[3]) + m( a[2], a[2]), + m(aa[0], a[5]) + m(aa[1], a[4]) + m(aa[2], a[3]), + m(aa[0], a[6]) + m(aa[1], a[5]) + m(aa[2], a[4]) + m( a[3], a[3]), + m(aa[0], a[7]) + m(aa[1], a[6]) + m(aa[2], a[5]) + m(aa[3], a[4]), + m(aa[0], a[8]) + m(aa[1], a[7]) + m(aa[2], a[6]) + m(aa[3], a[5]) + m( a[4], a[4]), + m(aa[1], a[8]) + m(aa[2], a[7]) + m(aa[3], a[6]) + m(aa[4], a[5]), + m(aa[2], a[8]) + m(aa[3], a[7]) + m(aa[4], a[6]) + m( a[5], a[5]), + m(aa[3], a[8]) + m(aa[4], a[7]) + m(aa[5], a[6]), + m(aa[4], a[8]) + m(aa[5], a[7]) + m( a[6], a[6]), + m(aa[5], a[8]) + m(aa[6], a[7]), + m(aa[6], a[8]) + m( a[7], a[7]), + m(aa[7], a[8]), + m( a[8], a[8]), + ] + } + + /// Compute `limbs/R` (mod l), where R is the Montgomery modulus 2^261 + #[inline(always)] + #[rustfmt::skip] // keep alignment of part1() and part2() computations + pub (crate) fn montgomery_reduce(limbs: &[u64; 17]) -> Scalar29 { + + #[inline(always)] + fn part1(sum: u64) -> (u64, u32) { + let p = (sum as u32).wrapping_mul(constants::LFACTOR) & ((1u32 << 29) - 1); + ((sum + m(p,constants::L[0])) >> 29, p) + } + + #[inline(always)] + fn part2(sum: u64) -> (u64, u32) { + let w = (sum as u32) & ((1u32 << 29) - 1); + (sum >> 29, w) + } + + // note: l5,l6,l7 are zero, so their multiplies can be skipped + let l = &constants::L; + + // the first half computes the Montgomery adjustment factor n, and begins adding n*l to make limbs divisible by R + let (carry, n0) = part1( limbs[ 0]); + let (carry, n1) = part1(carry + limbs[ 1] + m(n0,l[1])); + let (carry, n2) = part1(carry + limbs[ 2] + m(n0,l[2]) + m(n1,l[1])); + let (carry, n3) = part1(carry + limbs[ 3] + m(n0,l[3]) + m(n1,l[2]) + m(n2,l[1])); + let (carry, n4) = part1(carry + limbs[ 4] + m(n0,l[4]) + m(n1,l[3]) + m(n2,l[2]) + m(n3,l[1])); + let (carry, n5) = part1(carry + limbs[ 5] + m(n1,l[4]) + m(n2,l[3]) + m(n3,l[2]) + m(n4,l[1])); + let (carry, n6) = part1(carry + limbs[ 6] + m(n2,l[4]) + m(n3,l[3]) + m(n4,l[2]) + m(n5,l[1])); + let (carry, n7) = part1(carry + limbs[ 7] + m(n3,l[4]) + m(n4,l[3]) + m(n5,l[2]) + m(n6,l[1])); + let (carry, n8) = part1(carry + limbs[ 8] + m(n0,l[8]) + m(n4,l[4]) + m(n5,l[3]) + m(n6,l[2]) + m(n7,l[1])); + + // limbs is divisible by R now, so we can divide by R by simply storing the upper half as the result + let (carry, r0) = part2(carry + limbs[ 9] + m(n1,l[8]) + m(n5,l[4]) + m(n6,l[3]) + m(n7,l[2]) + m(n8,l[1])); + let (carry, r1) = part2(carry + limbs[10] + m(n2,l[8]) + m(n6,l[4]) + m(n7,l[3]) + m(n8,l[2])); + let (carry, r2) = part2(carry + limbs[11] + m(n3,l[8]) + m(n7,l[4]) + m(n8,l[3])); + let (carry, r3) = part2(carry + limbs[12] + m(n4,l[8]) + m(n8,l[4])); + let (carry, r4) = part2(carry + limbs[13] + m(n5,l[8]) ); + let (carry, r5) = part2(carry + limbs[14] + m(n6,l[8]) ); + let (carry, r6) = part2(carry + limbs[15] + m(n7,l[8]) ); + let (carry, r7) = part2(carry + limbs[16] + m(n8,l[8])); + let r8 = carry as u32; + + // result may be >= l, so attempt to subtract l + Scalar29::sub(&Scalar29([r0,r1,r2,r3,r4,r5,r6,r7,r8]), l) + } + + /// Compute `a * b` (mod l). + #[inline(never)] + pub fn mul(a: &Scalar29, b: &Scalar29) -> Scalar29 { + let ab = Scalar29::montgomery_reduce(&Scalar29::mul_internal(a, b)); + Scalar29::montgomery_reduce(&Scalar29::mul_internal(&ab, &constants::RR)) + } + + /// Compute `a^2` (mod l). + #[inline(never)] + #[allow(dead_code)] // XXX we don't expose square() via the Scalar API + pub fn square(&self) -> Scalar29 { + let aa = Scalar29::montgomery_reduce(&Scalar29::square_internal(self)); + Scalar29::montgomery_reduce(&Scalar29::mul_internal(&aa, &constants::RR)) + } + + /// Compute `(a * b) / R` (mod l), where R is the Montgomery modulus 2^261 + #[inline(never)] + pub fn montgomery_mul(a: &Scalar29, b: &Scalar29) -> Scalar29 { + Scalar29::montgomery_reduce(&Scalar29::mul_internal(a, b)) + } + + /// Compute `(a^2) / R` (mod l) in Montgomery form, where R is the Montgomery modulus 2^261 + #[inline(never)] + pub fn montgomery_square(&self) -> Scalar29 { + Scalar29::montgomery_reduce(&Scalar29::square_internal(self)) + } + + /// Puts a Scalar29 in to Montgomery form, i.e. computes `a*R (mod l)` + #[inline(never)] + pub fn as_montgomery(&self) -> Scalar29 { + Scalar29::montgomery_mul(self, &constants::RR) + } + + /// Takes a Scalar29 out of Montgomery form, i.e. computes `a/R (mod l)` + #[allow(clippy::wrong_self_convention)] + pub fn from_montgomery(&self) -> Scalar29 { + let mut limbs = [0u64; 17]; + for i in 0..9 { + limbs[i] = self[i] as u64; + } + Scalar29::montgomery_reduce(&limbs) + } +} + +#[cfg(test)] +mod test { + use super::*; + + /// Note: x is 2^253-1 which is slightly larger than the largest scalar produced by + /// this implementation (l-1), and should verify there are no overflows for valid scalars + /// + /// x = 2^253-1 = 14474011154664524427946373126085988481658748083205070504932198000989141204991 + /// x = 7237005577332262213973186563042994240801631723825162898930247062703686954002 mod l + /// x = 5147078182513738803124273553712992179887200054963030844803268920753008712037*R mod l in Montgomery form + pub static X: Scalar29 = Scalar29([ + 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, + 0x1fffffff, 0x001fffff, + ]); + + /// x^2 = 3078544782642840487852506753550082162405942681916160040940637093560259278169 mod l + pub static XX: Scalar29 = Scalar29([ + 0x00217559, 0x000b3401, 0x103ff43b, 0x1462a62c, 0x1d6f9f38, 0x18e7a42f, 0x09a3dcee, + 0x008dbe18, 0x0006ce65, + ]); + + /// x^2 = 2912514428060642753613814151688322857484807845836623976981729207238463947987*R mod l in Montgomery form + pub static XX_MONT: Scalar29 = Scalar29([ + 0x152b4d2e, 0x0571d53b, 0x1da6d964, 0x188663b6, 0x1d1b5f92, 0x19d50e3f, 0x12306c29, + 0x0c6f26fe, 0x00030edb, + ]); + + /// y = 6145104759870991071742105800796537629880401874866217824609283457819451087098 + pub static Y: Scalar29 = Scalar29([ + 0x1e1458fa, 0x165ba838, 0x1d787b36, 0x0e577f3a, 0x1d2baf06, 0x1d689a19, 0x1fff3047, + 0x117704ab, 0x000d9601, + ]); + + /// x*y = 36752150652102274958925982391442301741 + pub static XY: Scalar29 = Scalar29([ + 0x0ba7632d, 0x017736bb, 0x15c76138, 0x0c69daa1, 0x000001ba, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, + ]); + + /// x*y = 3783114862749659543382438697751927473898937741870308063443170013240655651591*R mod l in Montgomery form + pub static XY_MONT: Scalar29 = Scalar29([ + 0x077b51e1, 0x1c64e119, 0x02a19ef5, 0x18d2129e, 0x00de0430, 0x045a7bc8, 0x04cfc7c9, + 0x1c002681, 0x000bdc1c, + ]); + + /// a = 2351415481556538453565687241199399922945659411799870114962672658845158063753 + pub static A: Scalar29 = Scalar29([ + 0x07b3be89, 0x02291b60, 0x14a99f03, 0x07dc3787, 0x0a782aae, 0x16262525, 0x0cfdb93f, + 0x13f5718d, 0x000532da, + ]); + + /// b = 4885590095775723760407499321843594317911456947580037491039278279440296187236 + pub static B: Scalar29 = Scalar29([ + 0x15421564, 0x1e69fd72, 0x093d9692, 0x161785be, 0x1587d69f, 0x09d9dada, 0x130246c0, + 0x0c0a8e72, 0x000acd25, + ]); + + /// a+b = 0 + /// a-b = 4702830963113076907131374482398799845891318823599740229925345317690316127506 + pub static AB: Scalar29 = Scalar29([ + 0x0f677d12, 0x045236c0, 0x09533e06, 0x0fb86f0f, 0x14f0555c, 0x0c4c4a4a, 0x19fb727f, + 0x07eae31a, 0x000a65b5, + ]); + + // c = (2^512 - 1) % l = 1627715501170711445284395025044413883736156588369414752970002579683115011840 + pub static C: Scalar29 = Scalar29([ + 0x049c0f00, 0x00308f1a, 0x0164d1e9, 0x1c374ed1, 0x1be65d00, 0x19e90bfa, 0x08f73bb1, + 0x036f8613, 0x00039941, + ]); + + #[test] + fn mul_max() { + let res = Scalar29::mul(&X, &X); + for i in 0..9 { + assert!(res[i] == XX[i]); + } + } + + #[test] + fn square_max() { + let res = X.square(); + for i in 0..9 { + assert!(res[i] == XX[i]); + } + } + + #[test] + fn montgomery_mul_max() { + let res = Scalar29::montgomery_mul(&X, &X); + for i in 0..9 { + assert!(res[i] == XX_MONT[i]); + } + } + + #[test] + fn montgomery_square_max() { + let res = X.montgomery_square(); + for i in 0..9 { + assert!(res[i] == XX_MONT[i]); + } + } + + #[test] + fn mul() { + let res = Scalar29::mul(&X, &Y); + for i in 0..9 { + assert!(res[i] == XY[i]); + } + } + + #[test] + fn montgomery_mul() { + let res = Scalar29::montgomery_mul(&X, &Y); + for i in 0..9 { + assert!(res[i] == XY_MONT[i]); + } + } + + #[test] + fn add() { + let res = Scalar29::add(&A, &B); + let zero = Scalar29::ZERO; + for i in 0..9 { + assert!(res[i] == zero[i]); + } + } + + #[test] + fn sub() { + let res = Scalar29::sub(&A, &B); + for i in 0..9 { + assert!(res[i] == AB[i]); + } + } + + #[test] + fn from_bytes_wide() { + let bignum = [255u8; 64]; // 2^512 - 1 + let reduced = Scalar29::from_bytes_wide(&bignum); + for i in 0..9 { + assert!(reduced[i] == C[i]); + } + } +} diff --git a/curve25519-elligator2/src/backend/serial/u64/constants.rs b/curve25519-elligator2/src/backend/serial/u64/constants.rs new file mode 100644 index 00000000..06ded49a --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/u64/constants.rs @@ -0,0 +1,7771 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! This module contains backend-specific constant values, such as the 64-bit limbs of curve constants. + +use super::field::FieldElement51; +use super::scalar::Scalar52; +use crate::edwards::EdwardsPoint; + +#[cfg(feature = "precomputed-tables")] +use crate::{ + backend::serial::curve_models::AffineNielsPoint, + edwards::EdwardsBasepointTable, + window::{LookupTable, NafLookupTable8}, +}; + +/// The value of minus one, equal to `-&FieldElement::ONE` +pub(crate) const MINUS_ONE: FieldElement51 = FieldElement51::from_limbs([ + 2251799813685228, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, +]); + +/// Edwards `d` value, equal to `-121665/121666 mod p`. +pub(crate) const EDWARDS_D: FieldElement51 = FieldElement51::from_limbs([ + 929955233495203, + 466365720129213, + 1662059464998953, + 2033849074728123, + 1442794654840575, +]); + +/// Edwards `2*d` value, equal to `2*(-121665/121666) mod p`. +pub(crate) const EDWARDS_D2: FieldElement51 = FieldElement51::from_limbs([ + 1859910466990425, + 932731440258426, + 1072319116312658, + 1815898335770999, + 633789495995903, +]); + +/// One minus edwards `d` value squared, equal to `(1 - (-121665/121666) mod p) pow 2` +pub(crate) const ONE_MINUS_EDWARDS_D_SQUARED: FieldElement51 = FieldElement51::from_limbs([ + 1136626929484150, + 1998550399581263, + 496427632559748, + 118527312129759, + 45110755273534, +]); + +/// Edwards `d` value minus one squared, equal to `(((-121665/121666) mod p) - 1) pow 2` +pub(crate) const EDWARDS_D_MINUS_ONE_SQUARED: FieldElement51 = FieldElement51::from_limbs([ + 1507062230895904, + 1572317787530805, + 683053064812840, + 317374165784489, + 1572899562415810, +]); + +/// `= sqrt(a*d - 1)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters. +pub(crate) const SQRT_AD_MINUS_ONE: FieldElement51 = FieldElement51::from_limbs([ + 2241493124984347, + 425987919032274, + 2207028919301688, + 1220490630685848, + 974799131293748, +]); + +/// `= 1/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters. +pub(crate) const INVSQRT_A_MINUS_D: FieldElement51 = FieldElement51::from_limbs([ + 278908739862762, + 821645201101625, + 8113234426968, + 1777959178193151, + 2118520810568447, +]); + +/// Precomputed value of one of the square roots of -1 (mod p) +pub(crate) const SQRT_M1: FieldElement51 = FieldElement51::from_limbs([ + 1718705420411056, + 234908883556509, + 2233514472574048, + 2117202627021982, + 765476049583133, +]); + +/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within the Montgomery ladder.) +pub(crate) const APLUS2_OVER_FOUR: FieldElement51 = + FieldElement51::from_limbs([121666, 0, 0, 0, 0]); + +#[cfg(feature = "elligator2")] +/// `MONTGOMERY_A` is equal to 486662, which is a constant of the curve equation +/// for Curve25519 in its Montgomery form. (This is used internally within the +/// Elligator map.) +pub(crate) const MONTGOMERY_A: FieldElement51 = FieldElement51::from_limbs([486662, 0, 0, 0, 0]); + +#[cfg(feature = "elligator2")] +/// `MONTGOMERY_A_NEG` is equal to -486662. (This is used internally within the +/// Elligator map.) +pub(crate) const MONTGOMERY_A_NEG: FieldElement51 = FieldElement51::from_limbs([ + 2251799813198567, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, +]); + +/// `L` is the order of base point, i.e. 2^252 + 27742317777372353535851937790883648493 +pub(crate) const L: Scalar52 = Scalar52([ + 0x0002631a5cf5d3ed, + 0x000dea2f79cd6581, + 0x000000000014def9, + 0x0000000000000000, + 0x0000100000000000, +]); + +/// `L` * `LFACTOR` = -1 (mod 2^52) +pub(crate) const LFACTOR: u64 = 0x51da312547e1b; + +/// `R` = R % L where R = 2^260 +pub(crate) const R: Scalar52 = Scalar52([ + 0x000f48bd6721e6ed, + 0x0003bab5ac67e45a, + 0x000fffffeb35e51b, + 0x000fffffffffffff, + 0x00000fffffffffff, +]); + +/// `RR` = (R^2) % L where R = 2^260 +pub(crate) const RR: Scalar52 = Scalar52([ + 0x0009d265e952d13b, + 0x000d63c715bea69f, + 0x0005be65cb687604, + 0x0003dceec73d217f, + 0x000009411b7c309a, +]); + +/// The Ed25519 basepoint, as an `EdwardsPoint`. +/// +/// This is called `_POINT` to distinguish it from +/// `ED25519_BASEPOINT_TABLE`, which should be used for scalar +/// multiplication (it's much faster). +pub const ED25519_BASEPOINT_POINT: EdwardsPoint = EdwardsPoint { + X: FieldElement51::from_limbs([ + 1738742601995546, + 1146398526822698, + 2070867633025821, + 562264141797630, + 587772402128613, + ]), + Y: FieldElement51::from_limbs([ + 1801439850948184, + 1351079888211148, + 450359962737049, + 900719925474099, + 1801439850948198, + ]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([ + 1841354044333475, + 16398895984059, + 755974180946558, + 900171276175154, + 1821297809914039, + ]), +}; + +/// The 8-torsion subgroup \\(\mathcal E \[8\]\\). +/// +/// In the case of Curve25519, it is cyclic; the \\(i\\)-th element of +/// the array is \\(\[i\]P\\), where \\(P\\) is a point of order \\(8\\) +/// generating \\(\mathcal E\[8\]\\). +/// +/// Thus \\(\mathcal E\[4\]\\) is the points indexed by `0,2,4,6`, and +/// \\(\mathcal E\[2\]\\) is the points indexed by `0,4`. +pub const EIGHT_TORSION: [EdwardsPoint; 8] = EIGHT_TORSION_INNER_DOC_HIDDEN; + +/// Inner item used to hide limb constants from cargo doc output. +#[doc(hidden)] +pub const EIGHT_TORSION_INNER_DOC_HIDDEN: [EdwardsPoint; 8] = [ + EdwardsPoint { + X: FieldElement51::from_limbs([0, 0, 0, 0, 0]), + Y: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([0, 0, 0, 0, 0]), + }, + EdwardsPoint { + X: FieldElement51::from_limbs([ + 358744748052810, + 1691584618240980, + 977650209285361, + 1429865912637724, + 560044844278676, + ]), + Y: FieldElement51::from_limbs([ + 84926274344903, + 473620666599931, + 365590438845504, + 1028470286882429, + 2146499180330972, + ]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([ + 1448326834587521, + 1857896831960481, + 1093722731865333, + 1677408490711241, + 1915505153018406, + ]), + }, + EdwardsPoint { + X: FieldElement51::from_limbs([ + 533094393274173, + 2016890930128738, + 18285341111199, + 134597186663265, + 1486323764102114, + ]), + Y: FieldElement51::from_limbs([0, 0, 0, 0, 0]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([0, 0, 0, 0, 0]), + }, + EdwardsPoint { + X: FieldElement51::from_limbs([ + 358744748052810, + 1691584618240980, + 977650209285361, + 1429865912637724, + 560044844278676, + ]), + Y: FieldElement51::from_limbs([ + 2166873539340326, + 1778179147085316, + 1886209374839743, + 1223329526802818, + 105300633354275, + ]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([ + 803472979097708, + 393902981724766, + 1158077081819914, + 574391322974006, + 336294660666841, + ]), + }, + EdwardsPoint { + X: FieldElement51::from_limbs([0, 0, 0, 0, 0]), + Y: FieldElement51::from_limbs([ + 2251799813685228, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, + ]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([0, 0, 0, 0, 0]), + }, + EdwardsPoint { + X: FieldElement51::from_limbs([ + 1893055065632419, + 560215195444267, + 1274149604399886, + 821933901047523, + 1691754969406571, + ]), + Y: FieldElement51::from_limbs([ + 2166873539340326, + 1778179147085316, + 1886209374839743, + 1223329526802818, + 105300633354275, + ]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([ + 1448326834587521, + 1857896831960481, + 1093722731865333, + 1677408490711241, + 1915505153018406, + ]), + }, + EdwardsPoint { + X: FieldElement51::from_limbs([ + 1718705420411056, + 234908883556509, + 2233514472574048, + 2117202627021982, + 765476049583133, + ]), + Y: FieldElement51::from_limbs([0, 0, 0, 0, 0]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([0, 0, 0, 0, 0]), + }, + EdwardsPoint { + X: FieldElement51::from_limbs([ + 1893055065632419, + 560215195444267, + 1274149604399886, + 821933901047523, + 1691754969406571, + ]), + Y: FieldElement51::from_limbs([ + 84926274344903, + 473620666599931, + 365590438845504, + 1028470286882429, + 2146499180330972, + ]), + Z: FieldElement51::from_limbs([1, 0, 0, 0, 0]), + T: FieldElement51::from_limbs([ + 803472979097708, + 393902981724766, + 1158077081819914, + 574391322974006, + 336294660666841, + ]), + }, +]; + +/// Table containing precomputed multiples of the Ed25519 basepoint \\(B = (x, 4/5)\\). +#[cfg(feature = "precomputed-tables")] +pub static ED25519_BASEPOINT_TABLE: &EdwardsBasepointTable = + &ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN; + +/// Inner constant, used to avoid filling the docs with precomputed points. +#[doc(hidden)] +#[cfg(feature = "precomputed-tables")] +static ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN: EdwardsBasepointTable = EdwardsBasepointTable([ + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3540182452943730, + 2497478415033846, + 2521227595762870, + 1462984067271729, + 2389212253076811, + ]), + y_minus_x: FieldElement51::from_limbs([ + 62697248952638, + 204681361388450, + 631292143396476, + 338455783676468, + 1213667448819585, + ]), + xy2d: FieldElement51::from_limbs([ + 301289933810280, + 1259582250014073, + 1422107436869536, + 796239922652654, + 1953934009299142, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3632771708514775, + 790832306631235, + 2067202295274102, + 1995808275510000, + 1566530869037010, + ]), + y_minus_x: FieldElement51::from_limbs([ + 463307831301544, + 432984605774163, + 1610641361907204, + 750899048855000, + 1894842303421586, + ]), + xy2d: FieldElement51::from_limbs([ + 748439484463711, + 1033211726465151, + 1396005112841647, + 1611506220286469, + 1972177495910992, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1601611775252272, + 1720807796594148, + 1132070835939856, + 3512254832574799, + 2147779492816910, + ]), + y_minus_x: FieldElement51::from_limbs([ + 316559037616741, + 2177824224946892, + 1459442586438991, + 1461528397712656, + 751590696113597, + ]), + xy2d: FieldElement51::from_limbs([ + 1850748884277385, + 1200145853858453, + 1068094770532492, + 672251375690438, + 1586055907191707, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 934282339813791, + 1846903124198670, + 1172395437954843, + 1007037127761661, + 1830588347719256, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1694390458783935, + 1735906047636159, + 705069562067493, + 648033061693059, + 696214010414170, + ]), + xy2d: FieldElement51::from_limbs([ + 1121406372216585, + 192876649532226, + 190294192191717, + 1994165897297032, + 2245000007398739, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 769950342298400, + 2384754244604994, + 3095885746880802, + 3225892188161580, + 2977876099231263, + ]), + y_minus_x: FieldElement51::from_limbs([ + 425251763115706, + 608463272472562, + 442562545713235, + 837766094556764, + 374555092627893, + ]), + xy2d: FieldElement51::from_limbs([ + 1086255230780037, + 274979815921559, + 1960002765731872, + 929474102396301, + 1190409889297339, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1388594989461809, + 316767091099457, + 2646098655878230, + 1230079486801004, + 1440737038838979, + ]), + y_minus_x: FieldElement51::from_limbs([ + 7380825640100, + 146210432690483, + 304903576448906, + 1198869323871120, + 997689833219095, + ]), + xy2d: FieldElement51::from_limbs([ + 1181317918772081, + 114573476638901, + 262805072233344, + 265712217171332, + 294181933805782, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2916800678241215, + 2065379846933858, + 2622030924071124, + 2602788184473875, + 1233371373142984, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2019367628972465, + 676711900706637, + 110710997811333, + 1108646842542025, + 517791959672113, + ]), + xy2d: FieldElement51::from_limbs([ + 965130719900578, + 247011430587952, + 526356006571389, + 91986625355052, + 2157223321444601, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 4320419353804412, + 4218074731744053, + 957728544705548, + 729906502578991, + 2411634706750414, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2073601412052185, + 31021124762708, + 264500969797082, + 248034690651703, + 1030252227928288, + ]), + xy2d: FieldElement51::from_limbs([ + 551790716293402, + 1989538725166328, + 801169423371717, + 2052451893578887, + 678432056995012, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1368953770187805, + 3042147450398169, + 2689308289352409, + 2142576377050579, + 1932081720066286, + ]), + y_minus_x: FieldElement51::from_limbs([ + 953638594433374, + 1092333936795051, + 1419774766716690, + 805677984380077, + 859228993502513, + ]), + xy2d: FieldElement51::from_limbs([ + 1200766035879111, + 20142053207432, + 1465634435977050, + 1645256912097844, + 295121984874596, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1735718747031538, + 1248237894295956, + 1204753118328107, + 976066523550493, + 2317743583219840, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1060098822528990, + 1586825862073490, + 212301317240126, + 1975302711403555, + 666724059764335, + ]), + xy2d: FieldElement51::from_limbs([ + 1091990273418756, + 1572899409348578, + 80968014455247, + 306009358661350, + 1520450739132526, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3732317023121341, + 1511153322193951, + 3496143672676420, + 2556587964178488, + 2620936670181690, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2151330273626164, + 762045184746182, + 1688074332551515, + 823046109005759, + 907602769079491, + ]), + xy2d: FieldElement51::from_limbs([ + 2047386910586836, + 168470092900250, + 1552838872594810, + 340951180073789, + 360819374702533, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1982622644432037, + 2014393600336956, + 2380709022489462, + 3869592437614438, + 2357094095599062, + ]), + y_minus_x: FieldElement51::from_limbs([ + 980234343912898, + 1712256739246056, + 588935272190264, + 204298813091998, + 841798321043288, + ]), + xy2d: FieldElement51::from_limbs([ + 197561292938973, + 454817274782871, + 1963754960082318, + 2113372252160468, + 971377527342673, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2416499262514576, + 2254927265442919, + 3451304785234000, + 1766155447043651, + 1899238924683527, + ]), + y_minus_x: FieldElement51::from_limbs([ + 732262946680281, + 1674412764227063, + 2182456405662809, + 1350894754474250, + 558458873295247, + ]), + xy2d: FieldElement51::from_limbs([ + 2103305098582922, + 1960809151316468, + 715134605001343, + 1454892949167181, + 40827143824949, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1239289043050193, + 1744654158124578, + 758702410031698, + 4048562808759936, + 2253402870349013, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2232056027107988, + 987343914584615, + 2115594492994461, + 1819598072792159, + 1119305654014850, + ]), + xy2d: FieldElement51::from_limbs([ + 320153677847348, + 939613871605645, + 641883205761567, + 1930009789398224, + 329165806634126, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3232730304159378, + 1242488692177892, + 1251446316964684, + 1086618677993530, + 1961430968465772, + ]), + y_minus_x: FieldElement51::from_limbs([ + 276821765317453, + 1536835591188030, + 1305212741412361, + 61473904210175, + 2051377036983058, + ]), + xy2d: FieldElement51::from_limbs([ + 833449923882501, + 1750270368490475, + 1123347002068295, + 185477424765687, + 278090826653186, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 794524995833413, + 1849907304548286, + 2305148486158393, + 1272368559505216, + 1147304168324779, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1504846112759364, + 1203096289004681, + 562139421471418, + 274333017451844, + 1284344053775441, + ]), + xy2d: FieldElement51::from_limbs([ + 483048732424432, + 2116063063343382, + 30120189902313, + 292451576741007, + 1156379271702225, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3180171966714267, + 2147692869914563, + 1455665844462196, + 1986737809425946, + 2437006863943337, + ]), + y_minus_x: FieldElement51::from_limbs([ + 137732961814206, + 706670923917341, + 1387038086865771, + 1965643813686352, + 1384777115696347, + ]), + xy2d: FieldElement51::from_limbs([ + 481144981981577, + 2053319313589856, + 2065402289827512, + 617954271490316, + 1106602634668125, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2948097833334040, + 3145099472726142, + 1148636718636008, + 2278533891034865, + 2203955659340680, + ]), + y_minus_x: FieldElement51::from_limbs([ + 657390353372855, + 998499966885562, + 991893336905797, + 810470207106761, + 343139804608786, + ]), + xy2d: FieldElement51::from_limbs([ + 791736669492960, + 934767652997115, + 824656780392914, + 1759463253018643, + 361530362383518, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2022541353055578, + 4346500076272714, + 3802807888710933, + 2494585331103411, + 2947785218648809, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1287487199965223, + 2215311941380308, + 1552928390931986, + 1664859529680196, + 1125004975265243, + ]), + xy2d: FieldElement51::from_limbs([ + 677434665154918, + 989582503122485, + 1817429540898386, + 1052904935475344, + 1143826298169798, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2619066141993637, + 2570231002607651, + 2947429167440602, + 2885885471266079, + 2276381426249673, + ]), + y_minus_x: FieldElement51::from_limbs([ + 773360688841258, + 1815381330538070, + 363773437667376, + 539629987070205, + 783280434248437, + ]), + xy2d: FieldElement51::from_limbs([ + 180820816194166, + 168937968377394, + 748416242794470, + 1227281252254508, + 1567587861004268, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2730575372268893, + 2062896624554806, + 2951191072970647, + 2609899222113120, + 1277310261461760, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1984740906540026, + 1079164179400229, + 1056021349262661, + 1659958556483663, + 1088529069025527, + ]), + xy2d: FieldElement51::from_limbs([ + 580736401511151, + 1842931091388998, + 1177201471228238, + 2075460256527244, + 1301133425678027, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1515728832059163, + 1575261009617579, + 1510246567196186, + 2442877836294952, + 2368461529974388, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1295295738269652, + 1714742313707026, + 545583042462581, + 2034411676262552, + 1513248090013606, + ]), + xy2d: FieldElement51::from_limbs([ + 230710545179830, + 30821514358353, + 760704303452229, + 390668103790604, + 573437871383156, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3421179921230875, + 2514967047430861, + 4274701112739695, + 3071700566936367, + 4275698278559832, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2102254323485823, + 1570832666216754, + 34696906544624, + 1993213739807337, + 70638552271463, + ]), + xy2d: FieldElement51::from_limbs([ + 894132856735058, + 548675863558441, + 845349339503395, + 1942269668326667, + 1615682209874691, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3539470031223082, + 1222355136884919, + 1846481788678694, + 1150426571265110, + 1613523400722047, + ]), + y_minus_x: FieldElement51::from_limbs([ + 793388516527298, + 1315457083650035, + 1972286999342417, + 1901825953052455, + 338269477222410, + ]), + xy2d: FieldElement51::from_limbs([ + 550201530671806, + 778605267108140, + 2063911101902983, + 115500557286349, + 2041641272971022, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 717255318455100, + 519313764361315, + 2080406977303708, + 541981206705521, + 774328150311600, + ]), + y_minus_x: FieldElement51::from_limbs([ + 261715221532238, + 1795354330069993, + 1496878026850283, + 499739720521052, + 389031152673770, + ]), + xy2d: FieldElement51::from_limbs([ + 1997217696294013, + 1717306351628065, + 1684313917746180, + 1644426076011410, + 1857378133465451, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3727234538477877, + 2328731709971226, + 3368528843456914, + 2002544139318041, + 2977347647489186, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2022306639183567, + 726296063571875, + 315345054448644, + 1058733329149221, + 1448201136060677, + ]), + xy2d: FieldElement51::from_limbs([ + 1710065158525665, + 1895094923036397, + 123988286168546, + 1145519900776355, + 1607510767693874, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2813405189107769, + 1071733543815036, + 2383296312486238, + 1946868434569998, + 3079937947649451, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1548495173745801, + 442310529226540, + 998072547000384, + 553054358385281, + 644824326376171, + ]), + xy2d: FieldElement51::from_limbs([ + 1445526537029440, + 2225519789662536, + 914628859347385, + 1064754194555068, + 1660295614401091, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3451490036797185, + 2275827949507588, + 2318438102929588, + 2309425969971222, + 2816893781664854, + ]), + y_minus_x: FieldElement51::from_limbs([ + 876926774220824, + 554618976488214, + 1012056309841565, + 839961821554611, + 1414499340307677, + ]), + xy2d: FieldElement51::from_limbs([ + 703047626104145, + 1266841406201770, + 165556500219173, + 486991595001879, + 1011325891650656, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1622861044480487, + 1156394801573634, + 4120932379100752, + 2578903799462977, + 2095342781472283, + ]), + y_minus_x: FieldElement51::from_limbs([ + 334886927423922, + 489511099221528, + 129160865966726, + 1720809113143481, + 619700195649254, + ]), + xy2d: FieldElement51::from_limbs([ + 1646545795166119, + 1758370782583567, + 714746174550637, + 1472693650165135, + 898994790308209, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2585203586724508, + 2547572356138185, + 1693106465353609, + 912330357530760, + 2723035471635610, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1811196219982022, + 1068969825533602, + 289602974833439, + 1988956043611592, + 863562343398367, + ]), + xy2d: FieldElement51::from_limbs([ + 906282429780072, + 2108672665779781, + 432396390473936, + 150625823801893, + 1708930497638539, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 925664675702309, + 2273216662253932, + 4083236455546587, + 601157008940112, + 2623617868729744, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1479786007267725, + 1738881859066675, + 68646196476567, + 2146507056100328, + 1247662817535471, + ]), + xy2d: FieldElement51::from_limbs([ + 52035296774456, + 939969390708103, + 312023458773250, + 59873523517659, + 1231345905848899, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2895154920100990, + 2541986621181021, + 2013561737429022, + 2571447883196794, + 2645536492181409, + ]), + y_minus_x: FieldElement51::from_limbs([ + 129358342392716, + 1932811617704777, + 1176749390799681, + 398040349861790, + 1170779668090425, + ]), + xy2d: FieldElement51::from_limbs([ + 2051980782668029, + 121859921510665, + 2048329875753063, + 1235229850149665, + 519062146124755, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3859970785658325, + 2667608874045675, + 1350468408164765, + 2038620059057678, + 3278704299674360, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1837656083115103, + 1510134048812070, + 906263674192061, + 1821064197805734, + 565375124676301, + ]), + xy2d: FieldElement51::from_limbs([ + 578027192365650, + 2034800251375322, + 2128954087207123, + 478816193810521, + 2196171989962750, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1633188840273120, + 3104586986058956, + 1548762607215795, + 1266275218902681, + 3359018017010381, + ]), + y_minus_x: FieldElement51::from_limbs([ + 462189358480054, + 1784816734159228, + 1611334301651368, + 1303938263943540, + 707589560319424, + ]), + xy2d: FieldElement51::from_limbs([ + 1038829280972848, + 38176604650029, + 753193246598573, + 1136076426528122, + 595709990562434, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3660251634545082, + 2194984964010832, + 2198361797561729, + 1061962440055713, + 1645147963442934, + ]), + y_minus_x: FieldElement51::from_limbs([ + 4701053362120, + 1647641066302348, + 1047553002242085, + 1923635013395977, + 206970314902065, + ]), + xy2d: FieldElement51::from_limbs([ + 1750479161778571, + 1362553355169293, + 1891721260220598, + 966109370862782, + 1024913988299801, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2464498862816952, + 1117950018299774, + 1873945661751056, + 3655602735669306, + 2382695896337945, + ]), + y_minus_x: FieldElement51::from_limbs([ + 636808533673210, + 1262201711667560, + 390951380330599, + 1663420692697294, + 561951321757406, + ]), + xy2d: FieldElement51::from_limbs([ + 520731594438141, + 1446301499955692, + 273753264629267, + 1565101517999256, + 1019411827004672, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3178327305714638, + 3443653291096626, + 734233225181170, + 2435838701226518, + 4042225960010590, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1464651961852572, + 1483737295721717, + 1519450561335517, + 1161429831763785, + 405914998179977, + ]), + xy2d: FieldElement51::from_limbs([ + 996126634382301, + 796204125879525, + 127517800546509, + 344155944689303, + 615279846169038, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2990523894660505, + 2188666632415295, + 1961313708559162, + 1506545807547587, + 3403101452654988, + ]), + y_minus_x: FieldElement51::from_limbs([ + 622917337413835, + 1218989177089035, + 1284857712846592, + 970502061709359, + 351025208117090, + ]), + xy2d: FieldElement51::from_limbs([ + 2067814584765580, + 1677855129927492, + 2086109782475197, + 235286517313238, + 1416314046739645, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2838644076315587, + 2559244195637442, + 458399356043425, + 2853867838192310, + 3280348017100490, + ]), + y_minus_x: FieldElement51::from_limbs([ + 678489922928203, + 2016657584724032, + 90977383049628, + 1026831907234582, + 615271492942522, + ]), + xy2d: FieldElement51::from_limbs([ + 301225714012278, + 1094837270268560, + 1202288391010439, + 644352775178361, + 1647055902137983, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1210746697896459, + 1416608304244708, + 2938287290903104, + 3496931005119382, + 3303038150540984, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1135604073198207, + 1683322080485474, + 769147804376683, + 2086688130589414, + 900445683120379, + ]), + xy2d: FieldElement51::from_limbs([ + 1971518477615628, + 401909519527336, + 448627091057375, + 1409486868273821, + 1214789035034363, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1364039144731711, + 1897497433586190, + 2203097701135459, + 2397261210496499, + 1349844460790698, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1045230323257973, + 818206601145807, + 630513189076103, + 1672046528998132, + 807204017562437, + ]), + xy2d: FieldElement51::from_limbs([ + 439961968385997, + 386362664488986, + 1382706320807688, + 309894000125359, + 2207801346498567, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3480804500082836, + 3172443782216110, + 2375775707596425, + 2933223806901024, + 1400559197080972, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2003766096898049, + 170074059235165, + 1141124258967971, + 1485419893480973, + 1573762821028725, + ]), + xy2d: FieldElement51::from_limbs([ + 729905708611432, + 1270323270673202, + 123353058984288, + 426460209632942, + 2195574535456672, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1271140255321216, + 2044363183174497, + 2303925201319937, + 3696920060379952, + 3194341800024331, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1761608437466135, + 583360847526804, + 1586706389685493, + 2157056599579261, + 1170692369685772, + ]), + xy2d: FieldElement51::from_limbs([ + 871476219910823, + 1878769545097794, + 2241832391238412, + 548957640601001, + 690047440233174, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2548994545820755, + 1366347803776819, + 3552985325930849, + 561849853336293, + 1533554921345731, + ]), + y_minus_x: FieldElement51::from_limbs([ + 999628998628371, + 1132836708493400, + 2084741674517453, + 469343353015612, + 678782988708035, + ]), + xy2d: FieldElement51::from_limbs([ + 2189427607417022, + 699801937082607, + 412764402319267, + 1478091893643349, + 2244675696854460, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3964091869651792, + 2456213404310121, + 3657538451018088, + 2660781114515010, + 3112882032961968, + ]), + y_minus_x: FieldElement51::from_limbs([ + 508561155940631, + 966928475686665, + 2236717801150132, + 424543858577297, + 2089272956986143, + ]), + xy2d: FieldElement51::from_limbs([ + 221245220129925, + 1156020201681217, + 491145634799213, + 542422431960839, + 828100817819207, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2405556784925632, + 1299874139923976, + 2644898978945750, + 1058234455773021, + 996989038681183, + ]), + y_minus_x: FieldElement51::from_limbs([ + 559086812798481, + 573177704212711, + 1629737083816402, + 1399819713462595, + 1646954378266038, + ]), + xy2d: FieldElement51::from_limbs([ + 1887963056288059, + 228507035730124, + 1468368348640282, + 930557653420194, + 613513962454686, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1224529808187534, + 1577022856702685, + 2206946542980843, + 625883007765001, + 2531730607197406, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1076287717051609, + 1114455570543035, + 187297059715481, + 250446884292121, + 1885187512550540, + ]), + xy2d: FieldElement51::from_limbs([ + 902497362940219, + 76749815795675, + 1657927525633846, + 1420238379745202, + 1340321636548352, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1129576631190765, + 3533793823712575, + 996844254743017, + 2509676177174497, + 3402650555740265, + ]), + y_minus_x: FieldElement51::from_limbs([ + 628740660038789, + 1943038498527841, + 467786347793886, + 1093341428303375, + 235413859513003, + ]), + xy2d: FieldElement51::from_limbs([ + 237425418909360, + 469614029179605, + 1512389769174935, + 1241726368345357, + 441602891065214, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3988217766743784, + 726531315520507, + 1833335034432527, + 1629442561574747, + 2876218732971333, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1960754663920689, + 497040957888962, + 1909832851283095, + 1271432136996826, + 2219780368020940, + ]), + xy2d: FieldElement51::from_limbs([ + 1537037379417136, + 1358865369268262, + 2130838645654099, + 828733687040705, + 1999987652890901, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 629042105241795, + 1098854999137608, + 887281544569320, + 3674901833560025, + 2259711072636808, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1811562332665373, + 1501882019007673, + 2213763501088999, + 359573079719636, + 36370565049116, + ]), + xy2d: FieldElement51::from_limbs([ + 218907117361280, + 1209298913016966, + 1944312619096112, + 1130690631451061, + 1342327389191701, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1369976867854685, + 1396479602419169, + 4017456468084104, + 2203659200586298, + 3250127649802489, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2230701885562825, + 1348173180338974, + 2172856128624598, + 1426538746123771, + 444193481326151, + ]), + xy2d: FieldElement51::from_limbs([ + 784210426627951, + 918204562375674, + 1284546780452985, + 1324534636134684, + 1872449409642708, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2571438643225542, + 2848082470493653, + 2037902696412607, + 1557219121643918, + 341938082688094, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1901860206695915, + 2004489122065736, + 1625847061568236, + 973529743399879, + 2075287685312905, + ]), + xy2d: FieldElement51::from_limbs([ + 1371853944110545, + 1042332820512553, + 1949855697918254, + 1791195775521505, + 37487364849293, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 687200189577836, + 1082536651125675, + 2896024754556794, + 2592723009743198, + 2595381160432643, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2082717129583892, + 27829425539422, + 145655066671970, + 1690527209845512, + 1865260509673478, + ]), + xy2d: FieldElement51::from_limbs([ + 1059729620568824, + 2163709103470266, + 1440302280256872, + 1769143160546397, + 869830310425069, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3861316033464273, + 777277757338816, + 2101121130363987, + 550762194946473, + 1905542338659364, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2024821921041576, + 426948675450149, + 595133284085473, + 471860860885970, + 600321679413000, + ]), + xy2d: FieldElement51::from_limbs([ + 598474602406721, + 1468128276358244, + 1191923149557635, + 1501376424093216, + 1281662691293476, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1721138489890688, + 1264336102277790, + 2684864359106535, + 1359988423149465, + 3813671107094695, + ]), + y_minus_x: FieldElement51::from_limbs([ + 719520245587143, + 393380711632345, + 132350400863381, + 1543271270810729, + 1819543295798660, + ]), + xy2d: FieldElement51::from_limbs([ + 396397949784152, + 1811354474471839, + 1362679985304303, + 2117033964846756, + 498041172552279, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1812471844975748, + 1856491995543149, + 126579494584102, + 3288044672967868, + 1975108050082549, + ]), + y_minus_x: FieldElement51::from_limbs([ + 650623932407995, + 1137551288410575, + 2125223403615539, + 1725658013221271, + 2134892965117796, + ]), + xy2d: FieldElement51::from_limbs([ + 522584000310195, + 1241762481390450, + 1743702789495384, + 2227404127826575, + 1686746002148897, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 427904865186293, + 1703211129693455, + 1585368107547509, + 3688784302429584, + 3012988348299225, + ]), + y_minus_x: FieldElement51::from_limbs([ + 318101947455002, + 248138407995851, + 1481904195303927, + 309278454311197, + 1258516760217879, + ]), + xy2d: FieldElement51::from_limbs([ + 1275068538599310, + 513726919533379, + 349926553492294, + 688428871968420, + 1702400196000666, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3313663849950481, + 3213411074010628, + 2573659446386085, + 3297400443644764, + 1985130202504037, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1558816436882417, + 1962896332636523, + 1337709822062152, + 1501413830776938, + 294436165831932, + ]), + xy2d: FieldElement51::from_limbs([ + 818359826554971, + 1862173000996177, + 626821592884859, + 573655738872376, + 1749691246745455, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1988022651432119, + 3333911312271288, + 1834020786104820, + 3706626690108935, + 692929915223121, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2146513703733331, + 584788900394667, + 464965657279958, + 2183973639356127, + 238371159456790, + ]), + xy2d: FieldElement51::from_limbs([ + 1129007025494441, + 2197883144413266, + 265142755578169, + 971864464758890, + 1983715884903702, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1291366624493056, + 2633256531874362, + 1711482489312443, + 1815233647702022, + 3144079596677715, + ]), + y_minus_x: FieldElement51::from_limbs([ + 444548969917454, + 1452286453853356, + 2113731441506810, + 645188273895859, + 810317625309512, + ]), + xy2d: FieldElement51::from_limbs([ + 2242724082797924, + 1373354730327868, + 1006520110883049, + 2147330369940688, + 1151816104883620, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3997520014069025, + 4163522956860564, + 2056329390702073, + 2607026987995097, + 3131032608056347, + ]), + y_minus_x: FieldElement51::from_limbs([ + 163723479936298, + 115424889803150, + 1156016391581227, + 1894942220753364, + 1970549419986329, + ]), + xy2d: FieldElement51::from_limbs([ + 681981452362484, + 267208874112496, + 1374683991933094, + 638600984916117, + 646178654558546, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2265178468539480, + 2358037120714814, + 1944412051589650, + 4093776581610705, + 2482502633520820, + ]), + y_minus_x: FieldElement51::from_limbs([ + 260683893467075, + 854060306077237, + 913639551980112, + 4704576840123, + 280254810808712, + ]), + xy2d: FieldElement51::from_limbs([ + 715374893080287, + 1173334812210491, + 1806524662079626, + 1894596008000979, + 398905715033393, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2751826223412909, + 3848231101880618, + 1420380351989369, + 3237011375206737, + 392444930785632, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2096421546958141, + 1922523000950363, + 789831022876840, + 427295144688779, + 320923973161730, + ]), + xy2d: FieldElement51::from_limbs([ + 1927770723575450, + 1485792977512719, + 1850996108474547, + 551696031508956, + 2126047405475647, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2112099158080129, + 2994370617594963, + 2258284371762679, + 1951119898618915, + 2344890196388664, + ]), + y_minus_x: FieldElement51::from_limbs([ + 383905201636970, + 859946997631870, + 855623867637644, + 1017125780577795, + 794250831877809, + ]), + xy2d: FieldElement51::from_limbs([ + 77571826285752, + 999304298101753, + 487841111777762, + 1038031143212339, + 339066367948762, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2926794589205781, + 2517835660016036, + 826951213393477, + 1405007746162285, + 1781791018620876, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1001412661522686, + 348196197067298, + 1666614366723946, + 888424995032760, + 580747687801357, + ]), + xy2d: FieldElement51::from_limbs([ + 1939560076207777, + 1409892634407635, + 552574736069277, + 383854338280405, + 190706709864139, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2177087163428741, + 1439255351721944, + 3459870654068041, + 2230616362004768, + 1396886392021913, + ]), + y_minus_x: FieldElement51::from_limbs([ + 676962063230039, + 1880275537148808, + 2046721011602706, + 888463247083003, + 1318301552024067, + ]), + xy2d: FieldElement51::from_limbs([ + 1466980508178206, + 617045217998949, + 652303580573628, + 757303753529064, + 207583137376902, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3762856566592150, + 2357202940576524, + 2745234706458093, + 1091943425335975, + 1802717338077427, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1853982405405128, + 1878664056251147, + 1528011020803992, + 1019626468153565, + 1128438412189035, + ]), + xy2d: FieldElement51::from_limbs([ + 1963939888391106, + 293456433791664, + 697897559513649, + 985882796904380, + 796244541237972, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2668570812315008, + 2641455366112301, + 1314476859406755, + 1749382513022778, + 3413705412424739, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1428358296490651, + 1027115282420478, + 304840698058337, + 441410174026628, + 1819358356278573, + ]), + xy2d: FieldElement51::from_limbs([ + 204943430200135, + 1554861433819175, + 216426658514651, + 264149070665950, + 2047097371738319, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1934415182909015, + 1393285083565062, + 2768209145458208, + 3409490548679139, + 2372839480279515, + ]), + y_minus_x: FieldElement51::from_limbs([ + 662035583584445, + 286736105093098, + 1131773000510616, + 818494214211439, + 472943792054479, + ]), + xy2d: FieldElement51::from_limbs([ + 665784778135882, + 1893179629898606, + 808313193813106, + 276797254706413, + 1563426179676396, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 945205108984213, + 2778077376644543, + 1324180513733565, + 1666970227868664, + 2405347422974421, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2031433403516252, + 203996615228162, + 170487168837083, + 981513604791390, + 843573964916831, + ]), + xy2d: FieldElement51::from_limbs([ + 1476570093962618, + 838514669399805, + 1857930577281364, + 2017007352225784, + 317085545220047, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1461557121912823, + 1600674043318359, + 2157134900399597, + 1670641601940616, + 2379565397488531, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1293543509393474, + 2143624609202546, + 1058361566797508, + 214097127393994, + 946888515472729, + ]), + xy2d: FieldElement51::from_limbs([ + 357067959932916, + 1290876214345711, + 521245575443703, + 1494975468601005, + 800942377643885, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2817916472785262, + 820247422481739, + 994464017954148, + 2578957425371613, + 2344391131796991, + ]), + y_minus_x: FieldElement51::from_limbs([ + 617256647603209, + 1652107761099439, + 1857213046645471, + 1085597175214970, + 817432759830522, + ]), + xy2d: FieldElement51::from_limbs([ + 771808161440705, + 1323510426395069, + 680497615846440, + 851580615547985, + 1320806384849017, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1219260086131896, + 2898968820282063, + 2331400938444953, + 2161724213426747, + 2656661710745446, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1327968293887866, + 1335500852943256, + 1401587164534264, + 558137311952440, + 1551360549268902, + ]), + xy2d: FieldElement51::from_limbs([ + 417621685193956, + 1429953819744454, + 396157358457099, + 1940470778873255, + 214000046234152, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1268047918491954, + 2172375426948536, + 1533916099229249, + 1761293575457130, + 3842422480712013, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1627072914981959, + 2211603081280073, + 1912369601616504, + 1191770436221309, + 2187309757525860, + ]), + xy2d: FieldElement51::from_limbs([ + 1149147819689533, + 378692712667677, + 828475842424202, + 2218619146419342, + 70688125792186, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3551539230764990, + 3690416477138006, + 3788528892189659, + 2053896748919837, + 3260220846276494, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2040723824657366, + 399555637875075, + 632543375452995, + 872649937008051, + 1235394727030233, + ]), + xy2d: FieldElement51::from_limbs([ + 2211311599327900, + 2139787259888175, + 938706616835350, + 12609661139114, + 2081897930719789, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1324994503390431, + 2588782144267879, + 1183998925654176, + 3343454479598522, + 2300527487656566, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1845522914617879, + 1222198248335542, + 150841072760134, + 1927029069940982, + 1189913404498011, + ]), + xy2d: FieldElement51::from_limbs([ + 1079559557592645, + 2215338383666441, + 1903569501302605, + 49033973033940, + 305703433934152, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2346453219102138, + 3637921163538246, + 3313930291577009, + 2288353761164521, + 3085469462634093, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1432015813136298, + 440364795295369, + 1395647062821501, + 1976874522764578, + 934452372723352, + ]), + xy2d: FieldElement51::from_limbs([ + 1296625309219774, + 2068273464883862, + 1858621048097805, + 1492281814208508, + 2235868981918946, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1490330266465551, + 1858795661361448, + 3688040948655011, + 2546373032584894, + 3459939824714180, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1282462923712748, + 741885683986255, + 2027754642827561, + 518989529541027, + 1826610009555945, + ]), + xy2d: FieldElement51::from_limbs([ + 1525827120027511, + 723686461809551, + 1597702369236987, + 244802101764964, + 1502833890372311, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2365421849929742, + 3485539881431101, + 2925909765963743, + 2114345180342964, + 2418564326541511, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2041668749310338, + 2184405322203901, + 1633400637611036, + 2110682505536899, + 2048144390084644, + ]), + xy2d: FieldElement51::from_limbs([ + 503058759232932, + 760293024620937, + 2027152777219493, + 666858468148475, + 1539184379870952, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1916168475367211, + 3167426246226591, + 883217071712574, + 363427871374304, + 1976029821251593, + ]), + y_minus_x: FieldElement51::from_limbs([ + 678039535434506, + 570587290189340, + 1605302676614120, + 2147762562875701, + 1706063797091704, + ]), + xy2d: FieldElement51::from_limbs([ + 1439489648586438, + 2194580753290951, + 832380563557396, + 561521973970522, + 584497280718389, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2439789269177838, + 681223515948274, + 1933493571072456, + 1872921007304880, + 2739962177820919, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1413466089534451, + 410844090765630, + 1397263346404072, + 408227143123410, + 1594561803147811, + ]), + xy2d: FieldElement51::from_limbs([ + 2102170800973153, + 719462588665004, + 1479649438510153, + 1097529543970028, + 1302363283777685, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3193865531532443, + 3321113493038208, + 2007341951411050, + 2322773230131539, + 1419433790163705, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1146565545556377, + 1661971299445212, + 406681704748893, + 564452436406089, + 1109109865829139, + ]), + xy2d: FieldElement51::from_limbs([ + 2214421081775077, + 1165671861210569, + 1890453018796184, + 3556249878661, + 442116172656317, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3005630360306059, + 1666955059895018, + 1530775289309243, + 3371786842789394, + 2164156153857579, + ]), + y_minus_x: FieldElement51::from_limbs([ + 615171919212796, + 1523849404854568, + 854560460547503, + 2067097370290715, + 1765325848586042, + ]), + xy2d: FieldElement51::from_limbs([ + 1094538949313667, + 1796592198908825, + 870221004284388, + 2025558921863561, + 1699010892802384, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1951351290725195, + 1916457206844795, + 2449824998123274, + 1909076887557594, + 1938542290318919, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1014323197538413, + 869150639940606, + 1756009942696599, + 1334952557375672, + 1544945379082874, + ]), + xy2d: FieldElement51::from_limbs([ + 764055910920305, + 1603590757375439, + 146805246592357, + 1843313433854297, + 954279890114939, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 80113526615731, + 764536758732259, + 3306939158785481, + 2721052465444637, + 2869697326116762, + ]), + y_minus_x: FieldElement51::from_limbs([ + 74497112547268, + 740094153192149, + 1745254631717581, + 727713886503130, + 1283034364416928, + ]), + xy2d: FieldElement51::from_limbs([ + 525892105991110, + 1723776830270342, + 1476444848991936, + 573789489857760, + 133864092632978, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2794411533877810, + 1986812262899320, + 1162535242465837, + 2733298779828712, + 2796400347268869, + ]), + y_minus_x: FieldElement51::from_limbs([ + 64123227344372, + 1239927720647794, + 1360722983445904, + 222610813654661, + 62429487187991, + ]), + xy2d: FieldElement51::from_limbs([ + 1793193323953132, + 91096687857833, + 70945970938921, + 2158587638946380, + 1537042406482111, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1895854577604590, + 3646695522634664, + 1728548428495943, + 3392664713925397, + 2815445147288308, + ]), + y_minus_x: FieldElement51::from_limbs([ + 141358280486863, + 91435889572504, + 1087208572552643, + 1829599652522921, + 1193307020643647, + ]), + xy2d: FieldElement51::from_limbs([ + 1611230858525381, + 950720175540785, + 499589887488610, + 2001656988495019, + 88977313255908, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3440880315164906, + 2184348804772596, + 3292618539427567, + 2018318290311833, + 1712060030915354, + ]), + y_minus_x: FieldElement51::from_limbs([ + 873966876953756, + 1090638350350440, + 1708559325189137, + 672344594801910, + 1320437969700239, + ]), + xy2d: FieldElement51::from_limbs([ + 1508590048271766, + 1131769479776094, + 101550868699323, + 428297785557897, + 561791648661744, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3008217384184691, + 2489682092917849, + 2136263418594015, + 1701968045454886, + 2955512998822720, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1781187809325462, + 1697624151492346, + 1381393690939988, + 175194132284669, + 1483054666415238, + ]), + xy2d: FieldElement51::from_limbs([ + 2175517777364616, + 708781536456029, + 955668231122942, + 1967557500069555, + 2021208005604118, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3366935780292116, + 2476017186636029, + 915967306279221, + 593866251291540, + 2813546907893254, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1443163092879439, + 391875531646162, + 2180847134654632, + 464538543018753, + 1594098196837178, + ]), + xy2d: FieldElement51::from_limbs([ + 850858855888869, + 319436476624586, + 327807784938441, + 740785849558761, + 17128415486016, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2132756334090048, + 2788047633840893, + 2300706964962114, + 2860273011285942, + 3513489358708031, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1525176236978354, + 974205476721062, + 293436255662638, + 148269621098039, + 137961998433963, + ]), + xy2d: FieldElement51::from_limbs([ + 1121075518299410, + 2071745529082111, + 1265567917414828, + 1648196578317805, + 496232102750820, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2374121042985030, + 3274721891178932, + 2001275453369483, + 2017441881607947, + 3245005694463250, + ]), + y_minus_x: FieldElement51::from_limbs([ + 654925550560074, + 1168810995576858, + 575655959430926, + 905758704861388, + 496774564663534, + ]), + xy2d: FieldElement51::from_limbs([ + 1954109525779738, + 2117022646152485, + 338102630417180, + 1194140505732026, + 107881734943492, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1714785840001267, + 4288299832366837, + 1876380234251965, + 2056717182974196, + 1645855254384642, + ]), + y_minus_x: FieldElement51::from_limbs([ + 106431476499341, + 62482972120563, + 1513446655109411, + 807258751769522, + 538491469114, + ]), + xy2d: FieldElement51::from_limbs([ + 2002850762893643, + 1243624520538135, + 1486040410574605, + 2184752338181213, + 378495998083531, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 922510868424903, + 1089502620807680, + 402544072617374, + 1131446598479839, + 1290278588136533, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1867998812076769, + 715425053580701, + 39968586461416, + 2173068014586163, + 653822651801304, + ]), + xy2d: FieldElement51::from_limbs([ + 162892278589453, + 182585796682149, + 75093073137630, + 497037941226502, + 133871727117371, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 4166396390264918, + 1608999621851577, + 1987629837704609, + 1519655314857977, + 1819193753409464, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1949315551096831, + 1069003344994464, + 1939165033499916, + 1548227205730856, + 1933767655861407, + ]), + xy2d: FieldElement51::from_limbs([ + 1730519386931635, + 1393284965610134, + 1597143735726030, + 416032382447158, + 1429665248828629, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 360275475604546, + 2799635544748326, + 2467160717872776, + 2848446553564254, + 2584509464110332, + ]), + y_minus_x: FieldElement51::from_limbs([ + 47602113726801, + 1522314509708010, + 437706261372925, + 814035330438027, + 335930650933545, + ]), + xy2d: FieldElement51::from_limbs([ + 1291597595523886, + 1058020588994081, + 402837842324045, + 1363323695882781, + 2105763393033193, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2361321796251793, + 3967057562270386, + 1112231216891515, + 2046641005101484, + 2386048970842261, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2156991030936798, + 2227544497153325, + 1869050094431622, + 754875860479115, + 1754242344267058, + ]), + xy2d: FieldElement51::from_limbs([ + 1846089562873800, + 98894784984326, + 1412430299204844, + 171351226625762, + 1100604760929008, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2335972195815721, + 2751510784385293, + 425749630620777, + 1762872794206857, + 2864642415813208, + ]), + y_minus_x: FieldElement51::from_limbs([ + 868309334532756, + 1703010512741873, + 1952690008738057, + 4325269926064, + 2071083554962116, + ]), + xy2d: FieldElement51::from_limbs([ + 523094549451158, + 401938899487815, + 1407690589076010, + 2022387426254453, + 158660516411257, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 612867287630009, + 2700012425789062, + 2823428891104443, + 1466796750919375, + 1728478129663858, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1723848973783452, + 2208822520534681, + 1718748322776940, + 1974268454121942, + 1194212502258141, + ]), + xy2d: FieldElement51::from_limbs([ + 1254114807944608, + 977770684047110, + 2010756238954993, + 1783628927194099, + 1525962994408256, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2484263871921055, + 1948628555342433, + 1835348780427694, + 1031609499437291, + 2316271920603621, + ]), + y_minus_x: FieldElement51::from_limbs([ + 767338676040683, + 754089548318405, + 1523192045639075, + 435746025122062, + 512692508440385, + ]), + xy2d: FieldElement51::from_limbs([ + 1255955808701983, + 1700487367990941, + 1166401238800299, + 1175121994891534, + 1190934801395380, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2600943821853521, + 1337012557669161, + 1475912332999108, + 3573418268585706, + 2299411105589567, + ]), + y_minus_x: FieldElement51::from_limbs([ + 877519947135419, + 2172838026132651, + 272304391224129, + 1655143327559984, + 886229406429814, + ]), + xy2d: FieldElement51::from_limbs([ + 375806028254706, + 214463229793940, + 572906353144089, + 572168269875638, + 697556386112979, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1168827102357825, + 823864273033637, + 4323338565789945, + 788062026895923, + 2851378154428610, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1948116082078088, + 2054898304487796, + 2204939184983900, + 210526805152138, + 786593586607626, + ]), + xy2d: FieldElement51::from_limbs([ + 1915320147894736, + 156481169009469, + 655050471180417, + 592917090415421, + 2165897438660879, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1726336468579724, + 1119932070398949, + 1929199510967666, + 2285718602008207, + 1836837863503149, + ]), + y_minus_x: FieldElement51::from_limbs([ + 829996854845988, + 217061778005138, + 1686565909803640, + 1346948817219846, + 1723823550730181, + ]), + xy2d: FieldElement51::from_limbs([ + 384301494966394, + 687038900403062, + 2211195391021739, + 254684538421383, + 1245698430589680, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1247567493562669, + 4229981908141095, + 2435671288478202, + 806570235643434, + 2540261331753164, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1449077384734201, + 38285445457996, + 2136537659177832, + 2146493000841573, + 725161151123125, + ]), + xy2d: FieldElement51::from_limbs([ + 1201928866368855, + 800415690605445, + 1703146756828343, + 997278587541744, + 1858284414104014, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2608268623334125, + 3034173730618399, + 1718002439402869, + 3644022065904502, + 663171266061950, + ]), + y_minus_x: FieldElement51::from_limbs([ + 759628738230460, + 1012693474275852, + 353780233086498, + 246080061387552, + 2030378857679162, + ]), + xy2d: FieldElement51::from_limbs([ + 2040672435071076, + 888593182036908, + 1298443657189359, + 1804780278521327, + 354070726137060, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1894938527423184, + 3715012855162525, + 2726210319182898, + 2499094776718546, + 877975941029127, + ]), + y_minus_x: FieldElement51::from_limbs([ + 207937160991127, + 12966911039119, + 820997788283092, + 1010440472205286, + 1701372890140810, + ]), + xy2d: FieldElement51::from_limbs([ + 218882774543183, + 533427444716285, + 1233243976733245, + 435054256891319, + 1509568989549904, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 4140638349397055, + 3303977572025869, + 3465353617009382, + 2420981822812579, + 2715174081801119, + ]), + y_minus_x: FieldElement51::from_limbs([ + 299137589460312, + 1594371588983567, + 868058494039073, + 257771590636681, + 1805012993142921, + ]), + xy2d: FieldElement51::from_limbs([ + 1806842755664364, + 2098896946025095, + 1356630998422878, + 1458279806348064, + 347755825962072, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1402334161391744, + 3811883484731547, + 1008585416617746, + 1147797150908892, + 1420416683642459, + ]), + y_minus_x: FieldElement51::from_limbs([ + 665506704253369, + 273770475169863, + 799236974202630, + 848328990077558, + 1811448782807931, + ]), + xy2d: FieldElement51::from_limbs([ + 1468412523962641, + 771866649897997, + 1931766110147832, + 799561180078482, + 524837559150077, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2223212657821831, + 2882216061048914, + 2144451165500327, + 3068710944633039, + 3276150872095279, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1266603897524861, + 156378408858100, + 1275649024228779, + 447738405888420, + 253186462063095, + ]), + xy2d: FieldElement51::from_limbs([ + 2022215964509735, + 136144366993649, + 1800716593296582, + 1193970603800203, + 871675847064218, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1862751661970309, + 851596246739884, + 1519315554814041, + 3794598280232697, + 3669775149586767, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1228168094547481, + 334133883362894, + 587567568420081, + 433612590281181, + 603390400373205, + ]), + xy2d: FieldElement51::from_limbs([ + 121893973206505, + 1843345804916664, + 1703118377384911, + 497810164760654, + 101150811654673, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2710146069631716, + 2542709749304591, + 1452768413850678, + 2802722688939463, + 1537286854336537, + ]), + y_minus_x: FieldElement51::from_limbs([ + 584322311184395, + 380661238802118, + 114839394528060, + 655082270500073, + 2111856026034852, + ]), + xy2d: FieldElement51::from_limbs([ + 996965581008991, + 2148998626477022, + 1012273164934654, + 1073876063914522, + 1688031788934939, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3175286832534829, + 2085106799623354, + 2779882615305384, + 1606206360876187, + 2987706905397772, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1697697887804317, + 1335343703828273, + 831288615207040, + 949416685250051, + 288760277392022, + ]), + xy2d: FieldElement51::from_limbs([ + 1419122478109648, + 1325574567803701, + 602393874111094, + 2107893372601700, + 1314159682671307, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2201150872731785, + 2180241023425241, + 2349463270108411, + 1633405770247823, + 3100744856129234, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1173339555550611, + 818605084277583, + 47521504364289, + 924108720564965, + 735423405754506, + ]), + xy2d: FieldElement51::from_limbs([ + 830104860549448, + 1886653193241086, + 1600929509383773, + 1475051275443631, + 286679780900937, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3828911108518224, + 3282698983453994, + 2396700729978777, + 4216472406664814, + 2820189914640497, + ]), + y_minus_x: FieldElement51::from_limbs([ + 278388655910247, + 487143369099838, + 927762205508727, + 181017540174210, + 1616886700741287, + ]), + xy2d: FieldElement51::from_limbs([ + 1191033906638969, + 940823957346562, + 1606870843663445, + 861684761499847, + 658674867251089, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1875032594195527, + 1427106132796197, + 2976536204647406, + 3153660325729987, + 2887068310954007, + ]), + y_minus_x: FieldElement51::from_limbs([ + 622869792298357, + 1903919278950367, + 1922588621661629, + 1520574711600434, + 1087100760174640, + ]), + xy2d: FieldElement51::from_limbs([ + 25465949416618, + 1693639527318811, + 1526153382657203, + 125943137857169, + 145276964043999, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2466539671654587, + 920212862967914, + 4191701364657517, + 3463662605460468, + 2336897329405367, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2006245852772938, + 734762734836159, + 254642929763427, + 1406213292755966, + 239303749517686, + ]), + xy2d: FieldElement51::from_limbs([ + 1619678837192149, + 1919424032779215, + 1357391272956794, + 1525634040073113, + 1310226789796241, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3292563523447371, + 1704449869235351, + 2857062884141577, + 1998838089036354, + 1312142911487502, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1996723311435669, + 1844342766567060, + 985455700466044, + 1165924681400960, + 311508689870129, + ]), + xy2d: FieldElement51::from_limbs([ + 43173156290518, + 2202883069785309, + 1137787467085917, + 1733636061944606, + 1394992037553852, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 670078326344559, + 2807454838744604, + 2723759199967685, + 2141455487356408, + 849015953823125, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2197214573372804, + 794254097241315, + 1030190060513737, + 267632515541902, + 2040478049202624, + ]), + xy2d: FieldElement51::from_limbs([ + 1812516004670529, + 1609256702920783, + 1706897079364493, + 258549904773295, + 996051247540686, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1540374301420565, + 1764656898914615, + 1810104162020396, + 3175608592848336, + 2916189887881826, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1323460699404750, + 1262690757880991, + 871777133477900, + 1060078894988977, + 1712236889662886, + ]), + xy2d: FieldElement51::from_limbs([ + 1696163952057966, + 1391710137550823, + 608793846867416, + 1034391509472039, + 1780770894075012, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1367603834210822, + 4383788460268472, + 890353773628143, + 1908908219165595, + 2522636708938139, + ]), + y_minus_x: FieldElement51::from_limbs([ + 597536315471731, + 40375058742586, + 1942256403956049, + 1185484645495932, + 312666282024145, + ]), + xy2d: FieldElement51::from_limbs([ + 1919411405316294, + 1234508526402192, + 1066863051997083, + 1008444703737597, + 1348810787701552, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2102881477513865, + 3822074379630609, + 1573617900503707, + 2270462449417831, + 2232324307922097, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1853931367696942, + 8107973870707, + 350214504129299, + 775206934582587, + 1752317649166792, + ]), + xy2d: FieldElement51::from_limbs([ + 1417148368003523, + 721357181628282, + 505725498207811, + 373232277872983, + 261634707184480, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2186733281493248, + 2250694917008620, + 1014829812957440, + 2731797975137637, + 2335366007561721, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1268116367301224, + 560157088142809, + 802626839600444, + 2210189936605713, + 1129993785579988, + ]), + xy2d: FieldElement51::from_limbs([ + 615183387352312, + 917611676109240, + 878893615973325, + 978940963313282, + 938686890583575, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 522024729211672, + 3296859129001056, + 1892245413707789, + 1907891107684253, + 2059998109500714, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1799679152208884, + 912132775900387, + 25967768040979, + 432130448590461, + 274568990261996, + ]), + xy2d: FieldElement51::from_limbs([ + 98698809797682, + 2144627600856209, + 1907959298569602, + 811491302610148, + 1262481774981493, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1791451399743152, + 1713538728337276, + 2370149810942738, + 1882306388849953, + 158235232210248, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1217809823321928, + 2173947284933160, + 1986927836272325, + 1388114931125539, + 12686131160169, + ]), + xy2d: FieldElement51::from_limbs([ + 1650875518872272, + 1136263858253897, + 1732115601395988, + 734312880662190, + 1252904681142109, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2624786269799113, + 2777230729143418, + 2116279931702134, + 2753222527273063, + 1907002872974924, + ]), + y_minus_x: FieldElement51::from_limbs([ + 803147181835288, + 868941437997146, + 316299302989663, + 943495589630550, + 571224287904572, + ]), + xy2d: FieldElement51::from_limbs([ + 227742695588364, + 1776969298667369, + 628602552821802, + 457210915378118, + 2041906378111140, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 815000523470260, + 3164885502413555, + 3303859931956420, + 1345536665214222, + 541623413135555, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1580216071604333, + 1877997504342444, + 857147161260913, + 703522726778478, + 2182763974211603, + ]), + xy2d: FieldElement51::from_limbs([ + 1870080310923419, + 71988220958492, + 1783225432016732, + 615915287105016, + 1035570475990230, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2982787564515398, + 857613889540279, + 1083813157271766, + 1002817255970169, + 1719228484436074, + ]), + y_minus_x: FieldElement51::from_limbs([ + 377616581647602, + 1581980403078513, + 804044118130621, + 2034382823044191, + 643844048472185, + ]), + xy2d: FieldElement51::from_limbs([ + 176957326463017, + 1573744060478586, + 528642225008045, + 1816109618372371, + 1515140189765006, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1888911448245718, + 3638910709296328, + 4176303607751676, + 1731539523700948, + 2230378382645454, + ]), + y_minus_x: FieldElement51::from_limbs([ + 443392177002051, + 233793396845137, + 2199506622312416, + 1011858706515937, + 974676837063129, + ]), + xy2d: FieldElement51::from_limbs([ + 1846351103143623, + 1949984838808427, + 671247021915253, + 1946756846184401, + 1929296930380217, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 849646212451983, + 1410198775302919, + 2325567699868943, + 1641663456615811, + 3014056086137659, + ]), + y_minus_x: FieldElement51::from_limbs([ + 692017667358279, + 723305578826727, + 1638042139863265, + 748219305990306, + 334589200523901, + ]), + xy2d: FieldElement51::from_limbs([ + 22893968530686, + 2235758574399251, + 1661465835630252, + 925707319443452, + 1203475116966621, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3053098849470395, + 3985092410411378, + 1664508947088595, + 2719548934677170, + 3899298398220870, + ]), + y_minus_x: FieldElement51::from_limbs([ + 903105258014366, + 427141894933047, + 561187017169777, + 1884330244401954, + 1914145708422219, + ]), + xy2d: FieldElement51::from_limbs([ + 1344191060517578, + 1960935031767890, + 1518838929955259, + 1781502350597190, + 1564784025565682, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2925523165433334, + 1979969272514922, + 3427087126180756, + 1187589090978665, + 1881897672213940, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1917185587363432, + 1098342571752737, + 5935801044414, + 2000527662351839, + 1538640296181569, + ]), + xy2d: FieldElement51::from_limbs([ + 2495540013192, + 678856913479236, + 224998292422872, + 219635787698590, + 1972465269000940, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 271413961212179, + 3604851875156899, + 2596511104968730, + 2014925838520661, + 2006221033113941, + ]), + y_minus_x: FieldElement51::from_limbs([ + 194583029968109, + 514316781467765, + 829677956235672, + 1676415686873082, + 810104584395840, + ]), + xy2d: FieldElement51::from_limbs([ + 1980510813313589, + 1948645276483975, + 152063780665900, + 129968026417582, + 256984195613935, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1860190562533083, + 1936576191345085, + 2712900106391212, + 1811043097042829, + 3209286562992083, + ]), + y_minus_x: FieldElement51::from_limbs([ + 796664815624365, + 1543160838872951, + 1500897791837765, + 1667315977988401, + 599303877030711, + ]), + xy2d: FieldElement51::from_limbs([ + 1151480509533204, + 2136010406720455, + 738796060240027, + 319298003765044, + 1150614464349587, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1731069268103131, + 2987442261301335, + 1364750481334267, + 2669032653668119, + 3178908082812908, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1017222050227968, + 1987716148359, + 2234319589635701, + 621282683093392, + 2132553131763026, + ]), + xy2d: FieldElement51::from_limbs([ + 1567828528453324, + 1017807205202360, + 565295260895298, + 829541698429100, + 307243822276582, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 249079270936229, + 1501514259790706, + 3199709537890096, + 944551802437486, + 2804458577667728, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2089966982947227, + 1854140343916181, + 2151980759220007, + 2139781292261749, + 158070445864917, + ]), + xy2d: FieldElement51::from_limbs([ + 1338766321464554, + 1906702607371284, + 1519569445519894, + 115384726262267, + 1393058953390992, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3616421371950629, + 3764188048593604, + 1926731583198685, + 2041482526432505, + 3172200936019022, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1884844597333588, + 601480070269079, + 620203503079537, + 1079527400117915, + 1202076693132015, + ]), + xy2d: FieldElement51::from_limbs([ + 840922919763324, + 727955812569642, + 1303406629750194, + 522898432152867, + 294161410441865, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2605560604520539, + 1598361541848742, + 3374705511887547, + 4174333403844152, + 2670907514351827, + ]), + y_minus_x: FieldElement51::from_limbs([ + 359856369838236, + 180914355488683, + 861726472646627, + 218807937262986, + 575626773232501, + ]), + xy2d: FieldElement51::from_limbs([ + 755467689082474, + 909202735047934, + 730078068932500, + 936309075711518, + 2007798262842972, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1609384177904054, + 2614544999293875, + 1335318541768200, + 3052765584121496, + 2799677792952659, + ]), + y_minus_x: FieldElement51::from_limbs([ + 984339177776787, + 815727786505884, + 1645154585713747, + 1659074964378553, + 1686601651984156, + ]), + xy2d: FieldElement51::from_limbs([ + 1697863093781930, + 599794399429786, + 1104556219769607, + 830560774794755, + 12812858601017, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1168737550514982, + 897832437380552, + 463140296333799, + 2554364413707795, + 2008360505135500, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1856930662813910, + 678090852002597, + 1920179140755167, + 1259527833759868, + 55540971895511, + ]), + xy2d: FieldElement51::from_limbs([ + 1158643631044921, + 476554103621892, + 178447851439725, + 1305025542653569, + 103433927680625, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2176793111709008, + 3828525530035639, + 2009350167273522, + 2012390194631546, + 2125297410909580, + ]), + y_minus_x: FieldElement51::from_limbs([ + 825403285195098, + 2144208587560784, + 1925552004644643, + 1915177840006985, + 1015952128947864, + ]), + xy2d: FieldElement51::from_limbs([ + 1807108316634472, + 1534392066433717, + 347342975407218, + 1153820745616376, + 7375003497471, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3234860815484973, + 2683011703586488, + 2201903782961092, + 3069193724749589, + 2214616493042166, + ]), + y_minus_x: FieldElement51::from_limbs([ + 228567918409756, + 865093958780220, + 358083886450556, + 159617889659320, + 1360637926292598, + ]), + xy2d: FieldElement51::from_limbs([ + 234147501399755, + 2229469128637390, + 2175289352258889, + 1397401514549353, + 1885288963089922, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3363562226636810, + 2504649386192636, + 3300514047508588, + 2397910909286693, + 1237505378776769, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1113790697840279, + 1051167139966244, + 1045930658550944, + 2011366241542643, + 1686166824620755, + ]), + xy2d: FieldElement51::from_limbs([ + 1054097349305049, + 1872495070333352, + 182121071220717, + 1064378906787311, + 100273572924182, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3558210666856834, + 1627717417672446, + 2302783034773665, + 1109249951172249, + 3122001602766640, + ]), + y_minus_x: FieldElement51::from_limbs([ + 104233794644221, + 1548919791188248, + 2224541913267306, + 2054909377116478, + 1043803389015153, + ]), + xy2d: FieldElement51::from_limbs([ + 216762189468802, + 707284285441622, + 190678557969733, + 973969342604308, + 1403009538434867, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3530824104723725, + 2596576648903557, + 2525521909702446, + 4086000250496689, + 634517197663803, + ]), + y_minus_x: FieldElement51::from_limbs([ + 343805853118335, + 1302216857414201, + 566872543223541, + 2051138939539004, + 321428858384280, + ]), + xy2d: FieldElement51::from_limbs([ + 470067171324852, + 1618629234173951, + 2000092177515639, + 7307679772789, + 1117521120249968, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2529951391976704, + 1810282338562946, + 1771599529530998, + 3635459223356879, + 2937173228157088, + ]), + y_minus_x: FieldElement51::from_limbs([ + 577009397403102, + 1791440261786291, + 2177643735971638, + 174546149911960, + 1412505077782326, + ]), + xy2d: FieldElement51::from_limbs([ + 893719721537457, + 1201282458018197, + 1522349501711173, + 58011597740583, + 1130406465887139, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 412607348255434, + 1280455764199780, + 2233277987330768, + 2265979894086913, + 2583384512102412, + ]), + y_minus_x: FieldElement51::from_limbs([ + 262483770854550, + 990511055108216, + 526885552771698, + 571664396646158, + 354086190278723, + ]), + xy2d: FieldElement51::from_limbs([ + 1820352417585487, + 24495617171480, + 1547899057533253, + 10041836186225, + 480457105094042, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2023310314989233, + 2889705151211129, + 2106474638900686, + 2809620524769320, + 1687858215057825, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1144168702609745, + 604444390410187, + 1544541121756138, + 1925315550126027, + 626401428894002, + ]), + xy2d: FieldElement51::from_limbs([ + 1922168257351784, + 2018674099908659, + 1776454117494445, + 956539191509034, + 36031129147635, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2796444352433270, + 1039872944430373, + 3128550222815858, + 2962457525011798, + 3468752501170219, + ]), + y_minus_x: FieldElement51::from_limbs([ + 58242421545916, + 2035812695641843, + 2118491866122923, + 1191684463816273, + 46921517454099, + ]), + xy2d: FieldElement51::from_limbs([ + 272268252444639, + 1374166457774292, + 2230115177009552, + 1053149803909880, + 1354288411641016, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1857910905368338, + 1754729879288912, + 3137745277795125, + 1516096106802165, + 1602902393369811, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1193437069800958, + 901107149704790, + 999672920611411, + 477584824802207, + 364239578697845, + ]), + xy2d: FieldElement51::from_limbs([ + 886299989548838, + 1538292895758047, + 1590564179491896, + 1944527126709657, + 837344427345298, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3006358179063534, + 1712186480903617, + 3955456640022779, + 3002110732175033, + 2770795853936147, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1309847803895382, + 1462151862813074, + 211370866671570, + 1544595152703681, + 1027691798954090, + ]), + xy2d: FieldElement51::from_limbs([ + 803217563745370, + 1884799722343599, + 1357706345069218, + 2244955901722095, + 730869460037413, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2941099284981214, + 1831210565161070, + 3626987155270686, + 3358084791231418, + 1893781834054268, + ]), + y_minus_x: FieldElement51::from_limbs([ + 696351368613042, + 1494385251239250, + 738037133616932, + 636385507851544, + 927483222611406, + ]), + xy2d: FieldElement51::from_limbs([ + 1949114198209333, + 1104419699537997, + 783495707664463, + 1747473107602770, + 2002634765788641, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1607325776830197, + 2782683755100581, + 1451089452727894, + 3833490970768671, + 496100432831153, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1068900648804224, + 2006891997072550, + 1134049269345549, + 1638760646180091, + 2055396084625778, + ]), + xy2d: FieldElement51::from_limbs([ + 2222475519314561, + 1870703901472013, + 1884051508440561, + 1344072275216753, + 1318025677799069, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 155711679280637, + 681100400509288, + 389811735211209, + 2135723811340709, + 2660533024889373, + ]), + y_minus_x: FieldElement51::from_limbs([ + 7813206966729, + 194444201427550, + 2071405409526507, + 1065605076176312, + 1645486789731291, + ]), + xy2d: FieldElement51::from_limbs([ + 16625790644959, + 1647648827778410, + 1579910185572704, + 436452271048548, + 121070048451050, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3289062842237779, + 2820185594063076, + 2549752917829677, + 3810384325616458, + 2238221839292470, + ]), + y_minus_x: FieldElement51::from_limbs([ + 190565267697443, + 672855706028058, + 338796554369226, + 337687268493904, + 853246848691734, + ]), + xy2d: FieldElement51::from_limbs([ + 1763863028400139, + 766498079432444, + 1321118624818005, + 69494294452268, + 858786744165651, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3543856582248253, + 1456632109855637, + 3352431060735432, + 1386133165675320, + 3484698163879000, + ]), + y_minus_x: FieldElement51::from_limbs([ + 366253102478259, + 525676242508811, + 1449610995265438, + 1183300845322183, + 185960306491545, + ]), + xy2d: FieldElement51::from_limbs([ + 28315355815982, + 460422265558930, + 1799675876678724, + 1969256312504498, + 1051823843138725, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2408714813047231, + 3857948219405196, + 1665208410108429, + 2569443092377519, + 1383783705665319, + ]), + y_minus_x: FieldElement51::from_limbs([ + 54684536365732, + 2210010038536222, + 1194984798155308, + 535239027773705, + 1516355079301361, + ]), + xy2d: FieldElement51::from_limbs([ + 1484387703771650, + 198537510937949, + 2186282186359116, + 617687444857508, + 647477376402122, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2147715541830533, + 2751832352131065, + 2898179830570073, + 2604027669016369, + 1488268620408051, + ]), + y_minus_x: FieldElement51::from_limbs([ + 159386186465542, + 1877626593362941, + 618737197060512, + 1026674284330807, + 1158121760792685, + ]), + xy2d: FieldElement51::from_limbs([ + 1744544377739822, + 1964054180355661, + 1685781755873170, + 2169740670377448, + 1286112621104591, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2333777063470241, + 3919742931398333, + 3920783633320113, + 1605016835177614, + 1353960708075544, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1602253788689063, + 439542044889886, + 2220348297664483, + 657877410752869, + 157451572512238, + ]), + xy2d: FieldElement51::from_limbs([ + 1029287186166717, + 65860128430192, + 525298368814832, + 1491902500801986, + 1461064796385400, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2660016802414475, + 2121095722306988, + 913562102267595, + 1879708920318308, + 2492861262121979, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1185483484383269, + 1356339572588553, + 584932367316448, + 102132779946470, + 1792922621116791, + ]), + xy2d: FieldElement51::from_limbs([ + 1966196870701923, + 2230044620318636, + 1425982460745905, + 261167817826569, + 46517743394330, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2358877405280588, + 3136759755857592, + 2279106683482647, + 2224911448949389, + 3216151871930471, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1730194207717538, + 431790042319772, + 1831515233279467, + 1372080552768581, + 1074513929381760, + ]), + xy2d: FieldElement51::from_limbs([ + 1450880638731607, + 1019861580989005, + 1229729455116861, + 1174945729836143, + 826083146840706, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1899935429242705, + 1602068751520477, + 940583196550370, + 2334230882739107, + 1540863155745695, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2136688454840028, + 2099509000964294, + 1690800495246475, + 1217643678575476, + 828720645084218, + ]), + xy2d: FieldElement51::from_limbs([ + 765548025667841, + 462473984016099, + 998061409979798, + 546353034089527, + 2212508972466858, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2298375097456408, + 3144370785258318, + 1281983193144089, + 1491520128287375, + 75847005908304, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1801436127943107, + 1734436817907890, + 1268728090345068, + 167003097070711, + 2233597765834956, + ]), + xy2d: FieldElement51::from_limbs([ + 1997562060465113, + 1048700225534011, + 7615603985628, + 1855310849546841, + 2242557647635213, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1161017320376250, + 2744424393854291, + 2169815802355236, + 3228296595417790, + 1770879511019628, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1357044908364776, + 729130645262438, + 1762469072918979, + 1365633616878458, + 181282906404941, + ]), + xy2d: FieldElement51::from_limbs([ + 1080413443139865, + 1155205815510486, + 1848782073549786, + 622566975152580, + 124965574467971, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1184526762066993, + 247622751762817, + 2943928830891604, + 3071818503097743, + 2188697339828084, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2020536369003019, + 202261491735136, + 1053169669150884, + 2056531979272544, + 778165514694311, + ]), + xy2d: FieldElement51::from_limbs([ + 237404399610207, + 1308324858405118, + 1229680749538400, + 720131409105291, + 1958958863624906, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2767383321724075, + 2269456792542436, + 1717918437373988, + 1568052070792483, + 2298775616809171, + ]), + y_minus_x: FieldElement51::from_limbs([ + 281527309158085, + 36970532401524, + 866906920877543, + 2222282602952734, + 1289598729589882, + ]), + xy2d: FieldElement51::from_limbs([ + 1278207464902042, + 494742455008756, + 1262082121427081, + 1577236621659884, + 1888786707293291, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 353042527954210, + 1830056151907359, + 1111731275799225, + 2426760769524072, + 404312815582674, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2064251142068628, + 1666421603389706, + 1419271365315441, + 468767774902855, + 191535130366583, + ]), + xy2d: FieldElement51::from_limbs([ + 1716987058588002, + 1859366439773457, + 1767194234188234, + 64476199777924, + 1117233614485261, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3236091949205521, + 2386938060636506, + 2220652137473166, + 1722843421165029, + 2442282371698157, + ]), + y_minus_x: FieldElement51::from_limbs([ + 298845952651262, + 1166086588952562, + 1179896526238434, + 1347812759398693, + 1412945390096208, + ]), + xy2d: FieldElement51::from_limbs([ + 1143239552672925, + 906436640714209, + 2177000572812152, + 2075299936108548, + 325186347798433, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2972824668060020, + 2936287674948563, + 3625238557779406, + 2193186935276994, + 1387043709851261, + ]), + y_minus_x: FieldElement51::from_limbs([ + 418098668140962, + 715065997721283, + 1471916138376055, + 2168570337288357, + 937812682637044, + ]), + xy2d: FieldElement51::from_limbs([ + 1043584187226485, + 2143395746619356, + 2209558562919611, + 482427979307092, + 847556718384018, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1248731221520740, + 1465200936117687, + 2792603306395388, + 2304778448366139, + 2513234303861356, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1057329623869501, + 620334067429122, + 461700859268034, + 2012481616501857, + 297268569108938, + ]), + xy2d: FieldElement51::from_limbs([ + 1055352180870759, + 1553151421852298, + 1510903185371259, + 1470458349428097, + 1226259419062731, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3744788603986897, + 3042126439258578, + 3441906842094992, + 3641194565844440, + 3872208010289441, + ]), + y_minus_x: FieldElement51::from_limbs([ + 47000654413729, + 1004754424173864, + 1868044813557703, + 173236934059409, + 588771199737015, + ]), + xy2d: FieldElement51::from_limbs([ + 30498470091663, + 1082245510489825, + 576771653181956, + 806509986132686, + 1317634017056939, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2672107869436803, + 3745154677001249, + 2417006535213335, + 4136645508605033, + 2065456951573058, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1115636332012334, + 1854340990964155, + 83792697369514, + 1972177451994021, + 457455116057587, + ]), + xy2d: FieldElement51::from_limbs([ + 1698968457310898, + 1435137169051090, + 1083661677032510, + 938363267483709, + 340103887207182, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1995325341336555, + 911500251774648, + 2415810569088940, + 855378419194761, + 3825401211214090, + ]), + y_minus_x: FieldElement51::from_limbs([ + 241719380661528, + 310028521317150, + 1215881323380194, + 1408214976493624, + 2141142156467363, + ]), + xy2d: FieldElement51::from_limbs([ + 1315157046163473, + 727368447885818, + 1363466668108618, + 1668921439990361, + 1398483384337907, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2326829491984875, + 3267188020145720, + 1849729037055211, + 4191614430138232, + 2696204044080201, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2053597130993710, + 2024431685856332, + 2233550957004860, + 2012407275509545, + 872546993104440, + ]), + xy2d: FieldElement51::from_limbs([ + 1217269667678610, + 599909351968693, + 1390077048548598, + 1471879360694802, + 739586172317596, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3970118453066023, + 1560510726633957, + 3156262694845170, + 1418028351780051, + 2346204163137185, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2132502667405250, + 214379346175414, + 1502748313768060, + 1960071701057800, + 1353971822643138, + ]), + xy2d: FieldElement51::from_limbs([ + 319394212043702, + 2127459436033571, + 717646691535162, + 663366796076914, + 318459064945314, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2657789238608841, + 1960452633787082, + 2919148848086913, + 3744474074452359, + 1451061489880786, + ]), + y_minus_x: FieldElement51::from_limbs([ + 947085906234007, + 323284730494107, + 1485778563977200, + 728576821512394, + 901584347702286, + ]), + xy2d: FieldElement51::from_limbs([ + 1575783124125742, + 2126210792434375, + 1569430791264065, + 1402582372904727, + 1891780248341114, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3090232019245924, + 4249503325136911, + 3270591693593114, + 1662001808174330, + 2330127946643001, + ]), + y_minus_x: FieldElement51::from_limbs([ + 739152638255629, + 2074935399403557, + 505483666745895, + 1611883356514088, + 628654635394878, + ]), + xy2d: FieldElement51::from_limbs([ + 1822054032121349, + 643057948186973, + 7306757352712, + 577249257962099, + 284735863382083, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3618358370049178, + 1448606567552085, + 3730680834630016, + 2417602993041145, + 1115718458123497, + ]), + y_minus_x: FieldElement51::from_limbs([ + 204146226972102, + 1630511199034723, + 2215235214174763, + 174665910283542, + 956127674017216, + ]), + xy2d: FieldElement51::from_limbs([ + 1562934578796716, + 1070893489712745, + 11324610642270, + 958989751581897, + 2172552325473805, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1770564423056008, + 2987323445349813, + 1326060113795288, + 1509650369341127, + 2317692235267932, + ]), + y_minus_x: FieldElement51::from_limbs([ + 623682558650637, + 1337866509471512, + 990313350206649, + 1314236615762469, + 1164772974270275, + ]), + xy2d: FieldElement51::from_limbs([ + 223256821462517, + 723690150104139, + 1000261663630601, + 933280913953265, + 254872671543046, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1969087237026022, + 2876595539132372, + 1335555107635968, + 2069986355593023, + 3963899963027150, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1236103475266979, + 1837885883267218, + 1026072585230455, + 1025865513954973, + 1801964901432134, + ]), + xy2d: FieldElement51::from_limbs([ + 1115241013365517, + 1712251818829143, + 2148864332502771, + 2096001471438138, + 2235017246626125, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3551068012286861, + 2047148477845620, + 2165648650132450, + 1612539282026145, + 2765997725314138, + ]), + y_minus_x: FieldElement51::from_limbs([ + 118352772338543, + 1067608711804704, + 1434796676193498, + 1683240170548391, + 230866769907437, + ]), + xy2d: FieldElement51::from_limbs([ + 1850689576796636, + 1601590730430274, + 1139674615958142, + 1954384401440257, + 76039205311, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1723387471374172, + 3249101280723658, + 2785727448808904, + 2272728458379212, + 1756575222802512, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2146711623855116, + 503278928021499, + 625853062251406, + 1109121378393107, + 1033853809911861, + ]), + xy2d: FieldElement51::from_limbs([ + 571005965509422, + 2005213373292546, + 1016697270349626, + 56607856974274, + 914438579435146, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1346698876211176, + 2076651707527589, + 3336561384795453, + 2517134292513653, + 1068954492309670, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1769967932677654, + 1695893319756416, + 1151863389675920, + 1781042784397689, + 400287774418285, + ]), + xy2d: FieldElement51::from_limbs([ + 1851867764003121, + 403841933237558, + 820549523771987, + 761292590207581, + 1743735048551143, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 410915148140008, + 2107072311871739, + 3256167275561751, + 2351484709082008, + 1180818713503223, + ]), + y_minus_x: FieldElement51::from_limbs([ + 285945406881439, + 648174397347453, + 1098403762631981, + 1366547441102991, + 1505876883139217, + ]), + xy2d: FieldElement51::from_limbs([ + 672095903120153, + 1675918957959872, + 636236529315028, + 1569297300327696, + 2164144194785875, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1902708175321798, + 3287143344600686, + 1178560808893262, + 2552895497743394, + 1280977479761117, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1615357281742403, + 404257611616381, + 2160201349780978, + 1160947379188955, + 1578038619549541, + ]), + xy2d: FieldElement51::from_limbs([ + 2013087639791217, + 822734930507457, + 1785668418619014, + 1668650702946164, + 389450875221715, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2705718263383616, + 2358206633614248, + 2072540975937134, + 308588860670238, + 1304394580755385, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1295082798350326, + 2091844511495996, + 1851348972587817, + 3375039684596, + 789440738712837, + ]), + xy2d: FieldElement51::from_limbs([ + 2083069137186154, + 848523102004566, + 993982213589257, + 1405313299916317, + 1532824818698468, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3747761112537659, + 1397203457344778, + 4026750030752190, + 2391102557240943, + 2318403398028034, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1782411379088302, + 1096724939964781, + 27593390721418, + 542241850291353, + 1540337798439873, + ]), + xy2d: FieldElement51::from_limbs([ + 693543956581437, + 171507720360750, + 1557908942697227, + 1074697073443438, + 1104093109037196, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 345288228393400, + 3351443383432420, + 2386681722088990, + 1740551994106739, + 2500011992985018, + ]), + y_minus_x: FieldElement51::from_limbs([ + 231429562203065, + 1526290236421172, + 2021375064026423, + 1520954495658041, + 806337791525116, + ]), + xy2d: FieldElement51::from_limbs([ + 1079623667189886, + 872403650198613, + 766894200588288, + 2163700860774109, + 2023464507911816, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 854645372543796, + 1936406001954827, + 2403260476226501, + 3077125552956802, + 1554306377287555, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1497138821904622, + 1044820250515590, + 1742593886423484, + 1237204112746837, + 849047450816987, + ]), + xy2d: FieldElement51::from_limbs([ + 667962773375330, + 1897271816877105, + 1399712621683474, + 1143302161683099, + 2081798441209593, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2378947665252234, + 1936114012888109, + 1704424366552046, + 3108474694401560, + 2968403435020606, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1072409664800960, + 2146937497077528, + 1508780108920651, + 935767602384853, + 1112800433544068, + ]), + xy2d: FieldElement51::from_limbs([ + 333549023751292, + 280219272863308, + 2104176666454852, + 1036466864875785, + 536135186520207, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2625466093568366, + 2398257055215356, + 2555916080813104, + 2667888562832962, + 3510376944868638, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1186115062588401, + 2251609796968486, + 1098944457878953, + 1153112761201374, + 1791625503417267, + ]), + xy2d: FieldElement51::from_limbs([ + 1870078460219737, + 2129630962183380, + 852283639691142, + 292865602592851, + 401904317342226, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1361070124828016, + 815664541425524, + 3278598711049919, + 1951790935390646, + 2807674705520038, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1546301003424277, + 459094500062839, + 1097668518375311, + 1780297770129643, + 720763293687608, + ]), + xy2d: FieldElement51::from_limbs([ + 1212405311403990, + 1536693382542438, + 61028431067459, + 1863929423417129, + 1223219538638038, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1294303766540260, + 3435357279640341, + 3134071170918340, + 2315654383110622, + 2213283684565086, + ]), + y_minus_x: FieldElement51::from_limbs([ + 339050984211414, + 601386726509773, + 413735232134068, + 966191255137228, + 1839475899458159, + ]), + xy2d: FieldElement51::from_limbs([ + 235605972169408, + 2174055643032978, + 1538335001838863, + 1281866796917192, + 1815940222628465, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1632352921721536, + 1833328609514701, + 2092779091951987, + 4175756015558474, + 2210068022482918, + ]), + y_minus_x: FieldElement51::from_limbs([ + 35271216625062, + 1712350667021807, + 983664255668860, + 98571260373038, + 1232645608559836, + ]), + xy2d: FieldElement51::from_limbs([ + 1998172393429622, + 1798947921427073, + 784387737563581, + 1589352214827263, + 1589861734168180, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1733739258725305, + 2283515530744786, + 2453769758904107, + 3243892858242237, + 1194308773174555, + ]), + y_minus_x: FieldElement51::from_limbs([ + 846415389605137, + 746163495539180, + 829658752826080, + 592067705956946, + 957242537821393, + ]), + xy2d: FieldElement51::from_limbs([ + 1758148849754419, + 619249044817679, + 168089007997045, + 1371497636330523, + 1867101418880350, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2578433797894864, + 2513559319756263, + 1700682323676192, + 1577907266349064, + 3469447477068264, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1714182387328607, + 1477856482074168, + 574895689942184, + 2159118410227270, + 1555532449716575, + ]), + xy2d: FieldElement51::from_limbs([ + 853828206885131, + 998498946036955, + 1835887550391235, + 207627336608048, + 258363815956050, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2392941288336925, + 3488528558590503, + 2894901233585134, + 1646615130509172, + 1208239602291765, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1501663228068911, + 1354879465566912, + 1444432675498247, + 897812463852601, + 855062598754348, + ]), + xy2d: FieldElement51::from_limbs([ + 714380763546606, + 1032824444965790, + 1774073483745338, + 1063840874947367, + 1738680636537158, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1640635546696233, + 2884968766877360, + 2212651044092395, + 2282390772269100, + 2620315074574625, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1171650314802029, + 1567085444565577, + 1453660792008405, + 757914533009261, + 1619511342778196, + ]), + xy2d: FieldElement51::from_limbs([ + 420958967093237, + 971103481109486, + 2169549185607107, + 1301191633558497, + 1661514101014240, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3158923465503550, + 1332556122804145, + 4075855067109735, + 3619414031128206, + 1982558335973171, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1121533090144639, + 1021251337022187, + 110469995947421, + 1511059774758394, + 2110035908131662, + ]), + xy2d: FieldElement51::from_limbs([ + 303213233384524, + 2061932261128138, + 352862124777736, + 40828818670255, + 249879468482660, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 856559257852200, + 2760317478634258, + 3629993581580163, + 3975258940632376, + 1962275756614520, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1445691340537320, + 40614383122127, + 402104303144865, + 485134269878232, + 1659439323587426, + ]), + xy2d: FieldElement51::from_limbs([ + 20057458979482, + 1183363722525800, + 2140003847237215, + 2053873950687614, + 2112017736174909, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2228654250927986, + 3735391177100515, + 1368661293910955, + 3328311098862539, + 526650682059607, + ]), + y_minus_x: FieldElement51::from_limbs([ + 709481497028540, + 531682216165724, + 316963769431931, + 1814315888453765, + 258560242424104, + ]), + xy2d: FieldElement51::from_limbs([ + 1053447823660455, + 1955135194248683, + 1010900954918985, + 1182614026976701, + 1240051576966610, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1957943897155478, + 1788667368028035, + 2389492723714354, + 2252839333292309, + 3078204576998275, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1848942433095597, + 1582009882530495, + 1849292741020143, + 1068498323302788, + 2001402229799484, + ]), + xy2d: FieldElement51::from_limbs([ + 1528282417624269, + 2142492439828191, + 2179662545816034, + 362568973150328, + 1591374675250271, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2411826493119617, + 2484141002903963, + 2149181472355544, + 598041771119831, + 2435658815595421, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2013278155187349, + 662660471354454, + 793981225706267, + 411706605985744, + 804490933124791, + ]), + xy2d: FieldElement51::from_limbs([ + 2051892037280204, + 488391251096321, + 2230187337030708, + 930221970662692, + 679002758255210, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1530723630438670, + 875873929577927, + 2593359947955236, + 2701702933216000, + 1055551308214178, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1461835919309432, + 1955256480136428, + 180866187813063, + 1551979252664528, + 557743861963950, + ]), + xy2d: FieldElement51::from_limbs([ + 359179641731115, + 1324915145732949, + 902828372691474, + 294254275669987, + 1887036027752957, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 4295071423139571, + 2038225437857463, + 1317528426475850, + 1398989128982787, + 2027639881006861, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2072902725256516, + 312132452743412, + 309930885642209, + 996244312618453, + 1590501300352303, + ]), + xy2d: FieldElement51::from_limbs([ + 1397254305160710, + 695734355138021, + 2233992044438756, + 1776180593969996, + 1085588199351115, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2692366865016258, + 2506694600041928, + 2745669038615469, + 1556322069683365, + 3819256354004466, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1950722461391320, + 1907845598854797, + 1822757481635527, + 2121567704750244, + 73811931471221, + ]), + xy2d: FieldElement51::from_limbs([ + 387139307395758, + 2058036430315676, + 1220915649965325, + 1794832055328951, + 1230009312169328, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1765973779329498, + 2911143873132225, + 2271621715291913, + 3553728154996461, + 3368065817761132, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1127572801181483, + 1224743760571696, + 1276219889847274, + 1529738721702581, + 1589819666871853, + ]), + xy2d: FieldElement51::from_limbs([ + 2181229378964934, + 2190885205260020, + 1511536077659137, + 1246504208580490, + 668883326494241, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2689666469258543, + 2920826224880015, + 2333696811665585, + 523874406393177, + 2496851874620484, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1975438052228868, + 1071801519999806, + 594652299224319, + 1877697652668809, + 1489635366987285, + ]), + xy2d: FieldElement51::from_limbs([ + 958592545673770, + 233048016518599, + 851568750216589, + 567703851596087, + 1740300006094761, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2014540178270324, + 192672779514432, + 2465676996326778, + 2194819933853410, + 1716422829364835, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1540769606609725, + 2148289943846077, + 1597804156127445, + 1230603716683868, + 815423458809453, + ]), + xy2d: FieldElement51::from_limbs([ + 1738560251245018, + 1779576754536888, + 1783765347671392, + 1880170990446751, + 1088225159617541, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2911103727614740, + 1956447718227572, + 1830568515922666, + 3092868863429656, + 1669607124206367, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1143465490433355, + 1532194726196059, + 1093276745494697, + 481041706116088, + 2121405433561163, + ]), + xy2d: FieldElement51::from_limbs([ + 1686424298744462, + 1451806974487153, + 266296068846582, + 1834686947542675, + 1720762336132256, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3141016840074207, + 3295090436969907, + 3107924901237156, + 1669272323124635, + 1603340330827879, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1206396181488998, + 333158148435054, + 1402633492821422, + 1120091191722026, + 1945474114550509, + ]), + xy2d: FieldElement51::from_limbs([ + 766720088232571, + 1512222781191002, + 1189719893490790, + 2091302129467914, + 2141418006894941, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2671463460991841, + 1998875112167986, + 3678399683938955, + 3406728169064757, + 2738338345823434, + ]), + y_minus_x: FieldElement51::from_limbs([ + 938160078005954, + 1421776319053174, + 1941643234741774, + 180002183320818, + 1414380336750546, + ]), + xy2d: FieldElement51::from_limbs([ + 398001940109652, + 1577721237663248, + 1012748649830402, + 1540516006905144, + 1011684812884559, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1653276489969611, + 2257881638852872, + 1921777941170835, + 1604139841794531, + 3113010867325889, + ]), + y_minus_x: FieldElement51::from_limbs([ + 996661541407379, + 1455877387952927, + 744312806857277, + 139213896196746, + 1000282908547789, + ]), + xy2d: FieldElement51::from_limbs([ + 1450817495603008, + 1476865707053229, + 1030490562252053, + 620966950353376, + 1744760161539058, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2811528223687828, + 2288856475326432, + 2038622963352005, + 1637244893271723, + 3278365165924196, + ]), + y_minus_x: FieldElement51::from_limbs([ + 962165956135846, + 1116599660248791, + 182090178006815, + 1455605467021751, + 196053588803284, + ]), + xy2d: FieldElement51::from_limbs([ + 796863823080135, + 1897365583584155, + 420466939481601, + 2165972651724672, + 932177357788289, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 877047233620613, + 1375632631944375, + 2895573425567369, + 2911822552533124, + 2271153746017078, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2216943882299338, + 394841323190322, + 2222656898319671, + 558186553950529, + 1077236877025190, + ]), + xy2d: FieldElement51::from_limbs([ + 801118384953213, + 1914330175515892, + 574541023311511, + 1471123787903705, + 1526158900256288, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3201417702772463, + 2207116611267330, + 3164719852826535, + 2752958352884036, + 2314162374456719, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1474518386765335, + 1760793622169197, + 1157399790472736, + 1622864308058898, + 165428294422792, + ]), + xy2d: FieldElement51::from_limbs([ + 1961673048027128, + 102619413083113, + 1051982726768458, + 1603657989805485, + 1941613251499678, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1401939116319247, + 2587106153588320, + 2323846009771033, + 862423201496005, + 3102318568216632, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1234706593321979, + 1083343891215917, + 898273974314935, + 1640859118399498, + 157578398571149, + ]), + xy2d: FieldElement51::from_limbs([ + 1143483057726416, + 1992614991758919, + 674268662140796, + 1773370048077526, + 674318359920189, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1835401379538542, + 173900035308392, + 818247630716732, + 4013900225838034, + 1021506399448290, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1506632088156630, + 2127481795522179, + 513812919490255, + 140643715928370, + 442476620300318, + ]), + xy2d: FieldElement51::from_limbs([ + 2056683376856736, + 219094741662735, + 2193541883188309, + 1841182310235800, + 556477468664293, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3566819241596075, + 1049075855992602, + 4318372866671791, + 2518704280870781, + 2040482348591519, + ]), + y_minus_x: FieldElement51::from_limbs([ + 94096246544434, + 922482381166992, + 24517828745563, + 2139430508542503, + 2097139044231004, + ]), + xy2d: FieldElement51::from_limbs([ + 537697207950515, + 1399352016347350, + 1563663552106345, + 2148749520888918, + 549922092988516, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1747985413252415, + 680511052635695, + 1809559829982725, + 2846074064615302, + 2453472984431229, + ]), + y_minus_x: FieldElement51::from_limbs([ + 323583936109569, + 1973572998577657, + 1192219029966558, + 79354804385273, + 1374043025560347, + ]), + xy2d: FieldElement51::from_limbs([ + 213277331329947, + 416202017849623, + 1950535221091783, + 1313441578103244, + 2171386783823658, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2440888617915079, + 993969372859109, + 3147669935222235, + 3799101348983503, + 1477373024911349, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1620578418245010, + 541035331188469, + 2235785724453865, + 2154865809088198, + 1974627268751826, + ]), + xy2d: FieldElement51::from_limbs([ + 1346805451740245, + 1350981335690626, + 942744349501813, + 2155094562545502, + 1012483751693409, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2107080134091762, + 1132567062788208, + 1824935377687210, + 769194804343737, + 1857941799971888, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1074666112436467, + 249279386739593, + 1174337926625354, + 1559013532006480, + 1472287775519121, + ]), + xy2d: FieldElement51::from_limbs([ + 1872620123779532, + 1892932666768992, + 1921559078394978, + 1270573311796160, + 1438913646755037, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3089190001333428, + 3264053113908846, + 989780015893986, + 1351393287739814, + 2580427560230798, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1028328827183114, + 1711043289969857, + 1350832470374933, + 1923164689604327, + 1495656368846911, + ]), + xy2d: FieldElement51::from_limbs([ + 1900828492104143, + 430212361082163, + 687437570852799, + 832514536673512, + 1685641495940794, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3094432661621646, + 605670026766215, + 290836444839585, + 2415010588577604, + 2213815011799644, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1176336383453996, + 1725477294339771, + 12700622672454, + 678015708818208, + 162724078519879, + ]), + xy2d: FieldElement51::from_limbs([ + 1448049969043497, + 1789411762943521, + 385587766217753, + 90201620913498, + 832999441066823, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2767886146978542, + 2240508292484615, + 3603469341851756, + 3475055379001735, + 3002035638112385, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1263624896582495, + 1102602401673328, + 526302183714372, + 2152015839128799, + 1483839308490010, + ]), + xy2d: FieldElement51::from_limbs([ + 442991718646863, + 1599275157036458, + 1925389027579192, + 899514691371390, + 350263251085160, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1689713572022124, + 2845654372939621, + 3229894858477217, + 1985127338729498, + 3927868934032873, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1557207018622683, + 340631692799603, + 1477725909476187, + 614735951619419, + 2033237123746766, + ]), + xy2d: FieldElement51::from_limbs([ + 968764929340557, + 1225534776710944, + 662967304013036, + 1155521416178595, + 791142883466590, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1487081286167458, + 3244839255500182, + 1792378982844639, + 2950452258685122, + 2153908693179753, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1123181311102823, + 685575944875442, + 507605465509927, + 1412590462117473, + 568017325228626, + ]), + xy2d: FieldElement51::from_limbs([ + 560258797465417, + 2193971151466401, + 1824086900849026, + 579056363542056, + 1690063960036441, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1918407319222397, + 2605567366745211, + 1930426334528098, + 1564816146005724, + 4113142195393344, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2131325168777276, + 1176636658428908, + 1756922641512981, + 1390243617176012, + 1966325177038383, + ]), + xy2d: FieldElement51::from_limbs([ + 2063958120364491, + 2140267332393533, + 699896251574968, + 273268351312140, + 375580724713232, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2024297515263178, + 2668759143407935, + 3330814048702549, + 2423412039258430, + 1031677520051052, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2033900009388450, + 1744902869870788, + 2190580087917640, + 1949474984254121, + 231049754293748, + ]), + xy2d: FieldElement51::from_limbs([ + 343868674606581, + 550155864008088, + 1450580864229630, + 481603765195050, + 896972360018042, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2151139328380127, + 2566545695770176, + 2311556639460451, + 1676664391494650, + 2048348075599360, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1528930066340597, + 1605003907059576, + 1055061081337675, + 1458319101947665, + 1234195845213142, + ]), + xy2d: FieldElement51::from_limbs([ + 830430507734812, + 1780282976102377, + 1425386760709037, + 362399353095425, + 2168861579799910, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3407562046415562, + 980662895504005, + 2053766700883521, + 2742766027762854, + 2762205690726604, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1683750316716132, + 652278688286128, + 1221798761193539, + 1897360681476669, + 319658166027343, + ]), + xy2d: FieldElement51::from_limbs([ + 618808732869972, + 72755186759744, + 2060379135624181, + 1730731526741822, + 48862757828238, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3714971784278753, + 3394840525452699, + 614590986558882, + 1409210575145591, + 1882816996436803, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2230133264691131, + 563950955091024, + 2042915975426398, + 827314356293472, + 672028980152815, + ]), + xy2d: FieldElement51::from_limbs([ + 264204366029760, + 1654686424479449, + 2185050199932931, + 2207056159091748, + 506015669043634, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1784446333136550, + 1973746527984364, + 334856327359575, + 3408569589569858, + 3275749938360725, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2065270940578383, + 31477096270353, + 306421879113491, + 181958643936686, + 1907105536686083, + ]), + xy2d: FieldElement51::from_limbs([ + 1496516440779464, + 1748485652986458, + 872778352227340, + 818358834654919, + 97932669284220, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2723435829455580, + 2924255216478824, + 1804995246884102, + 1842309243470804, + 3753662318666930, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1013216974933691, + 538921919682598, + 1915776722521558, + 1742822441583877, + 1886550687916656, + ]), + xy2d: FieldElement51::from_limbs([ + 2094270000643336, + 303971879192276, + 40801275554748, + 649448917027930, + 1818544418535447, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2241737709499146, + 549397817447461, + 838180519319392, + 1725686958520781, + 3957438894582995, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1216074541925116, + 50120933933509, + 1565829004133810, + 721728156134580, + 349206064666188, + ]), + xy2d: FieldElement51::from_limbs([ + 948617110470858, + 346222547451945, + 1126511960599975, + 1759386906004538, + 493053284802266, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1454933046815146, + 3126495827951610, + 1467170975468587, + 1432316382418897, + 2111710746366763, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2105387117364450, + 1996463405126433, + 1303008614294500, + 851908115948209, + 1353742049788635, + ]), + xy2d: FieldElement51::from_limbs([ + 750300956351719, + 1487736556065813, + 15158817002104, + 1511998221598392, + 971739901354129, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1874648163531674, + 2124487685930551, + 1810030029384882, + 918400043048335, + 2838148440985898, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1235084464747900, + 1166111146432082, + 1745394857881591, + 1405516473883040, + 4463504151617, + ]), + xy2d: FieldElement51::from_limbs([ + 1663810156463827, + 327797390285791, + 1341846161759410, + 1964121122800605, + 1747470312055380, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 660005247548214, + 2071860029952887, + 3610548013635355, + 911703252219106, + 3266179736709079, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2206641276178231, + 1690587809721504, + 1600173622825126, + 2156096097634421, + 1106822408548216, + ]), + xy2d: FieldElement51::from_limbs([ + 1344788193552206, + 1949552134239140, + 1735915881729557, + 675891104100469, + 1834220014427292, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1920949492387945, + 2410685102072778, + 2322108077349280, + 2877838278583064, + 3719881539786256, + ]), + y_minus_x: FieldElement51::from_limbs([ + 622221042073383, + 1210146474039168, + 1742246422343683, + 1403839361379025, + 417189490895736, + ]), + xy2d: FieldElement51::from_limbs([ + 22727256592983, + 168471543384997, + 1324340989803650, + 1839310709638189, + 504999476432775, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3565040332441556, + 1721896294296941, + 2304063388272514, + 2065069734239231, + 3056710287109878, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1337466662091884, + 1287645354669772, + 2018019646776184, + 652181229374245, + 898011753211715, + ]), + xy2d: FieldElement51::from_limbs([ + 1969792547910734, + 779969968247557, + 2011350094423418, + 1823964252907487, + 1058949448296945, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2459143550747250, + 1118176942430252, + 3010694408233412, + 806764629546265, + 1157700123092949, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1273565321399022, + 1638509681964574, + 759235866488935, + 666015124346707, + 897983460943405, + ]), + xy2d: FieldElement51::from_limbs([ + 1717263794012298, + 1059601762860786, + 1837819172257618, + 1054130665797229, + 680893204263559, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2237039662793603, + 2249022333361206, + 2058613546633703, + 2401253908530527, + 2215176649164581, + ]), + y_minus_x: FieldElement51::from_limbs([ + 79472182719605, + 1851130257050174, + 1825744808933107, + 821667333481068, + 781795293511946, + ]), + xy2d: FieldElement51::from_limbs([ + 755822026485370, + 152464789723500, + 1178207602290608, + 410307889503239, + 156581253571278, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3669985309815545, + 2736319981413860, + 3898537095128197, + 3653287498355512, + 1349185550126960, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1495380034400429, + 325049476417173, + 46346894893933, + 1553408840354856, + 828980101835683, + ]), + xy2d: FieldElement51::from_limbs([ + 1280337889310282, + 2070832742866672, + 1640940617225222, + 2098284908289951, + 450929509534434, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2659503167684029, + 2378371955168899, + 2537839641198868, + 1999255076709337, + 2030511179441770, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1254958221100483, + 1153235960999843, + 942907704968834, + 637105404087392, + 1149293270147267, + ]), + xy2d: FieldElement51::from_limbs([ + 894249020470196, + 400291701616810, + 406878712230981, + 1599128793487393, + 1145868722604026, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3749755063888563, + 2361916158338507, + 1128535642171975, + 1900106496009660, + 2381592531146157, + ]), + y_minus_x: FieldElement51::from_limbs([ + 452487513298665, + 1352120549024569, + 1173495883910956, + 1999111705922009, + 367328130454226, + ]), + xy2d: FieldElement51::from_limbs([ + 1717539401269642, + 1475188995688487, + 891921989653942, + 836824441505699, + 1885988485608364, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3493583935107776, + 2439136865632830, + 3370281625921440, + 2680547565621609, + 2282158712612572, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2022432361201842, + 1088816090685051, + 1977843398539868, + 1854834215890724, + 564238862029357, + ]), + xy2d: FieldElement51::from_limbs([ + 938868489100585, + 1100285072929025, + 1017806255688848, + 1957262154788833, + 152787950560442, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3119119231364171, + 2872271776627789, + 2477832016990963, + 2593801257642876, + 1761675818237335, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1295072362439987, + 931227904689414, + 1355731432641687, + 922235735834035, + 892227229410209, + ]), + xy2d: FieldElement51::from_limbs([ + 1680989767906154, + 535362787031440, + 2136691276706570, + 1942228485381244, + 1267350086882274, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2617818047455756, + 2684460443440843, + 2378209521329782, + 1973842949591661, + 2897427157127624, + ]), + y_minus_x: FieldElement51::from_limbs([ + 535509430575217, + 546885533737322, + 1524675609547799, + 2138095752851703, + 1260738089896827, + ]), + xy2d: FieldElement51::from_limbs([ + 1159906385590467, + 2198530004321610, + 714559485023225, + 81880727882151, + 1484020820037082, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1377485731340769, + 2046328105512000, + 1802058637158797, + 2313945950453421, + 1356993908853900, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2013612215646735, + 1830770575920375, + 536135310219832, + 609272325580394, + 270684344495013, + ]), + xy2d: FieldElement51::from_limbs([ + 1237542585982777, + 2228682050256790, + 1385281931622824, + 593183794882890, + 493654978552689, + ]), + }, + ]), + LookupTable([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2299141301692989, + 1891414891220256, + 983894663308928, + 2427961581972066, + 3378060928864955, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1694030170963455, + 502038567066200, + 1691160065225467, + 949628319562187, + 275110186693066, + ]), + xy2d: FieldElement51::from_limbs([ + 1124515748676336, + 1661673816593408, + 1499640319059718, + 1584929449166988, + 558148594103306, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1784525599998356, + 1619698033617383, + 2097300287550715, + 2510065271789004, + 1905684794832757, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1288941072872766, + 931787902039402, + 190731008859042, + 2006859954667190, + 1005931482221702, + ]), + xy2d: FieldElement51::from_limbs([ + 1465551264822703, + 152905080555927, + 680334307368453, + 173227184634745, + 666407097159852, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2111017076203943, + 3630560299479595, + 1248583954016455, + 3604089008549670, + 1895180776543895, + ]), + y_minus_x: FieldElement51::from_limbs([ + 171348223915638, + 662766099800389, + 462338943760497, + 466917763340314, + 656911292869115, + ]), + xy2d: FieldElement51::from_limbs([ + 488623681976577, + 866497561541722, + 1708105560937768, + 1673781214218839, + 1506146329818807, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2412225278142205, + 950394373239688, + 2682296937026182, + 711676555398831, + 320964687779005, + ]), + y_minus_x: FieldElement51::from_limbs([ + 988979367990485, + 1359729327576302, + 1301834257246029, + 294141160829308, + 29348272277475, + ]), + xy2d: FieldElement51::from_limbs([ + 1434382743317910, + 100082049942065, + 221102347892623, + 186982837860588, + 1305765053501834, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2205916462268190, + 2751663643476068, + 961960554686615, + 2409862576442233, + 1841471168298304, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1191737341426592, + 1847042034978363, + 1382213545049056, + 1039952395710448, + 788812858896859, + ]), + xy2d: FieldElement51::from_limbs([ + 1346965964571152, + 1291881610839830, + 2142916164336056, + 786821641205979, + 1571709146321039, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 787164375951248, + 2454669019058437, + 3608390234717387, + 1431233331032509, + 786341368775957, + ]), + y_minus_x: FieldElement51::from_limbs([ + 492448143532951, + 304105152670757, + 1761767168301056, + 233782684697790, + 1981295323106089, + ]), + xy2d: FieldElement51::from_limbs([ + 665807507761866, + 1343384868355425, + 895831046139653, + 439338948736892, + 1986828765695105, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3007896024559801, + 1721699973539148, + 2510565115413133, + 1390588532210644, + 1212530909934781, + ]), + y_minus_x: FieldElement51::from_limbs([ + 852891097972275, + 1816988871354562, + 1543772755726524, + 1174710635522444, + 202129090724628, + ]), + xy2d: FieldElement51::from_limbs([ + 1205281565824323, + 22430498399418, + 992947814485516, + 1392458699738672, + 688441466734558, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3302427242100220, + 1955849529137134, + 2171162376368357, + 2343545681983462, + 447733118757825, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1287181461435438, + 622722465530711, + 880952150571872, + 741035693459198, + 311565274989772, + ]), + xy2d: FieldElement51::from_limbs([ + 1003649078149734, + 545233927396469, + 1849786171789880, + 1318943684880434, + 280345687170552, + ]), + }, + ]), +]); + +/// Odd multiples of the basepoint `[B, 3B, 5B, 7B, 9B, 11B, 13B, 15B, ..., 127B]`. +#[cfg(feature = "precomputed-tables")] +#[allow(dead_code)] +pub(crate) const AFFINE_ODD_MULTIPLES_OF_BASEPOINT: NafLookupTable8 = + NafLookupTable8([ + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3540182452943730, + 2497478415033846, + 2521227595762870, + 1462984067271729, + 2389212253076811, + ]), + y_minus_x: FieldElement51::from_limbs([ + 62697248952638, + 204681361388450, + 631292143396476, + 338455783676468, + 1213667448819585, + ]), + xy2d: FieldElement51::from_limbs([ + 301289933810280, + 1259582250014073, + 1422107436869536, + 796239922652654, + 1953934009299142, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1601611775252272, + 1720807796594148, + 1132070835939856, + 3512254832574799, + 2147779492816910, + ]), + y_minus_x: FieldElement51::from_limbs([ + 316559037616741, + 2177824224946892, + 1459442586438991, + 1461528397712656, + 751590696113597, + ]), + xy2d: FieldElement51::from_limbs([ + 1850748884277385, + 1200145853858453, + 1068094770532492, + 672251375690438, + 1586055907191707, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 769950342298400, + 2384754244604994, + 3095885746880802, + 3225892188161580, + 2977876099231263, + ]), + y_minus_x: FieldElement51::from_limbs([ + 425251763115706, + 608463272472562, + 442562545713235, + 837766094556764, + 374555092627893, + ]), + xy2d: FieldElement51::from_limbs([ + 1086255230780037, + 274979815921559, + 1960002765731872, + 929474102396301, + 1190409889297339, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2916800678241215, + 2065379846933858, + 2622030924071124, + 2602788184473875, + 1233371373142984, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2019367628972465, + 676711900706637, + 110710997811333, + 1108646842542025, + 517791959672113, + ]), + xy2d: FieldElement51::from_limbs([ + 965130719900578, + 247011430587952, + 526356006571389, + 91986625355052, + 2157223321444601, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1802695059464988, + 1664899123557221, + 2845359304426105, + 2160434469266658, + 3179370264440279, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1725674970513508, + 1933645953859181, + 1542344539275782, + 1767788773573747, + 1297447965928905, + ]), + xy2d: FieldElement51::from_limbs([ + 1381809363726107, + 1430341051343062, + 2061843536018959, + 1551778050872521, + 2036394857967624, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 4222693909998302, + 2779866139518454, + 1619374932191226, + 2207306624415883, + 1169170329061080, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2070390218572616, + 1458919061857835, + 624171843017421, + 1055332792707765, + 433987520732508, + ]), + xy2d: FieldElement51::from_limbs([ + 893653801273833, + 1168026499324677, + 1242553501121234, + 1306366254304474, + 1086752658510815, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2465253816303469, + 3191571337672685, + 1159882208056013, + 2569188183312765, + 621213314200686, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1971678598905747, + 338026507889165, + 762398079972271, + 655096486107477, + 42299032696322, + ]), + xy2d: FieldElement51::from_limbs([ + 177130678690680, + 1754759263300204, + 1864311296286618, + 1180675631479880, + 1292726903152791, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1913163449625248, + 2712579013977241, + 2193883288642313, + 1008900146920800, + 1721983679009502, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1070401523076875, + 1272492007800961, + 1910153608563310, + 2075579521696771, + 1191169788841221, + ]), + xy2d: FieldElement51::from_limbs([ + 692896803108118, + 500174642072499, + 2068223309439677, + 1162190621851337, + 1426986007309901, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1819621230288238, + 2735700366193240, + 1755134670739586, + 3080648199451191, + 4172807995775876, + ]), + y_minus_x: FieldElement51::from_limbs([ + 992069868904071, + 799011518185730, + 1777586403832768, + 1134820506145684, + 1999461475558530, + ]), + xy2d: FieldElement51::from_limbs([ + 425204543703124, + 2040469794090382, + 1651690622153809, + 1500530168597569, + 1253908377065966, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2105824306960939, + 1387520302709358, + 3633176580451016, + 2211816663841753, + 1629085891776489, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1485201376284999, + 1022406647424656, + 504181009209019, + 962621520820995, + 590876713147230, + ]), + xy2d: FieldElement51::from_limbs([ + 265873406365287, + 1192742653492898, + 88553098803050, + 525037770869640, + 1266933811251234, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3552316659826612, + 1254279525791875, + 1609927932077699, + 3578654071679972, + 3750681296069893, + ]), + y_minus_x: FieldElement51::from_limbs([ + 37186803519861, + 1404297334376301, + 578519728836650, + 1740727951192592, + 2095534282477028, + ]), + xy2d: FieldElement51::from_limbs([ + 833234263154399, + 2023862470013762, + 1854137933982069, + 853924318090959, + 1589812702805850, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3679150557957763, + 1319179453661745, + 497496853611112, + 2665464286942351, + 1208137952365560, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1654513078530905, + 907489875842908, + 126098711296368, + 1726320004173677, + 28269495058173, + ]), + xy2d: FieldElement51::from_limbs([ + 114436686957443, + 532739313025996, + 115428841215897, + 2191499400074366, + 370280402676434, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1111146849833253, + 2016430049079759, + 1860522747477948, + 3537164738290194, + 4137142824844184, + ]), + y_minus_x: FieldElement51::from_limbs([ + 429069864577128, + 975327637149449, + 237881983565075, + 1654761232378630, + 2122527599091807, + ]), + xy2d: FieldElement51::from_limbs([ + 2093793463548278, + 754827233241879, + 1420389751719629, + 1829952782588138, + 2011865756773717, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 676293365438898, + 2850296017886344, + 1205350322490195, + 2763699392265669, + 2133931188538142, + ]), + y_minus_x: FieldElement51::from_limbs([ + 48340340349120, + 1299261101494832, + 1137329686775218, + 1534848106674340, + 1351662218216799, + ]), + xy2d: FieldElement51::from_limbs([ + 1904520614137939, + 1590301001714014, + 215781420985270, + 2043534301034629, + 1970888949300424, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2365217962409710, + 2061307169694064, + 1887478590157603, + 2169639621284316, + 2373810867477200, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1020052624656948, + 1260412094216707, + 366721640607121, + 585331442306596, + 345876457758061, + ]), + xy2d: FieldElement51::from_limbs([ + 975390299880933, + 1066555195234642, + 12651997758352, + 1184252205433068, + 1058378155074223, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1431537716602643, + 2024827957433813, + 3746434518400495, + 1087794891033550, + 2156817571680455, + ]), + y_minus_x: FieldElement51::from_limbs([ + 929288033346881, + 255179964546973, + 711057989588035, + 208899572612840, + 185348357387383, + ]), + xy2d: FieldElement51::from_limbs([ + 823689746424808, + 47266130989546, + 209403309368097, + 1100966895202707, + 710792075292719, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2311213117823762, + 3296668540922318, + 2004276520649823, + 1861500579441125, + 3148029033359833, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1563693677475261, + 1843782073741194, + 1950700654453170, + 911540858113949, + 2085151496302359, + ]), + xy2d: FieldElement51::from_limbs([ + 1427880892005482, + 106216431121745, + 42608394782284, + 1217295886989793, + 1514235272796882, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3544335535746750, + 2367994491347456, + 2567261456502612, + 1854058085060971, + 2263545563461076, + ]), + y_minus_x: FieldElement51::from_limbs([ + 787426011300053, + 2105981035769060, + 1130476291127206, + 1748659348100075, + 53470983013756, + ]), + xy2d: FieldElement51::from_limbs([ + 553548273865386, + 5927805718390, + 65184587381926, + 633576679686953, + 576048559439973, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 993787326657446, + 3868807161609258, + 1615796046728943, + 2514644292681953, + 2059021068660907, + ]), + y_minus_x: FieldElement51::from_limbs([ + 251010270518880, + 1681684095763484, + 1521949356387564, + 431593457045116, + 1855308922422910, + ]), + xy2d: FieldElement51::from_limbs([ + 618490909691959, + 1257497595618257, + 202952467594088, + 35577762721238, + 1494883566841973, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1673474571932262, + 2409784519770613, + 2636095316260487, + 2761112584601925, + 3333713288149876, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1600640202645197, + 1019569075331823, + 1041916487915822, + 1680448171313267, + 2126903137527901, + ]), + xy2d: FieldElement51::from_limbs([ + 894964745143659, + 106116880092678, + 1009869382959477, + 317866368542032, + 1986983122763912, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1765281781276487, + 2863247187455184, + 2589075472439062, + 1386435905543054, + 2182338478845320, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1144730936996693, + 2213315231278180, + 1489676672185125, + 665039429138074, + 1131283313040268, + ]), + xy2d: FieldElement51::from_limbs([ + 2004734176670602, + 1738311085075235, + 418866995976618, + 1050782508034394, + 577747313404652, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2185209688340293, + 1309276076461009, + 2514740038571278, + 3994889904012999, + 3018098826231021, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1405936970888515, + 1754621155316654, + 1211862168554999, + 1813045702919083, + 997853418197172, + ]), + xy2d: FieldElement51::from_limbs([ + 82037622045021, + 1646398333621944, + 613095452763466, + 1312329542583705, + 81014679202721, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2389287991277873, + 403851022333257, + 1597473361477193, + 2953351602509212, + 2135174663049062, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1826548187201150, + 302299893734126, + 1475477168615781, + 842617616347376, + 1438600873676130, + ]), + xy2d: FieldElement51::from_limbs([ + 663049852468609, + 1649295727846569, + 1048009692742781, + 628866177992421, + 1914360327429204, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1795645928096646, + 306878154408959, + 2924901319092394, + 2801261341654799, + 1653782432983523, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2077597317438627, + 212642017882064, + 674844477518888, + 875487498687554, + 2060550250171182, + ]), + xy2d: FieldElement51::from_limbs([ + 1420448018683809, + 1032663994771382, + 1341927003385267, + 1340360916546159, + 1988547473895228, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1082660122598844, + 2545055705583789, + 3888919679589007, + 1670283344995811, + 3403239134794618, + ]), + y_minus_x: FieldElement51::from_limbs([ + 90430593339788, + 1838338032241275, + 571293238480915, + 1639938867416883, + 257378872001111, + ]), + xy2d: FieldElement51::from_limbs([ + 1528535658865034, + 1516636853043960, + 787000569996728, + 1464531394704506, + 1684822625133795, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 811329918113934, + 2783463529007378, + 1769095754634835, + 2970819621866866, + 881037178164325, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1784566501964517, + 433890943689325, + 1186055625589419, + 1496077405487512, + 1731807117886548, + ]), + xy2d: FieldElement51::from_limbs([ + 424909811816304, + 1355993963741797, + 409606483251841, + 455665350637068, + 1617009023642808, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2478728492077816, + 2780289048655501, + 2328687177473769, + 4107341333582032, + 1316147724308250, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1617420574301156, + 1741273341070467, + 667135503486508, + 2100436564640123, + 1032223920000865, + ]), + xy2d: FieldElement51::from_limbs([ + 1753947659404033, + 247279202390193, + 1819288880178945, + 737334285670249, + 1037873664856104, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1762568490530034, + 673742465299012, + 2054571050635888, + 2040165159255111, + 3040123733327257, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1627187989987422, + 1686331580821752, + 1309895873498183, + 719718719104086, + 300063199808722, + ]), + xy2d: FieldElement51::from_limbs([ + 238176707016164, + 1440454788877048, + 203336037573144, + 1437789888677072, + 101522256664211, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1895216760098480, + 1934324337975022, + 3677350688973167, + 2536415965456176, + 714678003308640, + ]), + y_minus_x: FieldElement51::from_limbs([ + 508185358728815, + 1691320535341855, + 2168887448239256, + 1035124393070661, + 1936603999698584, + ]), + xy2d: FieldElement51::from_limbs([ + 390562831571647, + 1390223890708972, + 1383183990676371, + 435998174196410, + 1882086414390730, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3747620842612921, + 2081794785291195, + 3284594056262745, + 2090090346797895, + 2581692978935809, + ]), + y_minus_x: FieldElement51::from_limbs([ + 244144781251265, + 1290834426417077, + 1888701171101942, + 1233922456644870, + 241117402207491, + ]), + xy2d: FieldElement51::from_limbs([ + 1266169390045455, + 1148042013187970, + 878921907853942, + 1815738019658093, + 908920199341621, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2521768507305118, + 953557056811112, + 2015863732865770, + 1358382511861315, + 2835421647899992, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2239837206240498, + 330928973149665, + 422268062913642, + 1481280019493032, + 619879520439841, + ]), + xy2d: FieldElement51::from_limbs([ + 1360166735366017, + 1770556573948510, + 1395061284191031, + 1814003148068126, + 522781147076884, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2611794802645686, + 707234844948070, + 1314059396506491, + 2919250341703934, + 2161831667832785, + ]), + y_minus_x: FieldElement51::from_limbs([ + 934831784182383, + 433734253968318, + 1660867106725771, + 1968393082772831, + 873946300968490, + ]), + xy2d: FieldElement51::from_limbs([ + 26306827827554, + 430884999378685, + 1504310424376419, + 1761358720837522, + 542195685418530, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1762131062631725, + 3123952634417535, + 3619918390837537, + 2909990877347294, + 1411594230004385, + ]), + y_minus_x: FieldElement51::from_limbs([ + 538272372224622, + 1425714779586199, + 588313661410172, + 1497062084392578, + 1602174047128512, + ]), + xy2d: FieldElement51::from_limbs([ + 907490361939255, + 1963620338391363, + 626927432296975, + 1250748516081414, + 959901171882527, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1335066153744413, + 2887804660779657, + 2653073855954038, + 2765226981667422, + 938831784476763, + ]), + y_minus_x: FieldElement51::from_limbs([ + 296699434737224, + 2047543711075683, + 2076451038937139, + 227783599906901, + 1602062110967627, + ]), + xy2d: FieldElement51::from_limbs([ + 1574834773194203, + 1384279952062839, + 393652417255803, + 2166968242848859, + 1552890441390820, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1619646774410947, + 1576090644023562, + 3035228391320965, + 1735328519940543, + 2355324535937066, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1024074573633446, + 957088456885874, + 1690425531356997, + 2102187380180052, + 1082544623222033, + ]), + xy2d: FieldElement51::from_limbs([ + 1871906170635853, + 1719383891167200, + 1584032250247862, + 823764804192117, + 2244048510084261, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 642147846489775, + 3334304977145699, + 305205716788147, + 2589176626729533, + 2224680511484174, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1734162377166545, + 260713621840346, + 157174591942595, + 952544272517991, + 222818702471733, + ]), + xy2d: FieldElement51::from_limbs([ + 1213115494182947, + 286778704335711, + 2130189536016490, + 308349182281342, + 1217623948685491, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3360052266973635, + 1843486583624091, + 1561693837124349, + 1084041964025479, + 1866270922024009, + ]), + y_minus_x: FieldElement51::from_limbs([ + 460705465481210, + 1968151453817859, + 497005926994844, + 625618055866751, + 2176893440866887, + ]), + xy2d: FieldElement51::from_limbs([ + 1655800250476757, + 2036588542300609, + 666447448675243, + 1615721995750683, + 1508669225186765, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2245948203759141, + 1058306669699396, + 1452898014240582, + 3961024141962768, + 1633235287338608, + ]), + y_minus_x: FieldElement51::from_limbs([ + 986647273684279, + 1507266907811370, + 1260572633649005, + 2071672342077446, + 695976026010857, + ]), + xy2d: FieldElement51::from_limbs([ + 1312356620823495, + 1635278548098567, + 901946076841033, + 585120475533168, + 1240667113237384, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2313723935779695, + 1506054666773895, + 996040223525031, + 636592914999692, + 1497801917020297, + ]), + y_minus_x: FieldElement51::from_limbs([ + 292042016419794, + 1158932298133044, + 2062611870323738, + 1946058478962569, + 1749165808126286, + ]), + xy2d: FieldElement51::from_limbs([ + 654683942212830, + 1526897351349087, + 2006818439922838, + 2194919327350361, + 1451960776874416, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3015041017808905, + 2951823141773809, + 2584865668253675, + 2508192032998563, + 2582137700042019, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1628123495344283, + 2072923641214546, + 1647225812023982, + 855655925244679, + 1758126430071140, + ]), + xy2d: FieldElement51::from_limbs([ + 1615895096489599, + 275295258643784, + 937665541219916, + 1313496726746346, + 1186468946422626, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1603070202850694, + 2072127623773242, + 1692648737212158, + 2493373404187852, + 1248948672117105, + ]), + y_minus_x: FieldElement51::from_limbs([ + 11167836031898, + 596565174397990, + 2196351068723859, + 314744641791907, + 1102014997250781, + ]), + xy2d: FieldElement51::from_limbs([ + 1409047922401191, + 69960384467966, + 688103515547600, + 1309746102488044, + 150292892873778, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1986083055103168, + 691715819340300, + 1361811659746933, + 3459052030333434, + 1063594696046061, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1201987338414749, + 2198784582460616, + 1203335513981498, + 489243077045066, + 2205278143582433, + ]), + xy2d: FieldElement51::from_limbs([ + 2034744376624534, + 2077387101466387, + 148448542974969, + 1502697574577258, + 473186584705655, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 472016956315960, + 720786972252993, + 2840633661190043, + 3150798753357827, + 2816563335499153, + ]), + y_minus_x: FieldElement51::from_limbs([ + 253464247569755, + 168314237403057, + 511780806170295, + 1058862316549135, + 1646858476817137, + ]), + xy2d: FieldElement51::from_limbs([ + 595092995922219, + 1491311840717691, + 291581784452778, + 1569186646367854, + 1031385061400544, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3483137021572755, + 1526955102024322, + 2778006642704458, + 457549634924205, + 1097420237736736, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1246991699537710, + 81367319519439, + 530844036072196, + 163656863755855, + 1950742455979290, + ]), + xy2d: FieldElement51::from_limbs([ + 191532664076407, + 539378506082089, + 1021612562876554, + 1026603384732632, + 1773368780410653, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 4144620731387879, + 590179521333342, + 4034023318016108, + 2255745030335426, + 2699746851701250, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2206599697359952, + 553895797384417, + 181689161933786, + 1153123447919104, + 778568064152659, + ]), + xy2d: FieldElement51::from_limbs([ + 1706307000059211, + 1885601289314487, + 889758608505788, + 550131729999853, + 1006862664714268, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3210197754285058, + 2048500453422630, + 3403309827888207, + 927154428508963, + 4199813798872019, + ]), + y_minus_x: FieldElement51::from_limbs([ + 992058915374933, + 476120535358775, + 1973648780784340, + 2025282643598818, + 2182318983793230, + ]), + xy2d: FieldElement51::from_limbs([ + 1343440812005821, + 1316045839091795, + 1884951299078063, + 1765919609219175, + 2197567554627988, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3129247779382818, + 4415026969054274, + 1900265885969643, + 1528796215447059, + 2172730393748688, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1773355092297603, + 64654329538271, + 1332124041660957, + 748492100858001, + 895500006200535, + ]), + xy2d: FieldElement51::from_limbs([ + 2000840647851980, + 546565968824914, + 420633283457524, + 195470736374507, + 1958689297569520, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 743138980705446, + 3411117504637167, + 2591389959690621, + 2380042066577202, + 3022267940115114, + ]), + y_minus_x: FieldElement51::from_limbs([ + 165947002229363, + 115186103724967, + 1068573292121517, + 1842565776920938, + 1969395681111987, + ]), + xy2d: FieldElement51::from_limbs([ + 553322266190633, + 234265665613185, + 484544650202821, + 1238773526575826, + 2017991917953668, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2581954631514051, + 1245093644265357, + 3537016673825374, + 1834216551713857, + 923978372152807, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1855378315339552, + 890045579230758, + 1764718173975590, + 197904186055854, + 1718129022310327, + ]), + xy2d: FieldElement51::from_limbs([ + 1278162928734862, + 1894118254109862, + 987503995465517, + 177406744098996, + 781538103127693, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1996603431230215, + 1191888797552937, + 1207440075928499, + 2765853449051137, + 2525314961343288, + ]), + y_minus_x: FieldElement51::from_limbs([ + 808903879370889, + 990820108751280, + 1084429472258867, + 1078562781312589, + 254514692695625, + ]), + xy2d: FieldElement51::from_limbs([ + 615855140068469, + 586046731175395, + 693470779212674, + 1964537100203868, + 1350330550265229, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3344544372023708, + 720386671449874, + 2480841360702110, + 2036034126860286, + 2015744690201389, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1337446193390478, + 1984110761311871, + 746489405020285, + 407347127604128, + 1740475330360596, + ]), + xy2d: FieldElement51::from_limbs([ + 140840424783613, + 1063284623568331, + 1136446106453878, + 372042229029799, + 442607248430694, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2330781679120937, + 376801425148230, + 2032603686676107, + 1488926293635130, + 1317278311532959, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1290116731380016, + 2166899563471713, + 831997001838078, + 870954980505220, + 2108537278055823, + ]), + xy2d: FieldElement51::from_limbs([ + 1912719171026343, + 846194720551034, + 2043988124740726, + 993234269653961, + 421229796383281, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2651184584992902, + 2775702557638963, + 2539786009779572, + 2575974880015305, + 2122619079836732, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1154054290132562, + 931753998725577, + 1647742001778052, + 865765466488226, + 1083816107290025, + ]), + xy2d: FieldElement51::from_limbs([ + 986341121095108, + 1522330369638573, + 1990880546211047, + 501525962272123, + 198539304862139, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1496414019192687, + 3991034436173951, + 3380311659062196, + 2854747485359158, + 3346958036643152, + ]), + y_minus_x: FieldElement51::from_limbs([ + 805612068303425, + 1891790027761335, + 1587008567571549, + 722120737390201, + 378156757163816, + ]), + xy2d: FieldElement51::from_limbs([ + 1588994517921951, + 977362751042302, + 1329302387067714, + 2069348224564088, + 1586007159625211, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2490539421551682, + 1985699850375015, + 2331762317128172, + 4145097393776678, + 2521049460190674, + ]), + y_minus_x: FieldElement51::from_limbs([ + 615817553313996, + 2245962768078178, + 482564324326173, + 2101336843140780, + 1240914880829407, + ]), + xy2d: FieldElement51::from_limbs([ + 1438242482238189, + 874267817785463, + 1620810389770625, + 866155221338671, + 1040426546798301, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 2403083624110300, + 2548561409802975, + 2492699136535911, + 2358289519456539, + 3203964320363148, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1913986535403097, + 1977163223054199, + 1972905914623196, + 1650122133472502, + 1905849310819035, + ]), + xy2d: FieldElement51::from_limbs([ + 858174816360838, + 614595356564037, + 1099584959044836, + 636998087084906, + 1070393269058348, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3666695924830668, + 3585640662737501, + 2372994528684236, + 2628565977288995, + 3482812783469694, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1994161359147952, + 2198039369802658, + 62790022842537, + 1522306785848169, + 951223194802833, + ]), + xy2d: FieldElement51::from_limbs([ + 852296621440717, + 431889737774209, + 370755457746189, + 437604073958073, + 627857326892757, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1794955764684156, + 2586904290013612, + 1322647643615887, + 856117964085888, + 2652432778663153, + ]), + y_minus_x: FieldElement51::from_limbs([ + 933592377399646, + 78031722952813, + 926049890685253, + 1471649501316246, + 33789909190376, + ]), + xy2d: FieldElement51::from_limbs([ + 1479319468832059, + 203906207621608, + 659828362330083, + 44358398435755, + 1273573524210803, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1592342143350813, + 3227219208247713, + 2345240352078765, + 2577750109932929, + 2933512841197243, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2184946892642995, + 1517382324576002, + 1557940277419806, + 2170635134813213, + 747314658627002, + ]), + xy2d: FieldElement51::from_limbs([ + 1823193620577742, + 1135817878516419, + 1731253819308581, + 1031652967267804, + 2123506616999453, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1346190246005805, + 2052692552023851, + 1718128041785940, + 2491557332978474, + 3474370880388305, + ]), + y_minus_x: FieldElement51::from_limbs([ + 424776012994573, + 281050757243423, + 626466040846420, + 990194703866532, + 38571969885982, + ]), + xy2d: FieldElement51::from_limbs([ + 192408346595466, + 1054889725292349, + 584097975693004, + 1447909807397749, + 2134645004369136, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3169895788615063, + 3503097743181446, + 601598510029975, + 1422812237223371, + 2121009661378329, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1603348391996783, + 2066143816131699, + 1789627290363958, + 2145705961178118, + 1985578641438222, + ]), + xy2d: FieldElement51::from_limbs([ + 352633958653380, + 856927627345554, + 793925083122702, + 93551575767286, + 1222010153634215, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1756866499986349, + 911731956999969, + 2707505543214075, + 4006920335263786, + 822501008147910, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1094036422864347, + 1897208881572508, + 1503607738246960, + 1901060196071406, + 294068411105729, + ]), + xy2d: FieldElement51::from_limbs([ + 587776484399576, + 1116861711228807, + 343398777436088, + 936544065763093, + 1643746750211060, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 3477749685790410, + 267997399528836, + 2953780922004404, + 3252368924080907, + 3787792887348381, + ]), + y_minus_x: FieldElement51::from_limbs([ + 2042368155872443, + 41662387210459, + 1676313264498480, + 1333968523426810, + 1765708383352310, + ]), + xy2d: FieldElement51::from_limbs([ + 1453394896690938, + 1585795827439909, + 1469309456804303, + 1294645324464404, + 2042954198665899, + ]), + }, + AffineNielsPoint { + y_plus_x: FieldElement51::from_limbs([ + 1810069207599881, + 1358344669503239, + 1989371257548167, + 2316270051121225, + 3019675451276507, + ]), + y_minus_x: FieldElement51::from_limbs([ + 1866114438287676, + 1663420339568364, + 1437691317033088, + 538298302628038, + 1212711449614363, + ]), + xy2d: FieldElement51::from_limbs([ + 1769235035677897, + 1562012115317882, + 31277513664750, + 536198657928416, + 1976134212537183, + ]), + }, + ]); diff --git a/curve25519-elligator2/src/backend/serial/u64/field.rs b/curve25519-elligator2/src/backend/serial/u64/field.rs new file mode 100644 index 00000000..2e939b4c --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/u64/field.rs @@ -0,0 +1,595 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(64\\)-bit +//! limbs with \\(128\\)-bit products. + +use core::fmt::Debug; +use core::ops::Neg; +use core::ops::{Add, AddAssign}; +use core::ops::{Mul, MulAssign}; +use core::ops::{Sub, SubAssign}; + +use subtle::Choice; +use subtle::ConditionallySelectable; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +/// A `FieldElement51` represents an element of the field +/// \\( \mathbb Z / (2\^{255} - 19)\\). +/// +/// In the 64-bit implementation, a `FieldElement` is represented in +/// radix \\(2\^{51}\\) as five `u64`s; the coefficients are allowed to +/// grow up to \\(2\^{54}\\) between reductions modulo \\(p\\). +/// +/// # Note +/// +/// The `curve25519_elligator2::field` module provides a type alias +/// `curve25519_elligator2::field::FieldElement` to either `FieldElement51` +/// or `FieldElement2625`. +/// +/// The backend-specific type `FieldElement51` should not be used +/// outside of the `curve25519_elligator2::field` module. +#[derive(Copy, Clone)] +pub struct FieldElement51(pub(crate) [u64; 5]); + +impl Debug for FieldElement51 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "FieldElement51({:?})", &self.0[..]) + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for FieldElement51 { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 { + fn add_assign(&mut self, _rhs: &'b FieldElement51) { + for i in 0..5 { + self.0[i] += _rhs.0[i]; + } + } +} + +impl<'a, 'b> Add<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + fn add(self, _rhs: &'b FieldElement51) -> FieldElement51 { + let mut output = *self; + output += _rhs; + output + } +} + +impl<'b> SubAssign<&'b FieldElement51> for FieldElement51 { + fn sub_assign(&mut self, _rhs: &'b FieldElement51) { + let result = (self as &FieldElement51) - _rhs; + self.0 = result.0; + } +} + +impl<'a, 'b> Sub<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + fn sub(self, _rhs: &'b FieldElement51) -> FieldElement51 { + // To avoid underflow, first add a multiple of p. + // Choose 16*p = p << 4 to be larger than 54-bit _rhs. + // + // If we could statically track the bitlengths of the limbs + // of every FieldElement51, we could choose a multiple of p + // just bigger than _rhs and avoid having to do a reduction. + // + // Since we don't yet have type-level integers to do this, we + // have to add an explicit reduction call here. + FieldElement51::reduce([ + (self.0[0] + 36028797018963664u64) - _rhs.0[0], + (self.0[1] + 36028797018963952u64) - _rhs.0[1], + (self.0[2] + 36028797018963952u64) - _rhs.0[2], + (self.0[3] + 36028797018963952u64) - _rhs.0[3], + (self.0[4] + 36028797018963952u64) - _rhs.0[4], + ]) + } +} + +impl<'b> MulAssign<&'b FieldElement51> for FieldElement51 { + fn mul_assign(&mut self, _rhs: &'b FieldElement51) { + let result = (self as &FieldElement51) * _rhs; + self.0 = result.0; + } +} + +impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + + #[rustfmt::skip] // keep alignment of c* calculations + fn mul(self, _rhs: &'b FieldElement51) -> FieldElement51 { + /// Helper function to multiply two 64-bit integers with 128 + /// bits of output. + #[inline(always)] + fn m(x: u64, y: u64) -> u128 { (x as u128) * (y as u128) } + + // Alias self, _rhs for more readable formulas + let a: &[u64; 5] = &self.0; + let b: &[u64; 5] = &_rhs.0; + + // Precondition: assume input limbs a[i], b[i] are bounded as + // + // a[i], b[i] < 2^(51 + b) + // + // where b is a real parameter measuring the "bit excess" of the limbs. + + // 64-bit precomputations to avoid 128-bit multiplications. + // + // This fits into a u64 whenever 51 + b + lg(19) < 64. + // + // Since 51 + b + lg(19) < 51 + 4.25 + b + // = 55.25 + b, + // this fits if b < 8.75. + let b1_19 = b[1] * 19; + let b2_19 = b[2] * 19; + let b3_19 = b[3] * 19; + let b4_19 = b[4] * 19; + + // Multiply to get 128-bit coefficients of output + let c0: u128 = m(a[0], b[0]) + m(a[4], b1_19) + m(a[3], b2_19) + m(a[2], b3_19) + m(a[1], b4_19); + let mut c1: u128 = m(a[1], b[0]) + m(a[0], b[1]) + m(a[4], b2_19) + m(a[3], b3_19) + m(a[2], b4_19); + let mut c2: u128 = m(a[2], b[0]) + m(a[1], b[1]) + m(a[0], b[2]) + m(a[4], b3_19) + m(a[3], b4_19); + let mut c3: u128 = m(a[3], b[0]) + m(a[2], b[1]) + m(a[1], b[2]) + m(a[0], b[3]) + m(a[4], b4_19); + let mut c4: u128 = m(a[4], b[0]) + m(a[3], b[1]) + m(a[2], b[2]) + m(a[1], b[3]) + m(a[0] , b[4]); + + // How big are the c[i]? We have + // + // c[i] < 2^(102 + 2*b) * (1+i + (4-i)*19) + // < 2^(102 + lg(1 + 4*19) + 2*b) + // < 2^(108.27 + 2*b) + // + // The carry (c[i] >> 51) fits into a u64 when + // 108.27 + 2*b - 51 < 64 + // 2*b < 6.73 + // b < 3.365. + // + // So we require b < 3 to ensure this fits. + debug_assert!(a[0] < (1 << 54)); debug_assert!(b[0] < (1 << 54)); + debug_assert!(a[1] < (1 << 54)); debug_assert!(b[1] < (1 << 54)); + debug_assert!(a[2] < (1 << 54)); debug_assert!(b[2] < (1 << 54)); + debug_assert!(a[3] < (1 << 54)); debug_assert!(b[3] < (1 << 54)); + debug_assert!(a[4] < (1 << 54)); debug_assert!(b[4] < (1 << 54)); + + // Casting to u64 and back tells the compiler that the carry is + // bounded by 2^64, so that the addition is a u128 + u64 rather + // than u128 + u128. + + const LOW_51_BIT_MASK: u64 = (1u64 << 51) - 1; + let mut out = [0u64; 5]; + + c1 += ((c0 >> 51) as u64) as u128; + out[0] = (c0 as u64) & LOW_51_BIT_MASK; + + c2 += ((c1 >> 51) as u64) as u128; + out[1] = (c1 as u64) & LOW_51_BIT_MASK; + + c3 += ((c2 >> 51) as u64) as u128; + out[2] = (c2 as u64) & LOW_51_BIT_MASK; + + c4 += ((c3 >> 51) as u64) as u128; + out[3] = (c3 as u64) & LOW_51_BIT_MASK; + + let carry: u64 = (c4 >> 51) as u64; + out[4] = (c4 as u64) & LOW_51_BIT_MASK; + + // To see that this does not overflow, we need out[0] + carry * 19 < 2^64. + // + // c4 < a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0 + (carry from c3) + // < 5*(2^(51 + b) * 2^(51 + b)) + (carry from c3) + // < 2^(102 + 2*b + lg(5)) + 2^64. + // + // When b < 3 we get + // + // c4 < 2^110.33 so that carry < 2^59.33 + // + // so that + // + // out[0] + carry * 19 < 2^51 + 19 * 2^59.33 < 2^63.58 + // + // and there is no overflow. + out[0] += carry * 19; + + // Now out[1] < 2^51 + 2^(64 -51) = 2^51 + 2^13 < 2^(51 + epsilon). + out[1] += out[0] >> 51; + out[0] &= LOW_51_BIT_MASK; + + // Now out[i] < 2^(51 + epsilon) for all i. + FieldElement51(out) + } +} + +impl<'a> Neg for &'a FieldElement51 { + type Output = FieldElement51; + fn neg(self) -> FieldElement51 { + let mut output = *self; + output.negate(); + output + } +} + +impl ConditionallySelectable for FieldElement51 { + fn conditional_select( + a: &FieldElement51, + b: &FieldElement51, + choice: Choice, + ) -> FieldElement51 { + FieldElement51([ + u64::conditional_select(&a.0[0], &b.0[0], choice), + u64::conditional_select(&a.0[1], &b.0[1], choice), + u64::conditional_select(&a.0[2], &b.0[2], choice), + u64::conditional_select(&a.0[3], &b.0[3], choice), + u64::conditional_select(&a.0[4], &b.0[4], choice), + ]) + } + + fn conditional_swap(a: &mut FieldElement51, b: &mut FieldElement51, choice: Choice) { + u64::conditional_swap(&mut a.0[0], &mut b.0[0], choice); + u64::conditional_swap(&mut a.0[1], &mut b.0[1], choice); + u64::conditional_swap(&mut a.0[2], &mut b.0[2], choice); + u64::conditional_swap(&mut a.0[3], &mut b.0[3], choice); + u64::conditional_swap(&mut a.0[4], &mut b.0[4], choice); + } + + fn conditional_assign(&mut self, other: &FieldElement51, choice: Choice) { + self.0[0].conditional_assign(&other.0[0], choice); + self.0[1].conditional_assign(&other.0[1], choice); + self.0[2].conditional_assign(&other.0[2], choice); + self.0[3].conditional_assign(&other.0[3], choice); + self.0[4].conditional_assign(&other.0[4], choice); + } +} + +impl FieldElement51 { + pub(crate) const fn from_limbs(limbs: [u64; 5]) -> FieldElement51 { + FieldElement51(limbs) + } + + /// The scalar \\( 0 \\). + pub const ZERO: FieldElement51 = FieldElement51::from_limbs([0, 0, 0, 0, 0]); + /// The scalar \\( 1 \\). + pub const ONE: FieldElement51 = FieldElement51::from_limbs([1, 0, 0, 0, 0]); + /// The scalar \\( -1 \\). + pub const MINUS_ONE: FieldElement51 = FieldElement51::from_limbs([ + 2251799813685228, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, + ]); + + /// Invert the sign of this field element + pub fn negate(&mut self) { + // See commentary in the Sub impl + let neg = FieldElement51::reduce([ + 36028797018963664u64 - self.0[0], + 36028797018963952u64 - self.0[1], + 36028797018963952u64 - self.0[2], + 36028797018963952u64 - self.0[3], + 36028797018963952u64 - self.0[4], + ]); + self.0 = neg.0; + } + + /// Given 64-bit input limbs, reduce to enforce the bound 2^(51 + epsilon). + #[inline(always)] + fn reduce(mut limbs: [u64; 5]) -> FieldElement51 { + const LOW_51_BIT_MASK: u64 = (1u64 << 51) - 1; + + // Since the input limbs are bounded by 2^64, the biggest + // carry-out is bounded by 2^13. + // + // The biggest carry-in is c4 * 19, resulting in + // + // 2^51 + 19*2^13 < 2^51.0000000001 + // + // Because we don't need to canonicalize, only to reduce the + // limb sizes, it's OK to do a "weak reduction", where we + // compute the carry-outs in parallel. + + let c0 = limbs[0] >> 51; + let c1 = limbs[1] >> 51; + let c2 = limbs[2] >> 51; + let c3 = limbs[3] >> 51; + let c4 = limbs[4] >> 51; + + limbs[0] &= LOW_51_BIT_MASK; + limbs[1] &= LOW_51_BIT_MASK; + limbs[2] &= LOW_51_BIT_MASK; + limbs[3] &= LOW_51_BIT_MASK; + limbs[4] &= LOW_51_BIT_MASK; + + limbs[0] += c4 * 19; + limbs[1] += c0; + limbs[2] += c1; + limbs[3] += c2; + limbs[4] += c3; + + FieldElement51(limbs) + } + + /// Load a `FieldElement51` from the low 255 bits of a 256-bit + /// input. + /// + /// # Warning + /// + /// This function does not check that the input used the canonical + /// representative. It masks the high bit, but it will happily + /// decode 2^255 - 18 to 1. Applications that require a canonical + /// encoding of every field element should decode, re-encode to + /// the canonical encoding, and check that the input was + /// canonical. + /// + #[rustfmt::skip] // keep alignment of bit shifts + pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 { + let load8 = |input: &[u8]| -> u64 { + (input[0] as u64) + | ((input[1] as u64) << 8) + | ((input[2] as u64) << 16) + | ((input[3] as u64) << 24) + | ((input[4] as u64) << 32) + | ((input[5] as u64) << 40) + | ((input[6] as u64) << 48) + | ((input[7] as u64) << 56) + }; + + let low_51_bit_mask = (1u64 << 51) - 1; + FieldElement51( + // load bits [ 0, 64), no shift + [ load8(&bytes[ 0..]) & low_51_bit_mask + // load bits [ 48,112), shift to [ 51,112) + , (load8(&bytes[ 6..]) >> 3) & low_51_bit_mask + // load bits [ 96,160), shift to [102,160) + , (load8(&bytes[12..]) >> 6) & low_51_bit_mask + // load bits [152,216), shift to [153,216) + , (load8(&bytes[19..]) >> 1) & low_51_bit_mask + // load bits [192,256), shift to [204,112) + , (load8(&bytes[24..]) >> 12) & low_51_bit_mask + ]) + } + + /// Serialize this `FieldElement51` to a 32-byte array. The + /// encoding is canonical. + #[rustfmt::skip] // keep alignment of s[*] calculations + pub fn as_bytes(&self) -> [u8; 32] { + // Let h = limbs[0] + limbs[1]*2^51 + ... + limbs[4]*2^204. + // + // Write h = pq + r with 0 <= r < p. + // + // We want to compute r = h mod p. + // + // If h < 2*p = 2^256 - 38, + // then q = 0 or 1, + // + // with q = 0 when h < p + // and q = 1 when h >= p. + // + // Notice that h >= p <==> h + 19 >= p + 19 <==> h + 19 >= 2^255. + // Therefore q can be computed as the carry bit of h + 19. + + // First, reduce the limbs to ensure h < 2*p. + let mut limbs = FieldElement51::reduce(self.0).0; + + let mut q = (limbs[0] + 19) >> 51; + q = (limbs[1] + q) >> 51; + q = (limbs[2] + q) >> 51; + q = (limbs[3] + q) >> 51; + q = (limbs[4] + q) >> 51; + + // Now we can compute r as r = h - pq = r - (2^255-19)q = r + 19q - 2^255q + + limbs[0] += 19 * q; + + // Now carry the result to compute r + 19q ... + let low_51_bit_mask = (1u64 << 51) - 1; + limbs[1] += limbs[0] >> 51; + limbs[0] &= low_51_bit_mask; + limbs[2] += limbs[1] >> 51; + limbs[1] &= low_51_bit_mask; + limbs[3] += limbs[2] >> 51; + limbs[2] &= low_51_bit_mask; + limbs[4] += limbs[3] >> 51; + limbs[3] &= low_51_bit_mask; + // ... but instead of carrying (limbs[4] >> 51) = 2^255q + // into another limb, discard it, subtracting the value + limbs[4] &= low_51_bit_mask; + + // Now arrange the bits of the limbs. + let mut s = [0u8;32]; + s[ 0] = limbs[0] as u8; + s[ 1] = (limbs[0] >> 8) as u8; + s[ 2] = (limbs[0] >> 16) as u8; + s[ 3] = (limbs[0] >> 24) as u8; + s[ 4] = (limbs[0] >> 32) as u8; + s[ 5] = (limbs[0] >> 40) as u8; + s[ 6] = ((limbs[0] >> 48) | (limbs[1] << 3)) as u8; + s[ 7] = (limbs[1] >> 5) as u8; + s[ 8] = (limbs[1] >> 13) as u8; + s[ 9] = (limbs[1] >> 21) as u8; + s[10] = (limbs[1] >> 29) as u8; + s[11] = (limbs[1] >> 37) as u8; + s[12] = ((limbs[1] >> 45) | (limbs[2] << 6)) as u8; + s[13] = (limbs[2] >> 2) as u8; + s[14] = (limbs[2] >> 10) as u8; + s[15] = (limbs[2] >> 18) as u8; + s[16] = (limbs[2] >> 26) as u8; + s[17] = (limbs[2] >> 34) as u8; + s[18] = (limbs[2] >> 42) as u8; + s[19] = ((limbs[2] >> 50) | (limbs[3] << 1)) as u8; + s[20] = (limbs[3] >> 7) as u8; + s[21] = (limbs[3] >> 15) as u8; + s[22] = (limbs[3] >> 23) as u8; + s[23] = (limbs[3] >> 31) as u8; + s[24] = (limbs[3] >> 39) as u8; + s[25] = ((limbs[3] >> 47) | (limbs[4] << 4)) as u8; + s[26] = (limbs[4] >> 4) as u8; + s[27] = (limbs[4] >> 12) as u8; + s[28] = (limbs[4] >> 20) as u8; + s[29] = (limbs[4] >> 28) as u8; + s[30] = (limbs[4] >> 36) as u8; + s[31] = (limbs[4] >> 44) as u8; + + // High bit should be zero. + debug_assert!((s[31] & 0b1000_0000u8) == 0u8); + + s + } + + /// Given `k > 0`, return `self^(2^k)`. + #[rustfmt::skip] // keep alignment of c* calculations + pub fn pow2k(&self, mut k: u32) -> FieldElement51 { + + debug_assert!( k > 0 ); + + /// Multiply two 64-bit integers with 128 bits of output. + #[inline(always)] + fn m(x: u64, y: u64) -> u128 { + (x as u128) * (y as u128) + } + + let mut a: [u64; 5] = self.0; + + loop { + // Precondition: assume input limbs a[i] are bounded as + // + // a[i] < 2^(51 + b) + // + // where b is a real parameter measuring the "bit excess" of the limbs. + + // Precomputation: 64-bit multiply by 19. + // + // This fits into a u64 whenever 51 + b + lg(19) < 64. + // + // Since 51 + b + lg(19) < 51 + 4.25 + b + // = 55.25 + b, + // this fits if b < 8.75. + let a3_19 = 19 * a[3]; + let a4_19 = 19 * a[4]; + + // Multiply to get 128-bit coefficients of output. + // + // The 128-bit multiplications by 2 turn into 1 slr + 1 slrd each, + // which doesn't seem any better or worse than doing them as precomputations + // on the 64-bit inputs. + let c0: u128 = m(a[0], a[0]) + 2*( m(a[1], a4_19) + m(a[2], a3_19) ); + let mut c1: u128 = m(a[3], a3_19) + 2*( m(a[0], a[1]) + m(a[2], a4_19) ); + let mut c2: u128 = m(a[1], a[1]) + 2*( m(a[0], a[2]) + m(a[4], a3_19) ); + let mut c3: u128 = m(a[4], a4_19) + 2*( m(a[0], a[3]) + m(a[1], a[2]) ); + let mut c4: u128 = m(a[2], a[2]) + 2*( m(a[0], a[4]) + m(a[1], a[3]) ); + + // Same bound as in multiply: + // c[i] < 2^(102 + 2*b) * (1+i + (4-i)*19) + // < 2^(102 + lg(1 + 4*19) + 2*b) + // < 2^(108.27 + 2*b) + // + // The carry (c[i] >> 51) fits into a u64 when + // 108.27 + 2*b - 51 < 64 + // 2*b < 6.73 + // b < 3.365. + // + // So we require b < 3 to ensure this fits. + debug_assert!(a[0] < (1 << 54)); + debug_assert!(a[1] < (1 << 54)); + debug_assert!(a[2] < (1 << 54)); + debug_assert!(a[3] < (1 << 54)); + debug_assert!(a[4] < (1 << 54)); + + const LOW_51_BIT_MASK: u64 = (1u64 << 51) - 1; + + // Casting to u64 and back tells the compiler that the carry is bounded by 2^64, so + // that the addition is a u128 + u64 rather than u128 + u128. + c1 += ((c0 >> 51) as u64) as u128; + a[0] = (c0 as u64) & LOW_51_BIT_MASK; + + c2 += ((c1 >> 51) as u64) as u128; + a[1] = (c1 as u64) & LOW_51_BIT_MASK; + + c3 += ((c2 >> 51) as u64) as u128; + a[2] = (c2 as u64) & LOW_51_BIT_MASK; + + c4 += ((c3 >> 51) as u64) as u128; + a[3] = (c3 as u64) & LOW_51_BIT_MASK; + + let carry: u64 = (c4 >> 51) as u64; + a[4] = (c4 as u64) & LOW_51_BIT_MASK; + + // To see that this does not overflow, we need a[0] + carry * 19 < 2^64. + // + // c4 < a2^2 + 2*a0*a4 + 2*a1*a3 + (carry from c3) + // < 2^(102 + 2*b + lg(5)) + 2^64. + // + // When b < 3 we get + // + // c4 < 2^110.33 so that carry < 2^59.33 + // + // so that + // + // a[0] + carry * 19 < 2^51 + 19 * 2^59.33 < 2^63.58 + // + // and there is no overflow. + a[0] += carry * 19; + + // Now a[1] < 2^51 + 2^(64 -51) = 2^51 + 2^13 < 2^(51 + epsilon). + a[1] += a[0] >> 51; + a[0] &= LOW_51_BIT_MASK; + + // Now all a[i] < 2^(51 + epsilon) and a = self^(2^k). + + k -= 1; + if k == 0 { + break; + } + } + + FieldElement51(a) + } + + /// Returns the square of this field element. + pub fn square(&self) -> FieldElement51 { + self.pow2k(1) + } + + /// Returns 2 times the square of this field element. + pub fn square2(&self) -> FieldElement51 { + let mut square = self.pow2k(1); + for i in 0..5 { + square.0[i] *= 2; + } + + square + } + + /// Returns 1 if self is greater than the other and 0 otherwise + // implementation based on C libgmp -> mpn_sub_n + pub(crate) fn gt(&self, other: &Self) -> Choice { + let mut _ul = 0_u64; + let mut _vl = 0_u64; + let mut _rl = 0_u64; + + let mut cy = 0_u64; + for i in 0..5 { + _ul = self.0[i]; + _vl = other.0[i]; + + let (_sl, _cy1) = _ul.overflowing_sub(_vl); + let (_rl, _cy2) = _sl.overflowing_sub(cy); + cy = _cy1 as u64 | _cy2 as u64; + } + + Choice::from((cy != 0_u64) as u8) + } +} diff --git a/curve25519-elligator2/src/backend/serial/u64/mod.rs b/curve25519-elligator2/src/backend/serial/u64/mod.rs new file mode 100644 index 00000000..f62954f3 --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/u64/mod.rs @@ -0,0 +1,27 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2018 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! The `u64` backend uses `u64`s and a `(u64, u64) -> u128` multiplier. +//! +//! On x86_64, the idiom `(x as u128) * (y as u128)` lowers to `MUL` +//! instructions taking 64-bit inputs and producing 128-bit outputs. On +//! other platforms, this implementation is not recommended. +//! +//! On Haswell and newer, the BMI2 extension provides `MULX`, and on +//! Broadwell and newer, the ADX extension provides `ADCX` and `ADOX` +//! (allowing the CPU to compute two carry chains in parallel). These +//! will be used if available. + +pub mod field; + +pub mod scalar; + +pub mod constants; diff --git a/curve25519-elligator2/src/backend/serial/u64/scalar.rs b/curve25519-elligator2/src/backend/serial/u64/scalar.rs new file mode 100644 index 00000000..1cc2df4a --- /dev/null +++ b/curve25519-elligator2/src/backend/serial/u64/scalar.rs @@ -0,0 +1,497 @@ +//! Arithmetic mod \\(2\^{252} + 27742317777372353535851937790883648493\\) +//! with five \\(52\\)-bit unsigned limbs. +//! +//! \\(51\\)-bit limbs would cover the desired bit range (\\(253\\) +//! bits), but isn't large enough to reduce a \\(512\\)-bit number with +//! Montgomery multiplication, so \\(52\\) bits is used instead. To see +//! that this is safe for intermediate results, note that the largest +//! limb in a \\(5\times 5\\) product of \\(52\\)-bit limbs will be +//! +//! ```text +//! (0xfffffffffffff^2) * 5 = 0x4ffffffffffff60000000000005 (107 bits). +//! ``` + +use core::fmt::Debug; +use core::ops::{Index, IndexMut}; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +use crate::constants; + +/// The `Scalar52` struct represents an element in +/// \\(\mathbb Z / \ell \mathbb Z\\) as 5 \\(52\\)-bit limbs. +#[derive(Copy, Clone)] +pub struct Scalar52(pub [u64; 5]); + +impl Debug for Scalar52 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Scalar52: {:?}", &self.0[..]) + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for Scalar52 { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl Index for Scalar52 { + type Output = u64; + fn index(&self, _index: usize) -> &u64 { + &(self.0[_index]) + } +} + +impl IndexMut for Scalar52 { + fn index_mut(&mut self, _index: usize) -> &mut u64 { + &mut (self.0[_index]) + } +} + +/// u64 * u64 = u128 multiply helper +#[inline(always)] +fn m(x: u64, y: u64) -> u128 { + (x as u128) * (y as u128) +} + +impl Scalar52 { + /// The scalar \\( 0 \\). + pub const ZERO: Scalar52 = Scalar52([0, 0, 0, 0, 0]); + + /// Unpack a 32 byte / 256 bit scalar into 5 52-bit limbs. + #[rustfmt::skip] // keep alignment of s[*] calculations + pub fn from_bytes(bytes: &[u8; 32]) -> Scalar52 { + let mut words = [0u64; 4]; + for i in 0..4 { + for j in 0..8 { + words[i] |= (bytes[(i * 8) + j] as u64) << (j * 8); + } + } + + let mask = (1u64 << 52) - 1; + let top_mask = (1u64 << 48) - 1; + let mut s = Scalar52::ZERO; + + s[0] = words[0] & mask; + s[1] = ((words[0] >> 52) | (words[1] << 12)) & mask; + s[2] = ((words[1] >> 40) | (words[2] << 24)) & mask; + s[3] = ((words[2] >> 28) | (words[3] << 36)) & mask; + s[4] = (words[3] >> 16) & top_mask; + + s + } + + /// Reduce a 64 byte / 512 bit scalar mod l + #[rustfmt::skip] // keep alignment of lo[*] and hi[*] calculations + pub fn from_bytes_wide(bytes: &[u8; 64]) -> Scalar52 { + let mut words = [0u64; 8]; + for i in 0..8 { + for j in 0..8 { + words[i] |= (bytes[(i * 8) + j] as u64) << (j * 8); + } + } + + let mask = (1u64 << 52) - 1; + let mut lo = Scalar52::ZERO; + let mut hi = Scalar52::ZERO; + + lo[0] = words[0] & mask; + lo[1] = ((words[0] >> 52) | (words[ 1] << 12)) & mask; + lo[2] = ((words[1] >> 40) | (words[ 2] << 24)) & mask; + lo[3] = ((words[2] >> 28) | (words[ 3] << 36)) & mask; + lo[4] = ((words[3] >> 16) | (words[ 4] << 48)) & mask; + hi[0] = (words[4] >> 4) & mask; + hi[1] = ((words[4] >> 56) | (words[ 5] << 8)) & mask; + hi[2] = ((words[5] >> 44) | (words[ 6] << 20)) & mask; + hi[3] = ((words[6] >> 32) | (words[ 7] << 32)) & mask; + hi[4] = words[7] >> 20 ; + + lo = Scalar52::montgomery_mul(&lo, &constants::R); // (lo * R) / R = lo + hi = Scalar52::montgomery_mul(&hi, &constants::RR); // (hi * R^2) / R = hi * R + + Scalar52::add(&hi, &lo) + } + + /// Pack the limbs of this `Scalar52` into 32 bytes + #[rustfmt::skip] // keep alignment of s[*] calculations + #[allow(clippy::identity_op)] + pub fn as_bytes(&self) -> [u8; 32] { + let mut s = [0u8; 32]; + + s[ 0] = (self.0[ 0] >> 0) as u8; + s[ 1] = (self.0[ 0] >> 8) as u8; + s[ 2] = (self.0[ 0] >> 16) as u8; + s[ 3] = (self.0[ 0] >> 24) as u8; + s[ 4] = (self.0[ 0] >> 32) as u8; + s[ 5] = (self.0[ 0] >> 40) as u8; + s[ 6] = ((self.0[ 0] >> 48) | (self.0[ 1] << 4)) as u8; + s[ 7] = (self.0[ 1] >> 4) as u8; + s[ 8] = (self.0[ 1] >> 12) as u8; + s[ 9] = (self.0[ 1] >> 20) as u8; + s[10] = (self.0[ 1] >> 28) as u8; + s[11] = (self.0[ 1] >> 36) as u8; + s[12] = (self.0[ 1] >> 44) as u8; + s[13] = (self.0[ 2] >> 0) as u8; + s[14] = (self.0[ 2] >> 8) as u8; + s[15] = (self.0[ 2] >> 16) as u8; + s[16] = (self.0[ 2] >> 24) as u8; + s[17] = (self.0[ 2] >> 32) as u8; + s[18] = (self.0[ 2] >> 40) as u8; + s[19] = ((self.0[ 2] >> 48) | (self.0[ 3] << 4)) as u8; + s[20] = (self.0[ 3] >> 4) as u8; + s[21] = (self.0[ 3] >> 12) as u8; + s[22] = (self.0[ 3] >> 20) as u8; + s[23] = (self.0[ 3] >> 28) as u8; + s[24] = (self.0[ 3] >> 36) as u8; + s[25] = (self.0[ 3] >> 44) as u8; + s[26] = (self.0[ 4] >> 0) as u8; + s[27] = (self.0[ 4] >> 8) as u8; + s[28] = (self.0[ 4] >> 16) as u8; + s[29] = (self.0[ 4] >> 24) as u8; + s[30] = (self.0[ 4] >> 32) as u8; + s[31] = (self.0[ 4] >> 40) as u8; + + s + } + + /// Compute `a + b` (mod l) + pub fn add(a: &Scalar52, b: &Scalar52) -> Scalar52 { + let mut sum = Scalar52::ZERO; + let mask = (1u64 << 52) - 1; + + // a + b + let mut carry: u64 = 0; + for i in 0..5 { + carry = a[i] + b[i] + (carry >> 52); + sum[i] = carry & mask; + } + + // subtract l if the sum is >= l + Scalar52::sub(&sum, &constants::L) + } + + /// Compute `a - b` (mod l) + pub fn sub(a: &Scalar52, b: &Scalar52) -> Scalar52 { + let mut difference = Scalar52::ZERO; + let mask = (1u64 << 52) - 1; + + // a - b + let mut borrow: u64 = 0; + for i in 0..5 { + borrow = a[i].wrapping_sub(b[i] + (borrow >> 63)); + difference[i] = borrow & mask; + } + + // conditionally add l if the difference is negative + let underflow_mask = ((borrow >> 63) ^ 1).wrapping_sub(1); + let mut carry: u64 = 0; + for i in 0..5 { + carry = (carry >> 52) + difference[i] + (constants::L[i] & underflow_mask); + difference[i] = carry & mask; + } + + difference + } + + /// Compute `a * b` + #[inline(always)] + #[rustfmt::skip] // keep alignment of z[*] calculations + pub (crate) fn mul_internal(a: &Scalar52, b: &Scalar52) -> [u128; 9] { + let mut z = [0u128; 9]; + + z[0] = m(a[0], b[0]); + z[1] = m(a[0], b[1]) + m(a[1], b[0]); + z[2] = m(a[0], b[2]) + m(a[1], b[1]) + m(a[2], b[0]); + z[3] = m(a[0], b[3]) + m(a[1], b[2]) + m(a[2], b[1]) + m(a[3], b[0]); + z[4] = m(a[0], b[4]) + m(a[1], b[3]) + m(a[2], b[2]) + m(a[3], b[1]) + m(a[4], b[0]); + z[5] = m(a[1], b[4]) + m(a[2], b[3]) + m(a[3], b[2]) + m(a[4], b[1]); + z[6] = m(a[2], b[4]) + m(a[3], b[3]) + m(a[4], b[2]); + z[7] = m(a[3], b[4]) + m(a[4], b[3]); + z[8] = m(a[4], b[4]); + + z + } + + /// Compute `a^2` + #[inline(always)] + #[rustfmt::skip] // keep alignment of return calculations + fn square_internal(a: &Scalar52) -> [u128; 9] { + let aa = [ + a[0] * 2, + a[1] * 2, + a[2] * 2, + a[3] * 2, + ]; + + [ + m( a[0], a[0]), + m(aa[0], a[1]), + m(aa[0], a[2]) + m( a[1], a[1]), + m(aa[0], a[3]) + m(aa[1], a[2]), + m(aa[0], a[4]) + m(aa[1], a[3]) + m( a[2], a[2]), + m(aa[1], a[4]) + m(aa[2], a[3]), + m(aa[2], a[4]) + m( a[3], a[3]), + m(aa[3], a[4]), + m(a[4], a[4]) + ] + } + + /// Compute `limbs/R` (mod l), where R is the Montgomery modulus 2^260 + #[inline(always)] + #[rustfmt::skip] // keep alignment of n* and r* calculations + pub (crate) fn montgomery_reduce(limbs: &[u128; 9]) -> Scalar52 { + + #[inline(always)] + fn part1(sum: u128) -> (u128, u64) { + let p = (sum as u64).wrapping_mul(constants::LFACTOR) & ((1u64 << 52) - 1); + ((sum + m(p, constants::L[0])) >> 52, p) + } + + #[inline(always)] + fn part2(sum: u128) -> (u128, u64) { + let w = (sum as u64) & ((1u64 << 52) - 1); + (sum >> 52, w) + } + + // note: l[3] is zero, so its multiples can be skipped + let l = &constants::L; + + // the first half computes the Montgomery adjustment factor n, and begins adding n*l to make limbs divisible by R + let (carry, n0) = part1( limbs[0]); + let (carry, n1) = part1(carry + limbs[1] + m(n0, l[1])); + let (carry, n2) = part1(carry + limbs[2] + m(n0, l[2]) + m(n1, l[1])); + let (carry, n3) = part1(carry + limbs[3] + m(n1, l[2]) + m(n2, l[1])); + let (carry, n4) = part1(carry + limbs[4] + m(n0, l[4]) + m(n2, l[2]) + m(n3, l[1])); + + // limbs is divisible by R now, so we can divide by R by simply storing the upper half as the result + let (carry, r0) = part2(carry + limbs[5] + m(n1, l[4]) + m(n3, l[2]) + m(n4, l[1])); + let (carry, r1) = part2(carry + limbs[6] + m(n2,l[4]) + m(n4, l[2])); + let (carry, r2) = part2(carry + limbs[7] + m(n3, l[4]) ); + let (carry, r3) = part2(carry + limbs[8] + m(n4, l[4])); + let r4 = carry as u64; + + // result may be >= l, so attempt to subtract l + Scalar52::sub(&Scalar52([r0, r1, r2, r3, r4]), l) + } + + /// Compute `a * b` (mod l) + #[inline(never)] + pub fn mul(a: &Scalar52, b: &Scalar52) -> Scalar52 { + let ab = Scalar52::montgomery_reduce(&Scalar52::mul_internal(a, b)); + Scalar52::montgomery_reduce(&Scalar52::mul_internal(&ab, &constants::RR)) + } + + /// Compute `a^2` (mod l) + #[inline(never)] + #[allow(dead_code)] // XXX we don't expose square() via the Scalar API + pub fn square(&self) -> Scalar52 { + let aa = Scalar52::montgomery_reduce(&Scalar52::square_internal(self)); + Scalar52::montgomery_reduce(&Scalar52::mul_internal(&aa, &constants::RR)) + } + + /// Compute `(a * b) / R` (mod l), where R is the Montgomery modulus 2^260 + #[inline(never)] + pub fn montgomery_mul(a: &Scalar52, b: &Scalar52) -> Scalar52 { + Scalar52::montgomery_reduce(&Scalar52::mul_internal(a, b)) + } + + /// Compute `(a^2) / R` (mod l) in Montgomery form, where R is the Montgomery modulus 2^260 + #[inline(never)] + pub fn montgomery_square(&self) -> Scalar52 { + Scalar52::montgomery_reduce(&Scalar52::square_internal(self)) + } + + /// Puts a Scalar52 in to Montgomery form, i.e. computes `a*R (mod l)` + #[inline(never)] + pub fn as_montgomery(&self) -> Scalar52 { + Scalar52::montgomery_mul(self, &constants::RR) + } + + /// Takes a Scalar52 out of Montgomery form, i.e. computes `a/R (mod l)` + #[allow(clippy::wrong_self_convention)] + #[inline(never)] + pub fn from_montgomery(&self) -> Scalar52 { + let mut limbs = [0u128; 9]; + for i in 0..5 { + limbs[i] = self[i] as u128; + } + Scalar52::montgomery_reduce(&limbs) + } +} + +#[cfg(test)] +mod test { + use super::*; + + /// Note: x is 2^253-1 which is slightly larger than the largest scalar produced by + /// this implementation (l-1), and should show there are no overflows for valid scalars + /// + /// x = 14474011154664524427946373126085988481658748083205070504932198000989141204991 + /// x = 7237005577332262213973186563042994240801631723825162898930247062703686954002 mod l + /// x = 3057150787695215392275360544382990118917283750546154083604586903220563173085*R mod l in Montgomery form + pub static X: Scalar52 = Scalar52([ + 0x000fffffffffffff, + 0x000fffffffffffff, + 0x000fffffffffffff, + 0x000fffffffffffff, + 0x00001fffffffffff, + ]); + + /// x^2 = 3078544782642840487852506753550082162405942681916160040940637093560259278169 mod l + pub static XX: Scalar52 = Scalar52([ + 0x0001668020217559, + 0x000531640ffd0ec0, + 0x00085fd6f9f38a31, + 0x000c268f73bb1cf4, + 0x000006ce65046df0, + ]); + + /// x^2 = 4413052134910308800482070043710297189082115023966588301924965890668401540959*R mod l in Montgomery form + pub static XX_MONT: Scalar52 = Scalar52([ + 0x000c754eea569a5c, + 0x00063b6ed36cb215, + 0x0008ffa36bf25886, + 0x000e9183614e7543, + 0x0000061db6c6f26f, + ]); + + /// y = 6145104759870991071742105800796537629880401874866217824609283457819451087098 + pub static Y: Scalar52 = Scalar52([ + 0x000b75071e1458fa, + 0x000bf9d75e1ecdac, + 0x000433d2baf0672b, + 0x0005fffcc11fad13, + 0x00000d96018bb825, + ]); + + /// x*y = 36752150652102274958925982391442301741 mod l + pub static XY: Scalar52 = Scalar52([ + 0x000ee6d76ba7632d, + 0x000ed50d71d84e02, + 0x00000000001ba634, + 0x0000000000000000, + 0x0000000000000000, + ]); + + /// x*y = 658448296334113745583381664921721413881518248721417041768778176391714104386*R mod l in Montgomery form + pub static XY_MONT: Scalar52 = Scalar52([ + 0x0006d52bf200cfd5, + 0x00033fb1d7021570, + 0x000f201bc07139d8, + 0x0001267e3e49169e, + 0x000007b839c00268, + ]); + + /// a = 2351415481556538453565687241199399922945659411799870114962672658845158063753 + pub static A: Scalar52 = Scalar52([ + 0x0005236c07b3be89, + 0x0001bc3d2a67c0c4, + 0x000a4aa782aae3ee, + 0x0006b3f6e4fec4c4, + 0x00000532da9fab8c, + ]); + + /// b = 4885590095775723760407499321843594317911456947580037491039278279440296187236 + pub static B: Scalar52 = Scalar52([ + 0x000d3fae55421564, + 0x000c2df24f65a4bc, + 0x0005b5587d69fb0b, + 0x00094c091b013b3b, + 0x00000acd25605473, + ]); + + /// a+b = 0 + /// a-b = 4702830963113076907131374482398799845891318823599740229925345317690316127506 + pub static AB: Scalar52 = Scalar52([ + 0x000a46d80f677d12, + 0x0003787a54cf8188, + 0x0004954f0555c7dc, + 0x000d67edc9fd8989, + 0x00000a65b53f5718, + ]); + + // c = (2^512 - 1) % l = 1627715501170711445284395025044413883736156588369414752970002579683115011840 + pub static C: Scalar52 = Scalar52([ + 0x000611e3449c0f00, + 0x000a768859347a40, + 0x0007f5be65d00e1b, + 0x0009a3dceec73d21, + 0x00000399411b7c30, + ]); + + #[test] + fn mul_max() { + let res = Scalar52::mul(&X, &X); + for i in 0..5 { + assert!(res[i] == XX[i]); + } + } + + #[test] + fn square_max() { + let res = X.square(); + for i in 0..5 { + assert!(res[i] == XX[i]); + } + } + + #[test] + fn montgomery_mul_max() { + let res = Scalar52::montgomery_mul(&X, &X); + for i in 0..5 { + assert!(res[i] == XX_MONT[i]); + } + } + + #[test] + fn montgomery_square_max() { + let res = X.montgomery_square(); + for i in 0..5 { + assert!(res[i] == XX_MONT[i]); + } + } + + #[test] + fn mul() { + let res = Scalar52::mul(&X, &Y); + for i in 0..5 { + assert!(res[i] == XY[i]); + } + } + + #[test] + fn montgomery_mul() { + let res = Scalar52::montgomery_mul(&X, &Y); + for i in 0..5 { + assert!(res[i] == XY_MONT[i]); + } + } + + #[test] + fn add() { + let res = Scalar52::add(&A, &B); + let zero = Scalar52::ZERO; + for i in 0..5 { + assert!(res[i] == zero[i]); + } + } + + #[test] + fn sub() { + let res = Scalar52::sub(&A, &B); + for i in 0..5 { + assert!(res[i] == AB[i]); + } + } + + #[test] + fn from_bytes_wide() { + let bignum = [255u8; 64]; // 2^512 - 1 + let reduced = Scalar52::from_bytes_wide(&bignum); + for i in 0..5 { + assert!(reduced[i] == C[i]); + } + } +} diff --git a/curve25519-elligator2/src/backend/vector/avx2/constants.rs b/curve25519-elligator2/src/backend/vector/avx2/constants.rs new file mode 100644 index 00000000..0d258d70 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/avx2/constants.rs @@ -0,0 +1,1191 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! This module contains constants used by the AVX2 backend. + +use crate::backend::vector::packed_simd::u32x8; + +use crate::backend::vector::avx2::edwards::{CachedPoint, ExtendedPoint}; +use crate::backend::vector::avx2::field::FieldElement2625x4; + +#[cfg(feature = "precomputed-tables")] +use crate::window::NafLookupTable8; + +/// The identity element as an `ExtendedPoint`. +pub(crate) static EXTENDEDPOINT_IDENTITY: ExtendedPoint = ExtendedPoint(FieldElement2625x4([ + u32x8::new_const(0, 1, 0, 0, 1, 0, 0, 0), + u32x8::splat_const::<0>(), + u32x8::splat_const::<0>(), + u32x8::splat_const::<0>(), + u32x8::splat_const::<0>(), +])); + +/// The identity element as a `CachedPoint`. +pub(crate) static CACHEDPOINT_IDENTITY: CachedPoint = CachedPoint(FieldElement2625x4([ + u32x8::new_const(121647, 121666, 0, 0, 243332, 67108845, 0, 33554431), + u32x8::new_const(67108864, 0, 33554431, 0, 0, 67108863, 0, 33554431), + u32x8::new_const(67108863, 0, 33554431, 0, 0, 67108863, 0, 33554431), + u32x8::new_const(67108863, 0, 33554431, 0, 0, 67108863, 0, 33554431), + u32x8::new_const(67108863, 0, 33554431, 0, 0, 67108863, 0, 33554431), +])); + +/// The low limbs of (2p, 2p, 2p, 2p), so that +/// ```ascii,no_run +/// (2p, 2p, 2p, 2p) = [P_TIMES_2_LO, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI] +/// ``` +pub(crate) static P_TIMES_2_LO: u32x8 = u32x8::new_const( + 67108845 << 1, + 67108845 << 1, + 33554431 << 1, + 33554431 << 1, + 67108845 << 1, + 67108845 << 1, + 33554431 << 1, + 33554431 << 1, +); + +/// The high limbs of (2p, 2p, 2p, 2p), so that +/// ```ascii,no_run +/// (2p, 2p, 2p, 2p) = [P_TIMES_2_LO, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI] +/// ``` +pub(crate) static P_TIMES_2_HI: u32x8 = u32x8::new_const( + 67108863 << 1, + 67108863 << 1, + 33554431 << 1, + 33554431 << 1, + 67108863 << 1, + 67108863 << 1, + 33554431 << 1, + 33554431 << 1, +); + +/// The low limbs of (16p, 16p, 16p, 16p), so that +/// ```ascii,no_run +/// (16p, 16p, 16p, 16p) = [P_TIMES_16_LO, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI] +/// ``` +pub(crate) static P_TIMES_16_LO: u32x8 = u32x8::new_const( + 67108845 << 4, + 67108845 << 4, + 33554431 << 4, + 33554431 << 4, + 67108845 << 4, + 67108845 << 4, + 33554431 << 4, + 33554431 << 4, +); + +/// The high limbs of (16p, 16p, 16p, 16p), so that +/// ```ascii,no_run +/// (16p, 16p, 16p, 16p) = [P_TIMES_16_LO, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI] +/// ``` +pub(crate) static P_TIMES_16_HI: u32x8 = u32x8::new_const( + 67108863 << 4, + 67108863 << 4, + 33554431 << 4, + 33554431 << 4, + 67108863 << 4, + 67108863 << 4, + 33554431 << 4, + 33554431 << 4, +); + +/// Odd multiples of the Ed25519 basepoint: +#[cfg(feature = "precomputed-tables")] +pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8 = NafLookupTable8([ + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 3571425, 10045002, 19036563, 1096096, 243332, 65897020, 0, 28963681, + ), + u32x8::new_const( + 30896895, 63055514, 1614915, 5095970, 0, 53791688, 0, 31258312, + ), + u32x8::new_const( + 13347627, 40339464, 2236269, 11185503, 0, 22520087, 0, 8659512, + ), + u32x8::new_const( + 11125413, 29139905, 32037254, 28360723, 0, 64556417, 0, 9635759, + ), + u32x8::new_const( + 33268144, 47262491, 4336918, 15795740, 0, 22027545, 0, 4846528, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 47099681, 31447946, 29365447, 24740513, 42991046, 18317844, 16051644, 21404226, + ), + u32x8::new_const( + 31708133, 28909527, 2366091, 13703791, 469246, 54159622, 2601402, 32988002, + ), + u32x8::new_const( + 63432457, 30251794, 15163516, 18491340, 28144087, 35605455, 13682295, 18474872, + ), + u32x8::new_const( + 12221607, 4967598, 26061980, 26008006, 20226147, 9726961, 17410, 18051083, + ), + u32x8::new_const( + 60569645, 62487085, 11911242, 21920922, 4092105, 38186967, 22431483, 31366585, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 18147205, 62587998, 2554617, 536692, 11924528, 26674131, 17645433, 24341419, + ), + u32x8::new_const( + 11573357, 27579485, 31491870, 29000885, 10800976, 51902791, 28076395, 20464029, + ), + u32x8::new_const( + 56031649, 10856669, 11791193, 26769430, 25306956, 5922200, 6630685, 9385098, + ), + u32x8::new_const( + 31319348, 23906711, 16290213, 32142166, 61106354, 17181823, 3548308, 12022566, + ), + u32x8::new_const( + 5904298, 50218605, 11826440, 5492249, 10379071, 3472255, 172742, 31948344, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 10625852, 15193821, 22918394, 23676410, 53695416, 54987793, 10067515, 11747680, + ), + u32x8::new_const( + 65013325, 1309652, 29616320, 28922974, 60360891, 19621771, 9938982, 30406429, + ), + u32x8::new_const( + 54967954, 65931918, 5595602, 25719523, 64909864, 30566415, 15945272, 8495317, + ), + u32x8::new_const( + 1167157, 55265018, 11507029, 31641054, 43497904, 2367338, 12937761, 27517066, + ), + u32x8::new_const( + 656704, 2544994, 13006713, 480979, 38471594, 62541240, 25353597, 11531760, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 22176662, 3984313, 27495285, 4110608, 2909584, 30594106, 15677919, 2549183, + ), + u32x8::new_const( + 33979105, 62269905, 2071511, 6894756, 53189950, 47232857, 6408191, 6123225, + ), + u32x8::new_const( + 32553873, 63948030, 12612401, 3633166, 24054373, 37626618, 14481327, 8520484, + ), + u32x8::new_const( + 56552486, 10749438, 12034813, 28811946, 1445640, 36755601, 12104575, 10257833, + ), + u32x8::new_const( + 22795808, 48761311, 1136056, 9380768, 1411523, 5341811, 27318329, 9686767, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 21157200, 39156966, 20473176, 4934657, 61478183, 45121537, 5429856, 13035023, + ), + u32x8::new_const( + 7954529, 58789246, 31440083, 7054221, 38438565, 36856107, 1364112, 14548122, + ), + u32x8::new_const( + 26120083, 36321360, 4919997, 31687496, 33757765, 36237559, 15243054, 32163861, + ), + u32x8::new_const( + 25878307, 46544824, 19455951, 2414935, 16844726, 56521560, 32680554, 26660660, + ), + u32x8::new_const( + 48360220, 43407178, 12187042, 24925816, 7423722, 25746484, 12814654, 17395963, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 63153652, 32195955, 4087908, 8431689, 30392384, 47203165, 8986649, 9053039, + ), + u32x8::new_const( + 63659241, 47988767, 2931872, 19953600, 11747107, 51610101, 20952181, 13364887, + ), + u32x8::new_const( + 3659197, 58790649, 5930099, 2605312, 28477896, 580728, 20579735, 2610622, + ), + u32x8::new_const( + 41781607, 17161358, 10690531, 24368015, 47027031, 36742339, 5414694, 13156365, + ), + u32x8::new_const( + 13237853, 51182423, 8954802, 29006542, 22643989, 56896541, 22830593, 10289708, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 1401265, 58846825, 30911620, 32239180, 15391552, 15200821, 6339309, 16403588, + ), + u32x8::new_const( + 55913797, 29541724, 1664461, 21709410, 38470488, 47097092, 17674945, 32666066, + ), + u32x8::new_const( + 22844482, 10797709, 27548106, 31638735, 34500968, 26611503, 19727211, 13160873, + ), + u32x8::new_const( + 31485204, 14496164, 13981208, 10276888, 5748808, 35024436, 2740987, 7479021, + ), + u32x8::new_const( + 58541207, 14866135, 32344041, 545930, 62661488, 6941250, 27940205, 11976112, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 39849808, 44781685, 15697329, 24387845, 12501486, 50260092, 23199481, 31929024, + ), + u32x8::new_const( + 24823070, 27956017, 27034296, 10316465, 47664045, 11152446, 15719183, 30181617, + ), + u32x8::new_const( + 20771189, 19969144, 31433937, 19185213, 27565920, 10384445, 2893359, 9255362, + ), + u32x8::new_const( + 42894974, 11925545, 32134441, 32738810, 55916336, 32479272, 19563550, 5511385, + ), + u32x8::new_const( + 17857161, 47809169, 14564114, 27997751, 33024640, 38669671, 31956536, 27313245, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 58237774, 15917425, 18872208, 19394230, 17374297, 6101419, 4839741, 6596900, + ), + u32x8::new_const( + 66947393, 15744215, 18368993, 17750160, 41006525, 9205497, 2629667, 32170865, + ), + u32x8::new_const( + 66481381, 1919414, 28338762, 7372967, 33819153, 4156199, 27126309, 12739816, + ), + u32x8::new_const( + 44117158, 58545296, 22521371, 11809712, 28998792, 50731010, 30215699, 25748377, + ), + u32x8::new_const( + 23561284, 4160244, 9035405, 24895184, 39761639, 59253416, 8684759, 22487864, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 12671134, 56419053, 16092401, 30038207, 4002647, 47822606, 7151311, 28430768, + ), + u32x8::new_const( + 61041684, 35765374, 30598048, 19666539, 44150175, 40140037, 290469, 28442674, + ), + u32x8::new_const( + 18847796, 1371617, 33316881, 13199936, 43646578, 17068881, 12074900, 1537415, + ), + u32x8::new_const( + 10052225, 38316070, 27469797, 5297537, 50725570, 20435349, 10339121, 2779737, + ), + u32x8::new_const( + 18372189, 15466385, 24762130, 22217964, 23503887, 47844464, 10415034, 2606889, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 55082775, 45300503, 16032654, 5964396, 17743504, 24634761, 19493066, 5184611, + ), + u32x8::new_const( + 50172633, 35093294, 10040575, 23616256, 4543900, 61852191, 4049821, 7423669, + ), + u32x8::new_const( + 20295398, 40009376, 10487190, 15670429, 51972856, 58649552, 20436392, 3432497, + ), + u32x8::new_const( + 35189420, 54117751, 12825868, 6283038, 27540739, 30648758, 22658912, 9466689, + ), + u32x8::new_const( + 51737549, 40725785, 17409814, 25201086, 21156239, 34176168, 26814520, 5956424, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 8211442, 8014184, 6260823, 22108096, 32182620, 51844847, 2466270, 28582231, + ), + u32x8::new_const( + 27199739, 3848333, 31738017, 10892045, 4963982, 65391770, 32551997, 28906469, + ), + u32x8::new_const( + 16606846, 32207068, 26404535, 7614129, 45416902, 65584718, 13821785, 2646060, + ), + u32x8::new_const( + 36090634, 57981287, 32247670, 22837502, 31003861, 55448117, 6062915, 20369975, + ), + u32x8::new_const( + 27381403, 50578107, 522631, 29521058, 31137497, 40220737, 27628049, 1824195, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 59402443, 17056879, 29262689, 6131785, 52551472, 43367471, 29423199, 18899208, + ), + u32x8::new_const( + 5749414, 43514612, 11365899, 21514624, 65591890, 60945892, 19841732, 5628567, + ), + u32x8::new_const( + 19334369, 52500268, 12307673, 5267367, 3212103, 9035822, 29142161, 30520954, + ), + u32x8::new_const( + 57261330, 6819646, 22089161, 9800373, 55155453, 62250856, 13766735, 25244545, + ), + u32x8::new_const( + 54370226, 61888301, 24496089, 2540581, 65637506, 60274355, 18154273, 11687259, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 12521903, 26014045, 13995625, 33360175, 23605474, 7376434, 27229267, 17195036, + ), + u32x8::new_const( + 59482891, 10074423, 574357, 3857753, 61377787, 50306685, 5241065, 20234396, + ), + u32x8::new_const( + 23674717, 6997172, 20771841, 16858511, 40565304, 29973136, 7049812, 14585010, + ), + u32x8::new_const( + 1427477, 13295732, 31762066, 31499740, 60419925, 54666164, 22009424, 8089609, + ), + u32x8::new_const( + 58154031, 41593020, 15342328, 957047, 38937260, 37037498, 24871992, 32973409, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 30654745, 51286025, 21206982, 2433562, 12780105, 31732574, 33087964, 33081189, + ), + u32x8::new_const( + 66640017, 42720009, 16567620, 15300745, 1530367, 33001123, 20930247, 21042661, + ), + u32x8::new_const( + 15003356, 5294119, 22985605, 18928772, 32628461, 18230172, 14773298, 27193722, + ), + u32x8::new_const( + 27555, 65346287, 17017174, 7837720, 21499787, 42855613, 22474984, 13675085, + ), + u32x8::new_const( + 24164369, 50130116, 5973149, 24152073, 1577334, 25400030, 18648484, 32228854, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 49518649, 59119280, 31670678, 20396561, 61728330, 651402, 176032, 9529498, + ), + u32x8::new_const( + 61765532, 9082232, 32794568, 15526956, 48543100, 32614212, 19001206, 25680229, + ), + u32x8::new_const( + 32086091, 10373081, 8996131, 31822823, 35788988, 49973190, 30542040, 17858455, + ), + u32x8::new_const( + 48130197, 58121889, 27753291, 29923268, 54448075, 43300790, 9336565, 15770022, + ), + u32x8::new_const( + 57725546, 20557498, 9366233, 16023566, 16189031, 2837363, 24315301, 27003505, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 28286608, 10767548, 18220739, 5413236, 48253387, 58255702, 11864864, 28527159, + ), + u32x8::new_const( + 45038176, 58655197, 25648758, 10951484, 42564382, 34542843, 23146954, 22234334, + ), + u32x8::new_const( + 14858710, 24978793, 15040559, 4379220, 47621477, 40271440, 15650420, 1998736, + ), + u32x8::new_const( + 24106391, 9626149, 344505, 25253814, 34579800, 59687089, 25718289, 25904133, + ), + u32x8::new_const( + 1981195, 37751302, 26132048, 1764722, 13288231, 28808622, 12531301, 18292949, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 13869851, 31448904, 14963539, 7581293, 20536485, 35021083, 21257574, 33356609, + ), + u32x8::new_const( + 36903364, 18429241, 11097857, 5943856, 60583077, 40015815, 30509523, 31915271, + ), + u32x8::new_const( + 49161801, 40681915, 67892, 25454357, 22779677, 25798439, 15964829, 5863227, + ), + u32x8::new_const( + 60810637, 4496471, 5217137, 14095116, 50942411, 50712663, 2507380, 26844507, + ), + u32x8::new_const( + 34579752, 53519385, 10859797, 18816024, 42552864, 39478521, 6783896, 17277037, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 43287109, 27900723, 33182187, 2766754, 17041989, 1018260, 33392790, 4830032, + ), + u32x8::new_const( + 60194178, 30788903, 24728888, 14513195, 20897010, 28843233, 20111980, 17475240, + ), + u32x8::new_const( + 46042274, 19257042, 4628173, 31649727, 27388316, 66631493, 11541886, 6408028, + ), + u32x8::new_const( + 57024680, 49536568, 32050358, 31321917, 17437691, 49672356, 2884755, 20493991, + ), + u32x8::new_const( + 59553007, 46782643, 29001173, 1814088, 21930692, 51319706, 14965872, 30748046, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 16441817, 36111849, 6900424, 602234, 46522199, 16441484, 8135070, 21726541, + ), + u32x8::new_const( + 37711225, 32701959, 11679112, 13125533, 32154135, 9407918, 26554289, 620848, + ), + u32x8::new_const( + 19233407, 30086864, 14679568, 2797374, 4892806, 7993077, 247658, 5632804, + ), + u32x8::new_const( + 37427262, 26675495, 27125659, 13496131, 50718473, 40115609, 28505351, 27837393, + ), + u32x8::new_const( + 196819, 18410429, 7070012, 21691388, 29763371, 24754123, 9727048, 10930179, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 28319289, 40734650, 16225680, 24739184, 64272368, 35356897, 7866648, 13635853, + ), + u32x8::new_const( + 34165295, 48328447, 27041670, 23643655, 48949950, 52963288, 30411133, 6045174, + ), + u32x8::new_const( + 18583559, 41649834, 9813585, 26098520, 25682734, 26733526, 19276490, 10654728, + ), + u32x8::new_const( + 34867476, 52715968, 5694571, 13380978, 15134994, 1831255, 8608001, 17266401, + ), + u32x8::new_const( + 59925903, 44282172, 27802465, 1855069, 14234749, 36635487, 11302294, 10938429, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 8373273, 49064494, 4932071, 32997499, 38472880, 29335908, 14504412, 22460029, + ), + u32x8::new_const( + 31795930, 50785923, 25835990, 25790073, 65669841, 11360450, 9969157, 9008164, + ), + u32x8::new_const( + 50262498, 45869261, 16124434, 15336007, 882762, 42522623, 11277198, 26296377, + ), + u32x8::new_const( + 42332732, 59129236, 14452816, 567985, 208061, 34722729, 32008143, 14828749, + ), + u32x8::new_const( + 17937794, 36846032, 32102665, 4442466, 19745435, 31633451, 7146411, 15812027, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 30741269, 38648744, 12562645, 30092623, 25073992, 28730659, 27911745, 30000958, + ), + u32x8::new_const( + 2859794, 25991700, 17776078, 27091930, 2328322, 60061146, 18581824, 18039008, + ), + u32x8::new_const( + 58206333, 17917354, 1972306, 11853766, 2655376, 60543390, 18416710, 13287440, + ), + u32x8::new_const( + 62746330, 61423885, 21246577, 2266675, 60099139, 14804707, 14772234, 20679434, + ), + u32x8::new_const( + 26987698, 15488817, 715616, 2339565, 51980752, 17333865, 21965103, 10839820, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 18672548, 57660959, 16042910, 19519287, 62865851, 17580961, 26628347, 23774759, + ), + u32x8::new_const( + 368070, 3464471, 25888304, 30370559, 52396053, 45426828, 28745251, 9246829, + ), + u32x8::new_const( + 29090099, 57950037, 23104657, 4903923, 10987778, 56163684, 23621539, 10332760, + ), + u32x8::new_const( + 53338235, 44851161, 21606845, 31069622, 4243630, 34464392, 11286454, 5802022, + ), + u32x8::new_const( + 46710757, 63389067, 11642865, 1980986, 12967337, 28162061, 3854192, 30432268, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 12179834, 41005450, 12809619, 33525228, 4624405, 46957889, 16968743, 11827816, + ), + u32x8::new_const( + 51521162, 12466775, 31791271, 15303651, 49798465, 62714504, 6509600, 12918560, + ), + u32x8::new_const( + 20445559, 1756449, 28848701, 7920171, 9835040, 5900071, 28757409, 12376688, + ), + u32x8::new_const( + 18259496, 14281012, 21767026, 10232236, 20000226, 12400540, 4104902, 23570543, + ), + u32x8::new_const( + 3687440, 26546648, 13328821, 26841081, 49822734, 22334054, 244496, 24862543, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 59523541, 62195428, 3853227, 13954801, 12387708, 47627615, 27221350, 17899572, + ), + u32x8::new_const( + 63193587, 36343307, 14595132, 6880795, 1364792, 37648434, 3259017, 20536046, + ), + u32x8::new_const( + 30362834, 10440372, 9574624, 11729232, 63861613, 21748389, 5530846, 2721586, + ), + u32x8::new_const( + 18339760, 1550632, 17170271, 25732971, 28459263, 63142237, 21642345, 31557672, + ), + u32x8::new_const( + 10611282, 5204623, 18049257, 214175, 19432723, 49809070, 26010406, 27449522, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 19770733, 26478685, 9464541, 29158041, 28604307, 45196604, 7586524, 6641859, + ), + u32x8::new_const( + 65654484, 52230498, 30886612, 19112823, 47271809, 38942611, 16020035, 10773481, + ), + u32x8::new_const( + 27464323, 54451016, 20646645, 17732915, 23008717, 53626684, 3253189, 15614410, + ), + u32x8::new_const( + 52381752, 40693008, 7063024, 28469981, 51159478, 44543211, 19941777, 5985451, + ), + u32x8::new_const( + 13553668, 35524849, 14788737, 1883845, 12385775, 47958835, 29135466, 1776722, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 36719806, 20827965, 23175373, 32996806, 42041892, 65708790, 5467143, 20884008, + ), + u32x8::new_const( + 43256281, 40770646, 17244063, 31959819, 64366384, 43544617, 25057754, 12628720, + ), + u32x8::new_const( + 17337782, 58472057, 27906934, 15305274, 30292418, 39284317, 16946773, 24806712, + ), + u32x8::new_const( + 6485126, 32447403, 16261486, 13561940, 49439635, 10738368, 16419889, 8897231, + ), + u32x8::new_const( + 44812203, 40122262, 25496058, 2759794, 25295304, 52178368, 24154195, 29334408, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 42307254, 57217102, 1088936, 3832827, 33905401, 23130334, 6958056, 12622851, + ), + u32x8::new_const( + 3881189, 14870059, 19712830, 6071598, 38147944, 60776394, 3427938, 13765703, + ), + u32x8::new_const( + 7666911, 24227591, 17077136, 22967588, 6874639, 30915523, 11451695, 24292224, + ), + u32x8::new_const( + 13659529, 31984463, 28764736, 20506164, 64729627, 49321636, 28284636, 25472371, + ), + u32x8::new_const( + 39360308, 42281399, 9446504, 868960, 49227724, 21351115, 30561851, 11292096, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 7071115, 46444090, 5387916, 15432877, 27226682, 41506862, 2398278, 3978240, + ), + u32x8::new_const( + 51009614, 54216973, 24368938, 31392616, 38456150, 62313644, 6729154, 99724, + ), + u32x8::new_const( + 17474332, 62857913, 2619930, 30659308, 18268181, 32809239, 22826292, 24561895, + ), + u32x8::new_const( + 38187020, 67003092, 14118280, 16500577, 18808560, 64983716, 25712929, 32518261, + ), + u32x8::new_const( + 25735813, 62284262, 10824872, 20558596, 48149681, 31162667, 22608274, 26285185, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 963440, 63742255, 10230323, 25515008, 32506414, 6105697, 25980317, 24645129, + ), + u32x8::new_const( + 7162189, 8101249, 14679265, 33443386, 2002396, 8541405, 19442276, 4795881, + ), + u32x8::new_const( + 8116694, 51463069, 4415528, 25599140, 55805721, 39582709, 6719436, 30033839, + ), + u32x8::new_const( + 14468202, 42181869, 25188826, 9639755, 47546189, 62711146, 32762447, 18338064, + ), + u32x8::new_const( + 33880058, 32810909, 8969931, 13095238, 38360605, 40138517, 9246134, 4928058, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 63655588, 17883670, 9410246, 26162761, 5000571, 7349225, 23785252, 32751089, + ), + u32x8::new_const( + 28568737, 10733123, 9342397, 21570673, 54096560, 32467591, 20494687, 21511513, + ), + u32x8::new_const( + 47675157, 47932807, 29250946, 15672208, 59760469, 9945465, 14939287, 18437405, + ), + u32x8::new_const( + 37985267, 8609815, 31573002, 3373596, 47828883, 20834216, 13248616, 24154292, + ), + u32x8::new_const( + 5543543, 29553242, 3386453, 30501150, 25058089, 15236571, 8814395, 32462955, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 39158670, 15322548, 20495103, 3312736, 14557171, 12985179, 8044741, 3176899, + ), + u32x8::new_const( + 24673290, 29693310, 21412266, 18324699, 2154518, 40329021, 17500543, 3954277, + ), + u32x8::new_const( + 36758685, 38738957, 165513, 14691866, 3070475, 10424235, 17096536, 16896898, + ), + u32x8::new_const( + 59790459, 43094586, 8720681, 10423589, 1122030, 31545615, 4463786, 31811293, + ), + u32x8::new_const( + 49778992, 60881044, 20509974, 5832494, 64155961, 31483358, 4511231, 20307815, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 2863373, 40876242, 26865913, 24067353, 15726407, 40919070, 12953902, 9931535, + ), + u32x8::new_const( + 60934877, 42512204, 21649141, 21945190, 52211954, 60984193, 7046207, 5363493, + ), + u32x8::new_const( + 4205971, 64068464, 18197273, 7327176, 51527794, 21166920, 20669933, 11828242, + ), + u32x8::new_const( + 59782815, 49617225, 15379924, 457923, 9320508, 21498914, 3242540, 31563182, + ), + u32x8::new_const( + 27714753, 8664670, 3366162, 26338598, 56775518, 25796006, 13129151, 21388876, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 59276548, 49972346, 16795002, 33455915, 48430097, 53857205, 18627071, 32474471, + ), + u32x8::new_const( + 42160315, 50705892, 13530540, 28012698, 19833221, 55886870, 20191784, 9644313, + ), + u32x8::new_const( + 20372416, 28414713, 24084234, 31804096, 33815377, 36131001, 17251241, 18291088, + ), + u32x8::new_const( + 56234667, 14920441, 2033267, 29572003, 1724043, 45519699, 17873735, 501988, + ), + u32x8::new_const( + 50031659, 31517850, 15697583, 1016845, 43104661, 54769582, 8008601, 27257051, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 52951491, 66542164, 14853573, 30444631, 12045973, 24321813, 16545674, 18160646, + ), + u32x8::new_const( + 60107911, 1126003, 5947677, 19486116, 41119984, 30860440, 7935395, 13354438, + ), + u32x8::new_const( + 17841328, 11063269, 1664538, 26687568, 6268968, 22280371, 17275484, 4523163, + ), + u32x8::new_const( + 15886041, 56799482, 15446552, 21712778, 1005290, 17827215, 4978741, 6854882, + ), + u32x8::new_const( + 34319277, 47731002, 20321804, 28544575, 29591814, 63376351, 24754545, 26001714, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 66783087, 5234346, 46102, 8566476, 19947339, 20180418, 25398238, 3726678, + ), + u32x8::new_const( + 63890180, 46380965, 20674069, 5366544, 59661487, 48406612, 31533614, 7071217, + ), + u32x8::new_const( + 13104676, 1406631, 24326736, 19854367, 61039528, 11019904, 31967425, 19219275, + ), + u32x8::new_const( + 39003597, 30143957, 15351834, 8639435, 57309582, 61436794, 15830475, 10090318, + ), + u32x8::new_const( + 45923044, 6700175, 99413, 21263025, 23762647, 53905481, 6063914, 10065424, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 42822326, 57678669, 4052879, 25452667, 54049411, 2373092, 22337016, 7701046, + ), + u32x8::new_const( + 44382355, 43307377, 16761537, 30373573, 49790216, 23230748, 25655306, 10519391, + ), + u32x8::new_const( + 919475, 59371245, 1273450, 25558666, 9724711, 8556709, 25755845, 10887647, + ), + u32x8::new_const( + 25465699, 44651158, 17658392, 11257418, 29735193, 22885150, 7094716, 26828565, + ), + u32x8::new_const( + 48237389, 47661599, 27054393, 7328070, 27280193, 65616691, 23062005, 4170709, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 26535281, 60238317, 30343788, 25790743, 37993933, 24614372, 9523840, 10401918, + ), + u32x8::new_const( + 2783987, 29468958, 4697011, 19804475, 37246678, 46797720, 10261254, 18942252, + ), + u32x8::new_const( + 58135580, 60247753, 25301938, 6844561, 20949454, 39844754, 4552026, 919057, + ), + u32x8::new_const( + 6694071, 44126261, 32285330, 31370180, 24603698, 53328179, 13971149, 5325636, + ), + u32x8::new_const( + 64879487, 582094, 17982081, 19190425, 24951286, 26923842, 29077174, 33286062, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 54863941, 67016431, 1224043, 23371240, 62940074, 52101083, 13523637, 30366406, + ), + u32x8::new_const( + 36324581, 25407485, 18258623, 4698602, 50300544, 2658516, 26300935, 2611030, + ), + u32x8::new_const( + 27183975, 21791014, 18105064, 9875199, 58118912, 54198635, 6400311, 14767984, + ), + u32x8::new_const( + 33918318, 42937962, 14809334, 22136592, 10636588, 29082337, 29829692, 28549776, + ), + u32x8::new_const( + 61080905, 854212, 12202487, 20004503, 9256495, 6903981, 20567109, 347423, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 41391822, 34336880, 22362564, 14247996, 12115604, 41583344, 7639288, 28910945, + ), + u32x8::new_const( + 62066617, 59758859, 26665947, 11614812, 65737664, 45704543, 30324810, 12868376, + ), + u32x8::new_const( + 17491771, 43589814, 9454919, 26047850, 52629282, 39304244, 3868968, 19296062, + ), + u32x8::new_const( + 17826638, 30413590, 32534225, 32741469, 15012391, 14365713, 33039233, 14791399, + ), + u32x8::new_const( + 64115596, 59197067, 32739005, 23275744, 32954320, 22241406, 20788442, 4942942, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 31956192, 59570132, 2784352, 4237732, 47222312, 4860927, 18658867, 15279314, + ), + u32x8::new_const( + 63240583, 28160478, 23524941, 13390861, 66437406, 57718120, 33345312, 28896298, + ), + u32x8::new_const( + 39026193, 46239965, 21440243, 25070488, 64012383, 60999016, 16517060, 29565907, + ), + u32x8::new_const( + 18118181, 60161496, 4212092, 23976240, 36277753, 62363144, 5816868, 16964362, + ), + u32x8::new_const( + 18196138, 62490693, 281468, 7934713, 56027312, 62015725, 4837237, 32932252, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 29885826, 51028067, 30418143, 33438769, 62542283, 39442528, 31535876, 143299, + ), + u32x8::new_const( + 17143063, 56709783, 14451852, 15782104, 32762665, 14047066, 26295037, 5432487, + ), + u32x8::new_const( + 75151, 533606, 7539077, 30926189, 38410914, 23771680, 4872443, 29199566, + ), + u32x8::new_const( + 61522396, 48934708, 16223126, 207380, 11171993, 47975147, 14164574, 352966, + ), + u32x8::new_const( + 15449006, 56530757, 26796528, 12045834, 63738697, 40667227, 33001582, 9101885, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 43331297, 18431341, 25801195, 17267698, 19365485, 57295202, 22218985, 21284590, + ), + u32x8::new_const( + 2429849, 19152559, 10762172, 22564684, 21880390, 66866426, 20357935, 22641906, + ), + u32x8::new_const( + 19771185, 31652693, 3666117, 28136958, 23624283, 55101502, 6313920, 6783662, + ), + u32x8::new_const( + 3487137, 7092443, 11001876, 26196524, 47319246, 44542068, 17594073, 15027760, + ), + u32x8::new_const( + 49563607, 32191113, 4991283, 25400512, 46539152, 4155103, 32368171, 201203, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 20548943, 14334571, 4073874, 6368588, 53208883, 56484515, 15970071, 25561889, + ), + u32x8::new_const( + 49915097, 44030795, 11202344, 29284344, 60258023, 66225712, 8075764, 12383512, + ), + u32x8::new_const( + 45248912, 4933668, 9592153, 5819559, 31030983, 38174071, 32435814, 7442522, + ), + u32x8::new_const( + 62688129, 48218381, 22089545, 12897361, 21050881, 34278889, 7569163, 3225449, + ), + u32x8::new_const( + 19050183, 51089071, 32935757, 22640195, 66122318, 47144608, 18743677, 25177079, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 41186817, 46681702, 31819867, 32997133, 38559207, 27147015, 30293819, 16762988, + ), + u32x8::new_const( + 24154689, 51762873, 23883879, 13510519, 55338250, 61224161, 11663149, 30803960, + ), + u32x8::new_const( + 18104238, 14117824, 11724021, 21362053, 65704761, 35530242, 13498058, 33522849, + ), + u32x8::new_const( + 63812888, 23995539, 28920539, 24005193, 26412223, 36582218, 4251418, 26160309, + ), + u32x8::new_const( + 16822053, 66064082, 3482145, 31979593, 45937188, 54475379, 612917, 7976478, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 46509314, 55327128, 8944536, 274914, 26432930, 53829300, 21192572, 3569894, + ), + u32x8::new_const( + 20919764, 64356651, 30642344, 17215170, 20335124, 11203745, 18663316, 19024174, + ), + u32x8::new_const( + 59297055, 53842463, 3680204, 9806710, 54004169, 51484914, 29807998, 20134199, + ), + u32x8::new_const( + 14781592, 22628010, 26877930, 25880359, 30434803, 190607, 30184292, 8991040, + ), + u32x8::new_const( + 64400983, 64591751, 854562, 28216111, 20010398, 50414793, 9803872, 22687008, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 15091184, 32550863, 8818643, 4244752, 43123513, 64565526, 408838, 13206998, + ), + u32x8::new_const( + 16405061, 60379639, 31489017, 20949281, 27568751, 38734986, 8364264, 12451020, + ), + u32x8::new_const( + 16005217, 58008076, 1406778, 26546927, 39571784, 56365493, 31274296, 8918790, + ), + u32x8::new_const( + 23271122, 19453469, 27718201, 32742670, 234332, 36785342, 22601675, 14331046, + ), + u32x8::new_const( + 40636025, 22442705, 22115403, 23745859, 41164945, 61012, 12499614, 542137, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 62776018, 32835413, 17373246, 17187309, 54469193, 21770290, 15923753, 28996575, + ), + u32x8::new_const( + 59385210, 63082298, 12568449, 8509004, 9483342, 16105238, 5756054, 26890758, + ), + u32x8::new_const( + 53987996, 38201748, 5521661, 19060159, 18663191, 9093637, 27786835, 31189196, + ), + u32x8::new_const( + 65872678, 43635130, 27903055, 25020300, 65772737, 38110437, 5213502, 21909342, + ), + u32x8::new_const( + 4438979, 9680838, 10212446, 4764184, 13235684, 58245995, 20264570, 21024049, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 60835961, 48209103, 31049052, 4688268, 12426713, 59829045, 22302488, 29008521, + ), + u32x8::new_const( + 50401667, 29716596, 23531224, 7581281, 49071895, 6952617, 14934683, 8218256, + ), + u32x8::new_const( + 1601446, 36631413, 31774811, 29625330, 56786114, 8331539, 23129509, 19783344, + ), + u32x8::new_const( + 59514327, 64513110, 1772300, 5701338, 5737511, 16147555, 9461515, 5703271, + ), + u32x8::new_const( + 33072974, 54300426, 11940114, 1308663, 15627555, 4931627, 28443714, 20924342, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 18135013, 20358426, 4922557, 10015355, 65729669, 34786528, 26248549, 29194359, + ), + u32x8::new_const( + 797666, 34997544, 24316856, 25107230, 24612576, 4761401, 15307321, 32404252, + ), + u32x8::new_const( + 16501152, 60565831, 9487105, 9316022, 24986054, 31917592, 3962024, 2501883, + ), + u32x8::new_const( + 63356796, 50432342, 18044926, 30566881, 42032028, 31415202, 13524600, 16119907, + ), + u32x8::new_const( + 3927286, 57022374, 9265437, 21620772, 19481940, 3806938, 24836192, 14572399, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 10785787, 46564798, 368445, 33181384, 5319843, 52687136, 30347110, 29837357, + ), + u32x8::new_const( + 56436732, 47859251, 24141084, 22250712, 59046084, 4963427, 33463413, 17168859, + ), + u32x8::new_const( + 15512044, 6366740, 4737504, 27644548, 30307977, 25037929, 14593903, 12836490, + ), + u32x8::new_const( + 63878897, 34013023, 5860752, 7244096, 3689461, 57012135, 18389096, 11589351, + ), + u32x8::new_const( + 4682110, 36302830, 653422, 22316819, 14081831, 5657024, 11088376, 24110612, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 39907267, 45940262, 24887471, 18342609, 878445, 40456159, 12019082, 345107, + ), + u32x8::new_const( + 12794982, 28893944, 9447505, 11387200, 16961963, 13916996, 10893728, 25898006, + ), + u32x8::new_const( + 44934162, 53465865, 3583620, 1102334, 53917811, 63478576, 2426066, 10389549, + ), + u32x8::new_const( + 45096036, 37595344, 19367718, 20257175, 10280866, 41653449, 27665642, 375926, + ), + u32x8::new_const( + 45847901, 24064074, 32494820, 32204556, 10720704, 51079060, 1297436, 29853825, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 66303987, 36060363, 16494578, 24962147, 11971403, 49538586, 25060560, 1964341, + ), + u32x8::new_const( + 25988481, 27641502, 24909517, 27237087, 66646363, 52777626, 16360849, 10459972, + ), + u32x8::new_const( + 43930529, 34374176, 31225968, 8807030, 10394758, 35904854, 25325589, 19335583, + ), + u32x8::new_const( + 25094697, 34380951, 20051185, 32287161, 11739332, 53887441, 30517319, 26601892, + ), + u32x8::new_const( + 8868546, 35635502, 32513071, 28248087, 51946989, 14222744, 19198839, 23261841, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 51218008, 5070126, 11046681, 5320810, 61212079, 34104447, 23895089, 6460727, + ), + u32x8::new_const( + 39843528, 46278671, 10426120, 25624792, 66658766, 37140083, 28933107, 12969597, + ), + u32x8::new_const( + 59635793, 40220191, 5751421, 173680, 58321825, 740337, 1412847, 7682623, + ), + u32x8::new_const( + 975962, 56440763, 20812276, 22631115, 49095824, 19883130, 2419746, 31043648, + ), + u32x8::new_const( + 66208703, 39669328, 22525915, 3748897, 65994776, 34533552, 8126286, 18326047, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 64176557, 3912400, 19351673, 30068471, 31190055, 24221683, 33142424, 28698542, + ), + u32x8::new_const( + 34784792, 4109933, 3867193, 19557314, 2112512, 32715890, 24550117, 16595976, + ), + u32x8::new_const( + 35542761, 48024875, 10925431, 31526577, 66577735, 23189821, 13375709, 1735095, + ), + u32x8::new_const( + 59699254, 43854093, 29783239, 24777271, 19600372, 39924461, 2896720, 1472185, + ), + u32x8::new_const( + 56389656, 35980854, 33172342, 1370336, 23707480, 57654949, 7850973, 12655016, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 38372660, 57101970, 7044964, 12732710, 57535705, 6043201, 30858914, 10946592, + ), + u32x8::new_const( + 21023468, 6946992, 26403324, 23901823, 35695559, 23440687, 4763891, 6514074, + ), + u32x8::new_const( + 28662273, 30933699, 9352242, 26354829, 37402243, 3145176, 8770289, 525937, + ), + u32x8::new_const( + 54933102, 36695832, 3281859, 4755022, 23043294, 32794379, 15618886, 23602412, + ), + u32x8::new_const( + 9931565, 29897140, 2480737, 24193701, 7833615, 2284939, 893926, 13421882, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 22917795, 22088359, 28978099, 19794863, 60542318, 29878494, 31053731, 9080720, + ), + u32x8::new_const( + 23679072, 52547035, 28424916, 20647332, 4008761, 28267029, 12961289, 1589095, + ), + u32x8::new_const( + 55616194, 26678929, 14998265, 23274397, 54625466, 46244264, 28627706, 33030665, + ), + u32x8::new_const( + 11527330, 6449415, 26531607, 3472938, 41541592, 62607682, 19862690, 20564723, + ), + u32x8::new_const( + 32843805, 49066843, 28425824, 19521495, 48792073, 48242878, 27392443, 13175986, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 16185025, 61537525, 2961305, 1492442, 25123147, 3095034, 31896958, 33089615, + ), + u32x8::new_const( + 64748157, 18336595, 16522231, 25426312, 65718949, 35485695, 30554083, 10205918, + ), + u32x8::new_const( + 39626934, 39271045, 16420458, 9826240, 56483981, 27128085, 3783403, 13360006, + ), + u32x8::new_const( + 30793778, 66771960, 17241420, 6564573, 61102581, 29974476, 32385512, 9011754, + ), + u32x8::new_const( + 28068166, 11862220, 14323567, 12380617, 52090465, 16029056, 24495309, 21409233, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 59411973, 57437124, 11695483, 17586857, 16108987, 43449109, 31098002, 6248476, + ), + u32x8::new_const( + 42258047, 61595931, 29308533, 11742653, 43042345, 27373650, 30165249, 21929989, + ), + u32x8::new_const( + 49907221, 9620337, 21888081, 20981082, 56288861, 61562203, 33223566, 3582446, + ), + u32x8::new_const( + 57535017, 41003416, 22080416, 14463796, 65518565, 18127889, 24370863, 33332664, + ), + u32x8::new_const( + 66655380, 6430175, 471782, 11947673, 30596400, 18898659, 15930721, 4211851, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 6757410, 65455566, 13584784, 11362173, 10797127, 24451471, 19541370, 29309435, + ), + u32x8::new_const( + 40360156, 17685025, 18326181, 3846903, 13693365, 63049479, 31900359, 23385063, + ), + u32x8::new_const( + 52455038, 57513503, 22163311, 27095042, 48610726, 66454160, 12085341, 26357004, + ), + u32x8::new_const( + 22097042, 14063840, 6705778, 14342902, 66139825, 20702105, 31279090, 7495745, + ), + u32x8::new_const( + 27360710, 49314837, 18774847, 7146436, 37066216, 42004961, 22409916, 10524446, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 1497507, 33054449, 11839906, 2960428, 40538463, 18884538, 25018820, 4073970, + ), + u32x8::new_const( + 54484385, 43640735, 2808257, 20710708, 39840730, 27222424, 21783544, 11848522, + ), + u32x8::new_const( + 45765237, 48200555, 9299019, 9393151, 34818188, 56098995, 13575233, 21012731, + ), + u32x8::new_const( + 4265428, 49627650, 24960282, 9425650, 47883651, 2797524, 11853190, 22877329, + ), + u32x8::new_const( + 25008173, 64199503, 380047, 12107343, 12329448, 11914399, 764281, 29687002, + ), + ])), + CachedPoint(FieldElement2625x4([ + u32x8::new_const( + 35889734, 23047226, 4022841, 7017445, 7274086, 53316179, 25100176, 15310676, + ), + u32x8::new_const( + 42409427, 30270106, 6823853, 31551384, 40645017, 66489807, 18021817, 32669351, + ), + u32x8::new_const( + 39827134, 43680850, 28297996, 20258133, 26058742, 52643238, 22238331, 21690533, + ), + u32x8::new_const( + 60808002, 17499995, 30042246, 29310584, 48219954, 29389518, 8680514, 17844709, + ), + u32x8::new_const( + 6452896, 50116553, 9532047, 26821214, 44524351, 50428429, 21904953, 12608048, + ), + ])), +]); diff --git a/curve25519-elligator2/src/backend/vector/avx2/edwards.rs b/curve25519-elligator2/src/backend/vector/avx2/edwards.rs new file mode 100644 index 00000000..5a4f3752 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/avx2/edwards.rs @@ -0,0 +1,570 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Parallel Edwards Arithmetic for Curve25519. +//! +//! This module currently has two point types: +//! +//! * `ExtendedPoint`: a point stored in vector-friendly format, with +//! vectorized doubling and addition; +//! +//! * `CachedPoint`: used for readdition. +//! +//! Details on the formulas can be found in the documentation for the +//! parent `avx2` module. +//! +//! This API is designed to be safe: vectorized points can only be +//! created from serial points (which do validation on decompression), +//! and operations on valid points return valid points, so invalid +//! point states should be unrepresentable. +//! +//! This design goal is met, with one exception: the `Neg` +//! implementation for the `CachedPoint` performs a lazy negation, so +//! that subtraction can be efficiently implemented as a negation and +//! an addition. Repeatedly negating a `CachedPoint` will cause its +//! coefficients to grow and eventually overflow. Repeatedly negating +//! a point should not be necessary anyways. + +#![allow(non_snake_case)] + +use core::ops::{Add, Neg, Sub}; + +use subtle::Choice; +use subtle::ConditionallySelectable; + +use curve25519_dalek_derive::unsafe_target_feature; + +use crate::edwards; +use crate::window::{LookupTable, NafLookupTable5}; + +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +use crate::window::NafLookupTable8; + +use crate::traits::Identity; + +use super::constants; +use super::field::{FieldElement2625x4, Lanes, Shuffle}; + +/// A point on Curve25519, using parallel Edwards formulas for curve +/// operations. +/// +/// # Invariant +/// +/// The coefficients of an `ExtendedPoint` are bounded with +/// \\( b < 0.007 \\). +#[derive(Copy, Clone, Debug)] +pub struct ExtendedPoint(pub(super) FieldElement2625x4); + +#[unsafe_target_feature("avx2")] +impl From for ExtendedPoint { + fn from(P: edwards::EdwardsPoint) -> ExtendedPoint { + ExtendedPoint(FieldElement2625x4::new(&P.X, &P.Y, &P.Z, &P.T)) + } +} + +#[unsafe_target_feature("avx2")] +impl From for edwards::EdwardsPoint { + fn from(P: ExtendedPoint) -> edwards::EdwardsPoint { + let tmp = P.0.split(); + edwards::EdwardsPoint { + X: tmp[0], + Y: tmp[1], + Z: tmp[2], + T: tmp[3], + } + } +} + +#[unsafe_target_feature("avx2")] +impl ConditionallySelectable for ExtendedPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + ExtendedPoint(FieldElement2625x4::conditional_select(&a.0, &b.0, choice)) + } + + fn conditional_assign(&mut self, other: &Self, choice: Choice) { + self.0.conditional_assign(&other.0, choice); + } +} + +#[unsafe_target_feature("avx2")] +impl Default for ExtendedPoint { + fn default() -> ExtendedPoint { + ExtendedPoint::identity() + } +} + +#[unsafe_target_feature("avx2")] +impl Identity for ExtendedPoint { + fn identity() -> ExtendedPoint { + constants::EXTENDEDPOINT_IDENTITY + } +} + +#[unsafe_target_feature("avx2")] +impl ExtendedPoint { + /// Compute the double of this point. + pub fn double(&self) -> ExtendedPoint { + // Want to compute (X1 Y1 Z1 X1+Y1). + // Not sure how to do this less expensively than computing + // (X1 Y1 Z1 T1) --(256bit shuffle)--> (X1 Y1 X1 Y1) + // (X1 Y1 X1 Y1) --(2x128b shuffle)--> (Y1 X1 Y1 X1) + // and then adding. + + // Set tmp0 = (X1 Y1 X1 Y1) + let mut tmp0 = self.0.shuffle(Shuffle::ABAB); + + // Set tmp1 = (Y1 X1 Y1 X1) + let mut tmp1 = tmp0.shuffle(Shuffle::BADC); + + // Set tmp0 = (X1 Y1 Z1 X1+Y1) + tmp0 = self.0.blend(tmp0 + tmp1, Lanes::D); + + // Set tmp1 = tmp0^2, negating the D values + tmp1 = tmp0.square_and_negate_D(); + // Now tmp1 = (S1 S2 S3 -S4) with b < 0.007 + + // See discussion of bounds in the module-level documentation. + // We want to compute + // + // + | S1 | S1 | S1 | S1 | + // + | S2 | | | S2 | + // + | | | S3 | | + // + | | | S3 | | + // + | | | |-S4 | + // + | | 2p | 2p | | + // - | | S2 | S2 | | + // ======================= + // S5 S6 S8 S9 + + let zero = FieldElement2625x4::ZERO; + let S_1 = tmp1.shuffle(Shuffle::AAAA); + let S_2 = tmp1.shuffle(Shuffle::BBBB); + + tmp0 = zero.blend(tmp1 + tmp1, Lanes::C); + // tmp0 = (0, 0, 2S_3, 0) + tmp0 = tmp0.blend(tmp1, Lanes::D); + // tmp0 = (0, 0, 2S_3, -S_4) + tmp0 = tmp0 + S_1; + // tmp0 = ( S_1, S_1, S_1 + 2S_3, S_1 - S_4) + tmp0 = tmp0 + zero.blend(S_2, Lanes::AD); + // tmp0 = (S_1 + S_2, S_1, S_1 + 2S_3, S_1 + S_2 - S_4) + tmp0 = tmp0 + zero.blend(S_2.negate_lazy(), Lanes::BC); + // tmp0 = (S_1 + S_2, S_1 - S_2, S_1 - S_2 + 2S_3, S_1 + S_2 - S_4) + // b < ( 1.01, 1.6, 2.33, 1.6) + // Now tmp0 = (S_5, S_6, S_8, S_9) + + // Set tmp1 = ( S_9, S_6, S_6, S_9) + // b < ( 1.6, 1.6, 1.6, 1.6) + tmp1 = tmp0.shuffle(Shuffle::DBBD); + // Set tmp0 = ( S_8, S_5, S_8, S_5) + // b < (2.33, 1.01, 2.33, 1.01) + tmp0 = tmp0.shuffle(Shuffle::CACA); + + // Bounds on (tmp0, tmp1) are (2.33, 1.6) < (2.5, 1.75). + ExtendedPoint(&tmp0 * &tmp1) + } + + pub fn mul_by_pow_2(&self, k: u32) -> ExtendedPoint { + let mut tmp: ExtendedPoint = *self; + for _ in 0..k { + tmp = tmp.double(); + } + tmp + } +} + +/// A cached point with some precomputed variables used for readdition. +/// +/// # Warning +/// +/// It is not safe to negate this point more than once. +/// +/// # Invariant +/// +/// As long as the `CachedPoint` is not repeatedly negated, its +/// coefficients will be bounded with \\( b < 1.0 \\). +#[derive(Copy, Clone, Debug)] +pub struct CachedPoint(pub(super) FieldElement2625x4); + +#[unsafe_target_feature("avx2")] +impl From for CachedPoint { + fn from(P: ExtendedPoint) -> CachedPoint { + let mut x = P.0; + + x = x.blend(x.diff_sum(), Lanes::AB); + // x = (Y2 - X2, Y2 + X2, Z2, T2) = (S2 S3 Z2 T2) + + x = x * (121666, 121666, 2 * 121666, 2 * 121665); + // x = (121666*S2 121666*S3 2*121666*Z2 2*121665*T2) + + x = x.blend(-x, Lanes::D); + // x = (121666*S2 121666*S3 2*121666*Z2 -2*121665*T2) + + // The coefficients of the output are bounded with b < 0.007. + CachedPoint(x) + } +} + +#[unsafe_target_feature("avx2")] +impl Default for CachedPoint { + fn default() -> CachedPoint { + CachedPoint::identity() + } +} + +#[unsafe_target_feature("avx2")] +impl Identity for CachedPoint { + fn identity() -> CachedPoint { + constants::CACHEDPOINT_IDENTITY + } +} + +#[unsafe_target_feature("avx2")] +impl ConditionallySelectable for CachedPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + CachedPoint(FieldElement2625x4::conditional_select(&a.0, &b.0, choice)) + } + + fn conditional_assign(&mut self, other: &Self, choice: Choice) { + self.0.conditional_assign(&other.0, choice); + } +} + +#[unsafe_target_feature("avx2")] +impl Neg for &CachedPoint { + type Output = CachedPoint; + /// Lazily negate the point. + /// + /// # Warning + /// + /// Because this method does not perform a reduction, it is not + /// safe to repeatedly negate a point. + fn neg(self) -> CachedPoint { + let swapped = self.0.shuffle(Shuffle::BACD); + CachedPoint(swapped.blend(swapped.negate_lazy(), Lanes::D)) + } +} + +#[unsafe_target_feature("avx2")] +impl Add<&CachedPoint> for &ExtendedPoint { + type Output = ExtendedPoint; + + /// Add an `ExtendedPoint` and a `CachedPoint`. + fn add(self, other: &CachedPoint) -> ExtendedPoint { + // The coefficients of an `ExtendedPoint` are reduced after + // every operation. If the `CachedPoint` was negated, its + // coefficients grow by one bit. So on input, `self` is + // bounded with `b < 0.007` and `other` is bounded with + // `b < 1.0`. + + let mut tmp = self.0; + + tmp = tmp.blend(tmp.diff_sum(), Lanes::AB); + // tmp = (Y1-X1 Y1+X1 Z1 T1) = (S0 S1 Z1 T1) with b < 1.6 + + // (tmp, other) bounded with b < (1.6, 1.0) < (2.5, 1.75). + tmp = &tmp * &other.0; + // tmp = (S0*S2' S1*S3' Z1*Z2' T1*T2') = (S8 S9 S10 S11) + + tmp = tmp.shuffle(Shuffle::ABDC); + // tmp = (S8 S9 S11 S10) + + tmp = tmp.diff_sum(); + // tmp = (S9-S8 S9+S8 S10-S11 S10+S11) = (S12 S13 S14 S15) + + let t0 = tmp.shuffle(Shuffle::ADDA); + // t0 = (S12 S15 S15 S12) + let t1 = tmp.shuffle(Shuffle::CBCB); + // t1 = (S14 S13 S14 S13) + + // All coefficients of t0, t1 are bounded with b < 1.6. + // Return (S12*S14 S15*S13 S15*S14 S12*S13) = (X3 Y3 Z3 T3) + ExtendedPoint(&t0 * &t1) + } +} + +#[unsafe_target_feature("avx2")] +impl Sub<&CachedPoint> for &ExtendedPoint { + type Output = ExtendedPoint; + + /// Implement subtraction by negating the point and adding. + /// + /// Empirically, this seems about the same cost as a custom + /// subtraction impl (maybe because the benefit is cancelled by + /// increased code size?) + fn sub(self, other: &CachedPoint) -> ExtendedPoint { + self + &(-other) + } +} + +#[unsafe_target_feature("avx2")] +impl From<&edwards::EdwardsPoint> for LookupTable { + fn from(point: &edwards::EdwardsPoint) -> Self { + let P = ExtendedPoint::from(*point); + let mut points = [CachedPoint::from(P); 8]; + for i in 0..7 { + points[i + 1] = (&P + &points[i]).into(); + } + LookupTable(points) + } +} + +#[unsafe_target_feature("avx2")] +impl From<&edwards::EdwardsPoint> for NafLookupTable5 { + fn from(point: &edwards::EdwardsPoint) -> Self { + let A = ExtendedPoint::from(*point); + let mut Ai = [CachedPoint::from(A); 8]; + let A2 = A.double(); + for i in 0..7 { + Ai[i + 1] = (&A2 + &Ai[i]).into(); + } + // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A] + NafLookupTable5(Ai) + } +} + +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +#[unsafe_target_feature("avx2")] +impl From<&edwards::EdwardsPoint> for NafLookupTable8 { + fn from(point: &edwards::EdwardsPoint) -> Self { + let A = ExtendedPoint::from(*point); + let mut Ai = [CachedPoint::from(A); 64]; + let A2 = A.double(); + for i in 0..63 { + Ai[i + 1] = (&A2 + &Ai[i]).into(); + } + // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A, ..., 127A] + NafLookupTable8(Ai) + } +} + +#[cfg(target_feature = "avx2")] +#[cfg(test)] +mod test { + use super::*; + + #[rustfmt::skip] // keep alignment of some S* calculations + fn serial_add(P: edwards::EdwardsPoint, Q: edwards::EdwardsPoint) -> edwards::EdwardsPoint { + use crate::backend::serial::u64::field::FieldElement51; + + let (X1, Y1, Z1, T1) = (P.X, P.Y, P.Z, P.T); + let (X2, Y2, Z2, T2) = (Q.X, Q.Y, Q.Z, Q.T); + + macro_rules! print_var { + ($x:ident) => { + println!("{} = {:?}", stringify!($x), $x.as_bytes()); + }; + } + + let S0 = &Y1 - &X1; // R1 + let S1 = &Y1 + &X1; // R3 + let S2 = &Y2 - &X2; // R2 + let S3 = &Y2 + &X2; // R4 + print_var!(S0); + print_var!(S1); + print_var!(S2); + print_var!(S3); + println!(""); + + let S4 = &S0 * &S2; // R5 = R1 * R2 + let S5 = &S1 * &S3; // R6 = R3 * R4 + let S6 = &Z1 * &Z2; // R8 + let S7 = &T1 * &T2; // R7 + print_var!(S4); + print_var!(S5); + print_var!(S6); + print_var!(S7); + println!(""); + + let S8 = &S4 * &FieldElement51([ 121666,0,0,0,0]); // R5 + let S9 = &S5 * &FieldElement51([ 121666,0,0,0,0]); // R6 + let S10 = &S6 * &FieldElement51([2*121666,0,0,0,0]); // R8 + let S11 = &S7 * &(-&FieldElement51([2*121665,0,0,0,0])); // R7 + print_var!(S8); + print_var!(S9); + print_var!(S10); + print_var!(S11); + println!(""); + + let S12 = &S9 - &S8; // R1 + let S13 = &S9 + &S8; // R4 + let S14 = &S10 - &S11; // R2 + let S15 = &S10 + &S11; // R3 + print_var!(S12); + print_var!(S13); + print_var!(S14); + print_var!(S15); + println!(""); + + let X3 = &S12 * &S14; // R1 * R2 + let Y3 = &S15 * &S13; // R3 * R4 + let Z3 = &S15 * &S14; // R2 * R3 + let T3 = &S12 * &S13; // R1 * R4 + + edwards::EdwardsPoint { + X: X3, + Y: Y3, + Z: Z3, + T: T3, + } + } + + fn addition_test_helper(P: edwards::EdwardsPoint, Q: edwards::EdwardsPoint) { + // Test the serial implementation of the parallel addition formulas + let R_serial: edwards::EdwardsPoint = serial_add(P.into(), Q.into()).into(); + + // Test the vector implementation of the parallel readdition formulas + let cached_Q = CachedPoint::from(ExtendedPoint::from(Q)); + let R_vector: edwards::EdwardsPoint = (&ExtendedPoint::from(P) + &cached_Q).into(); + let S_vector: edwards::EdwardsPoint = (&ExtendedPoint::from(P) - &cached_Q).into(); + + println!("Testing point addition:"); + println!("P = {:?}", P); + println!("Q = {:?}", Q); + println!("cached Q = {:?}", cached_Q); + println!("R = P + Q = {:?}", &P + &Q); + println!("R_serial = {:?}", R_serial); + println!("R_vector = {:?}", R_vector); + println!("S = P - Q = {:?}", &P - &Q); + println!("S_vector = {:?}", S_vector); + assert_eq!(R_serial.compress(), (&P + &Q).compress()); + assert_eq!(R_vector.compress(), (&P + &Q).compress()); + assert_eq!(S_vector.compress(), (&P - &Q).compress()); + println!("OK!\n"); + } + + #[test] + fn vector_addition_vs_serial_addition_vs_edwards_extendedpoint() { + use crate::constants; + use crate::scalar::Scalar; + + println!("Testing id +- id"); + let P = edwards::EdwardsPoint::identity(); + let Q = edwards::EdwardsPoint::identity(); + addition_test_helper(P, Q); + + println!("Testing id +- B"); + let P = edwards::EdwardsPoint::identity(); + let Q = constants::ED25519_BASEPOINT_POINT; + addition_test_helper(P, Q); + + println!("Testing B +- B"); + let P = constants::ED25519_BASEPOINT_POINT; + let Q = constants::ED25519_BASEPOINT_POINT; + addition_test_helper(P, Q); + + println!("Testing B +- kB"); + let P = constants::ED25519_BASEPOINT_POINT; + let Q = constants::ED25519_BASEPOINT_TABLE * &Scalar::from(8475983829u64); + addition_test_helper(P, Q); + } + + fn serial_double(P: edwards::EdwardsPoint) -> edwards::EdwardsPoint { + let (X1, Y1, Z1, _T1) = (P.X, P.Y, P.Z, P.T); + + macro_rules! print_var { + ($x:ident) => { + println!("{} = {:?}", stringify!($x), $x.as_bytes()); + }; + } + + let S0 = &X1 + &Y1; // R1 + print_var!(S0); + println!(""); + + let S1 = X1.square(); + let S2 = Y1.square(); + let S3 = Z1.square(); + let S4 = S0.square(); + print_var!(S1); + print_var!(S2); + print_var!(S3); + print_var!(S4); + println!(""); + + let S5 = &S1 + &S2; + let S6 = &S1 - &S2; + let S7 = &S3 + &S3; + let S8 = &S7 + &S6; + let S9 = &S5 - &S4; + print_var!(S5); + print_var!(S6); + print_var!(S7); + print_var!(S8); + print_var!(S9); + println!(""); + + let X3 = &S8 * &S9; + let Y3 = &S5 * &S6; + let Z3 = &S8 * &S6; + let T3 = &S5 * &S9; + + edwards::EdwardsPoint { + X: X3, + Y: Y3, + Z: Z3, + T: T3, + } + } + + fn doubling_test_helper(P: edwards::EdwardsPoint) { + let R1: edwards::EdwardsPoint = serial_double(P.into()).into(); + let R2: edwards::EdwardsPoint = ExtendedPoint::from(P).double().into(); + println!("Testing point doubling:"); + println!("P = {:?}", P); + println!("(serial) R1 = {:?}", R1); + println!("(vector) R2 = {:?}", R2); + println!("P + P = {:?}", &P + &P); + assert_eq!(R1.compress(), (&P + &P).compress()); + assert_eq!(R2.compress(), (&P + &P).compress()); + println!("OK!\n"); + } + + #[test] + fn vector_doubling_vs_serial_doubling_vs_edwards_extendedpoint() { + use crate::constants; + use crate::scalar::Scalar; + + println!("Testing [2]id"); + let P = edwards::EdwardsPoint::identity(); + doubling_test_helper(P); + + println!("Testing [2]B"); + let P = constants::ED25519_BASEPOINT_POINT; + doubling_test_helper(P); + + println!("Testing [2]([k]B)"); + let P = constants::ED25519_BASEPOINT_TABLE * &Scalar::from(8475983829u64); + doubling_test_helper(P); + } + + #[cfg(any(feature = "precomputed-tables", feature = "alloc"))] + #[test] + fn basepoint_odd_lookup_table_verify() { + use crate::backend::vector::avx2::constants::BASEPOINT_ODD_LOOKUP_TABLE; + use crate::constants; + + let basepoint_odd_table = + NafLookupTable8::::from(&constants::ED25519_BASEPOINT_POINT); + println!("basepoint_odd_lookup_table = {:?}", basepoint_odd_table); + + let table_B = &BASEPOINT_ODD_LOOKUP_TABLE; + for (b_vec, base_vec) in table_B.0.iter().zip(basepoint_odd_table.0.iter()) { + let b_splits = b_vec.0.split(); + let base_splits = base_vec.0.split(); + + assert_eq!(base_splits[0], b_splits[0]); + assert_eq!(base_splits[1], b_splits[1]); + assert_eq!(base_splits[2], b_splits[2]); + assert_eq!(base_splits[3], b_splits[3]); + } + } +} diff --git a/curve25519-elligator2/src/backend/vector/avx2/field.rs b/curve25519-elligator2/src/backend/vector/avx2/field.rs new file mode 100644 index 00000000..14d9f15e --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/avx2/field.rs @@ -0,0 +1,983 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! An implementation of 4-way vectorized 32bit field arithmetic using +//! AVX2. +//! +//! The `FieldElement2625x4` struct provides a vector of four field +//! elements, implemented using AVX2 operations. Its API is designed +//! to abstract away the platform-dependent details, so that point +//! arithmetic can be implemented only in terms of a vector of field +//! elements. +//! +//! At this level, the API is optimized for speed and not safety. The +//! `FieldElement2625x4` does not always perform reductions. The pre- +//! and post-conditions on the bounds of the coefficients are +//! documented for each method, but it is the caller's responsibility +//! to ensure that there are no overflows. + +#![allow(non_snake_case)] + +const A_LANES: u8 = 0b0000_0101; +const B_LANES: u8 = 0b0000_1010; +const C_LANES: u8 = 0b0101_0000; +const D_LANES: u8 = 0b1010_0000; + +#[allow(unused)] +const A_LANES64: u8 = 0b00_00_00_11; +#[allow(unused)] +const B_LANES64: u8 = 0b00_00_11_00; +#[allow(unused)] +const C_LANES64: u8 = 0b00_11_00_00; +#[allow(unused)] +const D_LANES64: u8 = 0b11_00_00_00; + +use crate::backend::vector::packed_simd::{u32x8, u64x4}; +use core::ops::{Add, Mul, Neg}; + +use crate::backend::serial::u64::field::FieldElement51; +use crate::backend::vector::avx2::constants::{ + P_TIMES_16_HI, P_TIMES_16_LO, P_TIMES_2_HI, P_TIMES_2_LO, +}; + +use curve25519_dalek_derive::unsafe_target_feature; + +/// Unpack 32-bit lanes into 64-bit lanes: +/// ```ascii,no_run +/// (a0, b0, a1, b1, c0, d0, c1, d1) +/// ``` +/// into +/// ```ascii,no_run +/// (a0, 0, b0, 0, c0, 0, d0, 0) +/// (a1, 0, b1, 0, c1, 0, d1, 0) +/// ``` +#[unsafe_target_feature("avx2")] +#[inline(always)] +fn unpack_pair(src: u32x8) -> (u32x8, u32x8) { + let a: u32x8; + let b: u32x8; + let zero = u32x8::splat(0); + unsafe { + use core::arch::x86_64::_mm256_unpackhi_epi32; + use core::arch::x86_64::_mm256_unpacklo_epi32; + a = _mm256_unpacklo_epi32(src.into(), zero.into()).into(); + b = _mm256_unpackhi_epi32(src.into(), zero.into()).into(); + } + (a, b) +} + +/// Repack 64-bit lanes into 32-bit lanes: +/// ```ascii,no_run +/// (a0, 0, b0, 0, c0, 0, d0, 0) +/// (a1, 0, b1, 0, c1, 0, d1, 0) +/// ``` +/// into +/// ```ascii,no_run +/// (a0, b0, a1, b1, c0, d0, c1, d1) +/// ``` +#[unsafe_target_feature("avx2")] +#[inline(always)] +fn repack_pair(x: u32x8, y: u32x8) -> u32x8 { + unsafe { + use core::arch::x86_64::_mm256_blend_epi32; + use core::arch::x86_64::_mm256_shuffle_epi32; + + // Input: x = (a0, 0, b0, 0, c0, 0, d0, 0) + // Input: y = (a1, 0, b1, 0, c1, 0, d1, 0) + + let x_shuffled = _mm256_shuffle_epi32(x.into(), 0b11_01_10_00); + let y_shuffled = _mm256_shuffle_epi32(y.into(), 0b10_00_11_01); + + // x' = (a0, b0, 0, 0, c0, d0, 0, 0) + // y' = ( 0, 0, a1, b1, 0, 0, c1, d1) + + _mm256_blend_epi32(x_shuffled, y_shuffled, 0b11001100).into() + } +} + +/// The `Lanes` enum represents a subset of the lanes `A,B,C,D` of a +/// `FieldElement2625x4`. +/// +/// It's used to specify blend operations without +/// having to know details about the data layout of the +/// `FieldElement2625x4`. +#[allow(clippy::upper_case_acronyms)] +#[derive(Copy, Clone, Debug)] +pub enum Lanes { + C, + D, + AB, + AC, + CD, + AD, + BC, + ABCD, +} + +/// The `Shuffle` enum represents a shuffle of a `FieldElement2625x4`. +/// +/// The enum variants are named by what they do to a vector \\( +/// (A,B,C,D) \\); for instance, `Shuffle::BADC` turns \\( (A, B, C, +/// D) \\) into \\( (B, A, D, C) \\). +#[allow(clippy::upper_case_acronyms)] +#[derive(Copy, Clone, Debug)] +pub enum Shuffle { + AAAA, + BBBB, + CACA, + DBBD, + ADDA, + CBCB, + ABAB, + BADC, + BACD, + ABDC, +} + +/// A vector of four field elements. +/// +/// Each operation on a `FieldElement2625x4` has documented effects on +/// the bounds of the coefficients. This API is designed for speed +/// and not safety; it is the caller's responsibility to ensure that +/// the post-conditions of one operation are compatible with the +/// pre-conditions of the next. +#[derive(Clone, Copy, Debug)] +pub struct FieldElement2625x4(pub(crate) [u32x8; 5]); + +use subtle::Choice; +use subtle::ConditionallySelectable; + +#[unsafe_target_feature("avx2")] +impl ConditionallySelectable for FieldElement2625x4 { + fn conditional_select( + a: &FieldElement2625x4, + b: &FieldElement2625x4, + choice: Choice, + ) -> FieldElement2625x4 { + let mask = (-(choice.unwrap_u8() as i32)) as u32; + let mask_vec = u32x8::splat(mask); + FieldElement2625x4([ + a.0[0] ^ (mask_vec & (a.0[0] ^ b.0[0])), + a.0[1] ^ (mask_vec & (a.0[1] ^ b.0[1])), + a.0[2] ^ (mask_vec & (a.0[2] ^ b.0[2])), + a.0[3] ^ (mask_vec & (a.0[3] ^ b.0[3])), + a.0[4] ^ (mask_vec & (a.0[4] ^ b.0[4])), + ]) + } + + fn conditional_assign(&mut self, other: &FieldElement2625x4, choice: Choice) { + let mask = (-(choice.unwrap_u8() as i32)) as u32; + let mask_vec = u32x8::splat(mask); + self.0[0] ^= mask_vec & (self.0[0] ^ other.0[0]); + self.0[1] ^= mask_vec & (self.0[1] ^ other.0[1]); + self.0[2] ^= mask_vec & (self.0[2] ^ other.0[2]); + self.0[3] ^= mask_vec & (self.0[3] ^ other.0[3]); + self.0[4] ^= mask_vec & (self.0[4] ^ other.0[4]); + } +} + +#[unsafe_target_feature("avx2")] +impl FieldElement2625x4 { + pub const ZERO: FieldElement2625x4 = FieldElement2625x4([u32x8::splat_const::<0>(); 5]); + + /// Split this vector into an array of four (serial) field + /// elements. + #[rustfmt::skip] // keep alignment of extracted lanes + pub fn split(&self) -> [FieldElement51; 4] { + let mut out = [FieldElement51::ZERO; 4]; + for i in 0..5 { + let a_2i = self.0[i].extract::<0>() as u64; // + let b_2i = self.0[i].extract::<1>() as u64; // + let a_2i_1 = self.0[i].extract::<2>() as u64; // `. + let b_2i_1 = self.0[i].extract::<3>() as u64; // | pre-swapped to avoid + let c_2i = self.0[i].extract::<4>() as u64; // | a cross lane shuffle + let d_2i = self.0[i].extract::<5>() as u64; // .' + let c_2i_1 = self.0[i].extract::<6>() as u64; // + let d_2i_1 = self.0[i].extract::<7>() as u64; // + + out[0].0[i] = a_2i + (a_2i_1 << 26); + out[1].0[i] = b_2i + (b_2i_1 << 26); + out[2].0[i] = c_2i + (c_2i_1 << 26); + out[3].0[i] = d_2i + (d_2i_1 << 26); + } + + out + } + + /// Rearrange the elements of this vector according to `control`. + /// + /// The `control` parameter should be a compile-time constant, so + /// that when this function is inlined, LLVM is able to lower the + /// shuffle using an immediate. + #[inline] + pub fn shuffle(&self, control: Shuffle) -> FieldElement2625x4 { + #[inline(always)] + fn shuffle_lanes(x: u32x8, control: Shuffle) -> u32x8 { + unsafe { + use core::arch::x86_64::_mm256_permutevar8x32_epi32; + + let c: u32x8 = match control { + Shuffle::AAAA => u32x8::new(0, 0, 2, 2, 0, 0, 2, 2), + Shuffle::BBBB => u32x8::new(1, 1, 3, 3, 1, 1, 3, 3), + Shuffle::CACA => u32x8::new(4, 0, 6, 2, 4, 0, 6, 2), + Shuffle::DBBD => u32x8::new(5, 1, 7, 3, 1, 5, 3, 7), + Shuffle::ADDA => u32x8::new(0, 5, 2, 7, 5, 0, 7, 2), + Shuffle::CBCB => u32x8::new(4, 1, 6, 3, 4, 1, 6, 3), + Shuffle::ABAB => u32x8::new(0, 1, 2, 3, 0, 1, 2, 3), + Shuffle::BADC => u32x8::new(1, 0, 3, 2, 5, 4, 7, 6), + Shuffle::BACD => u32x8::new(1, 0, 3, 2, 4, 5, 6, 7), + Shuffle::ABDC => u32x8::new(0, 1, 2, 3, 5, 4, 7, 6), + }; + // Note that this gets turned into a generic LLVM + // shuffle-by-constants, which can be lowered to a simpler + // instruction than a generic permute. + _mm256_permutevar8x32_epi32(x.into(), c.into()).into() + } + } + + FieldElement2625x4([ + shuffle_lanes(self.0[0], control), + shuffle_lanes(self.0[1], control), + shuffle_lanes(self.0[2], control), + shuffle_lanes(self.0[3], control), + shuffle_lanes(self.0[4], control), + ]) + } + + /// Blend `self` with `other`, taking lanes specified in `control` from `other`. + /// + /// The `control` parameter should be a compile-time constant, so + /// that this function can be inlined and LLVM can lower it to a + /// blend instruction using an immediate. + #[inline] + pub fn blend(&self, other: FieldElement2625x4, control: Lanes) -> FieldElement2625x4 { + #[inline(always)] + fn blend_lanes(x: u32x8, y: u32x8, control: Lanes) -> u32x8 { + unsafe { + use core::arch::x86_64::_mm256_blend_epi32; + + // This would be much cleaner if we could factor out the match + // statement on the control. Unfortunately, rustc forgets + // constant-info very quickly, so we can't even write + // ``` + // match control { + // Lanes::C => { + // let imm = C_LANES as i32; + // _mm256_blend_epi32(..., imm) + // ``` + // let alone + // ``` + // let imm = match control { + // Lanes::C => C_LANES as i32, + // } + // _mm256_blend_epi32(..., imm) + // ``` + // even though both of these would be constant-folded by LLVM + // at a lower level (as happens in the shuffle implementation, + // which does not require a shuffle immediate but *is* lowered + // to immediate shuffles anyways). + match control { + Lanes::C => _mm256_blend_epi32(x.into(), y.into(), C_LANES as i32).into(), + Lanes::D => _mm256_blend_epi32(x.into(), y.into(), D_LANES as i32).into(), + Lanes::AD => { + _mm256_blend_epi32(x.into(), y.into(), (A_LANES | D_LANES) as i32).into() + } + Lanes::AB => { + _mm256_blend_epi32(x.into(), y.into(), (A_LANES | B_LANES) as i32).into() + } + Lanes::AC => { + _mm256_blend_epi32(x.into(), y.into(), (A_LANES | C_LANES) as i32).into() + } + Lanes::CD => { + _mm256_blend_epi32(x.into(), y.into(), (C_LANES | D_LANES) as i32).into() + } + Lanes::BC => { + _mm256_blend_epi32(x.into(), y.into(), (B_LANES | C_LANES) as i32).into() + } + Lanes::ABCD => _mm256_blend_epi32( + x.into(), + y.into(), + (A_LANES | B_LANES | C_LANES | D_LANES) as i32, + ) + .into(), + } + } + } + + FieldElement2625x4([ + blend_lanes(self.0[0], other.0[0], control), + blend_lanes(self.0[1], other.0[1], control), + blend_lanes(self.0[2], other.0[2], control), + blend_lanes(self.0[3], other.0[3], control), + blend_lanes(self.0[4], other.0[4], control), + ]) + } + + /// Convenience wrapper around `new(x,x,x,x)`. + pub fn splat(x: &FieldElement51) -> FieldElement2625x4 { + FieldElement2625x4::new(x, x, x, x) + } + + /// Create a `FieldElement2625x4` from four `FieldElement51`s. + /// + /// # Postconditions + /// + /// The resulting `FieldElement2625x4` is bounded with \\( b < 0.0002 \\). + #[rustfmt::skip] // keep alignment of computed lanes + pub fn new( + x0: &FieldElement51, + x1: &FieldElement51, + x2: &FieldElement51, + x3: &FieldElement51, + ) -> FieldElement2625x4 { + let mut buf = [u32x8::splat(0); 5]; + let low_26_bits = (1 << 26) - 1; + #[allow(clippy::needless_range_loop)] + for i in 0..5 { + let a_2i = (x0.0[i] & low_26_bits) as u32; + let a_2i_1 = (x0.0[i] >> 26) as u32; + let b_2i = (x1.0[i] & low_26_bits) as u32; + let b_2i_1 = (x1.0[i] >> 26) as u32; + let c_2i = (x2.0[i] & low_26_bits) as u32; + let c_2i_1 = (x2.0[i] >> 26) as u32; + let d_2i = (x3.0[i] & low_26_bits) as u32; + let d_2i_1 = (x3.0[i] >> 26) as u32; + + buf[i] = u32x8::new(a_2i, b_2i, a_2i_1, b_2i_1, c_2i, d_2i, c_2i_1, d_2i_1); + } + + // We don't know that the original `FieldElement51`s were + // fully reduced, so the odd limbs may exceed 2^25. + // Reduce them to be sure. + FieldElement2625x4(buf).reduce() + } + + /// Given \\((A,B,C,D)\\), compute \\((-A,-B,-C,-D)\\), without + /// performing a reduction. + /// + /// # Preconditions + /// + /// The coefficients of `self` must be bounded with \\( b < 0.999 \\). + /// + /// # Postconditions + /// + /// The coefficients of the result are bounded with \\( b < 1 \\). + #[inline] + pub fn negate_lazy(&self) -> FieldElement2625x4 { + // The limbs of self are bounded with b < 0.999, while the + // smallest limb of 2*p is 67108845 > 2^{26+0.9999}, so + // underflows are not possible. + FieldElement2625x4([ + P_TIMES_2_LO - self.0[0], + P_TIMES_2_HI - self.0[1], + P_TIMES_2_HI - self.0[2], + P_TIMES_2_HI - self.0[3], + P_TIMES_2_HI - self.0[4], + ]) + } + + /// Given `self = (A,B,C,D)`, compute `(B - A, B + A, D - C, D + C)`. + /// + /// # Preconditions + /// + /// The coefficients of `self` must be bounded with \\( b < 0.01 \\). + /// + /// # Postconditions + /// + /// The coefficients of the result are bounded with \\( b < 1.6 \\). + #[inline] + pub fn diff_sum(&self) -> FieldElement2625x4 { + // tmp1 = (B, A, D, C) + let tmp1 = self.shuffle(Shuffle::BADC); + // tmp2 = (-A, B, -C, D) + let tmp2 = self.blend(self.negate_lazy(), Lanes::AC); + // (B - A, B + A, D - C, D + C) bounded with b < 1.6 + tmp1 + tmp2 + } + + /// Reduce this vector of field elements \\(\mathrm{mod} p\\). + /// + /// # Postconditions + /// + /// The coefficients of the result are bounded with \\( b < 0.0002 \\). + #[inline] + pub fn reduce(&self) -> FieldElement2625x4 { + let shifts = u32x8::new(26, 26, 25, 25, 26, 26, 25, 25); + let masks = u32x8::new( + (1 << 26) - 1, + (1 << 26) - 1, + (1 << 25) - 1, + (1 << 25) - 1, + (1 << 26) - 1, + (1 << 26) - 1, + (1 << 25) - 1, + (1 << 25) - 1, + ); + + // Let c(x) denote the carryout of the coefficient x. + // + // Given ( x0, y0, x1, y1, z0, w0, z1, w1), + // compute (c(x1), c(y1), c(x0), c(y0), c(z1), c(w1), c(z0), c(w0)). + // + // The carryouts are bounded by 2^(32 - 25) = 2^7. + let rotated_carryout = |v: u32x8| -> u32x8 { + unsafe { + use core::arch::x86_64::_mm256_shuffle_epi32; + use core::arch::x86_64::_mm256_srlv_epi32; + + let c = _mm256_srlv_epi32(v.into(), shifts.into()); + _mm256_shuffle_epi32(c, 0b01_00_11_10).into() + } + }; + + // Combine (lo, lo, lo, lo, lo, lo, lo, lo) + // with (hi, hi, hi, hi, hi, hi, hi, hi) + // to (lo, lo, hi, hi, lo, lo, hi, hi) + // + // This allows combining carryouts, e.g., + // + // lo (c(x1), c(y1), c(x0), c(y0), c(z1), c(w1), c(z0), c(w0)) + // hi (c(x3), c(y3), c(x2), c(y2), c(z3), c(w3), c(z2), c(w2)) + // -> (c(x1), c(y1), c(x2), c(y2), c(z1), c(w1), c(z2), c(w2)) + // + // which is exactly the vector of carryins for + // + // ( x2, y2, x3, y3, z2, w2, z3, w3). + // + let combine = |v_lo: u32x8, v_hi: u32x8| -> u32x8 { + unsafe { + use core::arch::x86_64::_mm256_blend_epi32; + _mm256_blend_epi32(v_lo.into(), v_hi.into(), 0b11_00_11_00).into() + } + }; + + let mut v = self.0; + + let c10 = rotated_carryout(v[0]); + v[0] = (v[0] & masks) + combine(u32x8::splat(0), c10); + + let c32 = rotated_carryout(v[1]); + v[1] = (v[1] & masks) + combine(c10, c32); + + let c54 = rotated_carryout(v[2]); + v[2] = (v[2] & masks) + combine(c32, c54); + + let c76 = rotated_carryout(v[3]); + v[3] = (v[3] & masks) + combine(c54, c76); + + let c98 = rotated_carryout(v[4]); + v[4] = (v[4] & masks) + combine(c76, c98); + + let c9_19: u32x8 = unsafe { + use core::arch::x86_64::_mm256_mul_epu32; + use core::arch::x86_64::_mm256_shuffle_epi32; + + // Need to rearrange c98, since vpmuludq uses the low + // 32-bits of each 64-bit lane to compute the product: + // + // c98 = (c(x9), c(y9), c(x8), c(y8), c(z9), c(w9), c(z8), c(w8)); + // c9_spread = (c(x9), c(x8), c(y9), c(y8), c(z9), c(z8), c(w9), c(w8)). + let c9_spread = _mm256_shuffle_epi32(c98.into(), 0b11_01_10_00); + + // Since the carryouts are bounded by 2^7, their products with 19 + // are bounded by 2^11.25. This means that + // + // c9_19_spread = (19*c(x9), 0, 19*c(y9), 0, 19*c(z9), 0, 19*c(w9), 0). + let c9_19_spread = _mm256_mul_epu32(c9_spread, u64x4::splat(19).into()); + + // Unshuffle: + // c9_19 = (19*c(x9), 19*c(y9), 0, 0, 19*c(z9), 19*c(w9), 0, 0). + _mm256_shuffle_epi32(c9_19_spread, 0b11_01_10_00).into() + }; + + // Add the final carryin. + v[0] += c9_19; + + // Each output coefficient has exactly one carryin, which is + // bounded by 2^11.25, so they are bounded as + // + // c_even < 2^26 + 2^11.25 < 26.00006 < 2^{26+b} + // c_odd < 2^25 + 2^11.25 < 25.0001 < 2^{25+b} + // + // where b = 0.0002. + FieldElement2625x4(v) + } + + /// Given an array of wide coefficients, reduce them to a `FieldElement2625x4`. + /// + /// # Postconditions + /// + /// The coefficients of the result are bounded with \\( b < 0.007 \\). + #[inline] + #[rustfmt::skip] // keep alignment of carry chain + fn reduce64(mut z: [u64x4; 10]) -> FieldElement2625x4 { + // These aren't const because splat isn't a const fn + let LOW_25_BITS: u64x4 = u64x4::splat((1 << 25) - 1); + let LOW_26_BITS: u64x4 = u64x4::splat((1 << 26) - 1); + + // Carry the value from limb i = 0..8 to limb i+1 + let carry = |z: &mut [u64x4; 10], i: usize| { + debug_assert!(i < 9); + if i % 2 == 0 { + // Even limbs have 26 bits + z[i + 1] += z[i].shr::<26>(); + z[i] &= LOW_26_BITS; + } else { + // Odd limbs have 25 bits + z[i + 1] += z[i].shr::<25>(); + z[i] &= LOW_25_BITS; + } + }; + + // Perform two halves of the carry chain in parallel. + carry(&mut z, 0); carry(&mut z, 4); + carry(&mut z, 1); carry(&mut z, 5); + carry(&mut z, 2); carry(&mut z, 6); + carry(&mut z, 3); carry(&mut z, 7); + // Since z[3] < 2^64, c < 2^(64-25) = 2^39, + // so z[4] < 2^26 + 2^39 < 2^39.0002 + carry(&mut z, 4); carry(&mut z, 8); + // Now z[4] < 2^26 + // and z[5] < 2^25 + 2^13.0002 < 2^25.0004 (good enough) + + // Last carry has a multiplication by 19. In the serial case we + // do a 64-bit multiplication by 19, but here we want to do a + // 32-bit multiplication. However, if we only know z[9] < 2^64, + // the carry is bounded as c < 2^(64-25) = 2^39, which is too + // big. To ensure c < 2^32, we would need z[9] < 2^57. + // Instead, we split the carry in two, with c = c_0 + c_1*2^26. + + let c = z[9].shr::<25>(); + z[9] &= LOW_25_BITS; + let mut c0: u64x4 = c & LOW_26_BITS; // c0 < 2^26; + let mut c1: u64x4 = c.shr::<26>(); // c1 < 2^(39-26) = 2^13; + + let x19 = u64x4::splat(19); + c0 = u32x8::from(c0).mul32(u32x8::from(x19)); + c1 = u32x8::from(c1).mul32(u32x8::from(x19)); + + z[0] += c0; // z0 < 2^26 + 2^30.25 < 2^30.33 + z[1] += c1; // z1 < 2^25 + 2^17.25 < 2^25.0067 + carry(&mut z, 0); // z0 < 2^26, z1 < 2^25.0067 + 2^4.33 = 2^25.007 + + // The output coefficients are bounded with + // + // b = 0.007 for z[1] + // b = 0.0004 for z[5] + // b = 0 for other z[i]. + // + // So the packed result is bounded with b = 0.007. + FieldElement2625x4([ + repack_pair(z[0].into(), z[1].into()), + repack_pair(z[2].into(), z[3].into()), + repack_pair(z[4].into(), z[5].into()), + repack_pair(z[6].into(), z[7].into()), + repack_pair(z[8].into(), z[9].into()), + ]) + } + + /// Square this field element, and negate the result's \\(D\\) value. + /// + /// # Preconditions + /// + /// The coefficients of `self` must be bounded with \\( b < 1.5 \\). + /// + /// # Postconditions + /// + /// The coefficients of the result are bounded with \\( b < 0.007 \\). + #[rustfmt::skip] // keep alignment of z* calculations + pub fn square_and_negate_D(&self) -> FieldElement2625x4 { + #[inline(always)] + fn m(x: u32x8, y: u32x8) -> u64x4 { + x.mul32(y) + } + + #[inline(always)] + fn m_lo(x: u32x8, y: u32x8) -> u32x8 { + x.mul32(y).into() + } + + let v19 = u32x8::new(19, 0, 19, 0, 19, 0, 19, 0); + + let (x0, x1) = unpack_pair(self.0[0]); + let (x2, x3) = unpack_pair(self.0[1]); + let (x4, x5) = unpack_pair(self.0[2]); + let (x6, x7) = unpack_pair(self.0[3]); + let (x8, x9) = unpack_pair(self.0[4]); + + let x0_2 = x0.shl::<1>(); + let x1_2 = x1.shl::<1>(); + let x2_2 = x2.shl::<1>(); + let x3_2 = x3.shl::<1>(); + let x4_2 = x4.shl::<1>(); + let x5_2 = x5.shl::<1>(); + let x6_2 = x6.shl::<1>(); + let x7_2 = x7.shl::<1>(); + + let x5_19 = m_lo(v19, x5); + let x6_19 = m_lo(v19, x6); + let x7_19 = m_lo(v19, x7); + let x8_19 = m_lo(v19, x8); + let x9_19 = m_lo(v19, x9); + + let mut z0 = m(x0, x0) + m(x2_2, x8_19) + m(x4_2, x6_19) + ((m(x1_2, x9_19) + m(x3_2, x7_19) + m(x5, x5_19)).shl::<1>()); + let mut z1 = m(x0_2, x1) + m(x3_2, x8_19) + m(x5_2, x6_19) + ((m(x2, x9_19) + m(x4, x7_19)).shl::<1>()); + let mut z2 = m(x0_2, x2) + m(x1_2, x1) + m(x4_2, x8_19) + m(x6, x6_19) + ((m(x3_2, x9_19) + m(x5_2, x7_19)).shl::<1>()); + let mut z3 = m(x0_2, x3) + m(x1_2, x2) + m(x5_2, x8_19) + ((m(x4, x9_19) + m(x6, x7_19)).shl::<1>()); + let mut z4 = m(x0_2, x4) + m(x1_2, x3_2) + m(x2, x2) + m(x6_2, x8_19) + ((m(x5_2, x9_19) + m(x7, x7_19)).shl::<1>()); + let mut z5 = m(x0_2, x5) + m(x1_2, x4) + m(x2_2, x3) + m(x7_2, x8_19) + ((m(x6, x9_19)).shl::<1>()); + let mut z6 = m(x0_2, x6) + m(x1_2, x5_2) + m(x2_2, x4) + m(x3_2, x3) + m(x8, x8_19) + ((m(x7_2, x9_19)).shl::<1>()); + let mut z7 = m(x0_2, x7) + m(x1_2, x6) + m(x2_2, x5) + m(x3_2, x4) + ((m(x8, x9_19)).shl::<1>()); + let mut z8 = m(x0_2, x8) + m(x1_2, x7_2) + m(x2_2, x6) + m(x3_2, x5_2) + m(x4, x4) + ((m(x9, x9_19)).shl::<1>()); + let mut z9 = m(x0_2, x9) + m(x1_2, x8) + m(x2_2, x7) + m(x3_2, x6) + m(x4_2, x5) ; + + // The biggest z_i is bounded as z_i < 249*2^(51 + 2*b); + // if b < 1.5 we get z_i < 4485585228861014016. + // + // The limbs of the multiples of p are bounded above by + // + // 0x3fffffff << 37 = 9223371899415822336 < 2^63 + // + // and below by + // + // 0x1fffffff << 37 = 4611685880988434432 + // > 4485585228861014016 + // + // So these multiples of p are big enough to avoid underflow + // in subtraction, and small enough to fit within u64 + // with room for a carry. + + let low__p37 = u64x4::splat(0x3ffffed << 37); + let even_p37 = u64x4::splat(0x3ffffff << 37); + let odd__p37 = u64x4::splat(0x1ffffff << 37); + + let negate_D = |x: u64x4, p: u64x4| -> u64x4 { + unsafe { + use core::arch::x86_64::_mm256_blend_epi32; + _mm256_blend_epi32(x.into(), (p - x).into(), D_LANES64 as i32).into() + } + }; + + z0 = negate_D(z0, low__p37); + z1 = negate_D(z1, odd__p37); + z2 = negate_D(z2, even_p37); + z3 = negate_D(z3, odd__p37); + z4 = negate_D(z4, even_p37); + z5 = negate_D(z5, odd__p37); + z6 = negate_D(z6, even_p37); + z7 = negate_D(z7, odd__p37); + z8 = negate_D(z8, even_p37); + z9 = negate_D(z9, odd__p37); + + FieldElement2625x4::reduce64([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9]) + } +} + +#[unsafe_target_feature("avx2")] +impl Neg for FieldElement2625x4 { + type Output = FieldElement2625x4; + + /// Negate this field element, performing a reduction. + /// + /// If the coefficients are known to be small, use `negate_lazy` + /// to avoid performing a reduction. + /// + /// # Preconditions + /// + /// The coefficients of `self` must be bounded with \\( b < 4.0 \\). + /// + /// # Postconditions + /// + /// The coefficients of the result are bounded with \\( b < 0.0002 \\). + #[inline] + fn neg(self) -> FieldElement2625x4 { + FieldElement2625x4([ + P_TIMES_16_LO - self.0[0], + P_TIMES_16_HI - self.0[1], + P_TIMES_16_HI - self.0[2], + P_TIMES_16_HI - self.0[3], + P_TIMES_16_HI - self.0[4], + ]) + .reduce() + } +} + +#[unsafe_target_feature("avx2")] +impl Add for FieldElement2625x4 { + type Output = FieldElement2625x4; + /// Add two `FieldElement2625x4`s, without performing a reduction. + #[inline] + fn add(self, rhs: FieldElement2625x4) -> FieldElement2625x4 { + FieldElement2625x4([ + self.0[0] + rhs.0[0], + self.0[1] + rhs.0[1], + self.0[2] + rhs.0[2], + self.0[3] + rhs.0[3], + self.0[4] + rhs.0[4], + ]) + } +} + +#[unsafe_target_feature("avx2")] +impl Mul<(u32, u32, u32, u32)> for FieldElement2625x4 { + type Output = FieldElement2625x4; + /// Perform a multiplication by a vector of small constants. + /// + /// # Postconditions + /// + /// The coefficients of the result are bounded with \\( b < 0.007 \\). + #[inline] + fn mul(self, scalars: (u32, u32, u32, u32)) -> FieldElement2625x4 { + let consts = u32x8::new(scalars.0, 0, scalars.1, 0, scalars.2, 0, scalars.3, 0); + + let (b0, b1) = unpack_pair(self.0[0]); + let (b2, b3) = unpack_pair(self.0[1]); + let (b4, b5) = unpack_pair(self.0[2]); + let (b6, b7) = unpack_pair(self.0[3]); + let (b8, b9) = unpack_pair(self.0[4]); + + FieldElement2625x4::reduce64([ + b0.mul32(consts), + b1.mul32(consts), + b2.mul32(consts), + b3.mul32(consts), + b4.mul32(consts), + b5.mul32(consts), + b6.mul32(consts), + b7.mul32(consts), + b8.mul32(consts), + b9.mul32(consts), + ]) + } +} + +#[unsafe_target_feature("avx2")] +impl Mul<&FieldElement2625x4> for &FieldElement2625x4 { + type Output = FieldElement2625x4; + /// Multiply `self` by `rhs`. + /// + /// # Preconditions + /// + /// The coefficients of `self` must be bounded with \\( b < 2.5 \\). + /// + /// The coefficients of `rhs` must be bounded with \\( b < 1.75 \\). + /// + /// # Postconditions + /// + /// The coefficients of the result are bounded with \\( b < 0.007 \\). + /// + #[rustfmt::skip] // keep alignment of z* calculations + #[inline] + fn mul(self, rhs: &FieldElement2625x4) -> FieldElement2625x4 { + #[inline(always)] + fn m(x: u32x8, y: u32x8) -> u64x4 { + x.mul32(y) + } + + #[inline(always)] + fn m_lo(x: u32x8, y: u32x8) -> u32x8 { + x.mul32(y).into() + } + + let (x0, x1) = unpack_pair(self.0[0]); + let (x2, x3) = unpack_pair(self.0[1]); + let (x4, x5) = unpack_pair(self.0[2]); + let (x6, x7) = unpack_pair(self.0[3]); + let (x8, x9) = unpack_pair(self.0[4]); + + let (y0, y1) = unpack_pair(rhs.0[0]); + let (y2, y3) = unpack_pair(rhs.0[1]); + let (y4, y5) = unpack_pair(rhs.0[2]); + let (y6, y7) = unpack_pair(rhs.0[3]); + let (y8, y9) = unpack_pair(rhs.0[4]); + + let v19 = u32x8::new(19, 0, 19, 0, 19, 0, 19, 0); + + let y1_19 = m_lo(v19, y1); // This fits in a u32 + let y2_19 = m_lo(v19, y2); // iff 26 + b + lg(19) < 32 + let y3_19 = m_lo(v19, y3); // if b < 32 - 26 - 4.248 = 1.752 + let y4_19 = m_lo(v19, y4); + let y5_19 = m_lo(v19, y5); + let y6_19 = m_lo(v19, y6); + let y7_19 = m_lo(v19, y7); + let y8_19 = m_lo(v19, y8); + let y9_19 = m_lo(v19, y9); + + let x1_2 = x1 + x1; // This fits in a u32 iff 25 + b + 1 < 32 + let x3_2 = x3 + x3; // iff b < 6 + let x5_2 = x5 + x5; + let x7_2 = x7 + x7; + let x9_2 = x9 + x9; + + let z0 = m(x0, y0) + m(x1_2, y9_19) + m(x2, y8_19) + m(x3_2, y7_19) + m(x4, y6_19) + m(x5_2, y5_19) + m(x6, y4_19) + m(x7_2, y3_19) + m(x8, y2_19) + m(x9_2, y1_19); + let z1 = m(x0, y1) + m(x1, y0) + m(x2, y9_19) + m(x3, y8_19) + m(x4, y7_19) + m(x5, y6_19) + m(x6, y5_19) + m(x7, y4_19) + m(x8, y3_19) + m(x9, y2_19); + let z2 = m(x0, y2) + m(x1_2, y1) + m(x2, y0) + m(x3_2, y9_19) + m(x4, y8_19) + m(x5_2, y7_19) + m(x6, y6_19) + m(x7_2, y5_19) + m(x8, y4_19) + m(x9_2, y3_19); + let z3 = m(x0, y3) + m(x1, y2) + m(x2, y1) + m(x3, y0) + m(x4, y9_19) + m(x5, y8_19) + m(x6, y7_19) + m(x7, y6_19) + m(x8, y5_19) + m(x9, y4_19); + let z4 = m(x0, y4) + m(x1_2, y3) + m(x2, y2) + m(x3_2, y1) + m(x4, y0) + m(x5_2, y9_19) + m(x6, y8_19) + m(x7_2, y7_19) + m(x8, y6_19) + m(x9_2, y5_19); + let z5 = m(x0, y5) + m(x1, y4) + m(x2, y3) + m(x3, y2) + m(x4, y1) + m(x5, y0) + m(x6, y9_19) + m(x7, y8_19) + m(x8, y7_19) + m(x9, y6_19); + let z6 = m(x0, y6) + m(x1_2, y5) + m(x2, y4) + m(x3_2, y3) + m(x4, y2) + m(x5_2, y1) + m(x6, y0) + m(x7_2, y9_19) + m(x8, y8_19) + m(x9_2, y7_19); + let z7 = m(x0, y7) + m(x1, y6) + m(x2, y5) + m(x3, y4) + m(x4, y3) + m(x5, y2) + m(x6, y1) + m(x7, y0) + m(x8, y9_19) + m(x9, y8_19); + let z8 = m(x0, y8) + m(x1_2, y7) + m(x2, y6) + m(x3_2, y5) + m(x4, y4) + m(x5_2, y3) + m(x6, y2) + m(x7_2, y1) + m(x8, y0) + m(x9_2, y9_19); + let z9 = m(x0, y9) + m(x1, y8) + m(x2, y7) + m(x3, y6) + m(x4, y5) + m(x5, y4) + m(x6, y3) + m(x7, y2) + m(x8, y1) + m(x9, y0); + + // The bounds on z[i] are the same as in the serial 32-bit code + // and the comment below is copied from there: + + // How big is the contribution to z[i+j] from x[i], y[j]? + // + // Using the bounds above, we get: + // + // i even, j even: x[i]*y[j] < 2^(26+b)*2^(26+b) = 2*2^(51+2*b) + // i odd, j even: x[i]*y[j] < 2^(25+b)*2^(26+b) = 1*2^(51+2*b) + // i even, j odd: x[i]*y[j] < 2^(26+b)*2^(25+b) = 1*2^(51+2*b) + // i odd, j odd: 2*x[i]*y[j] < 2*2^(25+b)*2^(25+b) = 1*2^(51+2*b) + // + // We perform inline reduction mod p by replacing 2^255 by 19 + // (since 2^255 - 19 = 0 mod p). This adds a factor of 19, so + // we get the bounds (z0 is the biggest one, but calculated for + // posterity here in case finer estimation is needed later): + // + // z0 < ( 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 249*2^(51 + 2*b) + // z1 < ( 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 154*2^(51 + 2*b) + // z2 < ( 2 + 1 + 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 195*2^(51 + 2*b) + // z3 < ( 1 + 1 + 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 118*2^(51 + 2*b) + // z4 < ( 2 + 1 + 2 + 1 + 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 141*2^(51 + 2*b) + // z5 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 82*2^(51 + 2*b) + // z6 < ( 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 87*2^(51 + 2*b) + // z7 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1*19 + 1*19 )*2^(51 + 2b) = 46*2^(51 + 2*b) + // z8 < ( 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1*19 )*2^(51 + 2b) = 33*2^(51 + 2*b) + // z9 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 )*2^(51 + 2b) = 10*2^(51 + 2*b) + // + // So z[0] fits into a u64 if 51 + 2*b + lg(249) < 64 + // if b < 2.5. + + // In fact this bound is slightly sloppy, since it treats both + // inputs x and y as being bounded by the same parameter b, + // while they are in fact bounded by b_x and b_y, and we + // already require that b_y < 1.75 in order to fit the + // multiplications by 19 into a u32. The tighter bound on b_y + // means we could get a tighter bound on the outputs, or a + // looser bound on b_x. + FieldElement2625x4::reduce64([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9]) + } +} + +#[cfg(target_feature = "avx2")] +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn scale_by_curve_constants() { + let mut x = FieldElement2625x4::splat(&FieldElement51::ONE); + + x = x * (121666, 121666, 2 * 121666, 2 * 121665); + + let xs = x.split(); + assert_eq!(xs[0], FieldElement51([121666, 0, 0, 0, 0])); + assert_eq!(xs[1], FieldElement51([121666, 0, 0, 0, 0])); + assert_eq!(xs[2], FieldElement51([2 * 121666, 0, 0, 0, 0])); + assert_eq!(xs[3], FieldElement51([2 * 121665, 0, 0, 0, 0])); + } + + #[test] + fn diff_sum_vs_serial() { + let x0 = FieldElement51([10000, 10001, 10002, 10003, 10004]); + let x1 = FieldElement51([10100, 10101, 10102, 10103, 10104]); + let x2 = FieldElement51([10200, 10201, 10202, 10203, 10204]); + let x3 = FieldElement51([10300, 10301, 10302, 10303, 10304]); + + let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3).diff_sum(); + + let result = vec.split(); + + assert_eq!(result[0], &x1 - &x0); + assert_eq!(result[1], &x1 + &x0); + assert_eq!(result[2], &x3 - &x2); + assert_eq!(result[3], &x3 + &x2); + } + + #[test] + fn square_vs_serial() { + let x0 = FieldElement51([10000, 10001, 10002, 10003, 10004]); + let x1 = FieldElement51([10100, 10101, 10102, 10103, 10104]); + let x2 = FieldElement51([10200, 10201, 10202, 10203, 10204]); + let x3 = FieldElement51([10300, 10301, 10302, 10303, 10304]); + + let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3); + + let result = vec.square_and_negate_D().split(); + + assert_eq!(result[0], &x0 * &x0); + assert_eq!(result[1], &x1 * &x1); + assert_eq!(result[2], &x2 * &x2); + assert_eq!(result[3], -&(&x3 * &x3)); + } + + #[test] + fn multiply_vs_serial() { + let x0 = FieldElement51([10000, 10001, 10002, 10003, 10004]); + let x1 = FieldElement51([10100, 10101, 10102, 10103, 10104]); + let x2 = FieldElement51([10200, 10201, 10202, 10203, 10204]); + let x3 = FieldElement51([10300, 10301, 10302, 10303, 10304]); + + let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3); + let vecprime = vec.clone(); + + let result = (&vec * &vecprime).split(); + + assert_eq!(result[0], &x0 * &x0); + assert_eq!(result[1], &x1 * &x1); + assert_eq!(result[2], &x2 * &x2); + assert_eq!(result[3], &x3 * &x3); + } + + #[test] + fn test_unpack_repack_pair() { + let x0 = FieldElement51([10000 + (10001 << 26), 0, 0, 0, 0]); + let x1 = FieldElement51([10100 + (10101 << 26), 0, 0, 0, 0]); + let x2 = FieldElement51([10200 + (10201 << 26), 0, 0, 0, 0]); + let x3 = FieldElement51([10300 + (10301 << 26), 0, 0, 0, 0]); + + let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3); + + let src = vec.0[0]; + + let (a, b) = unpack_pair(src); + + let expected_a = u32x8::new(10000, 0, 10100, 0, 10200, 0, 10300, 0); + let expected_b = u32x8::new(10001, 0, 10101, 0, 10201, 0, 10301, 0); + + assert_eq!(a, expected_a); + assert_eq!(b, expected_b); + + let expected_src = repack_pair(a, b); + + assert_eq!(src, expected_src); + } + + #[test] + fn new_split_roundtrips() { + let x0 = FieldElement51::from_bytes(&[0x10; 32]); + let x1 = FieldElement51::from_bytes(&[0x11; 32]); + let x2 = FieldElement51::from_bytes(&[0x12; 32]); + let x3 = FieldElement51::from_bytes(&[0x13; 32]); + + let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3); + + let splits = vec.split(); + + assert_eq!(x0, splits[0]); + assert_eq!(x1, splits[1]); + assert_eq!(x2, splits[2]); + assert_eq!(x3, splits[3]); + } +} diff --git a/curve25519-elligator2/src/backend/vector/avx2/mod.rs b/curve25519-elligator2/src/backend/vector/avx2/mod.rs new file mode 100644 index 00000000..921a9bac --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/avx2/mod.rs @@ -0,0 +1,20 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +#![doc = include_str!("../../../../docs/avx2-notes.md")] + +pub(crate) mod field; + +pub(crate) mod edwards; + +pub(crate) mod constants; + +pub(crate) use self::edwards::{CachedPoint, ExtendedPoint}; diff --git a/curve25519-elligator2/src/backend/vector/ifma/constants.rs b/curve25519-elligator2/src/backend/vector/ifma/constants.rs new file mode 100644 index 00000000..ca2e9d07 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/ifma/constants.rs @@ -0,0 +1,2064 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2018-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Henry de Valence + +//! This module contains constants used by the IFMA backend. + +use crate::backend::vector::packed_simd::u64x4; + +#[cfg(feature = "precomputed-tables")] +use crate::window::NafLookupTable8; + +use super::edwards::{CachedPoint, ExtendedPoint}; +use super::field::{F51x4Reduced, F51x4Unreduced}; + +/// The identity element as an `ExtendedPoint`. +pub(crate) static EXTENDEDPOINT_IDENTITY: ExtendedPoint = ExtendedPoint(F51x4Unreduced([ + u64x4::new_const(0, 1, 1, 0), + u64x4::new_const(0, 0, 0, 0), + u64x4::new_const(0, 0, 0, 0), + u64x4::new_const(0, 0, 0, 0), + u64x4::new_const(0, 0, 0, 0), +])); + +/// The identity element as a `CachedPoint`. +pub(crate) static CACHEDPOINT_IDENTITY: CachedPoint = CachedPoint(F51x4Reduced([ + u64x4::new_const(121647, 121666, 243332, 2251799813685229), + u64x4::new_const(2251799813685248, 0, 0, 2251799813685247), + u64x4::new_const(2251799813685247, 0, 0, 2251799813685247), + u64x4::new_const(2251799813685247, 0, 0, 2251799813685247), + u64x4::new_const(2251799813685247, 0, 0, 2251799813685247), +])); + +/// Odd multiples of the Ed25519 basepoint: +#[cfg(feature = "precomputed-tables")] +pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8 = NafLookupTable8([ + CachedPoint(F51x4Reduced([ + u64x4::new_const(1277522120965857, 73557767439946, 243332, 1943719795065404), + u64x4::new_const(108375142003455, 341984820733594, 0, 2097709862669256), + u64x4::new_const(150073485536043, 750646439938056, 0, 581130035634455), + u64x4::new_const(2149983732744869, 1903255931888577, 0, 646644904824193), + u64x4::new_const(291045673509296, 1060034214701851, 0, 325245010451737), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1970681836121889, + 1660307753655178, + 1077207637163462, + 1436413309977108, + ), + u64x4::new_const( + 158785710838757, + 919645875412951, + 174577133496574, + 2213787394009350, + ), + u64x4::new_const( + 1017606396438281, + 1240932851489554, + 918203302506967, + 1239827708070863, + ), + u64x4::new_const( + 1748989883612327, + 1745367742532782, + 1168385548387, + 1211387683826673, + ), + u64x4::new_const( + 799349980018733, + 1471088235739693, + 1505351346057417, + 2104975925096407, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 171437462972293, + 36016853025886, + 1184164975342640, + 1633525003912147, + ), + u64x4::new_const( + 2113383632509037, + 1946216474924125, + 1884174984466256, + 1373317790955847, + ), + u64x4::new_const( + 791293623466401, + 1796466048084189, + 444977763198796, + 629823271230872, + ), + u64x4::new_const( + 1093217720067380, + 2157024270666135, + 238122980108466, + 806820763806847, + ), + u64x4::new_const( + 793658959468458, + 368578641413741, + 11592529764159, + 2144017075993471, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1538027396670268, + 1588896993892061, + 675619548648376, + 788373514423313, + ), + u64x4::new_const( + 1987517656073805, + 1940987929951188, + 666993851697339, + 2040540928108427, + ), + u64x4::new_const( + 375514548584082, + 1726008037083790, + 1070069155000872, + 570111103756303, + ), + u64x4::new_const( + 772223645372213, + 2123395244967674, + 868238486911408, + 1846639042240362, + ), + u64x4::new_const( + 872865734460736, + 32277956842850, + 1701451131455402, + 773883376061880, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1845177363882902, + 275858237213625, + 1052127336883600, + 171072805852218, + ), + u64x4::new_const( + 139016783952609, + 462699304987089, + 430046471494974, + 410922720999257, + ), + u64x4::new_const( + 846403935976337, + 243817706931454, + 971825428236901, + 571800039596794, + ), + u64x4::new_const( + 807642685434918, + 1933536976438782, + 812324278898440, + 688391556487313, + ), + u64x4::new_const( + 76239450396192, + 629532732688863, + 1833302026979779, + 650067934544499, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1373931604989264, + 331159264656614, + 364391529321767, + 874765630865409, + ), + u64x4::new_const( + 2109908262150241, + 473400816504190, + 91544045127333, + 976307977609515, + ), + u64x4::new_const( + 330175435673491, + 2126511895885904, + 1022944071588421, + 2158480209801463, + ), + u64x4::new_const( + 1305666795527971, + 162063591028664, + 2193154870675382, + 1789166662611800, + ), + u64x4::new_const( + 817858592500508, + 1672743239440202, + 859976879916778, + 1167423340862516, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 274334925170164, + 565841102587251, + 603083835949120, + 607539210240861, + ), + u64x4::new_const( + 196754662972649, + 1339063476699167, + 1406077076979491, + 896902435668469, + ), + u64x4::new_const( + 397962210956733, + 174839587476217, + 1381082665748936, + 175195877334136, + ), + u64x4::new_const( + 717429432748391, + 1635309821746318, + 363374010274647, + 882908746261699, + ), + u64x4::new_const( + 600946602802781, + 1946596133370711, + 1532135183320341, + 690530671668253, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 2074443704000945, + 2163534804938345, + 425423840926528, + 1100826171404853, + ), + u64x4::new_const( + 111700142796101, + 1456893872751964, + 1186145518682968, + 2192182627706116, + ), + u64x4::new_const( + 1848722121856066, + 2123239575044749, + 1323870754599272, + 883211262889775, + ), + u64x4::new_const( + 938263017712916, + 689670293631396, + 183944529557576, + 501908638166580, + ), + u64x4::new_const( + 2170571907220631, + 36636756989655, + 1875035480138608, + 803703278398018, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1053429956874064, + 1636640618139765, + 1556890827801070, + 2142720579528828, + ), + u64x4::new_const( + 1814240918422814, + 692326274601777, + 1054896561802157, + 2025454041705534, + ), + u64x4::new_const( + 2109495823888757, + 1287497869997176, + 194170063200096, + 621116840113213, + ), + u64x4::new_const( + 2156505873679998, + 2197064359737385, + 1312887672223536, + 369862818895912, + ), + u64x4::new_const( + 977381163563657, + 1878897311974033, + 2144566861359744, + 1832960882773351, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1266492498289486, + 1301524759372145, + 324789537938521, + 442710471023019, + ), + u64x4::new_const( + 1232722320001345, + 1191193089162455, + 176474006074813, + 2158950213252857, + ), + u64x4::new_const( + 1901782191467749, + 494791441598902, + 1820415815322129, + 854954583485223, + ), + u64x4::new_const( + 1511383667649702, + 792536415032464, + 2027741263854728, + 1727944381044738, + ), + u64x4::new_const( + 606355788891204, + 1670687521471220, + 582824350365415, + 1509135066079912, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1079942762813598, + 2015830004785901, + 479916361323351, + 1907956590950158, + ), + u64x4::new_const( + 2053400302939156, + 1319799126867070, + 19493088767391, + 1908755581402373, + ), + u64x4::new_const( + 2235858054780980, + 885832711204321, + 810332865560178, + 103174191215441, + ), + u64x4::new_const( + 1843466881032833, + 355511728384038, + 693846715794114, + 186545012724117, + ), + u64x4::new_const( + 1661758432892509, + 1491022339899281, + 698941123765263, + 174945407208560, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1075933251927831, + 400263885306647, + 1308157532880528, + 347933379126665, + ), + u64x4::new_const( + 673811632329433, + 1584860147186478, + 271778891257244, + 498194055154207, + ), + u64x4::new_const( + 703783427747558, + 1051624728592032, + 1371463103351544, + 230351033002960, + ), + u64x4::new_const( + 860729466483372, + 421647596766583, + 1520613871336707, + 635298775280054, + ), + u64x4::new_const( + 1168352891728845, + 1691216293752089, + 1799491997061519, + 399728882318504, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 420156727446514, + 1483649215777128, + 165508610199900, + 1918121104840431, + ), + u64x4::new_const( + 2129902293682427, + 730952770435213, + 2184527544565390, + 1939880362232986, + ), + u64x4::new_const( + 1771978364905086, + 510975579746524, + 927564335219142, + 177574146260558, + ), + u64x4::new_const( + 2164104536437514, + 1532598873799015, + 406875369182421, + 1367005937406517, + ), + u64x4::new_const( + 35073200082587, + 1981124717036219, + 1854087014063833, + 122419694385217, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1963785875777739, + 411497142699119, + 1974557512687408, + 1268304422747183, + ), + u64x4::new_const( + 762752575978150, + 1443822019541748, + 1331556159904338, + 377726798263780, + ), + u64x4::new_const( + 825953972847841, + 353487068141356, + 1955697322427207, + 2048226560172078, + ), + u64x4::new_const( + 1482378558684434, + 657691905625918, + 923870001994493, + 1694132799397736, + ), + u64x4::new_const( + 1643904759603122, + 170495566698285, + 1218312703413378, + 784318735038131, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 939230507241903, + 2238763473105245, + 1827325199528162, + 1153939339775538, + ), + u64x4::new_const( + 38544505283339, + 258889431497015, + 351721979677947, + 1357907379592829, + ), + u64x4::new_const( + 1393974676373341, + 1131355528938676, + 473104915298872, + 978783482501776, + ), + u64x4::new_const( + 2131516168980501, + 2113911780991092, + 1477027502354261, + 542884524860340, + ), + u64x4::new_const( + 1029606261349423, + 64226378557628, + 1669131167474348, + 2212808057234874, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1423176501543193, + 163313632579593, + 2220495688893001, + 2220041045291870, + ), + u64x4::new_const( + 1111834224023697, + 1026815658023689, + 1404605100939775, + 1412149108248227, + ), + u64x4::new_const( + 1542537854906076, + 1270288391129127, + 991419278941933, + 1824939809581980, + ), + u64x4::new_const( + 1142003215657891, + 525980550896367, + 1508270666157963, + 917719462309053, + ), + u64x4::new_const( + 400851268057105, + 1620818232405188, + 1251478578139510, + 2162841805361886, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 2125383272208441, + 1368790097335984, + 11813369275978, + 639513785921674, + ), + u64x4::new_const( + 2200806265616284, + 1041996387620216, + 1275149397833084, + 1723371028064068, + ), + u64x4::new_const( + 603720163891275, + 2135593511176153, + 2049641644431548, + 1198460677818310, + ), + u64x4::new_const( + 1862491879401621, + 2008116580769441, + 626566325260235, + 1058308304975798, + ), + u64x4::new_const( + 628557314314858, + 1075323332046522, + 1631772244117095, + 1812174547405683, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1222773123817104, + 363276129291452, + 796237592807883, + 1914425291893078, + ), + u64x4::new_const( + 1721259057429088, + 734941709009373, + 1553365830564638, + 1492120931079419, + ), + u64x4::new_const( + 1009354843273686, + 293884504384873, + 1050281954944357, + 134132942667344, + ), + u64x4::new_const( + 23119363298711, + 1694754778833445, + 1725925193393496, + 1738396998222001, + ), + u64x4::new_const( + 1753692057254667, + 118428526447110, + 840961387840295, + 1227619055408558, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1004186117579547, + 508771992330056, + 1426571663072421, + 2238524171903259, + ), + u64x4::new_const( + 744764613007812, + 398885442368825, + 2047459490294949, + 2141797621077959, + ), + u64x4::new_const( + 4556204156489, + 1708213022802363, + 1071381560923933, + 393474529142567, + ), + u64x4::new_const( + 350116198213005, + 945907227204695, + 168267474358731, + 1801504420122711, + ), + u64x4::new_const( + 728788674520360, + 1262722049156121, + 455259596607008, + 1159442365834489, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 2226818917892677, + 185673745808179, + 2240952219732549, + 324137961621908, + ), + u64x4::new_const( + 1659527641857410, + 973964060249383, + 1349692151487730, + 1172743533370593, + ), + u64x4::new_const( + 310591478467746, + 2123977244137170, + 774562885265820, + 430035546191685, + ), + u64x4::new_const( + 2150863173197992, + 2101978317708856, + 193592648406011, + 1375328504508580, + ), + u64x4::new_const( + 1946235834250479, + 121741431658675, + 1004342690620100, + 2063466488599450, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 463079632200153, + 40415275714025, + 545935352782679, + 1458043501600908, + ), + u64x4::new_const( + 783771976559993, + 880839641726471, + 1782028201271831, + 41664413404590, + ), + u64x4::new_const( + 985129151724159, + 187728621410000, + 16620051933318, + 378011085567733, + ), + u64x4::new_const( + 1820372198168638, + 905710046480679, + 1912961774249737, + 1868135861067161, + ), + u64x4::new_const( + 474460473983187, + 1455684425673661, + 652771171116843, + 733511920760779, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1088886980746809, + 1660218575261626, + 527921875040240, + 915086639857889, + ), + u64x4::new_const( + 1814735788528175, + 1586698876186367, + 2040856637532862, + 405684812785624, + ), + u64x4::new_const( + 658578559700999, + 1751442070931114, + 1293623371490094, + 715026719042518, + ), + u64x4::new_const( + 382156225644820, + 897982285504960, + 577673183555858, + 1158728558309719, + ), + u64x4::new_const( + 1865791902475663, + 124491617513788, + 758484125168765, + 734065580770143, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 330985690350617, + 2214424721795630, + 973374650780848, + 1507267060932964, + ), + u64x4::new_const( + 1733823971011290, + 1730742552292995, + 669018866977489, + 604527664126146, + ), + u64x4::new_const( + 1082092498645474, + 1029182053935309, + 756799947765834, + 1764720030308351, + ), + u64x4::new_const( + 969912105693756, + 38116887248276, + 2148030115687613, + 995140534653865, + ), + u64x4::new_const( + 2154373397460354, + 298128883464656, + 479587543632539, + 1061127201140779, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 843064865526549, + 2019481782959016, + 1873125524281672, + 2013330239022371, + ), + u64x4::new_const( + 1192932403815186, + 1818108671859220, + 1247005102016258, + 1210577394628058, + ), + u64x4::new_const( + 132359273326717, + 795492788299178, + 1235924489372816, + 891705064411550, + ), + u64x4::new_const( + 1425833709104858, + 152114045731085, + 991347902581315, + 1387773338707683, + ), + u64x4::new_const( + 48024203807922, + 157005564892977, + 1474053161953744, + 727448023498345, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1076621484026788, + 1309917234320927, + 1786998180233659, + 1595497085944737, + ), + u64x4::new_const( + 1737334672694726, + 2038133716999447, + 1929061192400917, + 620544235219084, + ), + u64x4::new_const( + 1550527313469747, + 329096759623509, + 1585214659209474, + 693419841748324, + ), + u64x4::new_const( + 1450010875912315, + 2085047082180569, + 757421110771886, + 389367139787400, + ), + u64x4::new_const( + 781339490566117, + 132941783448971, + 258650459725225, + 2042274962585613, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 859638991542650, + 2249840007426442, + 1138753070862357, + 793751342318913, + ), + u64x4::new_const( + 2133476133447306, + 1027010646129239, + 436851910892865, + 866949948830344, + ), + u64x4::new_const( + 1936003572431223, + 531513680252193, + 1929877059408416, + 830585477662503, + ), + u64x4::new_const( + 1460760405777960, + 686673748420916, + 275475330051554, + 1581792376993692, + ), + u64x4::new_const( + 894482039456784, + 1801274480988632, + 16407898635278, + 1668497039215206, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 258585746227669, + 936490904651492, + 1826793887434108, + 1201219990633823, + ), + u64x4::new_const( + 979462791643635, + 461762372210187, + 218708929991480, + 1378150755760178, + ), + u64x4::new_const( + 642542170229970, + 787135445552820, + 371168855880557, + 182642566486693, + ), + u64x4::new_const( + 1152277399721904, + 1726910452705576, + 1452393215705343, + 2117799581546845, + ), + u64x4::new_const( + 1211265143925330, + 14373046151823, + 1745528818271507, + 1842106288572078, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 635154614562157, + 1956763034454109, + 509123035953043, + 445727657534780, + ), + u64x4::new_const( + 2072765509783252, + 1282639891593570, + 1075086397362049, + 722996110178195, + ), + u64x4::new_const( + 1385572918825603, + 1190035835509576, + 218317841176013, + 1047865370756924, + ), + u64x4::new_const( + 473991569426488, + 1910588123704592, + 1338270051770806, + 401676861680875, + ), + u64x4::new_const( + 992455353618436, + 126422733426929, + 1955248037756399, + 119233843022643, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1555272991526078, + 2214378187116349, + 366893798097444, + 1401502118355702, + ), + u64x4::new_const( + 1157229521930713, + 2144787187506262, + 1681597469697840, + 847499096518697, + ), + u64x4::new_const( + 1872802655800758, + 1027119609820793, + 1137278714788290, + 1664750301179485, + ), + u64x4::new_const( + 1091289858897030, + 910126419483563, + 1101920147235731, + 597083075893952, + ), + u64x4::new_const( + 1711011533670315, + 185206680336278, + 1620960612579784, + 1968598849170880, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 73077300235958, + 257216723095630, + 466947267713785, + 847105214181598, + ), + u64x4::new_const( + 1322905631406309, + 407458059314731, + 230045063190376, + 923800751267786, + ), + u64x4::new_const( + 1146027205000415, + 1541328763727623, + 768510249199119, + 1630223587589059, + ), + u64x4::new_const( + 1930368769879433, + 1376145403022159, + 1898149855343131, + 1709421930518180, + ), + u64x4::new_const( + 633944191571764, + 58314960742839, + 2050971151574988, + 757799756090059, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 361576929158539, + 1035682890165818, + 160945739362874, + 266975208626222, + ), + u64x4::new_const( + 1635371797076046, + 2106722851965197, + 451585919077206, + 6692426667180, + ), + u64x4::new_const( + 175820543533852, + 2057511393764025, + 1531846543720469, + 1648320903946519, + ), + u64x4::new_const( + 947461770620940, + 1107335044817620, + 1725565474111216, + 2182263619949220, + ), + u64x4::new_const( + 726444888601221, + 1379664085279206, + 1517215633290417, + 1763968936542507, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 686545355846512, + 1712283265573167, + 1743509592736302, + 1653906616429153, + ), + u64x4::new_const( + 985108805667149, + 2244347650874753, + 1304749057936860, + 321846134330589, + ), + u64x4::new_const( + 296321076156886, + 1717929256240029, + 450933772486425, + 2015536856431605, + ), + u64x4::new_const( + 1690393512821866, + 646913049470189, + 2198650647576397, + 1230646705710442, + ), + u64x4::new_const( + 601961913448442, + 878806578800541, + 620497587492381, + 330716414244629, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 631510982676132, + 1755753187697174, + 1596201246674299, + 2197888384902121, + ), + u64x4::new_const( + 626957678275745, + 1447583371478595, + 1375375216702128, + 1443613232818823, + ), + u64x4::new_const( + 1962997804660501, + 1051744123184519, + 1002558639300437, + 1237313314603385, + ), + u64x4::new_const( + 2118828335274995, + 226398203764759, + 889099617161107, + 1620967117678504, + ), + u64x4::new_const( + 227261019362935, + 2046897556746842, + 591524060355369, + 2178552047369691, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1375403119051662, + 222313965014452, + 539873444241395, + 213198095917915, + ), + u64x4::new_const( + 1436952871599114, + 1229749762725246, + 1174441562267670, + 265367077740349, + ), + u64x4::new_const( + 11107426165917, + 985954476039181, + 1147329112365579, + 1133931640328107, + ), + u64x4::new_const( + 585235055006843, + 699515259687482, + 299559608721134, + 2134819767146767, + ), + u64x4::new_const( + 1376401105588528, + 391412107507860, + 302743651807545, + 1362834426455518, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1802940904616205, + 1615132760193234, + 869321663313735, + 666494072545310, + ), + u64x4::new_const( + 1452849320020701, + 1472716813676364, + 472862999490802, + 359937983286145, + ), + u64x4::new_const( + 1221198323133843, + 491718521756528, + 1387135774113906, + 793779904904008, + ), + u64x4::new_const( + 1032129287829151, + 30730741946697, + 217603185195068, + 2118169309744162, + ), + u64x4::new_const( + 225899335574721, + 1767553399797342, + 881082465669982, + 1435383196392870, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1127093564374276, + 2245188499702906, + 1250041622887441, + 2179324911668149, + ), + u64x4::new_const( + 908019210866875, + 1879900391060964, + 1355047706206597, + 647218945377302, + ), + u64x4::new_const( + 1616265604422592, + 2134336781521657, + 1157711219915601, + 1227494173135033, + ), + u64x4::new_const( + 136450294813355, + 1984543542455033, + 1199486053011083, + 33687889941331, + ), + u64x4::new_const( + 1053447012707371, + 68239344331930, + 537448158443925, + 1829189783369646, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 996806463322563, + 2043104667851348, + 1110361398300309, + 1218740346887957, + ), + u64x4::new_const( + 399141907016839, + 1307691109658227, + 532535384961264, + 896201194398872, + ), + u64x4::new_const( + 111705272106160, + 1790972382466021, + 1159338112559144, + 303544352897203, + ), + u64x4::new_const( + 1036600573322969, + 1457119922663674, + 334117653665514, + 460023361701263, + ), + u64x4::new_const( + 1363773215189933, + 1915594049343802, + 1661249423378694, + 1744945551969247, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 3093919631215, + 574886478077610, + 1704446919728971, + 250093147254210, + ), + u64x4::new_const( + 1387413348737796, + 360142717826981, + 2116185073015983, + 474541388374100, + ), + u64x4::new_const( + 1632539630892580, + 1332404016215719, + 2145297637794728, + 1289783723173504, + ), + u64x4::new_const( + 1030244179060173, + 579782698595797, + 1062365251139982, + 677149839815546, + ), + u64x4::new_const( + 6671539419876, + 1426937459653775, + 406942403696343, + 675479224223817, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 271984148441782, + 1708099625818957, + 1499011822959235, + 516808451044836, + ), + u64x4::new_const( + 1124847751346323, + 2038336022958449, + 1721698491022600, + 705944403212572, + ), + u64x4::new_const( + 85459783780275, + 1715213099986669, + 1728445509034791, + 730657630359717, + ), + u64x4::new_const( + 1185034652652387, + 755472578204310, + 476118360897817, + 1800434542785310, + ), + u64x4::new_const( + 1815589628676941, + 491778500674079, + 1547664984392513, + 279891608681267, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 2036337168672113, + 1730787524684269, + 639134121311693, + 698060925015524, + ), + u64x4::new_const( + 315211075189491, + 1329055848835358, + 688621136402134, + 1271193060119448, + ), + u64x4::new_const( + 1697984374314012, + 459330773536457, + 305481314707918, + 61676911066002, + ), + u64x4::new_const( + 2166631826859191, + 2105217187401781, + 937587962768434, + 357397435365683, + ), + u64x4::new_const( + 1206757093145471, + 1287847622009294, + 1951336140421622, + 2233789834777410, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 82144190081093, + 1568417433687791, + 907555979158442, + 2037855062523867, + ), + u64x4::new_const( + 1225315484058853, + 315317868015613, + 1765025920288384, + 175223259828436, + ), + u64x4::new_const( + 1215010304871271, + 662713408454950, + 429517658575616, + 991062684008811, + ), + u64x4::new_const( + 993837615254894, + 1485561584889450, + 2001836754226476, + 1915943063896801, + ), + u64x4::new_const( + 818895101625673, + 1342479472068804, + 1380235330010671, + 23315169761453, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1500726307559118, + 956166860173424, + 512663951564436, + 1940180717699824, + ), + u64x4::new_const( + 1789521472720825, + 779456898652427, + 2035063615853504, + 863582140589407, + ), + u64x4::new_const( + 634508890793787, + 1748041666732214, + 259642099961634, + 1294936839797812, + ), + u64x4::new_const( + 2183334898697038, + 2197242820694806, + 2217225409073703, + 992633998226449, + ), + u64x4::new_const( + 2197077498155916, + 1562008797791883, + 1395088759904208, + 331715244679294, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 186854731652320, + 284389440026580, + 1252175415119400, + 1025377410100223, + ), + u64x4::new_const( + 1578732129417607, + 898645497852382, + 2237766074482974, + 1939197790303592, + ), + u64x4::new_const( + 1438830390640145, + 1682452015845597, + 1108441197232223, + 1984134492898664, + ), + u64x4::new_const( + 282668727301669, + 1609018289552856, + 390363439795705, + 1138459124667912, + ), + u64x4::new_const( + 18889015928490, + 532489638086725, + 324621535996080, + 2210046082697453, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 2041327051605378, + 2244037852176483, + 2116336876147147, + 9616672544864, + ), + u64x4::new_const( + 969847387559191, + 1059119127679639, + 1764630094670633, + 364568045311834, + ), + u64x4::new_const( + 505938893153679, + 2075421412172902, + 326984153045666, + 1959549727324704, + ), + u64x4::new_const( + 1088715617911260, + 13917085151028, + 950568481355929, + 23687195265771, + ), + u64x4::new_const( + 1798284568673198, + 808382292203333, + 2214698741961545, + 610817203275867, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1731488929623777, + 1158815615106413, + 1491090861948525, + 1428384712900962, + ), + u64x4::new_const( + 722237139522457, + 1514290328911535, + 1366197913116230, + 1519472657321210, + ), + u64x4::new_const( + 246028966932273, + 1888239319448405, + 423720022211163, + 455243905681470, + ), + u64x4::new_const( + 738323403716001, + 1758018973481179, + 1180718299482318, + 1008495946606708, + ), + u64x4::new_const( + 334959381596119, + 1704599537529481, + 2172191232106896, + 13502508918495, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 273393076768079, + 427388720298603, + 1071733376018227, + 1715429388968611, + ), + u64x4::new_const( + 751776629892313, + 1965239102856011, + 541955408230119, + 831043488876080, + ), + u64x4::new_const( + 643718536393104, + 390543998404644, + 2176730661486279, + 499459234889079, + ), + u64x4::new_const( + 1482404333915009, + 865527293526285, + 507957951411713, + 216456252558825, + ), + u64x4::new_const( + 2210281256300231, + 1519357818277551, + 1257866936775246, + 1689605217672864, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 2135395168187905, + 2214400157568614, + 2032983817870823, + 1124945109072647, + ), + u64x4::new_const( + 1602820011758145, + 906675633903289, + 782700735390986, + 2067218823525601, + ), + u64x4::new_const( + 786785748926382, + 1433583123655616, + 905839404290873, + 2249680349963778, + ), + u64x4::new_const( + 1940824582370584, + 1610961256326291, + 285307858781375, + 1755588655461194, + ), + u64x4::new_const( + 233682812055333, + 2146114223476434, + 41132209533476, + 535292431776371, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 600257696476418, + 18449221564824, + 1422209458591138, + 239571584769716, + ), + u64x4::new_const( + 2056372917056980, + 1155290566623531, + 1252473955568148, + 1276690716882081, + ), + u64x4::new_const( + 246974369025311, + 658117221519903, + 2000380937898441, + 1351183273924850, + ), + u64x4::new_const( + 1803747363753112, + 1736801515030186, + 2025633577199091, + 603378480769167, + ), + u64x4::new_const( + 57348749438551, + 1893551220299655, + 657926732731806, + 1522499384853705, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 591809128842736, + 284860517232591, + 27436696863545, + 886306697195798, + ), + u64x4::new_const( + 2113192175751749, + 1405882509906423, + 561316282804847, + 835573846576266, + ), + u64x4::new_const( + 94407289485409, + 1781534171669004, + 2098782516531528, + 598529921520053, + ), + u64x4::new_const( + 1860137004504786, + 2197323407480349, + 1516772733981532, + 961740253777086, + ), + u64x4::new_const( + 1484139612868217, + 1593557644636881, + 838834937143441, + 36382198263380, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1165898865828562, + 1153420815042389, + 1068625028915785, + 1945927229911090, + ), + u64x4::new_const( + 843454394017146, + 571029655293754, + 386282254545998, + 1804608237584150, + ), + u64x4::new_const( + 370552451091100, + 1279105656351124, + 1864742949668631, + 2093071521726981, + ), + u64x4::new_const( + 1872542389052198, + 1679083953574330, + 349872262454465, + 1470311090717925, + ), + u64x4::new_const( + 685345654160323, + 319718985807814, + 1359932285384164, + 1410900103316331, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 2083666668832889, + 314624387816655, + 1496694646480345, + 1946728950459189, + ), + u64x4::new_const( + 1579153761571203, + 508771185291380, + 1002249659402007, + 551517831173801, + ), + u64x4::new_const( + 2132371471626150, + 1988122278556533, + 1552195130653890, + 1327637750292755, + ), + u64x4::new_const( + 118937099181527, + 382610380973142, + 634951529106471, + 382740054041699, + ), + u64x4::new_const( + 801287519643470, + 87822941589258, + 1908825350108451, + 1404208826499115, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 330347226380261, + 672119116965146, + 1761510370768005, + 1959200302484704, + ), + u64x4::new_const( + 1631876583009250, + 1684917718484264, + 1027256947805920, + 2174612545251129, + ), + u64x4::new_const( + 636668855699872, + 625187713984839, + 265886954766790, + 167898557908504, + ), + u64x4::new_const( + 1210974548180860, + 2051308710365526, + 907620584086428, + 1081788677970850, + ), + u64x4::new_const( + 621792955460854, + 1450945504745382, + 1666728650687828, + 977937146451674, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 24725936182267, + 2226765032752574, + 2036560083102883, + 2002351185719584, + ), + u64x4::new_const( + 1620080779405308, + 1493220053370419, + 2245691691038916, + 1152182628629603, + ), + u64x4::new_const( + 317928527147500, + 1855194218440212, + 979380281964169, + 861442286685289, + ), + u64x4::new_const( + 393308472784625, + 486143087279967, + 1234071346236405, + 777748237119399, + ), + u64x4::new_const( + 43850412814718, + 1497656407486446, + 744128331046695, + 1618035787321792, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1670169946550211, + 1230951698726438, + 806586940221293, + 23159779184607, + ), + u64x4::new_const( + 634011340979302, + 764182085034744, + 731065727766955, + 1737985776442180, + ), + u64x4::new_const( + 240492712141842, + 73976435954441, + 162810587166835, + 697230894340912, + ), + u64x4::new_const( + 1299745598348388, + 1359436039694544, + 1856609816731554, + 25228008461513, + ), + u64x4::new_const( + 2180690501932381, + 2161211192848458, + 87069466793408, + 2003456332883860, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1106932458043379, + 1675181364231371, + 1681785724775243, + 131824742557210, + ), + u64x4::new_const( + 1671649414647169, + 1827849994880670, + 1097958057111899, + 701956891169434, + ), + u64x4::new_const( + 2095539283710881, + 591029812888096, + 1699571518315654, + 1297589045812566, + ), + u64x4::new_const( + 1345612272298537, + 2166754730876055, + 2047982622154948, + 1785222806258129, + ), + u64x4::new_const( + 2181915268829890, + 1895697064378670, + 1288412327355885, + 1561075738281368, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 741330264098392, + 357073519729966, + 1603572339180975, + 433572083688575, + ), + u64x4::new_const( + 699685108971208, + 1719650727634959, + 1941668009419214, + 870374958347891, + ), + u64x4::new_const( + 385971389331537, + 11655507719711, + 94814615497633, + 515572102810609, + ), + u64x4::new_const( + 1396688200590426, + 1518748475144123, + 162386454324368, + 2083303971579002, + ), + u64x4::new_const( + 1511688632419263, + 251584258592336, + 545345887993880, + 1229840230314160, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1298668855706029, + 2017860934939344, + 2224150456036391, + 1925926576297971, + ), + u64x4::new_const( + 259522963883544, + 1312469129541229, + 1647530465049600, + 1113737129047154, + ), + u64x4::new_const( + 733193298663145, + 2115712816303403, + 897628702762311, + 116440277571901, + ), + u64x4::new_const( + 1998719395229750, + 1662774553684237, + 194395608126452, + 98796702872301, + ), + u64x4::new_const( + 2226158244229144, + 91961728239158, + 526869903032152, + 849263805316773, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 472779569333556, + 854477760843410, + 2070906720349401, + 734613359834689, + ), + u64x4::new_const( + 1771897100487404, + 1604024196006064, + 319699348925383, + 437152129592623, + ), + u64x4::new_const( + 627618365135361, + 1768642666037955, + 588564169143939, + 35295037750744, + ), + u64x4::new_const( + 220241884231278, + 319104161410840, + 1048165719448798, + 1583931089774347, + ), + u64x4::new_const( + 166479451884333, + 1623611819962804, + 59990366193679, + 900727256046987, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 1944687327687331, + 1328410791053991, + 2083980670913902, + 609396833380574, + ), + u64x4::new_const( + 1907563845734496, + 1385619047697883, + 869817384774457, + 106642388505109, + ), + u64x4::new_const( + 1006516581737154, + 1561918369633937, + 1921172883211450, + 2216650451558824, + ), + u64x4::new_const( + 1780506017391778, + 233064930371847, + 1332962603425752, + 1380075261612354, + ), + u64x4::new_const( + 1907624789747741, + 1310065402098523, + 1838275780706825, + 884225500782782, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 198729830692545, + 100156148743413, + 2140568641558859, + 2220606475942394, + ), + u64x4::new_const( + 1108788217903741, + 1706330932366163, + 2050449866410661, + 684907598542847, + ), + u64x4::new_const( + 1101958322366646, + 659427843062405, + 253899933868173, + 896574852821269, + ), + u64x4::new_const( + 1157052140740658, + 440541103447032, + 2173354981480949, + 604768603561932, + ), + u64x4::new_const( + 961238337866054, + 830849154351308, + 1643852412409441, + 1436749321770368, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 784870637473285, + 1180234052037572, + 2086951602998715, + 419328169540373, + ), + u64x4::new_const( + 1966862397394559, + 788036164772123, + 2024355635709481, + 1471696676696146, + ), + u64x4::new_const( + 1468884300957205, + 1408016588131185, + 2229595828577885, + 240413942963547, + ), + u64x4::new_const( + 1481791691942441, + 970648959691160, + 1635500996148197, + 2236917233261585, + ), + u64x4::new_const( + 31660820731028, + 801794768903647, + 1069092619607344, + 282652554845923, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 911659428682786, + 762502588057038, + 1311399152500807, + 1966922911783311, + ), + u64x4::new_const( + 1229849228728540, + 258161307933217, + 2140796867375541, + 1569345075547911, + ), + u64x4::new_const( + 1487354676143742, + 1818317546165791, + 811033554173350, + 1768788663337616, + ), + u64x4::new_const( + 450017165913234, + 962535873747168, + 2099104262993585, + 503030952485785, + ), + u64x4::new_const( + 1259958681304518, + 479589250923541, + 1503904042161640, + 706283657294305, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 794562643024291, + 198670993088241, + 1678984629358943, + 273399517554618, + ), + u64x4::new_const( + 188458991574433, + 1389872130156447, + 1461868931574746, + 795140878721432, + ), + u64x4::new_const( + 624046647169653, + 630363741191019, + 911018499983500, + 1410140563046579, + ), + u64x4::new_const( + 1675056174405076, + 632544713589250, + 795454163559811, + 1535271563341780, + ), + u64x4::new_const( + 25504547444781, + 812510098987855, + 51290042016232, + 1992260991700127, + ), + ])), + CachedPoint(F51x4Reduced([ + u64x4::new_const( + 269968325452358, + 470932785179706, + 1684444304834150, + 1027482126748243, + ), + u64x4::new_const( + 457941065342419, + 2117377568137882, + 1209423706730905, + 2192403099717071, + ), + u64x4::new_const( + 1899046404863678, + 1359500336071762, + 1492389156724726, + 1455627081827750, + ), + u64x4::new_const( + 2016101061876546, + 1967000012916571, + 582539481696050, + 1197538178790094, + ), + u64x4::new_const( + 639684852217504, + 1799941252757449, + 1470016556327743, + 846111828965901, + ), + ])), +]); diff --git a/curve25519-elligator2/src/backend/vector/ifma/edwards.rs b/curve25519-elligator2/src/backend/vector/ifma/edwards.rs new file mode 100644 index 00000000..c148bf15 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/ifma/edwards.rs @@ -0,0 +1,337 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2018-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Henry de Valence + +#![allow(non_snake_case)] + +use crate::traits::Identity; + +use core::ops::{Add, Neg, Sub}; + +use subtle::Choice; +use subtle::ConditionallySelectable; + +use curve25519_dalek_derive::unsafe_target_feature; + +use crate::edwards; +use crate::window::{LookupTable, NafLookupTable5}; + +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +use crate::window::NafLookupTable8; + +use super::constants; +use super::field::{F51x4Reduced, F51x4Unreduced, Lanes, Shuffle}; + +#[derive(Copy, Clone, Debug)] +pub struct ExtendedPoint(pub(super) F51x4Unreduced); + +#[derive(Copy, Clone, Debug)] +pub struct CachedPoint(pub(super) F51x4Reduced); + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl From for ExtendedPoint { + fn from(P: edwards::EdwardsPoint) -> ExtendedPoint { + ExtendedPoint(F51x4Unreduced::new(&P.X, &P.Y, &P.Z, &P.T)) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl From for edwards::EdwardsPoint { + fn from(P: ExtendedPoint) -> edwards::EdwardsPoint { + let reduced = F51x4Reduced::from(P.0); + let tmp = F51x4Unreduced::from(reduced).split(); + edwards::EdwardsPoint { + X: tmp[0], + Y: tmp[1], + Z: tmp[2], + T: tmp[3], + } + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl From for CachedPoint { + fn from(P: ExtendedPoint) -> CachedPoint { + let mut x = P.0; + + x = x.blend(&x.diff_sum(), Lanes::AB); + x = &F51x4Reduced::from(x) * (121666, 121666, 2 * 121666, 2 * 121665); + x = x.blend(&x.negate_lazy(), Lanes::D); + + CachedPoint(F51x4Reduced::from(x)) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl Default for ExtendedPoint { + fn default() -> ExtendedPoint { + ExtendedPoint::identity() + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl Identity for ExtendedPoint { + fn identity() -> ExtendedPoint { + constants::EXTENDEDPOINT_IDENTITY + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl ExtendedPoint { + pub fn double(&self) -> ExtendedPoint { + // (Y1 X1 T1 Z1) -- uses vpshufd (1c latency @ 1/c) + let mut tmp0 = self.0.shuffle(Shuffle::BADC); + + // (X1+Y1 X1+Y1 X1+Y1 X1+Y1) -- can use vpinserti128 + let mut tmp1 = (self.0 + tmp0).shuffle(Shuffle::ABAB); + + // (X1 Y1 Z1 X1+Y1) + tmp0 = self.0.blend(&tmp1, Lanes::D); + + tmp1 = F51x4Reduced::from(tmp0).square(); + // Now tmp1 = (S1 S2 S3 S4) + + // We want to compute + // + // + | S1 | S1 | S1 | S1 | + // + | S2 | | | S2 | + // + | | | S3 | | + // + | | | S3 | | + // + | |16p |16p |16p | + // - | | S2 | S2 | | + // - | | | | S4 | + // ======================= + // S5 S6 S8 S9 + + let zero = F51x4Unreduced::ZERO; + + let S1_S1_S1_S1 = tmp1.shuffle(Shuffle::AAAA); + let S2_S2_S2_S2 = tmp1.shuffle(Shuffle::BBBB); + + let S2_S2_S2_S4 = S2_S2_S2_S2.blend(&tmp1, Lanes::D).negate_lazy(); + + tmp0 = S1_S1_S1_S1 + zero.blend(&(tmp1 + tmp1), Lanes::C); + tmp0 = tmp0 + zero.blend(&S2_S2_S2_S2, Lanes::AD); + tmp0 = tmp0 + zero.blend(&S2_S2_S2_S4, Lanes::BCD); + + let tmp2 = F51x4Reduced::from(tmp0); + + ExtendedPoint(&tmp2.shuffle(Shuffle::DBBD) * &tmp2.shuffle(Shuffle::CACA)) + } + + pub fn mul_by_pow_2(&self, k: u32) -> ExtendedPoint { + let mut tmp: ExtendedPoint = *self; + for _ in 0..k { + tmp = tmp.double(); + } + tmp + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl<'a, 'b> Add<&'b CachedPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + /// Add an `ExtendedPoint` and a `CachedPoint`. + fn add(self, other: &'b CachedPoint) -> ExtendedPoint { + let mut tmp = self.0; + + tmp = tmp.blend(&tmp.diff_sum(), Lanes::AB); + // tmp = (Y1-X1 Y1+X1 Z1 T1) = (S0 S1 Z1 T1) + + tmp = &F51x4Reduced::from(tmp) * &other.0; + // tmp = (S0*S2' S1*S3' Z1*Z2' T1*T2') = (S8 S9 S10 S11) + + tmp = tmp.shuffle(Shuffle::ABDC); + // tmp = (S8 S9 S11 S10) + + let tmp = F51x4Reduced::from(tmp.diff_sum()); + // tmp = (S9-S8 S9+S8 S10-S11 S10+S11) = (S12 S13 S14 S15) + + let t0 = tmp.shuffle(Shuffle::ADDA); + // t0 = (S12 S15 S15 S12) + let t1 = tmp.shuffle(Shuffle::CBCB); + // t1 = (S14 S13 S14 S13) + + // Return (S12*S14 S15*S13 S15*S14 S12*S13) = (X3 Y3 Z3 T3) + ExtendedPoint(&t0 * &t1) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl Default for CachedPoint { + fn default() -> CachedPoint { + CachedPoint::identity() + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl Identity for CachedPoint { + fn identity() -> CachedPoint { + constants::CACHEDPOINT_IDENTITY + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl ConditionallySelectable for CachedPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + CachedPoint(F51x4Reduced::conditional_select(&a.0, &b.0, choice)) + } + + fn conditional_assign(&mut self, other: &Self, choice: Choice) { + self.0.conditional_assign(&other.0, choice); + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl<'a> Neg for &'a CachedPoint { + type Output = CachedPoint; + + fn neg(self) -> CachedPoint { + let swapped = self.0.shuffle(Shuffle::BACD); + CachedPoint(swapped.blend(&(-self.0), Lanes::D)) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl<'a, 'b> Sub<&'b CachedPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + /// Implement subtraction by negating the point and adding. + fn sub(self, other: &'b CachedPoint) -> ExtendedPoint { + self + &(-other) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl<'a> From<&'a edwards::EdwardsPoint> for LookupTable { + fn from(point: &'a edwards::EdwardsPoint) -> Self { + let P = ExtendedPoint::from(*point); + let mut points = [CachedPoint::from(P); 8]; + for i in 0..7 { + points[i + 1] = (&P + &points[i]).into(); + } + LookupTable(points) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl<'a> From<&'a edwards::EdwardsPoint> for NafLookupTable5 { + fn from(point: &'a edwards::EdwardsPoint) -> Self { + let A = ExtendedPoint::from(*point); + let mut Ai = [CachedPoint::from(A); 8]; + let A2 = A.double(); + for i in 0..7 { + Ai[i + 1] = (&A2 + &Ai[i]).into(); + } + // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A] + NafLookupTable5(Ai) + } +} + +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl<'a> From<&'a edwards::EdwardsPoint> for NafLookupTable8 { + fn from(point: &'a edwards::EdwardsPoint) -> Self { + let A = ExtendedPoint::from(*point); + let mut Ai = [CachedPoint::from(A); 64]; + let A2 = A.double(); + for i in 0..63 { + Ai[i + 1] = (&A2 + &Ai[i]).into(); + } + // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A, ..., 127A] + NafLookupTable8(Ai) + } +} + +#[cfg(target_feature = "avx512ifma,avx512vl")] +#[cfg(test)] +mod test { + use super::*; + + fn addition_test_helper(P: edwards::EdwardsPoint, Q: edwards::EdwardsPoint) { + // Test the serial implementation of the parallel addition formulas + //let R_serial: edwards::EdwardsPoint = serial_add(P.into(), Q.into()).into(); + + // Test the vector implementation of the parallel readdition formulas + let cached_Q = CachedPoint::from(ExtendedPoint::from(Q)); + let R_vector: edwards::EdwardsPoint = (&ExtendedPoint::from(P) + &cached_Q).into(); + let S_vector: edwards::EdwardsPoint = (&ExtendedPoint::from(P) - &cached_Q).into(); + + println!("Testing point addition:"); + println!("P = {:?}", P); + println!("Q = {:?}", Q); + println!("cached Q = {:?}", cached_Q); + println!("R = P + Q = {:?}", &P + &Q); + //println!("R_serial = {:?}", R_serial); + println!("R_vector = {:?}", R_vector); + println!("S = P - Q = {:?}", &P - &Q); + println!("S_vector = {:?}", S_vector); + //assert_eq!(R_serial.compress(), (&P + &Q).compress()); + assert_eq!(R_vector.compress(), (&P + &Q).compress()); + assert_eq!(S_vector.compress(), (&P - &Q).compress()); + println!("OK!\n"); + } + + #[test] + fn vector_addition_vs_serial_addition_vs_edwards_extendedpoint() { + use crate::constants; + use crate::scalar::Scalar; + + println!("Testing id +- id"); + let P = edwards::EdwardsPoint::identity(); + let Q = edwards::EdwardsPoint::identity(); + addition_test_helper(P, Q); + + println!("Testing id +- B"); + let P = edwards::EdwardsPoint::identity(); + let Q = constants::ED25519_BASEPOINT_POINT; + addition_test_helper(P, Q); + + println!("Testing B +- B"); + let P = constants::ED25519_BASEPOINT_POINT; + let Q = constants::ED25519_BASEPOINT_POINT; + addition_test_helper(P, Q); + + println!("Testing B +- kB"); + let P = constants::ED25519_BASEPOINT_POINT; + let Q = constants::ED25519_BASEPOINT_TABLE * &Scalar::from(8475983829u64); + addition_test_helper(P, Q); + } + + fn doubling_test_helper(P: edwards::EdwardsPoint) { + //let R1: edwards::EdwardsPoint = serial_double(P.into()).into(); + let R2: edwards::EdwardsPoint = ExtendedPoint::from(P).double().into(); + println!("Testing point doubling:"); + println!("P = {:?}", P); + //println!("(serial) R1 = {:?}", R1); + println!("(vector) R2 = {:?}", R2); + println!("P + P = {:?}", &P + &P); + //assert_eq!(R1.compress(), (&P + &P).compress()); + assert_eq!(R2.compress(), (&P + &P).compress()); + println!("OK!\n"); + } + + #[test] + fn vector_doubling_vs_serial_doubling_vs_edwards_extendedpoint() { + use crate::constants; + use crate::scalar::Scalar; + + println!("Testing [2]id"); + let P = edwards::EdwardsPoint::identity(); + doubling_test_helper(P); + + println!("Testing [2]B"); + let P = constants::ED25519_BASEPOINT_POINT; + doubling_test_helper(P); + + println!("Testing [2]([k]B)"); + let P = constants::ED25519_BASEPOINT_TABLE * &Scalar::from(8475983829u64); + doubling_test_helper(P); + } +} diff --git a/curve25519-elligator2/src/backend/vector/ifma/field.rs b/curve25519-elligator2/src/backend/vector/ifma/field.rs new file mode 100644 index 00000000..deceebd8 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/ifma/field.rs @@ -0,0 +1,842 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +#![allow(non_snake_case)] + +use crate::backend::vector::packed_simd::u64x4; +use core::ops::{Add, Mul, Neg}; + +use crate::backend::serial::u64::field::FieldElement51; + +use curve25519_dalek_derive::unsafe_target_feature; + +/// A wrapper around `vpmadd52luq` that works on `u64x4`. +#[unsafe_target_feature("avx512ifma,avx512vl")] +#[inline] +unsafe fn madd52lo(z: u64x4, x: u64x4, y: u64x4) -> u64x4 { + use core::arch::x86_64::_mm256_madd52lo_epu64; + _mm256_madd52lo_epu64(z.into(), x.into(), y.into()).into() +} + +/// A wrapper around `vpmadd52huq` that works on `u64x4`. +#[unsafe_target_feature("avx512ifma,avx512vl")] +#[inline] +unsafe fn madd52hi(z: u64x4, x: u64x4, y: u64x4) -> u64x4 { + use core::arch::x86_64::_mm256_madd52hi_epu64; + _mm256_madd52hi_epu64(z.into(), x.into(), y.into()).into() +} + +/// A vector of four field elements in radix 2^51, with unreduced coefficients. +#[derive(Copy, Clone, Debug)] +pub struct F51x4Unreduced(pub(crate) [u64x4; 5]); + +/// A vector of four field elements in radix 2^51, with reduced coefficients. +#[derive(Copy, Clone, Debug)] +pub struct F51x4Reduced(pub(crate) [u64x4; 5]); + +#[allow(clippy::upper_case_acronyms)] +#[derive(Copy, Clone)] +pub enum Shuffle { + AAAA, + BBBB, + BADC, + BACD, + ADDA, + CBCB, + ABDC, + ABAB, + DBBD, + CACA, +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +#[inline(always)] +fn shuffle_lanes(x: u64x4, control: Shuffle) -> u64x4 { + unsafe { + use core::arch::x86_64::_mm256_permute4x64_epi64 as perm; + + match control { + Shuffle::AAAA => perm(x.into(), 0b00_00_00_00).into(), + Shuffle::BBBB => perm(x.into(), 0b01_01_01_01).into(), + Shuffle::BADC => perm(x.into(), 0b10_11_00_01).into(), + Shuffle::BACD => perm(x.into(), 0b11_10_00_01).into(), + Shuffle::ADDA => perm(x.into(), 0b00_11_11_00).into(), + Shuffle::CBCB => perm(x.into(), 0b01_10_01_10).into(), + Shuffle::ABDC => perm(x.into(), 0b10_11_01_00).into(), + Shuffle::ABAB => perm(x.into(), 0b01_00_01_00).into(), + Shuffle::DBBD => perm(x.into(), 0b11_01_01_11).into(), + Shuffle::CACA => perm(x.into(), 0b00_10_00_10).into(), + } + } +} + +#[allow(clippy::upper_case_acronyms)] +#[derive(Copy, Clone)] +pub enum Lanes { + D, + C, + AB, + AC, + AD, + BCD, +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +#[inline] +fn blend_lanes(x: u64x4, y: u64x4, control: Lanes) -> u64x4 { + unsafe { + use core::arch::x86_64::_mm256_blend_epi32 as blend; + + match control { + Lanes::D => blend(x.into(), y.into(), 0b11_00_00_00).into(), + Lanes::C => blend(x.into(), y.into(), 0b00_11_00_00).into(), + Lanes::AB => blend(x.into(), y.into(), 0b00_00_11_11).into(), + Lanes::AC => blend(x.into(), y.into(), 0b00_11_00_11).into(), + Lanes::AD => blend(x.into(), y.into(), 0b11_00_00_11).into(), + Lanes::BCD => blend(x.into(), y.into(), 0b11_11_11_00).into(), + } + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl F51x4Unreduced { + pub const ZERO: F51x4Unreduced = F51x4Unreduced([u64x4::splat_const::<0>(); 5]); + + pub fn new( + x0: &FieldElement51, + x1: &FieldElement51, + x2: &FieldElement51, + x3: &FieldElement51, + ) -> F51x4Unreduced { + F51x4Unreduced([ + u64x4::new(x0.0[0], x1.0[0], x2.0[0], x3.0[0]), + u64x4::new(x0.0[1], x1.0[1], x2.0[1], x3.0[1]), + u64x4::new(x0.0[2], x1.0[2], x2.0[2], x3.0[2]), + u64x4::new(x0.0[3], x1.0[3], x2.0[3], x3.0[3]), + u64x4::new(x0.0[4], x1.0[4], x2.0[4], x3.0[4]), + ]) + } + + pub fn split(&self) -> [FieldElement51; 4] { + let x = &self.0; + [ + FieldElement51([ + x[0].extract::<0>(), + x[1].extract::<0>(), + x[2].extract::<0>(), + x[3].extract::<0>(), + x[4].extract::<0>(), + ]), + FieldElement51([ + x[0].extract::<1>(), + x[1].extract::<1>(), + x[2].extract::<1>(), + x[3].extract::<1>(), + x[4].extract::<1>(), + ]), + FieldElement51([ + x[0].extract::<2>(), + x[1].extract::<2>(), + x[2].extract::<2>(), + x[3].extract::<2>(), + x[4].extract::<2>(), + ]), + FieldElement51([ + x[0].extract::<3>(), + x[1].extract::<3>(), + x[2].extract::<3>(), + x[3].extract::<3>(), + x[4].extract::<3>(), + ]), + ] + } + + #[inline] + pub fn diff_sum(&self) -> F51x4Unreduced { + // tmp1 = (B, A, D, C) + let tmp1 = self.shuffle(Shuffle::BADC); + // tmp2 = (-A, B, -C, D) + let tmp2 = self.blend(&self.negate_lazy(), Lanes::AC); + // (B - A, B + A, D - C, D + C) + tmp1 + tmp2 + } + + #[inline] + pub fn negate_lazy(&self) -> F51x4Unreduced { + let lo = u64x4::splat(36028797018963664u64); + let hi = u64x4::splat(36028797018963952u64); + F51x4Unreduced([ + lo - self.0[0], + hi - self.0[1], + hi - self.0[2], + hi - self.0[3], + hi - self.0[4], + ]) + } + + #[inline] + pub fn shuffle(&self, control: Shuffle) -> F51x4Unreduced { + F51x4Unreduced([ + shuffle_lanes(self.0[0], control), + shuffle_lanes(self.0[1], control), + shuffle_lanes(self.0[2], control), + shuffle_lanes(self.0[3], control), + shuffle_lanes(self.0[4], control), + ]) + } + + #[inline] + pub fn blend(&self, other: &F51x4Unreduced, control: Lanes) -> F51x4Unreduced { + F51x4Unreduced([ + blend_lanes(self.0[0], other.0[0], control), + blend_lanes(self.0[1], other.0[1], control), + blend_lanes(self.0[2], other.0[2], control), + blend_lanes(self.0[3], other.0[3], control), + blend_lanes(self.0[4], other.0[4], control), + ]) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl Neg for F51x4Reduced { + type Output = F51x4Reduced; + + fn neg(self) -> F51x4Reduced { + F51x4Unreduced::from(self).negate_lazy().into() + } +} + +use subtle::Choice; +use subtle::ConditionallySelectable; + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl ConditionallySelectable for F51x4Reduced { + #[inline] + fn conditional_select(a: &F51x4Reduced, b: &F51x4Reduced, choice: Choice) -> F51x4Reduced { + let mask = (-(choice.unwrap_u8() as i64)) as u64; + let mask_vec = u64x4::splat(mask); + F51x4Reduced([ + a.0[0] ^ (mask_vec & (a.0[0] ^ b.0[0])), + a.0[1] ^ (mask_vec & (a.0[1] ^ b.0[1])), + a.0[2] ^ (mask_vec & (a.0[2] ^ b.0[2])), + a.0[3] ^ (mask_vec & (a.0[3] ^ b.0[3])), + a.0[4] ^ (mask_vec & (a.0[4] ^ b.0[4])), + ]) + } + + #[inline] + fn conditional_assign(&mut self, other: &F51x4Reduced, choice: Choice) { + let mask = (-(choice.unwrap_u8() as i64)) as u64; + let mask_vec = u64x4::splat(mask); + self.0[0] ^= mask_vec & (self.0[0] ^ other.0[0]); + self.0[1] ^= mask_vec & (self.0[1] ^ other.0[1]); + self.0[2] ^= mask_vec & (self.0[2] ^ other.0[2]); + self.0[3] ^= mask_vec & (self.0[3] ^ other.0[3]); + self.0[4] ^= mask_vec & (self.0[4] ^ other.0[4]); + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl F51x4Reduced { + #[inline] + pub fn shuffle(&self, control: Shuffle) -> F51x4Reduced { + F51x4Reduced([ + shuffle_lanes(self.0[0], control), + shuffle_lanes(self.0[1], control), + shuffle_lanes(self.0[2], control), + shuffle_lanes(self.0[3], control), + shuffle_lanes(self.0[4], control), + ]) + } + + #[inline] + pub fn blend(&self, other: &F51x4Reduced, control: Lanes) -> F51x4Reduced { + F51x4Reduced([ + blend_lanes(self.0[0], other.0[0], control), + blend_lanes(self.0[1], other.0[1], control), + blend_lanes(self.0[2], other.0[2], control), + blend_lanes(self.0[3], other.0[3], control), + blend_lanes(self.0[4], other.0[4], control), + ]) + } + + #[inline] + pub fn square(&self) -> F51x4Unreduced { + unsafe { + let x = &self.0; + + // Represent values with coeff. 2 + let mut z0_2 = u64x4::splat(0); + let mut z1_2 = u64x4::splat(0); + let mut z2_2 = u64x4::splat(0); + let mut z3_2 = u64x4::splat(0); + let mut z4_2 = u64x4::splat(0); + let mut z5_2 = u64x4::splat(0); + let mut z6_2 = u64x4::splat(0); + let mut z7_2 = u64x4::splat(0); + let mut z9_2 = u64x4::splat(0); + + // Represent values with coeff. 4 + let mut z2_4 = u64x4::splat(0); + let mut z3_4 = u64x4::splat(0); + let mut z4_4 = u64x4::splat(0); + let mut z5_4 = u64x4::splat(0); + let mut z6_4 = u64x4::splat(0); + let mut z7_4 = u64x4::splat(0); + let mut z8_4 = u64x4::splat(0); + + let mut z0_1 = u64x4::splat(0); + z0_1 = madd52lo(z0_1, x[0], x[0]); + + let mut z1_1 = u64x4::splat(0); + z1_2 = madd52lo(z1_2, x[0], x[1]); + z1_2 = madd52hi(z1_2, x[0], x[0]); + + z2_4 = madd52hi(z2_4, x[0], x[1]); + let mut z2_1 = z2_4.shl::<2>(); + z2_2 = madd52lo(z2_2, x[0], x[2]); + z2_1 = madd52lo(z2_1, x[1], x[1]); + + z3_4 = madd52hi(z3_4, x[0], x[2]); + let mut z3_1 = z3_4.shl::<2>(); + z3_2 = madd52lo(z3_2, x[1], x[2]); + z3_2 = madd52lo(z3_2, x[0], x[3]); + z3_2 = madd52hi(z3_2, x[1], x[1]); + + z4_4 = madd52hi(z4_4, x[1], x[2]); + z4_4 = madd52hi(z4_4, x[0], x[3]); + let mut z4_1 = z4_4.shl::<2>(); + z4_2 = madd52lo(z4_2, x[1], x[3]); + z4_2 = madd52lo(z4_2, x[0], x[4]); + z4_1 = madd52lo(z4_1, x[2], x[2]); + + z5_4 = madd52hi(z5_4, x[1], x[3]); + z5_4 = madd52hi(z5_4, x[0], x[4]); + let mut z5_1 = z5_4.shl::<2>(); + z5_2 = madd52lo(z5_2, x[2], x[3]); + z5_2 = madd52lo(z5_2, x[1], x[4]); + z5_2 = madd52hi(z5_2, x[2], x[2]); + + z6_4 = madd52hi(z6_4, x[2], x[3]); + z6_4 = madd52hi(z6_4, x[1], x[4]); + let mut z6_1 = z6_4.shl::<2>(); + z6_2 = madd52lo(z6_2, x[2], x[4]); + z6_1 = madd52lo(z6_1, x[3], x[3]); + + z7_4 = madd52hi(z7_4, x[2], x[4]); + let mut z7_1 = z7_4.shl::<2>(); + z7_2 = madd52lo(z7_2, x[3], x[4]); + z7_2 = madd52hi(z7_2, x[3], x[3]); + + z8_4 = madd52hi(z8_4, x[3], x[4]); + let mut z8_1 = z8_4.shl::<2>(); + z8_1 = madd52lo(z8_1, x[4], x[4]); + + let mut z9_1 = u64x4::splat(0); + z9_2 = madd52hi(z9_2, x[4], x[4]); + + z5_1 += z5_2.shl::<1>(); + z6_1 += z6_2.shl::<1>(); + z7_1 += z7_2.shl::<1>(); + z9_1 += z9_2.shl::<1>(); + + let mut t0 = u64x4::splat(0); + let mut t1 = u64x4::splat(0); + let r19 = u64x4::splat(19); + + t0 = madd52hi(t0, r19, z9_1); + t1 = madd52lo(t1, r19, z9_1.shr::<52>()); + + z4_2 = madd52lo(z4_2, r19, z8_1.shr::<52>()); + z3_2 = madd52lo(z3_2, r19, z7_1.shr::<52>()); + z2_2 = madd52lo(z2_2, r19, z6_1.shr::<52>()); + z1_2 = madd52lo(z1_2, r19, z5_1.shr::<52>()); + + z0_2 = madd52lo(z0_2, r19, t0 + t1); + z1_2 = madd52hi(z1_2, r19, z5_1); + z2_2 = madd52hi(z2_2, r19, z6_1); + z3_2 = madd52hi(z3_2, r19, z7_1); + z4_2 = madd52hi(z4_2, r19, z8_1); + + z0_1 = madd52lo(z0_1, r19, z5_1); + z1_1 = madd52lo(z1_1, r19, z6_1); + z2_1 = madd52lo(z2_1, r19, z7_1); + z3_1 = madd52lo(z3_1, r19, z8_1); + z4_1 = madd52lo(z4_1, r19, z9_1); + + F51x4Unreduced([ + z0_1 + z0_2 + z0_2, + z1_1 + z1_2 + z1_2, + z2_1 + z2_2 + z2_2, + z3_1 + z3_2 + z3_2, + z4_1 + z4_2 + z4_2, + ]) + } + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl From for F51x4Unreduced { + #[inline] + fn from(x: F51x4Reduced) -> F51x4Unreduced { + F51x4Unreduced(x.0) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl From for F51x4Reduced { + #[inline] + fn from(x: F51x4Unreduced) -> F51x4Reduced { + let mask = u64x4::splat((1 << 51) - 1); + let r19 = u64x4::splat(19); + + // Compute carryouts in parallel + let c0 = x.0[0].shr::<51>(); + let c1 = x.0[1].shr::<51>(); + let c2 = x.0[2].shr::<51>(); + let c3 = x.0[3].shr::<51>(); + let c4 = x.0[4].shr::<51>(); + + unsafe { + F51x4Reduced([ + madd52lo(x.0[0] & mask, c4, r19), + (x.0[1] & mask) + c0, + (x.0[2] & mask) + c1, + (x.0[3] & mask) + c2, + (x.0[4] & mask) + c3, + ]) + } + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl Add for F51x4Unreduced { + type Output = F51x4Unreduced; + #[inline] + fn add(self, rhs: F51x4Unreduced) -> F51x4Unreduced { + F51x4Unreduced([ + self.0[0] + rhs.0[0], + self.0[1] + rhs.0[1], + self.0[2] + rhs.0[2], + self.0[3] + rhs.0[3], + self.0[4] + rhs.0[4], + ]) + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl<'a> Mul<(u32, u32, u32, u32)> for &'a F51x4Reduced { + type Output = F51x4Unreduced; + #[inline] + fn mul(self, scalars: (u32, u32, u32, u32)) -> F51x4Unreduced { + unsafe { + let x = &self.0; + let y = u64x4::new( + scalars.0 as u64, + scalars.1 as u64, + scalars.2 as u64, + scalars.3 as u64, + ); + let r19 = u64x4::splat(19); + + let mut z0_1 = u64x4::splat(0); + let mut z1_1 = u64x4::splat(0); + let mut z2_1 = u64x4::splat(0); + let mut z3_1 = u64x4::splat(0); + let mut z4_1 = u64x4::splat(0); + let mut z1_2 = u64x4::splat(0); + let mut z2_2 = u64x4::splat(0); + let mut z3_2 = u64x4::splat(0); + let mut z4_2 = u64x4::splat(0); + let mut z5_2 = u64x4::splat(0); + + // Wave 0 + z4_2 = madd52hi(z4_2, y, x[3]); + z5_2 = madd52hi(z5_2, y, x[4]); + z4_1 = madd52lo(z4_1, y, x[4]); + z0_1 = madd52lo(z0_1, y, x[0]); + z3_1 = madd52lo(z3_1, y, x[3]); + z2_1 = madd52lo(z2_1, y, x[2]); + z1_1 = madd52lo(z1_1, y, x[1]); + z3_2 = madd52hi(z3_2, y, x[2]); + + // Wave 2 + z2_2 = madd52hi(z2_2, y, x[1]); + z1_2 = madd52hi(z1_2, y, x[0]); + z0_1 = madd52lo(z0_1, z5_2 + z5_2, r19); + + F51x4Unreduced([ + z0_1, + z1_1 + z1_2 + z1_2, + z2_1 + z2_2 + z2_2, + z3_1 + z3_2 + z3_2, + z4_1 + z4_2 + z4_2, + ]) + } + } +} + +#[unsafe_target_feature("avx512ifma,avx512vl")] +impl<'a, 'b> Mul<&'b F51x4Reduced> for &'a F51x4Reduced { + type Output = F51x4Unreduced; + #[inline] + fn mul(self, rhs: &'b F51x4Reduced) -> F51x4Unreduced { + unsafe { + // Inputs + let x = &self.0; + let y = &rhs.0; + + // Accumulators for terms with coeff 1 + let mut z0_1 = u64x4::splat(0); + let mut z1_1 = u64x4::splat(0); + let mut z2_1 = u64x4::splat(0); + let mut z3_1 = u64x4::splat(0); + let mut z4_1 = u64x4::splat(0); + let mut z5_1 = u64x4::splat(0); + let mut z6_1 = u64x4::splat(0); + let mut z7_1 = u64x4::splat(0); + let mut z8_1 = u64x4::splat(0); + + // Accumulators for terms with coeff 2 + let mut z0_2 = u64x4::splat(0); + let mut z1_2 = u64x4::splat(0); + let mut z2_2 = u64x4::splat(0); + let mut z3_2 = u64x4::splat(0); + let mut z4_2 = u64x4::splat(0); + let mut z5_2 = u64x4::splat(0); + let mut z6_2 = u64x4::splat(0); + let mut z7_2 = u64x4::splat(0); + let mut z8_2 = u64x4::splat(0); + let mut z9_2 = u64x4::splat(0); + + // LLVM doesn't seem to do much work reordering IFMA + // instructions, so try to organize them into "waves" of 8 + // independent operations (4c latency, 0.5 c throughput + // means 8 in flight) + + // Wave 0 + z4_1 = madd52lo(z4_1, x[2], y[2]); + z5_2 = madd52hi(z5_2, x[2], y[2]); + z5_1 = madd52lo(z5_1, x[4], y[1]); + z6_2 = madd52hi(z6_2, x[4], y[1]); + z6_1 = madd52lo(z6_1, x[4], y[2]); + z7_2 = madd52hi(z7_2, x[4], y[2]); + z7_1 = madd52lo(z7_1, x[4], y[3]); + z8_2 = madd52hi(z8_2, x[4], y[3]); + + // Wave 1 + z4_1 = madd52lo(z4_1, x[3], y[1]); + z5_2 = madd52hi(z5_2, x[3], y[1]); + z5_1 = madd52lo(z5_1, x[3], y[2]); + z6_2 = madd52hi(z6_2, x[3], y[2]); + z6_1 = madd52lo(z6_1, x[3], y[3]); + z7_2 = madd52hi(z7_2, x[3], y[3]); + z7_1 = madd52lo(z7_1, x[3], y[4]); + z8_2 = madd52hi(z8_2, x[3], y[4]); + + // Wave 2 + z8_1 = madd52lo(z8_1, x[4], y[4]); + z9_2 = madd52hi(z9_2, x[4], y[4]); + z4_1 = madd52lo(z4_1, x[4], y[0]); + z5_2 = madd52hi(z5_2, x[4], y[0]); + z5_1 = madd52lo(z5_1, x[2], y[3]); + z6_2 = madd52hi(z6_2, x[2], y[3]); + z6_1 = madd52lo(z6_1, x[2], y[4]); + z7_2 = madd52hi(z7_2, x[2], y[4]); + + let z8 = z8_1 + z8_2 + z8_2; + let z9 = z9_2 + z9_2; + + // Wave 3 + z3_1 = madd52lo(z3_1, x[3], y[0]); + z4_2 = madd52hi(z4_2, x[3], y[0]); + z4_1 = madd52lo(z4_1, x[1], y[3]); + z5_2 = madd52hi(z5_2, x[1], y[3]); + z5_1 = madd52lo(z5_1, x[1], y[4]); + z6_2 = madd52hi(z6_2, x[1], y[4]); + z2_1 = madd52lo(z2_1, x[2], y[0]); + z3_2 = madd52hi(z3_2, x[2], y[0]); + + let z6 = z6_1 + z6_2 + z6_2; + let z7 = z7_1 + z7_2 + z7_2; + + // Wave 4 + z3_1 = madd52lo(z3_1, x[2], y[1]); + z4_2 = madd52hi(z4_2, x[2], y[1]); + z4_1 = madd52lo(z4_1, x[0], y[4]); + z5_2 = madd52hi(z5_2, x[0], y[4]); + z1_1 = madd52lo(z1_1, x[1], y[0]); + z2_2 = madd52hi(z2_2, x[1], y[0]); + z2_1 = madd52lo(z2_1, x[1], y[1]); + z3_2 = madd52hi(z3_2, x[1], y[1]); + + let z5 = z5_1 + z5_2 + z5_2; + + // Wave 5 + z3_1 = madd52lo(z3_1, x[1], y[2]); + z4_2 = madd52hi(z4_2, x[1], y[2]); + z0_1 = madd52lo(z0_1, x[0], y[0]); + z1_2 = madd52hi(z1_2, x[0], y[0]); + z1_1 = madd52lo(z1_1, x[0], y[1]); + z2_1 = madd52lo(z2_1, x[0], y[2]); + z2_2 = madd52hi(z2_2, x[0], y[1]); + z3_2 = madd52hi(z3_2, x[0], y[2]); + + let mut t0 = u64x4::splat(0); + let mut t1 = u64x4::splat(0); + let r19 = u64x4::splat(19); + + // Wave 6 + t0 = madd52hi(t0, r19, z9); + t1 = madd52lo(t1, r19, z9.shr::<52>()); + z3_1 = madd52lo(z3_1, x[0], y[3]); + z4_2 = madd52hi(z4_2, x[0], y[3]); + z1_2 = madd52lo(z1_2, r19, z5.shr::<52>()); + z2_2 = madd52lo(z2_2, r19, z6.shr::<52>()); + z3_2 = madd52lo(z3_2, r19, z7.shr::<52>()); + z0_1 = madd52lo(z0_1, r19, z5); + + // Wave 7 + z4_1 = madd52lo(z4_1, r19, z9); + z1_1 = madd52lo(z1_1, r19, z6); + z0_2 = madd52lo(z0_2, r19, t0 + t1); + z4_2 = madd52hi(z4_2, r19, z8); + z2_1 = madd52lo(z2_1, r19, z7); + z1_2 = madd52hi(z1_2, r19, z5); + z2_2 = madd52hi(z2_2, r19, z6); + z3_2 = madd52hi(z3_2, r19, z7); + + // Wave 8 + z3_1 = madd52lo(z3_1, r19, z8); + z4_2 = madd52lo(z4_2, r19, z8.shr::<52>()); + + F51x4Unreduced([ + z0_1 + z0_2 + z0_2, + z1_1 + z1_2 + z1_2, + z2_1 + z2_2 + z2_2, + z3_1 + z3_2 + z3_2, + z4_1 + z4_2 + z4_2, + ]) + } + } +} + +#[cfg(target_feature = "avx512ifma,avx512vl")] +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn vpmadd52luq() { + let x = u64x4::splat(2); + let y = u64x4::splat(3); + let mut z = u64x4::splat(5); + + z = unsafe { madd52lo(z, x, y) }; + + assert_eq!(z, u64x4::splat(5 + 2 * 3)); + } + + #[test] + fn new_split_round_trip_on_reduced_input() { + // Invert a small field element to get a big one + let a = FieldElement51([2438, 24, 243, 0, 0]).invert(); + + let ax4 = F51x4Unreduced::new(&a, &a, &a, &a); + let splits = ax4.split(); + + for i in 0..4 { + assert_eq!(a, splits[i]); + } + } + + #[test] + fn new_split_round_trip_on_unreduced_input() { + // Invert a small field element to get a big one + let a = FieldElement51([2438, 24, 243, 0, 0]).invert(); + // ... but now multiply it by 16 without reducing coeffs + let a16 = FieldElement51([ + a.0[0] << 4, + a.0[1] << 4, + a.0[2] << 4, + a.0[3] << 4, + a.0[4] << 4, + ]); + + let a16x4 = F51x4Unreduced::new(&a16, &a16, &a16, &a16); + let splits = a16x4.split(); + + for i in 0..4 { + assert_eq!(a16, splits[i]); + } + } + + #[test] + fn test_reduction() { + // Invert a small field element to get a big one + let a = FieldElement51([2438, 24, 243, 0, 0]).invert(); + // ... but now multiply it by 128 without reducing coeffs + let abig = FieldElement51([ + a.0[0] << 4, + a.0[1] << 4, + a.0[2] << 4, + a.0[3] << 4, + a.0[4] << 4, + ]); + + let abigx4: F51x4Reduced = F51x4Unreduced::new(&abig, &abig, &abig, &abig).into(); + + let splits = F51x4Unreduced::from(abigx4).split(); + let c = &a * &FieldElement51([(1 << 4), 0, 0, 0, 0]); + + for i in 0..4 { + assert_eq!(c, splits[i]); + } + } + + #[test] + fn mul_matches_serial() { + // Invert a small field element to get a big one + let a = FieldElement51([2438, 24, 243, 0, 0]).invert(); + let b = FieldElement51([98098, 87987897, 0, 1, 0]).invert(); + let c = &a * &b; + + let ax4: F51x4Reduced = F51x4Unreduced::new(&a, &a, &a, &a).into(); + let bx4: F51x4Reduced = F51x4Unreduced::new(&b, &b, &b, &b).into(); + let cx4 = &ax4 * &bx4; + + let splits = cx4.split(); + + for i in 0..4 { + assert_eq!(c, splits[i]); + } + } + + #[test] + fn iterated_mul_matches_serial() { + // Invert a small field element to get a big one + let a = FieldElement51([2438, 24, 243, 0, 0]).invert(); + let b = FieldElement51([98098, 87987897, 0, 1, 0]).invert(); + let mut c = &a * &b; + for _i in 0..1024 { + c = &a * &c; + c = &b * &c; + } + + let ax4: F51x4Reduced = F51x4Unreduced::new(&a, &a, &a, &a).into(); + let bx4: F51x4Reduced = F51x4Unreduced::new(&b, &b, &b, &b).into(); + let mut cx4 = &ax4 * &bx4; + for _i in 0..1024 { + cx4 = &ax4 * &F51x4Reduced::from(cx4); + cx4 = &bx4 * &F51x4Reduced::from(cx4); + } + + let splits = cx4.split(); + + for i in 0..4 { + assert_eq!(c, splits[i]); + } + } + + #[test] + fn square_matches_mul() { + // Invert a small field element to get a big one + let a = FieldElement51([2438, 24, 243, 0, 0]).invert(); + + let ax4: F51x4Reduced = F51x4Unreduced::new(&a, &a, &a, &a).into(); + let cx4 = &ax4 * &ax4; + let cx4_sq = ax4.square(); + + let splits = cx4.split(); + let splits_sq = cx4_sq.split(); + + for i in 0..4 { + assert_eq!(splits_sq[i], splits[i]); + } + } + + #[test] + fn iterated_square_matches_serial() { + // Invert a small field element to get a big one + let mut a = FieldElement51([2438, 24, 243, 0, 0]).invert(); + let mut ax4 = F51x4Unreduced::new(&a, &a, &a, &a); + for _j in 0..1024 { + a = a.square(); + ax4 = F51x4Reduced::from(ax4).square(); + + let splits = ax4.split(); + for i in 0..4 { + assert_eq!(a, splits[i]); + } + } + } + + #[test] + fn iterated_u32_mul_matches_serial() { + // Invert a small field element to get a big one + let a = FieldElement51([2438, 24, 243, 0, 0]).invert(); + let b = FieldElement51([121665, 0, 0, 0, 0]); + let mut c = &a * &b; + for _i in 0..1024 { + c = &b * &c; + } + + let ax4 = F51x4Unreduced::new(&a, &a, &a, &a); + let bx4 = (121665u32, 121665u32, 121665u32, 121665u32); + let mut cx4 = &F51x4Reduced::from(ax4) * bx4; + for _i in 0..1024 { + cx4 = &F51x4Reduced::from(cx4) * bx4; + } + + let splits = cx4.split(); + + for i in 0..4 { + assert_eq!(c, splits[i]); + } + } + + #[test] + fn shuffle_AAAA() { + let x0 = FieldElement51::from_bytes(&[0x10; 32]); + let x1 = FieldElement51::from_bytes(&[0x11; 32]); + let x2 = FieldElement51::from_bytes(&[0x12; 32]); + let x3 = FieldElement51::from_bytes(&[0x13; 32]); + + let x = F51x4Unreduced::new(&x0, &x1, &x2, &x3); + + let y = x.shuffle(Shuffle::AAAA); + let splits = y.split(); + + assert_eq!(splits[0], x0); + assert_eq!(splits[1], x0); + assert_eq!(splits[2], x0); + assert_eq!(splits[3], x0); + } + + #[test] + fn blend_AB() { + let x0 = FieldElement51::from_bytes(&[0x10; 32]); + let x1 = FieldElement51::from_bytes(&[0x11; 32]); + let x2 = FieldElement51::from_bytes(&[0x12; 32]); + let x3 = FieldElement51::from_bytes(&[0x13; 32]); + + let x = F51x4Unreduced::new(&x0, &x1, &x2, &x3); + let z = F51x4Unreduced::new(&x3, &x2, &x1, &x0); + + let y = x.blend(&z, Lanes::AB); + let splits = y.split(); + + assert_eq!(splits[0], x3); + assert_eq!(splits[1], x2); + assert_eq!(splits[2], x2); + assert_eq!(splits[3], x3); + } +} diff --git a/curve25519-elligator2/src/backend/vector/ifma/mod.rs b/curve25519-elligator2/src/backend/vector/ifma/mod.rs new file mode 100644 index 00000000..6191e364 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/ifma/mod.rs @@ -0,0 +1,20 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2018-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Henry de Valence + +#![doc = include_str!("../../../../docs/ifma-notes.md")] + +#[allow(missing_docs)] +pub mod field; + +#[allow(missing_docs)] +pub mod edwards; + +pub mod constants; + +pub(crate) use self::edwards::{CachedPoint, ExtendedPoint}; diff --git a/curve25519-elligator2/src/backend/vector/mod.rs b/curve25519-elligator2/src/backend/vector/mod.rs new file mode 100644 index 00000000..36b707e9 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/mod.rs @@ -0,0 +1,22 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +#![doc = include_str!("../../../docs/parallel-formulas.md")] + +#[allow(missing_docs)] +pub mod packed_simd; + +pub mod avx2; + +#[cfg(nightly)] +pub mod ifma; + +pub mod scalar_mul; diff --git a/curve25519-elligator2/src/backend/vector/packed_simd.rs b/curve25519-elligator2/src/backend/vector/packed_simd.rs new file mode 100644 index 00000000..4647e67f --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/packed_simd.rs @@ -0,0 +1,337 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// See LICENSE for licensing information. + +//! This module defines wrappers over platform-specific SIMD types to make them +//! more convenient to use. +//! +//! UNSAFETY: Everything in this module assumes that we're running on hardware +//! which supports at least AVX2. This invariant *must* be enforced +//! by the callers of this code. +use core::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitXor, BitXorAssign, Sub}; + +use curve25519_dalek_derive::unsafe_target_feature; + +macro_rules! impl_shared { + ( + $ty:ident, + $lane_ty:ident, + $add_intrinsic:ident, + $sub_intrinsic:ident, + $shl_intrinsic:ident, + $shr_intrinsic:ident, + $extract_intrinsic:ident + ) => { + #[allow(non_camel_case_types)] + #[derive(Copy, Clone, Debug)] + #[repr(transparent)] + pub struct $ty(core::arch::x86_64::__m256i); + + #[unsafe_target_feature("avx2")] + impl From<$ty> for core::arch::x86_64::__m256i { + #[inline] + fn from(value: $ty) -> core::arch::x86_64::__m256i { + value.0 + } + } + + #[unsafe_target_feature("avx2")] + impl From for $ty { + #[inline] + fn from(value: core::arch::x86_64::__m256i) -> $ty { + $ty(value) + } + } + + #[unsafe_target_feature("avx2")] + impl PartialEq for $ty { + #[inline] + fn eq(&self, rhs: &$ty) -> bool { + unsafe { + // This compares each pair of 8-bit packed integers and returns either 0xFF or + // 0x00 depending on whether they're equal. + // + // So the values are equal if (and only if) this returns a value that's filled + // with only 0xFF. + // + // Pseudocode of what this does: + // self.0 + // .bytes() + // .zip(rhs.0.bytes()) + // .map(|a, b| if a == b { 0xFF } else { 0x00 }) + // .join(); + let m = core::arch::x86_64::_mm256_cmpeq_epi8(self.0, rhs.0); + + // Now we need to reduce the 256-bit value to something on which we can branch. + // + // This will just take the most significant bit of every 8-bit packed integer + // and build an `i32` out of it. If the values we previously compared were + // equal then all off the most significant bits will be equal to 1, which means + // that this will return 0xFFFFFFFF, which is equal to -1 when represented as + // an `i32`. + core::arch::x86_64::_mm256_movemask_epi8(m) == -1 + } + } + } + + impl Eq for $ty {} + + #[unsafe_target_feature("avx2")] + impl Add for $ty { + type Output = Self; + + #[inline] + fn add(self, rhs: $ty) -> Self { + unsafe { core::arch::x86_64::$add_intrinsic(self.0, rhs.0).into() } + } + } + + #[allow(clippy::assign_op_pattern)] + #[unsafe_target_feature("avx2")] + impl AddAssign for $ty { + #[inline] + fn add_assign(&mut self, rhs: $ty) { + *self = *self + rhs + } + } + + #[unsafe_target_feature("avx2")] + impl Sub for $ty { + type Output = Self; + + #[inline] + fn sub(self, rhs: $ty) -> Self { + unsafe { core::arch::x86_64::$sub_intrinsic(self.0, rhs.0).into() } + } + } + + #[unsafe_target_feature("avx2")] + impl BitAnd for $ty { + type Output = Self; + + #[inline] + fn bitand(self, rhs: $ty) -> Self { + unsafe { core::arch::x86_64::_mm256_and_si256(self.0, rhs.0).into() } + } + } + + #[unsafe_target_feature("avx2")] + impl BitXor for $ty { + type Output = Self; + + #[inline] + fn bitxor(self, rhs: $ty) -> Self { + unsafe { core::arch::x86_64::_mm256_xor_si256(self.0, rhs.0).into() } + } + } + + #[allow(clippy::assign_op_pattern)] + #[unsafe_target_feature("avx2")] + impl BitAndAssign for $ty { + #[inline] + fn bitand_assign(&mut self, rhs: $ty) { + *self = *self & rhs; + } + } + + #[allow(clippy::assign_op_pattern)] + #[unsafe_target_feature("avx2")] + impl BitXorAssign for $ty { + #[inline] + fn bitxor_assign(&mut self, rhs: $ty) { + *self = *self ^ rhs; + } + } + + #[unsafe_target_feature("avx2")] + #[allow(dead_code)] + impl $ty { + #[inline] + pub fn shl(self) -> Self { + unsafe { core::arch::x86_64::$shl_intrinsic(self.0, N).into() } + } + + #[inline] + pub fn shr(self) -> Self { + unsafe { core::arch::x86_64::$shr_intrinsic(self.0, N).into() } + } + + #[inline] + pub fn extract(self) -> $lane_ty { + unsafe { core::arch::x86_64::$extract_intrinsic(self.0, N) as $lane_ty } + } + } + }; +} + +macro_rules! impl_conv { + ($src:ident => $($dst:ident),+) => { + $( + #[unsafe_target_feature("avx2")] + impl From<$src> for $dst { + #[inline] + fn from(value: $src) -> $dst { + $dst(value.0) + } + } + )+ + } +} + +// We define SIMD functionality over packed unsigned integer types. However, all the integer +// intrinsics deal with signed integers. So we cast unsigned to signed, pack it into SIMD, do +// add/sub/shl/shr arithmetic, and finally cast back to unsigned at the end. Why is this equivalent +// to doing the same thing on unsigned integers? Shl/shr is clear, because casting does not change +// the bits of the integer. But what about add/sub? This is due to the following: +// +// 1) Rust uses two's complement to represent signed integers. So we're assured that the values +// we cast into SIMD and extract out at the end are two's complement. +// +// https://doc.rust-lang.org/reference/types/numeric.html +// +// 2) Wrapping add/sub is compatible between two's complement signed and unsigned integers. +// That is, for all x,y: u64 (or any unsigned integer type), +// +// x.wrapping_add(y) == (x as i64).wrapping_add(y as i64) as u64, and +// x.wrapping_sub(y) == (x as i64).wrapping_sub(y as i64) as u64 +// +// https://julesjacobs.com/2019/03/20/why-twos-complement-works.html +// +// 3) The add/sub functions we use for SIMD are indeed wrapping. The docs indicate that +// __mm256_add/sub compile to vpaddX/vpsubX instructions where X = w, d, or q depending on +// the bitwidth. From x86 docs: +// +// When an individual result is too large to be represented in X bits (overflow), the +// result is wrapped around and the low X bits are written to the destination operand +// (that is, the carry is ignored). +// +// https://www.felixcloutier.com/x86/paddb:paddw:paddd:paddq +// https://www.felixcloutier.com/x86/psubb:psubw:psubd +// https://www.felixcloutier.com/x86/psubq + +impl_shared!( + u64x4, + u64, + _mm256_add_epi64, + _mm256_sub_epi64, + _mm256_slli_epi64, + _mm256_srli_epi64, + _mm256_extract_epi64 +); +impl_shared!( + u32x8, + u32, + _mm256_add_epi32, + _mm256_sub_epi32, + _mm256_slli_epi32, + _mm256_srli_epi32, + _mm256_extract_epi32 +); + +impl_conv!(u64x4 => u32x8); + +#[allow(dead_code)] +impl u64x4 { + /// A constified variant of `new`. + /// + /// Should only be called from `const` contexts. At runtime `new` is going to be faster. + #[inline] + pub const fn new_const(x0: u64, x1: u64, x2: u64, x3: u64) -> Self { + // SAFETY: Transmuting between an array and a SIMD type is safe + // https://rust-lang.github.io/unsafe-code-guidelines/layout/packed-simd-vectors.html + unsafe { Self(core::mem::transmute([x0, x1, x2, x3])) } + } + + /// A constified variant of `splat`. + /// + /// Should only be called from `const` contexts. At runtime `splat` is going to be faster. + #[inline] + pub const fn splat_const() -> Self { + Self::new_const(N, N, N, N) + } + + /// Constructs a new instance. + #[unsafe_target_feature("avx2")] + #[inline] + pub fn new(x0: u64, x1: u64, x2: u64, x3: u64) -> u64x4 { + unsafe { + // _mm256_set_epi64 sets the underlying vector in reverse order of the args + u64x4(core::arch::x86_64::_mm256_set_epi64x( + x3 as i64, x2 as i64, x1 as i64, x0 as i64, + )) + } + } + + /// Constructs a new instance with all of the elements initialized to the given value. + #[unsafe_target_feature("avx2")] + #[inline] + pub fn splat(x: u64) -> u64x4 { + unsafe { u64x4(core::arch::x86_64::_mm256_set1_epi64x(x as i64)) } + } +} + +#[allow(dead_code)] +impl u32x8 { + /// A constified variant of `new`. + /// + /// Should only be called from `const` contexts. At runtime `new` is going to be faster. + #[allow(clippy::too_many_arguments)] + #[inline] + pub const fn new_const( + x0: u32, + x1: u32, + x2: u32, + x3: u32, + x4: u32, + x5: u32, + x6: u32, + x7: u32, + ) -> Self { + // SAFETY: Transmuting between an array and a SIMD type is safe + // https://rust-lang.github.io/unsafe-code-guidelines/layout/packed-simd-vectors.html + unsafe { Self(core::mem::transmute([x0, x1, x2, x3, x4, x5, x6, x7])) } + } + + /// A constified variant of `splat`. + /// + /// Should only be called from `const` contexts. At runtime `splat` is going to be faster. + #[inline] + pub const fn splat_const() -> Self { + Self::new_const(N, N, N, N, N, N, N, N) + } + + /// Constructs a new instance. + #[allow(clippy::too_many_arguments)] + #[unsafe_target_feature("avx2")] + #[inline] + pub fn new(x0: u32, x1: u32, x2: u32, x3: u32, x4: u32, x5: u32, x6: u32, x7: u32) -> u32x8 { + unsafe { + // _mm256_set_epi32 sets the underlying vector in reverse order of the args + u32x8(core::arch::x86_64::_mm256_set_epi32( + x7 as i32, x6 as i32, x5 as i32, x4 as i32, x3 as i32, x2 as i32, x1 as i32, + x0 as i32, + )) + } + } + + /// Constructs a new instance with all of the elements initialized to the given value. + #[unsafe_target_feature("avx2")] + #[inline] + pub fn splat(x: u32) -> u32x8 { + unsafe { u32x8(core::arch::x86_64::_mm256_set1_epi32(x as i32)) } + } +} + +#[unsafe_target_feature("avx2")] +impl u32x8 { + /// Multiplies the low unsigned 32-bits from each packed 64-bit element + /// and returns the unsigned 64-bit results. + /// + /// (This ignores the upper 32-bits from each packed 64-bits!) + #[inline] + pub fn mul32(self, rhs: u32x8) -> u64x4 { + // NOTE: This ignores the upper 32-bits from each packed 64-bits. + unsafe { core::arch::x86_64::_mm256_mul_epu32(self.0, rhs.0).into() } + } +} diff --git a/curve25519-elligator2/src/backend/vector/scalar_mul/mod.rs b/curve25519-elligator2/src/backend/vector/scalar_mul/mod.rs new file mode 100644 index 00000000..db746a89 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/scalar_mul/mod.rs @@ -0,0 +1,30 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Implementations of various multiplication algorithms for the SIMD backends. + +#[allow(missing_docs)] +pub mod variable_base; + +#[allow(missing_docs)] +pub mod vartime_double_base; + +#[allow(missing_docs)] +#[cfg(feature = "alloc")] +pub mod straus; + +#[allow(missing_docs)] +#[cfg(feature = "alloc")] +pub mod precomputed_straus; + +#[allow(missing_docs)] +#[cfg(feature = "alloc")] +pub mod pippenger; diff --git a/curve25519-elligator2/src/backend/vector/scalar_mul/pippenger.rs b/curve25519-elligator2/src/backend/vector/scalar_mul/pippenger.rs new file mode 100644 index 00000000..4844ebc0 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/scalar_mul/pippenger.rs @@ -0,0 +1,176 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2019 Oleg Andreev +// See LICENSE for licensing information. +// +// Authors: +// - Oleg Andreev + +#![allow(non_snake_case)] + +#[curve25519_dalek_derive::unsafe_target_feature_specialize( + "avx2", + conditional("avx512ifma,avx512vl", nightly) +)] +pub mod spec { + + use alloc::vec::Vec; + + use core::borrow::Borrow; + use core::cmp::Ordering; + + #[for_target_feature("avx2")] + use crate::backend::vector::avx2::{CachedPoint, ExtendedPoint}; + + #[for_target_feature("avx512ifma")] + use crate::backend::vector::ifma::{CachedPoint, ExtendedPoint}; + + use crate::edwards::EdwardsPoint; + use crate::scalar::Scalar; + use crate::traits::{Identity, VartimeMultiscalarMul}; + + /// Implements a version of Pippenger's algorithm. + /// + /// See the documentation in the serial `scalar_mul::pippenger` module for details. + pub struct Pippenger; + + impl VartimeMultiscalarMul for Pippenger { + type Point = EdwardsPoint; + + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>, + { + let mut scalars = scalars.into_iter(); + let size = scalars.by_ref().size_hint().0; + let w = if size < 500 { + 6 + } else if size < 800 { + 7 + } else { + 8 + }; + + let max_digit: usize = 1 << w; + let digits_count: usize = Scalar::to_radix_2w_size_hint(w); + let buckets_count: usize = max_digit / 2; // digits are signed+centered hence 2^w/2, excluding 0-th bucket + + // Collect optimized scalars and points in a buffer for repeated access + // (scanning the whole collection per each digit position). + let scalars = scalars.map(|s| s.borrow().as_radix_2w(w)); + + let points = points + .into_iter() + .map(|p| p.map(|P| CachedPoint::from(ExtendedPoint::from(P)))); + + let scalars_points = scalars + .zip(points) + .map(|(s, maybe_p)| maybe_p.map(|p| (s, p))) + .collect::>>()?; + + // Prepare 2^w/2 buckets. + // buckets[i] corresponds to a multiplication factor (i+1). + let mut buckets: Vec = (0..buckets_count) + .map(|_| ExtendedPoint::identity()) + .collect(); + + let mut columns = (0..digits_count).rev().map(|digit_index| { + // Clear the buckets when processing another digit. + for bucket in &mut buckets { + *bucket = ExtendedPoint::identity(); + } + + // Iterate over pairs of (point, scalar) + // and add/sub the point to the corresponding bucket. + // Note: if we add support for precomputed lookup tables, + // we'll be adding/subtractiong point premultiplied by `digits[i]` to buckets[0]. + for (digits, pt) in scalars_points.iter() { + // Widen digit so that we don't run into edge cases when w=8. + let digit = digits[digit_index] as i16; + match digit.cmp(&0) { + Ordering::Greater => { + let b = (digit - 1) as usize; + buckets[b] = &buckets[b] + pt; + } + Ordering::Less => { + let b = (-digit - 1) as usize; + buckets[b] = &buckets[b] - pt; + } + Ordering::Equal => {} + } + } + + // Add the buckets applying the multiplication factor to each bucket. + // The most efficient way to do that is to have a single sum with two running sums: + // an intermediate sum from last bucket to the first, and a sum of intermediate sums. + // + // For example, to add buckets 1*A, 2*B, 3*C we need to add these points: + // C + // C B + // C B A Sum = C + (C+B) + (C+B+A) + let mut buckets_intermediate_sum = buckets[buckets_count - 1]; + let mut buckets_sum = buckets[buckets_count - 1]; + for i in (0..(buckets_count - 1)).rev() { + buckets_intermediate_sum = + &buckets_intermediate_sum + &CachedPoint::from(buckets[i]); + buckets_sum = &buckets_sum + &CachedPoint::from(buckets_intermediate_sum); + } + + buckets_sum + }); + + // Take the high column as an initial value to avoid wasting time doubling the identity element in `fold()`. + let hi_column = columns.next().expect("should have more than zero digits"); + + Some( + columns + .fold(hi_column, |total, p| { + &total.mul_by_pow_2(w as u32) + &CachedPoint::from(p) + }) + .into(), + ) + } + } + + #[cfg(test)] + mod test { + #[test] + fn test_vartime_pippenger() { + use super::*; + use crate::constants; + use crate::scalar::Scalar; + + // Reuse points across different tests + let mut n = 512; + let x = Scalar::from(2128506u64).invert(); + let y = Scalar::from(4443282u64).invert(); + let points: Vec<_> = (0..n) + .map(|i| constants::ED25519_BASEPOINT_POINT * Scalar::from(1 + i as u64)) + .collect(); + let scalars: Vec<_> = (0..n) + .map(|i| x + (Scalar::from(i as u64) * y)) // fast way to make ~random but deterministic scalars + .collect(); + + let premultiplied: Vec = scalars + .iter() + .zip(points.iter()) + .map(|(sc, pt)| sc * pt) + .collect(); + + while n > 0 { + let scalars = &scalars[0..n].to_vec(); + let points = &points[0..n].to_vec(); + let control: EdwardsPoint = premultiplied[0..n].iter().sum(); + + let subject = Pippenger::vartime_multiscalar_mul(scalars.clone(), points.clone()); + + assert_eq!(subject.compress(), control.compress()); + + n = n / 2; + } + } + } +} diff --git a/curve25519-elligator2/src/backend/vector/scalar_mul/precomputed_straus.rs b/curve25519-elligator2/src/backend/vector/scalar_mul/precomputed_straus.rs new file mode 100644 index 00000000..18648bf0 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/scalar_mul/precomputed_straus.rs @@ -0,0 +1,127 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2019 Henry de Valence. +// See LICENSE for licensing information. +// +// Authors: +// - Henry de Valence + +//! Precomputation for Straus's method. + +#![allow(non_snake_case)] + +#[curve25519_dalek_derive::unsafe_target_feature_specialize( + "avx2", + conditional("avx512ifma,avx512vl", nightly) +)] +pub mod spec { + + use alloc::vec::Vec; + + use core::borrow::Borrow; + use core::cmp::Ordering; + + #[for_target_feature("avx2")] + use crate::backend::vector::avx2::{CachedPoint, ExtendedPoint}; + + #[for_target_feature("avx512ifma")] + use crate::backend::vector::ifma::{CachedPoint, ExtendedPoint}; + + use crate::edwards::EdwardsPoint; + use crate::scalar::Scalar; + use crate::traits::Identity; + use crate::traits::VartimePrecomputedMultiscalarMul; + use crate::window::{NafLookupTable5, NafLookupTable8}; + + pub struct VartimePrecomputedStraus { + static_lookup_tables: Vec>, + } + + impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self { + static_lookup_tables: static_points + .into_iter() + .map(|P| NafLookupTable8::::from(P.borrow())) + .collect(), + } + } + + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>, + { + let static_nafs = static_scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect::>(); + let dynamic_nafs: Vec<_> = dynamic_scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect::>(); + + let dynamic_lookup_tables = dynamic_points + .into_iter() + .map(|P_opt| P_opt.map(|P| NafLookupTable5::::from(&P))) + .collect::>>()?; + + let sp = self.static_lookup_tables.len(); + let dp = dynamic_lookup_tables.len(); + assert_eq!(sp, static_nafs.len()); + assert_eq!(dp, dynamic_nafs.len()); + + // We could save some doublings by looking for the highest + // nonzero NAF coefficient, but since we might have a lot of + // them to search, it's not clear it's worthwhile to check. + let mut R = ExtendedPoint::identity(); + for j in (0..256).rev() { + R = R.double(); + + for i in 0..dp { + let t_ij = dynamic_nafs[i][j]; + match t_ij.cmp(&0) { + Ordering::Greater => { + R = &R + &dynamic_lookup_tables[i].select(t_ij as usize); + } + Ordering::Less => { + R = &R - &dynamic_lookup_tables[i].select(-t_ij as usize); + } + Ordering::Equal => {} + } + } + + #[allow(clippy::needless_range_loop)] + for i in 0..sp { + let t_ij = static_nafs[i][j]; + match t_ij.cmp(&0) { + Ordering::Greater => { + R = &R + &self.static_lookup_tables[i].select(t_ij as usize); + } + Ordering::Less => { + R = &R - &self.static_lookup_tables[i].select(-t_ij as usize); + } + Ordering::Equal => {} + } + } + } + + Some(R.into()) + } + } +} diff --git a/curve25519-elligator2/src/backend/vector/scalar_mul/straus.rs b/curve25519-elligator2/src/backend/vector/scalar_mul/straus.rs new file mode 100644 index 00000000..5e78df09 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/scalar_mul/straus.rs @@ -0,0 +1,126 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +#![allow(non_snake_case)] + +#[curve25519_dalek_derive::unsafe_target_feature_specialize( + "avx2", + conditional("avx512ifma,avx512vl", nightly) +)] +pub mod spec { + + use alloc::vec::Vec; + + use core::borrow::Borrow; + use core::cmp::Ordering; + + #[cfg(feature = "zeroize")] + use zeroize::Zeroizing; + + #[for_target_feature("avx2")] + use crate::backend::vector::avx2::{CachedPoint, ExtendedPoint}; + + #[for_target_feature("avx512ifma")] + use crate::backend::vector::ifma::{CachedPoint, ExtendedPoint}; + + use crate::edwards::EdwardsPoint; + use crate::scalar::Scalar; + use crate::traits::{Identity, MultiscalarMul, VartimeMultiscalarMul}; + use crate::window::{LookupTable, NafLookupTable5}; + + /// Multiscalar multiplication using interleaved window / Straus' + /// method. See the `Straus` struct in the serial backend for more + /// details. + /// + /// This exists as a seperate implementation from that one because the + /// AVX2 code uses different curve models (it does not pass between + /// multiple models during scalar mul), and it has to convert the + /// point representation on the fly. + pub struct Straus {} + + impl MultiscalarMul for Straus { + type Point = EdwardsPoint; + + fn multiscalar_mul(scalars: I, points: J) -> EdwardsPoint + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + { + // Construct a lookup table of [P,2P,3P,4P,5P,6P,7P,8P] + // for each input point P + let lookup_tables: Vec<_> = points + .into_iter() + .map(|point| LookupTable::::from(point.borrow())) + .collect(); + + let scalar_digits_vec: Vec<_> = scalars + .into_iter() + .map(|s| s.borrow().as_radix_16()) + .collect(); + // Pass ownership to a `Zeroizing` wrapper + #[cfg(feature = "zeroize")] + let scalar_digits_vec = Zeroizing::new(scalar_digits_vec); + + let mut Q = ExtendedPoint::identity(); + for j in (0..64).rev() { + Q = Q.mul_by_pow_2(4); + let it = scalar_digits_vec.iter().zip(lookup_tables.iter()); + for (s_i, lookup_table_i) in it { + // Q = Q + s_{i,j} * P_i + Q = &Q + &lookup_table_i.select(s_i[j]); + } + } + Q.into() + } + } + + impl VartimeMultiscalarMul for Straus { + type Point = EdwardsPoint; + + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>, + { + let nafs: Vec<_> = scalars + .into_iter() + .map(|c| c.borrow().non_adjacent_form(5)) + .collect(); + let lookup_tables: Vec<_> = points + .into_iter() + .map(|P_opt| P_opt.map(|P| NafLookupTable5::::from(&P))) + .collect::>>()?; + + let mut Q = ExtendedPoint::identity(); + + for i in (0..256).rev() { + Q = Q.double(); + + for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) { + match naf[i].cmp(&0) { + Ordering::Greater => { + Q = &Q + &lookup_table.select(naf[i] as usize); + } + Ordering::Less => { + Q = &Q - &lookup_table.select(-naf[i] as usize); + } + Ordering::Equal => {} + } + } + } + + Some(Q.into()) + } + } +} diff --git a/curve25519-elligator2/src/backend/vector/scalar_mul/variable_base.rs b/curve25519-elligator2/src/backend/vector/scalar_mul/variable_base.rs new file mode 100644 index 00000000..9f924f28 --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/scalar_mul/variable_base.rs @@ -0,0 +1,44 @@ +#![allow(non_snake_case)] + +#[curve25519_dalek_derive::unsafe_target_feature_specialize( + "avx2", + conditional("avx512ifma,avx512vl", nightly) +)] +pub mod spec { + + #[for_target_feature("avx2")] + use crate::backend::vector::avx2::{CachedPoint, ExtendedPoint}; + + #[for_target_feature("avx512ifma")] + use crate::backend::vector::ifma::{CachedPoint, ExtendedPoint}; + + use crate::edwards::EdwardsPoint; + use crate::scalar::Scalar; + use crate::traits::Identity; + use crate::window::LookupTable; + + /// Perform constant-time, variable-base scalar multiplication. + pub fn mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint { + // Construct a lookup table of [P,2P,3P,4P,5P,6P,7P,8P] + let lookup_table = LookupTable::::from(point); + // Setting s = scalar, compute + // + // s = s_0 + s_1*16^1 + ... + s_63*16^63, + // + // with `-8 ≤ s_i < 8` for `0 ≤ i < 63` and `-8 ≤ s_63 ≤ 8`. + let scalar_digits = scalar.as_radix_16(); + // Compute s*P as + // + // s*P = P*(s_0 + s_1*16^1 + s_2*16^2 + ... + s_63*16^63) + // s*P = P*s_0 + P*s_1*16^1 + P*s_2*16^2 + ... + P*s_63*16^63 + // s*P = P*s_0 + 16*(P*s_1 + 16*(P*s_2 + 16*( ... + P*s_63)...)) + // + // We sum right-to-left. + let mut Q = ExtendedPoint::identity(); + for i in (0..64).rev() { + Q = Q.mul_by_pow_2(4); + Q = &Q + &lookup_table.select(scalar_digits[i]); + } + Q.into() + } +} diff --git a/curve25519-elligator2/src/backend/vector/scalar_mul/vartime_double_base.rs b/curve25519-elligator2/src/backend/vector/scalar_mul/vartime_double_base.rs new file mode 100644 index 00000000..c42ef29b --- /dev/null +++ b/curve25519-elligator2/src/backend/vector/scalar_mul/vartime_double_base.rs @@ -0,0 +1,101 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +#![allow(non_snake_case)] + +#[curve25519_dalek_derive::unsafe_target_feature_specialize( + "avx2", + conditional("avx512ifma,avx512vl", nightly) +)] +pub mod spec { + + use core::cmp::Ordering; + + #[for_target_feature("avx2")] + use crate::backend::vector::avx2::{CachedPoint, ExtendedPoint}; + + #[for_target_feature("avx512ifma")] + use crate::backend::vector::ifma::{CachedPoint, ExtendedPoint}; + + #[cfg(feature = "precomputed-tables")] + #[for_target_feature("avx2")] + use crate::backend::vector::avx2::constants::BASEPOINT_ODD_LOOKUP_TABLE; + + #[cfg(feature = "precomputed-tables")] + #[for_target_feature("avx512ifma")] + use crate::backend::vector::ifma::constants::BASEPOINT_ODD_LOOKUP_TABLE; + + use crate::edwards::EdwardsPoint; + use crate::scalar::Scalar; + use crate::traits::Identity; + use crate::window::NafLookupTable5; + + /// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint. + pub fn mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint { + let a_naf = a.non_adjacent_form(5); + + #[cfg(feature = "precomputed-tables")] + let b_naf = b.non_adjacent_form(8); + #[cfg(not(feature = "precomputed-tables"))] + let b_naf = b.non_adjacent_form(5); + + // Find starting index + let mut i: usize = 255; + for j in (0..256).rev() { + i = j; + if a_naf[i] != 0 || b_naf[i] != 0 { + break; + } + } + + let table_A = NafLookupTable5::::from(A); + + #[cfg(feature = "precomputed-tables")] + let table_B = &BASEPOINT_ODD_LOOKUP_TABLE; + + #[cfg(not(feature = "precomputed-tables"))] + let table_B = + &NafLookupTable5::::from(&crate::constants::ED25519_BASEPOINT_POINT); + + let mut Q = ExtendedPoint::identity(); + + loop { + Q = Q.double(); + + match a_naf[i].cmp(&0) { + Ordering::Greater => { + Q = &Q + &table_A.select(a_naf[i] as usize); + } + Ordering::Less => { + Q = &Q - &table_A.select(-a_naf[i] as usize); + } + Ordering::Equal => {} + } + + match b_naf[i].cmp(&0) { + Ordering::Greater => { + Q = &Q + &table_B.select(b_naf[i] as usize); + } + Ordering::Less => { + Q = &Q - &table_B.select(-b_naf[i] as usize); + } + Ordering::Equal => {} + } + + if i == 0 { + break; + } + i -= 1; + } + + Q.into() + } +} diff --git a/curve25519-elligator2/src/constants.rs b/curve25519-elligator2/src/constants.rs new file mode 100644 index 00000000..251c12bc --- /dev/null +++ b/curve25519-elligator2/src/constants.rs @@ -0,0 +1,178 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence +//! Various constants, such as the Ristretto and Ed25519 basepoints. + +#![allow(non_snake_case)] + +use cfg_if::cfg_if; + +use crate::edwards::CompressedEdwardsY; +use crate::montgomery::MontgomeryPoint; +use crate::ristretto::{CompressedRistretto, RistrettoPoint}; +use crate::scalar::Scalar; + +#[cfg(feature = "precomputed-tables")] +use crate::edwards::EdwardsBasepointTable; + +cfg_if! { + if #[cfg(curve25519_dalek_backend = "fiat")] { + #[cfg(curve25519_dalek_bits = "32")] + pub use crate::backend::serial::fiat_u32::constants::*; + #[cfg(curve25519_dalek_bits = "64")] + pub use crate::backend::serial::fiat_u64::constants::*; + } else { + #[cfg(curve25519_dalek_bits = "32")] + pub use crate::backend::serial::u32::constants::*; + #[cfg(curve25519_dalek_bits = "64")] + pub use crate::backend::serial::u64::constants::*; + } +} + +/// The Ed25519 basepoint, in `CompressedEdwardsY` format. +/// +/// This is the little-endian byte encoding of \\( 4/5 \pmod p \\), +/// which is the \\(y\\)-coordinate of the Ed25519 basepoint. +/// +/// The sign bit is 0 since the basepoint has \\(x\\) chosen to be positive. +pub const ED25519_BASEPOINT_COMPRESSED: CompressedEdwardsY = CompressedEdwardsY([ + 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, +]); + +/// The X25519 basepoint, in `MontgomeryPoint` format. +pub const X25519_BASEPOINT: MontgomeryPoint = MontgomeryPoint([ + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]); + +/// The Ristretto basepoint, in `CompressedRistretto` format. +pub const RISTRETTO_BASEPOINT_COMPRESSED: CompressedRistretto = CompressedRistretto([ + 0xe2, 0xf2, 0xae, 0x0a, 0x6a, 0xbc, 0x4e, 0x71, 0xa8, 0x84, 0xa9, 0x61, 0xc5, 0x00, 0x51, 0x5f, + 0x58, 0xe3, 0x0b, 0x6a, 0xa5, 0x82, 0xdd, 0x8d, 0xb6, 0xa6, 0x59, 0x45, 0xe0, 0x8d, 0x2d, 0x76, +]); + +/// The Ristretto basepoint, as a `RistrettoPoint`. +/// +/// This is called `_POINT` to distinguish it from `_TABLE`, which +/// provides fast scalar multiplication. +pub const RISTRETTO_BASEPOINT_POINT: RistrettoPoint = RistrettoPoint(ED25519_BASEPOINT_POINT); + +/// `BASEPOINT_ORDER` is the order of the Ristretto group and of the Ed25519 basepoint, i.e., +/// $$ +/// \ell = 2^\{252\} + 27742317777372353535851937790883648493. +/// $$ +#[deprecated(since = "4.1.1", note = "Should not have been in public API")] +pub const BASEPOINT_ORDER: Scalar = BASEPOINT_ORDER_PRIVATE; + +pub(crate) const BASEPOINT_ORDER_PRIVATE: Scalar = Scalar { + bytes: [ + 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, + ], +}; + +#[cfg(feature = "precomputed-tables")] +use crate::ristretto::RistrettoBasepointTable; + +/// The Ristretto basepoint, as a `RistrettoBasepointTable` for scalar multiplication. +#[cfg(feature = "precomputed-tables")] +pub static RISTRETTO_BASEPOINT_TABLE: &RistrettoBasepointTable = unsafe { + // SAFETY: `RistrettoBasepointTable` is a `#[repr(transparent)]` newtype of + // `EdwardsBasepointTable` + &*(ED25519_BASEPOINT_TABLE as *const EdwardsBasepointTable as *const RistrettoBasepointTable) +}; + +#[cfg(test)] +mod test { + use crate::constants; + use crate::field::FieldElement; + use crate::traits::{IsIdentity, ValidityCheck}; + + #[test] + fn test_eight_torsion() { + for i in 0..8 { + let Q = constants::EIGHT_TORSION[i].mul_by_pow_2(3); + assert!(Q.is_valid()); + assert!(Q.is_identity()); + } + } + + #[test] + fn test_four_torsion() { + for i in (0..8).filter(|i| i % 2 == 0) { + let Q = constants::EIGHT_TORSION[i].mul_by_pow_2(2); + assert!(Q.is_valid()); + assert!(Q.is_identity()); + } + } + + #[test] + fn test_two_torsion() { + for i in (0..8).filter(|i| i % 4 == 0) { + let Q = constants::EIGHT_TORSION[i].mul_by_pow_2(1); + assert!(Q.is_valid()); + assert!(Q.is_identity()); + } + } + + /// Test that SQRT_M1 is the positive square root of -1 + #[test] + fn test_sqrt_minus_one() { + let minus_one = FieldElement::MINUS_ONE; + let sqrt_m1_sq = &constants::SQRT_M1 * &constants::SQRT_M1; + assert_eq!(minus_one, sqrt_m1_sq); + assert!(bool::from(!constants::SQRT_M1.is_negative())); + } + + #[test] + fn test_sqrt_constants_sign() { + let minus_one = FieldElement::MINUS_ONE; + let (was_nonzero_square, invsqrt_m1) = minus_one.invsqrt(); + assert!(bool::from(was_nonzero_square)); + let sign_test_sqrt = &invsqrt_m1 * &constants::SQRT_M1; + assert_eq!(sign_test_sqrt, minus_one); + } + + /// Test that d = -121665/121666 + #[test] + #[cfg(all(curve25519_dalek_bits = "32", not(curve25519_dalek_backend = "fiat")))] + fn test_d_vs_ratio() { + use crate::backend::serial::u32::field::FieldElement2625; + let a = -&FieldElement2625([121665, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let b = FieldElement2625([121666, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let d = &a * &b.invert(); + let d2 = &d + &d; + assert_eq!(d, constants::EDWARDS_D); + assert_eq!(d2, constants::EDWARDS_D2); + } + + /// Test that d = -121665/121666 + #[test] + #[cfg(all(curve25519_dalek_bits = "64", not(curve25519_dalek_backend = "fiat")))] + fn test_d_vs_ratio() { + use crate::backend::serial::u64::field::FieldElement51; + let a = -&FieldElement51([121665, 0, 0, 0, 0]); + let b = FieldElement51([121666, 0, 0, 0, 0]); + let d = &a * &b.invert(); + let d2 = &d + &d; + assert_eq!(d, constants::EDWARDS_D); + assert_eq!(d2, constants::EDWARDS_D2); + } + + #[test] + fn test_sqrt_ad_minus_one() { + let a = FieldElement::MINUS_ONE; + let ad_minus_one = &(&a * &constants::EDWARDS_D) + &a; + let should_be_ad_minus_one = constants::SQRT_AD_MINUS_ONE.square(); + assert_eq!(should_be_ad_minus_one, ad_minus_one); + } +} diff --git a/curve25519-elligator2/src/diagnostics.rs b/curve25519-elligator2/src/diagnostics.rs new file mode 100644 index 00000000..d5becef6 --- /dev/null +++ b/curve25519-elligator2/src/diagnostics.rs @@ -0,0 +1,25 @@ +//! Build time diagnostics + +// auto is assumed or selected +#[cfg(curve25519_dalek_backend = "auto")] +compile_error!("curve25519_dalek_backend is 'auto'"); + +// fiat was overriden +#[cfg(curve25519_dalek_backend = "fiat")] +compile_error!("curve25519_dalek_backend is 'fiat'"); + +// serial was assumed or overriden +#[cfg(curve25519_dalek_backend = "serial")] +compile_error!("curve25519_dalek_backend is 'serial'"); + +// simd was assumed over overriden +#[cfg(curve25519_dalek_backend = "simd")] +compile_error!("curve25519_dalek_backend is 'simd'"); + +// 32 bits target_pointer_width was assumed or overriden +#[cfg(curve25519_dalek_bits = "32")] +compile_error!("curve25519_dalek_bits is '32'"); + +// 64 bits target_pointer_width was assumed or overriden +#[cfg(curve25519_dalek_bits = "64")] +compile_error!("curve25519_dalek_bits is '64'"); diff --git a/curve25519-elligator2/src/edwards.rs b/curve25519-elligator2/src/edwards.rs new file mode 100644 index 00000000..275946f3 --- /dev/null +++ b/curve25519-elligator2/src/edwards.rs @@ -0,0 +1,2279 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2020 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Group operations for Curve25519, in Edwards form. +//! +//! ## Encoding and Decoding +//! +//! Encoding is done by converting to and from a `CompressedEdwardsY` +//! struct, which is a typed wrapper around `[u8; 32]`. +//! +//! ## Equality Testing +//! +//! The `EdwardsPoint` struct implements the [`subtle::ConstantTimeEq`] +//! trait for constant-time equality checking, and the Rust `Eq` trait +//! for variable-time equality checking. +//! +//! ## Cofactor-related functions +//! +//! The order of the group of points on the curve \\(\mathcal E\\) +//! is \\(|\mathcal E| = 8\ell \\), so its structure is \\( \mathcal +//! E = \mathcal E\[8\] \times \mathcal E[\ell]\\). The torsion +//! subgroup \\( \mathcal E\[8\] \\) consists of eight points of small +//! order. Technically, all of \\(\mathcal E\\) is torsion, but we +//! use the word only to refer to the small \\(\mathcal E\[8\]\\) part, not +//! the large prime-order \\(\mathcal E[\ell]\\) part. +//! +//! To test if a point is in \\( \mathcal E\[8\] \\), use +//! [`EdwardsPoint::is_small_order`]. +//! +//! To test if a point is in \\( \mathcal E[\ell] \\), use +//! [`EdwardsPoint::is_torsion_free`]. +//! +//! To multiply by the cofactor, use [`EdwardsPoint::mul_by_cofactor`]. +//! +//! To avoid dealing with cofactors entirely, consider using Ristretto. +//! +//! ## Scalars +//! +//! Scalars are represented by the [`Scalar`] struct. To construct a scalar, see +//! [`Scalar::from_canonical_bytes`] or [`Scalar::from_bytes_mod_order_wide`]. +//! +//! ## Scalar Multiplication +//! +//! Scalar multiplication on Edwards points is provided by: +//! +//! * the `*` operator between a `Scalar` and a `EdwardsPoint`, which +//! performs constant-time variable-base scalar multiplication; +//! +//! * the `*` operator between a `Scalar` and a +//! `EdwardsBasepointTable`, which performs constant-time fixed-base +//! scalar multiplication; +//! +//! * an implementation of the +//! [`MultiscalarMul`](../traits/trait.MultiscalarMul.html) trait for +//! constant-time variable-base multiscalar multiplication; +//! +//! * an implementation of the +//! [`VartimeMultiscalarMul`](../traits/trait.VartimeMultiscalarMul.html) +//! trait for variable-time variable-base multiscalar multiplication; +//! +//! ## Implementation +//! +//! The Edwards arithmetic is implemented using the “extended twisted +//! coordinates” of Hisil, Wong, Carter, and Dawson, and the +//! corresponding complete formulas. For more details, +//! see the [`curve_models` submodule][curve_models] +//! of the internal documentation. +//! +//! ## Validity Checking +//! +//! There is no function for checking whether a point is valid. +//! Instead, the `EdwardsPoint` struct is guaranteed to hold a valid +//! point on the curve. +//! +//! We use the Rust type system to make invalid points +//! unrepresentable: `EdwardsPoint` objects can only be created via +//! successful decompression of a compressed point, or else by +//! operations on other (valid) `EdwardsPoint`s. +//! +//! [curve_models]: https://docs.rs/curve25519-elligator2/latest/curve25519-elligator2/backend/serial/curve_models/index.html + +// We allow non snake_case names because coordinates in projective space are +// traditionally denoted by the capitalisation of their respective +// counterparts in affine space. Yeah, you heard me, rustc, I'm gonna have my +// affine and projective cakes and eat both of them too. +#![allow(non_snake_case)] + +use core::array::TryFromSliceError; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::iter::Sum; +use core::ops::{Add, Neg, Sub}; +use core::ops::{AddAssign, SubAssign}; +use core::ops::{Mul, MulAssign}; + +use cfg_if::cfg_if; + +#[cfg(feature = "digest")] +use digest::{generic_array::typenum::U64, Digest}; + +#[cfg(feature = "group")] +use { + group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding}, + subtle::CtOption, +}; + +#[cfg(feature = "group")] +use rand_core::RngCore; + +use subtle::Choice; +use subtle::ConditionallyNegatable; +use subtle::ConditionallySelectable; +use subtle::ConstantTimeEq; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +use crate::constants; + +use crate::field::FieldElement; +use crate::scalar::{clamp_integer, Scalar}; + +use crate::montgomery::MontgomeryPoint; + +use crate::backend::serial::curve_models::AffineNielsPoint; +use crate::backend::serial::curve_models::CompletedPoint; +use crate::backend::serial::curve_models::ProjectiveNielsPoint; +use crate::backend::serial::curve_models::ProjectivePoint; + +#[cfg(feature = "precomputed-tables")] +use crate::window::{ + LookupTableRadix128, LookupTableRadix16, LookupTableRadix256, LookupTableRadix32, + LookupTableRadix64, +}; + +#[cfg(feature = "precomputed-tables")] +use crate::traits::BasepointTable; + +use crate::traits::ValidityCheck; +use crate::traits::{Identity, IsIdentity}; + +#[cfg(feature = "alloc")] +use crate::traits::MultiscalarMul; +#[cfg(feature = "alloc")] +use crate::traits::{VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul}; + +// ------------------------------------------------------------------------ +// Compressed points +// ------------------------------------------------------------------------ + +/// In "Edwards y" / "Ed25519" format, the curve point \\((x,y)\\) is +/// determined by the \\(y\\)-coordinate and the sign of \\(x\\). +/// +/// The first 255 bits of a `CompressedEdwardsY` represent the +/// \\(y\\)-coordinate. The high bit of the 32nd byte gives the sign of \\(x\\). +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct CompressedEdwardsY(pub [u8; 32]); + +impl ConstantTimeEq for CompressedEdwardsY { + fn ct_eq(&self, other: &CompressedEdwardsY) -> Choice { + self.as_bytes().ct_eq(other.as_bytes()) + } +} + +impl Debug for CompressedEdwardsY { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "CompressedEdwardsY: {:?}", self.as_bytes()) + } +} + +impl CompressedEdwardsY { + /// View this `CompressedEdwardsY` as an array of bytes. + pub const fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + /// Copy this `CompressedEdwardsY` to an array of bytes. + pub const fn to_bytes(&self) -> [u8; 32] { + self.0 + } + + /// Attempt to decompress to an `EdwardsPoint`. + /// + /// Returns `None` if the input is not the \\(y\\)-coordinate of a + /// curve point. + pub fn decompress(&self) -> Option { + let (is_valid_y_coord, X, Y, Z) = decompress::step_1(self); + + if is_valid_y_coord.into() { + Some(decompress::step_2(self, X, Y, Z)) + } else { + None + } + } +} + +mod decompress { + use super::*; + + #[rustfmt::skip] // keep alignment of explanatory comments + pub(super) fn step_1( + repr: &CompressedEdwardsY, + ) -> (Choice, FieldElement, FieldElement, FieldElement) { + let Y = FieldElement::from_bytes(repr.as_bytes()); + let Z = FieldElement::ONE; + let YY = Y.square(); + let u = &YY - &Z; // u = y²-1 + let v = &(&YY * &constants::EDWARDS_D) + &Z; // v = dy²+1 + let (is_valid_y_coord, X) = FieldElement::sqrt_ratio_i(&u, &v); + + (is_valid_y_coord, X, Y, Z) + } + + #[rustfmt::skip] + pub(super) fn step_2( + repr: &CompressedEdwardsY, + mut X: FieldElement, + Y: FieldElement, + Z: FieldElement, + ) -> EdwardsPoint { + // FieldElement::sqrt_ratio_i always returns the nonnegative square root, + // so we negate according to the supplied sign bit. + let compressed_sign_bit = Choice::from(repr.as_bytes()[31] >> 7); + X.conditional_negate(compressed_sign_bit); + + EdwardsPoint { + X, + Y, + Z, + T: &X * &Y, + } + } +} + +impl TryFrom<&[u8]> for CompressedEdwardsY { + type Error = TryFromSliceError; + + fn try_from(slice: &[u8]) -> Result { + Self::from_slice(slice) + } +} + +// ------------------------------------------------------------------------ +// Serde support +// ------------------------------------------------------------------------ +// Serializes to and from `EdwardsPoint` directly, doing compression +// and decompression internally. This means that users can create +// structs containing `EdwardsPoint`s and use Serde's derived +// serializers to serialize those structures. + +#[cfg(feature = "serde")] +use serde::de::Visitor; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[cfg(feature = "serde")] +impl Serialize for EdwardsPoint { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeTuple; + let mut tup = serializer.serialize_tuple(32)?; + for byte in self.compress().as_bytes().iter() { + tup.serialize_element(byte)?; + } + tup.end() + } +} + +#[cfg(feature = "serde")] +impl Serialize for CompressedEdwardsY { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeTuple; + let mut tup = serializer.serialize_tuple(32)?; + for byte in self.as_bytes().iter() { + tup.serialize_element(byte)?; + } + tup.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for EdwardsPoint { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct EdwardsPointVisitor; + + impl<'de> Visitor<'de> for EdwardsPointVisitor { + type Value = EdwardsPoint; + + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + formatter.write_str("a valid point in Edwards y + sign format") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut bytes = [0u8; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + bytes[i] = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?; + } + CompressedEdwardsY(bytes) + .decompress() + .ok_or_else(|| serde::de::Error::custom("decompression failed")) + } + } + + deserializer.deserialize_tuple(32, EdwardsPointVisitor) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for CompressedEdwardsY { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct CompressedEdwardsYVisitor; + + impl<'de> Visitor<'de> for CompressedEdwardsYVisitor { + type Value = CompressedEdwardsY; + + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + formatter.write_str("32 bytes of data") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut bytes = [0u8; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + bytes[i] = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?; + } + Ok(CompressedEdwardsY(bytes)) + } + } + + deserializer.deserialize_tuple(32, CompressedEdwardsYVisitor) + } +} + +// ------------------------------------------------------------------------ +// Internal point representations +// ------------------------------------------------------------------------ + +/// An `EdwardsPoint` represents a point on the Edwards form of Curve25519. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub struct EdwardsPoint { + pub(crate) X: FieldElement, + pub(crate) Y: FieldElement, + pub(crate) Z: FieldElement, + pub(crate) T: FieldElement, +} + +// ------------------------------------------------------------------------ +// Constructors +// ------------------------------------------------------------------------ + +impl Identity for CompressedEdwardsY { + fn identity() -> CompressedEdwardsY { + CompressedEdwardsY([ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]) + } +} + +impl Default for CompressedEdwardsY { + fn default() -> CompressedEdwardsY { + CompressedEdwardsY::identity() + } +} + +impl CompressedEdwardsY { + /// Construct a `CompressedEdwardsY` from a slice of bytes. + /// + /// # Errors + /// + /// Returns [`TryFromSliceError`] if the input `bytes` slice does not have + /// a length of 32. + pub fn from_slice(bytes: &[u8]) -> Result { + bytes.try_into().map(CompressedEdwardsY) + } +} + +impl Identity for EdwardsPoint { + fn identity() -> EdwardsPoint { + EdwardsPoint { + X: FieldElement::ZERO, + Y: FieldElement::ONE, + Z: FieldElement::ONE, + T: FieldElement::ZERO, + } + } +} + +impl Default for EdwardsPoint { + fn default() -> EdwardsPoint { + EdwardsPoint::identity() + } +} + +// ------------------------------------------------------------------------ +// Zeroize implementations for wiping points from memory +// ------------------------------------------------------------------------ + +#[cfg(feature = "zeroize")] +impl Zeroize for CompressedEdwardsY { + /// Reset this `CompressedEdwardsY` to the compressed form of the identity element. + fn zeroize(&mut self) { + self.0.zeroize(); + self.0[0] = 1; + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for EdwardsPoint { + /// Reset this `CompressedEdwardsPoint` to the identity element. + fn zeroize(&mut self) { + self.X.zeroize(); + self.Y = FieldElement::ONE; + self.Z = FieldElement::ONE; + self.T.zeroize(); + } +} + +// ------------------------------------------------------------------------ +// Validity checks (for debugging, not CT) +// ------------------------------------------------------------------------ + +impl ValidityCheck for EdwardsPoint { + fn is_valid(&self) -> bool { + let point_on_curve = self.as_projective().is_valid(); + let on_segre_image = (&self.X * &self.Y) == (&self.Z * &self.T); + + point_on_curve && on_segre_image + } +} + +// ------------------------------------------------------------------------ +// Constant-time assignment +// ------------------------------------------------------------------------ + +impl ConditionallySelectable for EdwardsPoint { + fn conditional_select(a: &EdwardsPoint, b: &EdwardsPoint, choice: Choice) -> EdwardsPoint { + EdwardsPoint { + X: FieldElement::conditional_select(&a.X, &b.X, choice), + Y: FieldElement::conditional_select(&a.Y, &b.Y, choice), + Z: FieldElement::conditional_select(&a.Z, &b.Z, choice), + T: FieldElement::conditional_select(&a.T, &b.T, choice), + } + } +} + +// ------------------------------------------------------------------------ +// Equality +// ------------------------------------------------------------------------ + +impl ConstantTimeEq for EdwardsPoint { + fn ct_eq(&self, other: &EdwardsPoint) -> Choice { + // We would like to check that the point (X/Z, Y/Z) is equal to + // the point (X'/Z', Y'/Z') without converting into affine + // coordinates (x, y) and (x', y'), which requires two inversions. + // We have that X = xZ and X' = x'Z'. Thus, x = x' is equivalent to + // (xZ)Z' = (x'Z')Z, and similarly for the y-coordinate. + + (&self.X * &other.Z).ct_eq(&(&other.X * &self.Z)) + & (&self.Y * &other.Z).ct_eq(&(&other.Y * &self.Z)) + } +} + +impl PartialEq for EdwardsPoint { + fn eq(&self, other: &EdwardsPoint) -> bool { + self.ct_eq(other).into() + } +} + +impl Eq for EdwardsPoint {} + +// ------------------------------------------------------------------------ +// Point conversions +// ------------------------------------------------------------------------ + +impl EdwardsPoint { + /// Convert to a ProjectiveNielsPoint + pub(crate) fn as_projective_niels(&self) -> ProjectiveNielsPoint { + ProjectiveNielsPoint { + Y_plus_X: &self.Y + &self.X, + Y_minus_X: &self.Y - &self.X, + Z: self.Z, + T2d: &self.T * &constants::EDWARDS_D2, + } + } + + /// Convert the representation of this point from extended + /// coordinates to projective coordinates. + /// + /// Free. + pub(crate) const fn as_projective(&self) -> ProjectivePoint { + ProjectivePoint { + X: self.X, + Y: self.Y, + Z: self.Z, + } + } + + /// Dehomogenize to a AffineNielsPoint. + /// Mainly for testing. + pub(crate) fn as_affine_niels(&self) -> AffineNielsPoint { + let recip = self.Z.invert(); + let x = &self.X * &recip; + let y = &self.Y * &recip; + let xy2d = &(&x * &y) * &constants::EDWARDS_D2; + AffineNielsPoint { + y_plus_x: &y + &x, + y_minus_x: &y - &x, + xy2d, + } + } + + /// Convert this `EdwardsPoint` on the Edwards model to the + /// corresponding `MontgomeryPoint` on the Montgomery model. + /// + /// This function has one exceptional case; the identity point of + /// the Edwards curve is sent to the 2-torsion point \\((0,0)\\) + /// on the Montgomery curve. + /// + /// Note that this is a one-way conversion, since the Montgomery + /// model does not retain sign information. + pub fn to_montgomery(&self) -> MontgomeryPoint { + // We have u = (1+y)/(1-y) = (Z+Y)/(Z-Y). + // + // The denominator is zero only when y=1, the identity point of + // the Edwards curve. Since 0.invert() = 0, in this case we + // compute the 2-torsion point (0,0). + let U = &self.Z + &self.Y; + let W = &self.Z - &self.Y; + let u = &U * &W.invert(); + MontgomeryPoint(u.as_bytes()) + } + + /// Compress this point to `CompressedEdwardsY` format. + pub fn compress(&self) -> CompressedEdwardsY { + let recip = self.Z.invert(); + let x = &self.X * &recip; + let y = &self.Y * &recip; + let mut s: [u8; 32]; + + s = y.as_bytes(); + s[31] ^= x.is_negative().unwrap_u8() << 7; + CompressedEdwardsY(s) + } + + #[cfg(feature = "digest")] + /// Maps the digest of the input bytes to the curve. This is NOT a hash-to-curve function, as + /// it produces points with a non-uniform distribution. Rather, it performs something that + /// resembles (but is not) half of the + /// [`hash_to_curve`](https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#section-3-4.2.1) + /// function from the Elligator2 spec. + #[deprecated( + since = "4.0.0", + note = "previously named `hash_from_bytes`, this is not a secure hash function" + )] + pub fn nonspec_map_to_curve(bytes: &[u8]) -> EdwardsPoint + where + D: Digest + Default, + { + use crate::elligator2::Legacy; + + let mut hash = D::new(); + hash.update(bytes); + let h = hash.finalize(); + let mut res = [0u8; 32]; + res.copy_from_slice(&h[..32]); + + let sign_bit = (res[31] & 0x80) >> 7; + + // rfc9380 should always result in a valid point since no field elements + // are invalid. so unwrap should be safe. + #[allow(clippy::unwrap_used)] + let fe1 = MontgomeryPoint::from_representative::(&res).unwrap(); + let E1_opt = fe1.to_edwards(sign_bit); + + E1_opt + .expect("Montgomery conversion to Edwards point in Elligator failed") + .mul_by_cofactor() + } +} + +// ------------------------------------------------------------------------ +// Doubling +// ------------------------------------------------------------------------ + +impl EdwardsPoint { + /// Add this point to itself. + pub(crate) fn double(&self) -> EdwardsPoint { + self.as_projective().double().as_extended() + } +} + +// ------------------------------------------------------------------------ +// Addition and Subtraction +// ------------------------------------------------------------------------ + +impl<'a, 'b> Add<&'b EdwardsPoint> for &'a EdwardsPoint { + type Output = EdwardsPoint; + fn add(self, other: &'b EdwardsPoint) -> EdwardsPoint { + (self + &other.as_projective_niels()).as_extended() + } +} + +define_add_variants!( + LHS = EdwardsPoint, + RHS = EdwardsPoint, + Output = EdwardsPoint +); + +impl<'b> AddAssign<&'b EdwardsPoint> for EdwardsPoint { + fn add_assign(&mut self, _rhs: &'b EdwardsPoint) { + *self = (self as &EdwardsPoint) + _rhs; + } +} + +define_add_assign_variants!(LHS = EdwardsPoint, RHS = EdwardsPoint); + +impl<'a, 'b> Sub<&'b EdwardsPoint> for &'a EdwardsPoint { + type Output = EdwardsPoint; + fn sub(self, other: &'b EdwardsPoint) -> EdwardsPoint { + (self - &other.as_projective_niels()).as_extended() + } +} + +define_sub_variants!( + LHS = EdwardsPoint, + RHS = EdwardsPoint, + Output = EdwardsPoint +); + +impl<'b> SubAssign<&'b EdwardsPoint> for EdwardsPoint { + fn sub_assign(&mut self, _rhs: &'b EdwardsPoint) { + *self = (self as &EdwardsPoint) - _rhs; + } +} + +define_sub_assign_variants!(LHS = EdwardsPoint, RHS = EdwardsPoint); + +impl Sum for EdwardsPoint +where + T: Borrow, +{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(EdwardsPoint::identity(), |acc, item| acc + item.borrow()) + } +} + +// ------------------------------------------------------------------------ +// Negation +// ------------------------------------------------------------------------ + +impl<'a> Neg for &'a EdwardsPoint { + type Output = EdwardsPoint; + + fn neg(self) -> EdwardsPoint { + EdwardsPoint { + X: -(&self.X), + Y: self.Y, + Z: self.Z, + T: -(&self.T), + } + } +} + +impl Neg for EdwardsPoint { + type Output = EdwardsPoint; + + fn neg(self) -> EdwardsPoint { + -&self + } +} + +// ------------------------------------------------------------------------ +// Scalar multiplication +// ------------------------------------------------------------------------ + +impl<'b> MulAssign<&'b Scalar> for EdwardsPoint { + fn mul_assign(&mut self, scalar: &'b Scalar) { + let result = (self as &EdwardsPoint) * scalar; + *self = result; + } +} + +define_mul_assign_variants!(LHS = EdwardsPoint, RHS = Scalar); + +define_mul_variants!(LHS = EdwardsPoint, RHS = Scalar, Output = EdwardsPoint); +define_mul_variants!(LHS = Scalar, RHS = EdwardsPoint, Output = EdwardsPoint); + +impl<'a, 'b> Mul<&'b Scalar> for &'a EdwardsPoint { + type Output = EdwardsPoint; + /// Scalar multiplication: compute `scalar * self`. + /// + /// For scalar multiplication of a basepoint, + /// `EdwardsBasepointTable` is approximately 4x faster. + fn mul(self, scalar: &'b Scalar) -> EdwardsPoint { + crate::backend::variable_base_mul(self, scalar) + } +} + +impl<'a, 'b> Mul<&'b EdwardsPoint> for &'a Scalar { + type Output = EdwardsPoint; + + /// Scalar multiplication: compute `scalar * self`. + /// + /// For scalar multiplication of a basepoint, + /// `EdwardsBasepointTable` is approximately 4x faster. + fn mul(self, point: &'b EdwardsPoint) -> EdwardsPoint { + point * self + } +} + +impl EdwardsPoint { + /// Fixed-base scalar multiplication by the Ed25519 base point. + /// + /// Uses precomputed basepoint tables when the `precomputed-tables` feature + /// is enabled, trading off increased code size for ~4x better performance. + pub fn mul_base(scalar: &Scalar) -> Self { + #[cfg(not(feature = "precomputed-tables"))] + { + scalar * constants::ED25519_BASEPOINT_POINT + } + + #[cfg(feature = "precomputed-tables")] + { + scalar * constants::ED25519_BASEPOINT_TABLE + } + } + + /// Multiply this point by `clamp_integer(bytes)`. For a description of clamping, see + /// [`clamp_integer`]. + pub fn mul_clamped(self, bytes: [u8; 32]) -> Self { + // We have to construct a Scalar that is not reduced mod l, which breaks scalar invariant + // #2. But #2 is not necessary for correctness of variable-base multiplication. All that + // needs to hold is invariant #1, i.e., the scalar is less than 2^255. This is guaranteed + // by clamping. + // Further, we don't do any reduction or arithmetic with this clamped value, so there's no + // issues arising from the fact that the curve point is not necessarily in the prime-order + // subgroup. + let s = Scalar { + bytes: clamp_integer(bytes), + }; + s * self + } + + /// Multiply the basepoint by `clamp_integer(bytes)`. For a description of clamping, see + /// [`clamp_integer`]. + pub fn mul_base_clamped(bytes: [u8; 32]) -> Self { + // See reasoning in Self::mul_clamped why it is OK to make an unreduced Scalar here. We + // note that fixed-base multiplication is also defined for all values of `bytes` less than + // 2^255. + let s = Scalar { + bytes: clamp_integer(bytes), + }; + Self::mul_base(&s) + } +} + +// ------------------------------------------------------------------------ +// Multiscalar Multiplication impls +// ------------------------------------------------------------------------ + +// These use the iterator's size hint and the target settings to +// forward to a specific backend implementation. + +#[cfg(feature = "alloc")] +impl MultiscalarMul for EdwardsPoint { + type Point = EdwardsPoint; + + fn multiscalar_mul(scalars: I, points: J) -> EdwardsPoint + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + { + // Sanity-check lengths of input iterators + let mut scalars = scalars.into_iter(); + let mut points = points.into_iter(); + + // Lower and upper bounds on iterators + let (s_lo, s_hi) = scalars.by_ref().size_hint(); + let (p_lo, p_hi) = points.by_ref().size_hint(); + + // They should all be equal + assert_eq!(s_lo, p_lo); + assert_eq!(s_hi, Some(s_lo)); + assert_eq!(p_hi, Some(p_lo)); + + // Now we know there's a single size. When we do + // size-dependent algorithm dispatch, use this as the hint. + let _size = s_lo; + + crate::backend::straus_multiscalar_mul(scalars, points) + } +} + +#[cfg(feature = "alloc")] +impl VartimeMultiscalarMul for EdwardsPoint { + type Point = EdwardsPoint; + + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>, + { + // Sanity-check lengths of input iterators + let mut scalars = scalars.into_iter(); + let mut points = points.into_iter(); + + // Lower and upper bounds on iterators + let (s_lo, s_hi) = scalars.by_ref().size_hint(); + let (p_lo, p_hi) = points.by_ref().size_hint(); + + // They should all be equal + assert_eq!(s_lo, p_lo); + assert_eq!(s_hi, Some(s_lo)); + assert_eq!(p_hi, Some(p_lo)); + + // Now we know there's a single size. + // Use this as the hint to decide which algorithm to use. + let size = s_lo; + + if size < 190 { + crate::backend::straus_optional_multiscalar_mul(scalars, points) + } else { + crate::backend::pippenger_optional_multiscalar_mul(scalars, points) + } + } +} + +/// Precomputation for variable-time multiscalar multiplication with `EdwardsPoint`s. +// This wraps the inner implementation in a facade type so that we can +// decouple stability of the inner type from the stability of the +// outer type. +#[cfg(feature = "alloc")] +pub struct VartimeEdwardsPrecomputation(crate::backend::VartimePrecomputedStraus); + +#[cfg(feature = "alloc")] +impl VartimePrecomputedMultiscalarMul for VartimeEdwardsPrecomputation { + type Point = EdwardsPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self(crate::backend::VartimePrecomputedStraus::new(static_points)) + } + + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>, + { + self.0 + .optional_mixed_multiscalar_mul(static_scalars, dynamic_scalars, dynamic_points) + } +} + +impl EdwardsPoint { + /// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint. + pub fn vartime_double_scalar_mul_basepoint( + a: &Scalar, + A: &EdwardsPoint, + b: &Scalar, + ) -> EdwardsPoint { + crate::backend::vartime_double_base_mul(a, A, b) + } +} + +#[cfg(feature = "precomputed-tables")] +macro_rules! impl_basepoint_table { + (Name = $name:ident, LookupTable = $table:ident, Point = $point:ty, Radix = $radix:expr, Additions = $adds:expr) => { + /// A precomputed table of multiples of a basepoint, for accelerating + /// fixed-base scalar multiplication. One table, for the Ed25519 + /// basepoint, is provided in the [`constants`] module. + /// + /// The basepoint tables are reasonably large, so they should probably be boxed. + /// + /// The sizes for the tables and the number of additions required for one scalar + /// multiplication are as follows: + /// + /// * [`EdwardsBasepointTableRadix16`]: 30KB, 64A + /// (this is the default size, and is used for + /// [`constants::ED25519_BASEPOINT_TABLE`]) + /// * [`EdwardsBasepointTableRadix64`]: 120KB, 43A + /// * [`EdwardsBasepointTableRadix128`]: 240KB, 37A + /// * [`EdwardsBasepointTableRadix256`]: 480KB, 33A + /// + /// # Why 33 additions for radix-256? + /// + /// Normally, the radix-256 tables would allow for only 32 additions per scalar + /// multiplication. However, due to the fact that standardised definitions of + /// legacy protocols—such as x25519—require allowing unreduced 255-bit scalars + /// invariants, when converting such an unreduced scalar's representation to + /// radix-\\(2^{8}\\), we cannot guarantee the carry bit will fit in the last + /// coefficient (the coefficients are `i8`s). When, \\(w\\), the power-of-2 of + /// the radix, is \\(w < 8\\), we can fold the final carry onto the last + /// coefficient, \\(d\\), because \\(d < 2^{w/2}\\), so + /// $$ + /// d + carry \cdot 2^{w} = d + 1 \cdot 2^{w} < 2^{w+1} < 2^{8} + /// $$ + /// When \\(w = 8\\), we can't fit \\(carry \cdot 2^{w}\\) into an `i8`, so we + /// add the carry bit onto an additional coefficient. + #[derive(Clone)] + #[repr(transparent)] + pub struct $name(pub(crate) [$table; 32]); + + impl BasepointTable for $name { + type Point = $point; + + /// Create a table of precomputed multiples of `basepoint`. + fn create(basepoint: &$point) -> $name { + // XXX use init_with + let mut table = $name([$table::default(); 32]); + let mut P = *basepoint; + for i in 0..32 { + // P = (2w)^i * B + table.0[i] = $table::from(&P); + P = P.mul_by_pow_2($radix + $radix); + } + table + } + + /// Get the basepoint for this table as an `EdwardsPoint`. + fn basepoint(&self) -> $point { + // self.0[0].select(1) = 1*(16^2)^0*B + // but as an `AffineNielsPoint`, so add identity to convert to extended. + (&<$point>::identity() + &self.0[0].select(1)).as_extended() + } + + /// The computation uses Pippeneger's algorithm, as described for the + /// specific case of radix-16 on page 13 of the Ed25519 paper. + /// + /// # Piggenger's Algorithm Generalised + /// + /// Write the scalar \\(a\\) in radix-\\(w\\), where \\(w\\) is a power of + /// 2, with coefficients in \\([\frac{-w}{2},\frac{w}{2})\\), i.e., + /// $$ + /// a = a\_0 + a\_1 w\^1 + \cdots + a\_{x} w\^{x}, + /// $$ + /// with + /// $$ + /// \begin{aligned} + /// \frac{-w}{2} \leq a_i < \frac{w}{2} + /// &&\cdots&& + /// \frac{-w}{2} \leq a\_{x} \leq \frac{w}{2} + /// \end{aligned} + /// $$ + /// and the number of additions, \\(x\\), is given by + /// \\(x = \lceil \frac{256}{w} \rceil\\). Then + /// $$ + /// a B = a\_0 B + a\_1 w\^1 B + \cdots + a\_{x-1} w\^{x-1} B. + /// $$ + /// Grouping even and odd coefficients gives + /// $$ + /// \begin{aligned} + /// a B = \quad a\_0 w\^0 B +& a\_2 w\^2 B + \cdots + a\_{x-2} w\^{x-2} B \\\\ + /// + a\_1 w\^1 B +& a\_3 w\^3 B + \cdots + a\_{x-1} w\^{x-1} B \\\\ + /// = \quad(a\_0 w\^0 B +& a\_2 w\^2 B + \cdots + a\_{x-2} w\^{x-2} B) \\\\ + /// + w(a\_1 w\^0 B +& a\_3 w\^2 B + \cdots + a\_{x-1} w\^{x-2} B). \\\\ + /// \end{aligned} + /// $$ + /// For each \\(i = 0 \ldots 31\\), we create a lookup table of + /// $$ + /// [w\^{2i} B, \ldots, \frac{w}{2}\cdot w\^{2i} B], + /// $$ + /// and use it to select \\( y \cdot w\^{2i} \cdot B \\) in constant time. + /// + /// The radix-\\(w\\) representation requires that the scalar is bounded + /// by \\(2\^{255}\\), which is always the case. + /// + /// The above algorithm is trivially generalised to other powers-of-2 radices. + fn mul_base(&self, scalar: &Scalar) -> $point { + let a = scalar.as_radix_2w($radix); + + let tables = &self.0; + let mut P = <$point>::identity(); + + for i in (0..$adds).filter(|x| x % 2 == 1) { + P = (&P + &tables[i / 2].select(a[i])).as_extended(); + } + + P = P.mul_by_pow_2($radix); + + for i in (0..$adds).filter(|x| x % 2 == 0) { + P = (&P + &tables[i / 2].select(a[i])).as_extended(); + } + + P + } + } + + impl<'a, 'b> Mul<&'b Scalar> for &'a $name { + type Output = $point; + + /// Construct an `EdwardsPoint` from a `Scalar` \\(a\\) by + /// computing the multiple \\(aB\\) of this basepoint \\(B\\). + fn mul(self, scalar: &'b Scalar) -> $point { + // delegate to a private function so that its documentation appears in internal docs + self.mul_base(scalar) + } + } + + impl<'a, 'b> Mul<&'a $name> for &'b Scalar { + type Output = $point; + + /// Construct an `EdwardsPoint` from a `Scalar` \\(a\\) by + /// computing the multiple \\(aB\\) of this basepoint \\(B\\). + fn mul(self, basepoint_table: &'a $name) -> $point { + basepoint_table * self + } + } + + impl Debug for $name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?}([\n", stringify!($name))?; + for i in 0..32 { + write!(f, "\t{:?},\n", &self.0[i])?; + } + write!(f, "])") + } + } + }; +} // End macro_rules! impl_basepoint_table + +// The number of additions required is ceil(256/w) where w is the radix representation. +cfg_if! { + if #[cfg(feature = "precomputed-tables")] { + impl_basepoint_table! { + Name = EdwardsBasepointTable, + LookupTable = LookupTableRadix16, + Point = EdwardsPoint, + Radix = 4, + Additions = 64 + } + impl_basepoint_table! { + Name = EdwardsBasepointTableRadix32, + LookupTable = LookupTableRadix32, + Point = EdwardsPoint, + Radix = 5, + Additions = 52 + } + impl_basepoint_table! { + Name = EdwardsBasepointTableRadix64, + LookupTable = LookupTableRadix64, + Point = EdwardsPoint, + Radix = 6, + Additions = 43 + } + impl_basepoint_table! { + Name = EdwardsBasepointTableRadix128, + LookupTable = LookupTableRadix128, + Point = EdwardsPoint, + Radix = 7, + Additions = 37 + } + impl_basepoint_table! { + Name = EdwardsBasepointTableRadix256, + LookupTable = LookupTableRadix256, + Point = EdwardsPoint, + Radix = 8, + Additions = 33 + } + + /// A type-alias for [`EdwardsBasepointTable`] because the latter is + /// used as a constructor in the [`constants`] module. + // + // Same as for `LookupTableRadix16`, we have to define `EdwardsBasepointTable` + // first, because it's used as a constructor, and then provide a type alias for + // it. + pub type EdwardsBasepointTableRadix16 = EdwardsBasepointTable; + } +} + +#[cfg(feature = "precomputed-tables")] +macro_rules! impl_basepoint_table_conversions { + (LHS = $lhs:ty, RHS = $rhs:ty) => { + impl<'a> From<&'a $lhs> for $rhs { + fn from(table: &'a $lhs) -> $rhs { + <$rhs>::create(&table.basepoint()) + } + } + + impl<'a> From<&'a $rhs> for $lhs { + fn from(table: &'a $rhs) -> $lhs { + <$lhs>::create(&table.basepoint()) + } + } + }; +} + +cfg_if! { + if #[cfg(feature = "precomputed-tables")] { + // Conversions from radix 16 + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix16, + RHS = EdwardsBasepointTableRadix32 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix16, + RHS = EdwardsBasepointTableRadix64 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix16, + RHS = EdwardsBasepointTableRadix128 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix16, + RHS = EdwardsBasepointTableRadix256 + } + + // Conversions from radix 32 + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix32, + RHS = EdwardsBasepointTableRadix64 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix32, + RHS = EdwardsBasepointTableRadix128 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix32, + RHS = EdwardsBasepointTableRadix256 + } + + // Conversions from radix 64 + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix64, + RHS = EdwardsBasepointTableRadix128 + } + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix64, + RHS = EdwardsBasepointTableRadix256 + } + + // Conversions from radix 128 + impl_basepoint_table_conversions! { + LHS = EdwardsBasepointTableRadix128, + RHS = EdwardsBasepointTableRadix256 + } + } +} + +impl EdwardsPoint { + /// Multiply by the cofactor: return \\(\[8\]P\\). + pub fn mul_by_cofactor(&self) -> EdwardsPoint { + self.mul_by_pow_2(3) + } + + /// Compute \\([2\^k] P \\) by successive doublings. Requires \\( k > 0 \\). + pub(crate) fn mul_by_pow_2(&self, k: u32) -> EdwardsPoint { + debug_assert!(k > 0); + let mut r: CompletedPoint; + let mut s = self.as_projective(); + for _ in 0..(k - 1) { + r = s.double(); + s = r.as_projective(); + } + // Unroll last iteration so we can go directly as_extended() + s.double().as_extended() + } + + /// Determine if this point is of small order. + /// + /// # Return + /// + /// * `true` if `self` is in the torsion subgroup \\( \mathcal E\[8\] \\); + /// * `false` if `self` is not in the torsion subgroup \\( \mathcal E\[8\] \\). + /// + /// # Example + /// + /// ``` + /// use curve25519_elligator2::constants; + /// + /// // Generator of the prime-order subgroup + /// let P = constants::ED25519_BASEPOINT_POINT; + /// // Generator of the torsion subgroup + /// let Q = constants::EIGHT_TORSION[1]; + /// + /// // P has large order + /// assert_eq!(P.is_small_order(), false); + /// + /// // Q has small order + /// assert_eq!(Q.is_small_order(), true); + /// ``` + pub fn is_small_order(&self) -> bool { + self.mul_by_cofactor().is_identity() + } + + /// Determine if this point is “torsion-free”, i.e., is contained in + /// the prime-order subgroup. + /// + /// # Return + /// + /// * `true` if `self` has zero torsion component and is in the + /// prime-order subgroup; + /// * `false` if `self` has a nonzero torsion component and is not + /// in the prime-order subgroup. + /// + /// # Example + /// + /// ``` + /// use curve25519_elligator2::constants; + /// + /// // Generator of the prime-order subgroup + /// let P = constants::ED25519_BASEPOINT_POINT; + /// // Generator of the torsion subgroup + /// let Q = constants::EIGHT_TORSION[1]; + /// + /// // P is torsion-free + /// assert_eq!(P.is_torsion_free(), true); + /// + /// // P + Q is not torsion-free + /// assert_eq!((P+Q).is_torsion_free(), false); + /// ``` + pub fn is_torsion_free(&self) -> bool { + (self * constants::BASEPOINT_ORDER_PRIVATE).is_identity() + } +} + +// ------------------------------------------------------------------------ +// Debug traits +// ------------------------------------------------------------------------ + +impl Debug for EdwardsPoint { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "EdwardsPoint{{\n\tX: {:?},\n\tY: {:?},\n\tZ: {:?},\n\tT: {:?}\n}}", + &self.X, &self.Y, &self.Z, &self.T + ) + } +} + +// ------------------------------------------------------------------------ +// group traits +// ------------------------------------------------------------------------ + +// Use the full trait path to avoid Group::identity overlapping Identity::identity in the +// rest of the module (e.g. tests). +#[cfg(feature = "group")] +impl group::Group for EdwardsPoint { + type Scalar = Scalar; + + fn random(mut rng: impl RngCore) -> Self { + let mut repr = CompressedEdwardsY([0u8; 32]); + loop { + rng.fill_bytes(&mut repr.0); + if let Some(p) = repr.decompress() { + if !IsIdentity::is_identity(&p) { + break p; + } + } + } + } + + fn identity() -> Self { + Identity::identity() + } + + fn generator() -> Self { + constants::ED25519_BASEPOINT_POINT + } + + fn is_identity(&self) -> Choice { + self.ct_eq(&Identity::identity()) + } + + fn double(&self) -> Self { + self.double() + } +} + +#[cfg(feature = "group")] +impl GroupEncoding for EdwardsPoint { + type Repr = [u8; 32]; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + let repr = CompressedEdwardsY(*bytes); + let (is_valid_y_coord, X, Y, Z) = decompress::step_1(&repr); + CtOption::new(decompress::step_2(&repr, X, Y, Z), is_valid_y_coord) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // Just use the checked API; there are no checks we can skip. + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + self.compress().to_bytes() + } +} + +/// A `SubgroupPoint` represents a point on the Edwards form of Curve25519, that is +/// guaranteed to be in the prime-order subgroup. +#[cfg(feature = "group")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SubgroupPoint(EdwardsPoint); + +#[cfg(feature = "group")] +impl From for EdwardsPoint { + fn from(p: SubgroupPoint) -> Self { + p.0 + } +} + +#[cfg(feature = "group")] +impl Neg for SubgroupPoint { + type Output = Self; + + fn neg(self) -> Self::Output { + SubgroupPoint(-self.0) + } +} + +#[cfg(feature = "group")] +impl Add<&SubgroupPoint> for &SubgroupPoint { + type Output = SubgroupPoint; + fn add(self, other: &SubgroupPoint) -> SubgroupPoint { + SubgroupPoint(self.0 + other.0) + } +} + +#[cfg(feature = "group")] +define_add_variants!( + LHS = SubgroupPoint, + RHS = SubgroupPoint, + Output = SubgroupPoint +); + +#[cfg(feature = "group")] +impl Add<&SubgroupPoint> for &EdwardsPoint { + type Output = EdwardsPoint; + fn add(self, other: &SubgroupPoint) -> EdwardsPoint { + self + other.0 + } +} + +#[cfg(feature = "group")] +define_add_variants!( + LHS = EdwardsPoint, + RHS = SubgroupPoint, + Output = EdwardsPoint +); + +#[cfg(feature = "group")] +impl AddAssign<&SubgroupPoint> for SubgroupPoint { + fn add_assign(&mut self, rhs: &SubgroupPoint) { + self.0 += rhs.0 + } +} + +#[cfg(feature = "group")] +define_add_assign_variants!(LHS = SubgroupPoint, RHS = SubgroupPoint); + +#[cfg(feature = "group")] +impl AddAssign<&SubgroupPoint> for EdwardsPoint { + fn add_assign(&mut self, rhs: &SubgroupPoint) { + *self += rhs.0 + } +} + +#[cfg(feature = "group")] +define_add_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint); + +#[cfg(feature = "group")] +impl Sub<&SubgroupPoint> for &SubgroupPoint { + type Output = SubgroupPoint; + fn sub(self, other: &SubgroupPoint) -> SubgroupPoint { + SubgroupPoint(self.0 - other.0) + } +} + +#[cfg(feature = "group")] +define_sub_variants!( + LHS = SubgroupPoint, + RHS = SubgroupPoint, + Output = SubgroupPoint +); + +#[cfg(feature = "group")] +impl Sub<&SubgroupPoint> for &EdwardsPoint { + type Output = EdwardsPoint; + fn sub(self, other: &SubgroupPoint) -> EdwardsPoint { + self - other.0 + } +} + +#[cfg(feature = "group")] +define_sub_variants!( + LHS = EdwardsPoint, + RHS = SubgroupPoint, + Output = EdwardsPoint +); + +#[cfg(feature = "group")] +impl SubAssign<&SubgroupPoint> for SubgroupPoint { + fn sub_assign(&mut self, rhs: &SubgroupPoint) { + self.0 -= rhs.0; + } +} + +#[cfg(feature = "group")] +define_sub_assign_variants!(LHS = SubgroupPoint, RHS = SubgroupPoint); + +#[cfg(feature = "group")] +impl SubAssign<&SubgroupPoint> for EdwardsPoint { + fn sub_assign(&mut self, rhs: &SubgroupPoint) { + *self -= rhs.0; + } +} + +#[cfg(feature = "group")] +define_sub_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint); + +#[cfg(feature = "group")] +impl Sum for SubgroupPoint +where + T: Borrow, +{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + use group::Group; + iter.fold(SubgroupPoint::identity(), |acc, item| acc + item.borrow()) + } +} + +#[cfg(feature = "group")] +impl Mul<&Scalar> for &SubgroupPoint { + type Output = SubgroupPoint; + + /// Scalar multiplication: compute `scalar * self`. + /// + /// For scalar multiplication of a basepoint, + /// `EdwardsBasepointTable` is approximately 4x faster. + fn mul(self, scalar: &Scalar) -> SubgroupPoint { + SubgroupPoint(self.0 * scalar) + } +} + +#[cfg(feature = "group")] +define_mul_variants!(LHS = Scalar, RHS = SubgroupPoint, Output = SubgroupPoint); + +#[cfg(feature = "group")] +impl Mul<&SubgroupPoint> for &Scalar { + type Output = SubgroupPoint; + + /// Scalar multiplication: compute `scalar * self`. + /// + /// For scalar multiplication of a basepoint, + /// `EdwardsBasepointTable` is approximately 4x faster. + fn mul(self, point: &SubgroupPoint) -> SubgroupPoint { + point * self + } +} + +#[cfg(feature = "group")] +define_mul_variants!(LHS = SubgroupPoint, RHS = Scalar, Output = SubgroupPoint); + +#[cfg(feature = "group")] +impl MulAssign<&Scalar> for SubgroupPoint { + fn mul_assign(&mut self, scalar: &Scalar) { + self.0 *= scalar; + } +} + +#[cfg(feature = "group")] +define_mul_assign_variants!(LHS = SubgroupPoint, RHS = Scalar); + +#[cfg(feature = "group")] +impl group::Group for SubgroupPoint { + type Scalar = Scalar; + + fn random(mut rng: impl RngCore) -> Self { + use group::ff::Field; + + // This will almost never loop, but `Group::random` is documented as returning a + // non-identity element. + let s = loop { + let s: Scalar = Field::random(&mut rng); + if !s.is_zero_vartime() { + break s; + } + }; + + // This gives an element of the prime-order subgroup. + Self::generator() * s + } + + fn identity() -> Self { + SubgroupPoint(Identity::identity()) + } + + fn generator() -> Self { + SubgroupPoint(EdwardsPoint::generator()) + } + + fn is_identity(&self) -> Choice { + self.0.ct_eq(&Identity::identity()) + } + + fn double(&self) -> Self { + SubgroupPoint(self.0.double()) + } +} + +#[cfg(feature = "group")] +impl GroupEncoding for SubgroupPoint { + type Repr = ::Repr; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + EdwardsPoint::from_bytes(bytes).and_then(|p| p.into_subgroup()) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + EdwardsPoint::from_bytes_unchecked(bytes).and_then(|p| p.into_subgroup()) + } + + fn to_bytes(&self) -> Self::Repr { + self.0.compress().to_bytes() + } +} + +#[cfg(feature = "group")] +impl PrimeGroup for SubgroupPoint {} + +/// Ristretto has a cofactor of 1. +#[cfg(feature = "group")] +impl CofactorGroup for EdwardsPoint { + type Subgroup = SubgroupPoint; + + fn clear_cofactor(&self) -> Self::Subgroup { + SubgroupPoint(self.mul_by_cofactor()) + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(SubgroupPoint(self), CofactorGroup::is_torsion_free(&self)) + } + + fn is_torsion_free(&self) -> Choice { + (self * constants::BASEPOINT_ORDER_PRIVATE).ct_eq(&Self::identity()) + } +} + +// ------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------ + +#[cfg(test)] +mod test { + use super::*; + + // If `group` is set, then this is already imported in super + #[cfg(not(feature = "group"))] + use rand_core::RngCore; + + #[cfg(feature = "alloc")] + use alloc::vec::Vec; + + #[cfg(feature = "precomputed-tables")] + use crate::constants::ED25519_BASEPOINT_TABLE; + + /// X coordinate of the basepoint. + /// = 15112221349535400772501151409588531511454012693041857206046113283949847762202 + static BASE_X_COORD_BYTES: [u8; 32] = [ + 0x1a, 0xd5, 0x25, 0x8f, 0x60, 0x2d, 0x56, 0xc9, 0xb2, 0xa7, 0x25, 0x95, 0x60, 0xc7, 0x2c, + 0x69, 0x5c, 0xdc, 0xd6, 0xfd, 0x31, 0xe2, 0xa4, 0xc0, 0xfe, 0x53, 0x6e, 0xcd, 0xd3, 0x36, + 0x69, 0x21, + ]; + + /// Compressed Edwards Y form of 2*basepoint. + static BASE2_CMPRSSD: CompressedEdwardsY = CompressedEdwardsY([ + 0xc9, 0xa3, 0xf8, 0x6a, 0xae, 0x46, 0x5f, 0xe, 0x56, 0x51, 0x38, 0x64, 0x51, 0x0f, 0x39, + 0x97, 0x56, 0x1f, 0xa2, 0xc9, 0xe8, 0x5e, 0xa2, 0x1d, 0xc2, 0x29, 0x23, 0x09, 0xf3, 0xcd, + 0x60, 0x22, + ]); + + /// Compressed Edwards Y form of 16*basepoint. + static BASE16_CMPRSSD: CompressedEdwardsY = CompressedEdwardsY([ + 0xeb, 0x27, 0x67, 0xc1, 0x37, 0xab, 0x7a, 0xd8, 0x27, 0x9c, 0x07, 0x8e, 0xff, 0x11, 0x6a, + 0xb0, 0x78, 0x6e, 0xad, 0x3a, 0x2e, 0x0f, 0x98, 0x9f, 0x72, 0xc3, 0x7f, 0x82, 0xf2, 0x96, + 0x96, 0x70, + ]); + + /// 4493907448824000747700850167940867464579944529806937181821189941592931634714 + pub static A_SCALAR: Scalar = Scalar { + bytes: [ + 0x1a, 0x0e, 0x97, 0x8a, 0x90, 0xf6, 0x62, 0x2d, 0x37, 0x47, 0x02, 0x3f, 0x8a, 0xd8, + 0x26, 0x4d, 0xa7, 0x58, 0xaa, 0x1b, 0x88, 0xe0, 0x40, 0xd1, 0x58, 0x9e, 0x7b, 0x7f, + 0x23, 0x76, 0xef, 0x09, + ], + }; + + /// 2506056684125797857694181776241676200180934651973138769173342316833279714961 + pub static B_SCALAR: Scalar = Scalar { + bytes: [ + 0x91, 0x26, 0x7a, 0xcf, 0x25, 0xc2, 0x09, 0x1b, 0xa2, 0x17, 0x74, 0x7b, 0x66, 0xf0, + 0xb3, 0x2e, 0x9d, 0xf2, 0xa5, 0x67, 0x41, 0xcf, 0xda, 0xc4, 0x56, 0xa7, 0xd4, 0xaa, + 0xb8, 0x60, 0x8a, 0x05, + ], + }; + + /// A_SCALAR * basepoint, computed with ed25519.py + pub static A_TIMES_BASEPOINT: CompressedEdwardsY = CompressedEdwardsY([ + 0xea, 0x27, 0xe2, 0x60, 0x53, 0xdf, 0x1b, 0x59, 0x56, 0xf1, 0x4d, 0x5d, 0xec, 0x3c, 0x34, + 0xc3, 0x84, 0xa2, 0x69, 0xb7, 0x4c, 0xc3, 0x80, 0x3e, 0xa8, 0xe2, 0xe7, 0xc9, 0x42, 0x5e, + 0x40, 0xa5, + ]); + + /// A_SCALAR * (A_TIMES_BASEPOINT) + B_SCALAR * BASEPOINT + /// computed with ed25519.py + static DOUBLE_SCALAR_MULT_RESULT: CompressedEdwardsY = CompressedEdwardsY([ + 0x7d, 0xfd, 0x6c, 0x45, 0xaf, 0x6d, 0x6e, 0x0e, 0xba, 0x20, 0x37, 0x1a, 0x23, 0x64, 0x59, + 0xc4, 0xc0, 0x46, 0x83, 0x43, 0xde, 0x70, 0x4b, 0x85, 0x09, 0x6f, 0xfe, 0x35, 0x4f, 0x13, + 0x2b, 0x42, + ]); + + /// Test round-trip decompression for the basepoint. + #[test] + fn basepoint_decompression_compression() { + let base_X = FieldElement::from_bytes(&BASE_X_COORD_BYTES); + let bp = constants::ED25519_BASEPOINT_COMPRESSED + .decompress() + .unwrap(); + assert!(bp.is_valid()); + // Check that decompression actually gives the correct X coordinate + assert_eq!(base_X, bp.X); + assert_eq!(bp.compress(), constants::ED25519_BASEPOINT_COMPRESSED); + } + + /// Test sign handling in decompression + #[test] + fn decompression_sign_handling() { + // Manually set the high bit of the last byte to flip the sign + let mut minus_basepoint_bytes = *constants::ED25519_BASEPOINT_COMPRESSED.as_bytes(); + minus_basepoint_bytes[31] |= 1 << 7; + let minus_basepoint = CompressedEdwardsY(minus_basepoint_bytes) + .decompress() + .unwrap(); + // Test projective coordinates exactly since we know they should + // only differ by a flipped sign. + assert_eq!(minus_basepoint.X, -(&constants::ED25519_BASEPOINT_POINT.X)); + assert_eq!(minus_basepoint.Y, constants::ED25519_BASEPOINT_POINT.Y); + assert_eq!(minus_basepoint.Z, constants::ED25519_BASEPOINT_POINT.Z); + assert_eq!(minus_basepoint.T, -(&constants::ED25519_BASEPOINT_POINT.T)); + } + + /// Test that computing 1*basepoint gives the correct basepoint. + #[cfg(feature = "precomputed-tables")] + #[test] + fn basepoint_mult_one_vs_basepoint() { + let bp = ED25519_BASEPOINT_TABLE * &Scalar::ONE; + let compressed = bp.compress(); + assert_eq!(compressed, constants::ED25519_BASEPOINT_COMPRESSED); + } + + /// Test that `EdwardsBasepointTable::basepoint()` gives the correct basepoint. + #[cfg(feature = "precomputed-tables")] + #[test] + fn basepoint_table_basepoint_function_correct() { + let bp = ED25519_BASEPOINT_TABLE.basepoint(); + assert_eq!(bp.compress(), constants::ED25519_BASEPOINT_COMPRESSED); + } + + /// Test `impl Add for EdwardsPoint` + /// using basepoint + basepoint versus the 2*basepoint constant. + #[test] + fn basepoint_plus_basepoint_vs_basepoint2() { + let bp = constants::ED25519_BASEPOINT_POINT; + let bp_added = bp + bp; + assert_eq!(bp_added.compress(), BASE2_CMPRSSD); + } + + /// Test `impl Add for EdwardsPoint` + /// using the basepoint, basepoint2 constants + #[test] + fn basepoint_plus_basepoint_projective_niels_vs_basepoint2() { + let bp = constants::ED25519_BASEPOINT_POINT; + let bp_added = (&bp + &bp.as_projective_niels()).as_extended(); + assert_eq!(bp_added.compress(), BASE2_CMPRSSD); + } + + /// Test `impl Add for EdwardsPoint` + /// using the basepoint, basepoint2 constants + #[test] + fn basepoint_plus_basepoint_affine_niels_vs_basepoint2() { + let bp = constants::ED25519_BASEPOINT_POINT; + let bp_affine_niels = bp.as_affine_niels(); + let bp_added = (&bp + &bp_affine_niels).as_extended(); + assert_eq!(bp_added.compress(), BASE2_CMPRSSD); + } + + /// Check that equality of `EdwardsPoints` handles projective + /// coordinates correctly. + #[test] + fn extended_point_equality_handles_scaling() { + let mut two_bytes = [0u8; 32]; + two_bytes[0] = 2; + let id1 = EdwardsPoint::identity(); + let id2 = EdwardsPoint { + X: FieldElement::ZERO, + Y: FieldElement::from_bytes(&two_bytes), + Z: FieldElement::from_bytes(&two_bytes), + T: FieldElement::ZERO, + }; + assert!(bool::from(id1.ct_eq(&id2))); + } + + /// Sanity check for conversion to precomputed points + #[cfg(feature = "precomputed-tables")] + #[test] + fn to_affine_niels_clears_denominators() { + // construct a point as aB so it has denominators (ie. Z != 1) + let aB = ED25519_BASEPOINT_TABLE * &A_SCALAR; + let aB_affine_niels = aB.as_affine_niels(); + let also_aB = (&EdwardsPoint::identity() + &aB_affine_niels).as_extended(); + assert_eq!(aB.compress(), also_aB.compress()); + } + + /// Test mul_base versus a known scalar multiple from ed25519.py + #[test] + fn basepoint_mult_vs_ed25519py() { + let aB = EdwardsPoint::mul_base(&A_SCALAR); + assert_eq!(aB.compress(), A_TIMES_BASEPOINT); + } + + /// Test that multiplication by the basepoint order kills the basepoint + #[test] + fn basepoint_mult_by_basepoint_order() { + let should_be_id = EdwardsPoint::mul_base(&constants::BASEPOINT_ORDER_PRIVATE); + assert!(should_be_id.is_identity()); + } + + /// Test precomputed basepoint mult + #[cfg(feature = "precomputed-tables")] + #[test] + fn test_precomputed_basepoint_mult() { + let aB_1 = ED25519_BASEPOINT_TABLE * &A_SCALAR; + let aB_2 = constants::ED25519_BASEPOINT_POINT * A_SCALAR; + assert_eq!(aB_1.compress(), aB_2.compress()); + } + + /// Test scalar_mul versus a known scalar multiple from ed25519.py + #[test] + fn scalar_mul_vs_ed25519py() { + let aB = constants::ED25519_BASEPOINT_POINT * A_SCALAR; + assert_eq!(aB.compress(), A_TIMES_BASEPOINT); + } + + /// Test basepoint.double() versus the 2*basepoint constant. + #[test] + fn basepoint_double_vs_basepoint2() { + assert_eq!( + constants::ED25519_BASEPOINT_POINT.double().compress(), + BASE2_CMPRSSD + ); + } + + /// Test that computing 2*basepoint is the same as basepoint.double() + #[test] + fn basepoint_mult_two_vs_basepoint2() { + let two = Scalar::from(2u64); + let bp2 = EdwardsPoint::mul_base(&two); + assert_eq!(bp2.compress(), BASE2_CMPRSSD); + } + + /// Test that all the basepoint table types compute the same results. + #[cfg(feature = "precomputed-tables")] + #[test] + fn basepoint_tables() { + let P = &constants::ED25519_BASEPOINT_POINT; + let a = A_SCALAR; + + let table_radix16 = EdwardsBasepointTableRadix16::create(P); + let table_radix32 = EdwardsBasepointTableRadix32::create(P); + let table_radix64 = EdwardsBasepointTableRadix64::create(P); + let table_radix128 = EdwardsBasepointTableRadix128::create(P); + let table_radix256 = EdwardsBasepointTableRadix256::create(P); + + let aP = (ED25519_BASEPOINT_TABLE * &a).compress(); + let aP16 = (&table_radix16 * &a).compress(); + let aP32 = (&table_radix32 * &a).compress(); + let aP64 = (&table_radix64 * &a).compress(); + let aP128 = (&table_radix128 * &a).compress(); + let aP256 = (&table_radix256 * &a).compress(); + + assert_eq!(aP, aP16); + assert_eq!(aP16, aP32); + assert_eq!(aP32, aP64); + assert_eq!(aP64, aP128); + assert_eq!(aP128, aP256); + } + + /// Check unreduced scalar multiplication by the basepoint tables is the same no matter what + /// radix the table is. + #[cfg(feature = "precomputed-tables")] + #[test] + fn basepoint_tables_unreduced_scalar() { + let P = &constants::ED25519_BASEPOINT_POINT; + let a = crate::scalar::test::LARGEST_UNREDUCED_SCALAR; + + let table_radix16 = EdwardsBasepointTableRadix16::create(P); + let table_radix32 = EdwardsBasepointTableRadix32::create(P); + let table_radix64 = EdwardsBasepointTableRadix64::create(P); + let table_radix128 = EdwardsBasepointTableRadix128::create(P); + let table_radix256 = EdwardsBasepointTableRadix256::create(P); + + let aP = (ED25519_BASEPOINT_TABLE * &a).compress(); + let aP16 = (&table_radix16 * &a).compress(); + let aP32 = (&table_radix32 * &a).compress(); + let aP64 = (&table_radix64 * &a).compress(); + let aP128 = (&table_radix128 * &a).compress(); + let aP256 = (&table_radix256 * &a).compress(); + + assert_eq!(aP, aP16); + assert_eq!(aP16, aP32); + assert_eq!(aP32, aP64); + assert_eq!(aP64, aP128); + assert_eq!(aP128, aP256); + } + + /// Check that converting to projective and then back to extended round-trips. + #[test] + fn basepoint_projective_extended_round_trip() { + assert_eq!( + constants::ED25519_BASEPOINT_POINT + .as_projective() + .as_extended() + .compress(), + constants::ED25519_BASEPOINT_COMPRESSED + ); + } + + /// Test computing 16*basepoint vs mul_by_pow_2(4) + #[test] + fn basepoint16_vs_mul_by_pow_2_4() { + let bp16 = constants::ED25519_BASEPOINT_POINT.mul_by_pow_2(4); + assert_eq!(bp16.compress(), BASE16_CMPRSSD); + } + + /// Check that mul_base_clamped and mul_clamped agree + #[test] + fn mul_base_clamped() { + let mut csprng = rand_core::OsRng; + + // Make a random curve point in the curve. Give it torsion to make things interesting. + #[cfg(feature = "precomputed-tables")] + let random_point = { + let mut b = [0u8; 32]; + csprng.fill_bytes(&mut b); + EdwardsPoint::mul_base_clamped(b) + constants::EIGHT_TORSION[1] + }; + // Make a basepoint table from the random point. We'll use this with mul_base_clamped + #[cfg(feature = "precomputed-tables")] + let random_table = EdwardsBasepointTableRadix256::create(&random_point); + + // Now test scalar mult. agreement on the default basepoint as well as random_point + + // Test that mul_base_clamped and mul_clamped agree on a large integer. Even after + // clamping, this integer is not reduced mod l. + let a_bytes = [0xff; 32]; + assert_eq!( + EdwardsPoint::mul_base_clamped(a_bytes), + constants::ED25519_BASEPOINT_POINT.mul_clamped(a_bytes) + ); + #[cfg(feature = "precomputed-tables")] + assert_eq!( + random_table.mul_base_clamped(a_bytes), + random_point.mul_clamped(a_bytes) + ); + + // Test agreement on random integers + for _ in 0..100 { + // This will be reduced mod l with probability l / 2^256 ≈ 6.25% + let mut a_bytes = [0u8; 32]; + csprng.fill_bytes(&mut a_bytes); + + assert_eq!( + EdwardsPoint::mul_base_clamped(a_bytes), + constants::ED25519_BASEPOINT_POINT.mul_clamped(a_bytes) + ); + #[cfg(feature = "precomputed-tables")] + assert_eq!( + random_table.mul_base_clamped(a_bytes), + random_point.mul_clamped(a_bytes) + ); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn impl_sum() { + // Test that sum works for non-empty iterators + let BASE = constants::ED25519_BASEPOINT_POINT; + + let s1 = Scalar::from(999u64); + let P1 = BASE * s1; + + let s2 = Scalar::from(333u64); + let P2 = BASE * s2; + + let vec = vec![P1, P2]; + let sum: EdwardsPoint = vec.iter().sum(); + + assert_eq!(sum, P1 + P2); + + // Test that sum works for the empty iterator + let empty_vector: Vec = vec![]; + let sum: EdwardsPoint = empty_vector.iter().sum(); + + assert_eq!(sum, EdwardsPoint::identity()); + + // Test that sum works on owning iterators + let s = Scalar::from(2u64); + let mapped = vec.iter().map(|x| x * s); + let sum: EdwardsPoint = mapped.sum(); + + assert_eq!(sum, P1 * s + P2 * s); + } + + /// Test that the conditional assignment trait works for AffineNielsPoints. + #[test] + fn conditional_assign_for_affine_niels_point() { + let id = AffineNielsPoint::identity(); + let mut p1 = AffineNielsPoint::identity(); + let bp = constants::ED25519_BASEPOINT_POINT.as_affine_niels(); + + p1.conditional_assign(&bp, Choice::from(0)); + assert_eq!(p1, id); + p1.conditional_assign(&bp, Choice::from(1)); + assert_eq!(p1, bp); + } + + #[test] + fn is_small_order() { + // The basepoint has large prime order + assert!(!constants::ED25519_BASEPOINT_POINT.is_small_order()); + // constants::EIGHT_TORSION has all points of small order. + for torsion_point in &constants::EIGHT_TORSION { + assert!(torsion_point.is_small_order()); + } + } + + #[test] + fn compressed_identity() { + assert_eq!( + EdwardsPoint::identity().compress(), + CompressedEdwardsY::identity() + ); + } + + #[test] + fn is_identity() { + assert!(EdwardsPoint::identity().is_identity()); + assert!(!constants::ED25519_BASEPOINT_POINT.is_identity()); + } + + /// Rust's debug builds have overflow and underflow trapping, + /// and enable `debug_assert!()`. This performs many scalar + /// multiplications to attempt to trigger possible overflows etc. + /// + /// For instance, the `u64` `Mul` implementation for + /// `FieldElements` requires the input `Limb`s to be bounded by + /// 2^54, but we cannot enforce this dynamically at runtime, or + /// statically at compile time (until Rust gets type-level + /// integers, at which point we can encode "bits of headroom" into + /// the type system and prove correctness). + #[test] + fn monte_carlo_overflow_underflow_debug_assert_test() { + let mut P = constants::ED25519_BASEPOINT_POINT; + // N.B. each scalar_mul does 1407 field mults, 1024 field squarings, + // so this does ~ 1M of each operation. + for _ in 0..1_000 { + P *= &A_SCALAR; + } + } + + #[test] + fn scalarmult_extended_point_works_both_ways() { + let G: EdwardsPoint = constants::ED25519_BASEPOINT_POINT; + let s: Scalar = A_SCALAR; + + let P1 = G * s; + let P2 = s * G; + + assert!(P1.compress().to_bytes() == P2.compress().to_bytes()); + } + + // A single iteration of a consistency check for MSM. + #[cfg(feature = "alloc")] + fn multiscalar_consistency_iter(n: usize) { + let mut rng = rand::thread_rng(); + + // Construct random coefficients x0, ..., x_{n-1}, + // followed by some extra hardcoded ones. + let xs = (0..n).map(|_| Scalar::random(&mut rng)).collect::>(); + let check = xs.iter().map(|xi| xi * xi).sum::(); + + // Construct points G_i = x_i * B + let Gs = xs.iter().map(EdwardsPoint::mul_base).collect::>(); + + // Compute H1 = (consttime) + let H1 = EdwardsPoint::multiscalar_mul(&xs, &Gs); + // Compute H2 = (vartime) + let H2 = EdwardsPoint::vartime_multiscalar_mul(&xs, &Gs); + // Compute H3 = = sum(xi^2) * B + let H3 = EdwardsPoint::mul_base(&check); + + assert_eq!(H1, H3); + assert_eq!(H2, H3); + } + + // Use different multiscalar sizes to hit different internal + // parameters. + + #[test] + #[cfg(feature = "alloc")] + fn multiscalar_consistency_n_100() { + let iters = 50; + for _ in 0..iters { + multiscalar_consistency_iter(100); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn multiscalar_consistency_n_250() { + let iters = 50; + for _ in 0..iters { + multiscalar_consistency_iter(250); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn multiscalar_consistency_n_500() { + let iters = 50; + for _ in 0..iters { + multiscalar_consistency_iter(500); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn multiscalar_consistency_n_1000() { + let iters = 50; + for _ in 0..iters { + multiscalar_consistency_iter(1000); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn vartime_precomputed_vs_nonprecomputed_multiscalar() { + let mut rng = rand::thread_rng(); + + let static_scalars = (0..128) + .map(|_| Scalar::random(&mut rng)) + .collect::>(); + + let dynamic_scalars = (0..128) + .map(|_| Scalar::random(&mut rng)) + .collect::>(); + + let check_scalar: Scalar = static_scalars + .iter() + .chain(dynamic_scalars.iter()) + .map(|s| s * s) + .sum(); + + let static_points = static_scalars + .iter() + .map(EdwardsPoint::mul_base) + .collect::>(); + let dynamic_points = dynamic_scalars + .iter() + .map(EdwardsPoint::mul_base) + .collect::>(); + + let precomputation = VartimeEdwardsPrecomputation::new(static_points.iter()); + + let P = precomputation.vartime_mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ); + + use crate::traits::VartimeMultiscalarMul; + let Q = EdwardsPoint::vartime_multiscalar_mul( + static_scalars.iter().chain(dynamic_scalars.iter()), + static_points.iter().chain(dynamic_points.iter()), + ); + + let R = EdwardsPoint::mul_base(&check_scalar); + + assert_eq!(P.compress(), R.compress()); + assert_eq!(Q.compress(), R.compress()); + } + + mod vartime { + use super::super::*; + use super::{A_SCALAR, A_TIMES_BASEPOINT, B_SCALAR, DOUBLE_SCALAR_MULT_RESULT}; + + /// Test double_scalar_mul_vartime vs ed25519.py + #[test] + fn double_scalar_mul_basepoint_vs_ed25519py() { + let A = A_TIMES_BASEPOINT.decompress().unwrap(); + let result = + EdwardsPoint::vartime_double_scalar_mul_basepoint(&A_SCALAR, &A, &B_SCALAR); + assert_eq!(result.compress(), DOUBLE_SCALAR_MULT_RESULT); + } + + #[test] + #[cfg(feature = "alloc")] + fn multiscalar_mul_vs_ed25519py() { + let A = A_TIMES_BASEPOINT.decompress().unwrap(); + let result = EdwardsPoint::vartime_multiscalar_mul( + &[A_SCALAR, B_SCALAR], + &[A, constants::ED25519_BASEPOINT_POINT], + ); + assert_eq!(result.compress(), DOUBLE_SCALAR_MULT_RESULT); + } + + #[test] + #[cfg(feature = "alloc")] + fn multiscalar_mul_vartime_vs_consttime() { + let A = A_TIMES_BASEPOINT.decompress().unwrap(); + let result_vartime = EdwardsPoint::vartime_multiscalar_mul( + &[A_SCALAR, B_SCALAR], + &[A, constants::ED25519_BASEPOINT_POINT], + ); + let result_consttime = EdwardsPoint::multiscalar_mul( + &[A_SCALAR, B_SCALAR], + &[A, constants::ED25519_BASEPOINT_POINT], + ); + + assert_eq!(result_vartime.compress(), result_consttime.compress()); + } + } + + #[test] + #[cfg(feature = "serde")] + fn serde_bincode_basepoint_roundtrip() { + use bincode; + + let encoded = bincode::serialize(&constants::ED25519_BASEPOINT_POINT).unwrap(); + let enc_compressed = bincode::serialize(&constants::ED25519_BASEPOINT_COMPRESSED).unwrap(); + assert_eq!(encoded, enc_compressed); + + // Check that the encoding is 32 bytes exactly + assert_eq!(encoded.len(), 32); + + let dec_uncompressed: EdwardsPoint = bincode::deserialize(&encoded).unwrap(); + let dec_compressed: CompressedEdwardsY = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(dec_uncompressed, constants::ED25519_BASEPOINT_POINT); + assert_eq!(dec_compressed, constants::ED25519_BASEPOINT_COMPRESSED); + + // Check that the encoding itself matches the usual one + let raw_bytes = constants::ED25519_BASEPOINT_COMPRESSED.as_bytes(); + let bp: EdwardsPoint = bincode::deserialize(raw_bytes).unwrap(); + assert_eq!(bp, constants::ED25519_BASEPOINT_POINT); + } + + //////////////////////////////////////////////////////////// + // Signal tests from // + // https://github.com/signalapp/libsignal-protocol-c/ // + //////////////////////////////////////////////////////////// + + #[cfg(all(feature = "alloc", feature = "digest"))] + fn test_vectors() -> Vec> { + vec![ + vec![ + "214f306e1576f5a7577636fe303ca2c625b533319f52442b22a9fa3b7ede809f", + "c95becf0f93595174633b9d4d6bbbeb88e16fa257176f877ce426e1424626052", + ], + vec![ + "2eb10d432702ea7f79207da95d206f82d5a3b374f5f89f17a199531f78d3bea6", + "d8f8b508edffbb8b6dab0f602f86a9dd759f800fe18f782fdcac47c234883e7f", + ], + vec![ + "c85165952490dc1839cb69012a3d9f2cc4b02343613263ab93a26dc89fd58267", + "43cbe8685fd3c90665b91835debb89ff1477f906f5170f38a192f6a199556537", + ], + vec![ + "26e7fc4a78d863b1a4ccb2ce0951fbcd021e106350730ee4157bacb4502e1b76", + "b6fc3d738c2c40719479b2f23818180cdafa72a14254d4016bbed8f0b788a835", + ], + vec![ + "1618c08ef0233f94f0f163f9435ec7457cd7a8cd4bb6b160315d15818c30f7a2", + "da0b703593b29dbcd28ebd6e7baea17b6f61971f3641cae774f6a5137a12294c", + ], + vec![ + "a744d582b3a34d14d311b7629da06d003045ae77cebceeb4e0e72734d63bd07d", + "fad25a5ea15d4541258af8785acaf697a886c1b872c793790e60a6837b1adbc0", + ], + vec![ + "f06fc939bc10551a0fd415aebf107ef0b9c4ee1ef9a164157bdd089127782617", + "785b2a6a00a5579cc9da1ff997ce8339b6f9fb46c6f10cf7a12ff2986341a6e0", + ], + // Non Least-Square-Root representative values. (i.e. representative > 2^254-10 ) + vec![ + "84cbe9accdd32b46f4a8ef51c85fd39d028711f77fb00e204a613fc235fd68b9", + "93c73e0289afd1d1fc9e4e78a505d5d1b2642fbdf91a1eff7d281930654b1453", + ], + vec![ + "48b73039db6fcdcb6030c4a38e8be80b6390d8ae46890e77e623f87254ef149c", + "ca11b25acbc80566603eabeb9364ebd50e0306424c61049e1ce9385d9f349966", + ], + vec![ + "80a6ff33494c471c5eff7efb9febfbcf30a946fe6535b3451cda79f2154a7095", + "57ac03913309b3f8cd3c3d4c49d878bb21f4d97dc74a1eaccbe5c601f7f06f47", + ], + ] + } + + #[test] + #[allow(deprecated)] + #[cfg(all(feature = "alloc", feature = "digest"))] + fn elligator_signal_test_vectors() { + for (n, vector) in test_vectors().iter().enumerate() { + let input = hex::decode(vector[0]).expect("failed to decode hex input"); + let output = hex::decode(vector[1]).expect("failed to decode hex output"); + + let point = EdwardsPoint::nonspec_map_to_curve::(&input); + assert_eq!( + hex::encode(point.compress().to_bytes()), + hex::encode(&output[..]), + "signal map_to_curve failed for test {n}" + ); + } + } +} diff --git a/curve25519-elligator2/src/elligator2.rs b/curve25519-elligator2/src/elligator2.rs new file mode 100644 index 00000000..f1704838 --- /dev/null +++ b/curve25519-elligator2/src/elligator2.rs @@ -0,0 +1,786 @@ +// -*- mode: rust; -*- + +//! Functions mapping curve points to representative (random) values +//! and back again. +//! +//! ## Usage +//! +//! ```rust ignore +//! use rand::RngCore; +//! use curve25519_elligator2::{RFC9380, MapToPointVariant}; +//! +//! type A = RFC9380; +//! +//! let mut rng = rand::thread_rng(); +//! let mut privkey = [0_u8;32]; +//! rng.fill_bytes(&mut privkey); +//! let tweak = rng.next_u32() as u8; +//! +//! let public_key = A::mul_base_clamped(privkey); +//! // only about half of points are representable, so this will fail ~50% of the time. +//! let representative = A::to_representative(&privkey, tweak) +//! .expect("non representable point :(" ); +//! +//! // The representative gets distributed in place of the public key, +//! // it can then be converted back into the curve25519 point. +//! let public_key1 = A::from_representative(&representative).unwrap(); +//! +//! # let p = public_key.to_montgomery().to_bytes(); +//! # let p1 = public_key1.to_montgomery().to_bytes(); +//! # assert_eq!(hex::encode(&p), hex::encode(&p1)); +//! ``` +//! +//! The elligator2 transforms can also be applied to [`MontgomeryPoint`] and +//! [`EdwardsPoint`] objects themselves. +//! +//! ```rust +//! use rand::RngCore; +//! use curve25519_elligator2::{RFC9380, Randomized, MapToPointVariant, MontgomeryPoint, EdwardsPoint}; +//! +//! // Montgomery Points can be mapped to and from elligator representatives +//! // using any algorithm variant. +//! let tweak = rand::thread_rng().next_u32() as u8; +//! let mont_point = MontgomeryPoint::default(); // example point known to be representable +//! let r = mont_point.to_representative::(tweak).unwrap(); +//! +//! _ = MontgomeryPoint::from_representative::(&r).unwrap(); +//! +//! // Edwards Points can be transformed as well. +//! let edw_point = EdwardsPoint::default(); // example point known to be representable +//! let r = edw_point.to_representative::(tweak).unwrap(); +//! +//! _ = EdwardsPoint::from_representative::(&r).unwrap(); +//! ``` +//! +//! ### Generating Representable Points. +//! +//! As only about 50% of points are actually representable using elligator2. In +//! order to guarantee that generated points are representable we can just try +//! in a loop as the probability of overall success (given a proper source of +//! randomness) over `N` trials is approximately `P_success = 1 - (0.5)^N`. +//! +//! ```rust +//! use rand::{RngCore, CryptoRng}; +//! use curve25519_elligator2::{MapToPointVariant, Randomized}; +//! +//! type A = Randomized; +//! const RETRY_LIMIT: usize = 64; +//! +//! pub fn key_from_rng(mut csprng: R) -> ([u8;32], u8) { +//! let mut private = [0_u8;32]; +//! csprng.fill_bytes(&mut private); +//! +//! // The tweak only needs generated once as it doesn't affect the overall +//! // validity of the elligator2 representative. +//! let tweak = csprng.next_u32() as u8; +//! +//! let mut repres: Option<[u8; 32]> = +//! A::to_representative(&private, tweak).into(); +//! +//! for _ in 0..RETRY_LIMIT { +//! if repres.is_some() { +//! return (private, tweak) +//! } +//! csprng.fill_bytes(&mut private); +//! repres = A::to_representative(&private, tweak).into(); +//! } +//! +//! panic!("failed to generate representable secret, bad RNG provided"); +//! } +//! +//! let mut rng = rand::thread_rng(); +//! let k = key_from_rng(&mut rng); +//! ``` +//! +//! ### Which variant is right for me? +//! +//! As the variants are not equivalent and do not generate the same 1) public key +//! given a private key (`mul_base_clamped`) 2) representative given a private +//! key (`to_representative`), the variant you choose will depend on your use case. +//! +//! The major difference in use case depends on +//! whether you need to convert public keys to randomized representatives, and +//! those representatives need to be **indistinguishable from uniform random**. +//! If this is the case, use [`Randomized`]. +//! If this does not describe your use case, for example, you are interested in +//! using this implementation for something like `Hash2Curve`, you should instead +//! use [`RFC9380`]. +//! +//! +//! If you are unsure, you will likely want to use the [`RFC9380`] variant. +//! +//! ## Security +//! +//! As with the backing curve25519-dalek crate, all operations are implemented +//! using constant-time logic (no secret-dependent branches, +//! no secret-dependent memory accesses), unless specifically marked as being +//! variable-time code. +//! +//! The [`Randomized`] variant provides a stronger guarantee of being +//! indistinguishable from uniform randomness, both through statistical analysis +//! and computational transformations. This is comes at the cost of compatability +//! with the `Hash2Curve` RFC as the algorithms defined by RFC 9380 and implemented +//! in the [`RFC9380`] variant can be computationally transformed such that they +//! are distinguishable from unifrom random points of the field. +//! + +use crate::constants::{MONTGOMERY_A, MONTGOMERY_A_NEG, SQRT_M1}; +use crate::field::FieldElement; +use crate::montgomery::MontgomeryPoint; +use crate::EdwardsPoint; + +use cfg_if::cfg_if; +use subtle::{ + Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, + CtOption, +}; + +/// bitmask for a single byte when clearing the high order two bits of a representative +pub(crate) const MASK_UNSET_BYTE: u8 = 0x3f; +/// bitmask for a single byte when setting the high order two bits of a representative +pub(crate) const MASK_SET_BYTE: u8 = 0xc0; + +/// (p - 1) / 2 = 2^254 - 10 +pub(crate) const DIVIDE_MINUS_P_1_2_BYTES: [u8; 32] = [ + 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, +]; + +/// Common interface for the different ways to compute the elligator2 forward +/// and reverse transformations. +pub trait MapToPointVariant { + /// Takes a public representative and returns an Edwards curve field element + /// if one exists. + fn from_representative(representative: &[u8; 32]) -> CtOption; + + /// Takes a private key value and gets a byte representation of the public representative. + /// + /// The `tweak` parameter is used to adjust the computed representative making + /// it computationally indistinguishable from uniform random. If this property + /// is not required then the provided tweak value does not matter. + fn to_representative(point: &[u8; 32], tweak: u8) -> CtOption<[u8; 32]>; + + /// Provides direct access to the scalar base multiplication that will produce + /// a public key point from a private key point. + fn mul_base_clamped(bytes: [u8; 32]) -> EdwardsPoint { + EdwardsPoint::mul_base_clamped(bytes) + } +} + +/// Converts between a point on elliptic curve E (Curve25519) and an element of +/// the finite field F over which E is defined. See section 6.7.1 of +/// [RFC 9380 specification](https://datatracker.ietf.org/doc/rfc9380/). +/// +/// We add randomness to the top two bits of the generated representative as the +/// generated values always 0 in those two bits. Similarly we clear the top two +/// bits of a given representative FieldElement before mapping it to the curve. +pub struct RFC9380; + +impl MapToPointVariant for RFC9380 { + fn from_representative(representative: &[u8; 32]) -> CtOption { + let mut r = *representative; + r[31] &= MASK_UNSET_BYTE; + let representative = FieldElement::from_bytes(&r); + let (x, y) = map_fe_to_edwards(&representative); + let point = EdwardsPoint { + X: x, + Y: y, + Z: FieldElement::ONE, + T: &x * &y, + }; + CtOption::new(point, Choice::from(1)) + } + + fn to_representative(point: &[u8; 32], tweak: u8) -> CtOption<[u8; 32]> { + let pubkey = EdwardsPoint::mul_base_clamped(*point).to_montgomery(); + let v_in_sqrt = v_in_sqrt(point); + let p: Option<[u8; 32]> = point_to_representative(&pubkey, v_in_sqrt.into()).into(); + match p { + None => CtOption::new([0u8; 32], Choice::from(0)), + Some(mut a) => { + a[31] |= MASK_SET_BYTE & tweak; + CtOption::new(a, Choice::from(1)) + } + } + } +} + +/// Converts between a point on elliptic curve E (Curve25519) and an element of +/// the finite field F over which E is defined. Ensures that generated field +/// elements are indistinguishable from uniform random at the cost of compatability +/// with RFC 9380. +/// +/// Differs from [`RFC9380`] in the implementation of the `to_representative` function +/// as RFC9380 misses a computational distinguisher that would allow an attacker to +/// distinguish the representative from random bytes. +pub struct Randomized; + +impl MapToPointVariant for Randomized { + fn from_representative(representative: &[u8; 32]) -> CtOption { + RFC9380::from_representative(representative) + } + + fn to_representative(point: &[u8; 32], tweak: u8) -> CtOption<[u8; 32]> { + let u = EdwardsPoint::mul_base_clamped_dirty(*point); + representative_from_pubkey(&u, tweak) + } + + fn mul_base_clamped(bytes: [u8; 32]) -> EdwardsPoint { + EdwardsPoint::mul_base_clamped_dirty(bytes) + } +} + +#[cfg(feature = "digest")] +/// Converts between a point on elliptic curve E (Curve25519) and an element of +/// the finite field F over which E is defined. Supports older implementations +/// with a common implementation bug (Signal, Kleshni-C). +/// +/// In contrast to the [`RFC9380`] variant, `Legacy` does NOT assume that input values are always +/// going to be the least-square-root representation of the field element. +/// This is divergent from the specifications for both elligator2 and RFC 9380, +/// however, some older implementations miss this detail. This allows us to be +/// compatible with those alternate implementations if necessary, since the +/// resulting point will be different for inputs with either of the +/// high-order two bits set. The kleshni C and Signal implementations are examples +/// of libraries that don't always use the least square root. +/// +/// In general this mode should NOT be used unless there is a very specific +/// reason to do so. +/// +// We return the LSR for to_representative values. This is here purely for testing +// compatability and ensuring that we understand the subtle differences that can +// influence proper implementation. +pub struct Legacy; + +#[cfg(feature = "digest")] +impl MapToPointVariant for Legacy { + fn from_representative(representative: &[u8; 32]) -> CtOption { + let representative = FieldElement::from_bytes(representative); + let (x, y) = map_fe_to_edwards(&representative); + let point = EdwardsPoint { + X: x, + Y: y, + Z: FieldElement::ONE, + T: &x * &y, + }; + CtOption::new(point, Choice::from(1)) + } + + fn to_representative(point: &[u8; 32], _tweak: u8) -> CtOption<[u8; 32]> { + let pubkey = EdwardsPoint::mul_base_clamped(*point); + let v_in_sqrt = v_in_sqrt_pubkey_edwards(&pubkey); + point_to_representative(&MontgomeryPoint(*point), v_in_sqrt.into()) + } +} + +// =========================================================================== +// Montgomery and Edwards Interfaces +// =========================================================================== + +impl MontgomeryPoint { + #[cfg(feature = "elligator2")] + /// Perform the Elligator2 mapping to a [`MontgomeryPoint`]. + /// + /// Calculates a point on elliptic curve E (Curve25519) from an element of + /// the finite field F over which E is defined. See section 6.7.1 of the + /// RFC. It is assumed that input values are always going to be the + /// least-square-root representation of the field element in allignment + /// with both the elligator2 specification and RFC9380. + /// + /// The input r and output P are elements of the field F. Note that + /// the output P is a point on the Montgomery curve and as such it's byte + /// representation is distinguishable from uniform random. + /// + /// Input: + /// * r -> an element of field F. + /// + /// Output: + /// * P - a point on the Montgomery elliptic curve. + /// + /// See + pub fn map_to_point(r: &[u8; 32]) -> MontgomeryPoint { + let mut clamped = *r; + clamped[31] &= MASK_UNSET_BYTE; + let r_0 = FieldElement::from_bytes(&clamped); + let (p, _) = map_fe_to_montgomery(&r_0); + MontgomeryPoint(p.as_bytes()) + } + + /// Maps a representative to a curve point. + /// + /// This function is the inverse of `to_representative`. + pub fn from_representative(representative: &[u8; 32]) -> Option { + let b: Option = T::from_representative(representative).into(); + b.map(|x| x.to_montgomery()) + } + + /// Mapts a point on the curve to a representative. + pub fn to_representative(&self, tweak: u8) -> Option<[u8; 32]> { + T::to_representative(&self.0, tweak).into() + } +} + +impl EdwardsPoint { + #[cfg(feature = "elligator2")] + /// Perform the Elligator2 mapping to an [`EdwardsPoint`]. + /// + /// Calculates a point on elliptic curve E (Curve25519) from an element of + /// the finite field F over which E is defined. See section 6.7.1 of the + /// RFC. + /// + /// The input r and output P are elements of the field F. Note that + /// the output P is a point on the edwards curve and as such it's byte + /// representation is distinguishable from uniform random. + /// + /// Input: + /// * r -> an element of field F. + /// + /// Output: + /// * P - a point on the Edwards elliptic curve. + /// + /// See + pub fn map_to_point(r: &[u8; 32]) -> EdwardsPoint { + let mut clamped = *r; + clamped[31] &= MASK_UNSET_BYTE; + let r_0 = FieldElement::from_bytes(&clamped); + let (x, y) = map_fe_to_edwards(&r_0); + Self::from_xy(&x, &y) + } + + #[cfg(feature = "elligator2")] + fn from_xy(x: &FieldElement, y: &FieldElement) -> EdwardsPoint { + let z = FieldElement::ONE; + let t = x * y; + + EdwardsPoint { + X: *x, + Y: *y, + Z: z, + T: t, + } + } + + /// Maps a representative to a curve point. + /// + /// This function is the inverse of `to_representative`. + pub fn from_representative(representative: &[u8; 32]) -> Option { + T::from_representative(representative).into() + } + + /// Mapts a point on the curve to a representative. + pub fn to_representative(&self, tweak: u8) -> Option<[u8; 32]> { + T::to_representative(&self.to_montgomery().0, tweak).into() + } +} + +// =========================================================================== +// Randomized implementation +// =========================================================================== + +/// Gets a public representative for a key pair using the private key (RFC 9380). +/// +/// The `tweak` parameter is used to adjust the computed representative making +/// it computationally indistinguishable from uniform random. If this property +/// is not required then the provided tweak value does not matter. +/// +/// The tweak allows us to overcome three limitations: +/// - Representatives are not always canonical. +/// - Bit 255 (the most significant bit) was always zero. +/// - Only points from the large prime-order subgroup are represented. +/// +/// In order for the returned representative to be canonical a tweak to the +/// high order two bits must be applied. +/// ```txt +/// [An adversary could] observe a representative, interpret it as a field +/// element, square it, then take the square root using the same +/// non-canonical square root algorithm. With representatives produced by +/// an affected version of [the elligator2 implementation], the output of +/// the square-then-root operation would always match the input. With +/// random strings, the output would match only half the time. +/// ``` +/// +/// For a more in-depth explanation see: +/// +/// +pub fn representative_from_privkey(privkey: &[u8; 32], tweak: u8) -> Option<[u8; 32]> { + RFC9380::to_representative(privkey, tweak).into() +} + +cfg_if! { + if #[cfg(curve25519_dalek_bits = "64")] { + /// Low order point Edwards x-coordinate `sqrt((sqrt(d + 1) + 1) / d)`. + const LOW_ORDER_POINT_X: FieldElement = FieldElement::from_limbs([ + 0x00014646c545d14a, + 0x0006027cbc471bd4, + 0x0003792aed7064f1, + 0x0005147499cc991c, + 0x0001fd5b9a006394, + ]); + /// Low order point Edwards y-coordinate `-lop_x * sqrtm1` + const LOW_ORDER_POINT_Y: FieldElement = FieldElement::from_limbs([ + 0x0007b2c28f95e826, + 0x0006513e9868b604, + 0x0006b37f57c263bf, + 0x0004589c99e36982, + 0x00005fc536d88023, + ]); + const FE_MINUS_TWO: FieldElement = FieldElement::from_limbs([ + 2251799813685227, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, + ]); + } + else if #[cfg(curve25519_dalek_bits = "32")] { + const LOW_ORDER_POINT_X: FieldElement = FieldElement::from_limbs([ + 0x0145d14a, 0x005191b1, 0x00471bd4, 0x01809f2f, 0x017064f1, + 0x00de4abb, 0x01cc991c, 0x01451d26, 0x02006394, 0x007f56e6 + ]); + const LOW_ORDER_POINT_Y: FieldElement = FieldElement::from_limbs([ + 0x0395e826, 0x01ecb0a3, 0x0068b604, 0x01944fa6, 0x03c263bf, + 0x01acdfd5, 0x01e36982, 0x01162726, 0x02d88023, 0x0017f14d + ]); + const FE_MINUS_TWO: FieldElement = FieldElement::from_limbs([ + 0x03ffffeb, 0x01ffffff, 0x03ffffff, 0x01ffffff, 0x03ffffff, + 0x01ffffff, 0x03ffffff, 0x01ffffff, 0x03ffffff, 0x01ffffff + ]); + } +} + +#[allow(clippy::identity_op)] +fn select_low_order_point(a: &FieldElement, b: &FieldElement, cofactor: u8) -> FieldElement { + // bit 0 + let c0 = !Choice::from((cofactor >> 1) & 1); + let out = FieldElement::conditional_select(b, &FieldElement::ZERO, c0); + + // bit 1 + let c1 = !Choice::from((cofactor >> 0) & 1); + let mut out = FieldElement::conditional_select(a, &out, c1); + + // bit 2 + let c2 = Choice::from((cofactor >> 2) & 1); + out.conditional_negate(c2); + out +} + +#[cfg(feature = "elligator2")] +impl EdwardsPoint { + /// Multiply the basepoint by `clamp_integer(bytes)`. For a description of clamping, see + /// [`clamp_integer`]. This variant integrates a low order point into the resulting + /// value in order to prevent a computational distinguisher attack that would allow + /// an adversary to check if representatives are always points or order L when multiplied + /// with the base point. + /// + /// > A scalar multiplication with the base point, even one that does not clamp the scalar, will always yield a point of order L. That is, for all s: `L × (s × B) = zero`. + /// > A random Elligator2 representative however will only map to a point of order L 12.5% of the time. + /// + /// [`clamp_integer`]: crate::scalar::clamp_integer + pub fn mul_base_clamped_dirty(privkey: [u8; 32]) -> Self { + let lop_x = select_low_order_point(&LOW_ORDER_POINT_X, &SQRT_M1, privkey[0]); + let (cofactor, _) = privkey[0].overflowing_add(2); + let lop_y = select_low_order_point(&LOW_ORDER_POINT_Y, &FieldElement::ONE, cofactor); + let lop_t = &lop_x * &lop_y; + + let low_order_point = EdwardsPoint { + X: lop_x, + Y: lop_y, + Z: FieldElement::ONE, + T: lop_t, + }; + + // add the low order point to the public key + EdwardsPoint::mul_base_clamped(privkey) + low_order_point + } +} + +/// Gets the pulic representative using the ['EdwardsPoint'] public key. +fn representative_from_pubkey(pubkey: &EdwardsPoint, tweak: u8) -> CtOption<[u8; 32]> { + let mut t1 = FieldElement::from_bytes(&pubkey.to_montgomery().0); + + // -2 * u * (u + A) + let t2 = &t1 + &MONTGOMERY_A; + let t3 = &(&t1 * &t2) * &FE_MINUS_TWO; + + let (is_sq, mut t3) = FieldElement::sqrt_ratio_i(&FieldElement::ONE, &t3); + + t1 = FieldElement::conditional_select(&t1, &t2, Choice::from(tweak & 1)); + t3 = &t1 * &t3; + t1 = &t3 + &t3; + + let tmp: u8 = t1.as_bytes()[0] & 1; + t3.conditional_negate(Choice::from(tmp)); + + // get representative and pad with bits from the tweak. + let mut representative = t3.as_bytes(); + representative[31] |= tweak & MASK_SET_BYTE; + + CtOption::new(representative, is_sq) +} + +// =========================================================================== +// RFC9380 (and Legacy) implementation +// =========================================================================== + +/// This function is used to map a curve point (i.e. an x25519 public key) +/// to a point that is effectively indistinguishable from random noise. +/// +/// This operation may fail because not all curve points are capable of being +/// hidden. On failure, this function will return None. +/// +/// This implementation is adapted from both: +/// - [kleshni C implementation](https://github.com/Kleshni/Elligator-2) +/// - [agl/ed25519 golang forks](https://gitlab.com/yawning/edwards25519-extra) +/// +/// +/// Note: the output field elements of this function are uniformly +/// distributed among the nonnegative field elements, but only if the +/// input points are also uniformly distributed among all points of +/// the curve. In particular, if the inputs are only selected from +/// members of the prime order group, then the outputs are +/// distinguishable from random. +/// +/// # Inputs +/// +/// * `point`: the \\(u\\)-coordinate of a point on the curve. Not all +/// points map to field elements. +/// +/// * `v_in_sqrt`: true if the \\(v\\)-coordinate of the point is negative. +/// +/// # Returns +/// +/// Either `None`, if the point couldn't be mapped, or `Some(fe)` such +/// that `fe` is nonnegative and `map_to_point(&fe) == point`. +/// +/// [elligator paper](https://elligator.cr.yp.to/elligator-20130828.pdf) +/// [elligator site](https://elligator.org/) +/// +pub(crate) fn point_to_representative( + point: &MontgomeryPoint, + v_in_sqrt: bool, +) -> CtOption<[u8; 32]> { + let divide_minus_p_1_2 = FieldElement::from_bytes(&DIVIDE_MINUS_P_1_2_BYTES); + + // a := point + let a = &FieldElement::from_bytes(&point.0); + let a_neg = -a; + + let is_encodable = is_encodable(a); + + // Calculate r1 = sqrt(-a/(2*(a+A))) + let (_r1_sqrt, r1) = FieldElement::sqrt_ratio_i( + &a_neg, + &(&(a + &MONTGOMERY_A) * &(&FieldElement::ONE + &FieldElement::ONE)), + ); + + // Calculate r0 = sqrt(-(a+A)/(2a)) + let (_r0_sqrt, r0) = FieldElement::sqrt_ratio_i( + &(&a_neg - &MONTGOMERY_A), + &(a * &(&FieldElement::ONE + &FieldElement::ONE)), + ); + + // if v_in_sqrt root=r0 otherwise r1 + let mut b = FieldElement::conditional_select(&r1, &r0, Choice::from(v_in_sqrt as u8)); + + // If root > (p - 1) / 2, root := -root + let negate = divide_minus_p_1_2.ct_gt(&b); + FieldElement::conditional_negate(&mut b, negate); + + CtOption::new(b.as_bytes(), is_encodable) +} + +/// Determines whether a point is encodable as a representative. Approximately +/// 50% of points are not encodable. +#[inline] +fn is_encodable(u: &FieldElement) -> Choice { + let b0 = u + &MONTGOMERY_A; + let b1 = &(&(&b0.square().square() * &b0.square()) * &b0) * u; // b1 = u * (u + A)^7 + let c = b1.pow_p58(); + + let b2 = &(&b0.square().square().square() * &b0.square().square()) * &b0.square(); // (u + A)^14 + let mut chi = &(&c.square().square() * &u.square()) * &b2; // chi = -c^4 * u^2 * (u + A)^14 + chi = -χ + + let chi_bytes = chi.as_bytes(); + + // chi[1] is either 0 or 0xff + chi_bytes[1].ct_eq(&0_u8) +} + +/// `high_y` - Montgomery points can have have two different values for a +/// single X coordinate (based on sign). The Kleshni implementation determines +/// which sign value to use with this function. +/// +/// If the point is 0, this should be ignored. +#[inline] +pub(crate) fn high_y(d: &FieldElement) -> Choice { + let d_sq = &d.square(); + let au = &MONTGOMERY_A * d; + + let inner = &(d_sq + &au) + &FieldElement::ONE; + let eps = d * &inner; /* eps = d^3 + Ad^2 + d */ + + let (eps_is_sq, _) = FieldElement::sqrt_ratio_i(&eps, &FieldElement::ONE); + + eps_is_sq +} + +/// Determines if `V <= (p - 1)/2` for a Scalar (e.g an x25519 private key) and +/// returns a [`Choice`] indicating the result. +/// +/// Note: When using the elligator2 transformations on x25519 keypairs this +/// requires the use of the clamped scalar_base_mult of the private key to +/// get the edwards representation of the public key, otherwise we need to know +/// the correct sign value for the edwards conversion or the derivation of +/// the `V` value will be broken. As noted in [`EdwardsPoint::to_montgomery`], +/// the sign information about the X coordinate is lost on conversion so we +/// have to use the edwards point derived from the private key to guarantee the +/// correct value here. +/// +/// Alternatively you can keep track of the public key and sign bit manually +/// and construct an EdwardsPoint for which [`v_in_sqrt_pubkey_edwards`] will +/// give you the same result. +/// +// As an interface, using the private key should work just fine. This allows +// us to match the existing [`PublicKey`] generation interface for the +// [`PublicRepresentative`] in the [`x25519_dalek`] crate. AFAIK there is no +// need for anyone with only the public key to be able to generate the +// representative. +pub(crate) fn v_in_sqrt(key_input: &[u8; 32]) -> Choice { + let mut masked_pk = *key_input; + masked_pk[0] &= 0xf8; + masked_pk[31] &= 0x7f; + masked_pk[31] |= 0x40; + + let pubkey = EdwardsPoint::mul_base_clamped(masked_pk); + v_in_sqrt_pubkey_edwards(&pubkey) +} + +/// Determines if `V <= (p - 1)/2` for an EdwardsPoint (e.g an x25519 public key) +/// and returns a [`Choice`] indicating the result. +pub(crate) fn v_in_sqrt_pubkey_edwards(pubkey: &EdwardsPoint) -> Choice { + let divide_minus_p_1_2 = FieldElement::from_bytes(&DIVIDE_MINUS_P_1_2_BYTES); + + // sqrtMinusAPlus2 is sqrt(-(486662+2)) + let (_, sqrt_minus_a_plus_2) = FieldElement::sqrt_ratio_i( + &(&MONTGOMERY_A_NEG - &(&FieldElement::ONE + &FieldElement::ONE)), + &FieldElement::ONE, + ); + + // inv1 = 1/((A.Z - A.y) * A.X) + let inv1 = (&(&pubkey.Z - &pubkey.Y) * &pubkey.X).invert(); + + // t0 = A.Y + A.Z + let t0 = &pubkey.Y + &pubkey.Z; + + // v = t0 * inv1 * A.Z * sqrtMinusAPlus2 + let v = &(&t0 * &inv1) * &(&pubkey.Z * &sqrt_minus_a_plus_2); + + // is v <= (q-1)/2 ? + divide_minus_p_1_2.ct_gt(&v) +} + +// ============================================================================ +// ============================================================================ + +fn map_to_curve_parts( + r: &FieldElement, +) -> (FieldElement, FieldElement, FieldElement, FieldElement) { + let zero = FieldElement::ZERO; + let one = FieldElement::ONE; + let minus_one = -&FieldElement::ONE; + + // Exceptional case 2u^2 == -1 + let mut tv1 = r.square2(); + tv1.conditional_assign(&zero, tv1.ct_eq(&minus_one)); + + let d_1 = &one + &tv1; /* 1 + 2u^2 */ + let d = &MONTGOMERY_A_NEG * &(d_1.invert()); /* d = -A/(1+2u^2) */ + + let inner = &(&d.square() + &(&d * &MONTGOMERY_A)) + &one; + let gx1 = &d * &inner; /* gx1 = d^3 + Ad^2 + d */ + let gx2 = &gx1 * &tv1; + + let eps_is_sq = high_y(&d); + + // complete X + /* A_temp = 0, or A if nonsquare*/ + let a_temp = FieldElement::conditional_select(&MONTGOMERY_A, &zero, eps_is_sq); + let mut x = &d + &a_temp; /* d, or d+A if nonsquare */ + x.conditional_negate(!eps_is_sq); /* d, or -d-A if nonsquare */ + + // complete Y + let y2 = FieldElement::conditional_select(&gx2, &gx1, eps_is_sq); + let (_, mut y) = FieldElement::sqrt_ratio_i(&y2, &one); + y.conditional_negate(eps_is_sq ^ y.is_negative()); + + (&x * &d_1, d_1, y, one) +} + +pub(crate) fn map_fe_to_montgomery(r: &FieldElement) -> (FieldElement, FieldElement) { + let (xmn, xmd, y, _) = map_to_curve_parts(r); + (&xmn * &(xmd.invert()), y) +} + +pub(crate) fn map_fe_to_edwards(r: &FieldElement) -> (FieldElement, FieldElement) { + // 1. (xMn, xMd, yMn, yMd) = map_to_curve_elligator2_curve25519(u) + let (xmn, xmd, ymn, ymd) = map_to_curve_parts(r); + // c1 = sqrt(-486664) + // this cannot fail as it computes a constant + let c1 = &(&MONTGOMERY_A_NEG - &FieldElement::ONE) - &FieldElement::ONE; + let (_, c1) = FieldElement::sqrt_ratio_i(&c1, &FieldElement::ONE); + + // 2. xn = xMn * yMd + // 3. xn = xn * c1 + let mut xn = &(&xmn * &ymd) * &c1; + + // 4. xd = xMd * yMn # xn / xd = c1 * xM / yM + let mut xd = &xmd * &ymn; + + // 5. yn = xMn - xMd + let mut yn = &xmn - &xmd; + // 6. yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d) + let mut yd = &xmn + &xmd; + + // 7. tv1 = xd * yd + // 8. e = tv1 == 0 + let cond = (&xd * &yd).is_zero(); + + // 9. xn = CMOV(xn, 0, e) + // 10. xd = CMOV(xd, 1, e) + // 11. yn = CMOV(yn, 1, e) + // 12. yd = CMOV(yd, 1, e) + xn = FieldElement::conditional_select(&xn, &FieldElement::ZERO, cond); + xd = FieldElement::conditional_select(&xd, &FieldElement::ONE, cond); + yn = FieldElement::conditional_select(&yn, &FieldElement::ONE, cond); + yd = FieldElement::conditional_select(&yd, &FieldElement::ONE, cond); + + // 13. return (xn, xd, yn, yd) + (&xn * &(xd.invert()), &yn * &(yd.invert())) +} + +// ======================================================================== +// Tests +// ======================================================================== + +#[cfg(test)] +#[cfg(feature = "elligator2")] +#[cfg(feature = "alloc")] +mod compatibility; + +#[cfg(test)] +#[cfg(feature = "elligator2")] +mod rfc9380; + +#[cfg(test)] +#[cfg(feature = "elligator2")] +mod randomness; + +#[cfg(test)] +#[cfg(feature = "elligator2")] +mod subgroup; + +#[cfg(test)] +#[cfg(feature = "elligator2")] +#[cfg(feature = "digest")] +mod legacy; diff --git a/curve25519-elligator2/src/elligator2/compatibility.rs b/curve25519-elligator2/src/elligator2/compatibility.rs new file mode 100644 index 00000000..8ba7a735 --- /dev/null +++ b/curve25519-elligator2/src/elligator2/compatibility.rs @@ -0,0 +1,257 @@ +use super::*; + +use hex::FromHex; + +//////////////////////////////////////////////////////////// +// Ntor tests // +//////////////////////////////////////////////////////////// + +#[test] +#[cfg(feature = "elligator2")] +fn pubkey_from_repres() { + // testcases from golang agl/ed25519 + for (i, vector) in ntor_valid_test_vectors().iter().enumerate() { + let true_pubkey = vector[2]; + let repres = <[u8; 32]>::from_hex(vector[3]).expect("failed to decode hex representative"); + + // ensure that the representative can be reversed to recover the + // original public key. + let pubkey = MontgomeryPoint::map_to_point(&repres); + assert_eq!( + true_pubkey, + hex::encode(pubkey.to_bytes()), + "[good case] agl/ed25519 ({i}) bad pubkey from true representative" + ); + } +} + +#[test] +#[cfg(feature = "elligator2")] +/// Ensure private keys generate the expected representatives. These tests +/// are generated from agl/ed25519 to ensure compatibility. +fn repres_from_privkey_agl() { + for (i, vector) in ntor_valid_test_vectors().iter().enumerate() { + let privkey = <[u8; 32]>::from_hex(vector[0]).expect("failed to decode hex privatekey"); + let true_repres = vector[3]; + + let pubkey_clean = EdwardsPoint::mul_base_clamped(privkey) + .to_montgomery() + .to_bytes(); + assert_eq!( + hex::encode(pubkey_clean), + vector[1], + "[good case] agl/ed25519 ({i}) incorrect clean pubkey from privkey" + ); + + let pubkey = Randomized::mul_base_clamped(privkey); + assert_eq!( + hex::encode(pubkey.to_montgomery().to_bytes()), + vector[2], + "[good case] agl/ed25519 ({i}) incorrect pubkey from privkey" + ); + + let r1 = Randomized::to_representative(&privkey, 0u8) + .expect("[good case] agl/ed25519 ({i}) failed to get representative from privkey"); + + let repres = representative_from_pubkey(&pubkey, 0u8) + .expect("[good case] agl/ed25519 ({i}) failed to get representative from pubkey"); + + assert_eq!(hex::encode(r1), hex::encode(repres)); + assert_eq!( + true_repres, + hex::encode(repres), + "[good case] agl/ed25519 ({i}) incorrect representative from privkey" + ); + } +} + +#[test] +#[cfg(feature = "elligator2")] +fn non_representable_points() { + for (i, key) in ntor_invalid_keys().iter().enumerate() { + let privkey = <[u8; 32]>::from_hex(key).expect("failed to decode hex privkey"); + + // ensure that the representative can be reversed to recover the + // original public key. + let res: Option<[u8; 32]> = MontgomeryPoint(privkey).to_representative::(0u8); + assert!( + res.is_none(), + "[bad case] agl/ed25519 ({i}) expected None, got Some({})", + hex::encode(res.expect("this shouldn't happen")) + ); + } +} + +/// returns a set of keypair encodings generated by (a fork of) the golang +/// implementation agl/ed25519 which is used by the mainstream obfs4 +/// library. Each set of three strings is +/// +/// 1) the generated private key scalar +/// 2) the associated public key point when using normal mul_base_clamped +/// 2) the associated public key point w/ cofactor creates using mul_base_clamped_dirty +/// 3) the successfully created representative from the dirty publicKey +/// +/// All of these are valid keypairs and our public key to +/// representative implementation (and repres-to-pubkey) should match and +/// handle these cases. +/// +fn ntor_valid_test_vectors() -> [[&'static str; 4]; 21] { + [ + [ + "b531f4243aa4a013f0f87a2eaaec47807844a2f375d40b774e824d37a196b2b6", + "aa2d8f1c47f717d96edbf503ff0d9fa782849c486ad8a7c15b2bf4a793902c35", + "04a47c7903661acf03cd71e5400c8b96650bb3620d2ae91b674713ca5f2ac673", + "f30130eb1c192cda48a503932c8751232fd784ab7792acba499807e78084a909", + ], + [ + "d63f245d00c57683e8f3f7174b2601aa89319d39823e42aa388fb4f349b8df16", + "97ed90e92c02c5a488d4d9589174867d0d640c460caf4c61833f6c6cb5f5f874", + "3741b60e375affc17d0dba931bb0f5f4540c31c67a979c3399596103283dd27d", + "3a50a823f482a7af1ac898446850dd643b9a68b530df4cebe7d9f108ebe8933b", + ], + [ + "2b6b4888ec2d23748f708627c1a9260b0f10dd2fbc941e44cd578d69760d9873", + "79a9d72800606a520b580bdba40882fa78f13f61a4a402ef615cf01ef5875450", + "d8db37c0fafe81757adcf4f580daff6f68b200a4a38965a4a71ced905e67ee26", + "ed3b270440c2c9d6630bd2b92fd07ede4b8ac3881a286bc9d5f225e35e1d5a31", + ], + [ + "9228b2e3a95462b6d9f32cc326c3c55107972e9cb426301d33861bec15049b65", + "995ac596a4c4f6da1a7ae3cc5ef9dcfb74443717a68dc03972882c9ba7db1823", + "9d8ef0820c3ed0b0c7d29e178eaf31431ca4ee1e97cdab08ea6c85946a3e5e6b", + "b918077e9e1283c0cc44055a28b97ea131e8517983ed033d60e99b818f55fe00", + ], + [ + "21af4b82c709c6dbf0a1aa28b7a045ea6a20448ec36c9f5f6eadf60c9a71ee66", + "f8ac0dd56d7c9f7d681a8fec38675a39e37e2b9780995ce6cdc1f4da25356860", + "41b49d99d7490aae2c774893d2f24147c353f4179ef318c1954899eb93ba7316", + "e1d2992fce97a72d40483f940aa1156d34eea4e5789336155d718f0d74eb9e27", + ], + [ + "465fd0ac29e17ecadf922676a99a40e59ef20ed7d964663fce6d09cfe247599e", + "5246cdd46625f43e590699a8c0f8740af173190036875c163fc9b430c72b301e", + "9b85e75bfe129adf7e28d2ac1a8a2a186259c91e9afe5b60497f59fe48a61363", + "cfa4592dc98b4625c70bcc5f5b39611043436e4d8818e977ae479e2321b4e731", + ], + [ + "f0bfc68ccc5cde3aac026b8668055c06bb286950197cf8df5b8546c2a55f8676", + "51f046dc5d42d9a73fc12d9b3720723915f58b8b617584ae469bd081118c784d", + "51f046dc5d42d9a73fc12d9b3720723915f58b8b617584ae469bd081118c784d", + "0cb6b5eb789ec03c3561b6574554f1a3e9ceb265cf92e8f1edf03e445641e13a", + ], + [ + "3d4269b3aa052fa291db18183ebd9e05d0230faa7f474eaa28c7cebb191b552c", + "22b0fa046129a0b3a1ddaacb7b62edd952d239aec38ea8c89bf00e15fe859547", + "af0c7b6703225e5ff0260e2cec7c79ded10506c76f1fd86b6134b0b41481f05f", + "932f7c251dfc76ca8fc893068ac7f76c82f311830bbc39a7d69ab1dc75adb419", + ], + [ + "2507de9b7116c8e38b8d9d4a9787c7ad58a5ac82c7cafca8f1f20ed13d41c07e", + "a2a75aa84a1e31a332a49ef20767c0b4b7f2fc81784b720a6f39a666b3d28e53", + "769d48797268911011619509485d5f68649bc56c208057de42188b7f4b6e4a40", + "4b0946387189f8eb0d51ac2ca1156297cc69f3e7d681da2353af7b61055f6c04", + ], + [ + "ab675f987fa1ff564d9532b94103d382424c804e16f33278d5daf8460cafc12f", + "d186d70dd4a861fe0b13bf5bdd43a28ffd5457128d47aee934b8efd4fa163972", + "f3875d1c9118a39467f774837d5b73f26245add28adface6c3831d0b16311600", + "55dc20e8bdfc4d13a6a3f1c49859f35248d1bda1d0db26efc02ccf407ec5670c", + ], + [ + "7f74e1ee4b024dfa1d0922f3b848891ca0bda69760a72354ee38c9a340293511", + "1616f968954f044a8033d0639ef9eaa22658a8deb9d3bd75c4f900b8439ac053", + "9511eb90311692dba1df5cabe16835545d8105a3a51bb1a4e2e3e77625789e59", + "bab2454581ba3c31d61525e341c6e45dae64d63ddeb18ec7246104225d9aad27", + ], + [ + "38af5f517bc769ef83371332d83c438b9a15cafb94718c2d417a2b5595919325", + "ba6d043159102150818942f0bfca69377198ce93d7df638814b4a84df74e7e3f", + "ba6d043159102150818942f0bfca69377198ce93d7df638814b4a84df74e7e3f", + "b3c91ba8db89652d7c93568e141479644b27fbefbb9f8ebd66697c2f3b40fc12", + ], + [ + "84b40c80c5c44f91e7165328f84db2507c8fd0fb58cd939705565dc60f98d4ab", + "7cfb1e927d172cfd0fecf3616bd3077a1e485f8487f5f792a44af24602aeee78", + "0796354388c13f2f43263a04791be6f341c0827dca22cf602e3711aeff3c270d", + "33c419bdec3cbc5bf10811f53c8745e7832d38b0fd4f4975150989e763431702", + ], + [ + "fcb6c9a0acbea273445cc1c84c5fe486ba88f76ff6197e4d66732ac1b7905e0e", + "a865fcb0d433c9205079046ddff9e3add7b3d5b85eabf77570aef6221625845a", + "82d52b90191fa82e6125352d0df011f40d1c52c936d8472c80ab5f39be4dc275", + "a500286f77a44da3954cd2d28bc6ceb698fe9b5f59296494cdba2223ed82781d", + ], + [ + "3a89d10d54534bc595f95b7266ea95a7509a13e35eaede55be49931c73e29f51", + "9a2fd476dbc15148a72d2915223142060ebee6e8fce2a110bc00977d618f4354", + "6d027e1197c78eeed729cdf0a08b25f6c2a36d726dcf2dc7592cbf22a64c4667", + "26591aa301c6b90b41bb2d7d227b7513446a2281138135b606b2ccfd67073d28", + ], + [ + "5ad1beed45793f7e7fd2b59b1d3c64441087c31f5edbc71d5adacf3e87f3b409", + "69947803a666ae9746282ec985bb140473bd2cca8e7e806e6b7c63dba12abf09", + "e562faef7f4846624819ed16de66bfbc5cee8c7200d2579fa81ee00e457d0c41", + "304f86219a3b6f8045038baa1d51271c69b4594582b05409fb08310d15893830", + ], + [ + "248c40bf9a05281c5c42d884c37d916c4dface070f936d293f30a1d05bce5caa", + "ea506f676538b2aa4c60092026453ecb37a3562295be551d134cb334063aad4a", + "27934d458e6c253b0211a6d5ffead34a7f18264c9744c8d860a5af02b486960e", + "3fb9e265959e9ce605f662d983cf5c7606be10cf95dcf1eb96931d7d189fcd1f", + ], + [ + "88d60d17395bb4df69205cc2750bb2e83f8646aa56acaaeccdaec527e085bcc2", + "ae62dc36e3f8bcc7c436cb8adb1b931af32bbe1501d048dc50d9947399f0ac0c", + "ae62dc36e3f8bcc7c436cb8adb1b931af32bbe1501d048dc50d9947399f0ac0c", + "1e8c4ba9a54eccd2bd9020c8333033857187eab8e17aa40f46e22acaca07d31e", + ], + [ + "39e952a23697e05cf624e82c36c36bb51d546ffef5aa4fc4d7b695a51cae499a", + "8d04ce07d2251923e7c871df78ad6eb56db6c0c8b4b11864c2078f91dce17c1f", + "94d13ad7430c8d95f9687b7f030bd22079471f02c96938fabe1d8eac13ad0e3b", + "f33d05d0a975fcd9777db0e78a4c74ec161d90026c29dfc2bdaf79d1a24d6c0d", + ], + [ + "d6111d6215c5e799b92331c0e2984f25de42e9c02e37d53abad8b7cb2a9c5e9f", + "8fb6b77c034c1b96eca447a1192e54e8efd14944595f2d8f37e0bb13b9537e59", + "0bce86adc6fee70bb5ecb91c56afe59dd0cdf7a92bf6fa75347a48fa91bcb35c", + "ff8143b7f3dd11e594ba558c0bc67116676376637971c03394d707a642f1fc25", + ], + [ + "d08d3be3602c52c45df91f7b8b16132f779e8336f88a4e6ad320874eaabf60a2", + "04dd9e5f7311cb8ba72487effab05b8ecbd89f3c7f2e18733d2bda22a4ebd00e", + "04dd9e5f7311cb8ba72487effab05b8ecbd89f3c7f2e18733d2bda22a4ebd00e", + "80c34dd01d6832dbb2589120b905f302c8a7adf5b7aac0418c9c6ebb0fad4e35", + ], + // The following key sets have a v_in_sqrt value of 0 + ] +} + +/// returns a set of private keys generated by (a fork of) the golang +/// implementation agl/ed25519 which is used by the mainstream obfs4 +/// library. +/// +/// All of these fail to generate a keypair capable of using elligator2 +/// to encode of the public key. +/// +#[cfg(feature = "elligator2")] +fn ntor_invalid_keys() -> [&'static str; 16] { + [ + "e3457a03d99b91ab2860470a9501e03f30f4f91c9655c5d2700e43fc07262f3f", + "f73f18d545882268e7ca793f717d175acb8c628af8de8445b0c5a0c4cca7fab8", + "3e0b46dc90be39c8abc65231caabfa935ee055f83c4055b89fc189715c10c9fa", + "8d2a6143e68ad022f544e13a21954a0615eb544e072399f31a4535c6bb5a4343", + "42e01260c570a1be859ecce34b029acf600b7ef520e864a7c75cd122e4eb650b", + "270eece8fa7e73fd071c4e7deefcd4f58553fa2572ef9b750605a48dfea959c0", + "5f4dd2d5e3a4d3288a61b5f91f10e89b16ed1ae4496a06b15ae1b0fbd2b8c283", + "01f7ca332f44b4467e4b336c16fb59dae75e2a81eef1f54a3a6352ab7c8cdb3c", + "c3f3c1a6f5521a796f96889bbc36977eb9b80a479dee2213bdad4f5615a8877f", + "222a8a00c74e1386c5ce7817c26d63e9c4bad0cd4799275cda4f5ca098f8fe64", + "c54a7937d97d33c0782dcf5585db41f1bf69e0013bc0d8123e77468f1c0caec7", + "9088b66f4b128156c2a600d26ee9c5468d22505b061351c2578067a83184f9a5", + "7d5cc59a6b9ec4e0c5c8cf05e02809a77c3c21f78a035489c1f11defdceaf159", + "53aa1fa8d06555851e24b6a0827d591d37a8eb434661014844048375fd2c3447", + "d7781aae42b8d8b07146d92e575092ad94d7ab2d3a71f6303a3fee1436e61a29", + "8d470a34b3b9e6f12348d70a60fb2eaaf9e920bc9ca157bc65c51fc7294c6333", + ] +} diff --git a/curve25519-elligator2/src/elligator2/legacy.rs b/curve25519-elligator2/src/elligator2/legacy.rs new file mode 100644 index 00000000..7513e43d --- /dev/null +++ b/curve25519-elligator2/src/elligator2/legacy.rs @@ -0,0 +1,230 @@ +use crate::{elligator2::*, MontgomeryPoint}; + +use hex::{self, FromHex}; + +#[test] +#[cfg(feature = "elligator2")] +fn repres_from_pubkey_kleshni() { + // testcases from kleshni + for (i, testcase) in encoding_testcases().iter().enumerate() { + // the testcases for kleshni have public key values encoded as montgomery + // points with high_y as the sign bit + let pubkey = <[u8; 32]>::from_hex(testcase.point).expect("failed to decode hex point"); + + let edw_point = MontgomeryPoint(pubkey) + .to_edwards(testcase.high_y as u8) + .expect("failed to convert point to edwards"); + let v_in_sqrt = v_in_sqrt_pubkey_edwards(&edw_point); + let repres = point_to_representative(&MontgomeryPoint(pubkey), v_in_sqrt.into()); + + if testcase.representative.is_some() { + let r = repres.expect("failed to get representative from point"); + + // make sure that we did in fact get a representative + assert_eq!( + testcase + .representative + .expect("checked, is some - this should never fail"), + hex::encode(r), + "[good case] kleshni ({i}) bad representative from privkey", + ); + + // make sure that the representative we got is the correct representative + let p = Legacy::from_representative(&r) + .expect("failed to get pubkey from valid representative"); + + // make sure that the public key derived from the representative matches + // the public key derived from the privatekey. + assert_eq!( + hex::encode(pubkey), + hex::encode(p.to_montgomery().to_bytes()), + "[good case] kleshni ({i}) pubkey from repres doesn't match pubkey from privkey" + ); + } else { + // We expect the provided private key NOT to map to a representative + assert!( + >::into(repres.is_none()), + "[good case] kleshni ({i}) expected none got repres {}", + hex::encode(repres.expect("this should not fail")) + ); + } + } +} + +#[test] +#[cfg(feature = "elligator2")] +fn pubkey_from_repres() { + // testcases from kleshni + for (i, testcase) in decoding_testcases().iter().enumerate() { + let repres = <[u8; 32]>::from_hex(testcase.representative) + .expect("failed to decode hex representative"); + + let point = MontgomeryPoint::map_to_point(&repres); + assert_eq!( + testcase.point, + hex::encode(point.to_bytes()), + "[good case] kleshni ({i}) bad representative from point" + ); + + let point_from_unbounded = MontgomeryPoint::from_representative::(&repres) + .expect("expected point, failed"); + assert_eq!( + testcase.non_lsr_point, + hex::encode(point_from_unbounded.to_bytes()), + "[good case] kleshni ({i}) bad representative from point" + ); + } +} + +const ENCODING_TESTS_COUNT: usize = 10; +struct EncodingTestCase { + /// sign value of the Montgomery point representation of the public key point + high_y: bool, + /// publkic key value, byte encoding of a Montgomery point + point: &'static str, + /// representative value associated with the provided point, if one exists. + representative: Option<&'static str>, +} + +/// In these testcases the `point` is the montgomery representation of the public +/// key value. We do not need the private key value to check these tests as we can +/// convert from public key to representative, and for some of the examples we may +/// not know what the associated private key would be as we manually swap the sign +/// ('high_y`) value for the public key point. +fn encoding_testcases() -> [EncodingTestCase; ENCODING_TESTS_COUNT] { + [ + // A not encodable point with both "high_y" values + EncodingTestCase { + point: "e6f66fdf6e230c603c5e6e59a254ea1476a13eb9511b9549846781e12e52230a", + high_y: false, + representative: None, + }, + EncodingTestCase { + point: "e6f66fdf6e230c603c5e6e59a254ea1476a13eb9511b9549846781e12e52230a", + high_y: true, + representative: None, + }, + // An encodable point with both "high_y" values + EncodingTestCase { + point: "33951964003c940878063ccfd0348af42150ca16d2646f2c5856e8338377d800", + high_y: false, + representative: Some( + "999b591b6697d074f266192277d554dec3c24c2ef6108101f63d94f7fff3a013", + ), + }, + EncodingTestCase { + point: "33951964003c940878063ccfd0348af42150ca16d2646f2c5856e8338377d800", + high_y: true, + representative: Some( + "bd3d2a7ed1c8a100a977f8d992e33aaa6f630d55089770ea469101d7fd73d13d", + ), + }, + // 0 with both "high_y" values + EncodingTestCase { + point: "0000000000000000000000000000000000000000000000000000000000000000", + high_y: false, + representative: Some( + "0000000000000000000000000000000000000000000000000000000000000000", + ), + }, + EncodingTestCase { + point: "0000000000000000000000000000000000000000000000000000000000000000", + high_y: true, + representative: Some( + "0000000000000000000000000000000000000000000000000000000000000000", + ), + }, + // A not encodable point with both "high_y" values + EncodingTestCase { + point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", + high_y: false, + representative: Some( + "d660db8cf212d31ce8c6f7139e69b9ac47fd81c7c0bfcb93e364b2d424e24813", + ), + }, + EncodingTestCase { + point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", + high_y: true, + representative: Some( + "489a2e0f6955e08f1ae6eb8dcdbc0f867a87a96a02d2dfd2aca21d8b536f0f1b", + ), + }, + // A not encodable point with both "high_y" values + EncodingTestCase { + point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", + high_y: false, + representative: Some( + "63d0d79e7f3c279cf4a0a5c3833fd85aa1f2c004c4e466f3a3844b3c2e06e410", + ), + }, + EncodingTestCase { + point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", + high_y: true, + representative: Some( + "0f03b41c86aeb49acf2f76b39cc90a55a0b140b7290f1c9e032591ddcb074537", + ), + }, + ] +} + +const DECODING_TESTS_COUNT: usize = 7; +struct DecodingTestCase { + representative: &'static str, + /// if we only allow least-square-root values as the representative and + /// clear the high order two bits (effectively) ensuring that the + /// representative value is less than `2^254 - 10`, this is the point + /// that we should receive. + point: &'static str, + /// if we allow unbounded values to be used directly as representatives, + /// not only least-square-root values, this is the point we should receive. + non_lsr_point: &'static str, +} + +fn decoding_testcases() -> [DecodingTestCase; DECODING_TESTS_COUNT] { + [ + // A small representative with false "high_y" property + DecodingTestCase { + representative: "e73507d38bae63992b3f57aac48c0abc14509589288457995a2b4ca3490aa207", + point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f", + non_lsr_point: "1e8afffed6bf53fe271ad572473262ded8faec68e5e67ef45ebb82eeba52604f", + }, + // A small representative with true "high_y" property + DecodingTestCase { + representative: "95a16019041dbefed9832048ede11928d90365f24a38aa7aef1b97e23954101b", + point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363", + non_lsr_point: "794f05ba3e3a72958022468c88981e0be5782be1e1145ce2c3c6fde16ded5363", + }, + // The last representative returning true: (p - 1) / 2 + DecodingTestCase { + representative: "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + point: "9cdb525555555555555555555555555555555555555555555555555555555555", + non_lsr_point: "9cdb525555555555555555555555555555555555555555555555555555555555", + }, + // The first representative returning false: (p + 1) / 2 + DecodingTestCase { + representative: "f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + point: "9cdb525555555555555555555555555555555555555555555555555555555555", + non_lsr_point: "9cdb525555555555555555555555555555555555555555555555555555555555", + }, + // 0 + DecodingTestCase { + representative: "0000000000000000000000000000000000000000000000000000000000000000", + point: "0000000000000000000000000000000000000000000000000000000000000000", + non_lsr_point: "0000000000000000000000000000000000000000000000000000000000000000", + }, + // These two tests are not least-square-root representations. + + // A large representative with false "high_y" property + DecodingTestCase { + representative: "179f24730ded2ce3173908ec61964653b8027e383f40346c1c9b4d2bdb1db76c", + point: "e6e5355e0482e952cc951f13db26316ab111ae9edb58c45428a984ce7042d349", + non_lsr_point: "10745497d35c6ede6ea6b330546a6fcbf15c903a7be28ae69b1ca14e0bf09b60", + }, + // A large representative with true "high_y" property + DecodingTestCase { + representative: "8a2f286180c3d8630b5f5a3c7cc027a55e0d3ffb3b1b990c5c7bb4c3d1f91b6f", + point: "27e222fec324b0293842a59a63b8201b0f97b1dd599ebcd478a896b7261aff3e", + non_lsr_point: "6d3187192afc3bcc05a497928816e3e2336dc539aa7fc296a9ee013f560db843", + }, + ] +} diff --git a/curve25519-elligator2/src/elligator2/randomness.rs b/curve25519-elligator2/src/elligator2/randomness.rs new file mode 100644 index 00000000..c28bcaaf --- /dev/null +++ b/curve25519-elligator2/src/elligator2/randomness.rs @@ -0,0 +1,179 @@ +use super::*; + +use kolmogorov_smirnov as ks; +use rand::{thread_rng, RngCore}; +use rand_distr::{Binomial, Distribution}; +use std::vec::Vec; + +struct BitCounts { + counts: [[u64; 100]; 32 * 8], + entries: usize, +} + +impl BitCounts { + fn new() -> Self { + BitCounts { + counts: [[0u64; 100]; 256], + entries: 0, + } + } + fn entry(&mut self, arr: &[u8; 32]) { + for (i, arr_byte) in arr.iter().enumerate() { + for j in 0..8 { + self.counts[i * 8 + j][self.entries % 100] += ((arr_byte >> j) & 0x01) as u64; + } + } + self.entries += 1; + } + fn outliers(&self) -> Vec { + let mut rng = thread_rng(); + let binomial_100 = Binomial::new(100, 0.5).expect("failed to build binomial distribution"); + let expected_dist: [u64; 100] = binomial_100 + .sample_iter(&mut rng) + .take(100) + .collect::>() + .try_into() + .expect("failed to build example binomial expected distribution"); + // this is a high confidence, but we want to avoid this test failing + // due to statistical variability on repeat runs. + let confidence = 0.95; + let mut outlier_indices = vec![]; + + for (n, bit_trial) in self.counts.iter().enumerate() { + let result = ks::test(bit_trial, &expected_dist, confidence); + // require reject_probability == 1.0 to avoid statistical variability on re-runs + if result.is_rejected && result.reject_probability == 1.0 { + // samples definitely not from same distribution + outlier_indices.push(n); + println!( + "{n}, {} {} {} {}", + result.statistic, + result.reject_probability, + result.critical_value, + result.confidence + ); + if n == 255 { + println!("{:?}\n{:?}", bit_trial, expected_dist); + } + } + } + outlier_indices + } +} + +#[test] +/// If we use a "random" value for the tweak the high order bits will be +/// set such that the representative is: +/// 1) unchanged w.r.t. the public key value derived from the representative +/// i.e) it still derives the same public key value from the (clamped) +/// representative that we get from the private key. +/// 2) statistically indistinguishable from uniform random. +/// 4) computationally indistinguishable from random strings for the `a ?= sqrt(a^2)` test. +/// +/// (To see this test fail change `rng.next_u32() as u8` to `0u8`) +fn bitwise_entropy() { + const ITERATIONS: usize = 10000; + // number of iterations + let mut i = 0usize; + let mut rng = thread_rng(); + let mut privkey = [0u8; 32]; + + // count of occurences of a 1 per bit in the representative + let mut bitcounts = BitCounts::new(); + + while i < ITERATIONS { + rng.fill_bytes(&mut privkey); + let alice_representative = + match Randomized::to_representative(&privkey, rng.next_u32() as u8).into() { + None => continue, + Some(r) => r, + }; + + bitcounts.entry(&alice_representative); + + let pub_from_repr = + MontgomeryPoint::from_representative::(&alice_representative) + .expect("failed pub from repres"); + let pub_from_priv = Randomized::mul_base_clamped(privkey).to_montgomery(); + assert_eq!( + hex::encode(pub_from_priv.as_bytes()), + hex::encode(pub_from_repr.as_bytes()), + "failed pubkey match at iteration {i}" + ); + + i += 1; + } + + let outliers = bitcounts.outliers(); + assert!(outliers.is_empty(), "bad bits: {:?}", outliers); +} + +/// TLDR: The sqrt_ratio_i function is canonical so this library does not +/// suffer from the describbed computational distinguisher. +/// +/// The specific issue that this is testing for can be described as: +/// ```txt +/// An instantiation of Elligator is parameterized by what might be called +/// a “canonical” square root function, one with the property that +/// `√a2 = √(−a)2` for all field elements `a`. That is, we designate just +/// over half the field elements as “non-negative,” and the image of the +/// square root function consists of exactly those elements. A convenient +/// definition of “non-negative” for Curve25519, suggested by its authors, +/// is the lower half of the field, the elements `{0, 1, …, (q − 1) / 2}`. +/// When there are two options for a square root, take the smaller of the two. +/// ``` +/// +/// Any Elligator implementation that does not do this canonicalization of +/// the final square root, and instead it maps a given input systematically +/// to either its negative or non-negative root is vulnerable to the +/// following computational distinguisher. +/// +/// ```txt +/// [An adversary could] observe a representative, interpret it as a field +/// element, square it, then take the square root using the same +/// non-canonical square root algorithm. With representatives produced by +/// an affected version of [the elligator2 implementation], the output of +/// the square-then-root operation would always match the input. With +/// random strings, the output would match only half the time. +/// ``` +/// +/// For a more in-depth explanation see: +/// https://github.com/agl/ed25519/issues/27 +/// https://www.bamsoftware.com/papers/fep-flaws/ +#[test] +fn test_canonical() { + const ITERATIONS: usize = 10000; + // number of iterations + let mut i = 0usize; + let mut rng = thread_rng(); + let mut privkey = [0u8; 32]; + + // number of times the representative (interpreted as a point) squared, then square_rooted, + // equals the original representative. Should happen w/ 50% probability. + let mut squares_equal = 0usize; + + while i < ITERATIONS { + rng.fill_bytes(&mut privkey); + let alice_representative = match Randomized::to_representative(&privkey, 0u8).into() { + None => continue, + Some(r) => r, + }; + + if is_canonical(&alice_representative) { + squares_equal += 1; + } + i += 1; + } + + let expected_range = 4500..5500; // if truly binomial n=10000, p=0.5 then this should "always" pass (> 10x std dev) + assert!( + expected_range.contains(&squares_equal), + "squares_equal: {squares_equal} is not in [4500:5500]" + ); +} + +fn is_canonical(repres: &[u8; 32]) -> bool { + let r_fe = FieldElement::from_bytes(repres); + let (ok, r_fe_prime) = FieldElement::sqrt_ratio_i(&r_fe.square(), &FieldElement::ONE); + (r_fe.ct_eq(&r_fe_prime) & ok).into() +} diff --git a/curve25519-elligator2/src/elligator2/rfc9380.rs b/curve25519-elligator2/src/elligator2/rfc9380.rs new file mode 100644 index 00000000..ea623127 --- /dev/null +++ b/curve25519-elligator2/src/elligator2/rfc9380.rs @@ -0,0 +1,442 @@ +use super::*; + +use hex::FromHex; +use std::string::String; + +#[test] +fn map_to_curve_test_go_ed25519_extra() { + for (i, testcase) in CURVE25519_ELL2.iter().enumerate() { + let u = testcase[0].must_from_be(); + let mut clamped = u; + clamped[31] &= 63; + + // map point to curve + let (q_x, _) = map_fe_to_montgomery(&FieldElement::from_bytes(&clamped)); + + // check resulting point + assert_eq!( + q_x.encode_be(), + testcase[1], + "({i}) incorrect x curve25519 ELL2\n" + ); + } +} + +#[test] +fn map_to_curve_test_curve25519() { + for (i, testcase) in curve25519_XMD_SHA512_ELL2_NU.iter().enumerate() { + let u = FieldElement::from_bytes(&testcase.u_0.must_from_le()); + + // map point to curve + let (q_x, q_y) = map_fe_to_montgomery(&u); + + // check resulting point + assert_eq!( + q_x.encode_le(), + testcase.Q_x, + "({i}) incorrect Q0_x curve25519 NU\n{:?}", + testcase + ); + assert_eq!( + q_y.encode_le(), + testcase.Q_y, + "({i}) incorrect Q0_y curve25519 NU\n{:?}", + testcase + ); + } + for (i, testcase) in curve25519_XMD_SHA512_ELL2_RO.iter().enumerate() { + let u0 = FieldElement::from_bytes(&testcase.u_0.must_from_le()); + let u1 = FieldElement::from_bytes(&testcase.u_1.must_from_le()); + + // map points to curve + let (q0_x, q0_y) = map_fe_to_montgomery(&u0); + let (q1_x, q1_y) = map_fe_to_montgomery(&u1); + + // check resulting points + assert_eq!( + q0_x.encode_le(), + testcase.Q0_x, + "({i}) incorrect Q0_x curve25519 RO\n{:?}", + testcase + ); + assert_eq!( + q0_y.encode_le(), + testcase.Q0_y, + "({i}) incorrect Q0_y curve25519 RO\n{:?}", + testcase + ); + assert_eq!( + q1_x.encode_le(), + testcase.Q1_x, + "({i}) incorrect Q1_x curve25519 RO\n{:?}", + testcase + ); + assert_eq!( + q1_y.encode_le(), + testcase.Q1_y, + "({i}) incorrect Q1_y curve25519 RO\n{:?}", + testcase + ); + } +} + +#[test] +fn map_to_curve_test_edwards25519() { + for (i, testcase) in edwards25519_XMD_SHA512_ELL2_NU.iter().enumerate() { + let u = FieldElement::from_bytes(&testcase.u_0.must_from_le()); + let (q_x, q_y) = map_fe_to_edwards(&u); + + // check resulting point + assert_eq!( + q_x.encode_le(), + testcase.Q_x, + "({i}) incorrect Q0_x edwards25519 NU\n{:?}", + testcase + ); + assert_eq!( + q_y.encode_le(), + testcase.Q_y, + "({i}) incorrect Q0_y edwards25519 NU\n{:?}", + testcase + ); + } + for (i, testcase) in edwards25519_XMD_SHA512_ELL2_RO.iter().enumerate() { + let u0 = FieldElement::from_bytes(&testcase.u_0.must_from_le()); + let u1 = FieldElement::from_bytes(&testcase.u_1.must_from_le()); + + // map points to curve + let (q0_x, q0_y) = map_fe_to_edwards(&u0); + let (q1_x, q1_y) = map_fe_to_edwards(&u1); + + // check resulting points + assert_eq!( + q0_x.encode_le(), + testcase.Q0_x, + "({i}) incorrect Q0_x edwards25519 RO\n{:?}", + testcase + ); + assert_eq!( + q0_y.encode_le(), + testcase.Q0_y, + "({i}) incorrect Q0_y edwards25519 RO\n{:?}", + testcase + ); + assert_eq!( + q1_x.encode_le(), + testcase.Q1_x, + "({i}) incorrect Q1_x edwards25519 RO\n{:?}", + testcase + ); + assert_eq!( + q1_y.encode_le(), + testcase.Q1_y, + "({i}) incorrect Q1_y edwards25519 RO\n{:?}", + testcase + ); + } +} + +/// Example test cases found in gitlab.com/yawning/edwards25519-extra +/// +/// 1. representative +/// 2. associated point +/// +/// These test cases need the upper two bits cleared to be properly mapped. +const CURVE25519_ELL2: [[&str; 2]; 14] = [ + [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + ], + [ + "0000000000000000000000000000000000000000000000000000000000000040", + "0000000000000000000000000000000000000000000000000000000000000000", + ], + [ + "0000000000000000000000000000000000000000000000000000000000000080", + "0000000000000000000000000000000000000000000000000000000000000000", + ], + [ + "00000000000000000000000000000000000000000000000000000000000000c0", + "0000000000000000000000000000000000000000000000000000000000000000", + ], + [ + "673a505e107189ee54ca93310ac42e4545e9e59050aaac6f8b5f64295c8ec02f", + "242ae39ef158ed60f20b89396d7d7eef5374aba15dc312a6aea6d1e57cacf85e", + ], + [ + "922688fa428d42bc1fa8806998fbc5959ae801817e85a42a45e8ec25a0d7545a", + "696f341266c64bcfa7afa834f8c34b2730be11c932e08474d1a22f26ed82410b", + ], + [ + "0d3b0eb88b74ed13d5f6a130e03c4ad607817057dc227152827c0506a538bbba", + "0b00df174d9fb0b6ee584d2cf05613130bad18875268c38b377e86dfefef177f", + ], + [ + "01a3ea5658f4e00622eeacf724e0bd82068992fae66ed2b04a8599be16662ef5", + "7ae4c58bc647b5646c9f5ae4c2554ccbf7c6e428e7b242a574a5a9c293c21f7e", + ], + [ + "69599ab5a829c3e9515128d368da7354a8b69fcee4e34d0a668b783b6cae550f", + "09024abaaef243e3b69366397e8dfc1fdc14a0ecc7cf497cbe4f328839acce69", + ], + [ + "9172922f96d2fa41ea0daf961857056f1656ab8406db80eaeae76af58f8c9f50", + "beab745a2a4b4e7f1a7335c3ffcdbd85139f3a72b667a01ee3e3ae0e530b3372", + ], + [ + "6850a20ac5b6d2fa7af7042ad5be234d3311b9fb303753dd2b610bd566983281", + "1287388eb2beeff706edb9cf4fcfdd35757f22541b61528570b86e8915be1530", + ], + [ + "84417826c0e80af7cb25a73af1ba87594ff7048a26248b5757e52f2824e068f1", + "51acd2e8910e7d28b4993db7e97e2b995005f26736f60dcdde94bdf8cb542251", + ], + [ + "b0fbe152849f49034d2fa00ccc7b960fad7b30b6c4f9f2713eb01c147146ad31", + "98508bb3590886af3be523b61c3d0ce6490bb8b27029878caec57e4c750f993d", + ], + [ + "a0ca9ff75afae65598630b3b93560834c7f4dd29a557aa29c7becd49aeef3753", + "3c5fad0516bb8ec53da1c16e910c23f792b971c7e2a0ee57d57c32e3655a646b", + ], +]; + +// J.4.1. curve25519_XMD:SHA-512_ELL2_RO_ +// +// Random Oracle Curve25519 SHA512 Elligator2 +// +// suite = curve25519_XMD:SHA-512_ELL2_RO_ +// dst = QUUX-V01-CS02-with-curve25519_XMD:SHA-512_ELL2_RO_ +// +#[allow(non_upper_case_globals)] +const curve25519_XMD_SHA512_ELL2_RO: [xmd_sha512_25519_ro_testcase; 5] = [ + xmd_sha512_25519_ro_testcase { + u_0: "49bed021c7a3748f09fa8cdfcac044089f7829d3531066ac9e74e0994e05bc7d", + u_1: "5c36525b663e63389d886105cee7ed712325d5a97e60e140aba7e2ce5ae851b6", + Q0_x: "16b3d86e056b7970fa00165f6f48d90b619ad618791661b7b5e1ec78be10eac1", + Q0_y: "4ab256422d84c5120b278cbdfc4e1facc5baadffeccecf8ee9bf3946106d50ca", + Q1_x: "7ec29ddbf34539c40adfa98fcb39ec36368f47f30e8f888cc7e86f4d46e0c264", + Q1_y: "10d1abc1cae2d34c06e247f2141ba897657fb39f1080d54f09ce0af128067c74", + }, + xmd_sha512_25519_ro_testcase { + u_0: "6412b7485ba26d3d1b6c290a8e1435b2959f03721874939b21782df17323d160", + u_1: "24c7b46c1c6d9a21d32f5707be1380ab82db1054fde82865d5c9e3d968f287b2", + Q0_x: "71de3dadfe268872326c35ac512164850860567aea0e7325e6b91a98f86533ad", + Q0_y: "26a08b6e9a18084c56f2147bf515414b9b63f1522e1b6c5649f7d4b0324296ec", + Q1_x: "5704069021f61e41779e2ba6b932268316d6d2a6f064f997a22fef16d1eaeaca", + Q1_y: "50483c7540f64fb4497619c050f2c7fe55454ec0f0e79870bb44302e34232210", + }, + xmd_sha512_25519_ro_testcase { + u_0: "5e123990f11bbb5586613ffabdb58d47f64bb5f2fa115f8ea8df0188e0c9e1b5", + u_1: "5e8553eb00438a0bb1e7faa59dec6d8087f9c8011e5fb8ed9df31cb6c0d4ac19", + Q0_x: "7a94d45a198fb5daa381f45f2619ab279744efdd8bd8ed587fc5b65d6cea1df0", + Q0_y: "67d44f85d376e64bb7d713585230cdbfafc8e2676f7568e0b6ee59361116a6e1", + Q1_x: "30506fb7a32136694abd61b6113770270debe593027a968a01f271e146e60c18", + Q1_y: "7eeee0e706b40c6b5174e551426a67f975ad5a977ee2f01e8e20a6d612458c3b", + }, + xmd_sha512_25519_ro_testcase { + u_0: "20f481e85da7a3bf60ac0fb11ed1d0558fc6f941b3ac5469aa8b56ec883d6d7d", + u_1: "017d57fd257e9a78913999a23b52ca988157a81b09c5442501d07fed20869465", + Q0_x: "02d606e2699b918ee36f2818f2bc5013e437e673c9f9b9cdc15fd0c5ee913970", + Q0_y: "29e9dc92297231ef211245db9e31767996c5625dfbf92e1c8107ef887365de1e", + Q1_x: "38920e9b988d1ab7449c0fa9a6058192c0c797bb3d42ac345724341a1aa98745", + Q1_y: "24dcc1be7c4d591d307e89049fd2ed30aae8911245a9d8554bf6032e5aa40d3d", + }, + xmd_sha512_25519_ro_testcase { + u_0: "005fe8a7b8fef0a16c105e6cadf5a6740b3365e18692a9c05bfbb4d97f645a6a", + u_1: "1347edbec6a2b5d8c02e058819819bee177077c9d10a4ce165aab0fd0252261a", + Q0_x: "36b4df0c864c64707cbf6cf36e9ee2c09a6cb93b28313c169be29561bb904f98", + Q0_y: "6cd59d664fb58c66c892883cd0eb792e52055284dac3907dd756b45d15c3983d", + Q1_x: "3fa114783a505c0b2b2fbeef0102853c0b494e7757f2a089d0daae7ed9a0db2b", + Q1_y: "76c0fe7fec932aaafb8eefb42d9cbb32eb931158f469ff3050af15cfdbbeff94", + }, +]; + +// J.4.2. curve25519_XMD:SHA-512_ELL2_NU_ +// +// Nonuniform Encoding Curve25519 SHA512 Elligator2 +// +// suite: curve25519_XMD:SHA-512_ELL2_NU_ +// dst: QUUX-V01-CS02-with-curve25519_XMD:SHA-512_ELL2_NU_ +// +#[allow(non_upper_case_globals)] +const curve25519_XMD_SHA512_ELL2_NU: [xmd_sha512_25519_nu_testcase; 5] = [ + xmd_sha512_25519_nu_testcase { + u_0: "608d892b641f0328523802a6603427c26e55e6f27e71a91a478148d45b5093cd", + Q_x: "51125222da5e763d97f3c10fcc92ea6860b9ccbbd2eb1285728f566721c1e65b", + Q_y: "343d2204f812d3dfc5304a5808c6c0d81a903a5d228b342442aa3c9ba5520a3d", + }, + xmd_sha512_25519_nu_testcase { + u_0: "46f5b22494bfeaa7f232cc8d054be68561af50230234d7d1d63d1d9abeca8da5", + Q_x: "7d56d1e08cb0ccb92baf069c18c49bb5a0dcd927eff8dcf75ca921ef7f3e6eeb", + Q_y: "404d9a7dc25c9c05c44ab9a94590e7c3fe2dcec74533a0b24b188a5d5dacf429", + }, + xmd_sha512_25519_nu_testcase { + u_0: "235fe40c443766ce7e18111c33862d66c3b33267efa50d50f9e8e5d252a40aaa", + Q_x: "3fbe66b9c9883d79e8407150e7c2a1c8680bee496c62fabe4619a72b3cabe90f", + Q_y: "08ec476147c9a0a3ff312d303dbbd076abb7551e5fce82b48ab14b433f8d0a7b", + }, + xmd_sha512_25519_nu_testcase { + u_0: "001e92a544463bda9bd04ddbe3d6eed248f82de32f522669efc5ddce95f46f5b", + Q_x: "227e0bb89de700385d19ec40e857db6e6a3e634b1c32962f370d26f84ff19683", + Q_y: "5f86ff3851d262727326a32c1bf7655a03665830fa7f1b8b1e5a09d85bc66e4a", + }, + xmd_sha512_25519_nu_testcase { + u_0: "1a68a1af9f663592291af987203393f707305c7bac9c8d63d6a729bdc553dc19", + Q_x: "3bcd651ee54d5f7b6013898aab251ee8ecc0688166fce6e9548d38472f6bd196", + Q_y: "1bb36ad9197299f111b4ef21271c41f4b7ecf5543db8bb5931307ebdb2eaa465", + }, +]; + +// J.5.1. edwards25519_XMD:SHA-512_ELL2_RO_ +// +// Random Oracle Edwards 25519 SHA512 Elligator2 +// +// suite: edwards25519_XMD:SHA-512_ELL2_RO_ +// dst: QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_RO_ +// +#[allow(non_upper_case_globals)] +const edwards25519_XMD_SHA512_ELL2_RO: [xmd_sha512_25519_ro_testcase; 5] = [ + xmd_sha512_25519_ro_testcase { + u_0: "03fef4813c8cb5f98c6eef88fae174e6e7d5380de2b007799ac7ee712d203f3a", + u_1: "780bdddd137290c8f589dc687795aafae35f6b674668d92bf92ae793e6a60c75", + Q0_x: "6549118f65bb617b9e8b438decedc73c496eaed496806d3b2eb9ee60b88e09a7", + Q0_y: "7315bcc8cf47ed68048d22bad602c6680b3382a08c7c5d3f439a973fb4cf9feb", + Q1_x: "31dcfc5c58aa1bee6e760bf78cbe71c2bead8cebb2e397ece0f37a3da19c9ed2", + Q1_y: "7876d81474828d8a5928b50c82420b2bd0898d819e9550c5c82c39fc9bafa196", + }, + xmd_sha512_25519_ro_testcase { + u_0: "5081955c4141e4e7d02ec0e36becffaa1934df4d7a270f70679c78f9bd57c227", + u_1: "005bdc17a9b378b6272573a31b04361f21c371b256252ae5463119aa0b925b76", + Q0_x: "5c1525bd5d4b4e034512949d187c39d48e8cd84242aa4758956e4adc7d445573", + Q0_y: "2bf426cf7122d1a90abc7f2d108befc2ef415ce8c2d09695a7407240faa01f29", + Q1_x: "37b03bba828860c6b459ddad476c83e0f9285787a269df2156219b7e5c86210c", + Q1_y: "285ebf5412f84d0ad7bb4e136729a9ffd2195d5b8e73c0dc85110ce06958f432", + }, + xmd_sha512_25519_ro_testcase { + u_0: "285ebaa3be701b79871bcb6e225ecc9b0b32dff2d60424b4c50642636a78d5b3", + u_1: "2e253e6a0ef658fedb8e4bd6a62d1544fd6547922acb3598ec6b369760b81b31", + Q0_x: "3ac463dd7fddb773b069c5b2b01c0f6b340638f54ee3bd92d452fcec3015b52d", + Q0_y: "7b03ba1e8db9ec0b390d5c90168a6a0b7107156c994c674b61fe696cbeb46baf", + Q1_x: "0757e7e904f5e86d2d2f4acf7e01c63827fde2d363985aa7432106f1b3a444ec", + Q1_y: "50026c96930a24961e9d86aa91ea1465398ff8e42015e2ec1fa397d416f6a1c0", + }, + xmd_sha512_25519_ro_testcase { + u_0: "4fedd25431c41f2a606952e2945ef5e3ac905a42cf64b8b4d4a83c533bf321af", + u_1: "02f20716a5801b843987097a8276b6d869295b2e11253751ca72c109d37485a9", + Q0_x: "703e69787ea7524541933edf41f94010a201cc841c1cce60205ec38513458872", + Q0_y: "32bb192c4f89106466f0874f5fd56a0d6b6f101cb714777983336c159a9bec75", + Q1_x: "0c9077c5c31720ed9413abe59bf49ce768506128d810cb882435aa90f713ef6b", + Q1_y: "7d5aec5210db638c53f050597964b74d6dda4be5b54fa73041bf909ccb3826cb", + }, + xmd_sha512_25519_ro_testcase { + u_0: "6e34e04a5106e9bd59f64aba49601bf09d23b27f7b594e56d5de06df4a4ea33b", + u_1: "1c1c2cb59fc053f44b86c5d5eb8c1954b64976d0302d3729ff66e84068f5fd96", + Q0_x: "21091b2e3f9258c7dfa075e7ae513325a94a3d8a28e1b1cb3b5b6f5d65675592", + Q0_y: "41a33d324c89f570e0682cdf7bdb78852295daf8084c669f2cc9692896ab5026", + Q1_x: "4c07ec48c373e39a23bd7954f9e9b66eeab9e5ee1279b867b3d5315aa815454f", + Q1_y: "67ccac7c3cb8d1381242d8d6585c57eabaddbb5dca5243a68a8aeb5477d94b3a", + }, +]; + +// J.5.2. edwards25519_XMD:SHA-512_ELL2_NU_ +// +// Nonuniform Encoding Edwards 25519 SHA512 Elligator2 +// +// suite: edwards25519_XMD:SHA-512_ELL2_NU_ +// dst: QUUX-V01-CS02-with-edwards25519_XMD:SHA-512_ELL2_NU_ +// +#[allow(non_upper_case_globals)] +const edwards25519_XMD_SHA512_ELL2_NU: [xmd_sha512_25519_nu_testcase; 5] = [ + xmd_sha512_25519_nu_testcase { + u_0: "7f3e7fb9428103ad7f52db32f9df32505d7b427d894c5093f7a0f0374a30641d", + Q_x: "42836f691d05211ebc65ef8fcf01e0fb6328ec9c4737c26050471e50803022eb", + Q_y: "22cb4aaa555e23bd460262d2130d6a3c9207aa8bbb85060928beb263d6d42a95", + }, + xmd_sha512_25519_nu_testcase { + u_0: "09cfa30ad79bd59456594a0f5d3a76f6b71c6787b04de98be5cd201a556e253b", + Q_x: "333e41b61c6dd43af220c1ac34a3663e1cf537f996bab50ab66e33c4bd8e4e19", + Q_y: "51b6f178eb08c4a782c820e306b82c6e273ab22e258d972cd0c511787b2a3443", + }, + xmd_sha512_25519_nu_testcase { + u_0: "475ccff99225ef90d78cc9338e9f6a6bb7b17607c0c4428937de75d33edba941", + Q_x: "55186c242c78e7d0ec5b6c9553f04c6aeef64e69ec2e824472394da32647cfc6", + Q_y: "5b9ea3c265ee42256a8f724f616307ef38496ef7eba391c08f99f3bea6fa88f0", + }, + xmd_sha512_25519_nu_testcase { + u_0: "049a1c8bd51bcb2aec339f387d1ff51428b88d0763a91bcdf6929814ac95d03d", + Q_x: "024b6e1621606dca8071aa97b43dce4040ca78284f2a527dcf5d0fbfac2b07e7", + Q_y: "5102353883d739bdc9f8a3af650342b171217167dcce34f8db57208ec1dfdbf2", + }, + xmd_sha512_25519_nu_testcase { + u_0: "3cb0178a8137cefa5b79a3a57c858d7eeeaa787b2781be4a362a2f0750d24fa0", + Q_x: "3e6368cff6e88a58e250c54bd27d2c989ae9b3acb6067f2651ad282ab8c21cd9", + Q_y: "38fb39f1566ca118ae6c7af42810c0bb9767ae5960abb5a8ca792530bfb9447d", + }, +]; + +#[allow(non_camel_case_types, non_snake_case)] +#[derive(Debug)] +struct xmd_sha512_25519_ro_testcase { + u_0: &'static str, + u_1: &'static str, + // Output + Q0_x: &'static str, + Q0_y: &'static str, + Q1_x: &'static str, + Q1_y: &'static str, +} + +#[allow(non_camel_case_types, non_snake_case)] +#[derive(Debug)] +struct xmd_sha512_25519_nu_testcase { + u_0: &'static str, + // output + Q_x: &'static str, + Q_y: &'static str, +} + +trait FromByteString { + fn must_from_le(&self) -> [u8; 32]; + fn must_from_be(&self) -> [u8; 32]; +} + +impl<'a> FromByteString for &'a str { + fn must_from_le(&self) -> [u8; 32] { + let mut u = <[u8; 32]>::from_hex(self).expect("failed to unhex"); + u.reverse(); + u + } + fn must_from_be(&self) -> [u8; 32] { + <[u8; 32]>::from_hex(self).expect("failed to unhex from be") + } +} + +trait ToByteString { + fn encode_le(&self) -> String; + fn encode_be(&self) -> String; +} + +impl ToByteString for FieldElement { + fn encode_le(&self) -> String { + let mut b = self.as_bytes(); + b.reverse(); + hex::encode(b) + } + + fn encode_be(&self) -> String { + hex::encode(self.as_bytes()) + } +} + +impl ToByteString for [u8; 32] { + fn encode_le(&self) -> String { + let mut b = *self; + b.reverse(); + hex::encode(b) + } + + fn encode_be(&self) -> String { + hex::encode(self) + } +} diff --git a/curve25519-elligator2/src/elligator2/subgroup.rs b/curve25519-elligator2/src/elligator2/subgroup.rs new file mode 100644 index 00000000..fb4d9652 --- /dev/null +++ b/curve25519-elligator2/src/elligator2/subgroup.rs @@ -0,0 +1,133 @@ +use super::*; +use crate::{MontgomeryPoint, Scalar}; + +use rand::{thread_rng, Rng}; + +#[test] +#[cfg(feature = "elligator2")] +/// pubkey_subgroup_check2 tests that Elligator representatives produced by +/// NewKeypair map to public keys that are not always on the prime-order subgroup +/// of Curve25519. (And incidentally that Elligator representatives agree with +/// the public key stored in the Keypair.) +/// +/// See discussion under "Step 2" at https://elligator.org/key-exchange. +// We will test the public keys that comes out of NewKeypair by +// multiplying each one by L, the order of the prime-order subgroup of +// Curve25519, then checking the order of the resulting point. The error +// condition we are checking for specifically is output points always +// having order 1, which means that public keys are always on the +// prime-order subgroup of Curve25519, which would make Elligator +// representatives distinguishable from random. More generally, we want +// to ensure that all possible output points of low order are covered. +// +// We have to do some contortions to conform to the interfaces we use. +// We do scalar multiplication by L using Edwards coordinates, rather +// than the Montgomery coordinates output by Keypair.Public and +// Representative.ToPublic, because the Montgomery-based +// crypto/curve25519.X25519 clamps the scalar to be a multiple of 8, +// which would not allow us to use the scalar we need. The Edwards-based +// ScalarMult only accepts scalars that are strictly less than L; we +// work around this by multiplying the point by L - 1, then adding the +// point once to the product. +fn pubkey_subgroup_check() { + // This is the same as scMinusOne in filippo.io/edwards25519. + // https://github.com/FiloSottile/edwards25519/blob/v1.0.0/scalar.go#L34 + let scalar_order_minus1 = Scalar::from_canonical_bytes([ + 236_u8, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, + ]) + .unwrap(); + + // Returns a new edwards25519.Point that is v multiplied by the subgroup order. + let scalar_mult_order = |v: &EdwardsPoint| -> EdwardsPoint { + // v * (L - 1) + v => v * L + let p = v * scalar_order_minus1; + p + v + }; + + // Generates a new Keypair using, and returns the public key representative + // along, with its public key as a newly allocated edwards25519.Point. + let generate = || -> ([u8; 32], EdwardsPoint) { + for _ in 0..63 { + let y_sk = thread_rng().gen::<[u8; 32]>(); + let y_sk_tweak = thread_rng().gen::(); + + let y_repr_bytes = match Randomized::to_representative(&y_sk, y_sk_tweak).into() { + Some(r) => r, + None => continue, + }; + let y_pk = Randomized::mul_base_clamped(y_sk); + + assert_eq!( + MontgomeryPoint::from_representative::(&y_repr_bytes) + .expect("failed to re-derive point from representative"), + y_pk.to_montgomery() + ); + + return (y_repr_bytes, y_pk); + } + panic!("failed to generate a valid keypair"); + }; + + // These are all the points of low order that may result from + // multiplying an Elligator-mapped point by L. We will test that all of + // them are covered. + let low_order_points = [ + "0100000000000000000000000000000000000000000000000000000000000000", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000080", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + ]; + let mut counts = [0; 8]; + + // Assuming a uniform distribution of representatives, the probability + // that a specific low-order point will not be covered after n trials is + // (7/8)^n. The probability that *any* of the 8 low-order points will + // remain uncovered after n trials is at most 8 times that, 8*(7/8)^n. + // We must do at least log((1e-12)/8)/log(7/8) = 222.50 trials, in the + // worst case, to ensure a false error rate of less than 1 in a + // trillion. In practice, we keep track of the number of covered points + // and break the loop when it reaches 8, so when representatives are + // actually uniform we will usually run much fewer iterations. + let mut num_covered = 0; + for _ in 0..255 { + let (repr, pk) = generate(); + let v = scalar_mult_order(&pk); + + let b = v.compress().to_bytes(); + let b_str = hex::encode(b); + let index = match low_order_points.iter().position(|x| x == &b_str) { + Some(idx) => idx, + None => { + panic!( + "map({})*order yielded unexpected point {:}", + hex::encode(repr), + b_str + ); + } + }; + + counts[index] += 1; + if counts[index] == 1 { + // We just covered a new point for the first time. + num_covered += 1; + if num_covered == low_order_points.len() { + break; + } + } + } + let mut failed = false; + for count in counts.iter() { + if *count == 0 { + failed = true; + } + } + + if failed { + panic!("not all low order points were covered") + } +} diff --git a/curve25519-elligator2/src/field.rs b/curve25519-elligator2/src/field.rs new file mode 100644 index 00000000..3c404b92 --- /dev/null +++ b/curve25519-elligator2/src/field.rs @@ -0,0 +1,507 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis agora lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - Isis Agora Lovecruft +// - Henry de Valence + +//! Field arithmetic modulo \\(p = 2\^{255} - 19\\). +//! +//! The `curve25519_elligator2::field` module provides a type alias +//! `curve25519_elligator2::field::FieldElement` to a field element type +//! defined in the `backend` module; either `FieldElement51` or +//! `FieldElement2625`. +//! +//! Field operations defined in terms of machine +//! operations, such as field multiplication or squaring, are defined in +//! the backend implementation. +//! +//! Field operations defined in terms of other field operations, such as +//! field inversion or square roots, are defined here. + +#![allow(unused_qualifications)] + +use cfg_if::cfg_if; + +use subtle::Choice; +use subtle::ConditionallyNegatable; +use subtle::ConditionallySelectable; +use subtle::{ConstantTimeEq, ConstantTimeGreater}; + +use crate::backend; +use crate::constants; + +cfg_if! { + if #[cfg(curve25519_dalek_backend = "fiat")] { + /// A `FieldElement` represents an element of the field + /// \\( \mathbb Z / (2\^{255} - 19)\\). + /// + /// The `FieldElement` type is an alias for one of the platform-specific + /// implementations. + /// + /// Using formally-verified field arithmetic from fiat-crypto. + #[cfg(curve25519_dalek_bits = "32")] + pub(crate) type FieldElement = backend::serial::fiat_u32::field::FieldElement2625; + + /// A `FieldElement` represents an element of the field + /// \\( \mathbb Z / (2\^{255} - 19)\\). + /// + /// The `FieldElement` type is an alias for one of the platform-specific + /// implementations. + /// + /// Using formally-verified field arithmetic from fiat-crypto. + #[cfg(curve25519_dalek_bits = "64")] + pub(crate) type FieldElement = backend::serial::fiat_u64::field::FieldElement51; + } else if #[cfg(curve25519_dalek_bits = "64")] { + /// A `FieldElement` represents an element of the field + /// \\( \mathbb Z / (2\^{255} - 19)\\). + /// + /// The `FieldElement` type is an alias for one of the platform-specific + /// implementations. + pub(crate) type FieldElement = backend::serial::u64::field::FieldElement51; + } else { + /// A `FieldElement` represents an element of the field + /// \\( \mathbb Z / (2\^{255} - 19)\\). + /// + /// The `FieldElement` type is an alias for one of the platform-specific + /// implementations. + pub(crate) type FieldElement = backend::serial::u32::field::FieldElement2625; + } +} + +impl Eq for FieldElement {} + +impl PartialEq for FieldElement { + fn eq(&self, other: &FieldElement) -> bool { + self.ct_eq(other).into() + } +} + +impl ConstantTimeEq for FieldElement { + /// Test equality between two `FieldElement`s. Since the + /// internal representation is not canonical, the field elements + /// are normalized to wire format before comparison. + fn ct_eq(&self, other: &FieldElement) -> Choice { + self.as_bytes().ct_eq(&other.as_bytes()) + } +} + +impl FieldElement { + /// Determine if this `FieldElement` is negative, in the sense + /// used in the ed25519 paper: `x` is negative if the low bit is + /// set. + /// + /// # Return + /// + /// If negative, return `Choice(1)`. Otherwise, return `Choice(0)`. + pub(crate) fn is_negative(&self) -> Choice { + let bytes = self.as_bytes(); + (bytes[0] & 1).into() + } + + /// Determine if this `FieldElement` is zero. + /// + /// # Return + /// + /// If zero, return `Choice(1)`. Otherwise, return `Choice(0)`. + pub(crate) fn is_zero(&self) -> Choice { + let zero = [0u8; 32]; + let bytes = self.as_bytes(); + + bytes.ct_eq(&zero) + } + + /// Compute (self^(2^250-1), self^11), used as a helper function + /// within invert() and pow22523(). + #[rustfmt::skip] // keep alignment of explanatory comments + fn pow22501(&self) -> (FieldElement, FieldElement) { + // Instead of managing which temporary variables are used + // for what, we define as many as we need and leave stack + // allocation to the compiler + // + // Each temporary variable t_i is of the form (self)^e_i. + // Squaring t_i corresponds to multiplying e_i by 2, + // so the pow2k function shifts e_i left by k places. + // Multiplying t_i and t_j corresponds to adding e_i + e_j. + // + // Temporary t_i Nonzero bits of e_i + // + let t0 = self.square(); // 1 e_0 = 2^1 + let t1 = t0.square().square(); // 3 e_1 = 2^3 + let t2 = self * &t1; // 3,0 e_2 = 2^3 + 2^0 + let t3 = &t0 * &t2; // 3,1,0 + let t4 = t3.square(); // 4,2,1 + let t5 = &t2 * &t4; // 4,3,2,1,0 + let t6 = t5.pow2k(5); // 9,8,7,6,5 + let t7 = &t6 * &t5; // 9,8,7,6,5,4,3,2,1,0 + let t8 = t7.pow2k(10); // 19..10 + let t9 = &t8 * &t7; // 19..0 + let t10 = t9.pow2k(20); // 39..20 + let t11 = &t10 * &t9; // 39..0 + let t12 = t11.pow2k(10); // 49..10 + let t13 = &t12 * &t7; // 49..0 + let t14 = t13.pow2k(50); // 99..50 + let t15 = &t14 * &t13; // 99..0 + let t16 = t15.pow2k(100); // 199..100 + let t17 = &t16 * &t15; // 199..0 + let t18 = t17.pow2k(50); // 249..50 + let t19 = &t18 * &t13; // 249..0 + + (t19, t3) + } + + /// Given a slice of pub(crate)lic `FieldElements`, replace each with its inverse. + /// + /// When an input `FieldElement` is zero, its value is unchanged. + #[cfg(feature = "alloc")] + pub(crate) fn batch_invert(inputs: &mut [FieldElement]) { + // Montgomery’s Trick and Fast Implementation of Masked AES + // Genelle, Prouff and Quisquater + // Section 3.2 + + let n = inputs.len(); + let mut scratch = vec![FieldElement::ONE; n]; + + // Keep an accumulator of all of the previous products + let mut acc = FieldElement::ONE; + + // Pass through the input vector, recording the previous + // products in the scratch space + for (input, scratch) in inputs.iter().zip(scratch.iter_mut()) { + *scratch = acc; + // acc <- acc * input, but skipping zeros (constant-time) + acc.conditional_assign(&(&acc * input), !input.is_zero()); + } + + // acc is nonzero because we skipped zeros in inputs + assert!(bool::from(!acc.is_zero())); + + // Compute the inverse of all products + acc = acc.invert(); + + // Pass through the vector backwards to compute the inverses + // in place + for (input, scratch) in inputs.iter_mut().rev().zip(scratch.into_iter().rev()) { + let tmp = &acc * input; + // input <- acc * scratch, then acc <- tmp + // Again, we skip zeros in a constant-time way + let nz = !input.is_zero(); + input.conditional_assign(&(&acc * &scratch), nz); + acc.conditional_assign(&tmp, nz); + } + } + + /// Given a nonzero field element, compute its inverse. + /// + /// The inverse is computed as self^(p-2), since + /// x^(p-2)x = x^(p-1) = 1 (mod p). + /// + /// This function returns zero on input zero. + #[rustfmt::skip] // keep alignment of explanatory comments + #[allow(clippy::let_and_return)] + pub(crate) fn invert(&self) -> FieldElement { + // The bits of p-2 = 2^255 -19 -2 are 11010111111...11. + // + // nonzero bits of exponent + let (t19, t3) = self.pow22501(); // t19: 249..0 ; t3: 3,1,0 + let t20 = t19.pow2k(5); // 254..5 + let t21 = &t20 * &t3; // 254..5,3,1,0 + + t21 + } + + /// Raise this field element to the power (p-5)/8 = 2^252 -3. + #[rustfmt::skip] // keep alignment of explanatory comments + #[allow(clippy::let_and_return)] + pub(crate) fn pow_p58(&self) -> FieldElement { + // The bits of (p-5)/8 are 101111.....11. + // + // nonzero bits of exponent + let (t19, _) = self.pow22501(); // 249..0 + let t20 = t19.pow2k(2); // 251..2 + let t21 = self * &t20; // 251..2,0 + + t21 + } + + /// Given `FieldElements` `u` and `v`, compute either `sqrt(u/v)` + /// or `sqrt(i*u/v)` in constant time. + /// + /// This function always returns the nonnegative square root. + /// + /// # Return + /// + /// - `(Choice(1), +sqrt(u/v)) ` if `v` is nonzero and `u/v` is square; + /// - `(Choice(1), zero) ` if `u` is zero; + /// - `(Choice(0), zero) ` if `v` is zero and `u` is nonzero; + /// - `(Choice(0), +sqrt(i*u/v))` if `u/v` is nonsquare (so `i*u/v` is square). + /// + pub(crate) fn sqrt_ratio_i(u: &FieldElement, v: &FieldElement) -> (Choice, FieldElement) { + // Using the same trick as in ed25519 decoding, we merge the + // inversion, the square root, and the square test as follows. + // + // To compute sqrt(α), we can compute β = α^((p+3)/8). + // Then β^2 = ±α, so multiplying β by sqrt(-1) if necessary + // gives sqrt(α). + // + // To compute 1/sqrt(α), we observe that + // 1/β = α^(p-1 - (p+3)/8) = α^((7p-11)/8) + // = α^3 * (α^7)^((p-5)/8). + // + // We can therefore compute sqrt(u/v) = sqrt(u)/sqrt(v) + // by first computing + // r = u^((p+3)/8) v^(p-1-(p+3)/8) + // = u u^((p-5)/8) v^3 (v^7)^((p-5)/8) + // = (uv^3) (uv^7)^((p-5)/8). + // + // If v is nonzero and u/v is square, then r^2 = ±u/v, + // so vr^2 = ±u. + // If vr^2 = u, then sqrt(u/v) = r. + // If vr^2 = -u, then sqrt(u/v) = r*sqrt(-1). + // + // If v is zero, r is also zero. + + let v3 = &v.square() * v; + let v7 = &v3.square() * v; + let mut r = &(u * &v3) * &(u * &v7).pow_p58(); + let check = v * &r.square(); + + let i = &constants::SQRT_M1; + + let correct_sign_sqrt = check.ct_eq(u); + let flipped_sign_sqrt = check.ct_eq(&(-u)); + let flipped_sign_sqrt_i = check.ct_eq(&(&(-u) * i)); + + let r_prime = &constants::SQRT_M1 * &r; + r.conditional_assign(&r_prime, flipped_sign_sqrt | flipped_sign_sqrt_i); + + // Choose the nonnegative square root. + let r_is_negative = r.is_negative(); + r.conditional_negate(r_is_negative); + + let was_nonzero_square = correct_sign_sqrt | flipped_sign_sqrt; + + (was_nonzero_square, r) + } + + /// Attempt to compute `sqrt(1/self)` in constant time. + /// + /// Convenience wrapper around `sqrt_ratio_i`. + /// + /// This function always returns the nonnegative square root. + /// + /// # Return + /// + /// - `(Choice(1), +sqrt(1/self)) ` if `self` is a nonzero square; + /// - `(Choice(0), zero) ` if `self` is zero; + /// - `(Choice(0), +sqrt(i/self)) ` if `self` is a nonzero nonsquare; + /// + pub(crate) fn invsqrt(&self) -> (Choice, FieldElement) { + FieldElement::sqrt_ratio_i(&FieldElement::ONE, self) + } +} + +impl ConstantTimeGreater for FieldElement { + /// Test equality between two `FieldElement`s. Since the + /// internal representation is not canonical, the field elements + /// are normalized to wire format before comparison. + /// + /// If self > other return Choice(1), otherwise return Choice(0) + /// + fn ct_gt(&self, other: &FieldElement) -> Choice { + self.gt(other) + } +} + +#[cfg(test)] +mod test { + use crate::field::*; + + /// Random element a of GF(2^255-19), from Sage + /// a = 1070314506888354081329385823235218444233221\ + /// 2228051251926706380353716438957572 + static A_BYTES: [u8; 32] = [ + 0x04, 0xfe, 0xdf, 0x98, 0xa7, 0xfa, 0x0a, 0x68, 0x84, 0x92, 0xbd, 0x59, 0x08, 0x07, 0xa7, + 0x03, 0x9e, 0xd1, 0xf6, 0xf2, 0xe1, 0xd9, 0xe2, 0xa4, 0xa4, 0x51, 0x47, 0x36, 0xf3, 0xc3, + 0xa9, 0x17, + ]; + + /// Byte representation of a**2 + static ASQ_BYTES: [u8; 32] = [ + 0x75, 0x97, 0x24, 0x9e, 0xe6, 0x06, 0xfe, 0xab, 0x24, 0x04, 0x56, 0x68, 0x07, 0x91, 0x2d, + 0x5d, 0x0b, 0x0f, 0x3f, 0x1c, 0xb2, 0x6e, 0xf2, 0xe2, 0x63, 0x9c, 0x12, 0xba, 0x73, 0x0b, + 0xe3, 0x62, + ]; + + /// Byte representation of 1/a + static AINV_BYTES: [u8; 32] = [ + 0x96, 0x1b, 0xcd, 0x8d, 0x4d, 0x5e, 0xa2, 0x3a, 0xe9, 0x36, 0x37, 0x93, 0xdb, 0x7b, 0x4d, + 0x70, 0xb8, 0x0d, 0xc0, 0x55, 0xd0, 0x4c, 0x1d, 0x7b, 0x90, 0x71, 0xd8, 0xe9, 0xb6, 0x18, + 0xe6, 0x30, + ]; + + /// Byte representation of a^((p-5)/8) + static AP58_BYTES: [u8; 32] = [ + 0x6a, 0x4f, 0x24, 0x89, 0x1f, 0x57, 0x60, 0x36, 0xd0, 0xbe, 0x12, 0x3c, 0x8f, 0xf5, 0xb1, + 0x59, 0xe0, 0xf0, 0xb8, 0x1b, 0x20, 0xd2, 0xb5, 0x1f, 0x15, 0x21, 0xf9, 0xe3, 0xe1, 0x61, + 0x21, 0x55, + ]; + + #[test] + fn a_mul_a_vs_a_squared_constant() { + let a = FieldElement::from_bytes(&A_BYTES); + let asq = FieldElement::from_bytes(&ASQ_BYTES); + assert_eq!(asq, &a * &a); + } + + #[test] + fn a_square_vs_a_squared_constant() { + let a = FieldElement::from_bytes(&A_BYTES); + let asq = FieldElement::from_bytes(&ASQ_BYTES); + assert_eq!(asq, a.square()); + } + + #[test] + fn a_square2_vs_a_squared_constant() { + let a = FieldElement::from_bytes(&A_BYTES); + let asq = FieldElement::from_bytes(&ASQ_BYTES); + assert_eq!(a.square2(), &asq + &asq); + } + + #[test] + fn a_invert_vs_inverse_of_a_constant() { + let a = FieldElement::from_bytes(&A_BYTES); + let ainv = FieldElement::from_bytes(&AINV_BYTES); + let should_be_inverse = a.invert(); + assert_eq!(ainv, should_be_inverse); + assert_eq!(FieldElement::ONE, &a * &should_be_inverse); + } + + #[test] + #[cfg(feature = "alloc")] + fn batch_invert_a_matches_nonbatched() { + let a = FieldElement::from_bytes(&A_BYTES); + let ap58 = FieldElement::from_bytes(&AP58_BYTES); + let asq = FieldElement::from_bytes(&ASQ_BYTES); + let ainv = FieldElement::from_bytes(&AINV_BYTES); + let a0 = &a - &a; + let a2 = &a + &a; + let a_list = vec![a, ap58, asq, ainv, a0, a2]; + let mut ainv_list = a_list.clone(); + FieldElement::batch_invert(&mut ainv_list[..]); + for i in 0..6 { + assert_eq!(a_list[i].invert(), ainv_list[i]); + } + } + + #[test] + fn sqrt_ratio_behavior() { + let zero = FieldElement::ZERO; + let one = FieldElement::ONE; + let i = constants::SQRT_M1; + let two = &one + &one; // 2 is nonsquare mod p. + let four = &two + &two; // 4 is square mod p. + + // 0/0 should return (1, 0) since u is 0 + let (choice, sqrt) = FieldElement::sqrt_ratio_i(&zero, &zero); + assert!(bool::from(choice)); + assert_eq!(sqrt, zero); + assert!(bool::from(!sqrt.is_negative())); + + // 1/0 should return (0, 0) since v is 0, u is nonzero + let (choice, sqrt) = FieldElement::sqrt_ratio_i(&one, &zero); + assert!(bool::from(!choice)); + assert_eq!(sqrt, zero); + assert!(bool::from(!sqrt.is_negative())); + + // 2/1 is nonsquare, so we expect (0, sqrt(i*2)) + let (choice, sqrt) = FieldElement::sqrt_ratio_i(&two, &one); + assert!(bool::from(!choice)); + assert_eq!(sqrt.square(), &two * &i); + assert!(bool::from(!sqrt.is_negative())); + + // 4/1 is square, so we expect (1, sqrt(4)) + let (choice, sqrt) = FieldElement::sqrt_ratio_i(&four, &one); + assert!(bool::from(choice)); + assert_eq!(sqrt.square(), four); + assert!(bool::from(!sqrt.is_negative())); + + // 1/4 is square, so we expect (1, 1/sqrt(4)) + let (choice, sqrt) = FieldElement::sqrt_ratio_i(&one, &four); + assert!(bool::from(choice)); + assert_eq!(&sqrt.square() * &four, one); + assert!(bool::from(!sqrt.is_negative())); + } + + #[test] + fn a_p58_vs_ap58_constant() { + let a = FieldElement::from_bytes(&A_BYTES); + let ap58 = FieldElement::from_bytes(&AP58_BYTES); + assert_eq!(ap58, a.pow_p58()); + } + + #[test] + fn equality() { + let a = FieldElement::from_bytes(&A_BYTES); + let ainv = FieldElement::from_bytes(&AINV_BYTES); + assert!(a == a); + assert!(a != ainv); + } + + /// Notice that the last element has the high bit set, which + /// should be ignored + static B_BYTES: [u8; 32] = [ + 113, 191, 169, 143, 91, 234, 121, 15, 241, 131, 217, 36, 230, 101, 92, 234, 8, 208, 170, + 251, 97, 127, 70, 210, 58, 23, 166, 87, 240, 169, 184, 178, + ]; + + #[test] + fn from_bytes_highbit_is_ignored() { + let mut cleared_bytes = B_BYTES; + cleared_bytes[31] &= 127u8; + let with_highbit_set = FieldElement::from_bytes(&B_BYTES); + let without_highbit_set = FieldElement::from_bytes(&cleared_bytes); + assert_eq!(without_highbit_set, with_highbit_set); + } + + #[test] + fn conditional_negate() { + let one = FieldElement::ONE; + let minus_one = FieldElement::MINUS_ONE; + let mut x = one; + x.conditional_negate(Choice::from(1)); + assert_eq!(x, minus_one); + x.conditional_negate(Choice::from(0)); + assert_eq!(x, minus_one); + x.conditional_negate(Choice::from(1)); + assert_eq!(x, one); + } + + #[test] + fn encoding_is_canonical() { + // Encode 1 wrongly as 1 + (2^255 - 19) = 2^255 - 18 + let one_encoded_wrongly_bytes: [u8; 32] = [ + 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7f, + ]; + // Decode to a field element + let one = FieldElement::from_bytes(&one_encoded_wrongly_bytes); + // .. then check that the encoding is correct + let one_bytes = one.as_bytes(); + assert_eq!(one_bytes[0], 1); + for byte in &one_bytes[1..] { + assert_eq!(*byte, 0); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn batch_invert_empty() { + FieldElement::batch_invert(&mut []); + } +} diff --git a/curve25519-elligator2/src/lib.rs b/curve25519-elligator2/src/lib.rs new file mode 100644 index 00000000..0489a4a0 --- /dev/null +++ b/curve25519-elligator2/src/lib.rs @@ -0,0 +1,119 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +#![no_std] +#![cfg_attr( + all( + curve25519_dalek_backend = "simd", + nightly, + any(target_arch = "x86", target_arch = "x86_64") + ), + feature(stdarch_x86_avx512) +)] +#![cfg_attr( + all(curve25519_dalek_backend = "simd", nightly), + feature(avx512_target_feature) +)] +#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg, doc_cfg_hide))] +#![cfg_attr(docsrs, doc(cfg_hide(docsrs)))] +//------------------------------------------------------------------------ +// Documentation: +//------------------------------------------------------------------------ +#![doc( + html_logo_url = "https://cdn.jsdelivr.net/gh/dalek-cryptography/curve25519-dalek/docs/assets/dalek-logo-clear.png" +)] +#![doc = include_str!("../README.md")] +//------------------------------------------------------------------------ +// Linting: +//------------------------------------------------------------------------ +#![cfg_attr(allow_unused_unsafe, allow(unused_unsafe))] +#![warn( + clippy::unwrap_used, + missing_docs, + rust_2018_idioms, + unused_lifetimes, + unused_qualifications +)] + +//------------------------------------------------------------------------ +// External dependencies: +//------------------------------------------------------------------------ + +#[cfg(feature = "alloc")] +#[allow(unused_imports)] +#[macro_use] +extern crate alloc; + +// TODO: move std-dependent tests to `tests/` +#[cfg(test)] +#[macro_use] +extern crate std; + +#[cfg(feature = "digest")] +pub use digest; + +// Internal macros. Must come first! +#[macro_use] +pub(crate) mod macros; + +//------------------------------------------------------------------------ +// curve25519-elligator2 public modules +//------------------------------------------------------------------------ + +// Scalar arithmetic mod l = 2^252 + ..., the order of the Ristretto group +pub mod scalar; + +// Point operations on the Montgomery form of Curve25519 +pub mod montgomery; + +// Point operations on the Edwards form of Curve25519 +pub mod edwards; + +// Group operations on the Ristretto group +pub mod ristretto; + +// Useful constants, like the Ed25519 basepoint +pub mod constants; + +// External (and internal) traits. +pub mod traits; + +//------------------------------------------------------------------------ +// curve25519-elligator2 internal modules +//------------------------------------------------------------------------ + +// Finite field arithmetic mod p = 2^255 - 19 +pub(crate) mod field; + +// elligator2 implementation for encoding/decoding points to representatives and +// vice versa. +#[cfg(feature = "elligator2")] +pub mod elligator2; + +// Arithmetic backends (using u32, u64, etc) live here +#[cfg(docsrs)] +pub mod backend; +#[cfg(not(docsrs))] +pub(crate) mod backend; + +// Generic code for window lookups +pub(crate) mod window; + +pub use crate::{ + edwards::EdwardsPoint, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar, +}; + +#[cfg(feature = "elligator2")] +pub use elligator2::{representative_from_privkey, MapToPointVariant, Randomized, RFC9380}; + +// Build time diagnostics for validation +#[cfg(curve25519_dalek_diagnostics = "build")] +mod diagnostics; diff --git a/curve25519-elligator2/src/macros.rs b/curve25519-elligator2/src/macros.rs new file mode 100644 index 00000000..da79c15c --- /dev/null +++ b/curve25519-elligator2/src/macros.rs @@ -0,0 +1,123 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis agora lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Internal macros. + +/// Define borrow and non-borrow variants of `Add`. +macro_rules! define_add_variants { + (LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => { + impl<'b> Add<&'b $rhs> for $lhs { + type Output = $out; + fn add(self, rhs: &'b $rhs) -> $out { + &self + rhs + } + } + + impl<'a> Add<$rhs> for &'a $lhs { + type Output = $out; + fn add(self, rhs: $rhs) -> $out { + self + &rhs + } + } + + impl Add<$rhs> for $lhs { + type Output = $out; + fn add(self, rhs: $rhs) -> $out { + &self + &rhs + } + } + }; +} + +/// Define non-borrow variants of `AddAssign`. +macro_rules! define_add_assign_variants { + (LHS = $lhs:ty, RHS = $rhs:ty) => { + impl AddAssign<$rhs> for $lhs { + fn add_assign(&mut self, rhs: $rhs) { + *self += &rhs; + } + } + }; +} + +/// Define borrow and non-borrow variants of `Sub`. +macro_rules! define_sub_variants { + (LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => { + impl<'b> Sub<&'b $rhs> for $lhs { + type Output = $out; + fn sub(self, rhs: &'b $rhs) -> $out { + &self - rhs + } + } + + impl<'a> Sub<$rhs> for &'a $lhs { + type Output = $out; + fn sub(self, rhs: $rhs) -> $out { + self - &rhs + } + } + + impl Sub<$rhs> for $lhs { + type Output = $out; + fn sub(self, rhs: $rhs) -> $out { + &self - &rhs + } + } + }; +} + +/// Define non-borrow variants of `SubAssign`. +macro_rules! define_sub_assign_variants { + (LHS = $lhs:ty, RHS = $rhs:ty) => { + impl SubAssign<$rhs> for $lhs { + fn sub_assign(&mut self, rhs: $rhs) { + *self -= &rhs; + } + } + }; +} + +/// Define borrow and non-borrow variants of `Mul`. +macro_rules! define_mul_variants { + (LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => { + impl<'b> Mul<&'b $rhs> for $lhs { + type Output = $out; + fn mul(self, rhs: &'b $rhs) -> $out { + &self * rhs + } + } + + impl<'a> Mul<$rhs> for &'a $lhs { + type Output = $out; + fn mul(self, rhs: $rhs) -> $out { + self * &rhs + } + } + + impl Mul<$rhs> for $lhs { + type Output = $out; + fn mul(self, rhs: $rhs) -> $out { + &self * &rhs + } + } + }; +} + +/// Define non-borrow variants of `MulAssign`. +macro_rules! define_mul_assign_variants { + (LHS = $lhs:ty, RHS = $rhs:ty) => { + impl MulAssign<$rhs> for $lhs { + fn mul_assign(&mut self, rhs: $rhs) { + *self *= &rhs; + } + } + }; +} diff --git a/curve25519-elligator2/src/montgomery.rs b/curve25519-elligator2/src/montgomery.rs new file mode 100644 index 00000000..03ad9d12 --- /dev/null +++ b/curve25519-elligator2/src/montgomery.rs @@ -0,0 +1,633 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Scalar multiplication on the Montgomery form of Curve25519. +//! +//! To avoid notational confusion with the Edwards code, we use +//! variables \\( u, v \\) for the Montgomery curve, so that “Montgomery +//! \\(u\\)” here corresponds to “Montgomery \\(x\\)” elsewhere. +//! +//! Montgomery arithmetic works not on the curve itself, but on the +//! \\(u\\)-line, which discards sign information and unifies the curve +//! and its quadratic twist. See [_Montgomery curves and their +//! arithmetic_][costello-smith] by Costello and Smith for more details. +//! +//! The `MontgomeryPoint` struct contains the affine \\(u\\)-coordinate +//! \\(u\_0(P)\\) of a point \\(P\\) on either the curve or the twist. +//! Here the map \\(u\_0 : \mathcal M \rightarrow \mathbb F\_p \\) is +//! defined by \\(u\_0((u,v)) = u\\); \\(u\_0(\mathcal O) = 0\\). See +//! section 5.4 of Costello-Smith for more details. +//! +//! # Scalar Multiplication +//! +//! Scalar multiplication on `MontgomeryPoint`s is provided by the `*` +//! operator, which implements the Montgomery ladder. +//! +//! # Edwards Conversion +//! +//! The \\(2\\)-to-\\(1\\) map from the Edwards model to the Montgomery +//! \\(u\\)-line is provided by `EdwardsPoint::to_montgomery()`. +//! +//! To lift a `MontgomeryPoint` to an `EdwardsPoint`, use +//! `MontgomeryPoint::to_edwards()`, which takes a sign parameter. +//! This function rejects `MontgomeryPoints` which correspond to points +//! on the twist. +//! +//! [costello-smith]: https://eprint.iacr.org/2017/212.pdf + +// We allow non snake_case names because coordinates in projective space are +// traditionally denoted by the capitalisation of their respective +// counterparts in affine space. Yeah, you heard me, rustc, I'm gonna have my +// affine and projective cakes and eat both of them too. +#![allow(non_snake_case)] + +use core::{ + hash::{Hash, Hasher}, + ops::{Mul, MulAssign}, +}; + +use crate::constants::APLUS2_OVER_FOUR; +use crate::edwards::{CompressedEdwardsY, EdwardsPoint}; +use crate::field::FieldElement; +use crate::scalar::{clamp_integer, Scalar}; +use crate::traits::Identity; + +use subtle::Choice; +use subtle::ConditionallySelectable; +use subtle::ConstantTimeEq; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +/// Holds the \\(u\\)-coordinate of a point on the Montgomery form of +/// Curve25519 or its twist. +#[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MontgomeryPoint(pub [u8; 32]); + +/// Equality of `MontgomeryPoint`s is defined mod p. +impl ConstantTimeEq for MontgomeryPoint { + fn ct_eq(&self, other: &MontgomeryPoint) -> Choice { + let self_fe = FieldElement::from_bytes(&self.0); + let other_fe = FieldElement::from_bytes(&other.0); + + self_fe.ct_eq(&other_fe) + } +} + +impl PartialEq for MontgomeryPoint { + fn eq(&self, other: &MontgomeryPoint) -> bool { + self.ct_eq(other).into() + } +} + +impl Eq for MontgomeryPoint {} + +// Equal MontgomeryPoints must hash to the same value. So we have to get them into a canonical +// encoding first +impl Hash for MontgomeryPoint { + fn hash(&self, state: &mut H) { + // Do a round trip through a `FieldElement`. `as_bytes` is guaranteed to give a canonical + // 32-byte encoding + let canonical_bytes = FieldElement::from_bytes(&self.0).as_bytes(); + canonical_bytes.hash(state); + } +} + +impl Identity for MontgomeryPoint { + /// Return the group identity element, which has order 4. + fn identity() -> MontgomeryPoint { + MontgomeryPoint([0u8; 32]) + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for MontgomeryPoint { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl MontgomeryPoint { + /// Fixed-base scalar multiplication (i.e. multiplication by the base point). + pub fn mul_base(scalar: &Scalar) -> Self { + EdwardsPoint::mul_base(scalar).to_montgomery() + } + + /// Multiply this point by `clamp_integer(bytes)`. For a description of clamping, see + /// [`clamp_integer`]. + pub fn mul_clamped(self, bytes: [u8; 32]) -> Self { + // We have to construct a Scalar that is not reduced mod l, which breaks scalar invariant + // #2. But #2 is not necessary for correctness of variable-base multiplication. All that + // needs to hold is invariant #1, i.e., the scalar is less than 2^255. This is guaranteed + // by clamping. + // Further, we don't do any reduction or arithmetic with this clamped value, so there's no + // issues arising from the fact that the curve point is not necessarily in the prime-order + // subgroup. + let s = Scalar { + bytes: clamp_integer(bytes), + }; + s * self + } + + /// Multiply the basepoint by `clamp_integer(bytes)`. For a description of clamping, see + /// [`clamp_integer`]. + pub fn mul_base_clamped(bytes: [u8; 32]) -> Self { + // See reasoning in Self::mul_clamped why it is OK to make an unreduced Scalar here. We + // note that fixed-base multiplication is also defined for all values of `bytes` less than + // 2^255. + let s = Scalar { + bytes: clamp_integer(bytes), + }; + Self::mul_base(&s) + } + + /// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer + /// \\(n\\), return \\( u\_0(\[n\]P) \\). This is constant time in the length of `bits`. + /// + /// **NOTE:** You probably do not want to use this function. Almost every protocol built on + /// Curve25519 uses _clamped multiplication_, explained + /// [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/). + /// When in doubt, use [`Self::mul_clamped`]. + pub fn mul_bits_be(&self, bits: impl Iterator) -> MontgomeryPoint { + // Algorithm 8 of Costello-Smith 2017 + let affine_u = FieldElement::from_bytes(&self.0); + let mut x0 = ProjectivePoint::identity(); + let mut x1 = ProjectivePoint { + U: affine_u, + W: FieldElement::ONE, + }; + + // Go through the bits from most to least significant, using a sliding window of 2 + let mut prev_bit = false; + for cur_bit in bits { + let choice: u8 = (prev_bit ^ cur_bit) as u8; + + debug_assert!(choice == 0 || choice == 1); + + ProjectivePoint::conditional_swap(&mut x0, &mut x1, choice.into()); + differential_add_and_double(&mut x0, &mut x1, &affine_u); + + prev_bit = cur_bit; + } + // The final value of prev_bit above is scalar.bits()[0], i.e., the LSB of scalar + ProjectivePoint::conditional_swap(&mut x0, &mut x1, Choice::from(prev_bit as u8)); + // Don't leave the bit in the stack + #[cfg(feature = "zeroize")] + prev_bit.zeroize(); + + x0.as_affine() + } + + /// View this `MontgomeryPoint` as an array of bytes. + pub const fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + /// Convert this `MontgomeryPoint` to an array of bytes. + pub const fn to_bytes(&self) -> [u8; 32] { + self.0 + } + + /// Attempt to convert to an `EdwardsPoint`, using the supplied + /// choice of sign for the `EdwardsPoint`. + /// + /// # Inputs + /// + /// * `sign`: a `u8` donating the desired sign of the resulting + /// `EdwardsPoint`. `0` denotes positive and `1` negative. + /// + /// # Return + /// + /// * `Some(EdwardsPoint)` if `self` is the \\(u\\)-coordinate of a + /// point on (the Montgomery form of) Curve25519; + /// + /// * `None` if `self` is the \\(u\\)-coordinate of a point on the + /// twist of (the Montgomery form of) Curve25519; + /// + pub fn to_edwards(&self, sign: u8) -> Option { + // To decompress the Montgomery u coordinate to an + // `EdwardsPoint`, we apply the birational map to obtain the + // Edwards y coordinate, then do Edwards decompression. + // + // The birational map is y = (u-1)/(u+1). + // + // The exceptional points are the zeros of the denominator, + // i.e., u = -1. + // + // But when u = -1, v^2 = u*(u^2+486662*u+1) = 486660. + // + // Since this is nonsquare mod p, u = -1 corresponds to a point + // on the twist, not the curve, so we can reject it early. + + let u = FieldElement::from_bytes(&self.0); + + if u == FieldElement::MINUS_ONE { + return None; + } + + let one = FieldElement::ONE; + + let y = &(&u - &one) * &(&u + &one).invert(); + + let mut y_bytes = y.as_bytes(); + y_bytes[31] ^= sign << 7; + + CompressedEdwardsY(y_bytes).decompress() + } +} + +/// A `ProjectivePoint` holds a point on the projective line +/// \\( \mathbb P(\mathbb F\_p) \\), which we identify with the Kummer +/// line of the Montgomery curve. +#[derive(Copy, Clone, Debug)] +struct ProjectivePoint { + pub U: FieldElement, + pub W: FieldElement, +} + +impl Identity for ProjectivePoint { + fn identity() -> ProjectivePoint { + ProjectivePoint { + U: FieldElement::ONE, + W: FieldElement::ZERO, + } + } +} + +impl Default for ProjectivePoint { + fn default() -> ProjectivePoint { + ProjectivePoint::identity() + } +} + +impl ConditionallySelectable for ProjectivePoint { + fn conditional_select( + a: &ProjectivePoint, + b: &ProjectivePoint, + choice: Choice, + ) -> ProjectivePoint { + ProjectivePoint { + U: FieldElement::conditional_select(&a.U, &b.U, choice), + W: FieldElement::conditional_select(&a.W, &b.W, choice), + } + } +} + +impl ProjectivePoint { + /// Dehomogenize this point to affine coordinates. + /// + /// # Return + /// + /// * \\( u = U / W \\) if \\( W \neq 0 \\); + /// * \\( 0 \\) if \\( W \eq 0 \\); + pub fn as_affine(&self) -> MontgomeryPoint { + let u = &self.U * &self.W.invert(); + MontgomeryPoint(u.as_bytes()) + } +} + +/// Perform the double-and-add step of the Montgomery ladder. +/// +/// Given projective points +/// \\( (U\_P : W\_P) = u(P) \\), +/// \\( (U\_Q : W\_Q) = u(Q) \\), +/// and the affine difference +/// \\( u\_{P-Q} = u(P-Q) \\), set +/// $$ +/// (U\_P : W\_P) \gets u(\[2\]P) +/// $$ +/// and +/// $$ +/// (U\_Q : W\_Q) \gets u(P + Q). +/// $$ +#[rustfmt::skip] // keep alignment of explanatory comments +fn differential_add_and_double( + P: &mut ProjectivePoint, + Q: &mut ProjectivePoint, + affine_PmQ: &FieldElement, +) { + let t0 = &P.U + &P.W; + let t1 = &P.U - &P.W; + let t2 = &Q.U + &Q.W; + let t3 = &Q.U - &Q.W; + + let t4 = t0.square(); // (U_P + W_P)^2 = U_P^2 + 2 U_P W_P + W_P^2 + let t5 = t1.square(); // (U_P - W_P)^2 = U_P^2 - 2 U_P W_P + W_P^2 + + let t6 = &t4 - &t5; // 4 U_P W_P + + let t7 = &t0 * &t3; // (U_P + W_P) (U_Q - W_Q) = U_P U_Q + W_P U_Q - U_P W_Q - W_P W_Q + let t8 = &t1 * &t2; // (U_P - W_P) (U_Q + W_Q) = U_P U_Q - W_P U_Q + U_P W_Q - W_P W_Q + + let t9 = &t7 + &t8; // 2 (U_P U_Q - W_P W_Q) + let t10 = &t7 - &t8; // 2 (W_P U_Q - U_P W_Q) + + let t11 = t9.square(); // 4 (U_P U_Q - W_P W_Q)^2 + let t12 = t10.square(); // 4 (W_P U_Q - U_P W_Q)^2 + + let t13 = &APLUS2_OVER_FOUR * &t6; // (A + 2) U_P U_Q + + let t14 = &t4 * &t5; // ((U_P + W_P)(U_P - W_P))^2 = (U_P^2 - W_P^2)^2 + let t15 = &t13 + &t5; // (U_P - W_P)^2 + (A + 2) U_P W_P + + let t16 = &t6 * &t15; // 4 (U_P W_P) ((U_P - W_P)^2 + (A + 2) U_P W_P) + + let t17 = affine_PmQ * &t12; // U_D * 4 (W_P U_Q - U_P W_Q)^2 + let t18 = t11; // W_D * 4 (U_P U_Q - W_P W_Q)^2 + + P.U = t14; // U_{P'} = (U_P + W_P)^2 (U_P - W_P)^2 + P.W = t16; // W_{P'} = (4 U_P W_P) ((U_P - W_P)^2 + ((A + 2)/4) 4 U_P W_P) + Q.U = t18; // U_{Q'} = W_D * 4 (U_P U_Q - W_P W_Q)^2 + Q.W = t17; // W_{Q'} = U_D * 4 (W_P U_Q - U_P W_Q)^2 +} + +define_mul_assign_variants!(LHS = MontgomeryPoint, RHS = Scalar); + +define_mul_variants!( + LHS = MontgomeryPoint, + RHS = Scalar, + Output = MontgomeryPoint +); +define_mul_variants!( + LHS = Scalar, + RHS = MontgomeryPoint, + Output = MontgomeryPoint +); + +/// Multiply this `MontgomeryPoint` by a `Scalar`. +impl Mul<&Scalar> for &MontgomeryPoint { + type Output = MontgomeryPoint; + + /// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\) + fn mul(self, scalar: &Scalar) -> MontgomeryPoint { + // We multiply by the integer representation of the given Scalar. By scalar invariant #1, + // the MSB is 0, so we can skip it. + self.mul_bits_be(scalar.bits_le().rev().skip(1)) + } +} + +impl MulAssign<&Scalar> for MontgomeryPoint { + fn mul_assign(&mut self, scalar: &Scalar) { + *self = (self as &MontgomeryPoint) * scalar; + } +} + +impl Mul<&MontgomeryPoint> for &Scalar { + type Output = MontgomeryPoint; + + fn mul(self, point: &MontgomeryPoint) -> MontgomeryPoint { + point * self + } +} + +// ------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------ + +#[cfg(test)] +mod test { + use super::*; + use crate::constants; + + #[cfg(feature = "alloc")] + #[cfg(feature = "elligator2")] + use alloc::vec::Vec; + + use rand_core::{CryptoRng, RngCore}; + + #[test] + fn identity_in_different_coordinates() { + let id_projective = ProjectivePoint::identity(); + let id_montgomery = id_projective.as_affine(); + + assert!(id_montgomery == MontgomeryPoint::identity()); + } + + #[test] + fn identity_in_different_models() { + assert!(EdwardsPoint::identity().to_montgomery() == MontgomeryPoint::identity()); + } + + #[test] + #[cfg(feature = "serde")] + fn serde_bincode_basepoint_roundtrip() { + use bincode; + + let encoded = bincode::serialize(&constants::X25519_BASEPOINT).unwrap(); + let decoded: MontgomeryPoint = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(encoded.len(), 32); + assert_eq!(decoded, constants::X25519_BASEPOINT); + + let raw_bytes = constants::X25519_BASEPOINT.as_bytes(); + let bp: MontgomeryPoint = bincode::deserialize(raw_bytes).unwrap(); + assert_eq!(bp, constants::X25519_BASEPOINT); + } + + /// Test Montgomery -> Edwards on the X/Ed25519 basepoint + #[test] + fn basepoint_montgomery_to_edwards() { + // sign bit = 0 => basepoint + assert_eq!( + constants::ED25519_BASEPOINT_POINT, + constants::X25519_BASEPOINT.to_edwards(0).unwrap() + ); + // sign bit = 1 => minus basepoint + assert_eq!( + -constants::ED25519_BASEPOINT_POINT, + constants::X25519_BASEPOINT.to_edwards(1).unwrap() + ); + } + + /// Test Edwards -> Montgomery on the X/Ed25519 basepoint + #[test] + fn basepoint_edwards_to_montgomery() { + assert_eq!( + constants::ED25519_BASEPOINT_POINT.to_montgomery(), + constants::X25519_BASEPOINT + ); + } + + /// Check that Montgomery -> Edwards fails for points on the twist. + #[test] + fn montgomery_to_edwards_rejects_twist() { + let one = FieldElement::ONE; + + // u = 2 corresponds to a point on the twist. + let two = MontgomeryPoint((&one + &one).as_bytes()); + + assert!(two.to_edwards(0).is_none()); + + // u = -1 corresponds to a point on the twist, but should be + // checked explicitly because it's an exceptional point for the + // birational map. For instance, libsignal will accept it. + let minus_one = MontgomeryPoint((-&one).as_bytes()); + + assert!(minus_one.to_edwards(0).is_none()); + } + + #[test] + fn eq_defined_mod_p() { + let mut u18_bytes = [0u8; 32]; + u18_bytes[0] = 18; + let u18 = MontgomeryPoint(u18_bytes); + let u18_unred = MontgomeryPoint([255; 32]); + + assert_eq!(u18, u18_unred); + } + + /// Returns a random point on the prime-order subgroup + fn rand_prime_order_point(mut rng: impl RngCore + CryptoRng) -> EdwardsPoint { + let s: Scalar = Scalar::random(&mut rng); + EdwardsPoint::mul_base(&s) + } + + /// Given a bytestring that's little-endian at the byte level, return an iterator over all the + /// bits, in little-endian order. + fn bytestring_bits_le(x: &[u8]) -> impl DoubleEndedIterator + Clone + '_ { + let bitlen = x.len() * 8; + (0..bitlen).map(|i| { + // As i runs from 0..256, the bottom 3 bits index the bit, while the upper bits index + // the byte. Since self.bytes is little-endian at the byte level, this iterator is + // little-endian on the bit level + ((x[i >> 3] >> (i & 7)) & 1u8) == 1 + }) + } + + #[test] + fn montgomery_ladder_matches_edwards_scalarmult() { + let mut csprng = rand_core::OsRng; + + for _ in 0..100 { + let p_edwards = rand_prime_order_point(&mut csprng); + let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery(); + + let s: Scalar = Scalar::random(&mut csprng); + let expected = s * p_edwards; + let result = s * p_montgomery; + + assert_eq!(result, expected.to_montgomery()) + } + } + + // Tests that, on the prime-order subgroup, MontgomeryPoint::mul_bits_be is the same as + // multiplying by the Scalar representation of the same bits + #[test] + fn montgomery_mul_bits_be() { + let mut csprng = rand_core::OsRng; + + for _ in 0..100 { + // Make a random prime-order point P + let p_edwards = rand_prime_order_point(&mut csprng); + let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery(); + + // Make a random integer b + let mut bigint = [0u8; 64]; + csprng.fill_bytes(&mut bigint[..]); + let bigint_bits_be = bytestring_bits_le(&bigint).rev(); + + // Check that bP is the same whether calculated as scalar-times-edwards or + // integer-times-montgomery. + let expected = Scalar::from_bytes_mod_order_wide(&bigint) * p_edwards; + let result = p_montgomery.mul_bits_be(bigint_bits_be); + assert_eq!(result, expected.to_montgomery()) + } + } + + // Tests that MontgomeryPoint::mul_bits_be is consistent on any point, even ones that might be + // on the curve's twist. Specifically, this tests that b₁(b₂P) == b₂(b₁P) for random + // integers b₁, b₂ and random (curve or twist) point P. + #[test] + fn montgomery_mul_bits_be_twist() { + let mut csprng = rand_core::OsRng; + + for _ in 0..100 { + // Make a random point P on the curve or its twist + let p_montgomery = { + let mut buf = [0u8; 32]; + csprng.fill_bytes(&mut buf); + MontgomeryPoint(buf) + }; + + // Compute two big integers b₁ and b₂ + let mut bigint1 = [0u8; 64]; + let mut bigint2 = [0u8; 64]; + csprng.fill_bytes(&mut bigint1[..]); + csprng.fill_bytes(&mut bigint2[..]); + + // Compute b₁P and b₂P + let bigint1_bits_be = bytestring_bits_le(&bigint1).rev(); + let bigint2_bits_be = bytestring_bits_le(&bigint2).rev(); + let prod1 = p_montgomery.mul_bits_be(bigint1_bits_be.clone()); + let prod2 = p_montgomery.mul_bits_be(bigint2_bits_be.clone()); + + // Check that b₁(b₂P) == b₂(b₁P) + assert_eq!( + prod1.mul_bits_be(bigint2_bits_be), + prod2.mul_bits_be(bigint1_bits_be) + ); + } + } + + /// Check that mul_base_clamped and mul_clamped agree + #[test] + fn mul_base_clamped() { + let mut csprng = rand_core::OsRng; + + // Test agreement on a large integer. Even after clamping, this is not reduced mod l. + let a_bytes = [0xff; 32]; + assert_eq!( + MontgomeryPoint::mul_base_clamped(a_bytes), + constants::X25519_BASEPOINT.mul_clamped(a_bytes) + ); + + // Test agreement on random integers + for _ in 0..100 { + // This will be reduced mod l with probability l / 2^256 ≈ 6.25% + let mut a_bytes = [0u8; 32]; + csprng.fill_bytes(&mut a_bytes); + + assert_eq!( + MontgomeryPoint::mul_base_clamped(a_bytes), + constants::X25519_BASEPOINT.mul_clamped(a_bytes) + ); + } + } + + #[cfg(test)] + #[cfg(feature = "alloc")] + #[cfg(feature = "elligator2")] + const ELLIGATOR_CORRECT_OUTPUT: [u8; 32] = [ + 0x5f, 0x35, 0x20, 0x00, 0x1c, 0x6c, 0x99, 0x36, 0xa3, 0x12, 0x06, 0xaf, 0xe7, 0xc7, 0xac, + 0x22, 0x4e, 0x88, 0x61, 0x61, 0x9b, 0xf9, 0x88, 0x72, 0x44, 0x49, 0x15, 0x89, 0x9d, 0x95, + 0xf4, 0x6e, + ]; + + #[test] + #[cfg(feature = "alloc")] + #[cfg(feature = "elligator2")] + fn montgomery_elligator_correct() { + let bytes: Vec = (0u8..32u8).collect(); + let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken"); + + let eg = MontgomeryPoint::map_to_point(&bits_in); + assert_eq!(eg.0, ELLIGATOR_CORRECT_OUTPUT); + } + + #[test] + #[cfg(feature = "elligator2")] + fn montgomery_elligator_zero_zero() { + let zero = [0u8; 32]; + let eg = MontgomeryPoint::map_to_point(&zero); + assert_eq!(eg.0, zero); + } +} diff --git a/curve25519-elligator2/src/ristretto.rs b/curve25519-elligator2/src/ristretto.rs new file mode 100644 index 00000000..bf2cacad --- /dev/null +++ b/curve25519-elligator2/src/ristretto.rs @@ -0,0 +1,1872 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2020 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +// We allow non snake_case names because coordinates in projective space are +// traditionally denoted by the capitalisation of their respective +// counterparts in affine space. Yeah, you heard me, rustc, I'm gonna have my +// affine and projective cakes and eat both of them too. +#![allow(non_snake_case)] + +//! An implementation of [Ristretto][ristretto_main], which provides a +//! prime-order group. +//! +//! # The Ristretto Group +//! +//! Ristretto is a modification of Mike Hamburg's Decaf scheme to work +//! with cofactor-\\(8\\) curves, such as Curve25519. +//! +//! The introduction of the Decaf paper, [_Decaf: +//! Eliminating cofactors through point +//! compression_](https://eprint.iacr.org/2015/673.pdf), notes that while +//! most cryptographic systems require a group of prime order, most +//! concrete implementations using elliptic curve groups fall short – +//! they either provide a group of prime order, but with incomplete or +//! variable-time addition formulae (for instance, most Weierstrass +//! models), or else they provide a fast and safe implementation of a +//! group whose order is not quite a prime \\(q\\), but \\(hq\\) for a +//! small cofactor \\(h\\) (for instance, Edwards curves, which have +//! cofactor at least \\(4\\)). +//! +//! This abstraction mismatch is commonly “handled” by pushing the +//! complexity upwards, adding ad-hoc protocol modifications. But +//! these modifications require careful analysis and are a recurring +//! source of [vulnerabilities][cryptonote] and [design +//! complications][ed25519_hkd]. +//! +//! Instead, Decaf (and Ristretto) use a quotient group to implement a +//! prime-order group using a non-prime-order curve. This provides +//! the correct abstraction for cryptographic systems, while retaining +//! the speed and safety benefits of an Edwards curve. +//! +//! Decaf is named “after the procedure which divides the effect of +//! coffee by \\(4\\)”. However, Curve25519 has a cofactor of +//! \\(8\\). To eliminate its cofactor, Ristretto restricts further; +//! this [additional restriction][ristretto_coffee] gives the +//! _Ristretto_ encoding. +//! +//! More details on why Ristretto is necessary can be found in the +//! [Why Ristretto?][why_ristretto] section of the Ristretto website. +//! +//! Ristretto +//! points are provided in `curve25519-elligator2` by the `RistrettoPoint` +//! struct. +//! +//! ## Encoding and Decoding +//! +//! Encoding is done by converting to and from a `CompressedRistretto` +//! struct, which is a typed wrapper around `[u8; 32]`. +//! +//! The encoding is not batchable, but it is possible to +//! double-and-encode in a batch using +//! `RistrettoPoint::double_and_compress_batch`. +//! +//! ## Equality Testing +//! +//! Testing equality of points on an Edwards curve in projective +//! coordinates requires an expensive inversion. By contrast, equality +//! checking in the Ristretto group can be done in projective +//! coordinates without requiring an inversion, so it is much faster. +//! +//! The `RistrettoPoint` struct implements the +//! `subtle::ConstantTimeEq` trait for constant-time equality +//! checking, and the Rust `Eq` trait for variable-time equality +//! checking. +//! +//! ## Scalars +//! +//! Scalars are represented by the `Scalar` struct. Each scalar has a +//! canonical representative mod the group order. To attempt to load +//! a supposedly-canonical scalar, use +//! `Scalar::from_canonical_bytes()`. To check whether a +//! representative is canonical, use `Scalar::is_canonical()`. +//! +//! ## Scalar Multiplication +//! +//! Scalar multiplication on Ristretto points is provided by: +//! +//! * the `*` operator between a `Scalar` and a `RistrettoPoint`, which +//! performs constant-time variable-base scalar multiplication; +//! +//! * the `*` operator between a `Scalar` and a +//! `RistrettoBasepointTable`, which performs constant-time fixed-base +//! scalar multiplication; +//! +//! * an implementation of the +//! [`MultiscalarMul`](../traits/trait.MultiscalarMul.html) trait for +//! constant-time variable-base multiscalar multiplication; +//! +//! * an implementation of the +//! [`VartimeMultiscalarMul`](../traits/trait.VartimeMultiscalarMul.html) +//! trait for variable-time variable-base multiscalar multiplication; +//! +//! ## Random Points and Hashing to Ristretto +//! +//! The Ristretto group comes equipped with an Elligator map. This is +//! used to implement +//! +//! * `RistrettoPoint::random()`, which generates random points from an +//! RNG - enabled by `rand_core` feature; +//! +//! * `RistrettoPoint::from_hash()` and +//! `RistrettoPoint::hash_from_bytes()`, which perform hashing to the +//! group. +//! +//! The Elligator map itself is not currently exposed. +//! +//! ## Implementation +//! +//! The Decaf suggestion is to use a quotient group, such as \\(\mathcal +//! E / \mathcal E\[4\]\\) or \\(2 \mathcal E / \mathcal E\[2\] \\), to +//! implement a prime-order group using a non-prime-order curve. +//! +//! This requires only changing +//! +//! 1. the function for equality checking (so that two representatives +//! of the same coset are considered equal); +//! 2. the function for encoding (so that two representatives of the +//! same coset are encoded as identical bitstrings); +//! 3. the function for decoding (so that only the canonical encoding of +//! a coset is accepted). +//! +//! Internally, each coset is represented by a curve point; two points +//! \\( P, Q \\) may represent the same coset in the same way that two +//! points with different \\(X,Y,Z\\) coordinates may represent the +//! same point. The group operations are carried out with no overhead +//! using Edwards formulas. +//! +//! Notes on the details of the encoding can be found in the +//! [Details][ristretto_notes] section of the Ristretto website. +//! +//! [cryptonote]: +//! https://moderncrypto.org/mail-archive/curves/2017/000898.html +//! [ed25519_hkd]: +//! https://moderncrypto.org/mail-archive/curves/2017/000858.html +//! [ristretto_coffee]: +//! https://en.wikipedia.org/wiki/Ristretto +//! [ristretto_notes]: +//! https://ristretto.group/details/index.html +//! [why_ristretto]: +//! https://ristretto.group/why_ristretto.html +//! [ristretto_main]: +//! https://ristretto.group/ + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use core::array::TryFromSliceError; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::iter::Sum; +use core::ops::{Add, Neg, Sub}; +use core::ops::{AddAssign, SubAssign}; +use core::ops::{Mul, MulAssign}; + +#[cfg(any(test, feature = "rand_core"))] +use rand_core::CryptoRngCore; + +#[cfg(feature = "digest")] +use digest::generic_array::typenum::U64; +#[cfg(feature = "digest")] +use digest::Digest; + +use crate::constants; +use crate::field::FieldElement; + +#[cfg(feature = "group")] +use { + group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding}, + rand_core::RngCore, + subtle::CtOption, +}; + +use subtle::Choice; +use subtle::ConditionallyNegatable; +use subtle::ConditionallySelectable; +use subtle::ConstantTimeEq; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +#[cfg(feature = "precomputed-tables")] +use crate::edwards::EdwardsBasepointTable; +use crate::edwards::EdwardsPoint; + +use crate::scalar::Scalar; + +#[cfg(feature = "precomputed-tables")] +use crate::traits::BasepointTable; +use crate::traits::Identity; +#[cfg(feature = "alloc")] +use crate::traits::{MultiscalarMul, VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul}; + +// ------------------------------------------------------------------------ +// Compressed points +// ------------------------------------------------------------------------ + +/// A Ristretto point, in compressed wire format. +/// +/// The Ristretto encoding is canonical, so two points are equal if and +/// only if their encodings are equal. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct CompressedRistretto(pub [u8; 32]); + +impl ConstantTimeEq for CompressedRistretto { + fn ct_eq(&self, other: &CompressedRistretto) -> Choice { + self.as_bytes().ct_eq(other.as_bytes()) + } +} + +impl CompressedRistretto { + /// Copy the bytes of this `CompressedRistretto`. + pub const fn to_bytes(&self) -> [u8; 32] { + self.0 + } + + /// View this `CompressedRistretto` as an array of bytes. + pub const fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + /// Construct a `CompressedRistretto` from a slice of bytes. + /// + /// # Errors + /// + /// Returns [`TryFromSliceError`] if the input `bytes` slice does not have + /// a length of 32. + pub fn from_slice(bytes: &[u8]) -> Result { + bytes.try_into().map(CompressedRistretto) + } + + /// Attempt to decompress to an `RistrettoPoint`. + /// + /// # Return + /// + /// - `Some(RistrettoPoint)` if `self` was the canonical encoding of a point; + /// + /// - `None` if `self` was not the canonical encoding of a point. + pub fn decompress(&self) -> Option { + let (s_encoding_is_canonical, s_is_negative, s) = decompress::step_1(self); + + if (!s_encoding_is_canonical | s_is_negative).into() { + return None; + } + + let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s); + + if (!ok | t_is_negative | y_is_zero).into() { + None + } else { + Some(res) + } + } +} + +mod decompress { + use super::*; + + pub(super) fn step_1(repr: &CompressedRistretto) -> (Choice, Choice, FieldElement) { + // Step 1. Check s for validity: + // 1.a) s must be 32 bytes (we get this from the type system) + // 1.b) s < p + // 1.c) s is nonnegative + // + // Our decoding routine ignores the high bit, so the only + // possible failure for 1.b) is if someone encodes s in 0..18 + // as s+p in 2^255-19..2^255-1. We can check this by + // converting back to bytes, and checking that we get the + // original input, since our encoding routine is canonical. + + let s = FieldElement::from_bytes(repr.as_bytes()); + let s_bytes_check = s.as_bytes(); + let s_encoding_is_canonical = s_bytes_check[..].ct_eq(repr.as_bytes()); + let s_is_negative = s.is_negative(); + + (s_encoding_is_canonical, s_is_negative, s) + } + + pub(super) fn step_2(s: FieldElement) -> (Choice, Choice, Choice, RistrettoPoint) { + // Step 2. Compute (X:Y:Z:T). + let one = FieldElement::ONE; + let ss = s.square(); + let u1 = &one - &ss; // 1 + as² + let u2 = &one + &ss; // 1 - as² where a=-1 + let u2_sqr = u2.square(); // (1 - as²)² + + // v == ad(1+as²)² - (1-as²)² where d=-121665/121666 + let v = &(&(-&constants::EDWARDS_D) * &u1.square()) - &u2_sqr; + + let (ok, I) = (&v * &u2_sqr).invsqrt(); // 1/sqrt(v*u_2²) + + let Dx = &I * &u2; // 1/sqrt(v) + let Dy = &I * &(&Dx * &v); // 1/u2 + + // x == | 2s/sqrt(v) | == + sqrt(4s²/(ad(1+as²)² - (1-as²)²)) + let mut x = &(&s + &s) * &Dx; + let x_neg = x.is_negative(); + x.conditional_negate(x_neg); + + // y == (1-as²)/(1+as²) + let y = &u1 * &Dy; + + // t == ((1+as²) sqrt(4s²/(ad(1+as²)² - (1-as²)²)))/(1-as²) + let t = &x * &y; + + ( + ok, + t.is_negative(), + y.is_zero(), + RistrettoPoint(EdwardsPoint { + X: x, + Y: y, + Z: one, + T: t, + }), + ) + } +} + +impl Identity for CompressedRistretto { + fn identity() -> CompressedRistretto { + CompressedRistretto([0u8; 32]) + } +} + +impl Default for CompressedRistretto { + fn default() -> CompressedRistretto { + CompressedRistretto::identity() + } +} + +impl TryFrom<&[u8]> for CompressedRistretto { + type Error = TryFromSliceError; + + fn try_from(slice: &[u8]) -> Result { + Self::from_slice(slice) + } +} + +// ------------------------------------------------------------------------ +// Serde support +// ------------------------------------------------------------------------ +// Serializes to and from `RistrettoPoint` directly, doing compression +// and decompression internally. This means that users can create +// structs containing `RistrettoPoint`s and use Serde's derived +// serializers to serialize those structures. + +#[cfg(feature = "serde")] +use serde::de::Visitor; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[cfg(feature = "serde")] +impl Serialize for RistrettoPoint { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeTuple; + let mut tup = serializer.serialize_tuple(32)?; + for byte in self.compress().as_bytes().iter() { + tup.serialize_element(byte)?; + } + tup.end() + } +} + +#[cfg(feature = "serde")] +impl Serialize for CompressedRistretto { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeTuple; + let mut tup = serializer.serialize_tuple(32)?; + for byte in self.as_bytes().iter() { + tup.serialize_element(byte)?; + } + tup.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for RistrettoPoint { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct RistrettoPointVisitor; + + impl<'de> Visitor<'de> for RistrettoPointVisitor { + type Value = RistrettoPoint; + + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + formatter.write_str("a valid point in Ristretto format") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut bytes = [0u8; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + bytes[i] = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?; + } + CompressedRistretto(bytes) + .decompress() + .ok_or_else(|| serde::de::Error::custom("decompression failed")) + } + } + + deserializer.deserialize_tuple(32, RistrettoPointVisitor) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for CompressedRistretto { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct CompressedRistrettoVisitor; + + impl<'de> Visitor<'de> for CompressedRistrettoVisitor { + type Value = CompressedRistretto; + + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + formatter.write_str("32 bytes of data") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut bytes = [0u8; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + bytes[i] = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?; + } + Ok(CompressedRistretto(bytes)) + } + } + + deserializer.deserialize_tuple(32, CompressedRistrettoVisitor) + } +} + +// ------------------------------------------------------------------------ +// Internal point representations +// ------------------------------------------------------------------------ + +/// A `RistrettoPoint` represents a point in the Ristretto group for +/// Curve25519. Ristretto, a variant of Decaf, constructs a +/// prime-order group as a quotient group of a subgroup of (the +/// Edwards form of) Curve25519. +/// +/// Internally, a `RistrettoPoint` is implemented as a wrapper type +/// around `EdwardsPoint`, with custom equality, compression, and +/// decompression routines to account for the quotient. This means that +/// operations on `RistrettoPoint`s are exactly as fast as operations on +/// `EdwardsPoint`s. +/// +#[derive(Copy, Clone)] +pub struct RistrettoPoint(pub(crate) EdwardsPoint); + +impl RistrettoPoint { + /// Compress this point using the Ristretto encoding. + pub fn compress(&self) -> CompressedRistretto { + let mut X = self.0.X; + let mut Y = self.0.Y; + let Z = &self.0.Z; + let T = &self.0.T; + + let u1 = &(Z + &Y) * &(Z - &Y); + let u2 = &X * &Y; + // Ignore return value since this is always square + let (_, invsqrt) = (&u1 * &u2.square()).invsqrt(); + let i1 = &invsqrt * &u1; + let i2 = &invsqrt * &u2; + let z_inv = &i1 * &(&i2 * T); + let mut den_inv = i2; + + let iX = &X * &constants::SQRT_M1; + let iY = &Y * &constants::SQRT_M1; + let ristretto_magic = &constants::INVSQRT_A_MINUS_D; + let enchanted_denominator = &i1 * ristretto_magic; + + let rotate = (T * &z_inv).is_negative(); + + X.conditional_assign(&iY, rotate); + Y.conditional_assign(&iX, rotate); + den_inv.conditional_assign(&enchanted_denominator, rotate); + + Y.conditional_negate((&X * &z_inv).is_negative()); + + let mut s = &den_inv * &(Z - &Y); + let s_is_negative = s.is_negative(); + s.conditional_negate(s_is_negative); + + CompressedRistretto(s.as_bytes()) + } + + /// Double-and-compress a batch of points. The Ristretto encoding + /// is not batchable, since it requires an inverse square root. + /// + /// However, given input points \\( P\_1, \ldots, P\_n, \\) + /// it is possible to compute the encodings of their doubles \\( + /// \mathrm{enc}( \[2\]P\_1), \ldots, \mathrm{enc}( \[2\]P\_n ) \\) + /// in a batch. + /// + #[cfg_attr(feature = "rand_core", doc = "```")] + #[cfg_attr(not(feature = "rand_core"), doc = "```ignore")] + /// # use curve25519_elligator2::ristretto::RistrettoPoint; + /// use rand_core::OsRng; + /// + /// # // Need fn main() here in comment so the doctest compiles + /// # // See https://doc.rust-lang.org/book/documentation.html#documentation-as-tests + /// # fn main() { + /// let mut rng = OsRng; + /// + /// let points: Vec = + /// (0..32).map(|_| RistrettoPoint::random(&mut rng)).collect(); + /// + /// let compressed = RistrettoPoint::double_and_compress_batch(&points); + /// + /// for (P, P2_compressed) in points.iter().zip(compressed.iter()) { + /// assert_eq!(*P2_compressed, (P + P).compress()); + /// } + /// # } + /// ``` + #[cfg(feature = "alloc")] + pub fn double_and_compress_batch<'a, I>(points: I) -> Vec + where + I: IntoIterator, + { + #[derive(Copy, Clone, Debug)] + struct BatchCompressState { + e: FieldElement, + f: FieldElement, + g: FieldElement, + h: FieldElement, + eg: FieldElement, + fh: FieldElement, + } + + impl BatchCompressState { + fn efgh(&self) -> FieldElement { + &self.eg * &self.fh + } + } + + impl<'a> From<&'a RistrettoPoint> for BatchCompressState { + #[rustfmt::skip] // keep alignment of explanatory comments + fn from(P: &'a RistrettoPoint) -> BatchCompressState { + let XX = P.0.X.square(); + let YY = P.0.Y.square(); + let ZZ = P.0.Z.square(); + let dTT = &P.0.T.square() * &constants::EDWARDS_D; + + let e = &P.0.X * &(&P.0.Y + &P.0.Y); // = 2*X*Y + let f = &ZZ + &dTT; // = Z^2 + d*T^2 + let g = &YY + &XX; // = Y^2 - a*X^2 + let h = &ZZ - &dTT; // = Z^2 - d*T^2 + + let eg = &e * &g; + let fh = &f * &h; + + BatchCompressState{ e, f, g, h, eg, fh } + } + } + + let states: Vec = + points.into_iter().map(BatchCompressState::from).collect(); + + let mut invs: Vec = states.iter().map(|state| state.efgh()).collect(); + + FieldElement::batch_invert(&mut invs[..]); + + states + .iter() + .zip(invs.iter()) + .map(|(state, inv): (&BatchCompressState, &FieldElement)| { + let Zinv = &state.eg * inv; + let Tinv = &state.fh * inv; + + let mut magic = constants::INVSQRT_A_MINUS_D; + + let negcheck1 = (&state.eg * &Zinv).is_negative(); + + let mut e = state.e; + let mut g = state.g; + let mut h = state.h; + + let minus_e = -&e; + let f_times_sqrta = &state.f * &constants::SQRT_M1; + + e.conditional_assign(&state.g, negcheck1); + g.conditional_assign(&minus_e, negcheck1); + h.conditional_assign(&f_times_sqrta, negcheck1); + + magic.conditional_assign(&constants::SQRT_M1, negcheck1); + + let negcheck2 = (&(&h * &e) * &Zinv).is_negative(); + + g.conditional_negate(negcheck2); + + let mut s = &(&h - &g) * &(&magic * &(&g * &Tinv)); + + let s_is_negative = s.is_negative(); + s.conditional_negate(s_is_negative); + + CompressedRistretto(s.as_bytes()) + }) + .collect() + } + + /// Return the coset self + E\[4\], for debugging. + fn coset4(&self) -> [EdwardsPoint; 4] { + [ + self.0, + self.0 + constants::EIGHT_TORSION[2], + self.0 + constants::EIGHT_TORSION[4], + self.0 + constants::EIGHT_TORSION[6], + ] + } + + /// Computes the Ristretto Elligator map. This is the + /// [`MAP`](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-04#section-4.3.4) + /// function defined in the Ristretto spec. + /// + /// # Note + /// + /// This method is not public because it's just used for hashing + /// to a point -- proper elligator support is deferred for now. + pub(crate) fn elligator_ristretto_flavor(r_0: &FieldElement) -> RistrettoPoint { + let i = &constants::SQRT_M1; + let d = &constants::EDWARDS_D; + let one_minus_d_sq = &constants::ONE_MINUS_EDWARDS_D_SQUARED; + let d_minus_one_sq = &constants::EDWARDS_D_MINUS_ONE_SQUARED; + let mut c = constants::MINUS_ONE; + + let one = FieldElement::ONE; + + let r = i * &r_0.square(); + let N_s = &(&r + &one) * one_minus_d_sq; + let D = &(&c - &(d * &r)) * &(&r + d); + + let (Ns_D_is_sq, mut s) = FieldElement::sqrt_ratio_i(&N_s, &D); + let mut s_prime = &s * r_0; + let s_prime_is_pos = !s_prime.is_negative(); + s_prime.conditional_negate(s_prime_is_pos); + + s.conditional_assign(&s_prime, !Ns_D_is_sq); + c.conditional_assign(&r, !Ns_D_is_sq); + + let N_t = &(&(&c * &(&r - &one)) * d_minus_one_sq) - &D; + let s_sq = s.square(); + + use crate::backend::serial::curve_models::CompletedPoint; + + // The conversion from W_i is exactly the conversion from P1xP1. + RistrettoPoint( + CompletedPoint { + X: &(&s + &s) * &D, + Z: &N_t * &constants::SQRT_AD_MINUS_ONE, + Y: &FieldElement::ONE - &s_sq, + T: &FieldElement::ONE + &s_sq, + } + .as_extended(), + ) + } + + #[cfg(any(test, feature = "rand_core"))] + /// Return a `RistrettoPoint` chosen uniformly at random using a user-provided RNG. + /// + /// # Inputs + /// + /// * `rng`: any RNG which implements `CryptoRngCore` + /// (i.e. `CryptoRng` + `RngCore`) interface. + /// + /// # Returns + /// + /// A random element of the Ristretto group. + /// + /// # Implementation + /// + /// Uses the Ristretto-flavoured Elligator 2 map, so that the + /// discrete log of the output point with respect to any other + /// point should be unknown. The map is applied twice and the + /// results are added, to ensure a uniform distribution. + pub fn random(rng: &mut R) -> Self { + let mut uniform_bytes = [0u8; 64]; + rng.fill_bytes(&mut uniform_bytes); + + RistrettoPoint::from_uniform_bytes(&uniform_bytes) + } + + #[cfg(feature = "digest")] + /// Hash a slice of bytes into a `RistrettoPoint`. + /// + /// Takes a type parameter `D`, which is any `Digest` producing 64 + /// bytes of output. + /// + /// Convenience wrapper around `from_hash`. + /// + /// # Implementation + /// + /// Uses the Ristretto-flavoured Elligator 2 map, so that the + /// discrete log of the output point with respect to any other + /// point should be unknown. The map is applied twice and the + /// results are added, to ensure a uniform distribution. + /// + /// # Example + /// + #[cfg_attr(feature = "digest", doc = "```")] + #[cfg_attr(not(feature = "digest"), doc = "```ignore")] + /// # use curve25519_elligator2::ristretto::RistrettoPoint; + /// use sha2::Sha512; + /// + /// # // Need fn main() here in comment so the doctest compiles + /// # // See https://doc.rust-lang.org/book/documentation.html#documentation-as-tests + /// # fn main() { + /// let msg = "To really appreciate architecture, you may even need to commit a murder"; + /// let P = RistrettoPoint::hash_from_bytes::(msg.as_bytes()); + /// # } + /// ``` + /// + pub fn hash_from_bytes(input: &[u8]) -> RistrettoPoint + where + D: Digest + Default, + { + let mut hash = D::default(); + hash.update(input); + RistrettoPoint::from_hash(hash) + } + + #[cfg(feature = "digest")] + /// Construct a `RistrettoPoint` from an existing `Digest` instance. + /// + /// Use this instead of `hash_from_bytes` if it is more convenient + /// to stream data into the `Digest` than to pass a single byte + /// slice. + pub fn from_hash(hash: D) -> RistrettoPoint + where + D: Digest + Default, + { + // dealing with generic arrays is clumsy, until const generics land + let output = hash.finalize(); + let mut output_bytes = [0u8; 64]; + output_bytes.copy_from_slice(output.as_slice()); + + RistrettoPoint::from_uniform_bytes(&output_bytes) + } + + /// Construct a `RistrettoPoint` from 64 bytes of data. + /// + /// If the input bytes are uniformly distributed, the resulting + /// point will be uniformly distributed over the group, and its + /// discrete log with respect to other points should be unknown. + /// + /// # Implementation + /// + /// This function splits the input array into two 32-byte halves, + /// takes the low 255 bits of each half mod p, applies the + /// Ristretto-flavored Elligator map to each, and adds the results. + pub fn from_uniform_bytes(bytes: &[u8; 64]) -> RistrettoPoint { + // This follows the one-way map construction from the Ristretto RFC: + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-04#section-4.3.4 + let mut r_1_bytes = [0u8; 32]; + r_1_bytes.copy_from_slice(&bytes[0..32]); + let r_1 = FieldElement::from_bytes(&r_1_bytes); + let R_1 = RistrettoPoint::elligator_ristretto_flavor(&r_1); + + let mut r_2_bytes = [0u8; 32]; + r_2_bytes.copy_from_slice(&bytes[32..64]); + let r_2 = FieldElement::from_bytes(&r_2_bytes); + let R_2 = RistrettoPoint::elligator_ristretto_flavor(&r_2); + + // Applying Elligator twice and adding the results ensures a + // uniform distribution. + R_1 + R_2 + } +} + +impl Identity for RistrettoPoint { + fn identity() -> RistrettoPoint { + RistrettoPoint(EdwardsPoint::identity()) + } +} + +impl Default for RistrettoPoint { + fn default() -> RistrettoPoint { + RistrettoPoint::identity() + } +} + +// ------------------------------------------------------------------------ +// Equality +// ------------------------------------------------------------------------ + +impl PartialEq for RistrettoPoint { + fn eq(&self, other: &RistrettoPoint) -> bool { + self.ct_eq(other).into() + } +} + +impl ConstantTimeEq for RistrettoPoint { + /// Test equality between two `RistrettoPoint`s. + /// + /// # Returns + /// + /// * `Choice(1)` if the two `RistrettoPoint`s are equal; + /// * `Choice(0)` otherwise. + fn ct_eq(&self, other: &RistrettoPoint) -> Choice { + let X1Y2 = &self.0.X * &other.0.Y; + let Y1X2 = &self.0.Y * &other.0.X; + let X1X2 = &self.0.X * &other.0.X; + let Y1Y2 = &self.0.Y * &other.0.Y; + + X1Y2.ct_eq(&Y1X2) | X1X2.ct_eq(&Y1Y2) + } +} + +impl Eq for RistrettoPoint {} + +// ------------------------------------------------------------------------ +// Arithmetic +// ------------------------------------------------------------------------ + +impl<'a, 'b> Add<&'b RistrettoPoint> for &'a RistrettoPoint { + type Output = RistrettoPoint; + + fn add(self, other: &'b RistrettoPoint) -> RistrettoPoint { + RistrettoPoint(self.0 + other.0) + } +} + +define_add_variants!( + LHS = RistrettoPoint, + RHS = RistrettoPoint, + Output = RistrettoPoint +); + +impl<'b> AddAssign<&'b RistrettoPoint> for RistrettoPoint { + fn add_assign(&mut self, _rhs: &RistrettoPoint) { + *self = (self as &RistrettoPoint) + _rhs; + } +} + +define_add_assign_variants!(LHS = RistrettoPoint, RHS = RistrettoPoint); + +impl<'a, 'b> Sub<&'b RistrettoPoint> for &'a RistrettoPoint { + type Output = RistrettoPoint; + + fn sub(self, other: &'b RistrettoPoint) -> RistrettoPoint { + RistrettoPoint(self.0 - other.0) + } +} + +define_sub_variants!( + LHS = RistrettoPoint, + RHS = RistrettoPoint, + Output = RistrettoPoint +); + +impl<'b> SubAssign<&'b RistrettoPoint> for RistrettoPoint { + fn sub_assign(&mut self, _rhs: &RistrettoPoint) { + *self = (self as &RistrettoPoint) - _rhs; + } +} + +define_sub_assign_variants!(LHS = RistrettoPoint, RHS = RistrettoPoint); + +impl Sum for RistrettoPoint +where + T: Borrow, +{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(RistrettoPoint::identity(), |acc, item| acc + item.borrow()) + } +} + +impl<'a> Neg for &'a RistrettoPoint { + type Output = RistrettoPoint; + + fn neg(self) -> RistrettoPoint { + RistrettoPoint(-&self.0) + } +} + +impl Neg for RistrettoPoint { + type Output = RistrettoPoint; + + fn neg(self) -> RistrettoPoint { + -&self + } +} + +impl<'b> MulAssign<&'b Scalar> for RistrettoPoint { + fn mul_assign(&mut self, scalar: &'b Scalar) { + let result = (self as &RistrettoPoint) * scalar; + *self = result; + } +} + +impl<'a, 'b> Mul<&'b Scalar> for &'a RistrettoPoint { + type Output = RistrettoPoint; + /// Scalar multiplication: compute `scalar * self`. + fn mul(self, scalar: &'b Scalar) -> RistrettoPoint { + RistrettoPoint(self.0 * scalar) + } +} + +impl<'a, 'b> Mul<&'b RistrettoPoint> for &'a Scalar { + type Output = RistrettoPoint; + + /// Scalar multiplication: compute `self * scalar`. + fn mul(self, point: &'b RistrettoPoint) -> RistrettoPoint { + RistrettoPoint(self * point.0) + } +} + +impl RistrettoPoint { + /// Fixed-base scalar multiplication by the Ristretto base point. + /// + /// Uses precomputed basepoint tables when the `precomputed-tables` feature + /// is enabled, trading off increased code size for ~4x better performance. + pub fn mul_base(scalar: &Scalar) -> Self { + #[cfg(not(feature = "precomputed-tables"))] + { + scalar * constants::RISTRETTO_BASEPOINT_POINT + } + + #[cfg(feature = "precomputed-tables")] + { + scalar * constants::RISTRETTO_BASEPOINT_TABLE + } + } +} + +define_mul_assign_variants!(LHS = RistrettoPoint, RHS = Scalar); + +define_mul_variants!(LHS = RistrettoPoint, RHS = Scalar, Output = RistrettoPoint); +define_mul_variants!(LHS = Scalar, RHS = RistrettoPoint, Output = RistrettoPoint); + +// ------------------------------------------------------------------------ +// Multiscalar Multiplication impls +// ------------------------------------------------------------------------ + +// These use iterator combinators to unwrap the underlying points and +// forward to the EdwardsPoint implementations. + +#[cfg(feature = "alloc")] +impl MultiscalarMul for RistrettoPoint { + type Point = RistrettoPoint; + + fn multiscalar_mul(scalars: I, points: J) -> RistrettoPoint + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + { + let extended_points = points.into_iter().map(|P| P.borrow().0); + RistrettoPoint(EdwardsPoint::multiscalar_mul(scalars, extended_points)) + } +} + +#[cfg(feature = "alloc")] +impl VartimeMultiscalarMul for RistrettoPoint { + type Point = RistrettoPoint; + + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>, + { + let extended_points = points.into_iter().map(|opt_P| opt_P.map(|P| P.0)); + + EdwardsPoint::optional_multiscalar_mul(scalars, extended_points).map(RistrettoPoint) + } +} + +/// Precomputation for variable-time multiscalar multiplication with `RistrettoPoint`s. +// This wraps the inner implementation in a facade type so that we can +// decouple stability of the inner type from the stability of the +// outer type. +#[cfg(feature = "alloc")] +pub struct VartimeRistrettoPrecomputation(crate::backend::VartimePrecomputedStraus); + +#[cfg(feature = "alloc")] +impl VartimePrecomputedMultiscalarMul for VartimeRistrettoPrecomputation { + type Point = RistrettoPoint; + + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow, + { + Self(crate::backend::VartimePrecomputedStraus::new( + static_points.into_iter().map(|P| P.borrow().0), + )) + } + + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>, + { + self.0 + .optional_mixed_multiscalar_mul( + static_scalars, + dynamic_scalars, + dynamic_points.into_iter().map(|P_opt| P_opt.map(|P| P.0)), + ) + .map(RistrettoPoint) + } +} + +impl RistrettoPoint { + /// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the + /// Ristretto basepoint. + pub fn vartime_double_scalar_mul_basepoint( + a: &Scalar, + A: &RistrettoPoint, + b: &Scalar, + ) -> RistrettoPoint { + RistrettoPoint(EdwardsPoint::vartime_double_scalar_mul_basepoint( + a, &A.0, b, + )) + } +} + +/// A precomputed table of multiples of a basepoint, used to accelerate +/// scalar multiplication. +/// +/// A precomputed table of multiples of the Ristretto basepoint is +/// available in the `constants` module: +/// ``` +/// use curve25519_elligator2::constants::RISTRETTO_BASEPOINT_TABLE; +/// use curve25519_elligator2::scalar::Scalar; +/// +/// let a = Scalar::from(87329482u64); +/// let P = &a * RISTRETTO_BASEPOINT_TABLE; +/// ``` +#[cfg(feature = "precomputed-tables")] +#[derive(Clone)] +#[repr(transparent)] +pub struct RistrettoBasepointTable(pub(crate) EdwardsBasepointTable); + +#[cfg(feature = "precomputed-tables")] +impl<'a, 'b> Mul<&'b Scalar> for &'a RistrettoBasepointTable { + type Output = RistrettoPoint; + + fn mul(self, scalar: &'b Scalar) -> RistrettoPoint { + RistrettoPoint(&self.0 * scalar) + } +} + +#[cfg(feature = "precomputed-tables")] +impl<'a, 'b> Mul<&'a RistrettoBasepointTable> for &'b Scalar { + type Output = RistrettoPoint; + + fn mul(self, basepoint_table: &'a RistrettoBasepointTable) -> RistrettoPoint { + RistrettoPoint(self * &basepoint_table.0) + } +} + +#[cfg(feature = "precomputed-tables")] +impl RistrettoBasepointTable { + /// Create a precomputed table of multiples of the given `basepoint`. + pub fn create(basepoint: &RistrettoPoint) -> RistrettoBasepointTable { + RistrettoBasepointTable(EdwardsBasepointTable::create(&basepoint.0)) + } + + /// Get the basepoint for this table as a `RistrettoPoint`. + pub fn basepoint(&self) -> RistrettoPoint { + RistrettoPoint(self.0.basepoint()) + } +} + +// ------------------------------------------------------------------------ +// Constant-time conditional selection +// ------------------------------------------------------------------------ + +impl ConditionallySelectable for RistrettoPoint { + /// Conditionally select between `self` and `other`. + /// + /// # Example + /// + /// ``` + /// use subtle::ConditionallySelectable; + /// use subtle::Choice; + /// # + /// # use curve25519_elligator2::traits::Identity; + /// # use curve25519_elligator2::ristretto::RistrettoPoint; + /// # use curve25519_elligator2::constants; + /// # fn main() { + /// + /// let A = RistrettoPoint::identity(); + /// let B = constants::RISTRETTO_BASEPOINT_POINT; + /// + /// let mut P = A; + /// + /// P = RistrettoPoint::conditional_select(&A, &B, Choice::from(0)); + /// assert_eq!(P, A); + /// P = RistrettoPoint::conditional_select(&A, &B, Choice::from(1)); + /// assert_eq!(P, B); + /// # } + /// ``` + fn conditional_select( + a: &RistrettoPoint, + b: &RistrettoPoint, + choice: Choice, + ) -> RistrettoPoint { + RistrettoPoint(EdwardsPoint::conditional_select(&a.0, &b.0, choice)) + } +} + +// ------------------------------------------------------------------------ +// Debug traits +// ------------------------------------------------------------------------ + +impl Debug for CompressedRistretto { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "CompressedRistretto: {:?}", self.as_bytes()) + } +} + +impl Debug for RistrettoPoint { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let coset = self.coset4(); + write!( + f, + "RistrettoPoint: coset \n{:?}\n{:?}\n{:?}\n{:?}", + coset[0], coset[1], coset[2], coset[3] + ) + } +} + +// ------------------------------------------------------------------------ +// group traits +// ------------------------------------------------------------------------ + +// Use the full trait path to avoid Group::identity overlapping Identity::identity in the +// rest of the module (e.g. tests). +#[cfg(feature = "group")] +impl group::Group for RistrettoPoint { + type Scalar = Scalar; + + fn random(mut rng: impl RngCore) -> Self { + // NOTE: this is duplicated due to different `rng` bounds + let mut uniform_bytes = [0u8; 64]; + rng.fill_bytes(&mut uniform_bytes); + RistrettoPoint::from_uniform_bytes(&uniform_bytes) + } + + fn identity() -> Self { + Identity::identity() + } + + fn generator() -> Self { + constants::RISTRETTO_BASEPOINT_POINT + } + + fn is_identity(&self) -> Choice { + self.ct_eq(&Identity::identity()) + } + + fn double(&self) -> Self { + self + self + } +} + +#[cfg(feature = "group")] +impl GroupEncoding for RistrettoPoint { + type Repr = [u8; 32]; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + let (s_encoding_is_canonical, s_is_negative, s) = + decompress::step_1(&CompressedRistretto(*bytes)); + + let s_is_valid = s_encoding_is_canonical & !s_is_negative; + + let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s); + + CtOption::new(res, s_is_valid & ok & !t_is_negative & !y_is_zero) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // Just use the checked API; the checks we could skip aren't expensive. + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + self.compress().to_bytes() + } +} + +#[cfg(feature = "group")] +impl PrimeGroup for RistrettoPoint {} + +/// Ristretto has a cofactor of 1. +#[cfg(feature = "group")] +impl CofactorGroup for RistrettoPoint { + type Subgroup = Self; + + fn clear_cofactor(&self) -> Self::Subgroup { + *self + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, Choice::from(1)) + } + + fn is_torsion_free(&self) -> Choice { + Choice::from(1) + } +} + +// ------------------------------------------------------------------------ +// Zeroize traits +// ------------------------------------------------------------------------ + +#[cfg(feature = "zeroize")] +impl Zeroize for CompressedRistretto { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for RistrettoPoint { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +// ------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------ + +#[cfg(test)] +mod test { + use super::*; + use crate::edwards::CompressedEdwardsY; + + use rand_core::OsRng; + + #[test] + #[cfg(feature = "serde")] + fn serde_bincode_basepoint_roundtrip() { + use bincode; + + let encoded = bincode::serialize(&constants::RISTRETTO_BASEPOINT_POINT).unwrap(); + let enc_compressed = + bincode::serialize(&constants::RISTRETTO_BASEPOINT_COMPRESSED).unwrap(); + assert_eq!(encoded, enc_compressed); + + // Check that the encoding is 32 bytes exactly + assert_eq!(encoded.len(), 32); + + let dec_uncompressed: RistrettoPoint = bincode::deserialize(&encoded).unwrap(); + let dec_compressed: CompressedRistretto = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(dec_uncompressed, constants::RISTRETTO_BASEPOINT_POINT); + assert_eq!(dec_compressed, constants::RISTRETTO_BASEPOINT_COMPRESSED); + + // Check that the encoding itself matches the usual one + let raw_bytes = constants::RISTRETTO_BASEPOINT_COMPRESSED.as_bytes(); + let bp: RistrettoPoint = bincode::deserialize(raw_bytes).unwrap(); + assert_eq!(bp, constants::RISTRETTO_BASEPOINT_POINT); + } + + #[test] + fn scalarmult_ristrettopoint_works_both_ways() { + let P = constants::RISTRETTO_BASEPOINT_POINT; + let s = Scalar::from(999u64); + + let P1 = P * s; + let P2 = s * P; + + assert!(P1.compress().as_bytes() == P2.compress().as_bytes()); + } + + #[test] + #[cfg(feature = "alloc")] + fn impl_sum() { + // Test that sum works for non-empty iterators + let BASE = constants::RISTRETTO_BASEPOINT_POINT; + + let s1 = Scalar::from(999u64); + let P1 = BASE * s1; + + let s2 = Scalar::from(333u64); + let P2 = BASE * s2; + + let vec = vec![P1, P2]; + let sum: RistrettoPoint = vec.iter().sum(); + + assert_eq!(sum, P1 + P2); + + // Test that sum works for the empty iterator + let empty_vector: Vec = vec![]; + let sum: RistrettoPoint = empty_vector.iter().sum(); + + assert_eq!(sum, RistrettoPoint::identity()); + + // Test that sum works on owning iterators + let s = Scalar::from(2u64); + let mapped = vec.iter().map(|x| x * s); + let sum: RistrettoPoint = mapped.sum(); + + assert_eq!(sum, P1 * s + P2 * s); + } + + #[test] + fn decompress_negative_s_fails() { + // constants::d is neg, so decompression should fail as |d| != d. + let bad_compressed = CompressedRistretto(constants::EDWARDS_D.as_bytes()); + assert!(bad_compressed.decompress().is_none()); + } + + #[test] + fn decompress_id() { + let compressed_id = CompressedRistretto::identity(); + let id = compressed_id.decompress().unwrap(); + let mut identity_in_coset = false; + for P in &id.coset4() { + if P.compress() == CompressedEdwardsY::identity() { + identity_in_coset = true; + } + } + assert!(identity_in_coset); + } + + #[test] + fn compress_id() { + let id = RistrettoPoint::identity(); + assert_eq!(id.compress(), CompressedRistretto::identity()); + } + + #[test] + fn basepoint_roundtrip() { + let bp_compressed_ristretto = constants::RISTRETTO_BASEPOINT_POINT.compress(); + let bp_recaf = bp_compressed_ristretto.decompress().unwrap().0; + // Check that bp_recaf differs from bp by a point of order 4 + let diff = constants::RISTRETTO_BASEPOINT_POINT.0 - bp_recaf; + let diff4 = diff.mul_by_pow_2(2); + assert_eq!(diff4.compress(), CompressedEdwardsY::identity()); + } + + #[test] + fn encodings_of_small_multiples_of_basepoint() { + // Table of encodings of i*basepoint + // Generated using ristretto.sage + let compressed = [ + CompressedRistretto([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + CompressedRistretto([ + 226, 242, 174, 10, 106, 188, 78, 113, 168, 132, 169, 97, 197, 0, 81, 95, 88, 227, + 11, 106, 165, 130, 221, 141, 182, 166, 89, 69, 224, 141, 45, 118, + ]), + CompressedRistretto([ + 106, 73, 50, 16, 247, 73, 156, 209, 127, 236, 181, 16, 174, 12, 234, 35, 161, 16, + 232, 213, 185, 1, 248, 172, 173, 211, 9, 92, 115, 163, 185, 25, + ]), + CompressedRistretto([ + 148, 116, 31, 93, 93, 82, 117, 94, 206, 79, 35, 240, 68, 238, 39, 213, 209, 234, + 30, 43, 209, 150, 180, 98, 22, 107, 22, 21, 42, 157, 2, 89, + ]), + CompressedRistretto([ + 218, 128, 134, 39, 115, 53, 139, 70, 111, 250, 223, 224, 179, 41, 58, 179, 217, + 253, 83, 197, 234, 108, 149, 83, 88, 245, 104, 50, 45, 175, 106, 87, + ]), + CompressedRistretto([ + 232, 130, 177, 49, 1, 107, 82, 193, 211, 51, 112, 128, 24, 124, 247, 104, 66, 62, + 252, 203, 181, 23, 187, 73, 90, 184, 18, 196, 22, 15, 244, 78, + ]), + CompressedRistretto([ + 246, 71, 70, 211, 201, 43, 19, 5, 14, 216, 216, 2, 54, 167, 240, 0, 124, 59, 63, + 150, 47, 91, 167, 147, 209, 154, 96, 30, 187, 29, 244, 3, + ]), + CompressedRistretto([ + 68, 245, 53, 32, 146, 110, 200, 31, 189, 90, 56, 120, 69, 190, 183, 223, 133, 169, + 106, 36, 236, 225, 135, 56, 189, 207, 166, 167, 130, 42, 23, 109, + ]), + CompressedRistretto([ + 144, 50, 147, 216, 242, 40, 126, 190, 16, 226, 55, 77, 193, 165, 62, 11, 200, 135, + 229, 146, 105, 159, 2, 208, 119, 213, 38, 60, 221, 85, 96, 28, + ]), + CompressedRistretto([ + 2, 98, 42, 206, 143, 115, 3, 163, 28, 175, 198, 63, 143, 196, 143, 220, 22, 225, + 200, 200, 210, 52, 178, 240, 214, 104, 82, 130, 169, 7, 96, 49, + ]), + CompressedRistretto([ + 32, 112, 111, 215, 136, 178, 114, 10, 30, 210, 165, 218, 212, 149, 43, 1, 244, 19, + 188, 240, 231, 86, 77, 232, 205, 200, 22, 104, 158, 45, 185, 95, + ]), + CompressedRistretto([ + 188, 232, 63, 139, 165, 221, 47, 165, 114, 134, 76, 36, 186, 24, 16, 249, 82, 43, + 198, 0, 74, 254, 149, 135, 122, 199, 50, 65, 202, 253, 171, 66, + ]), + CompressedRistretto([ + 228, 84, 158, 225, 107, 154, 160, 48, 153, 202, 32, 140, 103, 173, 175, 202, 250, + 76, 63, 62, 78, 83, 3, 222, 96, 38, 227, 202, 143, 248, 68, 96, + ]), + CompressedRistretto([ + 170, 82, 224, 0, 223, 46, 22, 245, 95, 177, 3, 47, 195, 59, 196, 39, 66, 218, 214, + 189, 90, 143, 192, 190, 1, 103, 67, 108, 89, 72, 80, 31, + ]), + CompressedRistretto([ + 70, 55, 107, 128, 244, 9, 178, 157, 194, 181, 246, 240, 197, 37, 145, 153, 8, 150, + 229, 113, 111, 65, 71, 124, 211, 0, 133, 171, 127, 16, 48, 30, + ]), + CompressedRistretto([ + 224, 196, 24, 247, 200, 217, 196, 205, 215, 57, 91, 147, 234, 18, 79, 58, 217, 144, + 33, 187, 104, 29, 252, 51, 2, 169, 217, 154, 46, 83, 230, 78, + ]), + ]; + let mut bp = RistrettoPoint::identity(); + for point in compressed { + assert_eq!(bp.compress(), point); + bp += constants::RISTRETTO_BASEPOINT_POINT; + } + } + + #[test] + fn four_torsion_basepoint() { + let bp = constants::RISTRETTO_BASEPOINT_POINT; + let bp_coset = bp.coset4(); + for point in bp_coset { + assert_eq!(bp, RistrettoPoint(point)); + } + } + + #[test] + fn four_torsion_random() { + let mut rng = OsRng; + let P = RistrettoPoint::mul_base(&Scalar::random(&mut rng)); + let P_coset = P.coset4(); + for point in P_coset { + assert_eq!(P, RistrettoPoint(point)); + } + } + + #[test] + fn elligator_vs_ristretto_sage() { + // Test vectors extracted from ristretto.sage. + // + // Notice that all of the byte sequences have bit 255 set to 0; this is because + // ristretto.sage does not mask the high bit of a field element. When the high bit is set, + // the ristretto.sage elligator implementation gives different results, since it takes a + // different field element as input. + let bytes: [[u8; 32]; 16] = [ + [ + 184, 249, 135, 49, 253, 123, 89, 113, 67, 160, 6, 239, 7, 105, 211, 41, 192, 249, + 185, 57, 9, 102, 70, 198, 15, 127, 7, 26, 160, 102, 134, 71, + ], + [ + 229, 14, 241, 227, 75, 9, 118, 60, 128, 153, 226, 21, 183, 217, 91, 136, 98, 0, + 231, 156, 124, 77, 82, 139, 142, 134, 164, 169, 169, 62, 250, 52, + ], + [ + 115, 109, 36, 220, 180, 223, 99, 6, 204, 169, 19, 29, 169, 68, 84, 23, 21, 109, + 189, 149, 127, 205, 91, 102, 172, 35, 112, 35, 134, 69, 186, 34, + ], + [ + 16, 49, 96, 107, 171, 199, 164, 9, 129, 16, 64, 62, 241, 63, 132, 173, 209, 160, + 112, 215, 105, 50, 157, 81, 253, 105, 1, 154, 229, 25, 120, 83, + ], + [ + 156, 131, 161, 162, 236, 251, 5, 187, 167, 171, 17, 178, 148, 210, 90, 207, 86, 21, + 79, 161, 167, 215, 234, 1, 136, 242, 182, 248, 38, 85, 79, 86, + ], + [ + 251, 177, 124, 54, 18, 101, 75, 235, 245, 186, 19, 46, 133, 157, 229, 64, 10, 136, + 181, 185, 78, 144, 254, 167, 137, 49, 107, 10, 61, 10, 21, 25, + ], + [ + 232, 193, 20, 68, 240, 77, 186, 77, 183, 40, 44, 86, 150, 31, 198, 212, 76, 81, 3, + 217, 197, 8, 126, 128, 126, 152, 164, 208, 153, 44, 189, 77, + ], + [ + 173, 229, 149, 177, 37, 230, 30, 69, 61, 56, 172, 190, 219, 115, 167, 194, 71, 134, + 59, 75, 28, 244, 118, 26, 162, 97, 64, 16, 15, 189, 30, 64, + ], + [ + 106, 71, 61, 107, 250, 117, 42, 151, 91, 202, 212, 100, 52, 188, 190, 21, 125, 218, + 31, 18, 253, 241, 160, 133, 57, 242, 3, 164, 189, 68, 111, 75, + ], + [ + 112, 204, 182, 90, 220, 198, 120, 73, 173, 107, 193, 17, 227, 40, 162, 36, 150, + 141, 235, 55, 172, 183, 12, 39, 194, 136, 43, 153, 244, 118, 91, 89, + ], + [ + 111, 24, 203, 123, 254, 189, 11, 162, 51, 196, 163, 136, 204, 143, 10, 222, 33, + 112, 81, 205, 34, 35, 8, 66, 90, 6, 164, 58, 170, 177, 34, 25, + ], + [ + 225, 183, 30, 52, 236, 82, 6, 183, 109, 25, 227, 181, 25, 82, 41, 193, 80, 77, 161, + 80, 242, 203, 79, 204, 136, 245, 131, 110, 237, 106, 3, 58, + ], + [ + 207, 246, 38, 56, 30, 86, 176, 90, 27, 200, 61, 42, 221, 27, 56, 210, 79, 178, 189, + 120, 68, 193, 120, 167, 77, 185, 53, 197, 124, 128, 191, 126, + ], + [ + 1, 136, 215, 80, 240, 46, 63, 147, 16, 244, 230, 207, 82, 189, 74, 50, 106, 169, + 138, 86, 30, 131, 214, 202, 166, 125, 251, 228, 98, 24, 36, 21, + ], + [ + 210, 207, 228, 56, 155, 116, 207, 54, 84, 195, 251, 215, 249, 199, 116, 75, 109, + 239, 196, 251, 194, 246, 252, 228, 70, 146, 156, 35, 25, 39, 241, 4, + ], + [ + 34, 116, 123, 9, 8, 40, 93, 189, 9, 103, 57, 103, 66, 227, 3, 2, 157, 107, 134, + 219, 202, 74, 230, 154, 78, 107, 219, 195, 214, 14, 84, 80, + ], + ]; + let encoded_images: [CompressedRistretto; 16] = [ + CompressedRistretto([ + 176, 157, 237, 97, 66, 29, 140, 166, 168, 94, 26, 157, 212, 216, 229, 160, 195, + 246, 232, 239, 169, 112, 63, 193, 64, 32, 152, 69, 11, 190, 246, 86, + ]), + CompressedRistretto([ + 234, 141, 77, 203, 181, 225, 250, 74, 171, 62, 15, 118, 78, 212, 150, 19, 131, 14, + 188, 238, 194, 244, 141, 138, 166, 162, 83, 122, 228, 201, 19, 26, + ]), + CompressedRistretto([ + 232, 231, 51, 92, 5, 168, 80, 36, 173, 179, 104, 68, 186, 149, 68, 40, 140, 170, + 27, 103, 99, 140, 21, 242, 43, 62, 250, 134, 208, 255, 61, 89, + ]), + CompressedRistretto([ + 208, 120, 140, 129, 177, 179, 237, 159, 252, 160, 28, 13, 206, 5, 211, 241, 192, + 218, 1, 97, 130, 241, 20, 169, 119, 46, 246, 29, 79, 80, 77, 84, + ]), + CompressedRistretto([ + 202, 11, 236, 145, 58, 12, 181, 157, 209, 6, 213, 88, 75, 147, 11, 119, 191, 139, + 47, 142, 33, 36, 153, 193, 223, 183, 178, 8, 205, 120, 248, 110, + ]), + CompressedRistretto([ + 26, 66, 231, 67, 203, 175, 116, 130, 32, 136, 62, 253, 215, 46, 5, 214, 166, 248, + 108, 237, 216, 71, 244, 173, 72, 133, 82, 6, 143, 240, 104, 41, + ]), + CompressedRistretto([ + 40, 157, 102, 96, 201, 223, 200, 197, 150, 181, 106, 83, 103, 126, 143, 33, 145, + 230, 78, 6, 171, 146, 210, 143, 112, 5, 245, 23, 183, 138, 18, 120, + ]), + CompressedRistretto([ + 220, 37, 27, 203, 239, 196, 176, 131, 37, 66, 188, 243, 185, 250, 113, 23, 167, + 211, 154, 243, 168, 215, 54, 171, 159, 36, 195, 81, 13, 150, 43, 43, + ]), + CompressedRistretto([ + 232, 121, 176, 222, 183, 196, 159, 90, 238, 193, 105, 52, 101, 167, 244, 170, 121, + 114, 196, 6, 67, 152, 80, 185, 221, 7, 83, 105, 176, 208, 224, 121, + ]), + CompressedRistretto([ + 226, 181, 183, 52, 241, 163, 61, 179, 221, 207, 220, 73, 245, 242, 25, 236, 67, 84, + 179, 222, 167, 62, 167, 182, 32, 9, 92, 30, 165, 127, 204, 68, + ]), + CompressedRistretto([ + 226, 119, 16, 242, 200, 139, 240, 87, 11, 222, 92, 146, 156, 243, 46, 119, 65, 59, + 1, 248, 92, 183, 50, 175, 87, 40, 206, 53, 208, 220, 148, 13, + ]), + CompressedRistretto([ + 70, 240, 79, 112, 54, 157, 228, 146, 74, 122, 216, 88, 232, 62, 158, 13, 14, 146, + 115, 117, 176, 222, 90, 225, 244, 23, 94, 190, 150, 7, 136, 96, + ]), + CompressedRistretto([ + 22, 71, 241, 103, 45, 193, 195, 144, 183, 101, 154, 50, 39, 68, 49, 110, 51, 44, + 62, 0, 229, 113, 72, 81, 168, 29, 73, 106, 102, 40, 132, 24, + ]), + CompressedRistretto([ + 196, 133, 107, 11, 130, 105, 74, 33, 204, 171, 133, 221, 174, 193, 241, 36, 38, + 179, 196, 107, 219, 185, 181, 253, 228, 47, 155, 42, 231, 73, 41, 78, + ]), + CompressedRistretto([ + 58, 255, 225, 197, 115, 208, 160, 143, 39, 197, 82, 69, 143, 235, 92, 170, 74, 40, + 57, 11, 171, 227, 26, 185, 217, 207, 90, 185, 197, 190, 35, 60, + ]), + CompressedRistretto([ + 88, 43, 92, 118, 223, 136, 105, 145, 238, 186, 115, 8, 214, 112, 153, 253, 38, 108, + 205, 230, 157, 130, 11, 66, 101, 85, 253, 110, 110, 14, 148, 112, + ]), + ]; + for i in 0..16 { + let r_0 = FieldElement::from_bytes(&bytes[i]); + let Q = RistrettoPoint::elligator_ristretto_flavor(&r_0); + assert_eq!(Q.compress(), encoded_images[i]); + } + } + + // Known answer tests for the one-way mapping function in the Ristretto RFC + #[test] + fn one_way_map() { + // These inputs are from + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448-04#appendix-A.3 + let test_vectors: &[([u8; 64], CompressedRistretto)] = &[ + ( + [ + 0x5d, 0x1b, 0xe0, 0x9e, 0x3d, 0x0c, 0x82, 0xfc, 0x53, 0x81, 0x12, 0x49, 0x0e, + 0x35, 0x70, 0x19, 0x79, 0xd9, 0x9e, 0x06, 0xca, 0x3e, 0x2b, 0x5b, 0x54, 0xbf, + 0xfe, 0x8b, 0x4d, 0xc7, 0x72, 0xc1, 0x4d, 0x98, 0xb6, 0x96, 0xa1, 0xbb, 0xfb, + 0x5c, 0xa3, 0x2c, 0x43, 0x6c, 0xc6, 0x1c, 0x16, 0x56, 0x37, 0x90, 0x30, 0x6c, + 0x79, 0xea, 0xca, 0x77, 0x05, 0x66, 0x8b, 0x47, 0xdf, 0xfe, 0x5b, 0xb6, + ], + CompressedRistretto([ + 0x30, 0x66, 0xf8, 0x2a, 0x1a, 0x74, 0x7d, 0x45, 0x12, 0x0d, 0x17, 0x40, 0xf1, + 0x43, 0x58, 0x53, 0x1a, 0x8f, 0x04, 0xbb, 0xff, 0xe6, 0xa8, 0x19, 0xf8, 0x6d, + 0xfe, 0x50, 0xf4, 0x4a, 0x0a, 0x46, + ]), + ), + ( + [ + 0xf1, 0x16, 0xb3, 0x4b, 0x8f, 0x17, 0xce, 0xb5, 0x6e, 0x87, 0x32, 0xa6, 0x0d, + 0x91, 0x3d, 0xd1, 0x0c, 0xce, 0x47, 0xa6, 0xd5, 0x3b, 0xee, 0x92, 0x04, 0xbe, + 0x8b, 0x44, 0xf6, 0x67, 0x8b, 0x27, 0x01, 0x02, 0xa5, 0x69, 0x02, 0xe2, 0x48, + 0x8c, 0x46, 0x12, 0x0e, 0x92, 0x76, 0xcf, 0xe5, 0x46, 0x38, 0x28, 0x6b, 0x9e, + 0x4b, 0x3c, 0xdb, 0x47, 0x0b, 0x54, 0x2d, 0x46, 0xc2, 0x06, 0x8d, 0x38, + ], + CompressedRistretto([ + 0xf2, 0x6e, 0x5b, 0x6f, 0x7d, 0x36, 0x2d, 0x2d, 0x2a, 0x94, 0xc5, 0xd0, 0xe7, + 0x60, 0x2c, 0xb4, 0x77, 0x3c, 0x95, 0xa2, 0xe5, 0xc3, 0x1a, 0x64, 0xf1, 0x33, + 0x18, 0x9f, 0xa7, 0x6e, 0xd6, 0x1b, + ]), + ), + ( + [ + 0x84, 0x22, 0xe1, 0xbb, 0xda, 0xab, 0x52, 0x93, 0x8b, 0x81, 0xfd, 0x60, 0x2e, + 0xff, 0xb6, 0xf8, 0x91, 0x10, 0xe1, 0xe5, 0x72, 0x08, 0xad, 0x12, 0xd9, 0xad, + 0x76, 0x7e, 0x2e, 0x25, 0x51, 0x0c, 0x27, 0x14, 0x07, 0x75, 0xf9, 0x33, 0x70, + 0x88, 0xb9, 0x82, 0xd8, 0x3d, 0x7f, 0xcf, 0x0b, 0x2f, 0xa1, 0xed, 0xff, 0xe5, + 0x19, 0x52, 0xcb, 0xe7, 0x36, 0x5e, 0x95, 0xc8, 0x6e, 0xaf, 0x32, 0x5c, + ], + CompressedRistretto([ + 0x00, 0x6c, 0xcd, 0x2a, 0x9e, 0x68, 0x67, 0xe6, 0xa2, 0xc5, 0xce, 0xa8, 0x3d, + 0x33, 0x02, 0xcc, 0x9d, 0xe1, 0x28, 0xdd, 0x2a, 0x9a, 0x57, 0xdd, 0x8e, 0xe7, + 0xb9, 0xd7, 0xff, 0xe0, 0x28, 0x26, + ]), + ), + ( + [ + 0xac, 0x22, 0x41, 0x51, 0x29, 0xb6, 0x14, 0x27, 0xbf, 0x46, 0x4e, 0x17, 0xba, + 0xee, 0x8d, 0xb6, 0x59, 0x40, 0xc2, 0x33, 0xb9, 0x8a, 0xfc, 0xe8, 0xd1, 0x7c, + 0x57, 0xbe, 0xeb, 0x78, 0x76, 0xc2, 0x15, 0x0d, 0x15, 0xaf, 0x1c, 0xb1, 0xfb, + 0x82, 0x4b, 0xbd, 0x14, 0x95, 0x5f, 0x2b, 0x57, 0xd0, 0x8d, 0x38, 0x8a, 0xab, + 0x43, 0x1a, 0x39, 0x1c, 0xfc, 0x33, 0xd5, 0xba, 0xfb, 0x5d, 0xbb, 0xaf, + ], + CompressedRistretto([ + 0xf8, 0xf0, 0xc8, 0x7c, 0xf2, 0x37, 0x95, 0x3c, 0x58, 0x90, 0xae, 0xc3, 0x99, + 0x81, 0x69, 0x00, 0x5d, 0xae, 0x3e, 0xca, 0x1f, 0xbb, 0x04, 0x54, 0x8c, 0x63, + 0x59, 0x53, 0xc8, 0x17, 0xf9, 0x2a, + ]), + ), + ( + [ + 0x16, 0x5d, 0x69, 0x7a, 0x1e, 0xf3, 0xd5, 0xcf, 0x3c, 0x38, 0x56, 0x5b, 0xee, + 0xfc, 0xf8, 0x8c, 0x0f, 0x28, 0x2b, 0x8e, 0x7d, 0xbd, 0x28, 0x54, 0x4c, 0x48, + 0x34, 0x32, 0xf1, 0xce, 0xc7, 0x67, 0x5d, 0xeb, 0xea, 0x8e, 0xbb, 0x4e, 0x5f, + 0xe7, 0xd6, 0xf6, 0xe5, 0xdb, 0x15, 0xf1, 0x55, 0x87, 0xac, 0x4d, 0x4d, 0x4a, + 0x1d, 0xe7, 0x19, 0x1e, 0x0c, 0x1c, 0xa6, 0x66, 0x4a, 0xbc, 0xc4, 0x13, + ], + CompressedRistretto([ + 0xae, 0x81, 0xe7, 0xde, 0xdf, 0x20, 0xa4, 0x97, 0xe1, 0x0c, 0x30, 0x4a, 0x76, + 0x5c, 0x17, 0x67, 0xa4, 0x2d, 0x6e, 0x06, 0x02, 0x97, 0x58, 0xd2, 0xd7, 0xe8, + 0xef, 0x7c, 0xc4, 0xc4, 0x11, 0x79, + ]), + ), + ( + [ + 0xa8, 0x36, 0xe6, 0xc9, 0xa9, 0xca, 0x9f, 0x1e, 0x8d, 0x48, 0x62, 0x73, 0xad, + 0x56, 0xa7, 0x8c, 0x70, 0xcf, 0x18, 0xf0, 0xce, 0x10, 0xab, 0xb1, 0xc7, 0x17, + 0x2d, 0xdd, 0x60, 0x5d, 0x7f, 0xd2, 0x97, 0x98, 0x54, 0xf4, 0x7a, 0xe1, 0xcc, + 0xf2, 0x04, 0xa3, 0x31, 0x02, 0x09, 0x5b, 0x42, 0x00, 0xe5, 0xbe, 0xfc, 0x04, + 0x65, 0xac, 0xcc, 0x26, 0x31, 0x75, 0x48, 0x5f, 0x0e, 0x17, 0xea, 0x5c, + ], + CompressedRistretto([ + 0xe2, 0x70, 0x56, 0x52, 0xff, 0x9f, 0x5e, 0x44, 0xd3, 0xe8, 0x41, 0xbf, 0x1c, + 0x25, 0x1c, 0xf7, 0xdd, 0xdb, 0x77, 0xd1, 0x40, 0x87, 0x0d, 0x1a, 0xb2, 0xed, + 0x64, 0xf1, 0xa9, 0xce, 0x86, 0x28, + ]), + ), + ( + [ + 0x2c, 0xdc, 0x11, 0xea, 0xeb, 0x95, 0xda, 0xf0, 0x11, 0x89, 0x41, 0x7c, 0xdd, + 0xdb, 0xf9, 0x59, 0x52, 0x99, 0x3a, 0xa9, 0xcb, 0x9c, 0x64, 0x0e, 0xb5, 0x05, + 0x8d, 0x09, 0x70, 0x2c, 0x74, 0x62, 0x2c, 0x99, 0x65, 0xa6, 0x97, 0xa3, 0xb3, + 0x45, 0xec, 0x24, 0xee, 0x56, 0x33, 0x5b, 0x55, 0x6e, 0x67, 0x7b, 0x30, 0xe6, + 0xf9, 0x0a, 0xc7, 0x7d, 0x78, 0x10, 0x64, 0xf8, 0x66, 0xa3, 0xc9, 0x82, + ], + CompressedRistretto([ + 0x80, 0xbd, 0x07, 0x26, 0x25, 0x11, 0xcd, 0xde, 0x48, 0x63, 0xf8, 0xa7, 0x43, + 0x4c, 0xef, 0x69, 0x67, 0x50, 0x68, 0x1c, 0xb9, 0x51, 0x0e, 0xea, 0x55, 0x70, + 0x88, 0xf7, 0x6d, 0x9e, 0x50, 0x65, + ]), + ), + ( + [ + 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + CompressedRistretto([ + 0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb, + 0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79, + 0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f, + ]), + ), + ( + [ + 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ], + CompressedRistretto([ + 0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb, + 0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79, + 0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f, + ]), + ), + ( + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, + ], + CompressedRistretto([ + 0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb, + 0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79, + 0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f, + ]), + ), + ( + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + ], + CompressedRistretto([ + 0x30, 0x42, 0x82, 0x79, 0x10, 0x23, 0xb7, 0x31, 0x28, 0xd2, 0x77, 0xbd, 0xcb, + 0x5c, 0x77, 0x46, 0xef, 0x2e, 0xac, 0x08, 0xdd, 0xe9, 0xf2, 0x98, 0x33, 0x79, + 0xcb, 0x8e, 0x5e, 0xf0, 0x51, 0x7f, + ]), + ), + ]; + // Check that onewaymap(input) == output for all the above vectors + for (input, output) in test_vectors { + let Q = RistrettoPoint::from_uniform_bytes(input); + assert_eq!(&Q.compress(), output); + } + } + + #[test] + fn random_roundtrip() { + let mut rng = OsRng; + for _ in 0..100 { + let P = RistrettoPoint::mul_base(&Scalar::random(&mut rng)); + let compressed_P = P.compress(); + let Q = compressed_P.decompress().unwrap(); + assert_eq!(P, Q); + } + } + + #[test] + #[cfg(all(feature = "alloc", feature = "rand_core"))] + fn double_and_compress_1024_random_points() { + let mut rng = OsRng; + + let mut points: Vec = (0..1024) + .map(|_| RistrettoPoint::random(&mut rng)) + .collect(); + points[500] = RistrettoPoint::identity(); + + let compressed = RistrettoPoint::double_and_compress_batch(&points); + + for (P, P2_compressed) in points.iter().zip(compressed.iter()) { + assert_eq!(*P2_compressed, (P + P).compress()); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn vartime_precomputed_vs_nonprecomputed_multiscalar() { + let mut rng = rand::thread_rng(); + + let static_scalars = (0..128) + .map(|_| Scalar::random(&mut rng)) + .collect::>(); + + let dynamic_scalars = (0..128) + .map(|_| Scalar::random(&mut rng)) + .collect::>(); + + let check_scalar: Scalar = static_scalars + .iter() + .chain(dynamic_scalars.iter()) + .map(|s| s * s) + .sum(); + + let static_points = static_scalars + .iter() + .map(RistrettoPoint::mul_base) + .collect::>(); + let dynamic_points = dynamic_scalars + .iter() + .map(RistrettoPoint::mul_base) + .collect::>(); + + let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter()); + + let P = precomputation.vartime_mixed_multiscalar_mul( + &static_scalars, + &dynamic_scalars, + &dynamic_points, + ); + + use crate::traits::VartimeMultiscalarMul; + let Q = RistrettoPoint::vartime_multiscalar_mul( + static_scalars.iter().chain(dynamic_scalars.iter()), + static_points.iter().chain(dynamic_points.iter()), + ); + + let R = RistrettoPoint::mul_base(&check_scalar); + + assert_eq!(P.compress(), R.compress()); + assert_eq!(Q.compress(), R.compress()); + } +} diff --git a/curve25519-elligator2/src/scalar.rs b/curve25519-elligator2/src/scalar.rs new file mode 100644 index 00000000..593a3385 --- /dev/null +++ b/curve25519-elligator2/src/scalar.rs @@ -0,0 +1,2087 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// Portions Copyright 2017 Brian Smith +// See LICENSE for licensing information. +// +// Authors: +// - Isis Agora Lovecruft +// - Henry de Valence +// - Brian Smith + +//! Arithmetic on scalars (integers mod the group order). +//! +//! Both the Ristretto group and the Ed25519 basepoint have prime order +//! \\( \ell = 2\^{252} + 27742317777372353535851937790883648493 \\). +//! +//! This code is intended to be useful with both the Ristretto group +//! (where everything is done modulo \\( \ell \\)), and the X/Ed25519 +//! setting, which mandates specific bit-twiddles that are not +//! well-defined modulo \\( \ell \\). +//! +//! All arithmetic on `Scalars` is done modulo \\( \ell \\). +//! +//! # Constructing a scalar +//! +//! To create a [`Scalar`](struct.Scalar.html) from a supposedly canonical encoding, use +//! [`Scalar::from_canonical_bytes`](struct.Scalar.html#method.from_canonical_bytes). +//! +//! This function does input validation, ensuring that the input bytes +//! are the canonical encoding of a `Scalar`. +//! If they are, we'll get +//! `Some(Scalar)` in return: +//! +//! ``` +//! use curve25519_elligator2::scalar::Scalar; +//! +//! let one_as_bytes: [u8; 32] = Scalar::ONE.to_bytes(); +//! let a: Option = Scalar::from_canonical_bytes(one_as_bytes).into(); +//! +//! assert!(a.is_some()); +//! ``` +//! +//! However, if we give it bytes representing a scalar larger than \\( \ell \\) +//! (in this case, \\( \ell + 2 \\)), we'll get `None` back: +//! +//! ``` +//! use curve25519_elligator2::scalar::Scalar; +//! +//! let l_plus_two_bytes: [u8; 32] = [ +//! 0xef, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, +//! 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, +//! 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +//! 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, +//! ]; +//! let a: Option = Scalar::from_canonical_bytes(l_plus_two_bytes).into(); +//! +//! assert!(a.is_none()); +//! ``` +//! +//! Another way to create a `Scalar` is by reducing a \\(256\\)-bit integer mod +//! \\( \ell \\), for which one may use the +//! [`Scalar::from_bytes_mod_order`](struct.Scalar.html#method.from_bytes_mod_order) +//! method. In the case of the second example above, this would reduce the +//! resultant scalar \\( \mod \ell \\), producing \\( 2 \\): +//! +//! ``` +//! use curve25519_elligator2::scalar::Scalar; +//! +//! let l_plus_two_bytes: [u8; 32] = [ +//! 0xef, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, +//! 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, +//! 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +//! 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, +//! ]; +//! let a: Scalar = Scalar::from_bytes_mod_order(l_plus_two_bytes); +//! +//! let two: Scalar = Scalar::ONE + Scalar::ONE; +//! +//! assert!(a == two); +//! ``` +//! +//! There is also a constructor that reduces a \\(512\\)-bit integer, +//! [`Scalar::from_bytes_mod_order_wide`]. +//! + +//! When the `digest` feature is enabled, a [`Scalar`] can be construted from the +//! the hash of some input data using +#![cfg_attr(feature = "digest", doc = "[`Scalar::hash_from_bytes`]")] +#![cfg_attr(not(feature = "digest"), doc = "`Scalar::hash_from_bytes`")] +//! , which takes a buffer, or +#![cfg_attr(feature = "digest", doc = "[`Scalar::from_hash`]")] +#![cfg_attr(not(feature = "digest"), doc = "`Scalar::from_hash`")] +//! , which allows an IUF API. +//! +#![cfg_attr(feature = "digest", doc = "```")] +#![cfg_attr(not(feature = "digest"), doc = "```ignore")] +//! # fn main() { +//! use sha2::{Digest, Sha512}; +//! use curve25519_elligator2::scalar::Scalar; +//! +//! // Hashing a single byte slice +//! let a = Scalar::hash_from_bytes::(b"Abolish ICE"); +//! +//! // Streaming data into a hash object +//! let mut hasher = Sha512::default(); +//! hasher.update(b"Abolish "); +//! hasher.update(b"ICE"); +//! let a2 = Scalar::from_hash(hasher); +//! +//! assert_eq!(a, a2); +//! # } +//! ``` +//! +//! See also `Scalar::hash_from_bytes` and `Scalar::from_hash` that +//! reduces a \\(512\\)-bit integer, if the optional `digest` feature +//! has been enabled. + +use core::borrow::Borrow; +use core::fmt::Debug; +use core::iter::{Product, Sum}; +use core::ops::Index; +use core::ops::Neg; +use core::ops::{Add, AddAssign}; +use core::ops::{Mul, MulAssign}; +use core::ops::{Sub, SubAssign}; + +use cfg_if::cfg_if; + +#[cfg(feature = "group")] +use group::ff::{Field, FromUniformBytes, PrimeField}; +#[cfg(feature = "group-bits")] +use group::ff::{FieldBits, PrimeFieldBits}; + +#[cfg(any(test, feature = "group"))] +use rand_core::RngCore; + +#[cfg(any(test, feature = "rand_core"))] +use rand_core::CryptoRngCore; + +#[cfg(feature = "digest")] +use digest::generic_array::typenum::U64; +#[cfg(feature = "digest")] +use digest::Digest; + +use subtle::Choice; +use subtle::ConditionallySelectable; +use subtle::ConstantTimeEq; +use subtle::CtOption; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +use crate::backend; +use crate::constants; + +cfg_if! { + if #[cfg(curve25519_dalek_backend = "fiat")] { + /// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed. + /// + /// This is a type alias for one of the scalar types in the `backend` + /// module. + #[cfg(curve25519_dalek_bits = "32")] + #[cfg_attr( + docsrs, + doc(cfg(all(feature = "fiat_backend", curve25519_dalek_bits = "32"))) + )] + type UnpackedScalar = backend::serial::fiat_u32::scalar::Scalar29; + + /// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed. + /// + /// This is a type alias for one of the scalar types in the `backend` + /// module. + #[cfg(curve25519_dalek_bits = "64")] + #[cfg_attr( + docsrs, + doc(cfg(all(feature = "fiat_backend", curve25519_dalek_bits = "64"))) + )] + type UnpackedScalar = backend::serial::fiat_u64::scalar::Scalar52; + } else if #[cfg(curve25519_dalek_bits = "64")] { + /// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed. + /// + /// This is a type alias for one of the scalar types in the `backend` + /// module. + #[cfg_attr(docsrs, doc(cfg(curve25519_dalek_bits = "64")))] + type UnpackedScalar = backend::serial::u64::scalar::Scalar52; + } else { + /// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed. + /// + /// This is a type alias for one of the scalar types in the `backend` + /// module. + #[cfg_attr(docsrs, doc(cfg(curve25519_dalek_bits = "64")))] + type UnpackedScalar = backend::serial::u32::scalar::Scalar29; + } +} + +/// The `Scalar` struct holds an element of \\(\mathbb Z / \ell\mathbb Z \\). +#[allow(clippy::derived_hash_with_manual_eq)] +#[derive(Copy, Clone, Hash)] +pub struct Scalar { + /// `bytes` is a little-endian byte encoding of an integer representing a scalar modulo the + /// group order. + /// + /// # Invariant #1 + /// + /// The integer representing this scalar is less than \\(2\^{255}\\). That is, the most + /// significant bit of `bytes[31]` is 0. + /// + /// This is required for `EdwardsPoint` variable- and fixed-base multiplication, because most + /// integers above 2^255 are unrepresentable in our radix-16 NAF (see [`Self::as_radix_16`]). + /// The invariant is also required because our `MontgomeryPoint` multiplication assumes the MSB + /// is 0 (see `MontgomeryPoint::mul`). + /// + /// # Invariant #2 (weak) + /// + /// The integer representing this scalar is less than \\(2\^{255} - 19 \\), i.e., it represents + /// a canonical representative of an element of \\( \mathbb Z / \ell\mathbb Z \\). This is + /// stronger than invariant #1. It also sometimes has to be broken. + /// + /// This invariant is deliberately broken in the implementation of `EdwardsPoint::{mul_clamped, + /// mul_base_clamped}`, `MontgomeryPoint::{mul_clamped, mul_base_clamped}`, and + /// `BasepointTable::mul_base_clamped`. This is not an issue though. As mentioned above, + /// scalar-point multiplication is defined for any choice of `bytes` that satisfies invariant + /// #1. Since clamping guarantees invariant #1 is satisfied, these operations are well defined. + /// + /// Note: Scalar-point mult is the _only_ thing you can do safely with an unreduced scalar. + /// Scalar-scalar addition and subtraction are NOT correct when using unreduced scalars. + /// Multiplication is correct, but this is only due to a quirk of our implementation, and not + /// guaranteed to hold in general in the future. + /// + /// Note: It is not possible to construct an unreduced `Scalar` from the public API unless the + /// `legacy_compatibility` is enabled (thus making `Scalar::from_bits` public). Thus, for all + /// public non-legacy uses, invariant #2 + /// always holds. + /// + pub(crate) bytes: [u8; 32], +} + +impl Scalar { + /// Construct a `Scalar` by reducing a 256-bit little-endian integer + /// modulo the group order \\( \ell \\). + pub fn from_bytes_mod_order(bytes: [u8; 32]) -> Scalar { + // Temporarily allow s_unreduced.bytes > 2^255 ... + let s_unreduced = Scalar { bytes }; + + // Then reduce mod the group order and return the reduced representative. + let s = s_unreduced.reduce(); + debug_assert_eq!(0u8, s[31] >> 7); + + s + } + + /// Construct a `Scalar` by reducing a 512-bit little-endian integer + /// modulo the group order \\( \ell \\). + pub fn from_bytes_mod_order_wide(input: &[u8; 64]) -> Scalar { + UnpackedScalar::from_bytes_wide(input).pack() + } + + /// Attempt to construct a `Scalar` from a canonical byte representation. + /// + /// # Return + /// + /// - `Some(s)`, where `s` is the `Scalar` corresponding to `bytes`, + /// if `bytes` is a canonical byte representation modulo the group order \\( \ell \\); + /// - `None` if `bytes` is not a canonical byte representation. + pub fn from_canonical_bytes(bytes: [u8; 32]) -> CtOption { + let high_bit_unset = (bytes[31] >> 7).ct_eq(&0); + let candidate = Scalar { bytes }; + CtOption::new(candidate, high_bit_unset & candidate.is_canonical()) + } + + /// Construct a `Scalar` from the low 255 bits of a 256-bit integer. This breaks the invariant + /// that scalars are always reduced. Scalar-scalar arithmetic, i.e., addition, subtraction, + /// multiplication, **does not work** on scalars produced from this function. You may only use + /// the output of this function for `EdwardsPoint::mul`, `MontgomeryPoint::mul`, and + /// `EdwardsPoint::vartime_double_scalar_mul_basepoint`. **Do not use this function** unless + /// you absolutely have to. + #[cfg(feature = "legacy_compatibility")] + #[deprecated( + since = "4.0.0", + note = "This constructor outputs scalars with undefined scalar-scalar arithmetic. See docs." + )] + pub const fn from_bits(bytes: [u8; 32]) -> Scalar { + let mut s = Scalar { bytes }; + // Ensure invariant #1 holds. That is, make s < 2^255 by masking the high bit. + s.bytes[31] &= 0b0111_1111; + + s + } +} + +impl Debug for Scalar { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Scalar{{\n\tbytes: {:?},\n}}", &self.bytes) + } +} + +impl Eq for Scalar {} +impl PartialEq for Scalar { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl ConstantTimeEq for Scalar { + fn ct_eq(&self, other: &Self) -> Choice { + self.bytes.ct_eq(&other.bytes) + } +} + +impl Index for Scalar { + type Output = u8; + + /// Index the bytes of the representative for this `Scalar`. Mutation is not permitted. + fn index(&self, _index: usize) -> &u8 { + &(self.bytes[_index]) + } +} + +impl<'b> MulAssign<&'b Scalar> for Scalar { + fn mul_assign(&mut self, _rhs: &'b Scalar) { + *self = UnpackedScalar::mul(&self.unpack(), &_rhs.unpack()).pack(); + } +} + +define_mul_assign_variants!(LHS = Scalar, RHS = Scalar); + +impl<'a, 'b> Mul<&'b Scalar> for &'a Scalar { + type Output = Scalar; + fn mul(self, _rhs: &'b Scalar) -> Scalar { + UnpackedScalar::mul(&self.unpack(), &_rhs.unpack()).pack() + } +} + +define_mul_variants!(LHS = Scalar, RHS = Scalar, Output = Scalar); + +impl<'b> AddAssign<&'b Scalar> for Scalar { + fn add_assign(&mut self, _rhs: &'b Scalar) { + *self = *self + _rhs; + } +} + +define_add_assign_variants!(LHS = Scalar, RHS = Scalar); + +impl<'a, 'b> Add<&'b Scalar> for &'a Scalar { + type Output = Scalar; + #[allow(non_snake_case)] + fn add(self, _rhs: &'b Scalar) -> Scalar { + // The UnpackedScalar::add function produces reduced outputs if the inputs are reduced. By + // Scalar invariant #1, this is always the case. + UnpackedScalar::add(&self.unpack(), &_rhs.unpack()).pack() + } +} + +define_add_variants!(LHS = Scalar, RHS = Scalar, Output = Scalar); + +impl<'b> SubAssign<&'b Scalar> for Scalar { + fn sub_assign(&mut self, _rhs: &'b Scalar) { + *self = *self - _rhs; + } +} + +define_sub_assign_variants!(LHS = Scalar, RHS = Scalar); + +impl<'a, 'b> Sub<&'b Scalar> for &'a Scalar { + type Output = Scalar; + #[allow(non_snake_case)] + fn sub(self, rhs: &'b Scalar) -> Scalar { + // The UnpackedScalar::sub function produces reduced outputs if the inputs are reduced. By + // Scalar invariant #1, this is always the case. + UnpackedScalar::sub(&self.unpack(), &rhs.unpack()).pack() + } +} + +define_sub_variants!(LHS = Scalar, RHS = Scalar, Output = Scalar); + +impl<'a> Neg for &'a Scalar { + type Output = Scalar; + #[allow(non_snake_case)] + fn neg(self) -> Scalar { + let self_R = UnpackedScalar::mul_internal(&self.unpack(), &constants::R); + let self_mod_l = UnpackedScalar::montgomery_reduce(&self_R); + UnpackedScalar::sub(&UnpackedScalar::ZERO, &self_mod_l).pack() + } +} + +impl Neg for Scalar { + type Output = Scalar; + fn neg(self) -> Scalar { + -&self + } +} + +impl ConditionallySelectable for Scalar { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + let mut bytes = [0u8; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + bytes[i] = u8::conditional_select(&a.bytes[i], &b.bytes[i], choice); + } + Scalar { bytes } + } +} + +#[cfg(feature = "serde")] +use serde::de::Visitor; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl Serialize for Scalar { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeTuple; + let mut tup = serializer.serialize_tuple(32)?; + for byte in self.as_bytes().iter() { + tup.serialize_element(byte)?; + } + tup.end() + } +} + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl<'de> Deserialize<'de> for Scalar { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ScalarVisitor; + + impl<'de> Visitor<'de> for ScalarVisitor { + type Value = Scalar; + + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + formatter.write_str( + "a sequence of 32 bytes whose little-endian interpretation is less than the \ + basepoint order ℓ", + ) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut bytes = [0u8; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + bytes[i] = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?; + } + Option::from(Scalar::from_canonical_bytes(bytes)) + .ok_or_else(|| serde::de::Error::custom("scalar was not canonically encoded")) + } + } + + deserializer.deserialize_tuple(32, ScalarVisitor) + } +} + +impl Product for Scalar +where + T: Borrow, +{ + fn product(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Scalar::ONE, |acc, item| acc * item.borrow()) + } +} + +impl Sum for Scalar +where + T: Borrow, +{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Scalar::ZERO, |acc, item| acc + item.borrow()) + } +} + +impl Default for Scalar { + fn default() -> Scalar { + Scalar::ZERO + } +} + +impl From for Scalar { + fn from(x: u8) -> Scalar { + let mut s_bytes = [0u8; 32]; + s_bytes[0] = x; + Scalar { bytes: s_bytes } + } +} + +impl From for Scalar { + fn from(x: u16) -> Scalar { + let mut s_bytes = [0u8; 32]; + let x_bytes = x.to_le_bytes(); + s_bytes[0..x_bytes.len()].copy_from_slice(&x_bytes); + Scalar { bytes: s_bytes } + } +} + +impl From for Scalar { + fn from(x: u32) -> Scalar { + let mut s_bytes = [0u8; 32]; + let x_bytes = x.to_le_bytes(); + s_bytes[0..x_bytes.len()].copy_from_slice(&x_bytes); + Scalar { bytes: s_bytes } + } +} + +impl From for Scalar { + /// Construct a scalar from the given `u64`. + /// + /// # Inputs + /// + /// An `u64` to convert to a `Scalar`. + /// + /// # Returns + /// + /// A `Scalar` corresponding to the input `u64`. + /// + /// # Example + /// + /// ``` + /// use curve25519_elligator2::scalar::Scalar; + /// + /// let fourtytwo = Scalar::from(42u64); + /// let six = Scalar::from(6u64); + /// let seven = Scalar::from(7u64); + /// + /// assert!(fourtytwo == six * seven); + /// ``` + fn from(x: u64) -> Scalar { + let mut s_bytes = [0u8; 32]; + let x_bytes = x.to_le_bytes(); + s_bytes[0..x_bytes.len()].copy_from_slice(&x_bytes); + Scalar { bytes: s_bytes } + } +} + +impl From for Scalar { + fn from(x: u128) -> Scalar { + let mut s_bytes = [0u8; 32]; + let x_bytes = x.to_le_bytes(); + s_bytes[0..x_bytes.len()].copy_from_slice(&x_bytes); + Scalar { bytes: s_bytes } + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for Scalar { + fn zeroize(&mut self) { + self.bytes.zeroize(); + } +} + +impl Scalar { + /// The scalar \\( 0 \\). + pub const ZERO: Self = Self { bytes: [0u8; 32] }; + + /// The scalar \\( 1 \\). + pub const ONE: Self = Self { + bytes: [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ], + }; + + #[cfg(any(test, feature = "rand_core"))] + /// Return a `Scalar` chosen uniformly at random using a user-provided RNG. + /// + /// # Inputs + /// + /// * `rng`: any RNG which implements `CryptoRngCore` + /// (i.e. `CryptoRng` + `RngCore`) interface. + /// + /// # Returns + /// + /// A random scalar within \\(\mathbb{Z} / \ell\mathbb{Z}\\). + /// + /// # Example + /// + /// ``` + /// # fn main() { + /// use curve25519_elligator2::scalar::Scalar; + /// + /// use rand_core::OsRng; + /// + /// let mut csprng = OsRng; + /// let a: Scalar = Scalar::random(&mut csprng); + /// # } + pub fn random(rng: &mut R) -> Self { + let mut scalar_bytes = [0u8; 64]; + rng.fill_bytes(&mut scalar_bytes); + Scalar::from_bytes_mod_order_wide(&scalar_bytes) + } + + #[cfg(feature = "digest")] + /// Hash a slice of bytes into a scalar. + /// + /// Takes a type parameter `D`, which is any `Digest` producing 64 + /// bytes (512 bits) of output. + /// + /// Convenience wrapper around `from_hash`. + /// + /// # Example + /// + #[cfg_attr(feature = "digest", doc = "```")] + #[cfg_attr(not(feature = "digest"), doc = "```ignore")] + /// # use curve25519_elligator2::scalar::Scalar; + /// use sha2::Sha512; + /// + /// # // Need fn main() here in comment so the doctest compiles + /// # // See https://doc.rust-lang.org/book/documentation.html#documentation-as-tests + /// # fn main() { + /// let msg = "To really appreciate architecture, you may even need to commit a murder"; + /// let s = Scalar::hash_from_bytes::(msg.as_bytes()); + /// # } + /// ``` + pub fn hash_from_bytes(input: &[u8]) -> Scalar + where + D: Digest + Default, + { + let mut hash = D::default(); + hash.update(input); + Scalar::from_hash(hash) + } + + #[cfg(feature = "digest")] + /// Construct a scalar from an existing `Digest` instance. + /// + /// Use this instead of `hash_from_bytes` if it is more convenient + /// to stream data into the `Digest` than to pass a single byte + /// slice. + /// + /// # Example + /// + /// ``` + /// # use curve25519_elligator2::scalar::Scalar; + /// use curve25519_elligator2::digest::Update; + /// + /// use sha2::Digest; + /// use sha2::Sha512; + /// + /// # fn main() { + /// let mut h = Sha512::new() + /// .chain("To really appreciate architecture, you may even need to commit a murder.") + /// .chain("While the programs used for The Manhattan Transcripts are of the most extreme") + /// .chain("nature, they also parallel the most common formula plot: the archetype of") + /// .chain("murder. Other phantasms were occasionally used to underline the fact that") + /// .chain("perhaps all architecture, rather than being about functional standards, is") + /// .chain("about love and death."); + /// + /// let s = Scalar::from_hash(h); + /// + /// println!("{:?}", s.to_bytes()); + /// assert_eq!( + /// s.to_bytes(), + /// [ 21, 88, 208, 252, 63, 122, 210, 152, + /// 154, 38, 15, 23, 16, 167, 80, 150, + /// 192, 221, 77, 226, 62, 25, 224, 148, + /// 239, 48, 176, 10, 185, 69, 168, 11, ], + /// ); + /// # } + /// ``` + pub fn from_hash(hash: D) -> Scalar + where + D: Digest, + { + let mut output = [0u8; 64]; + output.copy_from_slice(hash.finalize().as_slice()); + Scalar::from_bytes_mod_order_wide(&output) + } + + /// Convert this `Scalar` to its underlying sequence of bytes. + /// + /// # Example + /// + /// ``` + /// use curve25519_elligator2::scalar::Scalar; + /// + /// let s: Scalar = Scalar::ZERO; + /// + /// assert!(s.to_bytes() == [0u8; 32]); + /// ``` + pub const fn to_bytes(&self) -> [u8; 32] { + self.bytes + } + + /// View the little-endian byte encoding of the integer representing this Scalar. + /// + /// # Example + /// + /// ``` + /// use curve25519_elligator2::scalar::Scalar; + /// + /// let s: Scalar = Scalar::ZERO; + /// + /// assert!(s.as_bytes() == &[0u8; 32]); + /// ``` + pub const fn as_bytes(&self) -> &[u8; 32] { + &self.bytes + } + + /// Given a nonzero `Scalar`, compute its multiplicative inverse. + /// + /// # Warning + /// + /// `self` **MUST** be nonzero. If you cannot + /// *prove* that this is the case, you **SHOULD NOT USE THIS + /// FUNCTION**. + /// + /// # Returns + /// + /// The multiplicative inverse of the this `Scalar`. + /// + /// # Example + /// + /// ``` + /// use curve25519_elligator2::scalar::Scalar; + /// + /// // x = 2238329342913194256032495932344128051776374960164957527413114840482143558222 + /// let X: Scalar = Scalar::from_bytes_mod_order([ + /// 0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, + /// 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2, 0x7d, 0x52, + /// 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, + /// 0xd4, 0x49, 0xf4, 0xa8, 0x79, 0xd9, 0xf2, 0x04, + /// ]); + /// // 1/x = 6859937278830797291664592131120606308688036382723378951768035303146619657244 + /// let XINV: Scalar = Scalar::from_bytes_mod_order([ + /// 0x1c, 0xdc, 0x17, 0xfc, 0xe0, 0xe9, 0xa5, 0xbb, + /// 0xd9, 0x24, 0x7e, 0x56, 0xbb, 0x01, 0x63, 0x47, + /// 0xbb, 0xba, 0x31, 0xed, 0xd5, 0xa9, 0xbb, 0x96, + /// 0xd5, 0x0b, 0xcd, 0x7a, 0x3f, 0x96, 0x2a, 0x0f, + /// ]); + /// + /// let inv_X: Scalar = X.invert(); + /// assert!(XINV == inv_X); + /// let should_be_one: Scalar = &inv_X * &X; + /// assert!(should_be_one == Scalar::ONE); + /// ``` + pub fn invert(&self) -> Scalar { + self.unpack().invert().pack() + } + + /// Given a slice of nonzero (possibly secret) `Scalar`s, + /// compute their inverses in a batch. + /// + /// # Return + /// + /// Each element of `inputs` is replaced by its inverse. + /// + /// The product of all inverses is returned. + /// + /// # Warning + /// + /// All input `Scalars` **MUST** be nonzero. If you cannot + /// *prove* that this is the case, you **SHOULD NOT USE THIS + /// FUNCTION**. + /// + /// # Example + /// + /// ``` + /// # use curve25519_elligator2::scalar::Scalar; + /// # fn main() { + /// let mut scalars = [ + /// Scalar::from(3u64), + /// Scalar::from(5u64), + /// Scalar::from(7u64), + /// Scalar::from(11u64), + /// ]; + /// + /// let allinv = Scalar::batch_invert(&mut scalars); + /// + /// assert_eq!(allinv, Scalar::from(3*5*7*11u64).invert()); + /// assert_eq!(scalars[0], Scalar::from(3u64).invert()); + /// assert_eq!(scalars[1], Scalar::from(5u64).invert()); + /// assert_eq!(scalars[2], Scalar::from(7u64).invert()); + /// assert_eq!(scalars[3], Scalar::from(11u64).invert()); + /// # } + /// ``` + #[cfg(feature = "alloc")] + pub fn batch_invert(inputs: &mut [Scalar]) -> Scalar { + // This code is essentially identical to the FieldElement + // implementation, and is documented there. Unfortunately, + // it's not easy to write it generically, since here we want + // to use `UnpackedScalar`s internally, and `Scalar`s + // externally, but there's no corresponding distinction for + // field elements. + + let n = inputs.len(); + let one: UnpackedScalar = Scalar::ONE.unpack().as_montgomery(); + + let mut scratch = vec![one; n]; + + // Keep an accumulator of all of the previous products + let mut acc = Scalar::ONE.unpack().as_montgomery(); + + // Pass through the input vector, recording the previous + // products in the scratch space + for (input, scratch) in inputs.iter_mut().zip(scratch.iter_mut()) { + *scratch = acc; + + // Avoid unnecessary Montgomery multiplication in second pass by + // keeping inputs in Montgomery form + let tmp = input.unpack().as_montgomery(); + *input = tmp.pack(); + acc = UnpackedScalar::montgomery_mul(&acc, &tmp); + } + + // acc is nonzero iff all inputs are nonzero + debug_assert!(acc.pack() != Scalar::ZERO); + + // Compute the inverse of all products + acc = acc.montgomery_invert().from_montgomery(); + + // We need to return the product of all inverses later + let ret = acc.pack(); + + // Pass through the vector backwards to compute the inverses + // in place + for (input, scratch) in inputs.iter_mut().rev().zip(scratch.iter().rev()) { + let tmp = UnpackedScalar::montgomery_mul(&acc, &input.unpack()); + *input = UnpackedScalar::montgomery_mul(&acc, scratch).pack(); + acc = tmp; + } + + #[cfg(feature = "zeroize")] + Zeroize::zeroize(&mut scratch); + + ret + } + + /// Get the bits of the scalar, in little-endian order + pub(crate) fn bits_le(&self) -> impl DoubleEndedIterator + '_ { + (0..256).map(|i| { + // As i runs from 0..256, the bottom 3 bits index the bit, while the upper bits index + // the byte. Since self.bytes is little-endian at the byte level, this iterator is + // little-endian on the bit level + ((self.bytes[i >> 3] >> (i & 7)) & 1u8) == 1 + }) + } + + /// Compute a width-\\(w\\) "Non-Adjacent Form" of this scalar. + /// + /// A width-\\(w\\) NAF of a positive integer \\(k\\) is an expression + /// $$ + /// k = \sum_{i=0}\^m n\_i 2\^i, + /// $$ + /// where each nonzero + /// coefficient \\(n\_i\\) is odd and bounded by \\(|n\_i| < 2\^{w-1}\\), + /// \\(n\_{m-1}\\) is nonzero, and at most one of any \\(w\\) consecutive + /// coefficients is nonzero. (Hankerson, Menezes, Vanstone; def 3.32). + /// + /// The length of the NAF is at most one more than the length of + /// the binary representation of \\(k\\). This is why the + /// `Scalar` type maintains an invariant (invariant #1) that the top bit is + /// \\(0\\), so that the NAF of a scalar has at most 256 digits. + /// + /// Intuitively, this is like a binary expansion, except that we + /// allow some coefficients to grow in magnitude up to + /// \\(2\^{w-1}\\) so that the nonzero coefficients are as sparse + /// as possible. + /// + /// When doing scalar multiplication, we can then use a lookup + /// table of precomputed multiples of a point to add the nonzero + /// terms \\( k_i P \\). Using signed digits cuts the table size + /// in half, and using odd digits cuts the table size in half + /// again. + /// + /// To compute a \\(w\\)-NAF, we use a modification of Algorithm 3.35 of HMV: + /// + /// 1. \\( i \gets 0 \\) + /// 2. While \\( k \ge 1 \\): + /// 1. If \\(k\\) is odd, \\( n_i \gets k \operatorname{mods} 2^w \\), \\( k \gets k - n_i \\). + /// 2. If \\(k\\) is even, \\( n_i \gets 0 \\). + /// 3. \\( k \gets k / 2 \\), \\( i \gets i + 1 \\). + /// 3. Return \\( n_0, n_1, ... , \\) + /// + /// Here \\( \bar x = x \operatorname{mods} 2^w \\) means the + /// \\( \bar x \\) with \\( \bar x \equiv x \pmod{2^w} \\) and + /// \\( -2^{w-1} \leq \bar x < 2^{w-1} \\). + /// + /// We implement this by scanning across the bits of \\(k\\) from + /// least-significant bit to most-significant-bit. + /// Write the bits of \\(k\\) as + /// $$ + /// k = \sum\_{i=0}\^m k\_i 2^i, + /// $$ + /// and split the sum as + /// $$ + /// k = \sum\_{i=0}^{w-1} k\_i 2^i + 2^w \sum\_{i=0} k\_{i+w} 2^i + /// $$ + /// where the first part is \\( k \mod 2^w \\). + /// + /// If \\( k \mod 2^w\\) is odd, and \\( k \mod 2^w < 2^{w-1} \\), then we emit + /// \\( n_0 = k \mod 2^w \\). Instead of computing + /// \\( k - n_0 \\), we just advance \\(w\\) bits and reindex. + /// + /// If \\( k \mod 2^w\\) is odd, and \\( k \mod 2^w \ge 2^{w-1} \\), then + /// \\( n_0 = k \operatorname{mods} 2^w = k \mod 2^w - 2^w \\). + /// The quantity \\( k - n_0 \\) is + /// $$ + /// \begin{aligned} + /// k - n_0 &= \sum\_{i=0}^{w-1} k\_i 2^i + 2^w \sum\_{i=0} k\_{i+w} 2^i + /// - \sum\_{i=0}^{w-1} k\_i 2^i + 2^w \\\\ + /// &= 2^w + 2^w \sum\_{i=0} k\_{i+w} 2^i + /// \end{aligned} + /// $$ + /// so instead of computing the subtraction, we can set a carry + /// bit, advance \\(w\\) bits, and reindex. + /// + /// If \\( k \mod 2^w\\) is even, we emit \\(0\\), advance 1 bit + /// and reindex. In fact, by setting all digits to \\(0\\) + /// initially, we don't need to emit anything. + pub(crate) fn non_adjacent_form(&self, w: usize) -> [i8; 256] { + // required by the NAF definition + debug_assert!(w >= 2); + // required so that the NAF digits fit in i8 + debug_assert!(w <= 8); + + let mut naf = [0i8; 256]; + + let mut x_u64 = [0u64; 5]; + read_le_u64_into(&self.bytes, &mut x_u64[0..4]); + + let width = 1 << w; + let window_mask = width - 1; + + let mut pos = 0; + let mut carry = 0; + while pos < 256 { + // Construct a buffer of bits of the scalar, starting at bit `pos` + let u64_idx = pos / 64; + let bit_idx = pos % 64; + let bit_buf: u64 = if bit_idx < 64 - w { + // This window's bits are contained in a single u64 + x_u64[u64_idx] >> bit_idx + } else { + // Combine the current u64's bits with the bits from the next u64 + (x_u64[u64_idx] >> bit_idx) | (x_u64[1 + u64_idx] << (64 - bit_idx)) + }; + + // Add the carry into the current window + let window = carry + (bit_buf & window_mask); + + if window & 1 == 0 { + // If the window value is even, preserve the carry and continue. + // Why is the carry preserved? + // If carry == 0 and window & 1 == 0, then the next carry should be 0 + // If carry == 1 and window & 1 == 0, then bit_buf & 1 == 1 so the next carry should be 1 + pos += 1; + continue; + } + + if window < width / 2 { + carry = 0; + naf[pos] = window as i8; + } else { + carry = 1; + naf[pos] = (window as i8).wrapping_sub(width as i8); + } + + pos += w; + } + + naf + } + + /// Write this scalar in radix 16, with coefficients in \\([-8,8)\\), + /// i.e., compute \\(a\_i\\) such that + /// $$ + /// a = a\_0 + a\_1 16\^1 + \cdots + a_{63} 16\^{63}, + /// $$ + /// with \\(-8 \leq a_i < 8\\) for \\(0 \leq i < 63\\) and \\(-8 \leq a_{63} \leq 8\\). + /// + /// The largest value that can be decomposed like this is just over \\(2^{255}\\). Thus, in + /// order to not error, the top bit MUST NOT be set, i.e., `Self` MUST be less than + /// \\(2^{255}\\). + pub(crate) fn as_radix_16(&self) -> [i8; 64] { + debug_assert!(self[31] <= 127); + let mut output = [0i8; 64]; + + // Step 1: change radix. + // Convert from radix 256 (bytes) to radix 16 (nibbles) + #[allow(clippy::identity_op)] + #[inline(always)] + fn bot_half(x: u8) -> u8 { + (x >> 0) & 15 + } + #[inline(always)] + fn top_half(x: u8) -> u8 { + (x >> 4) & 15 + } + + for i in 0..32 { + output[2 * i] = bot_half(self[i]) as i8; + output[2 * i + 1] = top_half(self[i]) as i8; + } + // Precondition note: since self[31] <= 127, output[63] <= 7 + + // Step 2: recenter coefficients from [0,16) to [-8,8) + for i in 0..63 { + let carry = (output[i] + 8) >> 4; + output[i] -= carry << 4; + output[i + 1] += carry; + } + // Precondition note: output[63] is not recentered. It + // increases by carry <= 1. Thus output[63] <= 8. + + output + } + + /// Returns a size hint indicating how many entries of the return + /// value of `to_radix_2w` are nonzero. + #[cfg(any(feature = "alloc", all(test, feature = "precomputed-tables")))] + pub(crate) fn to_radix_2w_size_hint(w: usize) -> usize { + debug_assert!(w >= 4); + debug_assert!(w <= 8); + + let digits_count = match w { + 4..=7 => (256 + w - 1) / w, + // See comment in to_radix_2w on handling the terminal carry. + 8 => (256 + w - 1) / w + 1_usize, + _ => panic!("invalid radix parameter"), + }; + + debug_assert!(digits_count <= 64); + digits_count + } + + /// Creates a representation of a Scalar in radix \\( 2^w \\) with \\(w = 4, 5, 6, 7, 8\\) for + /// use with the Pippenger algorithm. Higher radixes are not supported to save cache space. + /// Radix 256 is near-optimal even for very large inputs. + /// + /// Radix below 16 or above 256 is prohibited. + /// This method returns digits in a fixed-sized array, excess digits are zeroes. + /// + /// For radix 16, `Self` must be less than \\(2^{255}\\). This is because most integers larger + /// than \\(2^{255}\\) are unrepresentable in the form described below for \\(w = 4\\). This + /// would be true for \\(w = 8\\) as well, but it is compensated for by increasing the size + /// hint by 1. + /// + /// ## Scalar representation + /// + /// Radix \\(2\^w\\), with \\(n = ceil(256/w)\\) coefficients in \\([-(2\^w)/2,(2\^w)/2)\\), + /// i.e., scalar is represented using digits \\(a\_i\\) such that + /// $$ + /// a = a\_0 + a\_1 2\^1w + \cdots + a_{n-1} 2\^{w*(n-1)}, + /// $$ + /// with \\(-2\^w/2 \leq a_i < 2\^w/2\\) for \\(0 \leq i < (n-1)\\) and \\(-2\^w/2 \leq a_{n-1} \leq 2\^w/2\\). + /// + #[cfg(any(feature = "alloc", feature = "precomputed-tables"))] + pub(crate) fn as_radix_2w(&self, w: usize) -> [i8; 64] { + debug_assert!(w >= 4); + debug_assert!(w <= 8); + + if w == 4 { + return self.as_radix_16(); + } + + // Scalar formatted as four `u64`s with carry bit packed into the highest bit. + let mut scalar64x4 = [0u64; 4]; + read_le_u64_into(&self.bytes, &mut scalar64x4[0..4]); + + let radix: u64 = 1 << w; + let window_mask: u64 = radix - 1; + + let mut carry = 0u64; + let mut digits = [0i8; 64]; + let digits_count = (256 + w - 1) / w; + #[allow(clippy::needless_range_loop)] + for i in 0..digits_count { + // Construct a buffer of bits of the scalar, starting at `bit_offset`. + let bit_offset = i * w; + let u64_idx = bit_offset / 64; + let bit_idx = bit_offset % 64; + + // Read the bits from the scalar + let bit_buf: u64 = if bit_idx < 64 - w || u64_idx == 3 { + // This window's bits are contained in a single u64, + // or it's the last u64 anyway. + scalar64x4[u64_idx] >> bit_idx + } else { + // Combine the current u64's bits with the bits from the next u64 + (scalar64x4[u64_idx] >> bit_idx) | (scalar64x4[1 + u64_idx] << (64 - bit_idx)) + }; + + // Read the actual coefficient value from the window + let coef = carry + (bit_buf & window_mask); // coef = [0, 2^r) + + // Recenter coefficients from [0,2^w) to [-2^w/2, 2^w/2) + carry = (coef + (radix / 2)) >> w; + digits[i] = ((coef as i64) - (carry << w) as i64) as i8; + } + + // When 4 < w < 8, we can fold the final carry onto the last digit d, + // because d < 2^w/2 so d + carry*2^w = d + 1*2^w < 2^(w+1) < 2^8. + // + // When w = 8, we can't fit carry*2^w into an i8. This should + // not happen anyways, because the final carry will be 0 for + // reduced scalars, but Scalar invariant #1 allows 255-bit scalars. + // To handle this, we expand the size_hint by 1 when w=8, + // and accumulate the final carry onto another digit. + match w { + 8 => digits[digits_count] += carry as i8, + _ => digits[digits_count - 1] += (carry << w) as i8, + } + + digits + } + + /// Unpack this `Scalar` to an `UnpackedScalar` for faster arithmetic. + pub(crate) fn unpack(&self) -> UnpackedScalar { + UnpackedScalar::from_bytes(&self.bytes) + } + + /// Reduce this `Scalar` modulo \\(\ell\\). + #[allow(non_snake_case)] + fn reduce(&self) -> Scalar { + let x = self.unpack(); + let xR = UnpackedScalar::mul_internal(&x, &constants::R); + let x_mod_l = UnpackedScalar::montgomery_reduce(&xR); + x_mod_l.pack() + } + + /// Check whether this `Scalar` is the canonical representative mod \\(\ell\\). This is not + /// public because any `Scalar` that is publicly observed is reduced, by scalar invariant #2. + fn is_canonical(&self) -> Choice { + self.ct_eq(&self.reduce()) + } +} + +impl UnpackedScalar { + /// Pack the limbs of this `UnpackedScalar` into a `Scalar`. + fn pack(&self) -> Scalar { + Scalar { + bytes: self.as_bytes(), + } + } + + /// Inverts an UnpackedScalar in Montgomery form. + #[rustfmt::skip] // keep alignment of addition chain and squarings + #[allow(clippy::just_underscores_and_digits)] + pub fn montgomery_invert(&self) -> UnpackedScalar { + // Uses the addition chain from + // https://briansmith.org/ecc-inversion-addition-chains-01#curve25519_scalar_inversion + let _1 = *self; + let _10 = _1.montgomery_square(); + let _100 = _10.montgomery_square(); + let _11 = UnpackedScalar::montgomery_mul(&_10, &_1); + let _101 = UnpackedScalar::montgomery_mul(&_10, &_11); + let _111 = UnpackedScalar::montgomery_mul(&_10, &_101); + let _1001 = UnpackedScalar::montgomery_mul(&_10, &_111); + let _1011 = UnpackedScalar::montgomery_mul(&_10, &_1001); + let _1111 = UnpackedScalar::montgomery_mul(&_100, &_1011); + + // _10000 + let mut y = UnpackedScalar::montgomery_mul(&_1111, &_1); + + #[inline] + fn square_multiply(y: &mut UnpackedScalar, squarings: usize, x: &UnpackedScalar) { + for _ in 0..squarings { + *y = y.montgomery_square(); + } + *y = UnpackedScalar::montgomery_mul(y, x); + } + + square_multiply(&mut y, 123 + 3, &_101); + square_multiply(&mut y, 2 + 2, &_11); + square_multiply(&mut y, 1 + 4, &_1111); + square_multiply(&mut y, 1 + 4, &_1111); + square_multiply(&mut y, 4, &_1001); + square_multiply(&mut y, 2, &_11); + square_multiply(&mut y, 1 + 4, &_1111); + square_multiply(&mut y, 1 + 3, &_101); + square_multiply(&mut y, 3 + 3, &_101); + square_multiply(&mut y, 3, &_111); + square_multiply(&mut y, 1 + 4, &_1111); + square_multiply(&mut y, 2 + 3, &_111); + square_multiply(&mut y, 2 + 2, &_11); + square_multiply(&mut y, 1 + 4, &_1011); + square_multiply(&mut y, 2 + 4, &_1011); + square_multiply(&mut y, 6 + 4, &_1001); + square_multiply(&mut y, 2 + 2, &_11); + square_multiply(&mut y, 3 + 2, &_11); + square_multiply(&mut y, 3 + 2, &_11); + square_multiply(&mut y, 1 + 4, &_1001); + square_multiply(&mut y, 1 + 3, &_111); + square_multiply(&mut y, 2 + 4, &_1111); + square_multiply(&mut y, 1 + 4, &_1011); + square_multiply(&mut y, 3, &_101); + square_multiply(&mut y, 2 + 4, &_1111); + square_multiply(&mut y, 3, &_101); + square_multiply(&mut y, 1 + 2, &_11); + + y + } + + /// Inverts an UnpackedScalar not in Montgomery form. + pub fn invert(&self) -> UnpackedScalar { + self.as_montgomery().montgomery_invert().from_montgomery() + } +} + +#[cfg(feature = "group")] +impl Field for Scalar { + const ZERO: Self = Self::ZERO; + const ONE: Self = Self::ONE; + + fn random(mut rng: impl RngCore) -> Self { + // NOTE: this is duplicated due to different `rng` bounds + let mut scalar_bytes = [0u8; 64]; + rng.fill_bytes(&mut scalar_bytes); + Self::from_bytes_mod_order_wide(&scalar_bytes) + } + + fn square(&self) -> Self { + self * self + } + + fn double(&self) -> Self { + self + self + } + + fn invert(&self) -> CtOption { + CtOption::new(self.invert(), !self.is_zero()) + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + #[allow(unused_qualifications)] + group::ff::helpers::sqrt_ratio_generic(num, div) + } + + fn sqrt(&self) -> CtOption { + #[allow(unused_qualifications)] + group::ff::helpers::sqrt_tonelli_shanks( + self, + [ + 0xcb02_4c63_4b9e_ba7d, + 0x029b_df3b_d45e_f39a, + 0x0000_0000_0000_0000, + 0x0200_0000_0000_0000, + ], + ) + } +} + +#[cfg(feature = "group")] +impl PrimeField for Scalar { + type Repr = [u8; 32]; + + fn from_repr(repr: Self::Repr) -> CtOption { + Self::from_canonical_bytes(repr) + } + + fn from_repr_vartime(repr: Self::Repr) -> Option { + // Check that the high bit is not set + if (repr[31] >> 7) != 0u8 { + return None; + } + + let candidate = Scalar { bytes: repr }; + + if candidate == candidate.reduce() { + Some(candidate) + } else { + None + } + } + + fn to_repr(&self) -> Self::Repr { + self.to_bytes() + } + + fn is_odd(&self) -> Choice { + Choice::from(self.as_bytes()[0] & 1) + } + + const MODULUS: &'static str = + "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"; + const NUM_BITS: u32 = 253; + const CAPACITY: u32 = 252; + + const TWO_INV: Self = Self { + bytes: [ + 0xf7, 0xe9, 0x7a, 0x2e, 0x8d, 0x31, 0x09, 0x2c, 0x6b, 0xce, 0x7b, 0x51, 0xef, 0x7c, + 0x6f, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, + ], + }; + const MULTIPLICATIVE_GENERATOR: Self = Self { + bytes: [ + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ], + }; + const S: u32 = 2; + const ROOT_OF_UNITY: Self = Self { + bytes: [ + 0xd4, 0x07, 0xbe, 0xeb, 0xdf, 0x75, 0x87, 0xbe, 0xfe, 0x83, 0xce, 0x42, 0x53, 0x56, + 0xf0, 0x0e, 0x7a, 0xc2, 0xc1, 0xab, 0x60, 0x6d, 0x3d, 0x7d, 0xe7, 0x81, 0x79, 0xe0, + 0x10, 0x73, 0x4a, 0x09, + ], + }; + const ROOT_OF_UNITY_INV: Self = Self { + bytes: [ + 0x19, 0xcc, 0x37, 0x71, 0x3a, 0xed, 0x8a, 0x99, 0xd7, 0x18, 0x29, 0x60, 0x8b, 0xa3, + 0xee, 0x05, 0x86, 0x3d, 0x3e, 0x54, 0x9f, 0x92, 0xc2, 0x82, 0x18, 0x7e, 0x86, 0x1f, + 0xef, 0x8c, 0xb5, 0x06, + ], + }; + const DELTA: Self = Self { + bytes: [ + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ], + }; +} + +#[cfg(feature = "group-bits")] +impl PrimeFieldBits for Scalar { + type ReprBits = [u8; 32]; + + fn to_le_bits(&self) -> FieldBits { + self.to_repr().into() + } + + fn char_le_bits() -> FieldBits { + constants::BASEPOINT_ORDER_PRIVATE.to_bytes().into() + } +} + +#[cfg(feature = "group")] +impl FromUniformBytes<64> for Scalar { + fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { + Scalar::from_bytes_mod_order_wide(bytes) + } +} + +/// Read one or more u64s stored as little endian bytes. +/// +/// ## Panics +/// Panics if `src.len() != 8 * dst.len()`. +fn read_le_u64_into(src: &[u8], dst: &mut [u64]) { + assert!( + src.len() == 8 * dst.len(), + "src.len() = {}, dst.len() = {}", + src.len(), + dst.len() + ); + for (bytes, val) in src.chunks(8).zip(dst.iter_mut()) { + *val = u64::from_le_bytes( + bytes + .try_into() + .expect("Incorrect src length, should be 8 * dst.len()"), + ); + } +} + +/// _Clamps_ the given little-endian representation of a 32-byte integer. Clamping the value puts +/// it in the range: +/// +/// **n ∈ 2^254 + 8\*{0, 1, 2, 3, . . ., 2^251 − 1}** +/// +/// # Explanation of clamping +/// +/// For Curve25519, h = 8, and multiplying by 8 is the same as a binary left-shift by 3 bits. +/// If you take a secret scalar value between 2^251 and 2^252 – 1 and left-shift by 3 bits +/// then you end up with a 255-bit number with the most significant bit set to 1 and +/// the least-significant three bits set to 0. +/// +/// The Curve25519 clamping operation takes **an arbitrary 256-bit random value** and +/// clears the most-significant bit (making it a 255-bit number), sets the next bit, and then +/// clears the 3 least-significant bits. In other words, it directly creates a scalar value that is +/// in the right form and pre-multiplied by the cofactor. +/// +/// See [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/) for +/// more details. +#[must_use] +pub const fn clamp_integer(mut bytes: [u8; 32]) -> [u8; 32] { + bytes[0] &= 0b1111_1000; + bytes[31] &= 0b0111_1111; + bytes[31] |= 0b0100_0000; + bytes +} + +#[cfg(test)] +pub(crate) mod test { + use super::*; + + #[cfg(feature = "alloc")] + use alloc::vec::Vec; + + /// x = 2238329342913194256032495932344128051776374960164957527413114840482143558222 + pub static X: Scalar = Scalar { + bytes: [ + 0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2, + 0x7d, 0x52, 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, 0xd4, 0x49, 0xf4, 0xa8, + 0x79, 0xd9, 0xf2, 0x04, + ], + }; + /// 1/x = 6859937278830797291664592131120606308688036382723378951768035303146619657244 + pub static XINV: Scalar = Scalar { + bytes: [ + 0x1c, 0xdc, 0x17, 0xfc, 0xe0, 0xe9, 0xa5, 0xbb, 0xd9, 0x24, 0x7e, 0x56, 0xbb, 0x01, + 0x63, 0x47, 0xbb, 0xba, 0x31, 0xed, 0xd5, 0xa9, 0xbb, 0x96, 0xd5, 0x0b, 0xcd, 0x7a, + 0x3f, 0x96, 0x2a, 0x0f, + ], + }; + /// y = 2592331292931086675770238855846338635550719849568364935475441891787804997264 + pub static Y: Scalar = Scalar { + bytes: [ + 0x90, 0x76, 0x33, 0xfe, 0x1c, 0x4b, 0x66, 0xa4, 0xa2, 0x8d, 0x2d, 0xd7, 0x67, 0x83, + 0x86, 0xc3, 0x53, 0xd0, 0xde, 0x54, 0x55, 0xd4, 0xfc, 0x9d, 0xe8, 0xef, 0x7a, 0xc3, + 0x1f, 0x35, 0xbb, 0x05, + ], + }; + + /// The largest scalar that satisfies invariant #1, i.e., the largest scalar with the top bit + /// set to 0. Since this scalar violates invariant #2, i.e., it's greater than the modulus `l`, + /// addition and subtraction are broken. The only thing you can do with this is scalar-point + /// multiplication (and actually also scalar-scalar multiplication, but that's just a quirk of + /// our implementation). + pub(crate) static LARGEST_UNREDUCED_SCALAR: Scalar = Scalar { + bytes: [ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7f, + ], + }; + + /// x*y = 5690045403673944803228348699031245560686958845067437804563560795922180092780 + static X_TIMES_Y: Scalar = Scalar { + bytes: [ + 0x6c, 0x33, 0x74, 0xa1, 0x89, 0x4f, 0x62, 0x21, 0x0a, 0xaa, 0x2f, 0xe1, 0x86, 0xa6, + 0xf9, 0x2c, 0xe0, 0xaa, 0x75, 0xc2, 0x77, 0x95, 0x81, 0xc2, 0x95, 0xfc, 0x08, 0x17, + 0x9a, 0x73, 0x94, 0x0c, + ], + }; + + /// sage: l = 2^252 + 27742317777372353535851937790883648493 + /// sage: big = 2^256 - 1 + /// sage: repr((big % l).digits(256)) + static CANONICAL_2_256_MINUS_1: Scalar = Scalar { + bytes: [ + 28, 149, 152, 141, 116, 49, 236, 214, 112, 207, 125, 115, 244, 91, 239, 198, 254, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, + ], + }; + + static A_SCALAR: Scalar = Scalar { + bytes: [ + 0x1a, 0x0e, 0x97, 0x8a, 0x90, 0xf6, 0x62, 0x2d, 0x37, 0x47, 0x02, 0x3f, 0x8a, 0xd8, + 0x26, 0x4d, 0xa7, 0x58, 0xaa, 0x1b, 0x88, 0xe0, 0x40, 0xd1, 0x58, 0x9e, 0x7b, 0x7f, + 0x23, 0x76, 0xef, 0x09, + ], + }; + + static A_NAF: [i8; 256] = [ + 0, 13, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, -11, 0, 0, 0, 0, 3, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 11, 0, 0, 0, 0, + 11, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, -1, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, -15, 0, 0, 0, 0, -7, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 5, + 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, -11, 0, 0, 0, 0, -7, 0, 0, 0, 0, -13, 0, 0, + 0, 0, 11, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, -15, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 7, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 15, + 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, -15, 0, + 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + ]; + + const BASEPOINT_ORDER_MINUS_ONE: Scalar = Scalar { + bytes: [ + 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, + 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, + ], + }; + + /// The largest clamped integer + static LARGEST_CLAMPED_INTEGER: [u8; 32] = clamp_integer(LARGEST_UNREDUCED_SCALAR.bytes); + + #[test] + fn fuzzer_testcase_reduction() { + // LE bytes of 24519928653854221733733552434404946937899825954937634815 + let a_bytes = [ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + // LE bytes of 4975441334397345751130612518500927154628011511324180036903450236863266160640 + let b_bytes = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 210, 210, + 210, 255, 255, 255, 255, 10, + ]; + // LE bytes of 6432735165214683820902750800207468552549813371247423777071615116673864412038 + let c_bytes = [ + 134, 171, 119, 216, 180, 128, 178, 62, 171, 132, 32, 62, 34, 119, 104, 193, 47, 215, + 181, 250, 14, 207, 172, 93, 75, 207, 211, 103, 144, 204, 56, 14, + ]; + + let a = Scalar::from_bytes_mod_order(a_bytes); + let b = Scalar::from_bytes_mod_order(b_bytes); + let c = Scalar::from_bytes_mod_order(c_bytes); + + let mut tmp = [0u8; 64]; + + // also_a = (a mod l) + tmp[0..32].copy_from_slice(&a_bytes[..]); + let also_a = Scalar::from_bytes_mod_order_wide(&tmp); + + // also_b = (b mod l) + tmp[0..32].copy_from_slice(&b_bytes[..]); + let also_b = Scalar::from_bytes_mod_order_wide(&tmp); + + let expected_c = a * b; + let also_expected_c = also_a * also_b; + + assert_eq!(c, expected_c); + assert_eq!(c, also_expected_c); + } + + #[test] + fn non_adjacent_form_test_vector() { + let naf = A_SCALAR.non_adjacent_form(5); + for i in 0..256 { + assert_eq!(naf[i], A_NAF[i]); + } + } + + fn non_adjacent_form_iter(w: usize, x: &Scalar) { + let naf = x.non_adjacent_form(w); + + // Reconstruct the scalar from the computed NAF + let mut y = Scalar::ZERO; + for i in (0..256).rev() { + y += y; + let digit = if naf[i] < 0 { + -Scalar::from((-naf[i]) as u64) + } else { + Scalar::from(naf[i] as u64) + }; + y += digit; + } + + assert_eq!(*x, y); + } + + #[test] + fn non_adjacent_form_random() { + let mut rng = rand::thread_rng(); + for _ in 0..1_000 { + let x = Scalar::random(&mut rng); + for w in &[5, 6, 7, 8] { + non_adjacent_form_iter(*w, &x); + } + } + } + + #[test] + fn from_u64() { + let val: u64 = 0xdeadbeefdeadbeef; + let s = Scalar::from(val); + assert_eq!(s[7], 0xde); + assert_eq!(s[6], 0xad); + assert_eq!(s[5], 0xbe); + assert_eq!(s[4], 0xef); + assert_eq!(s[3], 0xde); + assert_eq!(s[2], 0xad); + assert_eq!(s[1], 0xbe); + assert_eq!(s[0], 0xef); + } + + #[test] + fn scalar_mul_by_one() { + let test_scalar = X * Scalar::ONE; + for i in 0..32 { + assert!(test_scalar[i] == X[i]); + } + } + + #[test] + fn add_reduces() { + // Check that addition wraps around the modulus + assert_eq!(BASEPOINT_ORDER_MINUS_ONE + Scalar::ONE, Scalar::ZERO); + } + + #[test] + fn sub_reduces() { + // Check that subtraction wraps around the modulus + assert_eq!(Scalar::ZERO - Scalar::ONE, BASEPOINT_ORDER_MINUS_ONE); + } + + #[test] + fn impl_add() { + let two = Scalar::from(2u64); + let one = Scalar::ONE; + let should_be_two = one + one; + assert_eq!(should_be_two, two); + } + + #[allow(non_snake_case)] + #[test] + fn impl_mul() { + let should_be_X_times_Y = X * Y; + assert_eq!(should_be_X_times_Y, X_TIMES_Y); + } + + #[allow(non_snake_case)] + #[test] + #[cfg(feature = "alloc")] + fn impl_product() { + // Test that product works for non-empty iterators + let X_Y_vector = [X, Y]; + let should_be_X_times_Y: Scalar = X_Y_vector.iter().product(); + assert_eq!(should_be_X_times_Y, X_TIMES_Y); + + // Test that product works for the empty iterator + let one = Scalar::ONE; + let empty_vector = []; + let should_be_one: Scalar = empty_vector.iter().product(); + assert_eq!(should_be_one, one); + + // Test that product works for iterators where Item = Scalar + let xs = [Scalar::from(2u64); 10]; + let ys = [Scalar::from(3u64); 10]; + // now zs is an iterator with Item = Scalar + let zs = xs.iter().zip(ys.iter()).map(|(x, y)| x * y); + + let x_prod: Scalar = xs.iter().product(); + let y_prod: Scalar = ys.iter().product(); + let z_prod: Scalar = zs.product(); + + assert_eq!(x_prod, Scalar::from(1024u64)); + assert_eq!(y_prod, Scalar::from(59049u64)); + assert_eq!(z_prod, Scalar::from(60466176u64)); + assert_eq!(x_prod * y_prod, z_prod); + } + + #[test] + #[cfg(feature = "alloc")] + fn impl_sum() { + // Test that sum works for non-empty iterators + let two = Scalar::from(2u64); + let one_vector = [Scalar::ONE, Scalar::ONE]; + let should_be_two: Scalar = one_vector.iter().sum(); + assert_eq!(should_be_two, two); + + // Test that sum works for the empty iterator + let zero = Scalar::ZERO; + let empty_vector = []; + let should_be_zero: Scalar = empty_vector.iter().sum(); + assert_eq!(should_be_zero, zero); + + // Test that sum works for owned types + let xs = [Scalar::from(1u64); 10]; + let ys = [Scalar::from(2u64); 10]; + // now zs is an iterator with Item = Scalar + let zs = xs.iter().zip(ys.iter()).map(|(x, y)| x + y); + + let x_sum: Scalar = xs.iter().sum(); + let y_sum: Scalar = ys.iter().sum(); + let z_sum: Scalar = zs.sum(); + + assert_eq!(x_sum, Scalar::from(10u64)); + assert_eq!(y_sum, Scalar::from(20u64)); + assert_eq!(z_sum, Scalar::from(30u64)); + assert_eq!(x_sum + y_sum, z_sum); + } + + #[test] + fn square() { + let expected = X * X; + let actual = X.unpack().square().pack(); + for i in 0..32 { + assert!(expected[i] == actual[i]); + } + } + + #[test] + fn reduce() { + let biggest = Scalar::from_bytes_mod_order([0xff; 32]); + assert_eq!(biggest, CANONICAL_2_256_MINUS_1); + } + + #[test] + fn from_bytes_mod_order_wide() { + let mut bignum = [0u8; 64]; + // set bignum = x + 2^256x + for i in 0..32 { + bignum[i] = X[i]; + bignum[32 + i] = X[i]; + } + // 3958878930004874126169954872055634648693766179881526445624823978500314864344 + // = x + 2^256x (mod l) + let reduced = Scalar { + bytes: [ + 216, 154, 179, 139, 210, 121, 2, 71, 69, 99, 158, 216, 23, 173, 63, 100, 204, 0, + 91, 50, 219, 153, 57, 249, 28, 82, 31, 197, 100, 165, 192, 8, + ], + }; + let test_red = Scalar::from_bytes_mod_order_wide(&bignum); + for i in 0..32 { + assert!(test_red[i] == reduced[i]); + } + } + + #[allow(non_snake_case)] + #[test] + fn invert() { + let inv_X = X.invert(); + assert_eq!(inv_X, XINV); + let should_be_one = inv_X * X; + assert_eq!(should_be_one, Scalar::ONE); + } + + // Negating a scalar twice should result in the original scalar. + #[allow(non_snake_case)] + #[test] + fn neg_twice_is_identity() { + let negative_X = -&X; + let should_be_X = -&negative_X; + + assert_eq!(should_be_X, X); + } + + #[test] + fn to_bytes_from_bytes_roundtrips() { + let unpacked = X.unpack(); + let bytes = unpacked.as_bytes(); + let should_be_unpacked = UnpackedScalar::from_bytes(&bytes); + + assert_eq!(should_be_unpacked.0, unpacked.0); + } + + #[test] + fn montgomery_reduce_matches_from_bytes_mod_order_wide() { + let mut bignum = [0u8; 64]; + + // set bignum = x + 2^256x + for i in 0..32 { + bignum[i] = X[i]; + bignum[32 + i] = X[i]; + } + // x + 2^256x (mod l) + // = 3958878930004874126169954872055634648693766179881526445624823978500314864344 + let expected = Scalar { + bytes: [ + 216, 154, 179, 139, 210, 121, 2, 71, 69, 99, 158, 216, 23, 173, 63, 100, 204, 0, + 91, 50, 219, 153, 57, 249, 28, 82, 31, 197, 100, 165, 192, 8, + ], + }; + let reduced = Scalar::from_bytes_mod_order_wide(&bignum); + + // The reduced scalar should match the expected + assert_eq!(reduced.bytes, expected.bytes); + + // (x + 2^256x) * R + let interim = + UnpackedScalar::mul_internal(&UnpackedScalar::from_bytes_wide(&bignum), &constants::R); + // ((x + 2^256x) * R) / R (mod l) + let montgomery_reduced = UnpackedScalar::montgomery_reduce(&interim); + + // The Montgomery reduced scalar should match the reduced one, as well as the expected + assert_eq!(montgomery_reduced.0, reduced.unpack().0); + assert_eq!(montgomery_reduced.0, expected.unpack().0) + } + + #[test] + fn canonical_decoding() { + // canonical encoding of 1667457891 + let canonical_bytes = [ + 99, 99, 99, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + // encoding of + // 7265385991361016183439748078976496179028704920197054998554201349516117938192 + // = 28380414028753969466561515933501938171588560817147392552250411230663687203 (mod l) + // non_canonical because unreduced mod l + let non_canonical_bytes_because_unreduced = [16; 32]; + + // encoding with high bit set, to check that the parser isn't pre-masking the high bit + let non_canonical_bytes_because_highbit = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 128, + ]; + + assert!(bool::from( + Scalar::from_canonical_bytes(canonical_bytes).is_some() + )); + assert!(bool::from( + Scalar::from_canonical_bytes(non_canonical_bytes_because_unreduced).is_none() + )); + assert!(bool::from( + Scalar::from_canonical_bytes(non_canonical_bytes_because_highbit).is_none() + )); + } + + #[test] + #[cfg(feature = "serde")] + fn serde_bincode_scalar_roundtrip() { + use bincode; + let encoded = bincode::serialize(&X).unwrap(); + let parsed: Scalar = bincode::deserialize(&encoded).unwrap(); + assert_eq!(parsed, X); + + // Check that the encoding is 32 bytes exactly + assert_eq!(encoded.len(), 32); + + // Check that the encoding itself matches the usual one + assert_eq!(X, bincode::deserialize(X.as_bytes()).unwrap(),); + } + + #[cfg(all(debug_assertions, feature = "alloc"))] + #[test] + #[should_panic] + fn batch_invert_with_a_zero_input_panics() { + let mut xs = vec![Scalar::ONE; 16]; + xs[3] = Scalar::ZERO; + // This should panic in debug mode. + Scalar::batch_invert(&mut xs); + } + + #[test] + #[cfg(feature = "alloc")] + fn batch_invert_empty() { + assert_eq!(Scalar::ONE, Scalar::batch_invert(&mut [])); + } + + #[test] + #[cfg(feature = "alloc")] + fn batch_invert_consistency() { + let mut x = Scalar::from(1u64); + let mut v1: Vec<_> = (0..16) + .map(|_| { + let tmp = x; + x = x + x; + tmp + }) + .collect(); + let v2 = v1.clone(); + + let expected: Scalar = v1.iter().product(); + let expected = expected.invert(); + let ret = Scalar::batch_invert(&mut v1); + assert_eq!(ret, expected); + + for (a, b) in v1.iter().zip(v2.iter()) { + assert_eq!(a * b, Scalar::ONE); + } + } + + #[cfg(feature = "precomputed-tables")] + fn test_pippenger_radix_iter(scalar: Scalar, w: usize) { + let digits_count = Scalar::to_radix_2w_size_hint(w); + let digits = scalar.as_radix_2w(w); + + let radix = Scalar::from((1 << w) as u64); + let mut term = Scalar::ONE; + let mut recovered_scalar = Scalar::ZERO; + for digit in &digits[0..digits_count] { + let digit = *digit; + if digit != 0 { + let sdigit = if digit < 0 { + -Scalar::from((-(digit as i64)) as u64) + } else { + Scalar::from(digit as u64) + }; + recovered_scalar += term * sdigit; + } + term *= radix; + } + // When the input is unreduced, we may only recover the scalar mod l. + assert_eq!(recovered_scalar, scalar.reduce()); + } + + #[test] + #[cfg(feature = "precomputed-tables")] + fn test_pippenger_radix() { + use core::iter; + // For each valid radix it tests that 1000 random-ish scalars can be restored + // from the produced representation precisely. + let cases = (2..100) + .map(|s| Scalar::from(s as u64).invert()) + // The largest unreduced scalar, s = 2^255-1. This is not reduced mod l. Scalar mult + // still works though. + .chain(iter::once(LARGEST_UNREDUCED_SCALAR)); + + for scalar in cases { + test_pippenger_radix_iter(scalar, 6); + test_pippenger_radix_iter(scalar, 7); + test_pippenger_radix_iter(scalar, 8); + } + } + + #[test] + #[cfg(feature = "alloc")] + fn test_read_le_u64_into() { + let cases: &[(&[u8], &[u64])] = &[ + ( + &[0xFE, 0xEF, 0x10, 0x01, 0x1F, 0xF1, 0x0F, 0xF0], + &[0xF00F_F11F_0110_EFFE], + ), + ( + &[ + 0xFE, 0xEF, 0x10, 0x01, 0x1F, 0xF1, 0x0F, 0xF0, 0x12, 0x34, 0x56, 0x78, 0x9A, + 0xBC, 0xDE, 0xF0, + ], + &[0xF00F_F11F_0110_EFFE, 0xF0DE_BC9A_7856_3412], + ), + ]; + + for (src, expected) in cases { + let mut dst = vec![0; expected.len()]; + read_le_u64_into(src, &mut dst); + + assert_eq!(&dst, expected, "Expected {:x?} got {:x?}", expected, dst); + } + } + + // Tests consistency of From<{integer}> impls for Scalar + #[test] + fn test_scalar_from_int() { + let s1 = Scalar::ONE; + + // For `x` in `u8`, `u16`, `u32`, `u64`, and `u128`, check that + // `Scalar::from(x + 1) == Scalar::from(x) + Scalar::from(1)` + + let x = 0x23u8; + let sx = Scalar::from(x); + assert_eq!(sx + s1, Scalar::from(x + 1)); + + let x = 0x2323u16; + let sx = Scalar::from(x); + assert_eq!(sx + s1, Scalar::from(x + 1)); + + let x = 0x2323_2323u32; + let sx = Scalar::from(x); + assert_eq!(sx + s1, Scalar::from(x + 1)); + + let x = 0x2323_2323_2323_2323u64; + let sx = Scalar::from(x); + assert_eq!(sx + s1, Scalar::from(x + 1)); + + let x = 0x2323_2323_2323_2323_2323_2323_2323_2323u128; + let sx = Scalar::from(x); + assert_eq!(sx + s1, Scalar::from(x + 1)); + } + + #[cfg(feature = "group")] + #[test] + fn ff_constants() { + assert_eq!(Scalar::from(2u64) * Scalar::TWO_INV, Scalar::ONE); + + assert_eq!( + Scalar::ROOT_OF_UNITY * Scalar::ROOT_OF_UNITY_INV, + Scalar::ONE, + ); + + // ROOT_OF_UNITY^{2^s} mod m == 1 + assert_eq!( + Scalar::ROOT_OF_UNITY.pow(&[1u64 << Scalar::S, 0, 0, 0]), + Scalar::ONE, + ); + + // DELTA^{t} mod m == 1 + assert_eq!( + Scalar::DELTA.pow(&[ + 0x9604_98c6_973d_74fb, + 0x0537_be77_a8bd_e735, + 0x0000_0000_0000_0000, + 0x0400_0000_0000_0000, + ]), + Scalar::ONE, + ); + } + + #[cfg(feature = "group")] + #[test] + fn ff_impls() { + assert!(bool::from(Scalar::ZERO.is_even())); + assert!(bool::from(Scalar::ONE.is_odd())); + assert!(bool::from(Scalar::from(2u64).is_even())); + assert!(bool::from(Scalar::DELTA.is_even())); + + assert!(bool::from(Field::invert(&Scalar::ZERO).is_none())); + assert_eq!(Field::invert(&X).unwrap(), XINV); + + let x_sq = X.square(); + // We should get back either the positive or negative root. + assert!([X, -X].contains(&x_sq.sqrt().unwrap())); + + assert_eq!(Scalar::from_repr_vartime(X.to_repr()), Some(X)); + assert_eq!(Scalar::from_repr_vartime([0xff; 32]), None); + + assert_eq!(Scalar::from_repr(X.to_repr()).unwrap(), X); + assert!(bool::from(Scalar::from_repr([0xff; 32]).is_none())); + } + + #[test] + #[should_panic] + fn test_read_le_u64_into_should_panic_on_bad_input() { + let mut dst = [0_u64; 1]; + // One byte short + read_le_u64_into(&[0xFE, 0xEF, 0x10, 0x01, 0x1F, 0xF1, 0x0F], &mut dst); + } + + #[test] + fn test_scalar_clamp() { + let input = A_SCALAR.bytes; + let expected = [ + 0x18, 0x0e, 0x97, 0x8a, 0x90, 0xf6, 0x62, 0x2d, 0x37, 0x47, 0x02, 0x3f, 0x8a, 0xd8, + 0x26, 0x4d, 0xa7, 0x58, 0xaa, 0x1b, 0x88, 0xe0, 0x40, 0xd1, 0x58, 0x9e, 0x7b, 0x7f, + 0x23, 0x76, 0xef, 0x49, + ]; + let actual = clamp_integer(input); + assert_eq!(actual, expected); + + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0x40, + ]; + let actual = clamp_integer([0; 32]); + assert_eq!(expected, actual); + let expected = [ + 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7f, + ]; + let actual = clamp_integer([0xff; 32]); + assert_eq!(actual, expected); + + assert_eq!( + LARGEST_CLAMPED_INTEGER, + clamp_integer(LARGEST_CLAMPED_INTEGER) + ); + } + + // Check that a * b == a.reduce() * a.reduce() for ANY scalars a,b, even ones that violate + // invariant #1, i.e., a,b > 2^255. Old versions of ed25519-dalek did multiplication where a + // was reduced and b was clamped and unreduced. This checks that that was always well-defined. + #[test] + fn test_mul_reduction_invariance() { + let mut rng = rand::thread_rng(); + + for _ in 0..10 { + // Also define c that's clamped. We'll make sure that clamping doesn't affect + // computation + let (a, b, c) = { + let mut a_bytes = [0u8; 32]; + let mut b_bytes = [0u8; 32]; + let mut c_bytes = [0u8; 32]; + rng.fill_bytes(&mut a_bytes); + rng.fill_bytes(&mut b_bytes); + rng.fill_bytes(&mut c_bytes); + ( + Scalar { bytes: a_bytes }, + Scalar { bytes: b_bytes }, + Scalar { + bytes: clamp_integer(c_bytes), + }, + ) + }; + + // Make sure this is the same product no matter how you cut it + let reduced_mul_ab = a.reduce() * b.reduce(); + let reduced_mul_ac = a.reduce() * c.reduce(); + assert_eq!(a * b, reduced_mul_ab); + assert_eq!(a.reduce() * b, reduced_mul_ab); + assert_eq!(a * b.reduce(), reduced_mul_ab); + assert_eq!(a * c, reduced_mul_ac); + assert_eq!(a.reduce() * c, reduced_mul_ac); + assert_eq!(a * c.reduce(), reduced_mul_ac); + } + } +} diff --git a/curve25519-elligator2/src/traits.rs b/curve25519-elligator2/src/traits.rs new file mode 100644 index 00000000..68a3b1f8 --- /dev/null +++ b/curve25519-elligator2/src/traits.rs @@ -0,0 +1,415 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Module for common traits. + +#![allow(non_snake_case)] + +use core::borrow::Borrow; + +use crate::scalar::{clamp_integer, Scalar}; +use subtle::ConstantTimeEq; + +// ------------------------------------------------------------------------ +// Public Traits +// ------------------------------------------------------------------------ + +/// Trait for getting the identity element of a point type. +pub trait Identity { + /// Returns the identity element of the curve. + /// Can be used as a constructor. + fn identity() -> Self; +} + +/// Trait for testing if a curve point is equivalent to the identity point. +pub trait IsIdentity { + /// Return true if this element is the identity element of the curve. + fn is_identity(&self) -> bool; +} + +/// Implement generic identity equality testing for a point representations +/// which have constant-time equality testing and a defined identity +/// constructor. +impl IsIdentity for T +where + T: ConstantTimeEq + Identity, +{ + fn is_identity(&self) -> bool { + self.ct_eq(&T::identity()).into() + } +} + +/// A precomputed table of basepoints, for optimising scalar multiplications. +pub trait BasepointTable { + /// The type of point contained within this table. + type Point; + + /// Generate a new precomputed basepoint table from the given basepoint. + fn create(basepoint: &Self::Point) -> Self; + + /// Retrieve the original basepoint from this table. + fn basepoint(&self) -> Self::Point; + + /// Multiply a `scalar` by this precomputed basepoint table, in constant time. + fn mul_base(&self, scalar: &Scalar) -> Self::Point; + + /// Multiply `clamp_integer(bytes)` by this precomputed basepoint table, in constant time. For + /// a description of clamping, see [`clamp_integer`]. + fn mul_base_clamped(&self, bytes: [u8; 32]) -> Self::Point { + // Basepoint multiplication is defined for all values of `bytes` up to and including + // 2^255 - 1. The limit comes from the fact that scalar.as_radix_16() doesn't work for + // most scalars larger than 2^255. + let s = Scalar { + bytes: clamp_integer(bytes), + }; + self.mul_base(&s) + } +} + +/// A trait for constant-time multiscalar multiplication without precomputation. +pub trait MultiscalarMul { + /// The type of point being multiplied, e.g., `RistrettoPoint`. + type Point; + + /// Given an iterator of (possibly secret) scalars and an iterator of + /// public points, compute + /// $$ + /// Q = c\_1 P\_1 + \cdots + c\_n P\_n. + /// $$ + /// + /// It is an error to call this function with two iterators of different lengths. + /// + /// # Examples + /// + /// The trait bound aims for maximum flexibility: the inputs must be + /// convertable to iterators (`I: IntoIter`), and the iterator's items + /// must be `Borrow` (or `Borrow`), to allow + /// iterators returning either `Scalar`s or `&Scalar`s. + /// + /// ``` + /// # #[cfg(feature = "alloc")] + /// # { + /// use curve25519_elligator2::constants; + /// use curve25519_elligator2::traits::MultiscalarMul; + /// use curve25519_elligator2::ristretto::RistrettoPoint; + /// use curve25519_elligator2::scalar::Scalar; + /// + /// // Some scalars + /// let a = Scalar::from(87329482u64); + /// let b = Scalar::from(37264829u64); + /// let c = Scalar::from(98098098u64); + /// + /// // Some points + /// let P = constants::RISTRETTO_BASEPOINT_POINT; + /// let Q = P + P; + /// let R = P + Q; + /// + /// // A1 = a*P + b*Q + c*R + /// let abc = [a,b,c]; + /// let A1 = RistrettoPoint::multiscalar_mul(&abc, &[P,Q,R]); + /// // Note: (&abc).into_iter(): Iterator + /// + /// // A2 = (-a)*P + (-b)*Q + (-c)*R + /// let minus_abc = abc.iter().map(|x| -x); + /// let A2 = RistrettoPoint::multiscalar_mul(minus_abc, &[P,Q,R]); + /// // Note: minus_abc.into_iter(): Iterator + /// + /// assert_eq!(A1.compress(), (-A2).compress()); + /// # } + /// ``` + fn multiscalar_mul(scalars: I, points: J) -> Self::Point + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow; +} + +/// A trait for variable-time multiscalar multiplication without precomputation. +pub trait VartimeMultiscalarMul { + /// The type of point being multiplied, e.g., `RistrettoPoint`. + type Point; + + /// Given an iterator of public scalars and an iterator of + /// `Option`s of points, compute either `Some(Q)`, where + /// $$ + /// Q = c\_1 P\_1 + \cdots + c\_n P\_n, + /// $$ + /// if all points were `Some(P_i)`, or else return `None`. + /// + /// This function is particularly useful when verifying statements + /// involving compressed points. Accepting `Option` allows + /// inlining point decompression into the multiscalar call, + /// avoiding the need for temporary buffers. + /// ``` + /// #[cfg(feature = "alloc")] + /// # { + /// use curve25519_elligator2::constants; + /// use curve25519_elligator2::traits::VartimeMultiscalarMul; + /// use curve25519_elligator2::ristretto::RistrettoPoint; + /// use curve25519_elligator2::scalar::Scalar; + /// + /// // Some scalars + /// let a = Scalar::from(87329482u64); + /// let b = Scalar::from(37264829u64); + /// let c = Scalar::from(98098098u64); + /// let abc = [a,b,c]; + /// + /// // Some points + /// let P = constants::RISTRETTO_BASEPOINT_POINT; + /// let Q = P + P; + /// let R = P + Q; + /// let PQR = [P, Q, R]; + /// + /// let compressed = [P.compress(), Q.compress(), R.compress()]; + /// + /// // Now we can compute A1 = a*P + b*Q + c*R using P, Q, R: + /// let A1 = RistrettoPoint::vartime_multiscalar_mul(&abc, &PQR); + /// + /// // Or using the compressed points: + /// let A2 = RistrettoPoint::optional_multiscalar_mul( + /// &abc, + /// compressed.iter().map(|pt| pt.decompress()), + /// ); + /// + /// assert_eq!(A2, Some(A1)); + /// + /// // It's also possible to mix compressed and uncompressed points: + /// let A3 = RistrettoPoint::optional_multiscalar_mul( + /// abc.iter() + /// .chain(abc.iter()), + /// compressed.iter().map(|pt| pt.decompress()) + /// .chain(PQR.iter().map(|&pt| Some(pt))), + /// ); + /// + /// assert_eq!(A3, Some(A1+A1)); + /// # } + /// ``` + fn optional_multiscalar_mul(scalars: I, points: J) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator>; + + /// Given an iterator of public scalars and an iterator of + /// public points, compute + /// $$ + /// Q = c\_1 P\_1 + \cdots + c\_n P\_n, + /// $$ + /// using variable-time operations. + /// + /// It is an error to call this function with two iterators of different lengths. + /// + /// # Examples + /// + /// The trait bound aims for maximum flexibility: the inputs must be + /// convertable to iterators (`I: IntoIter`), and the iterator's items + /// must be `Borrow` (or `Borrow`), to allow + /// iterators returning either `Scalar`s or `&Scalar`s. + /// + /// ``` + /// #[cfg(feature = "alloc")] + /// # { + /// use curve25519_elligator2::constants; + /// use curve25519_elligator2::traits::VartimeMultiscalarMul; + /// use curve25519_elligator2::ristretto::RistrettoPoint; + /// use curve25519_elligator2::scalar::Scalar; + /// + /// // Some scalars + /// let a = Scalar::from(87329482u64); + /// let b = Scalar::from(37264829u64); + /// let c = Scalar::from(98098098u64); + /// + /// // Some points + /// let P = constants::RISTRETTO_BASEPOINT_POINT; + /// let Q = P + P; + /// let R = P + Q; + /// + /// // A1 = a*P + b*Q + c*R + /// let abc = [a,b,c]; + /// let A1 = RistrettoPoint::vartime_multiscalar_mul(&abc, &[P,Q,R]); + /// // Note: (&abc).into_iter(): Iterator + /// + /// // A2 = (-a)*P + (-b)*Q + (-c)*R + /// let minus_abc = abc.iter().map(|x| -x); + /// let A2 = RistrettoPoint::vartime_multiscalar_mul(minus_abc, &[P,Q,R]); + /// // Note: minus_abc.into_iter(): Iterator + /// + /// assert_eq!(A1.compress(), (-A2).compress()); + /// # } + /// ``` + fn vartime_multiscalar_mul(scalars: I, points: J) -> Self::Point + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + Self::Point: Clone, + { + Self::optional_multiscalar_mul( + scalars, + points.into_iter().map(|P| Some(P.borrow().clone())), + ) + .expect("should return some point") + } +} + +/// A trait for variable-time multiscalar multiplication with precomputation. +/// +/// A general multiscalar multiplication with precomputation can be written as +/// $$ +/// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m, +/// $$ +/// where the \\(B_i\\) are *static* points, for which precomputation +/// is possible, and the \\(A_j\\) are *dynamic* points, for which +/// precomputation is not possible. +/// +/// This trait has three methods for performing this computation: +/// +/// * [`Self::vartime_multiscalar_mul`], which handles the special case where +/// \\(n = 0\\) and there are no dynamic points; +/// +/// * [`Self::vartime_mixed_multiscalar_mul`], which takes the dynamic points as +/// already-validated `Point`s and is infallible; +/// +/// * [`Self::optional_mixed_multiscalar_mul`], which takes the dynamic points +/// as `Option`s and returns an `Option`, allowing decompression +/// to be composed into the input iterators. +/// +/// All methods require that the lengths of the input iterators be +/// known and matching, as if they were `ExactSizeIterator`s. (It +/// does not require `ExactSizeIterator` only because that trait is +/// broken). +pub trait VartimePrecomputedMultiscalarMul: Sized { + /// The type of point to be multiplied, e.g., `RistrettoPoint`. + type Point: Clone; + + /// Given the static points \\( B_i \\), perform precomputation + /// and return the precomputation data. + fn new(static_points: I) -> Self + where + I: IntoIterator, + I::Item: Borrow; + + /// Given `static_scalars`, an iterator of public scalars + /// \\(b_i\\), compute + /// $$ + /// Q = b_1 B_1 + \cdots + b_m B_m, + /// $$ + /// where the \\(B_j\\) are the points that were supplied to `new`. + /// + /// It is an error to call this function with iterators of + /// inconsistent lengths. + /// + /// The trait bound aims for maximum flexibility: the input must + /// be convertable to iterators (`I: IntoIter`), and the + /// iterator's items must be `Borrow`, to allow iterators + /// returning either `Scalar`s or `&Scalar`s. + fn vartime_multiscalar_mul(&self, static_scalars: I) -> Self::Point + where + I: IntoIterator, + I::Item: Borrow, + { + use core::iter; + + Self::vartime_mixed_multiscalar_mul( + self, + static_scalars, + iter::empty::(), + iter::empty::(), + ) + } + + /// Given `static_scalars`, an iterator of public scalars + /// \\(b_i\\), `dynamic_scalars`, an iterator of public scalars + /// \\(a_i\\), and `dynamic_points`, an iterator of points + /// \\(A_i\\), compute + /// $$ + /// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m, + /// $$ + /// where the \\(B_j\\) are the points that were supplied to `new`. + /// + /// It is an error to call this function with iterators of + /// inconsistent lengths. + /// + /// The trait bound aims for maximum flexibility: the inputs must be + /// convertable to iterators (`I: IntoIter`), and the iterator's items + /// must be `Borrow` (or `Borrow`), to allow + /// iterators returning either `Scalar`s or `&Scalar`s. + fn vartime_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Self::Point + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator, + K::Item: Borrow, + { + Self::optional_mixed_multiscalar_mul( + self, + static_scalars, + dynamic_scalars, + dynamic_points.into_iter().map(|P| Some(P.borrow().clone())), + ) + .expect("should return some point") + } + + /// Given `static_scalars`, an iterator of public scalars + /// \\(b_i\\), `dynamic_scalars`, an iterator of public scalars + /// \\(a_i\\), and `dynamic_points`, an iterator of points + /// \\(A_i\\), compute + /// $$ + /// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m, + /// $$ + /// where the \\(B_j\\) are the points that were supplied to `new`. + /// + /// If any of the dynamic points were `None`, return `None`. + /// + /// It is an error to call this function with iterators of + /// inconsistent lengths. + /// + /// This function is particularly useful when verifying statements + /// involving compressed points. Accepting `Option` allows + /// inlining point decompression into the multiscalar call, + /// avoiding the need for temporary buffers. + fn optional_mixed_multiscalar_mul( + &self, + static_scalars: I, + dynamic_scalars: J, + dynamic_points: K, + ) -> Option + where + I: IntoIterator, + I::Item: Borrow, + J: IntoIterator, + J::Item: Borrow, + K: IntoIterator>; +} + +// ------------------------------------------------------------------------ +// Private Traits +// ------------------------------------------------------------------------ + +/// Trait for checking whether a point is on the curve. +/// +/// This trait is only for debugging/testing, since it should be +/// impossible for a `curve25519-elligator2` user to construct an invalid +/// point. +#[allow(dead_code)] +pub(crate) trait ValidityCheck { + /// Checks whether the point is on the curve. Not CT. + fn is_valid(&self) -> bool; +} diff --git a/curve25519-elligator2/src/window.rs b/curve25519-elligator2/src/window.rs new file mode 100644 index 00000000..99445914 --- /dev/null +++ b/curve25519-elligator2/src/window.rs @@ -0,0 +1,276 @@ +// -*- mode: rust; -*- +// +// This file is part of curve25519-elligator2. +// Copyright (c) 2016-2021 isis lovecruft +// Copyright (c) 2016-2019 Henry de Valence +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - Henry de Valence + +//! Code for fixed- and sliding-window functionality + +#![allow(non_snake_case)] + +use core::fmt::Debug; + +use cfg_if::cfg_if; + +use subtle::Choice; +use subtle::ConditionallyNegatable; +use subtle::ConditionallySelectable; +use subtle::ConstantTimeEq; + +use crate::traits::Identity; + +use crate::backend::serial::curve_models::AffineNielsPoint; +use crate::backend::serial::curve_models::ProjectiveNielsPoint; +use crate::edwards::EdwardsPoint; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +macro_rules! impl_lookup_table { + (Name = $name:ident, Size = $size:expr, SizeNeg = $neg:expr, SizeRange = $range:expr, ConversionRange = $conv_range:expr) => { + /// A lookup table of precomputed multiples of a point \\(P\\), used to + /// compute \\( xP \\) for \\( -8 \leq x \leq 8 \\). + /// + /// The computation of \\( xP \\) is done in constant time by the `select` function. + /// + /// Since `LookupTable` does not implement `Index`, it's more difficult + /// to accidentally use the table directly. Unfortunately the table is + /// only `pub(crate)` so that we can write hardcoded constants, so it's + /// still technically possible. It would be nice to prevent direct + /// access to the table. + #[derive(Copy, Clone)] + pub struct $name(pub(crate) [T; $size]); + + impl $name + where + T: Identity + ConditionallySelectable + ConditionallyNegatable, + { + /// Given \\(-8 \leq x \leq 8\\), return \\(xP\\) in constant time. + pub fn select(&self, x: i8) -> T { + debug_assert!(x >= $neg); + debug_assert!(x as i16 <= $size as i16); // XXX We have to convert to i16s here for the radix-256 case.. this is wrong. + + // Compute xabs = |x| + let xmask = x as i16 >> 7; + let xabs = (x as i16 + xmask) ^ xmask; + + // Set t = 0 * P = identity + let mut t = T::identity(); + for j in $range { + // Copy `points[j-1] == j*P` onto `t` in constant time if `|x| == j`. + let c = (xabs as u16).ct_eq(&(j as u16)); + t.conditional_assign(&self.0[j - 1], c); + } + // Now t == |x| * P. + + let neg_mask = Choice::from((xmask & 1) as u8); + t.conditional_negate(neg_mask); + // Now t == x * P. + + t + } + } + + impl Default for $name { + fn default() -> $name { + $name([T::default(); $size]) + } + } + + impl Debug for $name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?}(", stringify!($name))?; + + for x in self.0.iter() { + write!(f, "{:?}", x)?; + } + + write!(f, ")") + } + } + + impl<'a> From<&'a EdwardsPoint> for $name { + fn from(P: &'a EdwardsPoint) -> Self { + let mut points = [P.as_projective_niels(); $size]; + for j in $conv_range { + points[j + 1] = (P + &points[j]).as_extended().as_projective_niels(); + } + $name(points) + } + } + + impl<'a> From<&'a EdwardsPoint> for $name { + fn from(P: &'a EdwardsPoint) -> Self { + let mut points = [P.as_affine_niels(); $size]; + // XXX batch inversion would be good if perf mattered here + for j in $conv_range { + points[j + 1] = (P + &points[j]).as_extended().as_affine_niels() + } + $name(points) + } + } + + #[cfg(feature = "zeroize")] + impl Zeroize for $name + where + T: Copy + Default + Zeroize, + { + fn zeroize(&mut self) { + self.0.iter_mut().zeroize(); + } + } + }; +} // End macro_rules! impl_lookup_table + +// The first one has to be named "LookupTable" because it's used as a constructor for consts. +// This is radix-16 +impl_lookup_table! { + Name = LookupTable, + Size = 8, + SizeNeg = -8, + SizeRange = 1..9, + ConversionRange = 0..7 +} + +// The rest only get used to make basepoint tables +cfg_if! { + if #[cfg(feature = "precomputed-tables")] { + // radix-32 + impl_lookup_table! { + Name = LookupTableRadix32, + Size = 16, + SizeNeg = -16, + SizeRange = 1..17, + ConversionRange = 0..15 + } + // radix-64 + impl_lookup_table! { + Name = LookupTableRadix64, + Size = 32, + SizeNeg = -32, + SizeRange = 1..33, + ConversionRange = 0..31 + } + // radix-128 + impl_lookup_table! { + Name = LookupTableRadix128, + Size = 64, + SizeNeg = -64, + SizeRange = 1..65, + ConversionRange = 0..63 + } + // radix-256 + impl_lookup_table! { + Name = LookupTableRadix256, + Size = 128, + SizeNeg = -128, + SizeRange = 1..129, + ConversionRange = 0..127 + } + + // For homogeneity we then alias it to "LookupTableRadix16". + pub(crate) type LookupTableRadix16 = LookupTable; + } +} + +/// Holds odd multiples 1A, 3A, ..., 15A of a point A. +#[derive(Copy, Clone)] +pub(crate) struct NafLookupTable5(pub(crate) [T; 8]); + +impl NafLookupTable5 { + /// Given public, odd \\( x \\) with \\( 0 < x < 2^4 \\), return \\(xA\\). + pub fn select(&self, x: usize) -> T { + debug_assert_eq!(x & 1, 1); + debug_assert!(x < 16); + + self.0[x / 2] + } +} + +impl Debug for NafLookupTable5 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "NafLookupTable5({:?})", self.0) + } +} + +impl<'a> From<&'a EdwardsPoint> for NafLookupTable5 { + fn from(A: &'a EdwardsPoint) -> Self { + let mut Ai = [A.as_projective_niels(); 8]; + let A2 = A.double(); + for i in 0..7 { + Ai[i + 1] = (&A2 + &Ai[i]).as_extended().as_projective_niels(); + } + // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A] + NafLookupTable5(Ai) + } +} + +impl<'a> From<&'a EdwardsPoint> for NafLookupTable5 { + fn from(A: &'a EdwardsPoint) -> Self { + let mut Ai = [A.as_affine_niels(); 8]; + let A2 = A.double(); + for i in 0..7 { + Ai[i + 1] = (&A2 + &Ai[i]).as_extended().as_affine_niels(); + } + // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A] + NafLookupTable5(Ai) + } +} + +/// Holds stuff up to 8. The only time we use tables this big is for precomputed basepoint tables +/// and multiscalar multiplication (which requires alloc). +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +#[derive(Copy, Clone)] +pub(crate) struct NafLookupTable8(pub(crate) [T; 64]); + +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +impl NafLookupTable8 { + pub fn select(&self, x: usize) -> T { + debug_assert_eq!(x & 1, 1); + debug_assert!(x < 128); + + self.0[x / 2] + } +} + +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +impl Debug for NafLookupTable8 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + writeln!(f, "NafLookupTable8([")?; + for i in 0..64 { + writeln!(f, "\t{:?},", &self.0[i])?; + } + write!(f, "])") + } +} + +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +impl<'a> From<&'a EdwardsPoint> for NafLookupTable8 { + fn from(A: &'a EdwardsPoint) -> Self { + let mut Ai = [A.as_projective_niels(); 64]; + let A2 = A.double(); + for i in 0..63 { + Ai[i + 1] = (&A2 + &Ai[i]).as_extended().as_projective_niels(); + } + // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A, ..., 127A] + NafLookupTable8(Ai) + } +} + +#[cfg(any(feature = "precomputed-tables", feature = "alloc"))] +impl<'a> From<&'a EdwardsPoint> for NafLookupTable8 { + fn from(A: &'a EdwardsPoint) -> Self { + let mut Ai = [A.as_affine_niels(); 64]; + let A2 = A.double(); + for i in 0..63 { + Ai[i + 1] = (&A2 + &Ai[i]).as_extended().as_affine_niels(); + } + // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A, ..., 127A] + NafLookupTable8(Ai) + } +} diff --git a/curve25519-elligator2/tests/build_tests.sh b/curve25519-elligator2/tests/build_tests.sh new file mode 100755 index 00000000..ac6e7819 --- /dev/null +++ b/curve25519-elligator2/tests/build_tests.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +function match_and_report() { + PATTERN=$1 + FILE=$2 + + if grep -q "$PATTERN" "$FILE"; then + echo build OK "$FILE" : "$PATTERN" + else + echo build ERROR "$FILE" : "$PATTERN" + echo ">>>>>>>>>>>>>>>>>>>>>>>>>>" + cat "$FILE" + echo "<<<<<<<<<<<<<<<<<<<<<<<<<<" + exit 1 + fi +} + +# Assuming naively 64 bit host +cargo clean +OUT=build_1.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\"" cargo build > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'simd'" "$OUT" +match_and_report "curve25519_dalek_bits is '64'" "$OUT" + +# Override to 32 bits assuming naively 64 bit build host +cargo clean +OUT=build_2.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_bits=\"32\"" cargo build > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'serial'" "$OUT" +match_and_report "curve25519_dalek_bits is '32'" "$OUT" + +# Override to 64 bits on 32 bit target +cargo clean +OUT=build_3.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_bits=\"64\"" cargo build --target i686-unknown-linux-gnu > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'serial'" "$OUT" +match_and_report "curve25519_dalek_bits is '64'" "$OUT" + +# 32 bit target default +cargo clean +OUT=build_4.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\"" cargo build --target i686-unknown-linux-gnu > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'serial'" "$OUT" +match_and_report "curve25519_dalek_bits is '32'" "$OUT" + +# wasm 32 bit target default +cargo clean +OUT=build_5.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\"" cargo build --target wasm32-unknown-unknown > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'serial'" "$OUT" +match_and_report "curve25519_dalek_bits is '32'" "$OUT" + +# wasm 32 bit target default +# Attempted override w/ "simd" should result "serial" addition +cargo clean +OUT=build_5_1.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"simd\"" cargo build --target wasm32-unknown-unknown > "$OUT" 2>&1 +# This overide must fail the compilation since "simd" is not available +# See: issues/532 +match_and_report "Could not override curve25519_dalek_backend to simd" "$OUT" + +# fiat override with default 64 bit naive host assumption +cargo clean +OUT=build_6.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"fiat\"" cargo build > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'fiat'" "$OUT" +match_and_report "curve25519_dalek_bits is '64'" "$OUT" + +# fiat 32 bit override +cargo clean +OUT=build_7.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"fiat\" --cfg curve25519_dalek_bits=\"32\"" cargo build > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'fiat'" "$OUT" +match_and_report "curve25519_dalek_bits is '32'" "$OUT" + +# serial override with default 64 bit naive host assumption +cargo clean +OUT=build_8.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"serial\"" cargo build > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'serial'" "$OUT" +match_and_report "curve25519_dalek_bits is '64'" "$OUT" + +# serial 32 bit override +cargo clean +OUT=build_9.txt +env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"serial\" --cfg curve25519_dalek_bits=\"32\"" cargo build > "$OUT" 2>&1 +match_and_report "curve25519_dalek_backend is 'serial'" "$OUT" +match_and_report "curve25519_dalek_bits is '32'" "$OUT" diff --git a/curve25519-elligator2/vendor/ristretto.sage b/curve25519-elligator2/vendor/ristretto.sage new file mode 100644 index 00000000..04cf4f92 --- /dev/null +++ b/curve25519-elligator2/vendor/ristretto.sage @@ -0,0 +1,857 @@ +import binascii +class InvalidEncodingException(Exception): pass +class NotOnCurveException(Exception): pass +class SpecException(Exception): pass + +def lobit(x): return int(x) & 1 +def hibit(x): return lobit(2*x) +def negative(x): return lobit(x) +def enc_le(x,n): return bytearray([int(x)>>(8*i) & 0xFF for i in xrange(n)]) +def dec_le(x): return sum(b<<(8*i) for i,b in enumerate(x)) +def randombytes(n): return bytearray([randint(0,255) for _ in range(n)]) + +def optimized_version_of(spec): + """Decorator: This function is an optimized version of some specification""" + def decorator(f): + def wrapper(self,*args,**kwargs): + def pr(x): + if isinstance(x,bytearray): return binascii.hexlify(x) + else: return str(x) + try: spec_ans = getattr(self,spec,spec)(*args,**kwargs),None + except Exception as e: spec_ans = None,e + try: opt_ans = f(self,*args,**kwargs),None + except Exception as e: opt_ans = None,e + if spec_ans[1] is None and opt_ans[1] is not None: + raise + #raise SpecException("Mismatch in %s: spec returned %s but opt threw %s" + # % (f.__name__,str(spec_ans[0]),str(opt_ans[1]))) + if spec_ans[1] is not None and opt_ans[1] is None: + raise + #raise SpecException("Mismatch in %s: spec threw %s but opt returned %s" + # % (f.__name__,str(spec_ans[1]),str(opt_ans[0]))) + if spec_ans[0] != opt_ans[0]: + raise SpecException("Mismatch in %s: %s != %s" + % (f.__name__,pr(spec_ans[0]),pr(opt_ans[0]))) + if opt_ans[1] is not None: raise + else: return opt_ans[0] + wrapper.__name__ = f.__name__ + return wrapper + return decorator + +def xsqrt(x,exn=InvalidEncodingException("Not on curve")): + """Return sqrt(x)""" + if not is_square(x): raise exn + s = sqrt(x) + if negative(s): s=-s + return s + +def isqrt(x,exn=InvalidEncodingException("Not on curve")): + """Return 1/sqrt(x)""" + if x==0: return 0 + if not is_square(x): raise exn + s = sqrt(x) + #if negative(s): s=-s + return 1/s + +def inv0(x): return 1/x if x != 0 else 0 + +def isqrt_i(x): + """Return 1/sqrt(x) or 1/sqrt(zeta * x)""" + if x==0: return True,0 + gen = x.parent(-1) + while is_square(gen): gen = sqrt(gen) + if is_square(x): return True,1/sqrt(x) + else: return False,1/sqrt(x*gen) + +class QuotientEdwardsPoint(object): + """Abstract class for point an a quotiented Edwards curve; needs F,a,d,cofactor to work""" + def __init__(self,x=0,y=1): + x = self.x = self.F(x) + y = self.y = self.F(y) + if y^2 + self.a*x^2 != 1 + self.d*x^2*y^2: + raise NotOnCurveException(str(self)) + + def __repr__(self): + return "%s(0x%x,0x%x)" % (self.__class__.__name__, self.x, self.y) + + def __iter__(self): + yield self.x + yield self.y + + def __add__(self,other): + x,y = self + X,Y = other + a,d = self.a,self.d + return self.__class__( + (x*Y+y*X)/(1+d*x*y*X*Y), + (y*Y-a*x*X)/(1-d*x*y*X*Y) + ) + + def __neg__(self): return self.__class__(-self.x,self.y) + def __sub__(self,other): return self + (-other) + def __rmul__(self,other): return self*other + def __eq__(self,other): + """NB: this is the only method that is different from the usual one""" + x,y = self + X,Y = other + return x*Y == X*y or (self.cofactor==8 and -self.a*x*X == y*Y) + def __ne__(self,other): return not (self==other) + + def __mul__(self,exp): + exp = int(exp) + if exp < 0: exp,self = -exp,-self + total = self.__class__() + work = self + while exp != 0: + if exp & 1: total += work + work += work + exp >>= 1 + return total + + def xyzt(self): + x,y = self + z = self.F.random_element() + return x*z,y*z,z,x*y*z + + def torque(self): + """Apply cofactor group, except keeping the point even""" + if self.cofactor == 8: + if self.a == -1: return self.__class__(self.y*self.i, self.x*self.i) + if self.a == 1: return self.__class__(-self.y, self.x) + else: + return self.__class__(-self.x, -self.y) + + def doubleAndEncodeSpec(self): + return (self+self).encode() + + # Utility functions + @classmethod + def bytesToGf(cls,bytes,mustBeProper=True,mustBePositive=False,maskHiBits=False): + """Convert little-endian bytes to field element, sanity check length""" + if len(bytes) != cls.encLen: + raise InvalidEncodingException("wrong length %d" % len(bytes)) + s = dec_le(bytes) + if mustBeProper and s >= cls.F.order(): + raise InvalidEncodingException("%d out of range!" % s) + bitlen = int(ceil(log(cls.F.order())/log(2))) + if maskHiBits: s &= 2^bitlen-1 + s = cls.F(s) + if mustBePositive and negative(s): + raise InvalidEncodingException("%d is negative!" % s) + return s + + @classmethod + def gfToBytes(cls,x,mustBePositive=False): + """Convert little-endian bytes to field element, sanity check length""" + if negative(x) and mustBePositive: x = -x + return enc_le(x,cls.encLen) + +class RistrettoPoint(QuotientEdwardsPoint): + """The new Ristretto group""" + def encodeSpec(self): + """Unoptimized specification for encoding""" + x,y = self + if self.cofactor==8 and (negative(x*y) or y==0): (x,y) = self.torque() + if y == -1: y = 1 # Avoid divide by 0; doesn't affect impl + + if negative(x): x,y = -x,-y + s = xsqrt(self.mneg*(1-y)/(1+y),exn=Exception("Unimplemented: point is odd: " + str(self))) + return self.gfToBytes(s) + + @classmethod + def decodeSpec(cls,s): + """Unoptimized specification for decoding""" + s = cls.bytesToGf(s,mustBePositive=True) + + a,d = cls.a,cls.d + x = xsqrt(4*s^2 / (a*d*(1+a*s^2)^2 - (1-a*s^2)^2)) + y = (1+a*s^2) / (1-a*s^2) + + if cls.cofactor==8 and (negative(x*y) or y==0): + raise InvalidEncodingException("x*y has high bit") + + return cls(x,y) + + @optimized_version_of("encodeSpec") + def encode(self): + """Encode, optimized version""" + a,d,mneg = self.a,self.d,self.mneg + x,y,z,t = self.xyzt() + + if self.cofactor==8: + u1 = mneg*(z+y)*(z-y) + u2 = x*y # = t*z + isr = isqrt(u1*u2^2) + i1 = isr*u1 # sqrt(mneg*(z+y)*(z-y))/(x*y) + i2 = isr*u2 # 1/sqrt(a*(y+z)*(y-z)) + z_inv = i1*i2*t # 1/z + + if negative(t*z_inv): + if a==-1: + x,y = y*self.i,x*self.i + den_inv = self.magic * i1 + else: + x,y = -y,x + den_inv = self.i * self.magic * i1 + + else: + den_inv = i2 + + if negative(x*z_inv): y = -y + s = (z-y) * den_inv + else: + num = mneg*(z+y)*(z-y) + isr = isqrt(num*y^2) + if negative(isr^2*num*y*t): y = -y + s = isr*y*(z-y) + + return self.gfToBytes(s,mustBePositive=True) + + @optimized_version_of("doubleAndEncodeSpec") + def doubleAndEncode(self): + X,Y,Z,T = self.xyzt() + a,d,mneg = self.a,self.d,self.mneg + + if self.cofactor==8: + e = 2*X*Y + f = Z^2+d*T^2 + g = Y^2-a*X^2 + h = Z^2-d*T^2 + + inv1 = 1/(e*f*g*h) + z_inv = inv1*e*g # 1 / (f*h) + t_inv = inv1*f*h + + if negative(e*g*z_inv): + if a==-1: sqrta = self.i + else: sqrta = -1 + e,f,g,h = g,h,-e,f*sqrta + factor = self.i + else: + factor = self.magic + + if negative(h*e*z_inv): g=-g + s = (h-g)*factor*g*t_inv + + else: + foo = Y^2+a*X^2 + bar = X*Y + den = 1/(foo*bar) + if negative(2*bar^2*den): tmp = a*X^2 + else: tmp = Y^2 + s = self.magic*(Z^2-tmp)*foo*den + + return self.gfToBytes(s,mustBePositive=True) + + @classmethod + @optimized_version_of("decodeSpec") + def decode(cls,s): + """Decode, optimized version""" + s = cls.bytesToGf(s,mustBePositive=True) + + a,d = cls.a,cls.d + yden = 1-a*s^2 + ynum = 1+a*s^2 + yden_sqr = yden^2 + xden_sqr = a*d*ynum^2 - yden_sqr + + isr = isqrt(xden_sqr * yden_sqr) + + xden_inv = isr * yden + yden_inv = xden_inv * isr * xden_sqr + + x = 2*s*xden_inv + if negative(x): x = -x + y = ynum * yden_inv + + if cls.cofactor==8 and (negative(x*y) or y==0): + raise InvalidEncodingException("x*y is invalid: %d, %d" % (x,y)) + + return cls(x,y) + + @classmethod + def fromJacobiQuartic(cls,s,t,sgn=1): + """Convert point from its Jacobi Quartic representation""" + a,d = cls.a,cls.d + assert s^4 - 2*cls.a*(1-2*d/(d-a))*s^2 + 1 == t^2 + x = 2*s*cls.magic / t + y = (1+a*s^2) / (1-a*s^2) + return cls(sgn*x,y) + + @classmethod + def elligatorSpec(cls,r0): + a,d = cls.a,cls.d + r = cls.qnr * cls.bytesToGf(r0,mustBeProper=False,maskHiBits=True)^2 + den = (d*r-a)*(a*r-d) + if den == 0: return cls() + n1 = cls.a*(r+1)*(a+d)*(d-a)/den + n2 = r*n1 + if is_square(n1): + sgn,s,t = 1, xsqrt(n1), -(r-1)*(a+d)^2 / den - 1 + else: + sgn,s,t = -1,-xsqrt(n2), r*(r-1)*(a+d)^2 / den - 1 + + return cls.fromJacobiQuartic(s,t) + + @classmethod + @optimized_version_of("elligatorSpec") + def elligator(cls,r0): + a,d = cls.a,cls.d + r0 = cls.bytesToGf(r0,mustBeProper=False,maskHiBits=True) + r = cls.qnr * r0^2 + den = (d*r-a)*(a*r-d) + num = cls.a*(r+1)*(a+d)*(d-a) + + iss,isri = isqrt_i(num*den) + if iss: sgn,twiddle = 1,1 + else: sgn,twiddle = -1,r0*cls.qnr + isri *= twiddle + s = isri*num + t = -sgn*isri*s*(r-1)*(d+a)^2 - 1 + if negative(s) == iss: s = -s + return cls.fromJacobiQuartic(s,t) + + +class Decaf_1_1_Point(QuotientEdwardsPoint): + """Like current decaf but tweaked for simplicity""" + def encodeSpec(self): + """Unoptimized specification for encoding""" + a,d = self.a,self.d + x,y = self + if x==0 or y==0: return(self.gfToBytes(0)) + + if self.cofactor==8 and negative(x*y*self.isoMagic): + x,y = self.torque() + + sr = xsqrt(1-a*x^2) + altx = x*y*self.isoMagic / sr + if negative(altx): s = (1+sr)/x + else: s = (1-sr)/x + + return self.gfToBytes(s,mustBePositive=True) + + @classmethod + def decodeSpec(cls,s): + """Unoptimized specification for decoding""" + a,d = cls.a,cls.d + s = cls.bytesToGf(s,mustBePositive=True) + + if s==0: return cls() + t = xsqrt(s^4 + 2*(a-2*d)*s^2 + 1) + altx = 2*s*cls.isoMagic/t + if negative(altx): t = -t + x = 2*s / (1+a*s^2) + y = (1-a*s^2) / t + + if cls.cofactor==8 and (negative(x*y*cls.isoMagic) or y==0): + raise InvalidEncodingException("x*y is invalid: %d, %d" % (x,y)) + + return cls(x,y) + + def toJacobiQuartic(self,toggle_rotation=False,toggle_altx=False,toggle_s=False): + "Return s,t on jacobi curve" + a,d = self.a,self.d + x,y,z,t = self.xyzt() + + if self.cofactor == 8: + # Cofactor 8 version + # Simulate IMAGINE_TWIST because that's how libdecaf does it + x = self.i*x + t = self.i*t + a = -a + d = -d + + # OK, the actual libdecaf code should be here + num = (z+y)*(z-y) + den = x*y + isr = isqrt(num*(a-d)*den^2) + + iden = isr * den * self.isoMagic # 1/sqrt((z+y)(z-y)) = 1/sqrt(1-Y^2) / z + inum = isr * num # sqrt(1-Y^2) * z / xysqrt(a-d) ~ 1/sqrt(1-ax^2)/z + + if negative(iden*inum*self.i*t^2*(d-a)) != toggle_rotation: + iden,inum = inum,iden + fac = x*sqrt(a) + toggle=(a==-1) + else: + fac = y + toggle=False + + imi = self.isoMagic * self.i + altx = inum*t*imi + neg_altx = negative(altx) != toggle_altx + if neg_altx != toggle: inum =- inum + + tmp = fac*(inum*z + 1) + s = iden*tmp*imi + + negm1 = (negative(s) != toggle_s) != neg_altx + if negm1: m1 = a*fac + z + else: m1 = a*fac - z + + swap = toggle_s + + else: + # Much simpler cofactor 4 version + num = (x+t)*(x-t) + isr = isqrt(num*(a-d)*x^2) + ratio = isr*num + altx = ratio*self.isoMagic + + neg_altx = negative(altx) != toggle_altx + if neg_altx: ratio =- ratio + + tmp = ratio*z - t + s = (a-d)*isr*x*tmp + + negx = (negative(s) != toggle_s) != neg_altx + if negx: m1 = -a*t + x + else: m1 = -a*t - x + + swap = toggle_s + + if negative(s): s = -s + + return s,m1,a*tmp,swap + + def invertElligator(self,toggle_r=False,*args,**kwargs): + "Produce preimage of self under elligator, or None" + a,d = self.a,self.d + + rets = [] + + tr = [False,True] if self.cofactor == 8 else [False] + for toggle_rotation in tr: + for toggle_altx in [False,True]: + for toggle_s in [False,True]: + for toggle_r in [False,True]: + s,m1,m12,swap = self.toJacobiQuartic(toggle_rotation,toggle_altx,toggle_s) + + #print + #print toggle_rotation,toggle_altx,toggle_s + #print m1 + #print m12 + + + if self == self.__class__(): + if self.cofactor == 4: + # Hacks for identity! + if toggle_altx: m12 = 1 + elif toggle_s: m1 = 1 + elif toggle_r: continue + ## BOTH??? + + else: + m12 = 1 + imi = self.isoMagic * self.i + if toggle_rotation: + if toggle_altx: m1 = -imi + else: m1 = +imi + else: + if toggle_altx: m1 = 0 + else: m1 = a-d + + rnum = (d*a*m12-m1) + rden = ((d*a-1)*m12+m1) + if swap: rnum,rden = rden,rnum + + ok,sr = isqrt_i(rnum*rden*self.qnr) + if not ok: continue + sr *= rnum + #print "Works! %d %x" % (swap,sr) + + if negative(sr) != toggle_r: sr = -sr + ret = self.gfToBytes(sr) + if self.elligator(ret) != self and self.elligator(ret) != -self: + print "WRONG!",[toggle_rotation,toggle_altx,toggle_s] + if self.elligator(ret) == -self and self != -self: print "Negated!",[toggle_rotation,toggle_altx,toggle_s] + rets.append(bytes(ret)) + return rets + + @optimized_version_of("encodeSpec") + def encode(self): + """Encode, optimized version""" + return self.gfToBytes(self.toJacobiQuartic()[0]) + + @classmethod + @optimized_version_of("decodeSpec") + def decode(cls,s): + """Decode, optimized version""" + a,d = cls.a,cls.d + s = cls.bytesToGf(s,mustBePositive=True) + + #if s==0: return cls() + s2 = s^2 + den = 1+a*s2 + num = den^2 - 4*d*s2 + isr = isqrt(num*den^2) + altx = 2*s*isr*den*cls.isoMagic + if negative(altx): isr = -isr + x = 2*s *isr^2*den*num + y = (1-a*s^2) * isr*den + + if cls.cofactor==8 and (negative(x*y*cls.isoMagic) or y==0): + raise InvalidEncodingException("x*y is invalid: %d, %d" % (x,y)) + + return cls(x,y) + + @classmethod + def fromJacobiQuartic(cls,s,t,sgn=1): + """Convert point from its Jacobi Quartic representation""" + a,d = cls.a,cls.d + if s==0: return cls() + x = 2*s / (1+a*s^2) + y = (1-a*s^2) / t + return cls(x,sgn*y) + + @optimized_version_of("doubleAndEncodeSpec") + def doubleAndEncode(self): + X,Y,Z,T = self.xyzt() + a,d = self.a,self.d + + if self.cofactor == 8: + # Cofactor 8 version + # Simulate IMAGINE_TWIST because that's how libdecaf does it + X = self.i*X + T = self.i*T + a = -a + d = -d + # TODO: This is only being called for a=-1, so could + # be wrong for a=1 + + e = 2*X*Y + f = Y^2+a*X^2 + g = Y^2-a*X^2 + h = Z^2-d*T^2 + + eim = e*self.isoMagic + inv = 1/(eim*g*f*h) + fh_inv = eim*g*inv*self.i + + if negative(eim*g*fh_inv): + idf = g*self.isoMagic*self.i + bar = f + foo = g + test = eim*f + else: + idf = eim + bar = h + foo = -eim + test = g*h + + if negative(test*fh_inv): bar =- bar + s = idf*(foo+bar)*inv*f*h + + else: + xy = X*Y + h = Z^2-d*T^2 + inv = 1/(xy*h) + if negative(inv*2*xy^2*self.isoMagic): tmp = Y + else: tmp = X + s = tmp^2*h*inv # = X/Y or Y/X, interestingly + + return self.gfToBytes(s,mustBePositive=True) + + @classmethod + def elligatorSpec(cls,r0,fromR=False): + a,d = cls.a,cls.d + if fromR: r = r0 + else: r = cls.qnr * cls.bytesToGf(r0,mustBeProper=False,maskHiBits=True)^2 + + den = (d*r-(d-a))*((d-a)*r-d) + if den == 0: return cls() + n1 = (r+1)*(a-2*d)/den + n2 = r*n1 + if is_square(n1): + sgn,s,t = 1, xsqrt(n1), -(r-1)*(a-2*d)^2 / den - 1 + else: + sgn,s,t = -1, -xsqrt(n2), r*(r-1)*(a-2*d)^2 / den - 1 + + return cls.fromJacobiQuartic(s,t) + + @classmethod + @optimized_version_of("elligatorSpec") + def elligator(cls,r0): + a,d = cls.a,cls.d + r0 = cls.bytesToGf(r0,mustBeProper=False,maskHiBits=True) + r = cls.qnr * r0^2 + den = (d*r-(d-a))*((d-a)*r-d) + num = (r+1)*(a-2*d) + + iss,isri = isqrt_i(num*den) + if iss: sgn,twiddle = 1,1 + else: sgn,twiddle = -1,r0*cls.qnr + isri *= twiddle + s = isri*num + t = -sgn*isri*s*(r-1)*(a-2*d)^2 - 1 + if negative(s) == iss: s = -s + return cls.fromJacobiQuartic(s,t) + + def elligatorInverseBruteForce(self): + """Invert Elligator using SAGE's polynomial solver""" + a,d = self.a,self.d + R. = self.F[] + r = self.qnr * r0^2 + den = (d*r-(d-a))*((d-a)*r-d) + n1 = (r+1)*(a-2*d)/den + n2 = r*n1 + ret = set() + for s2,t in [(n1, -(r-1)*(a-2*d)^2 / den - 1), + (n2,r*(r-1)*(a-2*d)^2 / den - 1)]: + x2 = 4*s2/(1+a*s2)^2 + y = (1-a*s2) / t + + selfT = self + for i in xrange(self.cofactor/2): + xT,yT = selfT + polyX = xT^2-x2 + polyY = yT-y + sx = set(r for r,_ in polyX.numerator().roots()) + sy = set(r for r,_ in polyY.numerator().roots()) + ret = ret.union(sx.intersection(sy)) + + selfT = selfT.torque() + + ret = [self.gfToBytes(r) for r in ret] + + for r in ret: + assert self.elligator(r) in [self,-self] + + ret = [r for r in ret if self.elligator(r) == self] + + return ret + +class Ed25519Point(RistrettoPoint): + F = GF(2^255-19) + d = F(-121665/121666) + a = F(-1) + i = sqrt(F(-1)) + mneg = F(1) + qnr = i + magic = isqrt(a*d-1) + cofactor = 8 + encLen = 32 + + @classmethod + def base(cls): + return cls( 15112221349535400772501151409588531511454012693041857206046113283949847762202, 46316835694926478169428394003475163141307993866256225615783033603165251855960 + ) + +class NegEd25519Point(RistrettoPoint): + F = GF(2^255-19) + d = F(121665/121666) + a = F(1) + i = sqrt(F(-1)) + mneg = F(-1) # TODO checkme vs 1-ad or whatever + qnr = i + magic = isqrt(a*d-1) + cofactor = 8 + encLen = 32 + + @classmethod + def base(cls): + y = cls.F(4/5) + x = sqrt((y^2-1)/(cls.d*y^2-cls.a)) + if negative(x): x = -x + return cls(x,y) + +class IsoEd448Point(RistrettoPoint): + F = GF(2^448-2^224-1) + d = F(39082/39081) + a = F(1) + mneg = F(-1) + qnr = -1 + magic = isqrt(a*d-1) + cofactor = 4 + encLen = 56 + + @classmethod + def base(cls): + return cls( # RFC has it wrong + 345397493039729516374008604150537410266655260075183290216406970281645695073672344430481787759340633221708391583424041788924124567700732, + -363419362147803445274661903944002267176820680343659030140745099590306164083365386343198191849338272965044442230921818680526749009182718 + ) + +class TwistedEd448GoldilocksPoint(Decaf_1_1_Point): + F = GF(2^448-2^224-1) + d = F(-39082) + a = F(-1) + qnr = -1 + cofactor = 4 + encLen = 56 + isoMagic = IsoEd448Point.magic + + @classmethod + def base(cls): + return cls.decodeSpec(Ed448GoldilocksPoint.base().encodeSpec()) + +class Ed448GoldilocksPoint(Decaf_1_1_Point): + F = GF(2^448-2^224-1) + d = F(-39081) + a = F(1) + qnr = -1 + cofactor = 4 + encLen = 56 + isoMagic = IsoEd448Point.magic + + @classmethod + def base(cls): + return 2*cls( + 224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710, 298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660 + ) + +class IsoEd25519Point(Decaf_1_1_Point): + # TODO: twisted iso too! + # TODO: twisted iso might have to IMAGINE_TWIST or whatever + F = GF(2^255-19) + d = F(-121665) + a = F(1) + i = sqrt(F(-1)) + qnr = i + magic = isqrt(a*d-1) + cofactor = 8 + encLen = 32 + isoMagic = Ed25519Point.magic + isoA = Ed25519Point.a + + @classmethod + def base(cls): + return cls.decodeSpec(Ed25519Point.base().encode()) + +class TestFailedException(Exception): pass + +def test(cls,n): + print "Testing curve %s" % cls.__name__ + + specials = [1] + ii = cls.F(-1) + while is_square(ii): + specials.append(ii) + ii = sqrt(ii) + specials.append(ii) + for i in specials: + if negative(cls.F(i)): i = -i + i = enc_le(i,cls.encLen) + try: + Q = cls.decode(i) + QE = Q.encode() + if QE != i: + raise TestFailedException("Round trip special %s != %s" % + (binascii.hexlify(QE),binascii.hexlify(i))) + except NotOnCurveException: pass + except InvalidEncodingException: pass + + + P = cls.base() + Q = cls() + for i in xrange(n): + #print binascii.hexlify(Q.encode()) + QE = Q.encode() + QQ = cls.decode(QE) + if QQ != Q: raise TestFailedException("Round trip %s != %s" % (str(QQ),str(Q))) + + # Testing s -> 1/s: encodes -point on cofactor + s = cls.bytesToGf(QE) + if s != 0: + ss = cls.gfToBytes(1/s,mustBePositive=True) + try: + QN = cls.decode(ss) + if cls.cofactor == 8: + raise TestFailedException("1/s shouldnt work for cofactor 8") + if QN != -Q: + raise TestFailedException("s -> 1/s should negate point for cofactor 4") + except InvalidEncodingException as e: + # Should be raised iff cofactor==8 + if cls.cofactor == 4: + raise TestFailedException("s -> 1/s should work for cofactor 4") + + QT = Q + for h in xrange(cls.cofactor): + QT = QT.torque() + if QT.encode() != QE: + raise TestFailedException("Can't torque %s,%d" % (str(Q),h+1)) + + Q0 = Q + P + if Q0 == Q: raise TestFailedException("Addition doesn't work") + if Q0-P != Q: raise TestFailedException("Subtraction doesn't work") + + r = randint(1,1000) + Q1 = Q0*r + Q2 = Q0*(r+1) + if Q1 + Q0 != Q2: raise TestFailedException("Scalarmul doesn't work") + Q = Q1 + +def testElligator(cls,n): + print "Testing elligator on %s" % cls.__name__ + for i in xrange(n): + r = randombytes(cls.encLen) + P = cls.elligator(r) + if hasattr(P,"invertElligator"): + iv = P.invertElligator() + modr = bytes(cls.gfToBytes(cls.bytesToGf(r,mustBeProper=False,maskHiBits=True))) + iv2 = P.torque().invertElligator() + if modr not in iv: print "Failed to invert Elligator!" + if len(iv) != len(set(iv)): + print "Elligator inverses not unique!", len(set(iv)), len(iv) + if iv != iv2: + print "Elligator is untorqueable!" + #print [binascii.hexlify(j) for j in iv] + #print [binascii.hexlify(j) for j in iv2] + #break + else: + pass # TODO + +def gangtest(classes,n): + print "Gang test",[cls.__name__ for cls in classes] + specials = [1] + ii = classes[0].F(-1) + while is_square(ii): + specials.append(ii) + ii = sqrt(ii) + specials.append(ii) + + for i in xrange(n): + rets = [bytes((cls.base()*i).encode()) for cls in classes] + if len(set(rets)) != 1: + print "Divergence in encode at %d" % i + for c,ret in zip(classes,rets): + print c,binascii.hexlify(ret) + print + + if i < len(specials): r0 = enc_le(specials[i],classes[0].encLen) + else: r0 = randombytes(classes[0].encLen) + + rets = [bytes((cls.elligator(r0)*i).encode()) for cls in classes] + if len(set(rets)) != 1: + print "Divergence in elligator at %d" % i + for c,ret in zip(classes,rets): + print c,binascii.hexlify(ret) + print + +def testDoubleAndEncode(cls,n): + print "Testing doubleAndEncode on %s" % cls.__name__ + for i in xrange(n): + r1 = randombytes(cls.encLen) + r2 = randombytes(cls.encLen) + u = cls.elligator(r1) + cls.elligator(r2) + u.doubleAndEncode() + +testDoubleAndEncode(Ed25519Point,100) +testDoubleAndEncode(NegEd25519Point,100) +testDoubleAndEncode(IsoEd25519Point,100) +testDoubleAndEncode(IsoEd448Point,100) +testDoubleAndEncode(TwistedEd448GoldilocksPoint,100) +#test(Ed25519Point,100) +#test(NegEd25519Point,100) +#test(IsoEd25519Point,100) +#test(IsoEd448Point,100) +#test(TwistedEd448GoldilocksPoint,100) +#test(Ed448GoldilocksPoint,100) +#testElligator(Ed25519Point,100) +#testElligator(NegEd25519Point,100) +#testElligator(IsoEd25519Point,100) +#testElligator(IsoEd448Point,100) +#testElligator(Ed448GoldilocksPoint,100) +#testElligator(TwistedEd448GoldilocksPoint,100) +#gangtest([IsoEd448Point,TwistedEd448GoldilocksPoint,Ed448GoldilocksPoint],100) +#gangtest([Ed25519Point,IsoEd25519Point],100)