From d029ddea8396d7a39910028dd5ae436a3bd3e9bb Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 12 Dec 2019 11:32:47 -0700 Subject: [PATCH] Squashed 'jubjub/' content from commit 9987ddf git-subtree-dir: jubjub git-subtree-split: 9987ddf8d17a87bd2b14627665768e4038b657c4 --- .github/workflows/ci.yml | 95 +++ .gitignore | 3 + COPYRIGHT | 14 + Cargo.toml | 33 + LICENSE-APACHE | 201 ++++++ LICENSE-MIT | 23 + README.md | 53 ++ RELEASES.md | 24 + benches/fq_bench.rs | 51 ++ benches/fr_bench.rs | 51 ++ benches/point_bench.rs | 58 ++ doc/derive/.gitignore | 1 + doc/derive/derive.sage | 32 + doc/evidence/.gitignore | 102 +++ doc/evidence/LICENSE | 19 + doc/evidence/README.md | 28 + doc/evidence/a | 1 + doc/evidence/d | 1 + doc/evidence/l | 1 + doc/evidence/p | 1 + doc/evidence/rigid | 1 + doc/evidence/run.sh | 4 + doc/evidence/shape | 1 + doc/evidence/verify.sage | 444 +++++++++++++ doc/evidence/x0 | 1 + doc/evidence/x1 | 1 + doc/evidence/y0 | 1 + doc/evidence/y1 | 1 + src/fr.rs | 1023 +++++++++++++++++++++++++++++ src/lib.rs | 1323 ++++++++++++++++++++++++++++++++++++++ src/util.rs | 174 +++++ tests/common.rs | 29 + tests/fq_blackbox.rs | 120 ++++ tests/fr_blackbox.rs | 120 ++++ 34 files changed, 4035 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 COPYRIGHT create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 RELEASES.md create mode 100644 benches/fq_bench.rs create mode 100644 benches/fr_bench.rs create mode 100644 benches/point_bench.rs create mode 100644 doc/derive/.gitignore create mode 100644 doc/derive/derive.sage create mode 100644 doc/evidence/.gitignore create mode 100644 doc/evidence/LICENSE create mode 100644 doc/evidence/README.md create mode 100644 doc/evidence/a create mode 100644 doc/evidence/d create mode 100644 doc/evidence/l create mode 100644 doc/evidence/p create mode 100644 doc/evidence/rigid create mode 100644 doc/evidence/run.sh create mode 100644 doc/evidence/shape create mode 100644 doc/evidence/verify.sage create mode 100644 doc/evidence/x0 create mode 100644 doc/evidence/x1 create mode 100644 doc/evidence/y0 create mode 100644 doc/evidence/y1 create mode 100644 src/fr.rs create mode 100644 src/lib.rs create mode 100644 src/util.rs create mode 100644 tests/common.rs create mode 100644 tests/fq_blackbox.rs create mode 100644 tests/fr_blackbox.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..5d0efb3a2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,95 @@ +name: CI checks + +on: [push, pull_request] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + + # Ensure all code has been formatted with rustfmt + - run: rustup component add rustfmt + - name: Check formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: -- --check --color always + + test: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build tests + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --release --tests + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release + + no-std: + name: Check no-std compatibility + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.36.0 + override: true + - run: rustup target add thumbv6m-none-eabi + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --target thumbv6m-none-eabi --no-default-features + + doc-links: + name: Nightly lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + + # Ensure intra-documentation links all resolve correctly + # Requires #![deny(intra_doc_link_resolution_failure)] in crate. + - name: Check intra-doc links + uses: actions-rs/cargo@v1 + with: + command: doc + args: --document-private-items diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..693699042 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 000000000..aaca1cc67 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,14 @@ +Copyrights in the "jubjub" library are retained by their contributors. No +copyright assignment is required to contribute to the "jubjub" library. + +The "jubjub" library is licensed under either of + + * Apache License, Version 2.0, (see ./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license (see ./LICENSE-MIT or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..1bac85791 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +authors = [ + "Sean Bowe ", + "Eirik Ogilvie-Wigley ", + "Jack Grigg ", +] +description = "Implementation of the Jubjub elliptic curve group" +documentation = "https://docs.rs/jubjub/" +homepage = "https://github.com/zkcrypto/jubjub" +license = "MIT/Apache-2.0" +name = "jubjub" +repository = "https://github.com/zkcrypto/jubjub" +version = "0.3.0" +edition = "2018" + +[dependencies.bls12_381] +version = "0.1" +default-features = false + +[dependencies.subtle] +version = "^2.2.1" +default-features = false + +[dev-dependencies.rand_core] +version = "0.5" +default-features = false + +[dev-dependencies.rand_xorshift] +version = "0.2" +default-features = false + +[features] +default = [] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..31aa79387 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..da5bd530a --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# jubjub [![Crates.io](https://img.shields.io/crates/v/jubjub.svg)](https://crates.io/crates/jubjub) # + + + +This is a pure Rust implementation of the Jubjub elliptic curve group and its associated fields. + +* **This implementation has not been reviewed or audited. Use at your own risk.** +* This implementation targets Rust `1.36` or later. +* All operations are constant time unless explicitly noted. +* This implementation does not require the Rust standard library. + +## [Documentation](https://docs.rs/jubjub) + +## Curve Description + +Jubjub is the [twisted Edwards curve](https://en.wikipedia.org/wiki/Twisted_Edwards_curve) `-u^2 + v^2 = 1 + d.u^2.v^2` of rational points over `GF(q)` with a subgroup of prime order `r` and cofactor `8`. + +``` +q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +r = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7 +d = -(10240/10241) +``` + +The choice of `GF(q)` is made to be the scalar field of the BLS12-381 elliptic curve construction. + +Jubjub is birationally equivalent to a [Montgomery curve](https://en.wikipedia.org/wiki/Montgomery_curve) `y^2 = x^3 + Ax^2 + x` over the same field with `A = 40962`. This value of `A` is the smallest integer such that `(A - 2) / 4` is a small integer, `A^2 - 4` is nonsquare in `GF(q)`, and the Montgomery curve and its quadratic twist have small cofactors `8` and `4`, respectively. This is identical to the relationship between Curve25519 and ed25519. + +Please see [./doc/evidence/](./doc/evidence/) for supporting evidence that Jubjub meets the [SafeCurves](https://safecurves.cr.yp.to/index.html) criteria. The tool in [./doc/derive/](./doc/derive/) will derive the curve parameters via the above criteria to demonstrate rigidity. + +## Acknowledgements + +Jubjub was designed by Sean Bowe. Daira Hopwood is responsible for its name and specification. The security evidence in [./doc/evidence/](./doc/evidence/) is the product of Daira Hopwood and based on SafeCurves by Daniel J. Bernstein and Tanja Lange. Peter Newell and Daira Hopwood are responsible for the Jubjub bird image. + +Please see `Cargo.toml` for a list of primary authors of this codebase. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 000000000..45db61c1f --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,24 @@ +# 0.3.0 + +This release now depends on the `bls12_381` crate, which exposes the `Fq` field type that we re-export. + +* The `Fq` and `Fr` field types now have better constant function support for various operations and constructors. +* We no longer depend on the `byteorder` crate. +* We've bumped our `rand_core` dev-dependency up to 0.5. +* We've removed the `std` and `nightly` features. +* We've bumped our dependency of `subtle` up to `^2.2.1`. + +# 0.2.0 + +This release switches to `subtle 2.1` to bring in the `CtOption` type, and also makes a few useful API changes. + +* Implemented `Mul` for `AffineNielsPoint` and `ExtendedNielsPoint` +* Changed `AffinePoint::to_niels()` to be a `const` function so that constant curve points can be constructed without statics. +* Implemented `multiply_bits` for `AffineNielsPoint`, `ExtendedNielsPoint` +* Removed `CtOption` and replaced it with `CtOption` from `subtle` crate. +* Modified receivers of some methods to reduce stack usage +* Changed various `into_bytes` methods into `to_bytes` + +# 0.1.0 + +Initial release. diff --git a/benches/fq_bench.rs b/benches/fq_bench.rs new file mode 100644 index 000000000..39fc1483f --- /dev/null +++ b/benches/fq_bench.rs @@ -0,0 +1,51 @@ +#![feature(test)] + +extern crate test; + +use jubjub::*; +use test::Bencher; + +#[bench] +fn bench_mul_assign(bencher: &mut Bencher) { + let mut n = Fq::one(); + let b = -Fq::one(); + bencher.iter(move || { + n *= &b; + }); +} + +#[bench] +fn bench_sub_assign(bencher: &mut Bencher) { + let mut n = Fq::one(); + let b = -Fq::one(); + bencher.iter(move || { + n -= &b; + }); +} + +#[bench] +fn bench_add_assign(bencher: &mut Bencher) { + let mut n = Fq::one(); + let b = -Fq::one(); + bencher.iter(move || { + n += &b; + }); +} + +#[bench] +fn bench_square_assign(bencher: &mut Bencher) { + let n = Fq::one(); + bencher.iter(move || n.square()); +} + +#[bench] +fn bench_invert(bencher: &mut Bencher) { + let n = Fq::one(); + bencher.iter(move || n.invert()); +} + +#[bench] +fn bench_sqrt(bencher: &mut Bencher) { + let n = Fq::one().double().double(); + bencher.iter(move || n.sqrt()); +} diff --git a/benches/fr_bench.rs b/benches/fr_bench.rs new file mode 100644 index 000000000..b84c1b566 --- /dev/null +++ b/benches/fr_bench.rs @@ -0,0 +1,51 @@ +#![feature(test)] + +extern crate test; + +use jubjub::*; +use test::Bencher; + +#[bench] +fn bench_mul_assign(bencher: &mut Bencher) { + let mut n = Fr::one(); + let b = -Fr::one(); + bencher.iter(move || { + n *= &b; + }); +} + +#[bench] +fn bench_sub_assign(bencher: &mut Bencher) { + let mut n = Fr::one(); + let b = -Fr::one(); + bencher.iter(move || { + n -= &b; + }); +} + +#[bench] +fn bench_add_assign(bencher: &mut Bencher) { + let mut n = Fr::one(); + let b = -Fr::one(); + bencher.iter(move || { + n += &b; + }); +} + +#[bench] +fn bench_square_assign(bencher: &mut Bencher) { + let n = Fr::one(); + bencher.iter(move || n.square()); +} + +#[bench] +fn bench_invert(bencher: &mut Bencher) { + let n = Fr::one(); + bencher.iter(move || n.invert()); +} + +#[bench] +fn bench_sqrt(bencher: &mut Bencher) { + let n = Fr::one().double().double(); + bencher.iter(move || n.sqrt()); +} diff --git a/benches/point_bench.rs b/benches/point_bench.rs new file mode 100644 index 000000000..d5b33a939 --- /dev/null +++ b/benches/point_bench.rs @@ -0,0 +1,58 @@ +#![feature(test)] + +extern crate test; + +use jubjub::*; +use test::Bencher; + +// Non-Niels + +#[bench] +fn bench_point_doubling(bencher: &mut Bencher) { + let a = ExtendedPoint::identity(); + bencher.iter(move || a.double()); +} + +#[bench] +fn bench_point_addition(bencher: &mut Bencher) { + let a = ExtendedPoint::identity(); + let b = -ExtendedPoint::identity(); + bencher.iter(move || a + b); +} + +#[bench] +fn bench_point_subtraction(bencher: &mut Bencher) { + let a = ExtendedPoint::identity(); + let b = -ExtendedPoint::identity(); + bencher.iter(move || a + b); +} + +// Niels + +#[bench] +fn bench_cached_point_addition(bencher: &mut Bencher) { + let a = ExtendedPoint::identity(); + let b = ExtendedPoint::identity().to_niels(); + bencher.iter(move || &a + &b); +} + +#[bench] +fn bench_cached_affine_point_subtraction(bencher: &mut Bencher) { + let a = ExtendedPoint::identity(); + let b = AffinePoint::identity().to_niels(); + bencher.iter(move || &a + &b); +} + +#[bench] +fn bench_cached_point_subtraction(bencher: &mut Bencher) { + let a = ExtendedPoint::identity(); + let b = ExtendedPoint::identity().to_niels(); + bencher.iter(move || &a + &b); +} + +#[bench] +fn bench_cached_affine_point_addition(bencher: &mut Bencher) { + let a = ExtendedPoint::identity(); + let b = AffinePoint::identity().to_niels(); + bencher.iter(move || &a + &b); +} diff --git a/doc/derive/.gitignore b/doc/derive/.gitignore new file mode 100644 index 000000000..7c974cf29 --- /dev/null +++ b/doc/derive/.gitignore @@ -0,0 +1 @@ +*.sage.py diff --git a/doc/derive/derive.sage b/doc/derive/derive.sage new file mode 100644 index 000000000..c0c5310bf --- /dev/null +++ b/doc/derive/derive.sage @@ -0,0 +1,32 @@ +q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +Fq = GF(q) + +# We wish to find a Montgomery curve with B = 1 and A the smallest such +# that (A - 2) / 4 is a small integer. +def get_A(n): + return (n * 4) + 2 + +# A = 2 is invalid (singular curve), so we start at i = 1 (A = 6) +i = 1 + +while True: + A = Fq(get_A(i)) + i = i + 1 + + # We also want that A^2 - 4 is nonsquare. + if ((A^2) - 4).is_square(): + continue + + ec = EllipticCurve(Fq, [0, A, 0, 1, 0]) + o = ec.order() + + if (o % 8 == 0): + o = o // 8 + if is_prime(o): + twist = ec.quadratic_twist() + otwist = twist.order() + if (otwist % 4 == 0): + otwist = otwist // 4 + if is_prime(otwist): + print "A = %s" % A + exit(0) diff --git a/doc/evidence/.gitignore b/doc/evidence/.gitignore new file mode 100644 index 000000000..9a0d287f2 --- /dev/null +++ b/doc/evidence/.gitignore @@ -0,0 +1,102 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + diff --git a/doc/evidence/LICENSE b/doc/evidence/LICENSE new file mode 100644 index 000000000..9e181634a --- /dev/null +++ b/doc/evidence/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 The Zcash developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/doc/evidence/README.md b/doc/evidence/README.md new file mode 100644 index 000000000..26b2e36f2 --- /dev/null +++ b/doc/evidence/README.md @@ -0,0 +1,28 @@ +Jubjub supporting evidence +-------------------------- + +This repository contains supporting evidence that the twisted Edwards curve +-x^2 + y^2 = 1 - (10240/10241).x^2.y^2 of rational points over +GF(52435875175126190479447740508185965837690552500527637822603658699938581184513), +[also called "Jubjub"](https://z.cash/technology/jubjub.html), +satisfies the [SafeCurves criteria](https://safecurves.cr.yp.to/index.html). + +The script ``verify.sage`` is based on +[this script from the SafeCurves site](https://safecurves.cr.yp.to/verify.html), +modified + +* to support twisted Edwards curves; +* to generate a file 'primes' containing the primes needed for primality proofs, + if it is not already present; +* to change the directory in which Pocklington proof files are generated + (``proof/`` rather than ``../../../proof``), and to create that directory + if it does not exist. + +Prerequisites: + +* apt-get install sagemath +* pip install sortedcontainers + +Run ``sage verify.sage .``, or ``./run.sh`` to also print out the results. + +Note that the "rigidity" criterion cannot be checked automatically. diff --git a/doc/evidence/a b/doc/evidence/a new file mode 100644 index 000000000..3a2e3f498 --- /dev/null +++ b/doc/evidence/a @@ -0,0 +1 @@ +-1 diff --git a/doc/evidence/d b/doc/evidence/d new file mode 100644 index 000000000..767309a72 --- /dev/null +++ b/doc/evidence/d @@ -0,0 +1 @@ +19257038036680949359750312669786877991949435402254120286184196891950884077233 diff --git a/doc/evidence/l b/doc/evidence/l new file mode 100644 index 000000000..83f92d579 --- /dev/null +++ b/doc/evidence/l @@ -0,0 +1 @@ +6554484396890773809930967563523245729705921265872317281365359162392183254199 diff --git a/doc/evidence/p b/doc/evidence/p new file mode 100644 index 000000000..1dc0557a7 --- /dev/null +++ b/doc/evidence/p @@ -0,0 +1 @@ +52435875175126190479447740508185965837690552500527637822603658699938581184513 diff --git a/doc/evidence/rigid b/doc/evidence/rigid new file mode 100644 index 000000000..e560e4087 --- /dev/null +++ b/doc/evidence/rigid @@ -0,0 +1 @@ +fully rigid diff --git a/doc/evidence/run.sh b/doc/evidence/run.sh new file mode 100644 index 000000000..817f2faf8 --- /dev/null +++ b/doc/evidence/run.sh @@ -0,0 +1,4 @@ +#!/bin/sh +sage verify.sage . +grep -Rn '.' verify-* |grep '^verify-.*:1:' |sed 's/:1:/ = /' + diff --git a/doc/evidence/shape b/doc/evidence/shape new file mode 100644 index 000000000..796f74d09 --- /dev/null +++ b/doc/evidence/shape @@ -0,0 +1 @@ +tedwards diff --git a/doc/evidence/verify.sage b/doc/evidence/verify.sage new file mode 100644 index 000000000..1717c0bc6 --- /dev/null +++ b/doc/evidence/verify.sage @@ -0,0 +1,444 @@ +import os +import sys +from errno import ENOENT, EEXIST +from sortedcontainers import SortedSet + + +def readfile(fn): + fd = open(fn,'r') + r = fd.read() + fd.close() + return r + +def writefile(fn,s): + fd = open(fn,'w') + fd.write(s) + fd.close() + +def expand2(n): + s = "" + + while n != 0: + j = 16 + while 2**j < abs(n): j += 1 + if 2**j - abs(n) > abs(n) - 2**(j-1): j -= 1 + + if abs(abs(n) - 2**j) > 2**(j - 10): + if n > 0: + if s != "": s += " + " + s += str(n) + else: + s += " - " + str(-n) + n = 0 + elif n > 0: + if s != "": s += " + " + s += "2^" + str(j) + n -= 2**j + else: + s += " - 2^" + str(j) + n += 2**j + + return s + +def requirement(fn,istrue): + writefile(fn,str(istrue) + '\n') + return istrue + +def verify(): + try: + os.mkdir('proof') + except OSError as e: + if e.errno != EEXIST: raise + + try: + s = set(map(Integer, readfile('primes').split())) + except IOError, e: + if e.errno != ENOENT: raise + s = set() + + needtofactor = SortedSet() + V = SortedSet() # distinct verified primes + verify_primes(V, s, needtofactor) + verify_pass(V, needtofactor) + + old = V + needtofactor.update(V) + while len(needtofactor) > len(old): + k = len(needtofactor) - len(old) + sys.stdout.write('Factoring %d integer%s' % (k, '' if k == 1 else 's')) + sys.stdout.flush() + for x in needtofactor: + if x not in old: + for (y, z) in factor(x): + s.add(y) + sys.stdout.write('.') + sys.stdout.flush() + + print('') + + old = needtofactor.copy() + verify_primes(V, s, needtofactor) + + writefile('primes', '\n'.join(map(str, s)) + '\n') + writefile('verify-primes', '\n' + + ''.join(('2\n' if v == 2 else + '%s\n' % (v,v)) for v in V) + + '\n') + + verify_pass(V, needtofactor) + + +def verify_primes(V, s, needtofactor): + for n in sorted(s): + if not n.is_prime() or n in V: continue + needtofactor.add(n-1) + if n == 2: + V.add(n) + continue + for trybase in primes(2,10000): + base = Integers(n)(trybase) + if base^(n-1) != 1: continue + proof = 'Primality proof for n = %s:\n' % n + proof += '

Take b = %s.\n' % base + proof += '

b^(n-1) mod n = 1.\n' + f = factor(1) + for v in reversed(V): + if f.prod()^2 <= n: + if n % v == 1: + u = base^((n-1)/v)-1 + if u.is_unit(): + if v == 2: + proof += '

2 is prime.\n' + else: + proof += '

%s is prime.\n' % (v,v) + proof += '
b^((n-1)/%s)-1 mod n = %s, which is a unit, inverse %s.\n' % (v,u,1/u) + f *= factor(v)^(n-1).valuation(v) + if f.prod()^2 <= n: continue + if n % f.prod() != 1: continue + proof += '

(%s) divides n-1.\n' % f + proof += '

(%s)^2 > n.\n' % f + proof += "

n is prime by Pocklington's theorem.\n" + proof += '\n' + writefile('proof/%s.html' % n,proof) + V.add(n) + break + + +def verify_pass(V, needtofactor): + p = Integer(readfile('p')) + k = GF(p) + kz. = k[] + l = Integer(readfile('l')) + x0 = Integer(readfile('x0')) + y0 = Integer(readfile('y0')) + x1 = Integer(readfile('x1')) + y1 = Integer(readfile('y1')) + shape = readfile('shape').strip() + rigid = readfile('rigid').strip() + + safefield = True + safeeq = True + safebase = True + saferho = True + safetransfer = True + safedisc = True + saferigid = True + safeladder = True + safetwist = True + safecomplete = True + safeind = True + + pstatus = 'Unverified' + if not p.is_prime(): pstatus = 'False' + needtofactor.add(p) + if p in V: pstatus = 'True' + if pstatus != 'True': safefield = False + writefile('verify-pisprime',pstatus + '\n') + + pstatus = 'Unverified' + if not l.is_prime(): pstatus = 'False' + needtofactor.add(l) + if l in V: pstatus = 'True' + if pstatus != 'True': safebase = False + writefile('verify-lisprime',pstatus + '\n') + + writefile('expand2-p','= %s\n' % expand2(p)) + writefile('expand2-l','
= %s\n' % expand2(l)) + + writefile('hex-p',hex(p) + '\n') + writefile('hex-l',hex(l) + '\n') + writefile('hex-x0',hex(x0) + '\n') + writefile('hex-x1',hex(x1) + '\n') + writefile('hex-y0',hex(y0) + '\n') + writefile('hex-y1',hex(y1) + '\n') + + gcdlpis1 = gcd(l,p) == 1 + safetransfer &= requirement('verify-gcdlp1',gcdlpis1) + + writefile('verify-movsafe','Unverified\n') + writefile('verify-embeddingdegree','Unverified\n') + if gcdlpis1 and l.is_prime(): + u = Integers(l)(p) + d = l-1 + needtofactor.add(d) + for v in V: + while d % v == 0: d /= v + if d == 1: + d = l-1 + for v in V: + while d % v == 0: + if u^(d/v) != 1: break + d /= v + safetransfer &= requirement('verify-movsafe',(l-1)/d <= 100) + writefile('verify-embeddingdegree','%s
= (l-1)/%s\n' % (d,(l-1)/d)) + + t = p+1-l*round((p+1)/l) + if l^2 > 16*p: + writefile('verify-trace','%s\n' % t) + f = factor(1) + d = (p+1-t)/l + needtofactor.add(d) + for v in V: + while d % v == 0: + d //= v + f *= factor(v) + writefile('verify-cofactor','%s\n' % f) + else: + writefile('verify-trace','Unverified\n') + writefile('verify-cofactor','Unverified\n') + + D = t^2-4*p + needtofactor.add(D) + for v in V: + while D % v^2 == 0: D /= v^2 + if prod([v for v in V if D % v == 0]) != -D: + writefile('verify-disc','Unverified\n') + writefile('verify-discisbig','Unverified\n') + safedisc = False + else: + f = -prod([factor(v) for v in V if D % v == 0]) + if D % 4 != 1: + D *= 4 + f = factor(4) * f + Dbits = (log(-D)/log(2)).numerical_approx() + writefile('verify-disc','%s
= %s
≈ -2^%.1f\n' % (D,f,Dbits)) + safedisc &= requirement('verify-discisbig',D < -2^100) + + pi4 = 0.78539816339744830961566084581987572105 + rho = log(pi4*l)/log(4) + writefile('verify-rho','%.1f\n' % rho) + saferho &= requirement('verify-rhoabove100',rho.numerical_approx() >= 100) + + twistl = 'Unverified' + d = p+1+t + needtofactor.add(d) + for v in V: + while d % v == 0: d /= v + if d == 1: + d = p+1+t + for v in V: + if d % v == 0: + if twistl == 'Unverified' or v > twistl: twistl = v + + writefile('verify-twistl','%s\n' % twistl) + writefile('verify-twistembeddingdegree','Unverified\n') + writefile('verify-twistmovsafe','Unverified\n') + if twistl == 'Unverified': + writefile('hex-twistl','Unverified\n') + writefile('expand2-twistl','Unverified\n') + writefile('verify-twistcofactor','Unverified\n') + writefile('verify-gcdtwistlp1','Unverified\n') + writefile('verify-twistrho','Unverified\n') + safetwist = False + else: + writefile('hex-twistl',hex(twistl) + '\n') + writefile('expand2-twistl','
= %s\n' % expand2(twistl)) + f = factor(1) + d = (p+1+t)/twistl + needtofactor.add(d) + for v in V: + while d % v == 0: + d //= v + f *= factor(v) + writefile('verify-twistcofactor','%s\n' % f) + gcdtwistlpis1 = gcd(twistl,p) == 1 + safetwist &= requirement('verify-gcdtwistlp1',gcdtwistlpis1) + + movsafe = 'Unverified' + embeddingdegree = 'Unverified' + if gcdtwistlpis1 and twistl.is_prime(): + u = Integers(twistl)(p) + d = twistl-1 + needtofactor.add(d) + for v in V: + while d % v == 0: d /= v + if d == 1: + d = twistl-1 + for v in V: + while d % v == 0: + if u^(d/v) != 1: break + d /= v + safetwist &= requirement('verify-twistmovsafe',(twistl-1)/d <= 100) + writefile('verify-twistembeddingdegree',"%s
= (l'-1)/%s\n" % (d,(twistl-1)/d)) + + rho = log(pi4*twistl)/log(4) + writefile('verify-twistrho','%.1f\n' % rho) + safetwist &= requirement('verify-twistrhoabove100',rho.numerical_approx() >= 100) + + precomp = 0 + joint = l + needtofactor.add(p+1-t) + needtofactor.add(p+1+t) + for v in V: + d1 = p+1-t + d2 = p+1+t + while d1 % v == 0 or d2 % v == 0: + if d1 % v == 0: d1 //= v + if d2 % v == 0: d2 //= v + # best case for attack: cyclic; each power is usable + # also assume that kangaroo is as efficient as rho + if v + sqrt(pi4*joint/v) < sqrt(pi4*joint): + precomp += v + joint /= v + + rho = log(precomp + sqrt(pi4 * joint))/log(2) + writefile('verify-jointrho','%.1f\n' % rho) + safetwist &= requirement('verify-jointrhoabove100',rho.numerical_approx() >= 100) + + + x0 = k(x0) + y0 = k(y0) + x1 = k(x1) + y1 = k(y1) + + if shape in ('edwards', 'tedwards'): + d = Integer(readfile('d')) + a = 1 + if shape == 'tedwards': + a = Integer(readfile('a')) + + writefile('verify-shape','Twisted Edwards\n') + writefile('verify-equation','%sx^2+y^2 = 1%+dx^2y^2\n' % (a, d)) + if a == 1: + writefile('verify-shape','Edwards\n') + writefile('verify-equation','x^2+y^2 = 1%+dx^2y^2\n' % d) + + a = k(a) + d = k(d) + elliptic = a*d*(a-d) + level0 = a*x0^2+y0^2-1-d*x0^2*y0^2 + level1 = a*x1^2+y1^2-1-d*x1^2*y1^2 + + if shape == 'montgomery': + writefile('verify-shape','Montgomery\n') + A = Integer(readfile('A')) + B = Integer(readfile('B')) + equation = '%sy^2 = x^3%+dx^2+x' % (B,A) + if B == 1: + equation = 'y^2 = x^3%+dx^2+x' % A + writefile('verify-equation',equation + '\n') + + A = k(A) + B = k(B) + elliptic = B*(A^2-4) + level0 = B*y0^2-x0^3-A*x0^2-x0 + level1 = B*y1^2-x1^3-A*x1^2-x1 + + if shape == 'shortw': + writefile('verify-shape','short Weierstrass\n') + a = Integer(readfile('a')) + b = Integer(readfile('b')) + writefile('verify-equation','y^2 = x^3%+dx%+d\n' % (a,b)) + + a = k(a) + b = k(b) + elliptic = 4*a^3+27*b^2 + level0 = y0^2-x0^3-a*x0-b + level1 = y1^2-x1^3-a*x1-b + + writefile('verify-elliptic',str(elliptic) + '\n') + safeeq &= requirement('verify-iselliptic',elliptic != 0) + safebase &= requirement('verify-isoncurve0',level0 == 0) + safebase &= requirement('verify-isoncurve1',level1 == 0) + + if shape in ('edwards', 'tedwards'): + A = 2*(a+d)/(a-d) + B = 4/(a-d) + x0,y0 = (1+y0)/(1-y0),((1+y0)/(1-y0))/x0 + x1,y1 = (1+y1)/(1-y1),((1+y1)/(1-y1))/x1 + shape = 'montgomery' + + if shape == 'montgomery': + a = (3-A^2)/(3*B^2) + b = (2*A^3-9*A)/(27*B^3) + x0,y0 = (x0+A/3)/B,y0/B + x1,y1 = (x1+A/3)/B,y1/B + shape = 'shortw' + + try: + E = EllipticCurve([a,b]) + numorder2 = 0 + numorder4 = 0 + for P in E(0).division_points(4): + if P != 0 and 2*P == 0: + numorder2 += 1 + if 2*P != 0 and 4*P == 0: + numorder4 += 1 + writefile('verify-numorder2',str(numorder2) + '\n') + writefile('verify-numorder4',str(numorder4) + '\n') + completesingle = False + completemulti = False + if numorder4 == 2 and numorder2 == 1: + # complete edwards form, and montgomery with unique point of order 2 + completesingle = True + completemulti = True + # should extend this to allow complete twisted hessian + safecomplete &= requirement('verify-completesingle',completesingle) + safecomplete &= requirement('verify-completemulti',completemulti) + safecomplete &= requirement('verify-ltimesbase1is0',l * E([x1,y1]) == 0) + writefile('verify-ltimesbase1',str(l * E([x1,y1])) + '\n') + writefile('verify-cofactorbase01',str(((p+1-t)//l) * E([x0,y0]) == E([x1,y1])) + '\n') + except: + writefile('verify-numorder2','Unverified\n') + writefile('verify-numorder4','Unverified\n') + writefile('verify-ltimesbase1','Unverified\n') + writefile('verify-cofactorbase01','Unverified\n') + safecomplete = False + + montladder = False + for r,e in (z^3+a*z+b).roots(): + if (3*r^2+a).is_square(): + montladder = True + safeladder &= requirement('verify-montladder',montladder) + + indistinguishability = False + elligator2 = False + if (p+1-t) % 2 == 0: + if b != 0: + indistinguishability = True + elligator2 = True + safeind &= requirement('verify-indistinguishability',indistinguishability) + writefile('verify-ind-notes','Elligator 2: %s.\n' % ['No','Yes'][elligator2]) + + saferigid &= (rigid == 'fully rigid' or rigid == 'somewhat rigid') + + safecurve = True + safecurve &= requirement('verify-safefield',safefield) + safecurve &= requirement('verify-safeeq',safeeq) + safecurve &= requirement('verify-safebase',safebase) + safecurve &= requirement('verify-saferho',saferho) + safecurve &= requirement('verify-safetransfer',safetransfer) + safecurve &= requirement('verify-safedisc',safedisc) + safecurve &= requirement('verify-saferigid',saferigid) + safecurve &= requirement('verify-safeladder',safeladder) + safecurve &= requirement('verify-safetwist',safetwist) + safecurve &= requirement('verify-safecomplete',safecomplete) + safecurve &= requirement('verify-safeind',safeind) + requirement('verify-safecurve',safecurve) + +originaldir = os.open('.',os.O_RDONLY) +for i in range(1,len(sys.argv)): + os.fchdir(originaldir) + os.chdir(sys.argv[i]) + verify() + diff --git a/doc/evidence/x0 b/doc/evidence/x0 new file mode 100644 index 000000000..3b2097a36 --- /dev/null +++ b/doc/evidence/x0 @@ -0,0 +1 @@ +11076627216317271660298050606127911965867021807910416450833192264015104452986 diff --git a/doc/evidence/x1 b/doc/evidence/x1 new file mode 100644 index 000000000..c8c8fc308 --- /dev/null +++ b/doc/evidence/x1 @@ -0,0 +1 @@ +8076246640662884909881801758704306714034609987455869804520522091855516602923 diff --git a/doc/evidence/y0 b/doc/evidence/y0 new file mode 100644 index 000000000..b47cd2764 --- /dev/null +++ b/doc/evidence/y0 @@ -0,0 +1 @@ +44412834903739585386157632289020980010620626017712148233229312325549216099227 diff --git a/doc/evidence/y1 b/doc/evidence/y1 new file mode 100644 index 000000000..a46479fb5 --- /dev/null +++ b/doc/evidence/y1 @@ -0,0 +1 @@ +13262374693698910701929044844600465831413122818447359594527400194675274060458 diff --git a/src/fr.rs b/src/fr.rs new file mode 100644 index 000000000..4495e3bd7 --- /dev/null +++ b/src/fr.rs @@ -0,0 +1,1023 @@ +//! This module provides an implementation of the Jubjub scalar field $\mathbb{F}_r$ +//! where `r = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7` + +use core::convert::TryInto; +use core::fmt; +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +use crate::util::{adc, mac, sbb}; + +/// Represents an element of the scalar field $\mathbb{F}_r$ of the Jubjub elliptic +/// curve construction. +// The internal representation of this type is four 64-bit unsigned +// integers in little-endian order. Elements of Fr are always in +// Montgomery form; i.e., Fr(a) = aR mod r, with R = 2^256. +#[derive(Clone, Copy, Eq)] +pub struct Fr(pub(crate) [u64; 4]); + +impl fmt::Debug for Fr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let tmp = self.to_bytes(); + write!(f, "0x")?; + for &b in tmp.iter().rev() { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +impl From for Fr { + fn from(val: u64) -> Fr { + Fr([val, 0, 0, 0]) * R2 + } +} + +impl ConstantTimeEq for Fr { + fn ct_eq(&self, other: &Self) -> Choice { + self.0[0].ct_eq(&other.0[0]) + & self.0[1].ct_eq(&other.0[1]) + & self.0[2].ct_eq(&other.0[2]) + & self.0[3].ct_eq(&other.0[3]) + } +} + +impl PartialEq for Fr { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl ConditionallySelectable for Fr { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Fr([ + 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), + ]) + } +} + +/// Constant representing the modulus +/// r = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7 +pub const MODULUS: Fr = Fr([ + 0xd0970e5ed6f72cb7, + 0xa6682093ccc81082, + 0x06673b0101343b00, + 0x0e7db4ea6533afa9, +]); + +impl<'a> Neg for &'a Fr { + type Output = Fr; + + #[inline] + fn neg(self) -> Fr { + self.neg() + } +} + +impl Neg for Fr { + type Output = Fr; + + #[inline] + fn neg(self) -> Fr { + -&self + } +} + +impl<'a, 'b> Sub<&'b Fr> for &'a Fr { + type Output = Fr; + + #[inline] + fn sub(self, rhs: &'b Fr) -> Fr { + self.sub(rhs) + } +} + +impl<'a, 'b> Add<&'b Fr> for &'a Fr { + type Output = Fr; + + #[inline] + fn add(self, rhs: &'b Fr) -> Fr { + self.add(rhs) + } +} + +impl<'a, 'b> Mul<&'b Fr> for &'a Fr { + type Output = Fr; + + #[inline] + fn mul(self, rhs: &'b Fr) -> Fr { + // Schoolbook multiplication + + self.mul(rhs) + } +} + +impl_binops_additive!(Fr, Fr); +impl_binops_multiplicative!(Fr, Fr); + +/// INV = -(r^{-1} mod 2^64) mod 2^64 +const INV: u64 = 0x1ba3a358ef788ef9; + +/// R = 2^256 mod r +const R: Fr = Fr([ + 0x25f80bb3b99607d9, + 0xf315d62f66b6e750, + 0x932514eeeb8814f4, + 0x09a6fc6f479155c6, +]); + +/// R^2 = 2^512 mod r +const R2: Fr = Fr([ + 0x67719aa495e57731, + 0x51b0cef09ce3fc26, + 0x69dab7fac026e9a5, + 0x04f6547b8d127688, +]); + +/// R^2 = 2^768 mod r +const R3: Fr = Fr([ + 0xe0d6c6563d830544, + 0x323e3883598d0f85, + 0xf0fea3004c2e2ba8, + 0x05874f84946737ec, +]); + +impl Default for Fr { + fn default() -> Self { + Self::zero() + } +} + +impl Fr { + /// Returns zero, the additive identity. + #[inline] + pub const fn zero() -> Fr { + Fr([0, 0, 0, 0]) + } + + /// Returns one, the multiplicative identity. + #[inline] + pub const fn one() -> Fr { + R + } + + /// Doubles this field element. + #[inline] + pub const fn double(&self) -> Fr { + self.add(self) + } + + /// Attempts to convert a little-endian byte representation of + /// a field element into an element of `Fr`, failing if the input + /// is not canonical (is not smaller than r). + pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { + let mut tmp = Fr([0, 0, 0, 0]); + + tmp.0[0] = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); + tmp.0[1] = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); + tmp.0[2] = u64::from_le_bytes(bytes[16..24].try_into().unwrap()); + tmp.0[3] = u64::from_le_bytes(bytes[24..32].try_into().unwrap()); + + // Try to subtract the modulus + let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0); + let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow); + let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow); + let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow); + + // If the element is smaller than MODULUS then the + // subtraction will underflow, producing a borrow value + // of 0xffff...ffff. Otherwise, it'll be zero. + let is_some = (borrow as u8) & 1; + + // Convert to Montgomery form by computing + // (a.R^0 * R^2) / R = a.R + tmp *= &R2; + + CtOption::new(tmp, Choice::from(is_some)) + } + + /// Converts an element of `Fr` into a byte representation in + /// little-endian byte order. + pub fn to_bytes(&self) -> [u8; 32] { + // Turn into canonical form by computing + // (a.R) / R = a + let tmp = Fr::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); + + let mut res = [0; 32]; + res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + + res + } + + /// Converts a 512-bit little endian integer into + /// an element of Fr by reducing modulo r. + pub fn from_bytes_wide(bytes: &[u8; 64]) -> Fr { + Fr::from_u512([ + u64::from_le_bytes(bytes[0..8].try_into().unwrap()), + u64::from_le_bytes(bytes[8..16].try_into().unwrap()), + u64::from_le_bytes(bytes[16..24].try_into().unwrap()), + u64::from_le_bytes(bytes[24..32].try_into().unwrap()), + u64::from_le_bytes(bytes[32..40].try_into().unwrap()), + u64::from_le_bytes(bytes[40..48].try_into().unwrap()), + u64::from_le_bytes(bytes[48..56].try_into().unwrap()), + u64::from_le_bytes(bytes[56..64].try_into().unwrap()), + ]) + } + + fn from_u512(limbs: [u64; 8]) -> Fr { + // We reduce an arbitrary 512-bit number by decomposing it into two 256-bit digits + // with the higher bits multiplied by 2^256. Thus, we perform two reductions + // + // 1. the lower bits are multiplied by R^2, as normal + // 2. the upper bits are multiplied by R^2 * 2^256 = R^3 + // + // and computing their sum in the field. It remains to see that arbitrary 256-bit + // numbers can be placed into Montgomery form safely using the reduction. The + // reduction works so long as the product is less than R=2^256 multipled by + // the modulus. This holds because for any `c` smaller than the modulus, we have + // that (2^256 - 1)*c is an acceptable product for the reduction. Therefore, the + // reduction always works so long as `c` is in the field; in this case it is either the + // constant `R2` or `R3`. + let d0 = Fr([limbs[0], limbs[1], limbs[2], limbs[3]]); + let d1 = Fr([limbs[4], limbs[5], limbs[6], limbs[7]]); + // Convert to Montgomery form + d0 * R2 + d1 * R3 + } + + /// Converts from an integer represented in little endian + /// into its (congruent) `Fr` representation. + pub const fn from_raw(val: [u64; 4]) -> Self { + (&Fr(val)).mul(&R2) + } + + /// Squares this element. + #[inline] + pub const fn square(&self) -> Fr { + let (r1, carry) = mac(0, self.0[0], self.0[1], 0); + let (r2, carry) = mac(0, self.0[0], self.0[2], carry); + let (r3, r4) = mac(0, self.0[0], self.0[3], carry); + + let (r3, carry) = mac(r3, self.0[1], self.0[2], 0); + let (r4, r5) = mac(r4, self.0[1], self.0[3], carry); + + let (r5, r6) = mac(r5, self.0[2], self.0[3], 0); + + let r7 = r6 >> 63; + let r6 = (r6 << 1) | (r5 >> 63); + let r5 = (r5 << 1) | (r4 >> 63); + let r4 = (r4 << 1) | (r3 >> 63); + let r3 = (r3 << 1) | (r2 >> 63); + let r2 = (r2 << 1) | (r1 >> 63); + let r1 = r1 << 1; + + let (r0, carry) = mac(0, self.0[0], self.0[0], 0); + let (r1, carry) = adc(0, r1, carry); + let (r2, carry) = mac(r2, self.0[1], self.0[1], carry); + let (r3, carry) = adc(0, r3, carry); + let (r4, carry) = mac(r4, self.0[2], self.0[2], carry); + let (r5, carry) = adc(0, r5, carry); + let (r6, carry) = mac(r6, self.0[3], self.0[3], carry); + let (r7, _) = adc(0, r7, carry); + + Fr::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7) + } + + /// Computes the square root of this element, if it exists. + pub fn sqrt(&self) -> CtOption { + // Because r = 3 (mod 4) + // sqrt can be done with only one exponentiation, + // via the computation of self^((r + 1) // 4) (mod r) + let sqrt = self.pow_vartime(&[ + 0xb425c397b5bdcb2e, + 0x299a0824f3320420, + 0x4199cec0404d0ec0, + 0x039f6d3a994cebea, + ]); + + CtOption::new( + sqrt, + (&sqrt * &sqrt).ct_eq(self), // Only return Some if it's the square root. + ) + } + + /// Exponentiates `self` by `by`, where `by` is a + /// little-endian order integer exponent. + pub fn pow(&self, by: &[u64; 4]) -> Self { + let mut res = Self::one(); + for e in by.iter().rev() { + for i in (0..64).rev() { + res = res.square(); + let mut tmp = res; + tmp.mul_assign(self); + res.conditional_assign(&tmp, (((*e >> i) & 0x1) as u8).into()); + } + } + res + } + + /// Exponentiates `self` by `by`, where `by` is a + /// little-endian order integer exponent. + /// + /// **This operation is variable time with respect + /// to the exponent.** If the exponent is fixed, + /// this operation is effectively constant time. + pub fn pow_vartime(&self, by: &[u64; 4]) -> Self { + let mut res = Self::one(); + for e in by.iter().rev() { + for i in (0..64).rev() { + res = res.square(); + + if ((*e >> i) & 1) == 1 { + res.mul_assign(self); + } + } + } + res + } + + /// Computes the multiplicative inverse of this element, + /// failing if the element is zero. + pub fn invert(&self) -> CtOption { + #[inline(always)] + fn square_assign_multi(n: &mut Fr, num_times: usize) { + for _ in 0..num_times { + *n = n.square(); + } + } + // found using https://github.com/kwantam/addchain + let mut t1 = self.square(); + let mut t0 = t1.square(); + let mut t3 = t0 * &t1; + let t6 = t3 * self; + let t7 = t6 * &t1; + let t12 = t7 * &t3; + let t13 = t12 * &t0; + let t16 = t12 * &t3; + let t2 = t13 * &t3; + let t15 = t16 * &t3; + let t19 = t2 * &t0; + let t9 = t15 * &t3; + let t18 = t9 * &t3; + let t14 = t18 * &t1; + let t4 = t18 * &t0; + let t8 = t18 * &t3; + let t17 = t14 * &t3; + let t11 = t8 * &t3; + t1 = t17 * &t3; + let t5 = t11 * &t3; + t3 = t5 * &t0; + t0 = t5.square(); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t3); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t8); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t19); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t13); + square_assign_multi(&mut t0, 8); + t0.mul_assign(&t14); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t18); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t17); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t16); + square_assign_multi(&mut t0, 3); + t0.mul_assign(self); + square_assign_multi(&mut t0, 11); + t0.mul_assign(&t11); + square_assign_multi(&mut t0, 8); + t0.mul_assign(&t5); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t15); + square_assign_multi(&mut t0, 8); + t0.mul_assign(self); + square_assign_multi(&mut t0, 12); + t0.mul_assign(&t13); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t9); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t15); + square_assign_multi(&mut t0, 14); + t0.mul_assign(&t14); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t13); + square_assign_multi(&mut t0, 2); + t0.mul_assign(self); + square_assign_multi(&mut t0, 6); + t0.mul_assign(self); + square_assign_multi(&mut t0, 9); + t0.mul_assign(&t7); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t12); + square_assign_multi(&mut t0, 8); + t0.mul_assign(&t11); + square_assign_multi(&mut t0, 3); + t0.mul_assign(self); + square_assign_multi(&mut t0, 12); + t0.mul_assign(&t9); + square_assign_multi(&mut t0, 11); + t0.mul_assign(&t8); + square_assign_multi(&mut t0, 8); + t0.mul_assign(&t7); + square_assign_multi(&mut t0, 4); + t0.mul_assign(&t6); + square_assign_multi(&mut t0, 10); + t0.mul_assign(&t5); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t3); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t4); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t3); + square_assign_multi(&mut t0, 5); + t0.mul_assign(&t2); + square_assign_multi(&mut t0, 6); + t0.mul_assign(&t2); + square_assign_multi(&mut t0, 7); + t0.mul_assign(&t1); + + CtOption::new(t0, !self.ct_eq(&Self::zero())) + } + + #[inline] + const fn montgomery_reduce( + r0: u64, + r1: u64, + r2: u64, + r3: u64, + r4: u64, + r5: u64, + r6: u64, + r7: u64, + ) -> Self { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = r0.wrapping_mul(INV); + let (_, carry) = mac(r0, k, MODULUS.0[0], 0); + let (r1, carry) = mac(r1, k, MODULUS.0[1], carry); + let (r2, carry) = mac(r2, k, MODULUS.0[2], carry); + let (r3, carry) = mac(r3, k, MODULUS.0[3], carry); + let (r4, carry2) = adc(r4, 0, carry); + + let k = r1.wrapping_mul(INV); + let (_, carry) = mac(r1, k, MODULUS.0[0], 0); + let (r2, carry) = mac(r2, k, MODULUS.0[1], carry); + let (r3, carry) = mac(r3, k, MODULUS.0[2], carry); + let (r4, carry) = mac(r4, k, MODULUS.0[3], carry); + let (r5, carry2) = adc(r5, carry2, carry); + + let k = r2.wrapping_mul(INV); + let (_, carry) = mac(r2, k, MODULUS.0[0], 0); + let (r3, carry) = mac(r3, k, MODULUS.0[1], carry); + let (r4, carry) = mac(r4, k, MODULUS.0[2], carry); + let (r5, carry) = mac(r5, k, MODULUS.0[3], carry); + let (r6, carry2) = adc(r6, carry2, carry); + + let k = r3.wrapping_mul(INV); + let (_, carry) = mac(r3, k, MODULUS.0[0], 0); + let (r4, carry) = mac(r4, k, MODULUS.0[1], carry); + let (r5, carry) = mac(r5, k, MODULUS.0[2], carry); + let (r6, carry) = mac(r6, k, MODULUS.0[3], carry); + let (r7, _) = adc(r7, carry2, carry); + + // Result may be within MODULUS of the correct value + (&Fr([r4, r5, r6, r7])).sub(&MODULUS) + } + + /// Multiplies this element by another element + #[inline] + pub const fn mul(&self, rhs: &Self) -> Self { + // Schoolbook multiplication + + let (r0, carry) = mac(0, self.0[0], rhs.0[0], 0); + let (r1, carry) = mac(0, self.0[0], rhs.0[1], carry); + let (r2, carry) = mac(0, self.0[0], rhs.0[2], carry); + let (r3, r4) = mac(0, self.0[0], rhs.0[3], carry); + + let (r1, carry) = mac(r1, self.0[1], rhs.0[0], 0); + let (r2, carry) = mac(r2, self.0[1], rhs.0[1], carry); + let (r3, carry) = mac(r3, self.0[1], rhs.0[2], carry); + let (r4, r5) = mac(r4, self.0[1], rhs.0[3], carry); + + let (r2, carry) = mac(r2, self.0[2], rhs.0[0], 0); + let (r3, carry) = mac(r3, self.0[2], rhs.0[1], carry); + let (r4, carry) = mac(r4, self.0[2], rhs.0[2], carry); + let (r5, r6) = mac(r5, self.0[2], rhs.0[3], carry); + + let (r3, carry) = mac(r3, self.0[3], rhs.0[0], 0); + let (r4, carry) = mac(r4, self.0[3], rhs.0[1], carry); + let (r5, carry) = mac(r5, self.0[3], rhs.0[2], carry); + let (r6, r7) = mac(r6, self.0[3], rhs.0[3], carry); + + Fr::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7) + } + + /// Subtracts another element from this element. + #[inline] + pub const fn sub(&self, rhs: &Self) -> Self { + let (d0, borrow) = sbb(self.0[0], rhs.0[0], 0); + let (d1, borrow) = sbb(self.0[1], rhs.0[1], borrow); + let (d2, borrow) = sbb(self.0[2], rhs.0[2], borrow); + let (d3, borrow) = sbb(self.0[3], rhs.0[3], borrow); + + // If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise + // borrow = 0x000...000. Thus, we use it as a mask to conditionally add the modulus. + let (d0, carry) = adc(d0, MODULUS.0[0] & borrow, 0); + let (d1, carry) = adc(d1, MODULUS.0[1] & borrow, carry); + let (d2, carry) = adc(d2, MODULUS.0[2] & borrow, carry); + let (d3, _) = adc(d3, MODULUS.0[3] & borrow, carry); + + Fr([d0, d1, d2, d3]) + } + + /// Adds this element to another element. + #[inline] + pub const fn add(&self, rhs: &Self) -> Self { + let (d0, carry) = adc(self.0[0], rhs.0[0], 0); + let (d1, carry) = adc(self.0[1], rhs.0[1], carry); + let (d2, carry) = adc(self.0[2], rhs.0[2], carry); + let (d3, _) = adc(self.0[3], rhs.0[3], carry); + + // Attempt to subtract the modulus, to ensure the value + // is smaller than the modulus. + (&Fr([d0, d1, d2, d3])).sub(&MODULUS) + } + + /// Negates this element. + #[inline] + pub const fn neg(&self) -> Self { + // Subtract `self` from `MODULUS` to negate. Ignore the final + // borrow because it cannot underflow; self is guaranteed to + // be in the field. + let (d0, borrow) = sbb(MODULUS.0[0], self.0[0], 0); + let (d1, borrow) = sbb(MODULUS.0[1], self.0[1], borrow); + let (d2, borrow) = sbb(MODULUS.0[2], self.0[2], borrow); + let (d3, _) = sbb(MODULUS.0[3], self.0[3], borrow); + + // `tmp` could be `MODULUS` if `self` was zero. Create a mask that is + // zero if `self` was zero, and `u64::max_value()` if self was nonzero. + let mask = (((self.0[0] | self.0[1] | self.0[2] | self.0[3]) == 0) as u64).wrapping_sub(1); + + Fr([d0 & mask, d1 & mask, d2 & mask, d3 & mask]) + } +} + +impl<'a> From<&'a Fr> for [u8; 32] { + fn from(value: &'a Fr) -> [u8; 32] { + value.to_bytes() + } +} + +#[test] +fn test_inv() { + // Compute -(r^{-1} mod 2^64) mod 2^64 by exponentiating + // by totient(2**64) - 1 + + let mut inv = 1u64; + for _ in 0..63 { + inv = inv.wrapping_mul(inv); + inv = inv.wrapping_mul(MODULUS.0[0]); + } + inv = inv.wrapping_neg(); + + assert_eq!(inv, INV); +} + +#[test] +fn test_debug() { + assert_eq!( + format!("{:?}", Fr::zero()), + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + assert_eq!( + format!("{:?}", Fr::one()), + "0x0000000000000000000000000000000000000000000000000000000000000001" + ); + assert_eq!( + format!("{:?}", R2), + "0x09a6fc6f479155c6932514eeeb8814f4f315d62f66b6e75025f80bb3b99607d9" + ); +} + +#[test] +fn test_equality() { + assert_eq!(Fr::zero(), Fr::zero()); + assert_eq!(Fr::one(), Fr::one()); + assert_eq!(R2, R2); + + assert!(Fr::zero() != Fr::one()); + assert!(Fr::one() != R2); +} + +#[test] +fn test_to_bytes() { + assert_eq!( + Fr::zero().to_bytes(), + [ + 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 + ] + ); + + assert_eq!( + Fr::one().to_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 + ] + ); + + assert_eq!( + R2.to_bytes(), + [ + 217, 7, 150, 185, 179, 11, 248, 37, 80, 231, 182, 102, 47, 214, 21, 243, 244, 20, 136, + 235, 238, 20, 37, 147, 198, 85, 145, 71, 111, 252, 166, 9 + ] + ); + + assert_eq!( + (-&Fr::one()).to_bytes(), + [ + 182, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ] + ); +} + +#[test] +fn test_from_bytes() { + assert_eq!( + Fr::from_bytes(&[ + 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 + ]) + .unwrap(), + Fr::zero() + ); + + assert_eq!( + Fr::from_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 + ]) + .unwrap(), + Fr::one() + ); + + assert_eq!( + Fr::from_bytes(&[ + 217, 7, 150, 185, 179, 11, 248, 37, 80, 231, 182, 102, 47, 214, 21, 243, 244, 20, 136, + 235, 238, 20, 37, 147, 198, 85, 145, 71, 111, 252, 166, 9 + ]) + .unwrap(), + R2 + ); + + // -1 should work + assert!( + Fr::from_bytes(&[ + 182, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ]) + .is_some() + .unwrap_u8() + == 1 + ); + + // modulus is invalid + assert!( + Fr::from_bytes(&[ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + + // Anything larger than the modulus is invalid + assert!( + Fr::from_bytes(&[ + 184, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + + assert!( + Fr::from_bytes(&[ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 104, 6, 169, 175, 51, 101, 234, 180, 125, 14 + ]) + .is_none() + .unwrap_u8() + == 1 + ); + + assert!( + Fr::from_bytes(&[ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 15 + ]) + .is_none() + .unwrap_u8() + == 1 + ); +} + +#[test] +fn test_from_u512_zero() { + assert_eq!( + Fr::zero(), + Fr::from_u512([ + MODULUS.0[0], + MODULUS.0[1], + MODULUS.0[2], + MODULUS.0[3], + 0, + 0, + 0, + 0 + ]) + ); +} + +#[test] +fn test_from_u512_r() { + assert_eq!(R, Fr::from_u512([1, 0, 0, 0, 0, 0, 0, 0])); +} + +#[test] +fn test_from_u512_r2() { + assert_eq!(R2, Fr::from_u512([0, 0, 0, 0, 1, 0, 0, 0])); +} + +#[test] +fn test_from_u512_max() { + let max_u64 = 0xffffffffffffffff; + assert_eq!( + R3 - R, + Fr::from_u512([max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64]) + ); +} + +#[test] +fn test_from_bytes_wide_r2() { + assert_eq!( + R2, + Fr::from_bytes_wide(&[ + 217, 7, 150, 185, 179, 11, 248, 37, 80, 231, 182, 102, 47, 214, 21, 243, 244, 20, 136, + 235, 238, 20, 37, 147, 198, 85, 145, 71, 111, 252, 166, 9, 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, + ]) + ); +} + +#[test] +fn test_from_bytes_wide_negative_one() { + assert_eq!( + -&Fr::one(), + Fr::from_bytes_wide(&[ + 182, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, + 1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14, 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, + ]) + ); +} + +#[test] +fn test_from_bytes_wide_maximum() { + assert_eq!( + Fr([ + 0x8b75c9015ae42a22, + 0xe59082e7bf9e38b8, + 0x6440c91261da51b3, + 0xa5e07ffb20991cf + ]), + Fr::from_bytes_wide(&[0xff; 64]) + ); +} + +#[test] +fn test_zero() { + assert_eq!(Fr::zero(), -&Fr::zero()); + assert_eq!(Fr::zero(), Fr::zero() + Fr::zero()); + assert_eq!(Fr::zero(), Fr::zero() - Fr::zero()); + assert_eq!(Fr::zero(), Fr::zero() * Fr::zero()); +} + +#[cfg(test)] +const LARGEST: Fr = Fr([ + 0xd0970e5ed6f72cb6, + 0xa6682093ccc81082, + 0x06673b0101343b00, + 0x0e7db4ea6533afa9, +]); + +#[test] +fn test_addition() { + let mut tmp = LARGEST; + tmp += &LARGEST; + + assert_eq!( + tmp, + Fr([ + 0xd0970e5ed6f72cb5, + 0xa6682093ccc81082, + 0x06673b0101343b00, + 0x0e7db4ea6533afa9 + ]) + ); + + let mut tmp = LARGEST; + tmp += &Fr([1, 0, 0, 0]); + + assert_eq!(tmp, Fr::zero()); +} + +#[test] +fn test_negation() { + let tmp = -&LARGEST; + + assert_eq!(tmp, Fr([1, 0, 0, 0])); + + let tmp = -&Fr::zero(); + assert_eq!(tmp, Fr::zero()); + let tmp = -&Fr([1, 0, 0, 0]); + assert_eq!(tmp, LARGEST); +} + +#[test] +fn test_subtraction() { + let mut tmp = LARGEST; + tmp -= &LARGEST; + + assert_eq!(tmp, Fr::zero()); + + let mut tmp = Fr::zero(); + tmp -= &LARGEST; + + let mut tmp2 = MODULUS; + tmp2 -= &LARGEST; + + assert_eq!(tmp, tmp2); +} + +#[test] +fn test_multiplication() { + let mut cur = LARGEST; + + for _ in 0..100 { + let mut tmp = cur; + tmp *= &cur; + + let mut tmp2 = Fr::zero(); + for b in cur + .to_bytes() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| ((byte >> i) & 1u8) == 1u8)) + { + let tmp3 = tmp2; + tmp2.add_assign(&tmp3); + + if b { + tmp2.add_assign(&cur); + } + } + + assert_eq!(tmp, tmp2); + + cur.add_assign(&LARGEST); + } +} + +#[test] +fn test_squaring() { + let mut cur = LARGEST; + + for _ in 0..100 { + let mut tmp = cur; + tmp = tmp.square(); + + let mut tmp2 = Fr::zero(); + for b in cur + .to_bytes() + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| ((byte >> i) & 1u8) == 1u8)) + { + let tmp3 = tmp2; + tmp2.add_assign(&tmp3); + + if b { + tmp2.add_assign(&cur); + } + } + + assert_eq!(tmp, tmp2); + + cur.add_assign(&LARGEST); + } +} + +#[test] +fn test_inversion() { + assert_eq!(Fr::zero().invert().is_none().unwrap_u8(), 1); + assert_eq!(Fr::one().invert().unwrap(), Fr::one()); + assert_eq!((-&Fr::one()).invert().unwrap(), -&Fr::one()); + + let mut tmp = R2; + + for _ in 0..100 { + let mut tmp2 = tmp.invert().unwrap(); + tmp2.mul_assign(&tmp); + + assert_eq!(tmp2, Fr::one()); + + tmp.add_assign(&R2); + } +} + +#[test] +fn test_invert_is_pow() { + let r_minus_2 = [ + 0xd0970e5ed6f72cb5, + 0xa6682093ccc81082, + 0x06673b0101343b00, + 0x0e7db4ea6533afa9, + ]; + + let mut r1 = R; + let mut r2 = R; + let mut r3 = R; + + for _ in 0..100 { + r1 = r1.invert().unwrap(); + r2 = r2.pow_vartime(&r_minus_2); + r3 = r3.pow(&r_minus_2); + + assert_eq!(r1, r2); + assert_eq!(r2, r3); + // Add R so we check something different next time around + r1.add_assign(&R); + r2 = r1; + r3 = r1; + } +} + +#[test] +fn test_sqrt() { + let mut square = Fr([ + // r - 2 + 0xd0970e5ed6f72cb5, + 0xa6682093ccc81082, + 0x06673b0101343b00, + 0x0e7db4ea6533afa9, + ]); + + let mut none_count = 0; + + for _ in 0..100 { + let square_root = square.sqrt(); + if square_root.is_none().unwrap_u8() == 1 { + none_count += 1; + } else { + assert_eq!(square_root.unwrap() * square_root.unwrap(), square); + } + square -= Fr::one(); + } + + assert_eq!(47, none_count); +} + +#[test] +fn test_from_raw() { + assert_eq!( + Fr::from_raw([ + 0x25f80bb3b99607d8, + 0xf315d62f66b6e750, + 0x932514eeeb8814f4, + 0x9a6fc6f479155c6 + ]), + Fr::from_raw([0xffffffffffffffff; 4]) + ); + + assert_eq!(Fr::from_raw(MODULUS.0), Fr::zero()); + + assert_eq!(Fr::from_raw([1, 0, 0, 0]), R); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..841948796 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1323 @@ +//! This crate provides an implementation of the **Jubjub** elliptic curve and its associated +//! field arithmetic. See [`README.md`](https://github.com/zkcrypto/jubjub/blob/master/README.md) for more details about Jubjub. +//! +//! # API +//! +//! * `AffinePoint` / `ExtendedPoint` which are implementations of Jubjub group arithmetic +//! * `AffineNielsPoint` / `ExtendedNielsPoint` which are pre-processed Jubjub points +//! * `Fq`, which is the base field of Jubjub +//! * `Fr`, which is the scalar field of Jubjub +//! * `batch_normalize` for converting many `ExtendedPoint`s into `AffinePoint`s efficiently. +//! +//! # Constant Time +//! +//! All operations are constant time unless explicitly noted; these functions will contain +//! "vartime" in their name and they will be documented as variable time. +//! +//! This crate uses the `subtle` crate to perform constant-time operations. + +#![no_std] +// Catch documentation errors caused by code changes. +#![deny(intra_doc_link_resolution_failure)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![deny(unsafe_code)] +// This lint is described at +// https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl +// In our library, some of the arithmetic will necessarily involve various binary +// operators, and so this lint is triggered unnecessarily. +#![allow(clippy::suspicious_arithmetic_impl)] + +#[cfg(test)] +#[macro_use] +extern crate std; + +use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +#[macro_use] +mod util; + +mod fr; +pub use bls12_381::Scalar as Fq; +pub use fr::Fr; + +const FR_MODULUS_BYTES: [u8; 32] = [ + 183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52, 1, 1, 59, + 103, 6, 169, 175, 51, 101, 234, 180, 125, 14, +]; + +/// This represents a Jubjub point in the affine `(u, v)` +/// coordinates. +#[derive(Clone, Copy, Debug)] +pub struct AffinePoint { + u: Fq, + v: Fq, +} + +impl Neg for AffinePoint { + type Output = AffinePoint; + + /// This computes the negation of a point `P = (u, v)` + /// as `-P = (-u, v)`. + #[inline] + fn neg(self) -> AffinePoint { + AffinePoint { + u: -self.u, + v: self.v, + } + } +} + +impl ConstantTimeEq for AffinePoint { + fn ct_eq(&self, other: &Self) -> Choice { + self.u.ct_eq(&other.u) & self.v.ct_eq(&other.v) + } +} + +impl PartialEq for AffinePoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl ConditionallySelectable for AffinePoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + AffinePoint { + u: Fq::conditional_select(&a.u, &b.u, choice), + v: Fq::conditional_select(&a.v, &b.v, choice), + } + } +} + +/// This represents an extended point `(U, V, Z, T1, T2)` +/// with `Z` nonzero, corresponding to the affine point +/// `(U/Z, V/Z)`. We always have `T1 * T2 = UV/Z`. +/// +/// You can do the following things with a point in this +/// form: +/// +/// * Convert it into a point in the affine form. +/// * Add it to an `ExtendedPoint`, `AffineNielsPoint` or `ExtendedNielsPoint`. +/// * Double it using `double()`. +/// * Compare it with another extended point using `PartialEq` or `ct_eq()`. +#[derive(Clone, Copy, Debug)] +pub struct ExtendedPoint { + u: Fq, + v: Fq, + z: Fq, + t1: Fq, + t2: Fq, +} + +impl ConstantTimeEq for ExtendedPoint { + fn ct_eq(&self, other: &Self) -> Choice { + // (u/z, v/z) = (u'/z', v'/z') is implied by + // (uz'z = u'z'z) and + // (vz'z = v'z'z) + // as z and z' are always nonzero. + + (&self.u * &other.z).ct_eq(&(&other.u * &self.z)) + & (&self.v * &other.z).ct_eq(&(&other.v * &self.z)) + } +} + +impl ConditionallySelectable for ExtendedPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + ExtendedPoint { + u: Fq::conditional_select(&a.u, &b.u, choice), + v: Fq::conditional_select(&a.v, &b.v, choice), + z: Fq::conditional_select(&a.z, &b.z, choice), + t1: Fq::conditional_select(&a.t1, &b.t1, choice), + t2: Fq::conditional_select(&a.t2, &b.t2, choice), + } + } +} + +impl PartialEq for ExtendedPoint { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} + +impl Neg for ExtendedPoint { + type Output = ExtendedPoint; + + /// Computes the negation of a point `P = (U, V, Z, T)` + /// as `-P = (-U, V, Z, -T1, T2)`. The choice of `T1` + /// is made without loss of generality. + #[inline] + fn neg(self) -> ExtendedPoint { + ExtendedPoint { + u: -self.u, + v: self.v, + z: self.z, + t1: -self.t1, + t2: self.t2, + } + } +} + +impl From for ExtendedPoint { + /// Constructs an extended point (with `Z = 1`) from + /// an affine point using the map `(u, v) => (u, v, 1, u, v)`. + fn from(affine: AffinePoint) -> ExtendedPoint { + ExtendedPoint { + u: affine.u, + v: affine.v, + z: Fq::one(), + t1: affine.u, + t2: affine.v, + } + } +} + +impl<'a> From<&'a ExtendedPoint> for AffinePoint { + /// Constructs an affine point from an extended point + /// using the map `(U, V, Z, T1, T2) => (U/Z, V/Z)` + /// as Z is always nonzero. **This requires a field inversion + /// and so it is recommended to perform these in a batch + /// using [`batch_normalize`](crate::batch_normalize) instead.** + fn from(extended: &'a ExtendedPoint) -> AffinePoint { + // Z coordinate is always nonzero, so this is + // its inverse. + let zinv = extended.z.invert().unwrap(); + + AffinePoint { + u: extended.u * &zinv, + v: extended.v * &zinv, + } + } +} + +impl From for AffinePoint { + fn from(extended: ExtendedPoint) -> AffinePoint { + AffinePoint::from(&extended) + } +} + +/// This is a pre-processed version of an affine point `(u, v)` +/// in the form `(v + u, v - u, u * v * 2d)`. This can be added to an +/// [`ExtendedPoint`](crate::ExtendedPoint). +#[derive(Clone, Copy, Debug)] +pub struct AffineNielsPoint { + v_plus_u: Fq, + v_minus_u: Fq, + t2d: Fq, +} + +impl AffineNielsPoint { + /// Constructs this point from the neutral element `(0, 1)`. + pub const fn identity() -> Self { + AffineNielsPoint { + v_plus_u: Fq::one(), + v_minus_u: Fq::one(), + t2d: Fq::zero(), + } + } + + #[inline] + fn multiply(&self, by: &[u8; 32]) -> ExtendedPoint { + let zero = AffineNielsPoint::identity(); + + let mut acc = ExtendedPoint::identity(); + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading four bits because they're always + // unset for Fr. + for bit in by + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(4) + { + acc = acc.double(); + acc += AffineNielsPoint::conditional_select(&zero, &self, bit); + } + + acc + } + + /// Multiplies this point by the specific little-endian bit pattern in the + /// given byte array, ignoring the highest four bits. + pub fn multiply_bits(&self, by: &[u8; 32]) -> ExtendedPoint { + self.multiply(by) + } +} + +impl<'a, 'b> Mul<&'b Fr> for &'a AffineNielsPoint { + type Output = ExtendedPoint; + + fn mul(self, other: &'b Fr) -> ExtendedPoint { + self.multiply(&other.to_bytes()) + } +} + +impl_binops_multiplicative_mixed!(AffineNielsPoint, Fr, ExtendedPoint); + +impl ConditionallySelectable for AffineNielsPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + AffineNielsPoint { + v_plus_u: Fq::conditional_select(&a.v_plus_u, &b.v_plus_u, choice), + v_minus_u: Fq::conditional_select(&a.v_minus_u, &b.v_minus_u, choice), + t2d: Fq::conditional_select(&a.t2d, &b.t2d, choice), + } + } +} + +/// This is a pre-processed version of an extended point `(U, V, Z, T1, T2)` +/// in the form `(V + U, V - U, Z, T1 * T2 * 2d)`. +#[derive(Clone, Copy, Debug)] +pub struct ExtendedNielsPoint { + v_plus_u: Fq, + v_minus_u: Fq, + z: Fq, + t2d: Fq, +} + +impl ConditionallySelectable for ExtendedNielsPoint { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + ExtendedNielsPoint { + v_plus_u: Fq::conditional_select(&a.v_plus_u, &b.v_plus_u, choice), + v_minus_u: Fq::conditional_select(&a.v_minus_u, &b.v_minus_u, choice), + z: Fq::conditional_select(&a.z, &b.z, choice), + t2d: Fq::conditional_select(&a.t2d, &b.t2d, choice), + } + } +} + +impl ExtendedNielsPoint { + /// Constructs this point from the neutral element `(0, 1)`. + pub const fn identity() -> Self { + ExtendedNielsPoint { + v_plus_u: Fq::one(), + v_minus_u: Fq::one(), + z: Fq::one(), + t2d: Fq::zero(), + } + } + + #[inline] + fn multiply(&self, by: &[u8; 32]) -> ExtendedPoint { + let zero = ExtendedNielsPoint::identity(); + + let mut acc = ExtendedPoint::identity(); + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading four bits because they're always + // unset for Fr. + for bit in by + .iter() + .rev() + .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) + .skip(4) + { + acc = acc.double(); + acc += ExtendedNielsPoint::conditional_select(&zero, &self, bit); + } + + acc + } + + /// Multiplies this point by the specific little-endian bit pattern in the + /// given byte array, ignoring the highest four bits. + pub fn multiply_bits(&self, by: &[u8; 32]) -> ExtendedPoint { + self.multiply(by) + } +} + +impl<'a, 'b> Mul<&'b Fr> for &'a ExtendedNielsPoint { + type Output = ExtendedPoint; + + fn mul(self, other: &'b Fr) -> ExtendedPoint { + self.multiply(&other.to_bytes()) + } +} + +impl_binops_multiplicative_mixed!(ExtendedNielsPoint, Fr, ExtendedPoint); + +// `d = -(10240/10241)` +const EDWARDS_D: Fq = Fq::from_raw([ + 0x01065fd6d6343eb1, + 0x292d7f6d37579d26, + 0xf5fd9207e6bd7fd4, + 0x2a9318e74bfa2b48, +]); + +// `2*d` +const EDWARDS_D2: Fq = Fq::from_raw([ + 0x020cbfadac687d62, + 0x525afeda6eaf3a4c, + 0xebfb240fcd7affa8, + 0x552631ce97f45691, +]); + +impl AffinePoint { + /// Constructs the neutral element `(0, 1)`. + pub const fn identity() -> Self { + AffinePoint { + u: Fq::zero(), + v: Fq::one(), + } + } + + /// Multiplies this point by the cofactor, producing an + /// `ExtendedPoint` + pub fn mul_by_cofactor(&self) -> ExtendedPoint { + ExtendedPoint::from(*self).mul_by_cofactor() + } + + /// Determines if this point is of small order. + pub fn is_small_order(&self) -> Choice { + ExtendedPoint::from(*self).is_small_order() + } + + /// Determines if this point is torsion free and so is + /// in the prime order subgroup. + pub fn is_torsion_free(&self) -> Choice { + ExtendedPoint::from(*self).is_torsion_free() + } + + /// Determines if this point is prime order, or in other words that + /// the smallest scalar multiplied by this point that produces the + /// identity is `r`. This is equivalent to checking that the point + /// is both torsion free and not the identity. + pub fn is_prime_order(&self) -> Choice { + let extended = ExtendedPoint::from(*self); + extended.is_torsion_free() & (!extended.is_identity()) + } + + /// Converts this element into its byte representation. + pub fn to_bytes(&self) -> [u8; 32] { + let mut tmp = self.v.to_bytes(); + let u = self.u.to_bytes(); + + // Encode the sign of the u-coordinate in the most + // significant bit. + tmp[31] |= u[0] << 7; + + tmp + } + + /// Attempts to interpret a byte representation of an + /// affine point, failing if the element is not on + /// the curve or non-canonical. + pub fn from_bytes(mut b: [u8; 32]) -> CtOption { + // Grab the sign bit from the representation + let sign = b[31] >> 7; + + // Mask away the sign bit + b[31] &= 0b0111_1111; + + // Interpret what remains as the v-coordinate + Fq::from_bytes(&b).and_then(|v| { + // -u^2 + v^2 = 1 + d.u^2.v^2 + // -u^2 = 1 + d.u^2.v^2 - v^2 (rearrange) + // -u^2 - d.u^2.v^2 = 1 - v^2 (rearrange) + // u^2 + d.u^2.v^2 = v^2 - 1 (flip signs) + // u^2 (1 + d.v^2) = v^2 - 1 (factor) + // u^2 = (v^2 - 1) / (1 + d.v^2) (isolate u^2) + // We know that (1 + d.v^2) is nonzero for all v: + // (1 + d.v^2) = 0 + // d.v^2 = -1 + // v^2 = -(1 / d) No solutions, as -(1 / d) is not a square + + let v2 = v.square(); + + ((v2 - Fq::one()) * ((Fq::one() + EDWARDS_D * &v2).invert().unwrap_or(Fq::zero()))) + .sqrt() + .and_then(|u| { + // Fix the sign of `u` if necessary + let flip_sign = Choice::from((u.to_bytes()[0] ^ sign) & 1); + let u_negated = -u; + let final_u = Fq::conditional_select(&u, &u_negated, flip_sign); + + CtOption::new(AffinePoint { u: final_u, v }, Choice::from(1u8)) + }) + }) + } + + /// Returns the `u`-coordinate of this point. + pub fn get_u(&self) -> Fq { + self.u + } + + /// Returns the `v`-coordinate of this point. + pub fn get_v(&self) -> Fq { + self.v + } + + /// Performs a pre-processing step that produces an `AffineNielsPoint` + /// for use in multiple additions. + pub const fn to_niels(&self) -> AffineNielsPoint { + AffineNielsPoint { + v_plus_u: Fq::add(&self.v, &self.u), + v_minus_u: Fq::sub(&self.v, &self.u), + t2d: Fq::mul(&Fq::mul(&self.u, &self.v), &EDWARDS_D2), + } + } + + /// Constructs an AffinePoint given `u` and `v` without checking + /// that the point is on the curve. + pub const fn from_raw_unchecked(u: Fq, v: Fq) -> AffinePoint { + AffinePoint { u, v } + } + + /// This is only for debugging purposes and not + /// exposed in the public API. Checks that this + /// point is on the curve. + #[cfg(test)] + fn is_on_curve_vartime(&self) -> bool { + let u2 = self.u.square(); + let v2 = self.v.square(); + + &v2 - &u2 == Fq::one() + &EDWARDS_D * &u2 * &v2 + } +} + +impl ExtendedPoint { + /// Constructs an extended point from the neutral element `(0, 1)`. + pub const fn identity() -> Self { + ExtendedPoint { + u: Fq::zero(), + v: Fq::one(), + z: Fq::one(), + t1: Fq::zero(), + t2: Fq::zero(), + } + } + + /// Determines if this point is the identity. + pub fn is_identity(&self) -> Choice { + // If this point is the identity, then + // u = 0 * z = 0 + // and v = 1 * z = z + self.u.ct_eq(&Fq::zero()) & self.v.ct_eq(&self.z) + } + + /// Determines if this point is of small order. + pub fn is_small_order(&self) -> Choice { + // We only need to perform two doublings, since the 2-torsion + // points are (0, 1) and (0, -1), and so we only need to check + // that the u-coordinate of the result is zero to see if the + // point is small order. + self.double().double().u.ct_eq(&Fq::zero()) + } + + /// Determines if this point is torsion free and so is contained + /// in the prime order subgroup. + pub fn is_torsion_free(&self) -> Choice { + self.multiply(&FR_MODULUS_BYTES).is_identity() + } + + /// Determines if this point is prime order, or in other words that + /// the smallest scalar multiplied by this point that produces the + /// identity is `r`. This is equivalent to checking that the point + /// is both torsion free and not the identity. + pub fn is_prime_order(&self) -> Choice { + self.is_torsion_free() & (!self.is_identity()) + } + + /// Multiplies this element by the cofactor `8`. + pub fn mul_by_cofactor(&self) -> ExtendedPoint { + self.double().double().double() + } + + /// Performs a pre-processing step that produces an `ExtendedNielsPoint` + /// for use in multiple additions. + pub fn to_niels(&self) -> ExtendedNielsPoint { + ExtendedNielsPoint { + v_plus_u: &self.v + &self.u, + v_minus_u: &self.v - &self.u, + z: self.z, + t2d: &self.t1 * &self.t2 * EDWARDS_D2, + } + } + + /// Computes the doubling of a point more efficiently than a point can + /// be added to itself. + pub fn double(&self) -> ExtendedPoint { + // Doubling is more efficient (three multiplications, four squarings) + // when we work within the projective coordinate space (U:Z, V:Z). We + // rely on the most efficient formula, "dbl-2008-bbjlp", as described + // in Section 6 of "Twisted Edwards Curves" by Bernstein et al. + // + // See + // for more information. + // + // We differ from the literature in that we use (u, v) rather than + // (x, y) coordinates. We also have the constant `a = -1` implied. Let + // us rewrite the procedure of doubling (u, v, z) to produce (U, V, Z) + // as follows: + // + // B = (u + v)^2 + // C = u^2 + // D = v^2 + // F = D - C + // H = 2 * z^2 + // J = F - H + // U = (B - C - D) * J + // V = F * (- C - D) + // Z = F * J + // + // If we compute K = D + C, we can rewrite this: + // + // B = (u + v)^2 + // C = u^2 + // D = v^2 + // F = D - C + // K = D + C + // H = 2 * z^2 + // J = F - H + // U = (B - K) * J + // V = F * (-K) + // Z = F * J + // + // In order to avoid the unnecessary negation of K, + // we will negate J, transforming the result into + // an equivalent point with a negated z-coordinate. + // + // B = (u + v)^2 + // C = u^2 + // D = v^2 + // F = D - C + // K = D + C + // H = 2 * z^2 + // J = H - F + // U = (B - K) * J + // V = F * K + // Z = F * J + // + // Let us rename some variables to simplify: + // + // UV2 = (u + v)^2 + // UU = u^2 + // VV = v^2 + // VVmUU = VV - UU + // VVpUU = VV + UU + // ZZ2 = 2 * z^2 + // J = ZZ2 - VVmUU + // U = (UV2 - VVpUU) * J + // V = VVmUU * VVpUU + // Z = VVmUU * J + // + // We wish to obtain two factors of T = UV/Z. + // + // UV/Z = (UV2 - VVpUU) * (ZZ2 - VVmUU) * VVmUU * VVpUU / VVmUU / (ZZ2 - VVmUU) + // = (UV2 - VVpUU) * VVmUU * VVpUU / VVmUU + // = (UV2 - VVpUU) * VVpUU + // + // and so we have that T1 = (UV2 - VVpUU) and T2 = VVpUU. + + let uu = self.u.square(); + let vv = self.v.square(); + let zz2 = self.z.square().double(); + let uv2 = (&self.u + &self.v).square(); + let vv_plus_uu = &vv + &uu; + let vv_minus_uu = &vv - &uu; + + // The remaining arithmetic is exactly the process of converting + // from a completed point to an extended point. + CompletedPoint { + u: &uv2 - &vv_plus_uu, + v: vv_plus_uu, + z: vv_minus_uu, + t: &zz2 - &vv_minus_uu, + } + .into_extended() + } + + #[inline] + fn multiply(self, by: &[u8; 32]) -> Self { + self.to_niels().multiply(by) + } + + /// This is only for debugging purposes and not + /// exposed in the public API. Checks that this + /// point is on the curve. + #[cfg(test)] + fn is_on_curve_vartime(&self) -> bool { + let affine = AffinePoint::from(*self); + + self.z != Fq::zero() + && affine.is_on_curve_vartime() + && (affine.u * affine.v * self.z == self.t1 * self.t2) + } +} + +impl<'a, 'b> Mul<&'b Fr> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + fn mul(self, other: &'b Fr) -> ExtendedPoint { + self.multiply(&other.to_bytes()) + } +} + +impl_binops_multiplicative!(ExtendedPoint, Fr); + +impl<'a, 'b> Add<&'b ExtendedNielsPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn add(self, other: &'b ExtendedNielsPoint) -> ExtendedPoint { + // We perform addition in the extended coordinates. Here we use + // a formula presented by Hisil, Wong, Carter and Dawson in + // "Twisted Edward Curves Revisited" which only requires 8M. + // + // A = (V1 - U1) * (V2 - U2) + // B = (V1 + U1) * (V2 + U2) + // C = 2d * T1 * T2 + // D = 2 * Z1 * Z2 + // E = B - A + // F = D - C + // G = D + C + // H = B + A + // U3 = E * F + // Y3 = G * H + // Z3 = F * G + // T3 = E * H + + let a = (&self.v - &self.u) * &other.v_minus_u; + let b = (&self.v + &self.u) * &other.v_plus_u; + let c = &self.t1 * &self.t2 * &other.t2d; + let d = (&self.z * &other.z).double(); + + // The remaining arithmetic is exactly the process of converting + // from a completed point to an extended point. + CompletedPoint { + u: &b - &a, + v: &b + &a, + z: &d + &c, + t: &d - &c, + } + .into_extended() + } +} + +impl<'a, 'b> Sub<&'b ExtendedNielsPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, other: &'b ExtendedNielsPoint) -> ExtendedPoint { + let a = (&self.v - &self.u) * &other.v_plus_u; + let b = (&self.v + &self.u) * &other.v_minus_u; + let c = &self.t1 * &self.t2 * &other.t2d; + let d = (&self.z * &other.z).double(); + + CompletedPoint { + u: &b - &a, + v: &b + &a, + z: &d - &c, + t: &d + &c, + } + .into_extended() + } +} + +impl_binops_additive!(ExtendedPoint, ExtendedNielsPoint); + +impl<'a, 'b> Add<&'b AffineNielsPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn add(self, other: &'b AffineNielsPoint) -> ExtendedPoint { + // This is identical to the addition formula for `ExtendedNielsPoint`, + // except we can assume that `other.z` is one, so that we perform + // 7 multiplications. + + let a = (&self.v - &self.u) * &other.v_minus_u; + let b = (&self.v + &self.u) * &other.v_plus_u; + let c = &self.t1 * &self.t2 * &other.t2d; + let d = self.z.double(); + + // The remaining arithmetic is exactly the process of converting + // from a completed point to an extended point. + CompletedPoint { + u: &b - &a, + v: &b + &a, + z: &d + &c, + t: &d - &c, + } + .into_extended() + } +} + +impl<'a, 'b> Sub<&'b AffineNielsPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn sub(self, other: &'b AffineNielsPoint) -> ExtendedPoint { + let a = (&self.v - &self.u) * &other.v_plus_u; + let b = (&self.v + &self.u) * &other.v_minus_u; + let c = &self.t1 * &self.t2 * &other.t2d; + let d = self.z.double(); + + CompletedPoint { + u: &b - &a, + v: &b + &a, + z: &d - &c, + t: &d + &c, + } + .into_extended() + } +} + +impl_binops_additive!(ExtendedPoint, AffineNielsPoint); + +impl<'a, 'b> Add<&'b ExtendedPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[inline] + fn add(self, other: &'b ExtendedPoint) -> ExtendedPoint { + self + other.to_niels() + } +} + +impl<'a, 'b> Sub<&'b ExtendedPoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[inline] + fn sub(self, other: &'b ExtendedPoint) -> ExtendedPoint { + self - other.to_niels() + } +} + +impl_binops_additive!(ExtendedPoint, ExtendedPoint); + +impl<'a, 'b> Add<&'b AffinePoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[inline] + fn add(self, other: &'b AffinePoint) -> ExtendedPoint { + self + other.to_niels() + } +} + +impl<'a, 'b> Sub<&'b AffinePoint> for &'a ExtendedPoint { + type Output = ExtendedPoint; + + #[inline] + fn sub(self, other: &'b AffinePoint) -> ExtendedPoint { + self - other.to_niels() + } +} + +impl_binops_additive!(ExtendedPoint, AffinePoint); + +/// This is a "completed" point produced during a point doubling or +/// addition routine. These points exist in the `(U:Z, V:T)` model +/// of the curve. This is not exposed in the API because it is +/// an implementation detail. +struct CompletedPoint { + u: Fq, + v: Fq, + z: Fq, + t: Fq, +} + +impl CompletedPoint { + /// This converts a completed point into an extended point by + /// homogenizing: + /// + /// (u/z, v/t) = (u/z * t/t, v/t * z/z) = (ut/zt, vz/zt) + /// + /// The resulting T coordinate is utvz/zt = uv, and so + /// T1 = u, T2 = v, without loss of generality. + #[inline] + fn into_extended(self) -> ExtendedPoint { + ExtendedPoint { + u: &self.u * &self.t, + v: &self.v * &self.z, + z: &self.z * &self.t, + t1: self.u, + t2: self.v, + } + } +} + +impl Default for AffinePoint { + /// Returns the identity. + fn default() -> AffinePoint { + AffinePoint::identity() + } +} + +impl Default for ExtendedPoint { + /// Returns the identity. + fn default() -> ExtendedPoint { + ExtendedPoint::identity() + } +} + +/// This takes a mutable slice of `ExtendedPoint`s and "normalizes" them using +/// only a single inversion for the entire batch. This normalization results in +/// all of the points having a Z-coordinate of one. Further, an iterator is +/// returned which can be used to obtain `AffinePoint`s for each element in the +/// slice. +/// +/// This costs 5 multiplications per element, and a field inversion. +pub fn batch_normalize<'a>(v: &'a mut [ExtendedPoint]) -> impl Iterator + 'a { + let mut acc = Fq::one(); + for p in v.iter_mut() { + // We use the `t1` field of `ExtendedPoint` to store the product + // of previous z-coordinates seen. + p.t1 = acc; + acc *= &p.z; + } + + // This is the inverse, as all z-coordinates are nonzero. + acc = acc.invert().unwrap(); + + for p in v.iter_mut().rev() { + let mut q = *p; + + // Compute tmp = 1/z + let tmp = q.t1 * acc; + + // Cancel out z-coordinate in denominator of `acc` + acc *= &q.z; + + // Set the coordinates to the correct value + q.u *= &tmp; // Multiply by 1/z + q.v *= &tmp; // Multiply by 1/z + q.z = Fq::one(); // z-coordinate is now one + q.t1 = q.u; + q.t2 = q.v; + + *p = q; + } + + // All extended points are now normalized, but the type + // doesn't encode this fact. Let us offer affine points + // to the caller. + + v.iter().map(|p| AffinePoint { u: p.u, v: p.v }) +} + +#[test] +fn test_is_on_curve_var() { + assert!(AffinePoint::identity().is_on_curve_vartime()); +} + +#[test] +fn test_d_is_non_quadratic_residue() { + assert!(EDWARDS_D.sqrt().is_none().unwrap_u8() == 1); + assert!((-EDWARDS_D).sqrt().is_none().unwrap_u8() == 1); + assert!((-EDWARDS_D).invert().unwrap().sqrt().is_none().unwrap_u8() == 1); +} + +#[test] +fn test_affine_niels_point_identity() { + assert_eq!( + AffineNielsPoint::identity().v_plus_u, + AffinePoint::identity().to_niels().v_plus_u + ); + assert_eq!( + AffineNielsPoint::identity().v_minus_u, + AffinePoint::identity().to_niels().v_minus_u + ); + assert_eq!( + AffineNielsPoint::identity().t2d, + AffinePoint::identity().to_niels().t2d + ); +} + +#[test] +fn test_extended_niels_point_identity() { + assert_eq!( + ExtendedNielsPoint::identity().v_plus_u, + ExtendedPoint::identity().to_niels().v_plus_u + ); + assert_eq!( + ExtendedNielsPoint::identity().v_minus_u, + ExtendedPoint::identity().to_niels().v_minus_u + ); + assert_eq!( + ExtendedNielsPoint::identity().z, + ExtendedPoint::identity().to_niels().z + ); + assert_eq!( + ExtendedNielsPoint::identity().t2d, + ExtendedPoint::identity().to_niels().t2d + ); +} + +#[test] +fn test_assoc() { + let p = ExtendedPoint::from(AffinePoint { + u: Fq::from_raw([ + 0x81c571e5d883cfb0, + 0x049f7a686f147029, + 0xf539c860bc3ea21f, + 0x4284715b7ccc8162, + ]), + v: Fq::from_raw([ + 0xbf096275684bb8ca, + 0xc7ba245890af256d, + 0x59119f3e86380eb0, + 0x3793de182f9fb1d2, + ]), + }) + .mul_by_cofactor(); + assert!(p.is_on_curve_vartime()); + + assert_eq!( + (p * Fr::from(1000u64)) * Fr::from(3938u64), + p * (Fr::from(1000u64) * Fr::from(3938u64)), + ); +} + +#[test] +fn test_batch_normalize() { + let mut p = ExtendedPoint::from(AffinePoint { + u: Fq::from_raw([ + 0x81c571e5d883cfb0, + 0x049f7a686f147029, + 0xf539c860bc3ea21f, + 0x4284715b7ccc8162, + ]), + v: Fq::from_raw([ + 0xbf096275684bb8ca, + 0xc7ba245890af256d, + 0x59119f3e86380eb0, + 0x3793de182f9fb1d2, + ]), + }) + .mul_by_cofactor(); + + let mut v = vec![]; + for _ in 0..10 { + v.push(p); + p = p.double(); + } + + for p in &v { + assert!(p.is_on_curve_vartime()); + } + + let expected: std::vec::Vec<_> = v.iter().map(|p| AffinePoint::from(*p)).collect(); + let result1: std::vec::Vec<_> = batch_normalize(&mut v).collect(); + for i in 0..10 { + assert!(expected[i] == result1[i]); + assert!(v[i].is_on_curve_vartime()); + assert!(AffinePoint::from(v[i]) == expected[i]); + } + let result2: std::vec::Vec<_> = batch_normalize(&mut v).collect(); + for i in 0..10 { + assert!(expected[i] == result2[i]); + assert!(v[i].is_on_curve_vartime()); + assert!(AffinePoint::from(v[i]) == expected[i]); + } +} + +#[cfg(test)] +const FULL_GENERATOR: AffinePoint = AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0xe4b3d35df1a7adfe, + 0xcaf55d1b29bf81af, + 0x8b0f03ddd60a8187, + 0x62edcbb8bf3787c8, + ]), + Fq::from_raw([0xb, 0x0, 0x0, 0x0]), +); + +#[cfg(test)] +const EIGHT_TORSION: [AffinePoint; 8] = [ + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0xd92e6a7927200d43, + 0x7aa41ac43dae8582, + 0xeaaae086a16618d1, + 0x71d4df38ba9e7973, + ]), + Fq::from_raw([ + 0xff0d2068eff496dd, + 0x9106ee90f384a4a1, + 0x16a13035ad4d7266, + 0x4958bdb21966982e, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0xfffeffff00000001, + 0x67baa40089fb5bfe, + 0xa5e80b39939ed334, + 0x73eda753299d7d47, + ]), + Fq::from_raw([0x0, 0x0, 0x0, 0x0]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0xd92e6a7927200d43, + 0x7aa41ac43dae8582, + 0xeaaae086a16618d1, + 0x71d4df38ba9e7973, + ]), + Fq::from_raw([ + 0xf2df96100b6924, + 0xc2b6b5720c79b75d, + 0x1c98a7d25c54659e, + 0x2a94e9a11036e51a, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([0x0, 0x0, 0x0, 0x0]), + Fq::from_raw([ + 0xffffffff00000000, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0x26d19585d8dff2be, + 0xd919893ec24fd67c, + 0x488ef781683bbf33, + 0x218c81a6eff03d4, + ]), + Fq::from_raw([ + 0xf2df96100b6924, + 0xc2b6b5720c79b75d, + 0x1c98a7d25c54659e, + 0x2a94e9a11036e51a, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([0x1000000000000, 0xec03000276030000, 0x8d51ccce760304d0, 0x0]), + Fq::from_raw([0x0, 0x0, 0x0, 0x0]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([ + 0x26d19585d8dff2be, + 0xd919893ec24fd67c, + 0x488ef781683bbf33, + 0x218c81a6eff03d4, + ]), + Fq::from_raw([ + 0xff0d2068eff496dd, + 0x9106ee90f384a4a1, + 0x16a13035ad4d7266, + 0x4958bdb21966982e, + ]), + ), + AffinePoint::from_raw_unchecked( + Fq::from_raw([0x0, 0x0, 0x0, 0x0]), + Fq::from_raw([0x1, 0x0, 0x0, 0x0]), + ), +]; + +#[test] +fn find_eight_torsion() { + let g = ExtendedPoint::from(FULL_GENERATOR); + assert!(g.is_small_order().unwrap_u8() == 0); + let g = g.multiply(&FR_MODULUS_BYTES); + assert!(g.is_small_order().unwrap_u8() == 1); + + let mut cur = g; + + for (i, point) in EIGHT_TORSION.iter().enumerate() { + let tmp = AffinePoint::from(cur); + if &tmp != point { + panic!("{}th torsion point should be {:?}", i, tmp); + } + + cur += &g; + } +} + +#[test] +fn find_curve_generator() { + let mut trial_bytes = [0; 32]; + for _ in 0..255 { + let a = AffinePoint::from_bytes(trial_bytes); + if a.is_some().unwrap_u8() == 1 { + let a = a.unwrap(); + assert!(a.is_on_curve_vartime()); + let b = ExtendedPoint::from(a); + let b = b.multiply(&FR_MODULUS_BYTES); + assert!(b.is_small_order().unwrap_u8() == 1); + let b = b.double(); + assert!(b.is_small_order().unwrap_u8() == 1); + let b = b.double(); + assert!(b.is_small_order().unwrap_u8() == 1); + if b.is_identity().unwrap_u8() == 0 { + let b = b.double(); + assert!(b.is_small_order().unwrap_u8() == 1); + assert!(b.is_identity().unwrap_u8() == 1); + assert_eq!(FULL_GENERATOR, a); + assert!(a.mul_by_cofactor().is_torsion_free().unwrap_u8() == 1); + return; + } + } + + trial_bytes[0] += 1; + } + + panic!("should have found a generator of the curve"); +} + +#[test] +fn test_small_order() { + for point in EIGHT_TORSION.iter() { + assert!(point.is_small_order().unwrap_u8() == 1); + } +} + +#[test] +fn test_is_identity() { + let a = EIGHT_TORSION[0].mul_by_cofactor(); + let b = EIGHT_TORSION[1].mul_by_cofactor(); + + assert_eq!(a.u, b.u); + assert_eq!(a.v, a.z); + assert_eq!(b.v, b.z); + assert!(a.v != b.v); + assert!(a.z != b.z); + + assert!(a.is_identity().unwrap_u8() == 1); + assert!(b.is_identity().unwrap_u8() == 1); + + for point in EIGHT_TORSION.iter() { + assert!(point.mul_by_cofactor().is_identity().unwrap_u8() == 1); + } +} + +#[test] +fn test_mul_consistency() { + let a = Fr([ + 0x21e61211d9934f2e, + 0xa52c058a693c3e07, + 0x9ccb77bfb12d6360, + 0x07df2470ec94398e, + ]); + let b = Fr([ + 0x03336d1cbe19dbe0, + 0x0153618f6156a536, + 0x2604c9e1fc3c6b15, + 0x04ae581ceb028720, + ]); + let c = Fr([ + 0xd7abf5bb24683f4c, + 0x9d7712cc274b7c03, + 0x973293db9683789f, + 0x0b677e29380a97a7, + ]); + assert_eq!(a * b, c); + let p = ExtendedPoint::from(AffinePoint { + u: Fq::from_raw([ + 0x81c571e5d883cfb0, + 0x049f7a686f147029, + 0xf539c860bc3ea21f, + 0x4284715b7ccc8162, + ]), + v: Fq::from_raw([ + 0xbf096275684bb8ca, + 0xc7ba245890af256d, + 0x59119f3e86380eb0, + 0x3793de182f9fb1d2, + ]), + }) + .mul_by_cofactor(); + assert_eq!(p * c, (p * a) * b); + + // Test Mul implemented on ExtendedNielsPoint + assert_eq!(p * c, (p.to_niels() * a) * b); + assert_eq!(p.to_niels() * c, (p * a) * b); + assert_eq!(p.to_niels() * c, (p.to_niels() * a) * b); + + // Test Mul implemented on AffineNielsPoint + let p_affine_niels = AffinePoint::from(p).to_niels(); + assert_eq!(p * c, (p_affine_niels * a) * b); + assert_eq!(p_affine_niels * c, (p * a) * b); + assert_eq!(p_affine_niels * c, (p_affine_niels * a) * b); +} + +#[test] +fn test_serialization_consistency() { + let gen = FULL_GENERATOR.mul_by_cofactor(); + let mut p = gen; + + let v = vec![ + [ + 203, 85, 12, 213, 56, 234, 12, 193, 19, 132, 128, 64, 142, 110, 170, 185, 179, 108, 97, + 63, 13, 211, 247, 120, 79, 219, 110, 234, 131, 123, 19, 215, + ], + [ + 113, 154, 240, 230, 224, 198, 208, 170, 104, 15, 59, 126, 151, 222, 233, 195, 203, 195, + 167, 129, 89, 121, 240, 142, 51, 166, 64, 250, 184, 202, 154, 177, + ], + [ + 197, 41, 93, 209, 203, 55, 164, 174, 88, 0, 90, 199, 1, 156, 149, 141, 240, 29, 14, 82, + 86, 225, 126, 129, 186, 157, 148, 162, 219, 51, 156, 199, + ], + [ + 182, 117, 250, 241, 81, 196, 199, 227, 151, 74, 243, 17, 221, 97, 200, 139, 192, 83, + 231, 35, 214, 14, 95, 69, 130, 201, 4, 116, 177, 19, 179, 0, + ], + [ + 118, 41, 29, 200, 60, 189, 119, 252, 78, 40, 230, 18, 208, 221, 38, 214, 176, 250, 4, + 10, 77, 101, 26, 216, 193, 198, 226, 84, 25, 177, 230, 185, + ], + [ + 226, 189, 227, 208, 112, 117, 136, 98, 72, 38, 211, 167, 254, 82, 174, 113, 112, 166, + 138, 171, 166, 113, 52, 251, 129, 197, 138, 45, 195, 7, 61, 140, + ], + [ + 38, 198, 156, 196, 146, 225, 55, 163, 138, 178, 157, 128, 115, 135, 204, 215, 0, 33, + 171, 20, 60, 32, 142, 209, 33, 233, 125, 146, 207, 12, 16, 24, + ], + [ + 17, 187, 231, 83, 165, 36, 232, 184, 140, 205, 195, 252, 166, 85, 59, 86, 3, 226, 211, + 67, 179, 29, 238, 181, 102, 142, 58, 63, 57, 89, 174, 138, + ], + [ + 210, 159, 80, 16, 181, 39, 221, 204, 224, 144, 145, 79, 54, 231, 8, 140, 142, 216, 93, + 190, 183, 116, 174, 63, 33, 242, 177, 118, 148, 40, 241, 203, + ], + [ + 0, 143, 107, 102, 149, 187, 27, 124, 18, 10, 98, 28, 113, 123, 121, 185, 29, 152, 14, + 130, 149, 28, 87, 35, 135, 135, 153, 54, 112, 53, 54, 68, + ], + [ + 178, 131, 85, 160, 214, 51, 208, 157, 196, 152, 247, 93, 202, 56, 81, 239, 155, 122, + 59, 188, 237, 253, 11, 169, 208, 236, 12, 4, 163, 211, 88, 97, + ], + [ + 246, 194, 231, 195, 159, 101, 180, 133, 80, 21, 185, 220, 195, 115, 144, 12, 90, 150, + 44, 117, 8, 156, 168, 248, 206, 41, 60, 82, 67, 75, 57, 67, + ], + [ + 212, 205, 171, 153, 113, 16, 194, 241, 224, 43, 177, 110, 190, 248, 22, 201, 208, 166, + 2, 83, 134, 130, 85, 129, 166, 136, 185, 191, 163, 38, 54, 10, + ], + [ + 8, 60, 190, 39, 153, 222, 119, 23, 142, 237, 12, 110, 146, 9, 19, 219, 143, 64, 161, + 99, 199, 77, 39, 148, 70, 213, 246, 227, 150, 178, 237, 178, + ], + [ + 11, 114, 217, 160, 101, 37, 100, 220, 56, 114, 42, 31, 138, 33, 84, 157, 214, 167, 73, + 233, 115, 81, 124, 134, 15, 31, 181, 60, 184, 130, 175, 159, + ], + [ + 141, 238, 235, 202, 241, 32, 210, 10, 127, 230, 54, 31, 146, 80, 247, 9, 107, 124, 0, + 26, 203, 16, 237, 34, 214, 147, 133, 15, 29, 236, 37, 88, + ], + ]; + + for expected_serialized in v { + assert!(p.is_on_curve_vartime()); + let affine = AffinePoint::from(p); + let serialized = affine.to_bytes(); + let deserialized = AffinePoint::from_bytes(serialized).unwrap(); + assert_eq!(affine, deserialized); + assert_eq!(expected_serialized, serialized); + p = p + &gen; + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 000000000..bd25dd06a --- /dev/null +++ b/src/util.rs @@ -0,0 +1,174 @@ +/// Compute a + b + carry, returning the result and the new carry over. +#[inline(always)] +pub const fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) { + let ret = (a as u128) + (b as u128) + (carry as u128); + (ret as u64, (ret >> 64) as u64) +} + +/// Compute a - (b + borrow), returning the result and the new borrow. +#[inline(always)] +pub const fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) { + let ret = (a as u128).wrapping_sub((b as u128) + ((borrow >> 63) as u128)); + (ret as u64, (ret >> 64) as u64) +} + +/// Compute a + (b * c) + carry, returning the result and the new carry over. +#[inline(always)] +pub const fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) { + let ret = (a as u128) + ((b as u128) * (c as u128)) + (carry as u128); + (ret as u64, (ret >> 64) as u64) +} + +macro_rules! impl_add_binop_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Add<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: &'b $rhs) -> $output { + &self + rhs + } + } + + impl<'a> Add<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: $rhs) -> $output { + self + &rhs + } + } + + impl Add<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn add(self, rhs: $rhs) -> $output { + &self + &rhs + } + } + }; +} + +macro_rules! impl_sub_binop_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Sub<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: &'b $rhs) -> $output { + &self - rhs + } + } + + impl<'a> Sub<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: $rhs) -> $output { + self - &rhs + } + } + + impl Sub<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn sub(self, rhs: $rhs) -> $output { + &self - &rhs + } + } + }; +} + +macro_rules! impl_binops_additive_specify_output { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl_add_binop_specify_output!($lhs, $rhs, $output); + impl_sub_binop_specify_output!($lhs, $rhs, $output); + }; +} + +macro_rules! impl_binops_multiplicative_mixed { + ($lhs:ident, $rhs:ident, $output:ident) => { + impl<'b> Mul<&'b $rhs> for $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: &'b $rhs) -> $output { + &self * rhs + } + } + + impl<'a> Mul<$rhs> for &'a $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: $rhs) -> $output { + self * &rhs + } + } + + impl Mul<$rhs> for $lhs { + type Output = $output; + + #[inline] + fn mul(self, rhs: $rhs) -> $output { + &self * &rhs + } + } + }; +} + +macro_rules! impl_binops_additive { + ($lhs:ident, $rhs:ident) => { + impl_binops_additive_specify_output!($lhs, $rhs, $lhs); + + impl SubAssign<$rhs> for $lhs { + #[inline] + fn sub_assign(&mut self, rhs: $rhs) { + *self = &*self - &rhs; + } + } + + impl AddAssign<$rhs> for $lhs { + #[inline] + fn add_assign(&mut self, rhs: $rhs) { + *self = &*self + &rhs; + } + } + + impl<'b> SubAssign<&'b $rhs> for $lhs { + #[inline] + fn sub_assign(&mut self, rhs: &'b $rhs) { + *self = &*self - rhs; + } + } + + impl<'b> AddAssign<&'b $rhs> for $lhs { + #[inline] + fn add_assign(&mut self, rhs: &'b $rhs) { + *self = &*self + rhs; + } + } + }; +} + +macro_rules! impl_binops_multiplicative { + ($lhs:ident, $rhs:ident) => { + impl_binops_multiplicative_mixed!($lhs, $rhs, $lhs); + + impl MulAssign<$rhs> for $lhs { + #[inline] + fn mul_assign(&mut self, rhs: $rhs) { + *self = &*self * &rhs; + } + } + + impl<'b> MulAssign<&'b $rhs> for $lhs { + #[inline] + fn mul_assign(&mut self, rhs: &'b $rhs) { + *self = &*self * rhs; + } + } + }; +} diff --git a/tests/common.rs b/tests/common.rs new file mode 100644 index 000000000..a4535edbe --- /dev/null +++ b/tests/common.rs @@ -0,0 +1,29 @@ +use jubjub::*; +use rand_core::{RngCore, SeedableRng}; +use rand_xorshift::XorShiftRng; + +pub const NUM_BLACK_BOX_CHECKS: u32 = 2000; + +pub fn new_rng() -> XorShiftRng { + XorShiftRng::from_seed([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) +} + +pub trait MyRandom { + fn new_random(rng: &mut T) -> Self; +} + +impl MyRandom for Fq { + fn new_random(rng: &mut T) -> Self { + let mut random_bytes = [0u8; 64]; + rng.fill_bytes(&mut random_bytes); + Fq::from_bytes_wide(&random_bytes) + } +} + +impl MyRandom for Fr { + fn new_random(rng: &mut T) -> Self { + let mut random_bytes = [0u8; 64]; + rng.fill_bytes(&mut random_bytes); + Fr::from_bytes_wide(&random_bytes) + } +} diff --git a/tests/fq_blackbox.rs b/tests/fq_blackbox.rs new file mode 100644 index 000000000..a823c9b0c --- /dev/null +++ b/tests/fq_blackbox.rs @@ -0,0 +1,120 @@ +mod common; + +use common::{new_rng, MyRandom, NUM_BLACK_BOX_CHECKS}; +use jubjub::*; + +#[test] +fn test_to_and_from_bytes() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(a, Fq::from_bytes(&Fq::to_bytes(&a)).unwrap()); + } +} + +#[test] +fn test_additive_associativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let b = Fq::new_random(&mut rng); + let c = Fq::new_random(&mut rng); + assert_eq!((a + b) + c, a + (b + c)) + } +} + +#[test] +fn test_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(a, a + Fq::zero()); + assert_eq!(a, Fq::zero() + a); + } +} + +#[test] +fn test_subtract_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(a, a - Fq::zero()); + assert_eq!(a, Fq::zero() - -&a); + } +} + +#[test] +fn test_additive_inverse() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let a_neg = -&a; + assert_eq!(Fq::zero(), a + a_neg); + assert_eq!(Fq::zero(), a_neg + a); + } +} + +#[test] +fn test_additive_commutativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let b = Fq::new_random(&mut rng); + assert_eq!(a + b, b + a); + } +} + +#[test] +fn test_multiplicative_associativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let b = Fq::new_random(&mut rng); + let c = Fq::new_random(&mut rng); + assert_eq!((a * b) * c, a * (b * c)) + } +} + +#[test] +fn test_multiplicative_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(a, a * Fq::one()); + assert_eq!(a, Fq::one() * a); + } +} + +#[test] +fn test_multiplicative_inverse() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + if a == Fq::zero() { + continue; + } + let a_inv = a.invert().unwrap(); + assert_eq!(Fq::one(), a * a_inv); + assert_eq!(Fq::one(), a_inv * a); + } +} + +#[test] +fn test_multiplicative_commutativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + let b = Fq::new_random(&mut rng); + assert_eq!(a * b, b * a); + } +} + +#[test] +fn test_multiply_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fq::new_random(&mut rng); + assert_eq!(Fq::zero(), Fq::zero() * a); + assert_eq!(Fq::zero(), a * Fq::zero()); + } +} diff --git a/tests/fr_blackbox.rs b/tests/fr_blackbox.rs new file mode 100644 index 000000000..6e36d0f85 --- /dev/null +++ b/tests/fr_blackbox.rs @@ -0,0 +1,120 @@ +mod common; + +use common::{new_rng, MyRandom, NUM_BLACK_BOX_CHECKS}; +use jubjub::*; + +#[test] +fn test_to_and_from_bytes() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(a, Fr::from_bytes(&Fr::to_bytes(&a)).unwrap()); + } +} + +#[test] +fn test_additive_associativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let b = Fr::new_random(&mut rng); + let c = Fr::new_random(&mut rng); + assert_eq!((a + b) + c, a + (b + c)) + } +} + +#[test] +fn test_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(a, a + Fr::zero()); + assert_eq!(a, Fr::zero() + a); + } +} + +#[test] +fn test_subtract_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(a, a - Fr::zero()); + assert_eq!(a, Fr::zero() - -&a); + } +} + +#[test] +fn test_additive_inverse() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let a_neg = -&a; + assert_eq!(Fr::zero(), a + a_neg); + assert_eq!(Fr::zero(), a_neg + a); + } +} + +#[test] +fn test_additive_commutativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let b = Fr::new_random(&mut rng); + assert_eq!(a + b, b + a); + } +} + +#[test] +fn test_multiplicative_associativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let b = Fr::new_random(&mut rng); + let c = Fr::new_random(&mut rng); + assert_eq!((a * b) * c, a * (b * c)) + } +} + +#[test] +fn test_multiplicative_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(a, a * Fr::one()); + assert_eq!(a, Fr::one() * a); + } +} + +#[test] +fn test_multiplicative_inverse() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + if a == Fr::zero() { + continue; + } + let a_inv = a.invert().unwrap(); + assert_eq!(Fr::one(), a * a_inv); + assert_eq!(Fr::one(), a_inv * a); + } +} + +#[test] +fn test_multiplicative_commutativity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + let b = Fr::new_random(&mut rng); + assert_eq!(a * b, b * a); + } +} + +#[test] +fn test_multiply_additive_identity() { + let mut rng = new_rng(); + for _ in 0..NUM_BLACK_BOX_CHECKS { + let a = Fr::new_random(&mut rng); + assert_eq!(Fr::zero(), Fr::zero() * a); + assert_eq!(Fr::zero(), a * Fr::zero()); + } +}