From a06216f24b488fa2a25b42366cb3d3614218a7b5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 8 Jul 2017 10:55:43 -0600 Subject: [PATCH 001/140] Initial commit --- .gitignore | 3 + Cargo.toml | 13 + LICENSE-APACHE | 202 +++++ LICENSE-MIT | 21 + README.md | 21 + src/bls12_381/README.md | 57 ++ src/bls12_381/ec.rs | 1122 +++++++++++++++++++++++++ src/bls12_381/fq.rs | 1748 +++++++++++++++++++++++++++++++++++++++ src/bls12_381/fq12.rs | 289 +++++++ src/bls12_381/fq2.rs | 504 +++++++++++ src/bls12_381/fq6.rs | 372 +++++++++ src/bls12_381/fr.rs | 1464 ++++++++++++++++++++++++++++++++ src/bls12_381/mod.rs | 459 ++++++++++ src/lib.rs | 409 +++++++++ src/tests/curve.rs | 267 ++++++ src/tests/engine.rs | 120 +++ src/tests/field.rs | 229 +++++ src/tests/mod.rs | 3 + 18 files changed, 7303 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 src/bls12_381/README.md create mode 100644 src/bls12_381/ec.rs create mode 100644 src/bls12_381/fq.rs create mode 100644 src/bls12_381/fq12.rs create mode 100644 src/bls12_381/fq2.rs create mode 100644 src/bls12_381/fq6.rs create mode 100644 src/bls12_381/fr.rs create mode 100644 src/bls12_381/mod.rs create mode 100644 src/lib.rs create mode 100644 src/tests/curve.rs create mode 100644 src/tests/engine.rs create mode 100644 src/tests/field.rs create mode 100644 src/tests/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4308d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f024106 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pairing" +version = "0.9.0" +authors = ["Sean Bowe "] +license = "MIT/Apache-2.0" + +description = "Pairing-friendly elliptic curve library" +documentation = "https://docs.rs/pairing/" +homepage = "https://github.com/ebfull/pairing" +repository = "https://github.com/ebfull/pairing" + +[dependencies] +rand = "0.3" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..1e5006d --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + 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 0000000..ed3a13f --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Sean Bowe + +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 0000000..538a5c5 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# pairing [![Crates.io](https://img.shields.io/crates/v/pairing.svg)](https://crates.io/crates/pairing) # + +This is a Rust crate for using pairing-friendly elliptic curves. Currently, only the [BLS12-381](https://z.cash/blog/new-snark-curve.html) construction is implemented. + +## [Documentation](https://docs.rs/pairing/) + +## 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/src/bls12_381/README.md b/src/bls12_381/README.md new file mode 100644 index 0000000..f54fac3 --- /dev/null +++ b/src/bls12_381/README.md @@ -0,0 +1,57 @@ +# BLS12-381 + +This is an implementation of the BLS12-381 pairing-friendly elliptic curve construction. + +## BLS12 Parameterization + +BLS12 curves are parameterized by a value *x* such that the base field modulus *q* and subgroup *r* can be computed by: + +* q = (x - 1)2 ((x4 - x2 + 1) / 3) + x +* r = (x4 - x2 + 1) + +Given primes *q* and *r* parameterized as above, we can easily construct an elliptic curve over the prime field F*q* which contains a subgroup of order *r* such that *r* | (*q*12 - 1), giving it an embedding degree of 12. Instantiating its sextic twist over an extension field Fq2 gives rise to an efficient bilinear pairing function between elements of the order *r* subgroups of either curves, into an order *r* multiplicative subgroup of Fq12. + +In zk-SNARK schemes, we require Fr with large 2n roots of unity for performing efficient fast-fourier transforms. As such, guaranteeing that large 2n | (r - 1), or equivalently that *x* has a large 2n factor, gives rise to BLS12 curves suitable for zk-SNARKs. + +Due to recent research, it is estimated by many that *q* should be approximately 384 bits to target 128-bit security. Conveniently, *r* is approximately 256 bits when *q* is approximately 384 bits, making BLS12 curves ideal for 128-bit security. It also makes them ideal for many zk-SNARK applications, as the scalar field can be used for keying material such as embedded curve constructions. + +Many curves match our descriptions, but we require some extra properties for efficiency purposes: + +* *q* should be smaller than 2383, and *r* should be smaller than 2255, so that the most significant bit is unset when using 64-bit or 32-bit limbs. This allows for cheap reductions. +* Fq12 is typically constructed using towers of extension fields. As a byproduct of [research](https://eprint.iacr.org/2011/465.pdf) for BLS curves of embedding degree 24, we can identify subfamilies of BLS12 curves (for our purposes, where x mod 72 = {16, 64}) that produce efficient extension field towers and twisting isomorphisms. +* We desire *x* of small Hamming weight, to increase the performance of the pairing function. + +## BLS12-381 Instantiation + +The BLS12-381 construction is instantiated by `x = -0xd201000000010000`, which produces the largest `q` and smallest Hamming weight of `x` that meets the above requirements. This produces: + +* q = `0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab` (381 bits) +* r = `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` (255 bits) + +Our extension field tower is constructed as follows: + +1. Fq2 is constructed as Fq(u) / (u2 - β) where β = -1. +2. Fq6 is constructed as Fq2(v) / (v3 - ξ) where ξ = u + 1 +3. Fq12 is constructed as Fq6(w) / (w2 - γ) where γ = v + +Now, we instantiate the elliptic curve E(Fq) : y2 = x3 + 4, and the elliptic curve E'(Fq2) : y2 = x3 + 4v. + +The group G1 is the *r* order subgroup of E, which has cofactor (x - 1)2 / 3. The group G2 is the *r* order subgroup of E', which has cofactor (x8 - 4x7 + 5x6 - 4x4 + 6x3 - 4x2 - 4x + 13) / 9. + +### Generators + +The generators of G1 and G2 are computed by finding the lexicographically smallest valid `x`-coordinate, and its lexicographically smallest `y`-coordinate and scaling it by the cofactor such that the result is not the point at infinity. + +#### G1 + +``` +x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507 +y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569 +``` + +#### G2 + +``` +x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 +y = 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582*u + 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 +``` diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs new file mode 100644 index 0000000..4f0daf9 --- /dev/null +++ b/src/bls12_381/ec.rs @@ -0,0 +1,1122 @@ +macro_rules! curve_impl { + ( + $projective:ident, + $affine:ident, + $prepared:ident, + $basefield:ident, + $scalarfield:ident + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + pub struct $affine { + pub(crate) x: $basefield, + pub(crate) y: $basefield, + pub(crate) infinity: bool + } + + #[derive(Copy, Clone, Debug, Eq)] + pub struct $projective { + pub(crate) x: $basefield, + pub(crate) y: $basefield, + pub(crate) z: $basefield + } + + impl PartialEq for $projective { + fn eq(&self, other: &$projective) -> bool { + if self.is_zero() { + return other.is_zero(); + } + + if other.is_zero() { + return false; + } + + // The points (X, Y, Z) and (X', Y', Z') + // are equal when (X * Z^2) = (X' * Z'^2) + // and (Y * Z^3) = (Y' * Z'^3). + + let mut z1 = self.z; + z1.square(); + let mut z2 = other.z; + z2.square(); + + let mut tmp1 = self.x; + tmp1.mul_assign(&z2); + + let mut tmp2 = other.x; + tmp2.mul_assign(&z1); + + if tmp1 != tmp2 { + return false; + } + + z1.mul_assign(&self.z); + z2.mul_assign(&other.z); + z2.mul_assign(&self.y); + z1.mul_assign(&other.y); + + if z1 != z2 { + return false; + } + + true + } + } + + impl $affine { + fn is_on_curve(&self) -> bool { + if self.is_zero() { + true + } else { + // Check that the point is on the curve + let mut y2 = self.y; + y2.square(); + + let mut x3b = self.x; + x3b.square(); + x3b.mul_assign(&self.x); + x3b.add_assign(&Self::get_coeff_b()); + + if y2 == x3b { + true + } else { + false + } + } + } + + fn is_in_correct_subgroup(&self) -> bool { + if self.mul($scalarfield::char()).is_zero() { + true + } else { + false + } + } + } + + impl CurveAffine for $affine { + type Scalar = $scalarfield; + type Base = $basefield; + type Prepared = $prepared; + type Projective = $projective; + + fn zero() -> Self { + $affine { + x: $basefield::zero(), + y: $basefield::one(), + infinity: true + } + } + + fn one() -> Self { + Self::get_generator() + } + + fn is_zero(&self) -> bool { + self.infinity + } + + fn is_valid(&self) -> bool { + self.is_on_curve() && self.is_in_correct_subgroup() + } + + fn mul::Repr>>(&self, by: S) -> $projective { + let mut res = $projective::zero(); + + for i in BitIterator::new(by.into()) + { + res.double(); + + if i { + res.add_assign_mixed(self); + } + } + + res + } + + fn negate(&mut self) { + if !self.is_zero() { + self.y.negate(); + } + } + + fn prepare(&self) -> Self::Prepared { + $prepared::from_affine(*self) + } + + fn to_projective(&self) -> $projective { + (*self).into() + } + } + + impl Rand for $projective { + fn rand(rng: &mut R) -> Self { + $affine::one().mul($scalarfield::rand(rng)) + } + } + + impl CurveProjective for $projective { + type Scalar = $scalarfield; + type Base = $basefield; + type Affine = $affine; + + // The point at infinity is always represented by + // Z = 0. + fn zero() -> Self { + $projective { + x: $basefield::zero(), + y: $basefield::one(), + z: $basefield::zero() + } + } + + fn one() -> Self { + $affine::one().into() + } + + // The point at infinity is always represented by + // Z = 0. + fn is_zero(&self) -> bool { + self.z.is_zero() + } + + fn is_normalized(&self) -> bool { + self.is_zero() || self.z == $basefield::one() + } + + fn batch_normalization(v: &mut [Self]) + { + // Montgomery’s Trick and Fast Implementation of Masked AES + // Genelle, Prouff and Quisquater + // Section 3.2 + + // First pass: compute [a, ab, abc, ...] + let mut prod = Vec::with_capacity(v.len()); + let mut tmp = $basefield::one(); + for g in v.iter_mut() + // Ignore normalized elements + .filter(|g| !g.is_normalized()) + { + tmp.mul_assign(&g.z); + prod.push(tmp); + } + + // Invert `tmp`. + tmp = tmp.inverse().unwrap(); // Guaranteed to be nonzero. + + // Second pass: iterate backwards to compute inverses + for (g, s) in v.iter_mut() + // Backwards + .rev() + // Ignore normalized elements + .filter(|g| !g.is_normalized()) + // Backwards, skip last element, fill in one for last term. + .zip(prod.into_iter().rev().skip(1).chain(Some($basefield::one()))) + { + // tmp := tmp * g.z; g.z := tmp * s = 1/z + let mut newtmp = tmp; + newtmp.mul_assign(&g.z); + g.z = tmp; + g.z.mul_assign(&s); + tmp = newtmp; + } + + // Perform affine transformations + for g in v.iter_mut() + .filter(|g| !g.is_normalized()) + { + let mut z = g.z; // 1/z + z.square(); // 1/z^2 + g.x.mul_assign(&z); // x/z^2 + z.mul_assign(&g.z); // 1/z^3 + g.y.mul_assign(&z); // y/z^3 + g.z = $basefield::one(); // z = 1 + } + } + + fn double(&mut self) { + if self.is_zero() { + return; + } + + // Other than the point at infinity, no points on E or E' + // can double to equal the point at infinity, as y=0 is + // never true for points on the curve. (-4 and -4u-4 + // are not cubic residue in their respective fields.) + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + + // A = X1^2 + let mut a = self.x; + a.square(); + + // B = Y1^2 + let mut b = self.y; + b.square(); + + // C = B^2 + let mut c = b; + c.square(); + + // D = 2*((X1+B)2-A-C) + let mut d = self.x; + d.add_assign(&b); + d.square(); + d.sub_assign(&a); + d.sub_assign(&c); + d.double(); + + // E = 3*A + let mut e = a; + e.double(); + e.add_assign(&a); + + // F = E^2 + let mut f = e; + f.square(); + + // Z3 = 2*Y1*Z1 + self.z.mul_assign(&self.y); + self.z.double(); + + // X3 = F-2*D + self.x = f; + self.x.sub_assign(&d); + self.x.sub_assign(&d); + + // Y3 = E*(D-X3)-8*C + self.y = d; + self.y.sub_assign(&self.x); + self.y.mul_assign(&e); + c.double(); + c.double(); + c.double(); + self.y.sub_assign(&c); + } + + fn add_assign(&mut self, other: &Self) { + if self.is_zero() { + *self = *other; + return; + } + + if other.is_zero() { + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl + + // Z1Z1 = Z1^2 + let mut z1z1 = self.z; + z1z1.square(); + + // Z2Z2 = Z2^2 + let mut z2z2 = other.z; + z2z2.square(); + + // U1 = X1*Z2Z2 + let mut u1 = self.x; + u1.mul_assign(&z2z2); + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2.mul_assign(&z1z1); + + // S1 = Y1*Z2*Z2Z2 + let mut s1 = self.y; + s1.mul_assign(&other.z); + s1.mul_assign(&z2z2); + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2.mul_assign(&self.z); + s2.mul_assign(&z1z1); + + if u1 == u2 && s1 == s2 { + // The two points are equal, so we double. + self.double(); + } else { + // If we're adding -a and a together, self.z becomes zero as H becomes zero. + + // H = U2-U1 + let mut h = u2; + h.sub_assign(&u1); + + // I = (2*H)^2 + let mut i = h; + i.double(); + i.square(); + + // J = H*I + let mut j = h; + j.mul_assign(&i); + + // r = 2*(S2-S1) + let mut r = s2; + r.sub_assign(&s1); + r.double(); + + // V = U1*I + let mut v = u1; + v.mul_assign(&i); + + // X3 = r^2 - J - 2*V + self.x = r; + self.x.square(); + self.x.sub_assign(&j); + self.x.sub_assign(&v); + self.x.sub_assign(&v); + + // Y3 = r*(V - X3) - 2*S1*J + self.y = v; + self.y.sub_assign(&self.x); + self.y.mul_assign(&r); + s1.mul_assign(&j); // S1 = S1 * J * 2 + s1.double(); + self.y.sub_assign(&s1); + + // Z3 = ((Z1+Z2)^2 - Z1Z1 - Z2Z2)*H + self.z.add_assign(&other.z); + self.z.square(); + self.z.sub_assign(&z1z1); + self.z.sub_assign(&z2z2); + self.z.mul_assign(&h); + } + } + + fn add_assign_mixed(&mut self, other: &Self::Affine) { + if other.is_zero() { + return; + } + + if self.is_zero() { + self.x = other.x; + self.y = other.y; + self.z = $basefield::one(); + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-madd-2007-bl + + // Z1Z1 = Z1^2 + let mut z1z1 = self.z; + z1z1.square(); + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2.mul_assign(&z1z1); + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2.mul_assign(&self.z); + s2.mul_assign(&z1z1); + + if self.x == u2 && self.y == s2 { + // The two points are equal, so we double. + self.double(); + } else { + // If we're adding -a and a together, self.z becomes zero as H becomes zero. + + // H = U2-X1 + let mut h = u2; + h.sub_assign(&self.x); + + // HH = H^2 + let mut hh = h; + hh.square(); + + // I = 4*HH + let mut i = hh; + i.double(); + i.double(); + + // J = H*I + let mut j = h; + j.mul_assign(&i); + + // r = 2*(S2-Y1) + let mut r = s2; + r.sub_assign(&self.y); + r.double(); + + // V = X1*I + let mut v = self.x; + v.mul_assign(&i); + + // X3 = r^2 - J - 2*V + self.x = r; + self.x.square(); + self.x.sub_assign(&j); + self.x.sub_assign(&v); + self.x.sub_assign(&v); + + // Y3 = r*(V-X3)-2*Y1*J + j.mul_assign(&self.y); // J = 2*Y1*J + j.double(); + self.y = v; + self.y.sub_assign(&self.x); + self.y.mul_assign(&r); + self.y.sub_assign(&j); + + // Z3 = (Z1+H)^2-Z1Z1-HH + self.z.add_assign(&h); + self.z.square(); + self.z.sub_assign(&z1z1); + self.z.sub_assign(&hh); + } + } + + fn negate(&mut self) { + if !self.is_zero() { + self.y.negate() + } + } + + fn mul_assign::Repr>>(&mut self, other: S) { + let mut res = Self::zero(); + + for i in BitIterator::new(other.into()) + { + res.double(); + + if i { + res.add_assign(self); + } + } + + *self = res; + } + + fn to_affine(&self) -> $affine { + (*self).into() + } + } + + // The affine point X, Y is represented in the jacobian + // coordinates with Z = 1. + impl From<$affine> for $projective { + fn from(p: $affine) -> $projective { + if p.is_zero() { + $projective::zero() + } else { + $projective { + x: p.x, + y: p.y, + z: $basefield::one() + } + } + } + } + + // The projective point X, Y, Z is represented in the affine + // coordinates as X/Z^2, Y/Z^3. + impl From<$projective> for $affine { + fn from(p: $projective) -> $affine { + if p.is_zero() { + $affine::zero() + } else if p.z == $basefield::one() { + // If Z is one, the point is already normalized. + $affine { + x: p.x, + y: p.y, + infinity: false + } + } else { + // Z is nonzero, so it must have an inverse in a field. + let zinv = p.z.inverse().unwrap(); + let mut zinv_powered = zinv; + zinv_powered.square(); + + // X/Z^2 + let mut x = p.x; + x.mul_assign(&zinv_powered); + + // Y/Z^3 + let mut y = p.y; + zinv_powered.mul_assign(&zinv); + y.mul_assign(&zinv_powered); + + $affine { + x: x, + y: y, + infinity: false + } + } + } + } + } +} + +pub mod g1 { + use rand::{Rand, Rng}; + use super::super::{Fq, Fr}; + use ::{CurveProjective, CurveAffine, PrimeField, Field, BitIterator}; + + curve_impl!(G1, G1Affine, G1Prepared, Fq, Fr); + + impl G1Affine { + fn get_generator() -> Self { + G1Affine { + x: super::super::fq::G1_GENERATOR_X, + y: super::super::fq::G1_GENERATOR_Y, + infinity: false + } + } + + fn get_coeff_b() -> Fq { + super::super::fq::B_COEFF + } + } + + #[derive(Clone)] + pub struct G1Prepared(pub(crate) G1Affine); + + impl G1Prepared { + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + pub fn from_affine(p: G1Affine) -> Self { + G1Prepared(p) + } + } + + #[cfg(test)] + use super::super::{FqRepr}; + + #[test] + fn g1_generator() { + use ::SqrtField; + + let mut x = Fq::zero(); + let mut i = 0; + loop { + // y^2 = x^3 + b + let mut rhs = x; + rhs.square(); + rhs.mul_assign(&x); + rhs.add_assign(&G1Affine::get_coeff_b()); + + if let Some(y) = rhs.sqrt() { + let yrepr = y.into_repr(); + let mut negy = y; + negy.negate(); + let negyrepr = negy.into_repr(); + + let p = G1Affine { + x: x, + y: if yrepr < negyrepr { y } else { negy }, + infinity: false + }; + + assert!(!p.is_valid()); + + let mut g1 = G1::zero(); + + // Cofactor of G1 is 76329603384216526031706109802092473003. + // Calculated by: ((x-1)**2) // 3 + // where x is the BLS parameter. + for b in "111001011011001000110000000000010101010101010111100001010101101000110000000000101010101010101100000000000000001010101010101011" + .chars() + .map(|c| c == '1') + { + g1.double(); + + if b { + g1.add_assign_mixed(&p); + } + } + + if !g1.is_zero() { + assert_eq!(i, 4); + let g1 = G1Affine::from(g1); + + assert!(g1.is_valid()); + + assert_eq!(g1, G1Affine::one()); + break; + } + } + + i += 1; + x.add_assign(&Fq::one()); + } + } + + #[test] + fn g1_test_is_valid() { + // Reject point on isomorphic twist (b = 24) + { + let p = G1Affine { + x: Fq::from_repr(FqRepr([0xc58d887b66c035dc, 0x10cbfd301d553822, 0xaf23e064f1131ee5, 0x9fe83b1b4a5d648d, 0xf583cc5a508f6a40, 0xc3ad2aefde0bb13])).unwrap(), + y: Fq::from_repr(FqRepr([0x60aa6f9552f03aae, 0xecd01d5181300d35, 0x8af1cdb8aa8ce167, 0xe760f57922998c9d, 0x953703f5795a39e5, 0xfe3ae0922df702c])).unwrap(), + infinity: false + }; + assert!(!p.is_on_curve()); + assert!(p.is_in_correct_subgroup()); + assert!(!p.is_valid()); + } + + // Reject point on a twist (b = 3) + { + let p = G1Affine { + x: Fq::from_repr(FqRepr([0xee6adf83511e15f5, 0x92ddd328f27a4ba6, 0xe305bd1ac65adba7, 0xea034ee2928b30a8, 0xbd8833dc7c79a7f7, 0xe45c9f0c0438675])).unwrap(), + y: Fq::from_repr(FqRepr([0x3b450eb1ab7b5dad, 0xa65cb81e975e8675, 0xaa548682b21726e5, 0x753ddf21a2601d20, 0x532d0b640bd3ff8b, 0x118d2c543f031102])).unwrap(), + infinity: false + }; + assert!(!p.is_on_curve()); + assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_valid()); + } + + // Reject point in an invalid subgroup + // There is only one r-order subgroup, as r does not divide the cofactor. + { + let p = G1Affine { + x: Fq::from_repr(FqRepr([0x76e1c971c6db8fe8, 0xe37e1a610eff2f79, 0x88ae9c499f46f0c0, 0xf35de9ce0d6b4e84, 0x265bddd23d1dec54, 0x12a8778088458308])).unwrap(), + y: Fq::from_repr(FqRepr([0x8a22defa0d526256, 0xc57ca55456fcb9ae, 0x1ba194e89bab2610, 0x921beef89d4f29df, 0x5b6fda44ad85fa78, 0xed74ab9f302cbe0])).unwrap(), + infinity: false + }; + assert!(p.is_on_curve()); + assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_valid()); + } + } + + #[test] + fn test_g1_addition_correctness() { + let mut p = G1 { + x: Fq::from_repr(FqRepr([0x47fd1f891d6e8bbf, 0x79a3b0448f31a2aa, 0x81f3339e5f9968f, 0x485e77d50a5df10d, 0x4c6fcac4b55fd479, 0x86ed4d9906fb064])).unwrap(), + y: Fq::from_repr(FqRepr([0xd25ee6461538c65, 0x9f3bbb2ecd3719b9, 0xa06fd3f1e540910d, 0xcefca68333c35288, 0x570c8005f8573fa6, 0x152ca696fe034442])).unwrap(), + z: Fq::one() + }; + + p.add_assign(&G1 { + x: Fq::from_repr(FqRepr([0xeec78f3096213cbf, 0xa12beb1fea1056e6, 0xc286c0211c40dd54, 0x5f44314ec5e3fb03, 0x24e8538737c6e675, 0x8abd623a594fba8])).unwrap(), + y: Fq::from_repr(FqRepr([0x6b0528f088bb7044, 0x2fdeb5c82917ff9e, 0x9a5181f2fac226ad, 0xd65104c6f95a872a, 0x1f2998a5a9c61253, 0xe74846154a9e44])).unwrap(), + z: Fq::one() + }); + + let p = G1Affine::from(p); + + assert_eq!(p, G1Affine { + x: Fq::from_repr(FqRepr([0x6dd3098f22235df, 0xe865d221c8090260, 0xeb96bb99fa50779f, 0xc4f9a52a428e23bb, 0xd178b28dd4f407ef, 0x17fb8905e9183c69])).unwrap(), + y: Fq::from_repr(FqRepr([0xd0de9d65292b7710, 0xf6a05f2bcf1d9ca7, 0x1040e27012f20b64, 0xeec8d1a5b7466c58, 0x4bc362649dce6376, 0x430cbdc5455b00a])).unwrap(), + infinity: false + }); + } + + #[test] + fn test_g1_doubling_correctness() { + let mut p = G1 { + x: Fq::from_repr(FqRepr([0x47fd1f891d6e8bbf, 0x79a3b0448f31a2aa, 0x81f3339e5f9968f, 0x485e77d50a5df10d, 0x4c6fcac4b55fd479, 0x86ed4d9906fb064])).unwrap(), + y: Fq::from_repr(FqRepr([0xd25ee6461538c65, 0x9f3bbb2ecd3719b9, 0xa06fd3f1e540910d, 0xcefca68333c35288, 0x570c8005f8573fa6, 0x152ca696fe034442])).unwrap(), + z: Fq::one() + }; + + p.double(); + + let p = G1Affine::from(p); + + assert_eq!(p, G1Affine { + x: Fq::from_repr(FqRepr([0xf939ddfe0ead7018, 0x3b03942e732aecb, 0xce0e9c38fdb11851, 0x4b914c16687dcde0, 0x66c8baf177d20533, 0xaf960cff3d83833])).unwrap(), + y: Fq::from_repr(FqRepr([0x3f0675695f5177a8, 0x2b6d82ae178a1ba0, 0x9096380dd8e51b11, 0x1771a65b60572f4e, 0x8b547c1313b27555, 0x135075589a687b1e])).unwrap(), + infinity: false + }); + } + + #[test] + fn test_g1_same_y() { + // Test the addition of two points with different x coordinates + // but the same y coordinate. + + // x1 = 128100205326445210408953809171070606737678357140298133325128175840781723996595026100005714405541449960643523234125 + // x2 = 3821408151224848222394078037104966877485040835569514006839342061575586899845797797516352881516922679872117658572470 + // y = 2291134451313223670499022936083127939567618746216464377735567679979105510603740918204953301371880765657042046687078 + + let a = G1Affine { + x: Fq::from_repr(FqRepr([0xea431f2cc38fc94d, 0x3ad2354a07f5472b, 0xfe669f133f16c26a, 0x71ffa8021531705, 0x7418d484386d267, 0xd5108d8ff1fbd6])).unwrap(), + y: Fq::from_repr(FqRepr([0xa776ccbfe9981766, 0x255632964ff40f4a, 0xc09744e650b00499, 0x520f74773e74c8c3, 0x484c8fc982008f0, 0xee2c3d922008cc6])).unwrap(), + infinity: false + }; + + let b = G1Affine { + x: Fq::from_repr(FqRepr([0xe06cdb156b6356b6, 0xd9040b2d75448ad9, 0xe702f14bb0e2aca5, 0xc6e05201e5f83991, 0xf7c75910816f207c, 0x18d4043e78103106])).unwrap(), + y: Fq::from_repr(FqRepr([0xa776ccbfe9981766, 0x255632964ff40f4a, 0xc09744e650b00499, 0x520f74773e74c8c3, 0x484c8fc982008f0, 0xee2c3d922008cc6])).unwrap(), + infinity: false + }; + + // Expected + // x = 52901198670373960614757979459866672334163627229195745167587898707663026648445040826329033206551534205133090753192 + // y = 1711275103908443722918766889652776216989264073722543507596490456144926139887096946237734327757134898380852225872709 + let c = G1Affine { + x: Fq::from_repr(FqRepr([0xef4f05bdd10c8aa8, 0xad5bf87341a2df9, 0x81c7424206b78714, 0x9676ff02ec39c227, 0x4c12c15d7e55b9f3, 0x57fd1e317db9bd])).unwrap(), + y: Fq::from_repr(FqRepr([0x1288334016679345, 0xf955cd68615ff0b5, 0xa6998dbaa600f18a, 0x1267d70db51049fb, 0x4696deb9ab2ba3e7, 0xb1e4e11177f59d4])).unwrap(), + infinity: false + }; + + assert!(a.is_valid()); + assert!(b.is_valid()); + assert!(c.is_valid()); + + let mut tmp1 = a.to_projective(); + tmp1.add_assign(&b.to_projective()); + assert_eq!(tmp1.to_affine(), c); + assert_eq!(tmp1, c.to_projective()); + + let mut tmp2 = a.to_projective(); + tmp2.add_assign_mixed(&b); + assert_eq!(tmp2.to_affine(), c); + assert_eq!(tmp2, c.to_projective()); + } + + #[test] + fn g1_curve_tests() { + ::tests::curve::curve_tests::(); + } + + #[cfg(test)] + use rand::{SeedableRng, XorShiftRng}; + + #[bench] + fn bench_g1_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, Fr)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g1_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G1)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), G1::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g1_add_assign_mixed(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G1Affine)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), G1::rand(&mut rng).into())).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign_mixed(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } +} + +pub mod g2 { + use rand::{Rand, Rng}; + use super::super::{Fq2, Fr}; + use ::{CurveProjective, CurveAffine, PrimeField, Field, BitIterator}; + + curve_impl!(G2, G2Affine, G2Prepared, Fq2, Fr); + + impl G2Affine { + fn get_generator() -> Self { + G2Affine { + x: Fq2 { + c0: super::super::fq::G2_GENERATOR_X_C0, + c1: super::super::fq::G2_GENERATOR_X_C1 + }, + y: Fq2 { + c0: super::super::fq::G2_GENERATOR_Y_C0, + c1: super::super::fq::G2_GENERATOR_Y_C1 + }, + infinity: false + } + } + + fn get_coeff_b() -> Fq2 { + Fq2 { + c0: super::super::fq::B_COEFF, + c1: super::super::fq::B_COEFF + } + } + } + + #[derive(Clone)] + pub struct G2Prepared { + pub(crate) coeffs: Vec<(Fq2, Fq2, Fq2)>, + pub(crate) infinity: bool + } + + #[cfg(test)] + use super::super::{Fq, FqRepr}; + + #[test] + fn g2_generator() { + use ::SqrtField; + + let mut x = Fq2::zero(); + let mut i = 0; + loop { + // y^2 = x^3 + b + let mut rhs = x; + rhs.square(); + rhs.mul_assign(&x); + rhs.add_assign(&G2Affine::get_coeff_b()); + + if let Some(y) = rhs.sqrt() { + let yrepr = y.c1.into_repr(); + let mut negy = y; + negy.negate(); + let negyrepr = negy.c1.into_repr(); + + let p = G2Affine { + x: x, + y: if yrepr < negyrepr { y } else { negy }, + infinity: false + }; + + assert!(!p.is_valid()); + + let mut g2 = G2::zero(); + + // Cofactor of G2 is 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109. + // Calculated by: ((x**8) - (4 * (x**7)) + (5 * (x**6)) - (4 * (x**4)) + (6 * (x**3)) - (4 * (x**2)) - (4*x) + 13) // 9 + // where x is the BLS parameter. + for b in "101110101010100001110101001010101000001010011100111111100010000100100011101010100000111100100101000011101101010001000000010110011011001000111011110010001010100011100001000010110101011101010100110100010100010000001011011001011100101101001111101110111111010011000101000111100011100101101001101100111101000001011101111001000010101001101111110001010010011101001100110100100011010111000010110000101101110110001101110011110000110111100001100011100001100111100011100001110001110001100011100011100100011100011100101" + .chars() + .map(|c| c == '1') + { + g2.double(); + + if b { + g2.add_assign_mixed(&p); + } + } + + if !g2.is_zero() { + assert_eq!(i, 2); + let g2 = G2Affine::from(g2); + + assert!(g2.is_valid()); + + assert_eq!(g2, G2Affine::one()); + break; + } + } + + i += 1; + x.add_assign(&Fq2::one()); + } + } + + #[test] + fn g2_test_is_valid() { + // Reject point on isomorphic twist (b = 3 * (u + 1)) + { + let p = G2Affine { + x: Fq2 { + c0: Fq::from_repr(FqRepr([0xa757072d9fa35ba9, 0xae3fb2fb418f6e8a, 0xc1598ec46faa0c7c, 0x7a17a004747e3dbe, 0xcc65406a7c2e5a73, 0x10b8c03d64db4d0c])).unwrap(), + c1: Fq::from_repr(FqRepr([0xd30e70fe2f029778, 0xda30772df0f5212e, 0x5b47a9ff9a233a50, 0xfb777e5b9b568608, 0x789bac1fec71a2b9, 0x1342f02e2da54405])).unwrap() + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([0xfe0812043de54dca, 0xe455171a3d47a646, 0xa493f36bc20be98a, 0x663015d9410eb608, 0x78e82a79d829a544, 0x40a00545bb3c1e])).unwrap(), + c1: Fq::from_repr(FqRepr([0x4709802348e79377, 0xb5ac4dc9204bcfbd, 0xda361c97d02f42b2, 0x15008b1dc399e8df, 0x68128fd0548a3829, 0x16a613db5c873aaa])).unwrap() + }, + infinity: false + }; + assert!(!p.is_on_curve()); + assert!(p.is_in_correct_subgroup()); + assert!(!p.is_valid()); + } + + // Reject point on a twist (b = 2 * (u + 1)) + { + let p = G2Affine { + x: Fq2 { + c0: Fq::from_repr(FqRepr([0xf4fdfe95a705f917, 0xc2914df688233238, 0x37c6b12cca35a34b, 0x41abba710d6c692c, 0xffcc4b2b62ce8484, 0x6993ec01b8934ed])).unwrap(), + c1: Fq::from_repr(FqRepr([0xb94e92d5f874e26, 0x44516408bc115d95, 0xe93946b290caa591, 0xa5a0c2b7131f3555, 0x83800965822367e7, 0x10cf1d3ad8d90bfa])).unwrap() + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([0xbf00334c79701d97, 0x4fe714f9ff204f9a, 0xab70b28002f3d825, 0x5a9171720e73eb51, 0x38eb4fd8d658adb7, 0xb649051bbc1164d])).unwrap(), + c1: Fq::from_repr(FqRepr([0x9225814253d7df75, 0xc196c2513477f887, 0xe05e2fbd15a804e0, 0x55f2b8efad953e04, 0x7379345eda55265e, 0x377f2e6208fd4cb])).unwrap() + }, + infinity: false + }; + assert!(!p.is_on_curve()); + assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_valid()); + } + + // Reject point in an invalid subgroup + // There is only one r-order subgroup, as r does not divide the cofactor. + { + let p = G2Affine { + x: Fq2 { + c0: Fq::from_repr(FqRepr([0x262cea73ea1906c, 0x2f08540770fabd6, 0x4ceb92d0a76057be, 0x2199bc19c48c393d, 0x4a151b732a6075bf, 0x17762a3b9108c4a7])).unwrap(), + c1: Fq::from_repr(FqRepr([0x26f461e944bbd3d1, 0x298f3189a9cf6ed6, 0x74328ad8bc2aa150, 0x7e147f3f9e6e241, 0x72a9b63583963fff, 0x158b0083c000462])).unwrap() + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([0x91fb0b225ecf103b, 0x55d42edc1dc46ba0, 0x43939b11997b1943, 0x68cad19430706b4d, 0x3ccfb97b924dcea8, 0x1660f93434588f8d])).unwrap(), + c1: Fq::from_repr(FqRepr([0xaaed3985b6dcb9c7, 0xc1e985d6d898d9f4, 0x618bd2ac3271ac42, 0x3940a2dbb914b529, 0xbeb88137cf34f3e7, 0x1699ee577c61b694])).unwrap() + }, + infinity: false + }; + assert!(p.is_on_curve()); + assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_valid()); + } + } + + #[test] + fn test_g2_addition_correctness() { + let mut p = G2 { + x: Fq2 { + c0: Fq::from_repr(FqRepr([0x6c994cc1e303094e, 0xf034642d2c9e85bd, 0x275094f1352123a9, 0x72556c999f3707ac, 0x4617f2e6774e9711, 0x100b2fe5bffe030b])).unwrap(), + c1: Fq::from_repr(FqRepr([0x7a33555977ec608, 0xe23039d1fe9c0881, 0x19ce4678aed4fcb5, 0x4637c4f417667e2e, 0x93ebe7c3e41f6acc, 0xde884f89a9a371b])).unwrap() + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([0xe073119472e1eb62, 0x44fb3391fe3c9c30, 0xaa9b066d74694006, 0x25fd427b4122f231, 0xd83112aace35cae, 0x191b2432407cbb7f])).unwrap(), + c1: Fq::from_repr(FqRepr([0xf68ae82fe97662f5, 0xe986057068b50b7d, 0x96c30f0411590b48, 0x9eaa6d19de569196, 0xf6a03d31e2ec2183, 0x3bdafaf7ca9b39b])).unwrap() + }, + z: Fq2::one() + }; + + p.add_assign(&G2 { + x: Fq2 { + c0: Fq::from_repr(FqRepr([0xa8c763d25910bdd3, 0x408777b30ca3add4, 0x6115fcc12e2769e, 0x8e73a96b329ad190, 0x27c546f75ee1f3ab, 0xa33d27add5e7e82])).unwrap(), + c1: Fq::from_repr(FqRepr([0x93b1ebcd54870dfe, 0xf1578300e1342e11, 0x8270dca3a912407b, 0x2089faf462438296, 0x828e5848cd48ea66, 0x141ecbac1deb038b])).unwrap() + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([0xf5d2c28857229c3f, 0x8c1574228757ca23, 0xe8d8102175f5dc19, 0x2767032fc37cc31d, 0xd5ee2aba84fd10fe, 0x16576ccd3dd0a4e8])).unwrap(), + c1: Fq::from_repr(FqRepr([0x4da9b6f6a96d1dd2, 0x9657f7da77f1650e, 0xbc150712f9ffe6da, 0x31898db63f87363a, 0xabab040ddbd097cc, 0x11ad236b9ba02990])).unwrap() + }, + z: Fq2::one() + }); + + let p = G2Affine::from(p); + + assert_eq!(p, G2Affine { + x: Fq2 { + c0: Fq::from_repr(FqRepr([0xcde7ee8a3f2ac8af, 0xfc642eb35975b069, 0xa7de72b7dd0e64b7, 0xf1273e6406eef9cc, 0xababd760ff05cb92, 0xd7c20456617e89])).unwrap(), + c1: Fq::from_repr(FqRepr([0xd1a50b8572cbd2b8, 0x238f0ac6119d07df, 0x4dbe924fe5fd6ac2, 0x8b203284c51edf6b, 0xc8a0b730bbb21f5e, 0x1a3b59d29a31274])).unwrap() + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([0x9e709e78a8eaa4c9, 0xd30921c93ec342f4, 0x6d1ef332486f5e34, 0x64528ab3863633dc, 0x159384333d7cba97, 0x4cb84741f3cafe8])).unwrap(), + c1: Fq::from_repr(FqRepr([0x242af0dc3640e1a4, 0xe90a73ad65c66919, 0x2bd7ca7f4346f9ec, 0x38528f92b689644d, 0xb6884deec59fb21f, 0x3c075d3ec52ba90])).unwrap() + }, + infinity: false + }); + } + + #[test] + fn test_g2_doubling_correctness() { + let mut p = G2 { + x: Fq2 { + c0: Fq::from_repr(FqRepr([0x6c994cc1e303094e, 0xf034642d2c9e85bd, 0x275094f1352123a9, 0x72556c999f3707ac, 0x4617f2e6774e9711, 0x100b2fe5bffe030b])).unwrap(), + c1: Fq::from_repr(FqRepr([0x7a33555977ec608, 0xe23039d1fe9c0881, 0x19ce4678aed4fcb5, 0x4637c4f417667e2e, 0x93ebe7c3e41f6acc, 0xde884f89a9a371b])).unwrap() + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([0xe073119472e1eb62, 0x44fb3391fe3c9c30, 0xaa9b066d74694006, 0x25fd427b4122f231, 0xd83112aace35cae, 0x191b2432407cbb7f])).unwrap(), + c1: Fq::from_repr(FqRepr([0xf68ae82fe97662f5, 0xe986057068b50b7d, 0x96c30f0411590b48, 0x9eaa6d19de569196, 0xf6a03d31e2ec2183, 0x3bdafaf7ca9b39b])).unwrap() + }, + z: Fq2::one() + }; + + p.double(); + + let p = G2Affine::from(p); + + assert_eq!(p, G2Affine { + x: Fq2 { + c0: Fq::from_repr(FqRepr([0x91ccb1292727c404, 0x91a6cb182438fad7, 0x116aee59434de902, 0xbcedcfce1e52d986, 0x9755d4a3926e9862, 0x18bab73760fd8024])).unwrap(), + c1: Fq::from_repr(FqRepr([0x4e7c5e0a2ae5b99e, 0x96e582a27f028961, 0xc74d1cf4ef2d5926, 0xeb0cf5e610ef4fe7, 0x7b4c2bae8db6e70b, 0xf136e43909fca0])).unwrap() + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([0x954d4466ab13e58, 0x3ee42eec614cf890, 0x853bb1d28877577e, 0xa5a2a51f7fde787b, 0x8b92866bc6384188, 0x81a53fe531d64ef])).unwrap(), + c1: Fq::from_repr(FqRepr([0x4c5d607666239b34, 0xeddb5f48304d14b3, 0x337167ee6e8e3cb6, 0xb271f52f12ead742, 0x244e6c2015c83348, 0x19e2deae6eb9b441])).unwrap() + }, + infinity: false + }); + } + + #[test] + fn g2_curve_tests() { + ::tests::curve::curve_tests::(); + } + + #[cfg(test)] + use rand::{SeedableRng, XorShiftRng}; + + #[bench] + fn bench_g2_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, Fr)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g2_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, G2)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), G2::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g2_add_assign_mixed(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, G2Affine)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), G2::rand(&mut rng).into())).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign_mixed(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } +} + +pub use self::g1::*; +pub use self::g2::*; diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs new file mode 100644 index 0000000..503d737 --- /dev/null +++ b/src/bls12_381/fq.rs @@ -0,0 +1,1748 @@ +use ::{Field, PrimeField, SqrtField, PrimeFieldRepr}; +use super::fq2::Fq2; + +// q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 +const MODULUS: FqRepr = FqRepr([0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); + +// The number of bits needed to represent the modulus. +const MODULUS_BITS: u32 = 381; + +// The number of bits that must be shaved from the beginning of +// the representation when randomly sampling. +const REPR_SHAVE_BITS: u32 = 3; + +// R = 2**384 % q +const R: FqRepr = FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493]); + +// R2 = R^2 % q +const R2: FqRepr = FqRepr([0xf4df1f341c341746, 0xa76e6a609d104f1, 0x8de5476c4c95b6d5, 0x67eb88a9939d83c0, 0x9a793e85b519952d, 0x11988fe592cae3aa]); + +// INV = -(q^{-1} mod q) mod q +const INV: u64 = 0x89f3fffcfffcfffd; + +// GENERATOR = 2 (multiplicative generator of q-1 order, that is also quadratic nonresidue) +const GENERATOR: FqRepr = FqRepr([0x321300000006554f, 0xb93c0018d6c40005, 0x57605e0db0ddbb51, 0x8b256521ed1f9bcb, 0x6cf28d7901622c03, 0x11ebab9dbb81e28c]); + +// 2^s * t = MODULUS - 1 with t odd +const S: usize = 1; + +// 2^s root of unity computed by GENERATOR^t +const ROOT_OF_UNITY: FqRepr = FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206]); + +// B coefficient of BLS12-381 curve, 4. +pub const B_COEFF: Fq = Fq(FqRepr([0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478fe97a6b0a807f, 0xb1d37ebee6ba24d7, 0x8ec9733bbf78ab2f, 0x9d645513d83de7e])); + +// The generators of G1/G2 are computed by finding the lexicographically smallest valid x coordinate, +// and its lexicographically smallest y coordinate and multiplying it by the cofactor such that the +// result is nonzero. + +// Generator of G1 +// x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507 +// y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569 +pub const G1_GENERATOR_X: Fq = Fq(FqRepr([0x5cb38790fd530c16, 0x7817fc679976fff5, 0x154f95c7143ba1c1, 0xf0ae6acdf3d0e747, 0xedce6ecc21dbf440, 0x120177419e0bfb75])); +pub const G1_GENERATOR_Y: Fq = Fq(FqRepr([0xbaac93d50ce72271, 0x8c22631a7918fd8e, 0xdd595f13570725ce, 0x51ac582950405194, 0xe1c8c3fad0059c0, 0xbbc3efc5008a26a])); + +// Generator of G2 +// x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 +// y = 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582*u + 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 +pub const G2_GENERATOR_X_C0: Fq = Fq(FqRepr([0xf5f28fa202940a10, 0xb3f5fb2687b4961a, 0xa1a893b53e2ae580, 0x9894999d1a3caee9, 0x6f67b7631863366b, 0x58191924350bcd7])); +pub const G2_GENERATOR_X_C1: Fq = Fq(FqRepr([0xa5a9c0759e23f606, 0xaaa0c59dbccd60c3, 0x3bb17e18e2867806, 0x1b1ab6cc8541b367, 0xc2b6ed0ef2158547, 0x11922a097360edf3])); +pub const G2_GENERATOR_Y_C0: Fq = Fq(FqRepr([0x4c730af860494c4a, 0x597cfa1f5e369c5a, 0xe7e6856caa0a635a, 0xbbefb5e96e0d495f, 0x7d3a975f0ef25a2, 0x83fd8e7e80dae5])); +pub const G2_GENERATOR_Y_C1: Fq = Fq(FqRepr([0xadc0fc92df64b05d, 0x18aa270a2b1461dc, 0x86adac6a3be4eba0, 0x79495c4ec93da33a, 0xe7175850a43ccaed, 0xb2bc2a163de1bf2])); + +// Coefficients for the Frobenius automorphism. +pub const FROBENIUS_COEFF_FQ2_C1: [Fq; 2] = [ + // Fq(-1)**(((q^0) - 1) / 2) + Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])), + // Fq(-1)**(((q^1) - 1) / 2) + Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])) +]; + +pub const FROBENIUS_COEFF_FQ6_C1: [Fq2; 6] = [ + // Fq2(u + 1)**(((q^0) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^1) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), + c1: Fq(FqRepr([0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x3f97d6e83d050d2, 0x18f0206554638741])) + }, + // Fq2(u + 1)**(((q^2) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x51ba4ab241b6160])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^3) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), + c1: Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])) + }, + // Fq2(u + 1)**(((q^4) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x3f97d6e83d050d2, 0x18f0206554638741])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^5) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), + c1: Fq(FqRepr([0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x51ba4ab241b6160])) + } +]; + +pub const FROBENIUS_COEFF_FQ6_C2: [Fq2; 6] = [ + // Fq2(u + 1)**(((2q^0) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((2q^1) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([0x890dc9e4867545c3, 0x2af322533285a5d5, 0x50880866309b7e2c, 0xa20d1b8c7e881024, 0x14e4f04fe2db9068, 0x14e56d3f1564853a])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((2q^2) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x3f97d6e83d050d2, 0x18f0206554638741])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((2q^3) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((2q^4) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x51ba4ab241b6160])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((2q^5) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([0xecfb361b798dba3a, 0xc100ddb891865a2c, 0xec08ff1232bda8e, 0xd5c13cc6f1ca4721, 0x47222a47bf7b5c04, 0x110f184e51c5f59])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + } +]; + +// non_residue^((modulus^i-1)/6) for i=0,...,11 +pub const FROBENIUS_COEFF_FQ12_C1: [Fq2; 12] = [ + // Fq2(u + 1)**(((q^0) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^1) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x7089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, 0xa35baecab2dc29ee, 0x1ce393ea5daace4d, 0x8f2220fb0fb66eb])), + c1: Fq(FqRepr([0xb2f66aad4ce5d646, 0x5842a06bfc497cec, 0xcf4895d42599d394, 0xc11b9cba40a8e8d0, 0x2e3813cbe5a0de89, 0x110eefda88847faf])) + }, + // Fq2(u + 1)**(((q^2) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0xecfb361b798dba3a, 0xc100ddb891865a2c, 0xec08ff1232bda8e, 0xd5c13cc6f1ca4721, 0x47222a47bf7b5c04, 0x110f184e51c5f59])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^3) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x3e2f585da55c9ad1, 0x4294213d86c18183, 0x382844c88b623732, 0x92ad2afd19103e18, 0x1d794e4fac7cf0b9, 0xbd592fc7d825ec8])), + c1: Fq(FqRepr([0x7bcfa7a25aa30fda, 0xdc17dec12a927e7c, 0x2f088dd86b4ebef1, 0xd1ca2087da74d4a7, 0x2da2596696cebc1d, 0xe2b7eedbbfd87d2])) + }, + // Fq2(u + 1)**(((q^4) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x51ba4ab241b6160])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^5) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x3726c30af242c66c, 0x7c2ac1aad1b6fe70, 0xa04007fbba4b14a2, 0xef517c3266341429, 0x95ba654ed2226b, 0x2e370eccc86f7dd])), + c1: Fq(FqRepr([0x82d83cf50dbce43f, 0xa2813e53df9d018f, 0xc6f0caa53c65e181, 0x7525cf528d50fe95, 0x4a85ed50f4798a6b, 0x171da0fd6cf8eebd])) + }, + // Fq2(u + 1)**(((q^6) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^7) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0xb2f66aad4ce5d646, 0x5842a06bfc497cec, 0xcf4895d42599d394, 0xc11b9cba40a8e8d0, 0x2e3813cbe5a0de89, 0x110eefda88847faf])), + c1: Fq(FqRepr([0x7089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, 0xa35baecab2dc29ee, 0x1ce393ea5daace4d, 0x8f2220fb0fb66eb])) + }, + // Fq2(u + 1)**(((q^8) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x3f97d6e83d050d2, 0x18f0206554638741])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^9) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x7bcfa7a25aa30fda, 0xdc17dec12a927e7c, 0x2f088dd86b4ebef1, 0xd1ca2087da74d4a7, 0x2da2596696cebc1d, 0xe2b7eedbbfd87d2])), + c1: Fq(FqRepr([0x3e2f585da55c9ad1, 0x4294213d86c18183, 0x382844c88b623732, 0x92ad2afd19103e18, 0x1d794e4fac7cf0b9, 0xbd592fc7d825ec8])) + }, + // Fq2(u + 1)**(((q^10) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x890dc9e4867545c3, 0x2af322533285a5d5, 0x50880866309b7e2c, 0xa20d1b8c7e881024, 0x14e4f04fe2db9068, 0x14e56d3f1564853a])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + }, + // Fq2(u + 1)**(((q^11) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([0x82d83cf50dbce43f, 0xa2813e53df9d018f, 0xc6f0caa53c65e181, 0x7525cf528d50fe95, 0x4a85ed50f4798a6b, 0x171da0fd6cf8eebd])), + c1: Fq(FqRepr([0x3726c30af242c66c, 0x7c2ac1aad1b6fe70, 0xa04007fbba4b14a2, 0xef517c3266341429, 0x95ba654ed2226b, 0x2e370eccc86f7dd])) + } +]; + +// -((2**384) mod q) mod q +pub const NEGATIVE_ONE: Fq = Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])); + +#[derive(Copy, Clone, PartialEq, Eq, Default)] +pub struct FqRepr(pub [u64; 6]); + +impl ::rand::Rand for FqRepr { + #[inline(always)] + fn rand(rng: &mut R) -> Self { + FqRepr(rng.gen()) + } +} + +impl ::std::fmt::Debug for FqRepr +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + try!(write!(f, "0x")); + for i in self.0.iter().rev() { + try!(write!(f, "{:016x}", *i)); + } + + Ok(()) + } +} + +impl AsRef<[u64]> for FqRepr { + #[inline(always)] + fn as_ref(&self) -> &[u64] { + &self.0 + } +} + +impl From for FqRepr { + #[inline(always)] + fn from(val: u64) -> FqRepr { + use std::default::Default; + + let mut repr = Self::default(); + repr.0[0] = val; + repr + } +} + +impl Ord for FqRepr { + #[inline(always)] + fn cmp(&self, other: &FqRepr) -> ::std::cmp::Ordering { + for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { + if a < b { + return ::std::cmp::Ordering::Less + } else if a > b { + return ::std::cmp::Ordering::Greater + } + } + + ::std::cmp::Ordering::Equal + } +} + +impl PartialOrd for FqRepr { + #[inline(always)] + fn partial_cmp(&self, other: &FqRepr) -> Option<::std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl PrimeFieldRepr for FqRepr { + #[inline(always)] + fn is_odd(&self) -> bool { + self.0[0] & 1 == 1 + } + + #[inline(always)] + fn is_even(&self) -> bool { + !self.is_odd() + } + + #[inline(always)] + fn is_zero(&self) -> bool { + self.0.iter().all(|&e| e == 0) + } + + #[inline(always)] + fn divn(&mut self, mut n: usize) { + if n >= 64 * 6 { + *self = Self::from(0); + return; + } + + while n >= 64 { + let mut t = 0; + for i in self.0.iter_mut().rev() { + ::std::mem::swap(&mut t, i); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + for i in self.0.iter_mut().rev() { + let t2 = *i << (64 - n); + *i >>= n; + *i |= t; + t = t2; + } + } + } + + #[inline(always)] + fn div2(&mut self) { + let mut t = 0; + for i in self.0.iter_mut().rev() { + let t2 = *i << 63; + *i >>= 1; + *i |= t; + t = t2; + } + } + + #[inline(always)] + fn mul2(&mut self) { + let mut last = 0; + for i in self.0.iter_mut() { + let tmp = *i >> 63; + *i <<= 1; + *i |= last; + last = tmp; + } + } + + #[inline(always)] + fn num_bits(&self) -> u32 { + let mut ret = (6 as u32) * 64; + for i in self.0.iter().rev() { + let leading = i.leading_zeros(); + ret -= leading; + if leading != 64 { + break; + } + } + + ret + } + + #[inline(always)] + fn add_nocarry(&mut self, other: &FqRepr) -> bool { + let mut carry = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = ::adc(*a, *b, &mut carry); + } + + carry != 0 + } + + #[inline(always)] + fn sub_noborrow(&mut self, other: &FqRepr) -> bool { + let mut borrow = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = ::sbb(*a, *b, &mut borrow); + } + + borrow != 0 + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Fq(FqRepr); + +impl ::std::fmt::Debug for Fq +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fq({:?})", self.into_repr()) + } +} + +impl ::rand::Rand for Fq { + fn rand(rng: &mut R) -> Self { + loop { + let mut tmp = Fq(FqRepr::rand(rng)); + for _ in 0..REPR_SHAVE_BITS { + tmp.0.div2(); + } + if tmp.is_valid() { + return tmp + } + } + } +} + +impl From for FqRepr { + fn from(e: Fq) -> FqRepr { + e.into_repr() + } +} + +impl PrimeField for Fq { + type Repr = FqRepr; + + fn from_repr(r: FqRepr) -> Result { + let mut r = Fq(r); + if r.is_valid() { + r.mul_assign(&Fq(R2)); + + Ok(r) + } else { + Err(()) + } + } + + fn into_repr(&self) -> FqRepr { + let mut r = *self; + r.mont_reduce((self.0).0[0], (self.0).0[1], + (self.0).0[2], (self.0).0[3], + (self.0).0[4], (self.0).0[5], + 0, 0, 0, 0, 0, 0); + r.0 + } + + fn char() -> FqRepr { + MODULUS + } + + fn num_bits() -> u32 { + MODULUS_BITS + } + + fn capacity() -> u32 { + Self::num_bits() - 1 + } + + fn multiplicative_generator() -> Self { + Fq(GENERATOR) + } + + fn s() -> usize { + S + } + + fn root_of_unity() -> Self { + Fq(ROOT_OF_UNITY) + } +} + +impl Field for Fq { + #[inline] + fn zero() -> Self { + Fq(FqRepr::from(0)) + } + + #[inline] + fn one() -> Self { + Fq(R) + } + + #[inline] + fn is_zero(&self) -> bool { + self.0.is_zero() + } + + #[inline] + fn add_assign(&mut self, other: &Fq) { + // This cannot exceed the backing capacity. + self.0.add_nocarry(&other.0); + + // However, it may need to be reduced. + self.reduce(); + } + + #[inline] + fn double(&mut self) { + // This cannot exceed the backing capacity. + self.0.mul2(); + + // However, it may need to be reduced. + self.reduce(); + } + + #[inline] + fn sub_assign(&mut self, other: &Fq) { + // If `other` is larger than `self`, we'll need to add the modulus to self first. + if other.0 > self.0 { + self.0.add_nocarry(&MODULUS); + } + + self.0.sub_noborrow(&other.0); + } + + #[inline] + fn negate(&mut self) { + if !self.is_zero() { + let mut tmp = MODULUS; + tmp.sub_noborrow(&self.0); + self.0 = tmp; + } + } + + fn inverse(&self) -> Option { + if self.is_zero() { + None + } else { + // Guajardo Kumar Paar Pelzl + // Efficient Software-Implementation of Finite Fields with Applications to Cryptography + // Algorithm 16 (BEA for Inversion in Fp) + + let one = FqRepr::from(1); + + let mut u = self.0; + let mut v = MODULUS; + let mut b = Fq(R2); // Avoids unnecessary reduction step. + let mut c = Self::zero(); + + while u != one && v != one { + while u.is_even() { + u.div2(); + + if b.0.is_even() { + b.0.div2(); + } else { + b.0.add_nocarry(&MODULUS); + b.0.div2(); + } + } + + while v.is_even() { + v.div2(); + + if c.0.is_even() { + c.0.div2(); + } else { + c.0.add_nocarry(&MODULUS); + c.0.div2(); + } + } + + if v < u { + u.sub_noborrow(&v); + b.sub_assign(&c); + } else { + v.sub_noborrow(&u); + c.sub_assign(&b); + } + } + + if u == one { + Some(b) + } else { + Some(c) + } + } + } + + #[inline(always)] + fn frobenius_map(&mut self, _: usize) { + // This has no effect in a prime field. + } + + #[inline] + fn mul_assign(&mut self, other: &Fq) + { + let mut carry = 0; + let r0 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); + let r1 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); + let r2 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); + let r3 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); + let r4 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[4], &mut carry); + let r5 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[5], &mut carry); + let r6 = carry; + let mut carry = 0; + let r1 = ::mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); + let r2 = ::mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); + let r3 = ::mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); + let r5 = ::mac_with_carry(r5, (self.0).0[1], (other.0).0[4], &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[1], (other.0).0[5], &mut carry); + let r7 = carry; + let mut carry = 0; + let r2 = ::mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); + let r3 = ::mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); + let r5 = ::mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[2], (other.0).0[4], &mut carry); + let r7 = ::mac_with_carry(r7, (self.0).0[2], (other.0).0[5], &mut carry); + let r8 = carry; + let mut carry = 0; + let r3 = ::mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); + let r5 = ::mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); + let r7 = ::mac_with_carry(r7, (self.0).0[3], (other.0).0[4], &mut carry); + let r8 = ::mac_with_carry(r8, (self.0).0[3], (other.0).0[5], &mut carry); + let r9 = carry; + let mut carry = 0; + let r4 = ::mac_with_carry(r4, (self.0).0[4], (other.0).0[0], &mut carry); + let r5 = ::mac_with_carry(r5, (self.0).0[4], (other.0).0[1], &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[4], (other.0).0[2], &mut carry); + let r7 = ::mac_with_carry(r7, (self.0).0[4], (other.0).0[3], &mut carry); + let r8 = ::mac_with_carry(r8, (self.0).0[4], (other.0).0[4], &mut carry); + let r9 = ::mac_with_carry(r9, (self.0).0[4], (other.0).0[5], &mut carry); + let r10 = carry; + let mut carry = 0; + let r5 = ::mac_with_carry(r5, (self.0).0[5], (other.0).0[0], &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[5], (other.0).0[1], &mut carry); + let r7 = ::mac_with_carry(r7, (self.0).0[5], (other.0).0[2], &mut carry); + let r8 = ::mac_with_carry(r8, (self.0).0[5], (other.0).0[3], &mut carry); + let r9 = ::mac_with_carry(r9, (self.0).0[5], (other.0).0[4], &mut carry); + let r10 = ::mac_with_carry(r10, (self.0).0[5], (other.0).0[5], &mut carry); + let r11 = carry; + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11); + } + + #[inline] + fn square(&mut self) + { + let mut carry = 0; + let r1 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); + let r2 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); + let r3 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); + let r4 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[4], &mut carry); + let r5 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[5], &mut carry); + let r6 = carry; + let mut carry = 0; + let r3 = ::mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); + let r5 = ::mac_with_carry(r5, (self.0).0[1], (self.0).0[4], &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[1], (self.0).0[5], &mut carry); + let r7 = carry; + let mut carry = 0; + let r5 = ::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[2], (self.0).0[4], &mut carry); + let r7 = ::mac_with_carry(r7, (self.0).0[2], (self.0).0[5], &mut carry); + let r8 = carry; + let mut carry = 0; + let r7 = ::mac_with_carry(r7, (self.0).0[3], (self.0).0[4], &mut carry); + let r8 = ::mac_with_carry(r8, (self.0).0[3], (self.0).0[5], &mut carry); + let r9 = carry; + let mut carry = 0; + let r9 = ::mac_with_carry(r9, (self.0).0[4], (self.0).0[5], &mut carry); + let r10 = carry; + let tmp0 = r1 >> 63; + let r1 = r1 << 1; + let tmp1 = r2 >> 63; + let r2 = r2 << 1; + let r2 = r2 | tmp0; + let tmp0 = tmp1; + let tmp1 = r3 >> 63; + let r3 = r3 << 1; + let r3 = r3 | tmp0; + let tmp0 = tmp1; + let tmp1 = r4 >> 63; + let r4 = r4 << 1; + let r4 = r4 | tmp0; + let tmp0 = tmp1; + let tmp1 = r5 >> 63; + let r5 = r5 << 1; + let r5 = r5 | tmp0; + let tmp0 = tmp1; + let tmp1 = r6 >> 63; + let r6 = r6 << 1; + let r6 = r6 | tmp0; + let tmp0 = tmp1; + let tmp1 = r7 >> 63; + let r7 = r7 << 1; + let r7 = r7 | tmp0; + let tmp0 = tmp1; + let tmp1 = r8 >> 63; + let r8 = r8 << 1; + let r8 = r8 | tmp0; + let tmp0 = tmp1; + let tmp1 = r9 >> 63; + let r9 = r9 << 1; + let r9 = r9 | tmp0; + let tmp0 = tmp1; + let tmp1 = r10 >> 63; + let r10 = r10 << 1; + let r10 = r10 | tmp0; + let tmp0 = tmp1; + let r11 = tmp0; + let mut carry = 0; + let r0 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); + let r1 = ::adc(r1, 0, &mut carry); + let r2 = ::mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); + let r3 = ::adc(r3, 0, &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); + let r5 = ::adc(r5, 0, &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); + let r7 = ::adc(r7, 0, &mut carry); + let r8 = ::mac_with_carry(r8, (self.0).0[4], (self.0).0[4], &mut carry); + let r9 = ::adc(r9, 0, &mut carry); + let r10 = ::mac_with_carry(r10, (self.0).0[5], (self.0).0[5], &mut carry); + let r11 = ::adc(r11, 0, &mut carry); + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11); + } +} + +impl Fq { + /// Determines if the element is really in the field. This is only used + /// internally. + #[inline(always)] + fn is_valid(&self) -> bool { + self.0 < MODULUS + } + + /// Subtracts the modulus from this element if this element is not in the + /// field. Only used interally. + #[inline(always)] + fn reduce(&mut self) { + if !self.is_valid() { + self.0.sub_noborrow(&MODULUS); + } + } + + #[inline(always)] + fn mont_reduce( + &mut self, + r0: u64, + mut r1: u64, + mut r2: u64, + mut r3: u64, + mut r4: u64, + mut r5: u64, + mut r6: u64, + mut r7: u64, + mut r8: u64, + mut r9: u64, + mut r10: u64, + mut r11: u64 + ) + { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = r0.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r0, k, MODULUS.0[0], &mut carry); + r1 = ::mac_with_carry(r1, k, MODULUS.0[1], &mut carry); + r2 = ::mac_with_carry(r2, k, MODULUS.0[2], &mut carry); + r3 = ::mac_with_carry(r3, k, MODULUS.0[3], &mut carry); + r4 = ::mac_with_carry(r4, k, MODULUS.0[4], &mut carry); + r5 = ::mac_with_carry(r5, k, MODULUS.0[5], &mut carry); + r6 = ::adc(r6, 0, &mut carry); + let carry2 = carry; + let k = r1.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r1, k, MODULUS.0[0], &mut carry); + r2 = ::mac_with_carry(r2, k, MODULUS.0[1], &mut carry); + r3 = ::mac_with_carry(r3, k, MODULUS.0[2], &mut carry); + r4 = ::mac_with_carry(r4, k, MODULUS.0[3], &mut carry); + r5 = ::mac_with_carry(r5, k, MODULUS.0[4], &mut carry); + r6 = ::mac_with_carry(r6, k, MODULUS.0[5], &mut carry); + r7 = ::adc(r7, carry2, &mut carry); + let carry2 = carry; + let k = r2.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r2, k, MODULUS.0[0], &mut carry); + r3 = ::mac_with_carry(r3, k, MODULUS.0[1], &mut carry); + r4 = ::mac_with_carry(r4, k, MODULUS.0[2], &mut carry); + r5 = ::mac_with_carry(r5, k, MODULUS.0[3], &mut carry); + r6 = ::mac_with_carry(r6, k, MODULUS.0[4], &mut carry); + r7 = ::mac_with_carry(r7, k, MODULUS.0[5], &mut carry); + r8 = ::adc(r8, carry2, &mut carry); + let carry2 = carry; + let k = r3.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r3, k, MODULUS.0[0], &mut carry); + r4 = ::mac_with_carry(r4, k, MODULUS.0[1], &mut carry); + r5 = ::mac_with_carry(r5, k, MODULUS.0[2], &mut carry); + r6 = ::mac_with_carry(r6, k, MODULUS.0[3], &mut carry); + r7 = ::mac_with_carry(r7, k, MODULUS.0[4], &mut carry); + r8 = ::mac_with_carry(r8, k, MODULUS.0[5], &mut carry); + r9 = ::adc(r9, carry2, &mut carry); + let carry2 = carry; + let k = r4.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r4, k, MODULUS.0[0], &mut carry); + r5 = ::mac_with_carry(r5, k, MODULUS.0[1], &mut carry); + r6 = ::mac_with_carry(r6, k, MODULUS.0[2], &mut carry); + r7 = ::mac_with_carry(r7, k, MODULUS.0[3], &mut carry); + r8 = ::mac_with_carry(r8, k, MODULUS.0[4], &mut carry); + r9 = ::mac_with_carry(r9, k, MODULUS.0[5], &mut carry); + r10 = ::adc(r10, carry2, &mut carry); + let carry2 = carry; + let k = r5.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r5, k, MODULUS.0[0], &mut carry); + r6 = ::mac_with_carry(r6, k, MODULUS.0[1], &mut carry); + r7 = ::mac_with_carry(r7, k, MODULUS.0[2], &mut carry); + r8 = ::mac_with_carry(r8, k, MODULUS.0[3], &mut carry); + r9 = ::mac_with_carry(r9, k, MODULUS.0[4], &mut carry); + r10 = ::mac_with_carry(r10, k, MODULUS.0[5], &mut carry); + r11 = ::adc(r11, carry2, &mut carry); + (self.0).0[0] = r6; + (self.0).0[1] = r7; + (self.0).0[2] = r8; + (self.0).0[3] = r9; + (self.0).0[4] = r10; + (self.0).0[5] = r11; + self.reduce(); + } +} + +impl SqrtField for Fq { + fn sqrt(&self) -> Option { + // Shank's algorithm for q mod 4 = 3 + // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) + + // a1 = self^((q - 3) // 2) + let mut a1 = self.pow([0xee7fbfffffffeaaa, 0x7aaffffac54ffff, 0xd9cc34a83dac3d89, 0xd91dd2e13ce144af, 0x92c6e9ed90d2eb35, 0x680447a8e5ff9a6]); + let mut a0 = a1; + a0.square(); + a0.mul_assign(self); + + // if a0 == -1 + if a0.0 == FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206]) + { + None + } + else + { + a1.mul_assign(self); + Some(a1) + } + } +} + +#[test] +fn test_b_coeff() { + assert_eq!(Fq::from_repr(FqRepr::from(4)).unwrap(), B_COEFF); +} + +#[test] +fn test_frob_coeffs() { + let mut nqr = Fq::one(); + nqr.negate(); + + assert_eq!(FROBENIUS_COEFF_FQ2_C1[0], Fq::one()); + assert_eq!(FROBENIUS_COEFF_FQ2_C1[1], nqr.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d])); + + let nqr = Fq2 { + c0: Fq::one(), + c1: Fq::one() + }; + + assert_eq!(FROBENIUS_COEFF_FQ6_C1[0], Fq2::one()); + assert_eq!(FROBENIUS_COEFF_FQ6_C1[1], nqr.pow([0x9354ffffffffe38e, 0xa395554e5c6aaaa, 0xcd104635a790520c, 0xcc27c3d6fbd7063f, 0x190937e76bc3e447, 0x8ab05f8bdd54cde])); + assert_eq!(FROBENIUS_COEFF_FQ6_C1[2], nqr.pow([0xb78e0000097b2f68, 0xd44f23b47cbd64e3, 0x5cb9668120b069a9, 0xccea85f9bf7b3d16, 0xdba2c8d7adb356d, 0x9cd75ded75d7429, 0xfc65c31103284fab, 0xc58cb9a9b249ee24, 0xccf734c3118a2e9a, 0xa0f4304c5a256ce6, 0xc3f0d2f8e0ba61f8, 0xe167e192ebca97])); + assert_eq!(FROBENIUS_COEFF_FQ6_C1[3], nqr.pow([0xdbc6fcd6f35b9e06, 0x997dead10becd6aa, 0x9dbbd24c17206460, 0x72b97acc6057c45e, 0xf8e9a230bf0c628e, 0x647ccb1885c63a7, 0xce80264fc55bf6ee, 0x94d8d716c3939fc4, 0xad78f0eb77ee6ee1, 0xd6fe49bfe57dc5f9, 0x2656d6c15c63647, 0xdf6282f111fa903, 0x1bdba63e0632b4bb, 0x6883597bcaa505eb, 0xa56d4ec90c34a982, 0x7e4c42823bbe90b2, 0xf64728aa6dcb0f20, 0x16e57e16ef152f])); + assert_eq!(FROBENIUS_COEFF_FQ6_C1[4], nqr.pow([0x4649add3c71c6d90, 0x43caa6528972a865, 0xcda8445bbaaa0fbb, 0xc93dea665662aa66, 0x2863bc891834481d, 0x51a0c3f5d4ccbed8, 0x9210e660f90ccae9, 0xe2bd6836c546d65e, 0xf223abbaa7cf778b, 0xd4f10b222cf11680, 0xd540f5eff4a1962e, 0xa123a1f140b56526, 0x31ace500636a59f6, 0x3a82bc8c8dfa57a9, 0x648c511e217fc1f8, 0x36c17ffd53a4558f, 0x881bef5fd684eefd, 0x5d648dbdc5dbb522, 0x8fd07bf06e5e59b8, 0x8ddec8a9acaa4b51, 0x4cc1f8688e2def26, 0xa74e63cb492c03de, 0x57c968173d1349bb, 0x253674e02a866])); + assert_eq!(FROBENIUS_COEFF_FQ6_C1[5], nqr.pow([0xf896f792732eb2be, 0x49c86a6d1dc593a1, 0xe5b31e94581f91c3, 0xe3da5cc0a6b20d7f, 0x822caef950e0bfed, 0x317ed950b9ee67cd, 0xffd664016ee3f6cd, 0x77d991c88810b122, 0x62e72e635e698264, 0x905e1a1a2d22814a, 0xf5b7ab3a3f33d981, 0x175871b0bc0e25dd, 0x1e2e9a63df5c3772, 0xe888b1f7445b149d, 0x9551c19e5e7e2c24, 0xecf21939a3d2d6be, 0xd830dbfdab72dbd4, 0x7b34af8d622d40c0, 0x3df6d20a45671242, 0xaf86bee30e21d98, 0x41064c1534e5df5d, 0xf5f6cabd3164c609, 0xa5d14bdf2b7ee65, 0xa718c069defc9138, 0xdb1447e770e3110e, 0xc1b164a9e90af491, 0x7180441f9d251602, 0x1fd3a5e6a9a893e, 0x1e17b779d54d5db, 0x3c7afafe3174])); + + assert_eq!(FROBENIUS_COEFF_FQ6_C2[0], Fq2::one()); + assert_eq!(FROBENIUS_COEFF_FQ6_C2[1], nqr.pow([0x26a9ffffffffc71c, 0x1472aaa9cb8d5555, 0x9a208c6b4f20a418, 0x984f87adf7ae0c7f, 0x32126fced787c88f, 0x11560bf17baa99bc])); + assert_eq!(FROBENIUS_COEFF_FQ6_C2[2], nqr.pow([0x6f1c000012f65ed0, 0xa89e4768f97ac9c7, 0xb972cd024160d353, 0x99d50bf37ef67a2c, 0x1b74591af5b66adb, 0x139aebbdaebae852, 0xf8cb862206509f56, 0x8b1973536493dc49, 0x99ee698623145d35, 0x41e86098b44ad9cd, 0x87e1a5f1c174c3f1, 0x1c2cfc325d7952f])); + assert_eq!(FROBENIUS_COEFF_FQ6_C2[3], nqr.pow([0xb78df9ade6b73c0c, 0x32fbd5a217d9ad55, 0x3b77a4982e40c8c1, 0xe572f598c0af88bd, 0xf1d344617e18c51c, 0xc8f996310b8c74f, 0x9d004c9f8ab7eddc, 0x29b1ae2d87273f89, 0x5af1e1d6efdcddc3, 0xadfc937fcafb8bf3, 0x4cadad82b8c6c8f, 0x1bec505e223f5206, 0x37b74c7c0c656976, 0xd106b2f7954a0bd6, 0x4ada9d9218695304, 0xfc988504777d2165, 0xec8e5154db961e40, 0x2dcafc2dde2a5f])); + assert_eq!(FROBENIUS_COEFF_FQ6_C2[4], nqr.pow([0x8c935ba78e38db20, 0x87954ca512e550ca, 0x9b5088b775541f76, 0x927bd4ccacc554cd, 0x50c779123068903b, 0xa34187eba9997db0, 0x2421ccc1f21995d2, 0xc57ad06d8a8dacbd, 0xe44757754f9eef17, 0xa9e2164459e22d01, 0xaa81ebdfe9432c5d, 0x424743e2816aca4d, 0x6359ca00c6d4b3ed, 0x750579191bf4af52, 0xc918a23c42ff83f0, 0x6d82fffaa748ab1e, 0x1037debfad09ddfa, 0xbac91b7b8bb76a45, 0x1fa0f7e0dcbcb370, 0x1bbd9153595496a3, 0x9983f0d11c5bde4d, 0x4e9cc796925807bc, 0xaf92d02e7a269377, 0x4a6ce9c0550cc])); + assert_eq!(FROBENIUS_COEFF_FQ6_C2[5], nqr.pow([0xf12def24e65d657c, 0x9390d4da3b8b2743, 0xcb663d28b03f2386, 0xc7b4b9814d641aff, 0x4595df2a1c17fdb, 0x62fdb2a173dccf9b, 0xffacc802ddc7ed9a, 0xefb3239110216245, 0xc5ce5cc6bcd304c8, 0x20bc34345a450294, 0xeb6f56747e67b303, 0x2eb0e361781c4bbb, 0x3c5d34c7beb86ee4, 0xd11163ee88b6293a, 0x2aa3833cbcfc5849, 0xd9e4327347a5ad7d, 0xb061b7fb56e5b7a9, 0xf6695f1ac45a8181, 0x7beda4148ace2484, 0x15f0d7dc61c43b30, 0x820c982a69cbbeba, 0xebed957a62c98c12, 0x14ba297be56fdccb, 0x4e3180d3bdf92270, 0xb6288fcee1c6221d, 0x8362c953d215e923, 0xe300883f3a4a2c05, 0x3fa74bcd535127c, 0x3c2f6ef3aa9abb6, 0x78f5f5fc62e8])); + + assert_eq!(FROBENIUS_COEFF_FQ12_C1[0], Fq2::one()); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[1], nqr.pow([0x49aa7ffffffff1c7, 0x51caaaa72e35555, 0xe688231ad3c82906, 0xe613e1eb7deb831f, 0xc849bf3b5e1f223, 0x45582fc5eeaa66f])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[2], nqr.pow([0xdbc7000004bd97b4, 0xea2791da3e5eb271, 0x2e5cb340905834d4, 0xe67542fcdfbd9e8b, 0x86dd1646bd6d9ab6, 0x84e6baef6baeba14, 0x7e32e188819427d5, 0x62c65cd4d924f712, 0x667b9a6188c5174d, 0x507a18262d12b673, 0xe1f8697c705d30fc, 0x70b3f0c975e54b])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[3], nqr.pow(vec![0x6de37e6b79adcf03, 0x4cbef56885f66b55, 0x4edde9260b903230, 0x395cbd66302be22f, 0xfc74d1185f863147, 0x323e658c42e31d3, 0x67401327e2adfb77, 0xca6c6b8b61c9cfe2, 0xd6bc7875bbf73770, 0xeb7f24dff2bee2fc, 0x8132b6b60ae31b23, 0x86fb1417888fd481, 0x8dedd31f03195a5d, 0x3441acbde55282f5, 0x52b6a764861a54c1, 0x3f2621411ddf4859, 0xfb23945536e58790, 0xb72bf0b778a97])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[4], nqr.pow(vec![0xa324d6e9e38e36c8, 0xa1e5532944b95432, 0x66d4222ddd5507dd, 0xe49ef5332b315533, 0x1431de448c1a240e, 0xa8d061faea665f6c, 0x490873307c866574, 0xf15eb41b62a36b2f, 0x7911d5dd53e7bbc5, 0x6a78859116788b40, 0x6aa07af7fa50cb17, 0x5091d0f8a05ab293, 0x98d6728031b52cfb, 0x1d415e4646fd2bd4, 0xb246288f10bfe0fc, 0x9b60bffea9d22ac7, 0x440df7afeb42777e, 0x2eb246dee2edda91, 0xc7e83df8372f2cdc, 0x46ef6454d65525a8, 0x2660fc344716f793, 0xd3a731e5a49601ef, 0x2be4b40b9e89a4dd, 0x129b3a7015433])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[5], nqr.pow(vec![0xfc4b7bc93997595f, 0xa4e435368ee2c9d0, 0xf2d98f4a2c0fc8e1, 0xf1ed2e60535906bf, 0xc116577ca8705ff6, 0x98bf6ca85cf733e6, 0x7feb3200b771fb66, 0x3becc8e444085891, 0x31739731af34c132, 0xc82f0d0d169140a5, 0xfadbd59d1f99ecc0, 0xbac38d85e0712ee, 0x8f174d31efae1bb9, 0x744458fba22d8a4e, 0x4aa8e0cf2f3f1612, 0x76790c9cd1e96b5f, 0x6c186dfed5b96dea, 0x3d9a57c6b116a060, 0x1efb690522b38921, 0x857c35f718710ecc, 0xa083260a9a72efae, 0xfafb655e98b26304, 0x52e8a5ef95bf732, 0x538c6034ef7e489c, 0xed8a23f3b8718887, 0x60d8b254f4857a48, 0x38c0220fce928b01, 0x80fe9d2f354d449f, 0xf0bdbbceaa6aed, 0x1e3d7d7f18ba])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[6], nqr.pow(vec![0x21219610a012ba3c, 0xa5c19ad35375325, 0x4e9df1e497674396, 0xfb05b717c991c6ef, 0x4a1265bca93a32f2, 0xd875ff2a7bdc1f66, 0xc6d8754736c771b2, 0x2d80c759ba5a2ae7, 0x138a20df4b03cc1a, 0xc22d07fe68e93024, 0xd1dc474d3b433133, 0xc22aa5e75044e5c, 0xf657c6fbf9c17ebf, 0xc591a794a58660d, 0x2261850ee1453281, 0xd17d3bd3b7f5efb4, 0xf00cec8ec507d01, 0x2a6a775657a00ae6, 0x5f098a12ff470719, 0x409d194e7b5c5afa, 0x1d66478e982af5b, 0xda425a5b5e01ca3f, 0xf77e4f78747e903c, 0x177d49f73732c6fc, 0xa9618fecabe0e1f4, 0xba5337eac90bd080, 0x66fececdbc35d4e7, 0xa4cd583203d9206f, 0x98391632ceeca596, 0x4946b76e1236ad3f, 0xa0dec64e60e711a1, 0xfcb41ed3605013, 0x8ca8f9692ae1e3a9, 0xd3078bfc28cc1baf, 0xf0536f764e982f82, 0x3125f1a2656])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[7], nqr.pow(vec![0x742754a1f22fdb, 0x2a1955c2dec3a702, 0x9747b28c796d134e, 0xc113a0411f59db79, 0x3bb0fa929853bfc1, 0x28c3c25f8f6fb487, 0xbc2b6c99d3045b34, 0x98fb67d6badde1fd, 0x48841d76a24d2073, 0xd49891145fe93ae6, 0xc772b9c8e74d4099, 0xccf4e7b9907755bb, 0x9cf47b25d42fd908, 0x5616a0c347fc445d, 0xff93b7a7ad1b8a6d, 0xac2099256b78a77a, 0x7804a95b02892e1c, 0x5cf59ca7bfd69776, 0xa7023502acd3c866, 0xc76f4982fcf8f37, 0x51862a5a57ac986e, 0x38b80ed72b1b1023, 0x4a291812066a61e1, 0xcd8a685eff45631, 0x3f40f708764e4fa5, 0x8aa0441891285092, 0x9eff60d71cdf0a9, 0x4fdd9d56517e2bfa, 0x1f3c80d74a28bc85, 0x24617417c064b648, 0x7ddda1e4385d5088, 0xf9e132b11dd32a16, 0xcc957cb8ef66ab99, 0xd4f206d37cb752c5, 0x40de343f28ad616b, 0x8d1f24379068f0e3, 0x6f31d7947ea21137, 0x27311f9c32184061, 0x9eea0664cc78ce5f, 0x7d4151f6fea9a0da, 0x454096fa75bd571a, 0x4fe0f20ecb])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[8], nqr.pow(vec![0x802f5720d0b25710, 0x6714f0a258b85c7c, 0x31394c90afdf16e, 0xe9d2b0c64f957b19, 0xe67c0d9c5e7903ee, 0x3156fdc5443ea8ef, 0x7c4c50524d88c892, 0xc99dc8990c0ad244, 0xd37ababf3649a896, 0x76fe4b838ff7a20c, 0xcf69ee2cec728db3, 0xb83535548e5f41, 0x371147684ccb0c23, 0x194f6f4fa500db52, 0xc4571dc78a4c5374, 0xe4d46d479999ca97, 0x76b6785a615a151c, 0xcceb8bcea7eaf8c1, 0x80d87a6fbe5ae687, 0x6a97ddddb85ce85, 0xd783958f26034204, 0x7144506f2e2e8590, 0x948693d377aef166, 0x8364621ed6f96056, 0xf021777c4c09ee2d, 0xc6cf5e746ecd50b, 0xa2337b7aa22743df, 0xae753f8bbacab39c, 0xfc782a9e34d3c1cc, 0x21b827324fe494d9, 0x5692ce350ed03b38, 0xf323a2b3cd0481b0, 0xe859c97a4ccad2e3, 0x48434b70381e4503, 0x46042d62e4132ed8, 0x48c4d6f56122e2f2, 0xf87711ab9f5c1af7, 0xb14b7a054759b469, 0x8eb0a96993ffa9aa, 0x9b21fb6fc58b760c, 0xf3abdd115d2e7d25, 0xf7beac3d4d12409c, 0x40a5585cce69bf03, 0x697881e1ba22d5a8, 0x3d6c04e6ad373fd9, 0x849871bf627be886, 0x550f4b9b71b28ef9, 0x81d2e0d78])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[9], nqr.pow(vec![0x4af4accf7de0b977, 0x742485e21805b4ee, 0xee388fbc4ac36dec, 0x1e199da57ad178a, 0xc27c12b292c6726a, 0x162e6ed84505b5e8, 0xe191683f336e09df, 0x17deb7e8d1e0fce6, 0xd944f19ad06f5836, 0x4c5f5e59f6276026, 0xf1ba9c7c148a38a8, 0xd205fe2dba72b326, 0x9a2cf2a4c289824e, 0x4f47ad512c39e24d, 0xc5894d984000ea09, 0x2974c03ff7cf01fa, 0xfcd243b48cb99a22, 0x2b5150c9313ac1e8, 0x9089f37c7fc80eda, 0x989540cc9a7aea56, 0x1ab1d4e337e63018, 0x42b546c30d357e43, 0x1c6abc04f76233d9, 0x78b3b8d88bf73e47, 0x151c4e4c45dc68e6, 0x519a79c4f54397ed, 0x93f5b51535a127c5, 0x5fc51b6f52fa153e, 0x2e0504f2d4a965c3, 0xc85bd3a3da52bffe, 0x98c60957a46a89ef, 0x48c03b5976b91cae, 0xc6598040a0a61438, 0xbf0b49dc255953af, 0xb78dff905b628ab4, 0x68140b797ba74ab8, 0x116cf037991d1143, 0x2f7fe82e58acb0b8, 0xc20bf7a8f7be5d45, 0x86c2905c338d5709, 0xff13a3ae6c8ace3d, 0xb6f95e2282d08337, 0xd49f7b313e9cbf29, 0xf794517193a1ce8c, 0x39641fecb596a874, 0x411c4c4edf462fb3, 0x3f8cd55c10cf25b4, 0x2bdd7ea165e860b6, 0xacd7d2cef4caa193, 0x6558a1d09a05f96, 0x1f52b5f5b546fc20, 0x4ee22a5a8c250c12, 0xd3a63a54a205b6b3, 0xd2ff5be8])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[10], nqr.pow(vec![0xe5953a4f96cdda44, 0x336b2d734cbc32bb, 0x3f79bfe3cd7410e, 0x267ae19aaa0f0332, 0x85a9c4db78d5c749, 0x90996b046b5dc7d8, 0x8945eae9820afc6a, 0x2644ddea2b036bd, 0x39898e35ac2e3819, 0x2574eab095659ab9, 0x65953d51ac5ea798, 0xc6b8c7afe6752466, 0x40e9e993e9286544, 0x7e0ad34ad9700ea0, 0xac1015eba2c69222, 0x24f057a19239b5d8, 0x2043b48c8a3767eb, 0x1117c124a75d7ff4, 0x433cfd1a09fb3ce7, 0x25b087ce4bcf7fb, 0xbcee0dc53a3e5bdb, 0xbffda040cf028735, 0xf7cf103a25512acc, 0x31d4ecda673130b9, 0xea0906dab18461e6, 0x5a40585a5ac3050d, 0x803358fc14fd0eda, 0x3678ca654eada770, 0x7b91a1293a45e33e, 0xcd5e5b8ea8530e43, 0x21ae563ab34da266, 0xecb00dad60df8894, 0x77fe53e652facfef, 0x9b7d1ad0b00244ec, 0xe695df5ca73f801, 0x23cdb21feeab0149, 0x14de113e7ea810d9, 0x52600cd958dac7e7, 0xc83392c14667e488, 0x9f808444bc1717fc, 0x56facb4bcf7c788f, 0x8bcad53245fc3ca0, 0xdef661e83f27d81c, 0x37d4ebcac9ad87e5, 0x6fe8b24f5cdb9324, 0xee08a26c1197654c, 0xc98b22f65f237e9a, 0xf54873a908ed3401, 0x6e1cb951d41f3f3, 0x290b2250a54e8df6, 0x7f36d51eb1db669e, 0xb08c7ed81a6ee43e, 0x95e1c90fb092f680, 0x429e4afd0e8b820, 0x2c14a83ee87d715c, 0xf37267575cfc8af5, 0xb99e9afeda3c2c30, 0x8f0f69da75792d5a, 0x35074a85a533c73, 0x156ed119])); + assert_eq!(FROBENIUS_COEFF_FQ12_C1[11], nqr.pow(vec![0x107db680942de533, 0x6262b24d2052393b, 0x6136df824159ebc, 0xedb052c9970c5deb, 0xca813aea916c3777, 0xf49dacb9d76c1788, 0x624941bd372933bb, 0xa5e60c2520638331, 0xb38b661683411074, 0x1d2c9af4c43d962b, 0x17d807a0f14aa830, 0x6e6581a51012c108, 0x668a537e5b35e6f5, 0x6c396cf3782dca5d, 0x33b679d1bff536ed, 0x736cce41805d90aa, 0x8a562f369eb680bf, 0x9f61aa208a11ded8, 0x43dd89dd94d20f35, 0xcf84c6610575c10a, 0x9f318d49cf2fe8e6, 0xbbc6e5f25a6e434e, 0x6528c433d11d987b, 0xffced71cc48c0e8a, 0x4cbb1474f4cb2a26, 0x66a035c0b28b7231, 0xa6f2875faa1a82ae, 0xdd1ea3deff818b02, 0xe0cfdf0dcdecf701, 0x9aefa231f2f6d23, 0xfb251297efa06746, 0x5a40d367df985538, 0x1ea31d69ab506fed, 0xc64ea8280e89a73f, 0x969acf9f2d4496f4, 0xe84c9181ee60c52c, 0xc60f27fc19fc6ca4, 0x760b33d850154048, 0x84f69080f66c8457, 0xc0192ba0fabf640e, 0xd2c338765c23a3a8, 0xa7838c20f02cec6c, 0xb7cf01d020572877, 0xd63ffaeba0be200a, 0xf7492baeb5f041ac, 0x8602c5212170d117, 0xad9b2e83a5a42068, 0x2461829b3ba1083e, 0x7c34650da5295273, 0xdc824ba800a8265a, 0xd18d9b47836af7b2, 0x3af78945c58cbf4d, 0x7ed9575b8596906c, 0x6d0c133895009a66, 0x53bc1247ea349fe1, 0x6b3063078d41aa7a, 0x6184acd8cd880b33, 0x76f4d15503fd1b96, 0x7a9afd61eef25746, 0xce974aadece60609, 0x88ca59546a8ceafd, 0x6d29391c41a0ac07, 0x443843a60e0f46a6, 0xa1590f62fd2602c7, 0x536d5b15b514373f, 0x22d582b])); +} + +#[test] +fn test_neg_one() { + let mut o = Fq::one(); + o.negate(); + + assert_eq!(NEGATIVE_ONE, o); +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng, Rand}; + +#[test] +fn test_fq_repr_ordering() { + fn assert_equality(a: FqRepr, b: FqRepr) { + assert_eq!(a, b); + assert!(a.cmp(&b) == ::std::cmp::Ordering::Equal); + } + + fn assert_lt(a: FqRepr, b: FqRepr) { + assert!(a < b); + assert!(b > a); + } + + assert_equality(FqRepr([9999, 9999, 9999, 9999, 9999, 9999]), FqRepr([9999, 9999, 9999, 9999, 9999, 9999])); + assert_equality(FqRepr([9999, 9998, 9999, 9999, 9999, 9999]), FqRepr([9999, 9998, 9999, 9999, 9999, 9999])); + assert_equality(FqRepr([9999, 9999, 9999, 9997, 9999, 9999]), FqRepr([9999, 9999, 9999, 9997, 9999, 9999])); + assert_lt(FqRepr([9999, 9999, 9999, 9997, 9999, 9998]), FqRepr([9999, 9999, 9999, 9997, 9999, 9999])); + assert_lt(FqRepr([9999, 9999, 9999, 9997, 9998, 9999]), FqRepr([9999, 9999, 9999, 9997, 9999, 9999])); + assert_lt(FqRepr([9, 9999, 9999, 9997, 9998, 9999]), FqRepr([9999, 9999, 9999, 9997, 9999, 9999])); +} + +#[test] +fn test_fq_repr_from() { + assert_eq!(FqRepr::from(100), FqRepr([100, 0, 0, 0, 0, 0])); +} + +#[test] +fn test_fq_repr_is_odd() { + assert!(!FqRepr::from(0).is_odd()); + assert!(FqRepr::from(0).is_even()); + assert!(FqRepr::from(1).is_odd()); + assert!(!FqRepr::from(1).is_even()); + assert!(!FqRepr::from(324834872).is_odd()); + assert!(FqRepr::from(324834872).is_even()); + assert!(FqRepr::from(324834873).is_odd()); + assert!(!FqRepr::from(324834873).is_even()); +} + +#[test] +fn test_fq_repr_is_zero() { + assert!(FqRepr::from(0).is_zero()); + assert!(!FqRepr::from(1).is_zero()); + assert!(!FqRepr([0, 0, 0, 0, 1, 0]).is_zero()); +} + +#[test] +fn test_fq_repr_div2() { + let mut a = FqRepr([0x8b0ad39f8dd7482a, 0x147221c9a7178b69, 0x54764cb08d8a6aa0, 0x8519d708e1d83041, 0x41f82777bd13fdb, 0xf43944578f9b771b]); + a.div2(); + assert_eq!(a, FqRepr([0xc58569cfc6eba415, 0xa3910e4d38bc5b4, 0xaa3b265846c53550, 0xc28ceb8470ec1820, 0x820fc13bbde89fed, 0x7a1ca22bc7cdbb8d])); + for _ in 0..10 { + a.div2(); + } + assert_eq!(a, FqRepr([0x6d31615a73f1bae9, 0x54028e443934e2f1, 0x82a8ec99611b14d, 0xfb70a33ae11c3b06, 0xe36083f04eef7a27, 0x1e87288af1f36e])); + for _ in 0..300 { + a.div2(); + } + assert_eq!(a, FqRepr([0x7288af1f36ee3608, 0x1e8, 0x0, 0x0, 0x0, 0x0])); + for _ in 0..50 { + a.div2(); + } + assert_eq!(a, FqRepr([0x7a1ca2, 0x0, 0x0, 0x0, 0x0, 0x0])); + for _ in 0..22 { + a.div2(); + } + assert_eq!(a, FqRepr([0x1, 0x0, 0x0, 0x0, 0x0, 0x0])); + a.div2(); + assert!(a.is_zero()); +} + +#[test] +fn test_fq_repr_divn() { + let mut a = FqRepr([0xaa5cdd6172847ffd, 0x43242c06aed55287, 0x9ddd5b312f3dd104, 0xc5541fd48046b7e7, 0x16080cf4071e0b05, 0x1225f2901aea514e]); + a.divn(0); + assert_eq!( + a, + FqRepr([0xaa5cdd6172847ffd, 0x43242c06aed55287, 0x9ddd5b312f3dd104, 0xc5541fd48046b7e7, 0x16080cf4071e0b05, 0x1225f2901aea514e]) + ); + a.divn(1); + assert_eq!( + a, + FqRepr([0xd52e6eb0b9423ffe, 0x21921603576aa943, 0xceeead98979ee882, 0xe2aa0fea40235bf3, 0xb04067a038f0582, 0x912f9480d7528a7]) + ); + a.divn(50); + assert_eq!( + a, + FqRepr([0x8580d5daaa50f54b, 0xab6625e7ba208864, 0x83fa9008d6fcf3bb, 0x19e80e3c160b8aa, 0xbe52035d4a29c2c1, 0x244]) + ); + a.divn(130); + assert_eq!( + a, + FqRepr([0xa0fea40235bf3cee, 0x4067a038f0582e2a, 0x2f9480d7528a70b0, 0x91, 0x0, 0x0]) + ); + a.divn(64); + assert_eq!( + a, + FqRepr([0x4067a038f0582e2a, 0x2f9480d7528a70b0, 0x91, 0x0, 0x0, 0x0]) + ); +} + +#[test] +fn test_fq_repr_mul2() { + let mut a = FqRepr::from(23712937547); + a.mul2(); + assert_eq!(a, FqRepr([0xb0acd6c96, 0x0, 0x0, 0x0, 0x0, 0x0])); + for _ in 0..60 { + a.mul2(); + } + assert_eq!(a, FqRepr([0x6000000000000000, 0xb0acd6c9, 0x0, 0x0, 0x0, 0x0])); + for _ in 0..300 { + a.mul2(); + } + assert_eq!(a, FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0xcd6c960000000000])); + for _ in 0..17 { + a.mul2(); + } + assert_eq!(a, FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x2c00000000000000])); + for _ in 0..6 { + a.mul2(); + } + assert!(a.is_zero()); +} + +#[test] +fn test_fq_repr_num_bits() { + let mut a = FqRepr::from(0); + assert_eq!(0, a.num_bits()); + a = FqRepr::from(1); + for i in 1..385 { + assert_eq!(i, a.num_bits()); + a.mul2(); + } + assert_eq!(0, a.num_bits()); +} + +#[test] +fn test_fq_repr_sub_noborrow() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut t = FqRepr([0x827a4a08041ebd9, 0x3c239f3dcc8f0d6b, 0x9ab46a912d555364, 0x196936b17b43910b, 0xad0eb3948a5c34fd, 0xd56f7b5ab8b5ce8]); + t.sub_noborrow(&FqRepr([0xc7867917187ca02b, 0x5d75679d4911ffef, 0x8c5b3e48b1a71c15, 0x6a427ae846fd66aa, 0x7a37e7265ee1eaf9, 0x7c0577a26f59d5])); + assert!(t == FqRepr([0x40a12b8967c54bae, 0xdeae37a0837d0d7b, 0xe592c487bae374e, 0xaf26bbc934462a61, 0x32d6cc6e2b7a4a03, 0xcdaf23e091c0313])); + + for _ in 0..1000 { + let mut a = FqRepr::rand(&mut rng); + a.0[5] >>= 30; + let mut b = a; + for _ in 0..10 { + b.mul2(); + } + let mut c = b; + for _ in 0..10 { + c.mul2(); + } + + assert!(a < b); + assert!(b < c); + + let mut csub_ba = c; + csub_ba.sub_noborrow(&b); + csub_ba.sub_noborrow(&a); + + let mut csub_ab = c; + csub_ab.sub_noborrow(&a); + csub_ab.sub_noborrow(&b); + + assert_eq!(csub_ab, csub_ba); + } + + // Subtracting q+1 from q should produce a borrow + let mut qplusone = FqRepr([0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); + assert!(qplusone.sub_noborrow(&FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]))); + + // Subtracting x from x should produce no borrow + let mut x = FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); + assert!(!x.sub_noborrow(&FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]))) +} + +#[test] +fn test_fq_repr_add_nocarry() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut t = FqRepr([0x827a4a08041ebd9, 0x3c239f3dcc8f0d6b, 0x9ab46a912d555364, 0x196936b17b43910b, 0xad0eb3948a5c34fd, 0xd56f7b5ab8b5ce8]); + t.add_nocarry(&FqRepr([0xc7867917187ca02b, 0x5d75679d4911ffef, 0x8c5b3e48b1a71c15, 0x6a427ae846fd66aa, 0x7a37e7265ee1eaf9, 0x7c0577a26f59d5])); + assert!(t == FqRepr([0xcfae1db798be8c04, 0x999906db15a10d5a, 0x270fa8d9defc6f79, 0x83abb199c240f7b6, 0x27469abae93e1ff6, 0xdd2fd2d4dfab6be])); + + // Test for the associativity of addition. + for _ in 0..1000 { + let mut a = FqRepr::rand(&mut rng); + let mut b = FqRepr::rand(&mut rng); + let mut c = FqRepr::rand(&mut rng); + + // Unset the first few bits, so that overflow won't occur. + a.0[5] >>= 3; + b.0[5] >>= 3; + c.0[5] >>= 3; + + let mut abc = a; + abc.add_nocarry(&b); + abc.add_nocarry(&c); + + let mut acb = a; + acb.add_nocarry(&c); + acb.add_nocarry(&b); + + let mut bac = b; + bac.add_nocarry(&a); + bac.add_nocarry(&c); + + let mut bca = b; + bca.add_nocarry(&c); + bca.add_nocarry(&a); + + let mut cab = c; + cab.add_nocarry(&a); + cab.add_nocarry(&b); + + let mut cba = c; + cba.add_nocarry(&b); + cba.add_nocarry(&a); + + assert_eq!(abc, acb); + assert_eq!(abc, bac); + assert_eq!(abc, bca); + assert_eq!(abc, cab); + assert_eq!(abc, cba); + } + + // Adding 1 to (2^384 - 1) should produce a carry + let mut x = FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); + assert!(x.add_nocarry(&FqRepr::from(1))); + + // Adding 1 to q should not produce a carry + let mut x = FqRepr([0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); + assert!(!x.add_nocarry(&FqRepr::from(1))); +} + +#[bench] +fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES).map(|_| { + let mut tmp1 = FqRepr::rand(&mut rng); + let mut tmp2 = FqRepr::rand(&mut rng); + // Shave a few bits off to avoid overflow. + for _ in 0..3 { + tmp1.div2(); + tmp2.div2(); + } + (tmp1, tmp2) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_nocarry(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_sub_noborrow(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES).map(|_| { + let tmp1 = FqRepr::rand(&mut rng); + let mut tmp2 = tmp1; + // Ensure tmp2 is smaller than tmp1. + for _ in 0..10 { + tmp2.div2(); + } + (tmp1, tmp2) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_noborrow(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_num_bits(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].num_bits(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_mul2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.mul2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_div2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.div2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn test_fq_is_valid() { + let mut a = Fq(MODULUS); + assert!(!a.is_valid()); + a.0.sub_noborrow(&FqRepr::from(1)); + assert!(a.is_valid()); + assert!(Fq(FqRepr::from(0)).is_valid()); + assert!(Fq(FqRepr([0xdf4671abd14dab3e, 0xe2dc0c9f534fbd33, 0x31ca6c880cc444a6, 0x257a67e70ef33359, 0xf9b29e493f899b36, 0x17c8be1800b9f059])).is_valid()); + assert!(!Fq(FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])).is_valid()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = Fq::rand(&mut rng); + assert!(a.is_valid()); + } +} + +#[test] +fn test_fq_add_assign() { + { + // Random number + let mut tmp = Fq(FqRepr([0x624434821df92b69, 0x503260c04fd2e2ea, 0xd9df726e0d16e8ce, 0xfbcb39adfd5dfaeb, 0x86b8a22b0c88b112, 0x165a2ed809e4201b])); + assert!(tmp.is_valid()); + // Test that adding zero has no effect. + tmp.add_assign(&Fq(FqRepr::from(0))); + assert_eq!(tmp, Fq(FqRepr([0x624434821df92b69, 0x503260c04fd2e2ea, 0xd9df726e0d16e8ce, 0xfbcb39adfd5dfaeb, 0x86b8a22b0c88b112, 0x165a2ed809e4201b]))); + // Add one and test for the result. + tmp.add_assign(&Fq(FqRepr::from(1))); + assert_eq!(tmp, Fq(FqRepr([0x624434821df92b6a, 0x503260c04fd2e2ea, 0xd9df726e0d16e8ce, 0xfbcb39adfd5dfaeb, 0x86b8a22b0c88b112, 0x165a2ed809e4201b]))); + // Add another random number that exercises the reduction. + tmp.add_assign(&Fq(FqRepr([0x374d8f8ea7a648d8, 0xe318bb0ebb8bfa9b, 0x613d996f0a95b400, 0x9fac233cb7e4fef1, 0x67e47552d253c52, 0x5c31b227edf25da]))); + assert_eq!(tmp, Fq(FqRepr([0xdf92c410c59fc997, 0x149f1bd05a0add85, 0xd3ec393c20fba6ab, 0x37001165c1bde71d, 0x421b41c9f662408e, 0x21c38104f435f5b]))); + // Add one to (q - 1) and test for the result. + tmp = Fq(FqRepr([0xb9feffffffffaaaa, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])); + tmp.add_assign(&Fq(FqRepr::from(1))); + assert!(tmp.0.is_zero()); + // Add a random number to another one such that the result is q - 1 + tmp = Fq(FqRepr([0x531221a410efc95b, 0x72819306027e9717, 0x5ecefb937068b746, 0x97de59cd6feaefd7, 0xdc35c51158644588, 0xb2d176c04f2100])); + tmp.add_assign(&Fq(FqRepr([0x66ecde5bef0fe14f, 0xac2a6cf8aed568e8, 0x861d70d86483edd, 0xcc98f1b7839a22e8, 0x6ee5e2a4eae7674e, 0x194e40737930c599]))); + assert_eq!(tmp, Fq(FqRepr([0xb9feffffffffaaaa, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]))); + // Add one to the result and test for it. + tmp.add_assign(&Fq(FqRepr::from(1))); + assert!(tmp.0.is_zero()); + } + + // Test associativity + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Generate a, b, c and ensure (a + b) + c == a + (b + c). + let a = Fq::rand(&mut rng); + let b = Fq::rand(&mut rng); + let c = Fq::rand(&mut rng); + + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.add_assign(&c); + + let mut tmp2 = b; + tmp2.add_assign(&c); + tmp2.add_assign(&a); + + assert!(tmp1.is_valid()); + assert!(tmp2.is_valid()); + assert_eq!(tmp1, tmp2); + } +} + +#[test] +fn test_fq_sub_assign() { + { + // Test arbitrary subtraction that tests reduction. + let mut tmp = Fq(FqRepr([0x531221a410efc95b, 0x72819306027e9717, 0x5ecefb937068b746, 0x97de59cd6feaefd7, 0xdc35c51158644588, 0xb2d176c04f2100])); + tmp.sub_assign(&Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806]))); + assert_eq!(tmp, Fq(FqRepr([0x748014838971292c, 0xfd20fad49fddde5c, 0xcf87f198e3d3f336, 0x3d62d6e6e41883db, 0x45a3443cd88dc61b, 0x151d57aaf755ff94]))); + + // Test the opposite subtraction which doesn't test reduction. + tmp = Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806])); + tmp.sub_assign(&Fq(FqRepr([0x531221a410efc95b, 0x72819306027e9717, 0x5ecefb937068b746, 0x97de59cd6feaefd7, 0xdc35c51158644588, 0xb2d176c04f2100]))); + assert_eq!(tmp, Fq(FqRepr([0x457eeb7c768e817f, 0x218b052a117621a3, 0x97a8e10812dd02ed, 0x2714749e0f6c8ee3, 0x57863796abde6bc, 0x4e3ba3f4229e706]))); + + // Test for sensible results with zero + tmp = Fq(FqRepr::from(0)); + tmp.sub_assign(&Fq(FqRepr::from(0))); + assert!(tmp.is_zero()); + + tmp = Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806])); + tmp.sub_assign(&Fq(FqRepr::from(0))); + assert_eq!(tmp, Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806]))); + } + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure that (a - b) + (b - a) = 0. + let a = Fq::rand(&mut rng); + let b = Fq::rand(&mut rng); + + let mut tmp1 = a; + tmp1.sub_assign(&b); + + let mut tmp2 = b; + tmp2.sub_assign(&a); + + tmp1.add_assign(&tmp2); + assert!(tmp1.is_zero()); + } +} + +#[bench] +fn bench_fq_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn test_fq_mul_assign() { + let mut tmp = Fq(FqRepr([0xcc6200000020aa8a, 0x422800801dd8001a, 0x7f4f5e619041c62c, 0x8a55171ac70ed2ba, 0x3f69cc3a3d07d58b, 0xb972455fd09b8ef])); + tmp.mul_assign(&Fq(FqRepr([0x329300000030ffcf, 0x633c00c02cc40028, 0xbef70d925862a942, 0x4f7fa2a82a963c17, 0xdf1eb2575b8bc051, 0x1162b680fb8e9566]))); + assert!(tmp == Fq(FqRepr([0x9dc4000001ebfe14, 0x2850078997b00193, 0xa8197f1abb4d7bf, 0xc0309573f4bfe871, 0xf48d0923ffaf7620, 0x11d4b58c7a926e66]))); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000000 { + // Ensure that (a * b) * c = a * (b * c) + let a = Fq::rand(&mut rng); + let b = Fq::rand(&mut rng); + let c = Fq::rand(&mut rng); + + let mut tmp1 = a; + tmp1.mul_assign(&b); + tmp1.mul_assign(&c); + + let mut tmp2 = b; + tmp2.mul_assign(&c); + tmp2.mul_assign(&a); + + assert_eq!(tmp1, tmp2); + } + + for _ in 0..1000000 { + // Ensure that r * (a + b + c) = r*a + r*b + r*c + + let r = Fq::rand(&mut rng); + let mut a = Fq::rand(&mut rng); + let mut b = Fq::rand(&mut rng); + let mut c = Fq::rand(&mut rng); + + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.add_assign(&c); + tmp1.mul_assign(&r); + + a.mul_assign(&r); + b.mul_assign(&r); + c.mul_assign(&r); + + a.add_assign(&b); + a.add_assign(&c); + + assert_eq!(tmp1, a); + } +} + +#[bench] +fn bench_fq_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn test_fq_squaring() { + let mut a = Fq(FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x19ffffffffffffff])); + assert!(a.is_valid()); + a.square(); + assert_eq!(a, Fq::from_repr(FqRepr([0x1cfb28fe7dfbbb86, 0x24cbe1731577a59, 0xcce1d4edc120e66e, 0xdc05c659b4e15b27, 0x79361e5a802c6a23, 0x24bcbe5d51b9a6f])).unwrap()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000000 { + // Ensure that (a * a) = a^2 + let a = Fq::rand(&mut rng); + + let mut tmp = a; + tmp.square(); + + let mut tmp2 = a; + tmp2.mul_assign(&a); + + assert_eq!(tmp, tmp2); + } +} + +#[bench] +fn bench_fq_square(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn test_fq_inverse() { + assert!(Fq::zero().inverse().is_none()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let one = Fq::one(); + + for _ in 0..1000 { + // Ensure that a * a^-1 = 1 + let mut a = Fq::rand(&mut rng); + let ainv = a.inverse().unwrap(); + a.mul_assign(&ainv); + assert_eq!(a, one); + } +} + +#[bench] +fn bench_fq_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].inverse() + }); +} + +#[test] +fn test_fq_double() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure doubling a is equivalent to adding a to itself. + let mut a = Fq::rand(&mut rng); + let mut b = a; + b.add_assign(&a); + a.double(); + assert_eq!(a, b); + } +} + +#[test] +fn test_fq_negate() { + { + let mut a = Fq::zero(); + a.negate(); + + assert!(a.is_zero()); + } + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure (a - (-a)) = 0. + let mut a = Fq::rand(&mut rng); + let mut b = a; + b.negate(); + a.add_assign(&b); + + assert!(a.is_zero()); + } +} + +#[bench] +fn bench_fq_negate(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.negate(); + count = (count + 1) % SAMPLES; + tmp + }); +} + + +#[test] +fn test_fq_pow() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for i in 0..1000 { + // Exponentiate by various small numbers and ensure it consists with repeated + // multiplication. + let a = Fq::rand(&mut rng); + let target = a.pow(&[i]); + let mut c = Fq::one(); + for _ in 0..i { + c.mul_assign(&a); + } + assert_eq!(c, target); + } + + for _ in 0..1000 { + // Exponentiating by the modulus should have no effect in a prime field. + let a = Fq::rand(&mut rng); + + assert_eq!(a, a.pow(Fq::char())); + } +} + +#[test] +fn test_fq_sqrt() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + assert_eq!(Fq::zero().sqrt().unwrap(), Fq::zero()); + + for _ in 0..1000 { + // Ensure sqrt(a^2) = a or -a + let a = Fq::rand(&mut rng); + let mut nega = a; + nega.negate(); + let mut b = a; + b.square(); + + let b = b.sqrt().unwrap(); + + assert!(a == b || nega == b); + } + + for _ in 0..1000 { + // Ensure sqrt(a)^2 = a for random a + let a = Fq::rand(&mut rng); + + if let Some(mut tmp) = a.sqrt() { + tmp.square(); + + assert_eq!(a, tmp); + } + } +} + +#[bench] +fn bench_fq_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + let mut tmp = Fq::rand(&mut rng); + tmp.square(); + tmp + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].sqrt() + }); +} + +#[test] +fn test_fq_from_into_repr() { + // q + 1 should not be in the field + assert!(Fq::from_repr(FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])).is_err()); + + // q should not be in the field + assert!(Fq::from_repr(Fq::char()).is_err()); + + // Multiply some arbitrary representations to see if the result is as expected. + let a = FqRepr([0x4a49dad4ff6cde2d, 0xac62a82a8f51cd50, 0x2b1f41ab9f36d640, 0x908a387f480735f1, 0xae30740c08a875d7, 0x6c80918a365ef78]); + let mut a_fq = Fq::from_repr(a).unwrap(); + let b = FqRepr([0xbba57917c32f0cf0, 0xe7f878cf87f05e5d, 0x9498b4292fd27459, 0xd59fd94ee4572cfa, 0x1f607186d5bb0059, 0xb13955f5ac7f6a3]); + let b_fq = Fq::from_repr(b).unwrap(); + let c = FqRepr([0xf5f70713b717914c, 0x355ea5ac64cbbab1, 0xce60dd43417ec960, 0xf16b9d77b0ad7d10, 0xa44c204c1de7cdb7, 0x1684487772bc9a5a]); + a_fq.mul_assign(&b_fq); + assert_eq!(a_fq.into_repr(), c); + + // Zero should be in the field. + assert!(Fq::from_repr(FqRepr::from(0)).unwrap().is_zero()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Try to turn Fq elements into representations and back again, and compare. + let a = Fq::rand(&mut rng); + let a_repr = a.into_repr(); + let b_repr = FqRepr::from(a); + assert_eq!(a_repr, b_repr); + let a_again = Fq::from_repr(a_repr).unwrap(); + + assert_eq!(a, a_again); + } +} + +#[bench] +fn bench_fq_into_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fq::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].into_repr() + }); +} + +#[bench] +fn bench_fq_from_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fq::rand(&mut rng).into_repr() + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + Fq::from_repr(v[count]) + }); +} + +#[test] +fn test_fq_repr_debug() { + assert_eq!( + format!("{:?}", FqRepr([0xa956babf9301ea24, 0x39a8f184f3535c7b, 0xb38d35b3f6779585, 0x676cc4eef4c46f2c, 0xb1d4aad87651e694, 0x1947f0d5f4fe325a])), + "0x1947f0d5f4fe325ab1d4aad87651e694676cc4eef4c46f2cb38d35b3f677958539a8f184f3535c7ba956babf9301ea24".to_string() + ); + assert_eq!( + format!("{:?}", FqRepr([0xb4171485fd8622dd, 0x864229a6edec7ec5, 0xc57f7bdcf8dfb707, 0x6db7ff0ecea4584a, 0xf8d8578c4a57132d, 0x6eb66d42d9fcaaa])), + "0x06eb66d42d9fcaaaf8d8578c4a57132d6db7ff0ecea4584ac57f7bdcf8dfb707864229a6edec7ec5b4171485fd8622dd".to_string() + ); + assert_eq!( + format!("{:?}", FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string() + ); + assert_eq!( + format!("{:?}", FqRepr([0, 0, 0, 0, 0, 0])), + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string() + ); +} + +#[test] +fn test_fq_debug() { + assert_eq!( + format!("{:?}", Fq::from_repr(FqRepr([0xa956babf9301ea24, 0x39a8f184f3535c7b, 0xb38d35b3f6779585, 0x676cc4eef4c46f2c, 0xb1d4aad87651e694, 0x1947f0d5f4fe325a])).unwrap()), + "Fq(0x1947f0d5f4fe325ab1d4aad87651e694676cc4eef4c46f2cb38d35b3f677958539a8f184f3535c7ba956babf9301ea24)".to_string() + ); + assert_eq!( + format!("{:?}", Fq::from_repr(FqRepr([0xe28e79396ac2bbf8, 0x413f6f7f06ea87eb, 0xa4b62af4a792a689, 0xb7f89f88f59c1dc5, 0x9a551859b1e43a9a, 0x6c9f5a1060de974])).unwrap()), + "Fq(0x06c9f5a1060de9749a551859b1e43a9ab7f89f88f59c1dc5a4b62af4a792a689413f6f7f06ea87ebe28e79396ac2bbf8)".to_string() + ); +} + +#[test] +fn test_fq_num_bits() { + assert_eq!(Fq::num_bits(), 381); + assert_eq!(Fq::capacity(), 380); +} + +#[test] +fn test_fq_root_of_unity() { + assert_eq!(Fq::s(), 1); + assert_eq!(Fq::multiplicative_generator(), Fq::from_repr(FqRepr::from(2)).unwrap()); + assert_eq!( + Fq::multiplicative_generator().pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]), + Fq::root_of_unity() + ); + assert_eq!( + Fq::root_of_unity().pow([1 << Fq::s()]), + Fq::one() + ); + assert!(Fq::multiplicative_generator().sqrt().is_none()); +} + +#[test] +fn fq_field_tests() { + ::tests::field::random_field_tests::(); + ::tests::field::random_sqrt_tests::(); + ::tests::field::random_frobenius_tests::(Fq::char(), 13); +} diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs new file mode 100644 index 0000000..4df2282 --- /dev/null +++ b/src/bls12_381/fq12.rs @@ -0,0 +1,289 @@ +use rand::{Rng, Rand}; +use ::{Field}; +use super::fq6::Fq6; +use super::fq2::Fq2; +use super::fq::{FROBENIUS_COEFF_FQ12_C1}; + +/// An element of F_{q^12}, represented by c0 + c1 * w. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Fq12 { + pub c0: Fq6, + pub c1: Fq6 +} + +impl Rand for Fq12 { + fn rand(rng: &mut R) -> Self { + Fq12 { + c0: rng.gen(), + c1: rng.gen() + } + } +} + +impl Fq12 { + pub fn unitary_inverse(&mut self) + { + self.c1.negate(); + } + + pub fn mul_by_014( + &mut self, + c0: &Fq2, + c1: &Fq2, + c4: &Fq2 + ) + { + let mut aa = self.c0; + aa.mul_by_01(c0, c1); + let mut bb = self.c1; + bb.mul_by_1(c4); + let mut o = *c1; + o.add_assign(c4); + self.c1.add_assign(&self.c0); + self.c1.mul_by_01(c0, &o); + self.c1.sub_assign(&aa); + self.c1.sub_assign(&bb); + self.c0 = bb; + self.c0.mul_by_nonresidue(); + self.c0.add_assign(&aa); + } +} + +impl Field for Fq12 +{ + fn zero() -> Self { + Fq12 { + c0: Fq6::zero(), + c1: Fq6::zero() + } + } + + fn one() -> Self { + Fq12 { + c0: Fq6::one(), + c1: Fq6::zero() + } + } + + fn is_zero(&self) -> bool { + self.c0.is_zero() && self.c1.is_zero() + } + + fn double(&mut self) { + self.c0.double(); + self.c1.double(); + } + + fn negate(&mut self) { + self.c0.negate(); + self.c1.negate(); + } + + fn add_assign(&mut self, other: &Self) { + self.c0.add_assign(&other.c0); + self.c1.add_assign(&other.c1); + } + + fn sub_assign(&mut self, other: &Self) { + self.c0.sub_assign(&other.c0); + self.c1.sub_assign(&other.c1); + } + + fn frobenius_map(&mut self, power: usize) + { + self.c0.frobenius_map(power); + self.c1.frobenius_map(power); + + self.c1.c0.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + self.c1.c1.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + self.c1.c2.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + } + + fn square(&mut self) { + let mut ab = self.c0; + ab.mul_assign(&self.c1); + let mut c0c1 = self.c0; + c0c1.add_assign(&self.c1); + let mut c0 = self.c1; + c0.mul_by_nonresidue(); + c0.add_assign(&self.c0); + c0.mul_assign(&c0c1); + c0.sub_assign(&ab); + self.c1 = ab; + self.c1.add_assign(&ab); + ab.mul_by_nonresidue(); + c0.sub_assign(&ab); + self.c0 = c0; + } + + fn mul_assign(&mut self, other: &Self) { + let mut aa = self.c0; + aa.mul_assign(&other.c0); + let mut bb = self.c1; + bb.mul_assign(&other.c1); + let mut o = other.c0; + o.add_assign(&other.c1); + self.c1.add_assign(&self.c0); + self.c1.mul_assign(&o); + self.c1.sub_assign(&aa); + self.c1.sub_assign(&bb); + self.c0 = bb; + self.c0.mul_by_nonresidue(); + self.c0.add_assign(&aa); + } + + fn inverse(&self) -> Option { + let mut c0s = self.c0; + c0s.square(); + let mut c1s = self.c1; + c1s.square(); + c1s.mul_by_nonresidue(); + c0s.sub_assign(&c1s); + + c0s.inverse().map(|t| { + let mut tmp = Fq12 { + c0: t, + c1: t + }; + tmp.c0.mul_assign(&self.c0); + tmp.c1.mul_assign(&self.c1); + tmp.c1.negate(); + + tmp + }) + } +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng}; + +#[test] +fn test_fq12_mul_by_014() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let c0 = Fq2::rand(&mut rng); + let c1 = Fq2::rand(&mut rng); + let c5 = Fq2::rand(&mut rng); + let mut a = Fq12::rand(&mut rng); + let mut b = a; + + a.mul_by_014(&c0, &c1, &c5); + b.mul_assign(&Fq12 { + c0: Fq6 { + c0: c0, + c1: c1, + c2: Fq2::zero() + }, + c1: Fq6 { + c0: Fq2::zero(), + c1: c5, + c2: Fq2::zero() + } + }); + + assert_eq!(a, b); + } +} + +#[bench] +fn bench_fq12_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { + (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { + (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { + (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_squaring(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fq12::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fq12::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].inverse(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn fq12_field_tests() { + use ::PrimeField; + + ::tests::field::random_field_tests::(); + ::tests::field::random_frobenius_tests::(super::fq::Fq::char(), 13); +} diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs new file mode 100644 index 0000000..55da940 --- /dev/null +++ b/src/bls12_381/fq2.rs @@ -0,0 +1,504 @@ +use rand::{Rng, Rand}; +use ::{Field, SqrtField}; +use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; + +/// An element of F_{q^2}, represented by c0 + c1 * u. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Fq2 { + pub c0: Fq, + pub c1: Fq +} + +impl Fq2 { + /// Multiply this element by the cubic and quadratic nonresidue 1 + u. + pub fn mul_by_nonresidue(&mut self) { + let t0 = self.c0; + self.c0.sub_assign(&self.c1); + self.c1.add_assign(&t0); + } +} + +impl Rand for Fq2 { + fn rand(rng: &mut R) -> Self { + Fq2 { + c0: rng.gen(), + c1: rng.gen() + } + } +} + +impl Field for Fq2 { + fn zero() -> Self { + Fq2 { c0: Fq::zero(), c1: Fq::zero() } + } + + fn one() -> Self { + Fq2 { c0: Fq::one(), c1: Fq::zero() } + } + + fn is_zero(&self) -> bool { + self.c0.is_zero() && self.c1.is_zero() + } + + fn square(&mut self) { + let mut ab = self.c0; + ab.mul_assign(&self.c1); + let mut c0c1 = self.c0; + c0c1.add_assign(&self.c1); + let mut c0 = self.c1; + c0.negate(); + c0.add_assign(&self.c0); + c0.mul_assign(&c0c1); + c0.sub_assign(&ab); + self.c1 = ab; + self.c1.add_assign(&ab); + c0.add_assign(&ab); + self.c0 = c0; + } + + fn double(&mut self) { + self.c0.double(); + self.c1.double(); + } + + fn negate(&mut self) { + self.c0.negate(); + self.c1.negate(); + } + + fn add_assign(&mut self, other: &Self) { + self.c0.add_assign(&other.c0); + self.c1.add_assign(&other.c1); + } + + fn sub_assign(&mut self, other: &Self) { + self.c0.sub_assign(&other.c0); + self.c1.sub_assign(&other.c1); + } + + fn mul_assign(&mut self, other: &Self) { + let mut aa = self.c0; + aa.mul_assign(&other.c0); + let mut bb = self.c1; + bb.mul_assign(&other.c1); + let mut o = other.c0; + o.add_assign(&other.c1); + self.c1.add_assign(&self.c0); + self.c1.mul_assign(&o); + self.c1.sub_assign(&aa); + self.c1.sub_assign(&bb); + self.c0 = aa; + self.c0.sub_assign(&bb); + } + + fn inverse(&self) -> Option { + let mut t1 = self.c1; + t1.square(); + let mut t0 = self.c0; + t0.square(); + t0.add_assign(&t1); + t0.inverse().map(|t| { + let mut tmp = Fq2 { + c0: self.c0, + c1: self.c1 + }; + tmp.c0.mul_assign(&t); + tmp.c1.mul_assign(&t); + tmp.c1.negate(); + + tmp + }) + } + + fn frobenius_map(&mut self, power: usize) + { + self.c1.mul_assign(&FROBENIUS_COEFF_FQ2_C1[power % 2]); + } +} + +impl SqrtField for Fq2 { + fn sqrt(&self) -> Option { + // Algorithm 9, https://eprint.iacr.org/2012/685.pdf + + if self.is_zero() { + return Some(Self::zero()); + } else { + // a1 = self^((q - 3) / 4) + let mut a1 = self.pow([0xee7fbfffffffeaaa, 0x7aaffffac54ffff, 0xd9cc34a83dac3d89, 0xd91dd2e13ce144af, 0x92c6e9ed90d2eb35, 0x680447a8e5ff9a6]); + let mut alpha = a1; + alpha.square(); + alpha.mul_assign(self); + let mut a0 = alpha; + a0.frobenius_map(1); + a0.mul_assign(&alpha); + + let neg1 = Fq2 { + c0: NEGATIVE_ONE, + c1: Fq::zero() + }; + + if a0 == neg1 { + None + } else { + a1.mul_assign(self); + + if alpha == neg1 { + a1.mul_assign(&Fq2{c0: Fq::zero(), c1: Fq::one()}); + } else { + alpha.add_assign(&Fq2::one()); + // alpha = alpha^((q - 1) / 2) + alpha = alpha.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]); + a1.mul_assign(&alpha); + } + + Some(a1) + } + } + } +} + +#[test] +fn test_fq2_basics() { + assert_eq!(Fq2 { c0: Fq::zero(), c1: Fq::zero() }, Fq2::zero()); + assert_eq!(Fq2 { c0: Fq::one(), c1: Fq::zero() }, Fq2::one()); + assert!(Fq2::zero().is_zero()); + assert!(!Fq2::one().is_zero()); + assert!(!Fq2{c0: Fq::zero(), c1: Fq::one()}.is_zero()); +} + +#[test] +fn test_fq2_squaring() { + use ::PrimeField; + use super::fq::{FqRepr}; + + let mut a = Fq2 { c0: Fq::one(), c1: Fq::one() }; // u + 1 + a.square(); + assert_eq!(a, Fq2 { c0: Fq::zero(), c1: Fq::from_repr(FqRepr::from(2)).unwrap() }); // 2u + + let mut a = Fq2 { c0: Fq::zero(), c1: Fq::one() }; // u + a.square(); + assert_eq!(a, { + let mut neg1 = Fq::one(); + neg1.negate(); + Fq2 { c0: neg1, c1: Fq::zero() } + }); // -1 + + let mut a = Fq2 { + c0: Fq::from_repr(FqRepr([0x9c2c6309bbf8b598, 0x4eef5c946536f602, 0x90e34aab6fb6a6bd, 0xf7f295a94e58ae7c, 0x41b76dcc1c3fbe5e, 0x7080c5fa1d8e042])).unwrap(), + c1: Fq::from_repr(FqRepr([0x38f473b3c870a4ab, 0x6ad3291177c8c7e5, 0xdac5a4c911a4353e, 0xbfb99020604137a0, 0xfc58a7b7be815407, 0x10d1615e75250a21])).unwrap() + }; + a.square(); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0xf262c28c538bcf68, 0xb9f2a66eae1073ba, 0xdc46ab8fad67ae0, 0xcb674157618da176, 0x4cf17b5893c3d327, 0x7eac81369c43361])).unwrap(), + c1: Fq::from_repr(FqRepr([0xc1579cf58e980cf8, 0xa23eb7e12dd54d98, 0xe75138bce4cec7aa, 0x38d0d7275a9689e1, 0x739c983042779a65, 0x1542a61c8a8db994])).unwrap() + }); +} + +#[test] +fn test_fq2_mul() { + use ::PrimeField; + use super::fq::{FqRepr}; + + let mut a = Fq2 { + c0: Fq::from_repr(FqRepr([0x85c9f989e1461f03, 0xa2e33c333449a1d6, 0x41e461154a7354a3, 0x9ee53e7e84d7532e, 0x1c202d8ed97afb45, 0x51d3f9253e2516f])).unwrap(), + c1: Fq::from_repr(FqRepr([0xa7348a8b511aedcf, 0x143c215d8176b319, 0x4cc48081c09b8903, 0x9533e4a9a5158be, 0x7a5e1ecb676d65f9, 0x180c3ee46656b008])).unwrap() + }; + a.mul_assign(&Fq2 { + c0: Fq::from_repr(FqRepr([0xe21f9169805f537e, 0xfc87e62e179c285d, 0x27ece175be07a531, 0xcd460f9f0c23e430, 0x6c9110292bfa409, 0x2c93a72eb8af83e])).unwrap(), + c1: Fq::from_repr(FqRepr([0x4b1c3f936d8992d4, 0x1d2a72916dba4c8a, 0x8871c508658d1e5f, 0x57a06d3135a752ae, 0x634cd3c6c565096d, 0x19e17334d4e93558])).unwrap() + }); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x95b5127e6360c7e4, 0xde29c31a19a6937e, 0xf61a96dacf5a39bc, 0x5511fe4d84ee5f78, 0x5310a202d92f9963, 0x1751afbe166e5399])).unwrap(), + c1: Fq::from_repr(FqRepr([0x84af0e1bd630117a, 0x6c63cd4da2c2aa7, 0x5ba6e5430e883d40, 0xc975106579c275ee, 0x33a9ac82ce4c5083, 0x1ef1a36c201589d])).unwrap() + }); +} + +#[test] +fn test_fq2_inverse() { + use ::PrimeField; + use super::fq::{FqRepr}; + + assert!(Fq2::zero().inverse().is_none()); + + let a = Fq2 { + c0: Fq::from_repr(FqRepr([0x85c9f989e1461f03, 0xa2e33c333449a1d6, 0x41e461154a7354a3, 0x9ee53e7e84d7532e, 0x1c202d8ed97afb45, 0x51d3f9253e2516f])).unwrap(), + c1: Fq::from_repr(FqRepr([0xa7348a8b511aedcf, 0x143c215d8176b319, 0x4cc48081c09b8903, 0x9533e4a9a5158be, 0x7a5e1ecb676d65f9, 0x180c3ee46656b008])).unwrap() + }; + let a = a.inverse().unwrap(); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x70300f9bcb9e594, 0xe5ecda5fdafddbb2, 0x64bef617d2915a8f, 0xdfba703293941c30, 0xa6c3d8f9586f2636, 0x1351ef01941b70c4])).unwrap(), + c1: Fq::from_repr(FqRepr([0x8c39fd76a8312cb4, 0x15d7b6b95defbff0, 0x947143f89faedee9, 0xcbf651a0f367afb2, 0xdf4e54f0d3ef15a6, 0x103bdf241afb0019])).unwrap() + }); +} + +#[test] +fn test_fq2_addition() { + use ::PrimeField; + use super::fq::{FqRepr}; + + let mut a = Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + }; + a.add_assign(&Fq2 { + c0: Fq::from_repr(FqRepr([0x619a02d78dc70ef2, 0xb93adfc9119e33e8, 0x4bf0b99a9f0dca12, 0x3b88899a42a6318f, 0x986a4a62fa82a49d, 0x13ce433fa26027f5])).unwrap(), + c1: Fq::from_repr(FqRepr([0x66323bf80b58b9b9, 0xa1379b6facf6e596, 0x402aef1fb797e32f, 0x2236f55246d0d44d, 0x4c8c1800eb104566, 0x11d6e20e986c2085])).unwrap() + }); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x8e9a7adaf6eb0eb9, 0xcb207e6b3341eaba, 0xd70b0c7b481d23ff, 0xf4ef57d604b6bca2, 0x65309427b3d5d090, 0x14c715d5553f01d2])).unwrap(), + c1: Fq::from_repr(FqRepr([0xfdb032e7d9079a94, 0x35a2809d15468d83, 0xfe4b23317e0796d5, 0xd62fa51334f560fa, 0x9ad265eb46e01984, 0x1303f3465112c8bc])).unwrap() + }); +} + +#[test] +fn test_fq2_subtraction() { + use ::PrimeField; + use super::fq::{FqRepr}; + + let mut a = Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + }; + a.sub_assign(&Fq2 { + c0: Fq::from_repr(FqRepr([0x619a02d78dc70ef2, 0xb93adfc9119e33e8, 0x4bf0b99a9f0dca12, 0x3b88899a42a6318f, 0x986a4a62fa82a49d, 0x13ce433fa26027f5])).unwrap(), + c1: Fq::from_repr(FqRepr([0x66323bf80b58b9b9, 0xa1379b6facf6e596, 0x402aef1fb797e32f, 0x2236f55246d0d44d, 0x4c8c1800eb104566, 0x11d6e20e986c2085])).unwrap() + }); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x8565752bdb5c9b80, 0x7756bed7c15982e9, 0xa65a6be700b285fe, 0xe255902672ef6c43, 0x7f77a718021c342d, 0x72ba14049fe9881])).unwrap(), + c1: Fq::from_repr(FqRepr([0xeb4abaf7c255d1cd, 0x11df49bc6cacc256, 0xe52617930588c69a, 0xf63905f39ad8cb1f, 0x4cd5dd9fb40b3b8f, 0x957411359ba6e4c])).unwrap() + }); +} + +#[test] +fn test_fq2_negation() { + use ::PrimeField; + use super::fq::{FqRepr}; + + let mut a = Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + }; + a.negate(); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x8cfe87fc96dbaae4, 0xcc6615c8fb0492d, 0xdc167fc04da19c37, 0xab107d49317487ab, 0x7e555df189f880e3, 0x19083f5486a10cbd])).unwrap(), + c1: Fq::from_repr(FqRepr([0x228109103250c9d0, 0x8a411ad149045812, 0xa9109e8f3041427e, 0xb07e9bc405608611, 0xfcd559cbe77bd8b8, 0x18d400b280d93e62])).unwrap() + }); +} + +#[test] +fn test_fq2_doubling() { + use ::PrimeField; + use super::fq::{FqRepr}; + + let mut a = Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + }; + a.double(); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x5a00f006d247ff8e, 0x23cb3d4443476da4, 0x1634a5c1521eb3da, 0x72cd9c7784211627, 0x998c938972a657e7, 0x1f1a52b65bdb3b9])).unwrap(), + c1: Fq::from_repr(FqRepr([0x2efbeddf9b5dc1b6, 0x28d5ca5ad09f4fdb, 0x7c4068238cdf674b, 0x67f15f81dc49195b, 0x9c8c9bd4b79fa83d, 0x25a226f714d506e])).unwrap() + }); +} + +#[test] +fn test_fq2_frobenius_map() { + use ::PrimeField; + use super::fq::{FqRepr}; + + let mut a = Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + }; + a.frobenius_map(0); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + }); + a.frobenius_map(1); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x228109103250c9d0, 0x8a411ad149045812, 0xa9109e8f3041427e, 0xb07e9bc405608611, 0xfcd559cbe77bd8b8, 0x18d400b280d93e62])).unwrap() + }); + a.frobenius_map(1); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + }); + a.frobenius_map(2); + assert_eq!(a, Fq2 { + c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), + c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + }); +} + +#[test] +fn test_fq2_sqrt() { + use ::PrimeField; + use super::fq::{FqRepr}; + + assert_eq!( + Fq2 { + c0: Fq::from_repr(FqRepr([0x476b4c309720e227, 0x34c2d04faffdab6, 0xa57e6fc1bab51fd9, 0xdb4a116b5bf74aa1, 0x1e58b2159dfe10e2, 0x7ca7da1f13606ac])).unwrap(), + c1: Fq::from_repr(FqRepr([0xfa8de88b7516d2c3, 0x371a75ed14f41629, 0x4cec2dca577a3eb6, 0x212611bca4e99121, 0x8ee5394d77afb3d, 0xec92336650e49d5])).unwrap() + }.sqrt().unwrap(), + Fq2 { + c0: Fq::from_repr(FqRepr([0x40b299b2704258c5, 0x6ef7de92e8c68b63, 0x6d2ddbe552203e82, 0x8d7f1f723d02c1d3, 0x881b3e01b611c070, 0x10f6963bbad2ebc5])).unwrap(), + c1: Fq::from_repr(FqRepr([0xc099534fc209e752, 0x7670594665676447, 0x28a20faed211efe7, 0x6b852aeaf2afcb1b, 0xa4c93b08105d71a9, 0x8d7cfff94216330])).unwrap() + } + ); + + assert_eq!( + Fq2 { + c0: Fq::from_repr(FqRepr([0xb9f78429d1517a6b, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])).unwrap(), + c1: Fq::zero() + }.sqrt().unwrap(), + Fq2 { + c0: Fq::zero(), + c1: Fq::from_repr(FqRepr([0xb9fefffffd4357a3, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])).unwrap() + } + ); +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng}; + +#[test] +fn test_fq2_mul_nonresidue() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let nqr = Fq2 { + c0: Fq::one(), + c1: Fq::one() + }; + + for _ in 0..1000 { + let mut a = Fq2::rand(&mut rng); + let mut b = a; + a.mul_by_nonresidue(); + b.mul_assign(&nqr); + + assert_eq!(a, b); + } +} + +#[bench] +fn bench_fq2_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { + (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { + (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { + (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_squaring(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fq2::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fq2::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].inverse(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fq2::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].sqrt(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn fq2_field_tests() { + use ::PrimeField; + + ::tests::field::random_field_tests::(); + ::tests::field::random_sqrt_tests::(); + ::tests::field::random_frobenius_tests::(super::fq::Fq::char(), 13); +} diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs new file mode 100644 index 0000000..81226da --- /dev/null +++ b/src/bls12_381/fq6.rs @@ -0,0 +1,372 @@ +use rand::{Rng, Rand}; +use ::{Field}; +use super::fq2::Fq2; +use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; + +/// An element of F_{q^6}, represented by c0 + c1 * v + c2 * v^2. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Fq6 { + pub c0: Fq2, + pub c1: Fq2, + pub c2: Fq2 +} + +impl Rand for Fq6 { + fn rand(rng: &mut R) -> Self { + Fq6 { + c0: rng.gen(), + c1: rng.gen(), + c2: rng.gen() + } + } +} + +impl Fq6 { + /// Multiply by quadratic nonresidue v. + pub fn mul_by_nonresidue(&mut self) { + use std::mem::swap; + swap(&mut self.c0, &mut self.c1); + swap(&mut self.c0, &mut self.c2); + + self.c0.mul_by_nonresidue(); + } + + pub fn mul_by_1(&mut self, c1: &Fq2) + { + let mut b_b = self.c1; + b_b.mul_assign(c1); + + let mut t1 = *c1; + { + let mut tmp = self.c1; + tmp.add_assign(&self.c2); + + t1.mul_assign(&tmp); + t1.sub_assign(&b_b); + t1.mul_by_nonresidue(); + } + + let mut t2 = *c1; + { + let mut tmp = self.c0; + tmp.add_assign(&self.c1); + + t2.mul_assign(&tmp); + t2.sub_assign(&b_b); + } + + self.c0 = t1; + self.c1 = t2; + self.c2 = b_b; + } + + pub fn mul_by_01(&mut self, c0: &Fq2, c1: &Fq2) + { + let mut a_a = self.c0; + let mut b_b = self.c1; + a_a.mul_assign(c0); + b_b.mul_assign(c1); + + let mut t1 = *c1; + { + let mut tmp = self.c1; + tmp.add_assign(&self.c2); + + t1.mul_assign(&tmp); + t1.sub_assign(&b_b); + t1.mul_by_nonresidue(); + t1.add_assign(&a_a); + } + + let mut t3 = *c0; + { + let mut tmp = self.c0; + tmp.add_assign(&self.c2); + + t3.mul_assign(&tmp); + t3.sub_assign(&a_a); + t3.add_assign(&b_b); + } + + let mut t2 = *c0; + t2.add_assign(c1); + { + let mut tmp = self.c0; + tmp.add_assign(&self.c1); + + t2.mul_assign(&tmp); + t2.sub_assign(&a_a); + t2.sub_assign(&b_b); + } + + self.c0 = t1; + self.c1 = t2; + self.c2 = t3; + } +} + +impl Field for Fq6 +{ + fn zero() -> Self { + Fq6 { + c0: Fq2::zero(), + c1: Fq2::zero(), + c2: Fq2::zero() + } + } + + fn one() -> Self { + Fq6 { + c0: Fq2::one(), + c1: Fq2::zero(), + c2: Fq2::zero() + } + } + + fn is_zero(&self) -> bool { + self.c0.is_zero() && self.c1.is_zero() && self.c2.is_zero() + } + + fn double(&mut self) { + self.c0.double(); + self.c1.double(); + self.c2.double(); + } + + fn negate(&mut self) { + self.c0.negate(); + self.c1.negate(); + self.c2.negate(); + } + + fn add_assign(&mut self, other: &Self) { + self.c0.add_assign(&other.c0); + self.c1.add_assign(&other.c1); + self.c2.add_assign(&other.c2); + } + + fn sub_assign(&mut self, other: &Self) { + self.c0.sub_assign(&other.c0); + self.c1.sub_assign(&other.c1); + self.c2.sub_assign(&other.c2); + } + + fn frobenius_map(&mut self, power: usize) + { + self.c0.frobenius_map(power); + self.c1.frobenius_map(power); + self.c2.frobenius_map(power); + + self.c1.mul_assign(&FROBENIUS_COEFF_FQ6_C1[power % 6]); + self.c2.mul_assign(&FROBENIUS_COEFF_FQ6_C2[power % 6]); + } + + fn square(&mut self) { + let mut s0 = self.c0; + s0.square(); + let mut ab = self.c0; + ab.mul_assign(&self.c1); + let mut s1 = ab; + s1.double(); + let mut s2 = self.c0; + s2.sub_assign(&self.c1); + s2.add_assign(&self.c2); + s2.square(); + let mut bc = self.c1; + bc.mul_assign(&self.c2); + let mut s3 = bc; + s3.double(); + let mut s4 = self.c2; + s4.square(); + + self.c0 = s3; + self.c0.mul_by_nonresidue(); + self.c0.add_assign(&s0); + + self.c1 = s4; + self.c1.mul_by_nonresidue(); + self.c1.add_assign(&s1); + + self.c2 = s1; + self.c2.add_assign(&s2); + self.c2.add_assign(&s3); + self.c2.sub_assign(&s0); + self.c2.sub_assign(&s4); + } + + fn mul_assign(&mut self, other: &Self) { + let mut a_a = self.c0; + let mut b_b = self.c1; + let mut c_c = self.c2; + a_a.mul_assign(&other.c0); + b_b.mul_assign(&other.c1); + c_c.mul_assign(&other.c2); + + let mut t1 = other.c1; + t1.add_assign(&other.c2); + { + let mut tmp = self.c1; + tmp.add_assign(&self.c2); + + t1.mul_assign(&tmp); + t1.sub_assign(&b_b); + t1.sub_assign(&c_c); + t1.mul_by_nonresidue(); + t1.add_assign(&a_a); + } + + let mut t3 = other.c0; + t3.add_assign(&other.c2); + { + let mut tmp = self.c0; + tmp.add_assign(&self.c2); + + t3.mul_assign(&tmp); + t3.sub_assign(&a_a); + t3.add_assign(&b_b); + t3.sub_assign(&c_c); + } + + let mut t2 = other.c0; + t2.add_assign(&other.c1); + { + let mut tmp = self.c0; + tmp.add_assign(&self.c1); + + t2.mul_assign(&tmp); + t2.sub_assign(&a_a); + t2.sub_assign(&b_b); + c_c.mul_by_nonresidue(); + t2.add_assign(&c_c); + } + + self.c0 = t1; + self.c1 = t2; + self.c2 = t3; + } + + fn inverse(&self) -> Option { + let mut c0 = self.c2; + c0.mul_by_nonresidue(); + c0.mul_assign(&self.c1); + c0.negate(); + { + let mut c0s = self.c0; + c0s.square(); + c0.add_assign(&c0s); + } + let mut c1 = self.c2; + c1.square(); + c1.mul_by_nonresidue(); + { + let mut c01 = self.c0; + c01.mul_assign(&self.c1); + c1.sub_assign(&c01); + } + let mut c2 = self.c1; + c2.square(); + { + let mut c02 = self.c0; + c02.mul_assign(&self.c2); + c2.sub_assign(&c02); + } + + let mut tmp1 = self.c2; + tmp1.mul_assign(&c1); + let mut tmp2 = self.c1; + tmp2.mul_assign(&c2); + tmp1.add_assign(&tmp2); + tmp1.mul_by_nonresidue(); + tmp2 = self.c0; + tmp2.mul_assign(&c0); + tmp1.add_assign(&tmp2); + + match tmp1.inverse() { + Some(t) => { + let mut tmp = Fq6 { + c0: t, + c1: t, + c2: t + }; + tmp.c0.mul_assign(&c0); + tmp.c1.mul_assign(&c1); + tmp.c2.mul_assign(&c2); + + Some(tmp) + }, + None => None + } + } +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng}; + +#[test] +fn test_fq6_mul_nonresidue() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let nqr = Fq6 { + c0: Fq2::zero(), + c1: Fq2::one(), + c2: Fq2::zero() + }; + + for _ in 0..1000 { + let mut a = Fq6::rand(&mut rng); + let mut b = a; + a.mul_by_nonresidue(); + b.mul_assign(&nqr); + + assert_eq!(a, b); + } +} + +#[test] +fn test_fq6_mul_by_1() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let c1 = Fq2::rand(&mut rng); + let mut a = Fq6::rand(&mut rng); + let mut b = a; + + a.mul_by_1(&c1); + b.mul_assign(&Fq6 { + c0: Fq2::zero(), + c1: c1, + c2: Fq2::zero() + }); + + assert_eq!(a, b); + } +} + +#[test] +fn test_fq6_mul_by_01() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let c0 = Fq2::rand(&mut rng); + let c1 = Fq2::rand(&mut rng); + let mut a = Fq6::rand(&mut rng); + let mut b = a; + + a.mul_by_01(&c0, &c1); + b.mul_assign(&Fq6 { + c0: c0, + c1: c1, + c2: Fq2::zero() + }); + + assert_eq!(a, b); + } +} + +#[test] +fn fq6_field_tests() { + use ::PrimeField; + + ::tests::field::random_field_tests::(); + ::tests::field::random_frobenius_tests::(super::fq::Fq::char(), 13); +} diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs new file mode 100644 index 0000000..7c274ab --- /dev/null +++ b/src/bls12_381/fr.rs @@ -0,0 +1,1464 @@ +use ::{Field, PrimeField, SqrtField, PrimeFieldRepr}; + +// r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 +const MODULUS: FrRepr = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + +// The number of bits needed to represent the modulus. +const MODULUS_BITS: u32 = 255; + +// The number of bits that must be shaved from the beginning of +// the representation when randomly sampling. +const REPR_SHAVE_BITS: u32 = 1; + +// R = 2**256 % r +const R: FrRepr = FrRepr([0x1fffffffe, 0x5884b7fa00034802, 0x998c4fefecbc4ff5, 0x1824b159acc5056f]); + +// R2 = R^2 % r +const R2: FrRepr = FrRepr([0xc999e990f3f29c6d, 0x2b6cedcb87925c23, 0x5d314967254398f, 0x748d9d99f59ff11]); + +// INV = -(r^{-1} mod r) mod r +const INV: u64 = 0xfffffffeffffffff; + +// GENERATOR = 7 (multiplicative generator of r-1 order, that is also quadratic nonresidue) +const GENERATOR: FrRepr = FrRepr([0xefffffff1, 0x17e363d300189c0f, 0xff9c57876f8457b0, 0x351332208fc5a8c4]); + +// 2^s * t = MODULUS - 1 with t odd +const S: usize = 32; + +// 2^s root of unity computed by GENERATOR^t +const ROOT_OF_UNITY: FrRepr = FrRepr([0xb9b58d8c5f0e466a, 0x5b1b4c801819d7ec, 0xaf53ae352a31e64, 0x5bf3adda19e9b27b]); + +#[derive(Copy, Clone, PartialEq, Eq, Default)] +pub struct FrRepr(pub [u64; 4]); + +impl ::rand::Rand for FrRepr { + #[inline(always)] + fn rand(rng: &mut R) -> Self { + FrRepr(rng.gen()) + } +} + +impl ::std::fmt::Debug for FrRepr +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + try!(write!(f, "0x")); + for i in self.0.iter().rev() { + try!(write!(f, "{:016x}", *i)); + } + + Ok(()) + } +} + +impl AsRef<[u64]> for FrRepr { + #[inline(always)] + fn as_ref(&self) -> &[u64] { + &self.0 + } +} + +impl From for FrRepr { + #[inline(always)] + fn from(val: u64) -> FrRepr { + use std::default::Default; + + let mut repr = Self::default(); + repr.0[0] = val; + repr + } +} + +impl Ord for FrRepr { + #[inline(always)] + fn cmp(&self, other: &FrRepr) -> ::std::cmp::Ordering { + for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { + if a < b { + return ::std::cmp::Ordering::Less + } else if a > b { + return ::std::cmp::Ordering::Greater + } + } + + ::std::cmp::Ordering::Equal + } +} + +impl PartialOrd for FrRepr { + #[inline(always)] + fn partial_cmp(&self, other: &FrRepr) -> Option<::std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl PrimeFieldRepr for FrRepr { + #[inline(always)] + fn is_odd(&self) -> bool { + self.0[0] & 1 == 1 + } + + #[inline(always)] + fn is_even(&self) -> bool { + !self.is_odd() + } + + #[inline(always)] + fn is_zero(&self) -> bool { + self.0.iter().all(|&e| e == 0) + } + + #[inline(always)] + fn divn(&mut self, mut n: usize) { + if n >= 64 * 4 { + *self = Self::from(0); + return; + } + + while n >= 64 { + let mut t = 0; + for i in self.0.iter_mut().rev() { + ::std::mem::swap(&mut t, i); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + for i in self.0.iter_mut().rev() { + let t2 = *i << (64 - n); + *i >>= n; + *i |= t; + t = t2; + } + } + } + + #[inline(always)] + fn div2(&mut self) { + let mut t = 0; + for i in self.0.iter_mut().rev() { + let t2 = *i << 63; + *i >>= 1; + *i |= t; + t = t2; + } + } + + #[inline(always)] + fn mul2(&mut self) { + let mut last = 0; + for i in self.0.iter_mut() { + let tmp = *i >> 63; + *i <<= 1; + *i |= last; + last = tmp; + } + } + + #[inline(always)] + fn num_bits(&self) -> u32 { + let mut ret = (4 as u32) * 64; + for i in self.0.iter().rev() { + let leading = i.leading_zeros(); + ret -= leading; + if leading != 64 { + break; + } + } + + ret + } + + #[inline(always)] + fn add_nocarry(&mut self, other: &FrRepr) -> bool { + let mut carry = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = ::adc(*a, *b, &mut carry); + } + + carry != 0 + } + + #[inline(always)] + fn sub_noborrow(&mut self, other: &FrRepr) -> bool { + let mut borrow = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = ::sbb(*a, *b, &mut borrow); + } + + borrow != 0 + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Fr(FrRepr); + +impl ::std::fmt::Debug for Fr +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fr({:?})", self.into_repr()) + } +} + +impl ::rand::Rand for Fr { + fn rand(rng: &mut R) -> Self { + loop { + let mut tmp = Fr(FrRepr::rand(rng)); + for _ in 0..REPR_SHAVE_BITS { + tmp.0.div2(); + } + if tmp.is_valid() { + return tmp + } + } + } +} + +impl From for FrRepr { + fn from(e: Fr) -> FrRepr { + e.into_repr() + } +} + +impl PrimeField for Fr { + type Repr = FrRepr; + + fn from_repr(r: FrRepr) -> Result { + let mut r = Fr(r); + if r.is_valid() { + r.mul_assign(&Fr(R2)); + + Ok(r) + } else { + Err(()) + } + } + + fn into_repr(&self) -> FrRepr { + let mut r = *self; + r.mont_reduce((self.0).0[0], (self.0).0[1], + (self.0).0[2], (self.0).0[3], + 0, 0, 0, 0); + r.0 + } + + fn char() -> FrRepr { + MODULUS + } + + fn num_bits() -> u32 { + MODULUS_BITS + } + + fn capacity() -> u32 { + Self::num_bits() - 1 + } + + fn multiplicative_generator() -> Self { + Fr(GENERATOR) + } + + fn s() -> usize { + S + } + + fn root_of_unity() -> Self { + Fr(ROOT_OF_UNITY) + } +} + +impl Field for Fr { + #[inline] + fn zero() -> Self { + Fr(FrRepr::from(0)) + } + + #[inline] + fn one() -> Self { + Fr(R) + } + + #[inline] + fn is_zero(&self) -> bool { + self.0.is_zero() + } + + #[inline] + fn add_assign(&mut self, other: &Fr) { + // This cannot exceed the backing capacity. + self.0.add_nocarry(&other.0); + + // However, it may need to be reduced. + self.reduce(); + } + + #[inline] + fn double(&mut self) { + // This cannot exceed the backing capacity. + self.0.mul2(); + + // However, it may need to be reduced. + self.reduce(); + } + + #[inline] + fn sub_assign(&mut self, other: &Fr) { + // If `other` is larger than `self`, we'll need to add the modulus to self first. + if other.0 > self.0 { + self.0.add_nocarry(&MODULUS); + } + + self.0.sub_noborrow(&other.0); + } + + #[inline] + fn negate(&mut self) { + if !self.is_zero() { + let mut tmp = MODULUS; + tmp.sub_noborrow(&self.0); + self.0 = tmp; + } + } + + fn inverse(&self) -> Option { + if self.is_zero() { + None + } else { + // Guajardo Kumar Paar Pelzl + // Efficient Software-Implementation of Finite Fields with Applications to Cryptography + // Algorithm 16 (BEA for Inversion in Fp) + + let one = FrRepr::from(1); + + let mut u = self.0; + let mut v = MODULUS; + let mut b = Fr(R2); // Avoids unnecessary reduction step. + let mut c = Self::zero(); + + while u != one && v != one { + while u.is_even() { + u.div2(); + + if b.0.is_even() { + b.0.div2(); + } else { + b.0.add_nocarry(&MODULUS); + b.0.div2(); + } + } + + while v.is_even() { + v.div2(); + + if c.0.is_even() { + c.0.div2(); + } else { + c.0.add_nocarry(&MODULUS); + c.0.div2(); + } + } + + if v < u { + u.sub_noborrow(&v); + b.sub_assign(&c); + } else { + v.sub_noborrow(&u); + c.sub_assign(&b); + } + } + + if u == one { + Some(b) + } else { + Some(c) + } + } + } + + #[inline(always)] + fn frobenius_map(&mut self, _: usize) { + // This has no effect in a prime field. + } + + #[inline] + fn mul_assign(&mut self, other: &Fr) + { + let mut carry = 0; + let r0 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); + let r1 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); + let r2 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); + let r3 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); + let r4 = carry; + let mut carry = 0; + let r1 = ::mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); + let r2 = ::mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); + let r3 = ::mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); + let r5 = carry; + let mut carry = 0; + let r2 = ::mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); + let r3 = ::mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); + let r5 = ::mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); + let r6 = carry; + let mut carry = 0; + let r3 = ::mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); + let r5 = ::mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); + let r7 = carry; + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + } + + #[inline] + fn square(&mut self) + { + let mut carry = 0; + let r1 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); + let r2 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); + let r3 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); + let r4 = carry; + let mut carry = 0; + let r3 = ::mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); + let r5 = carry; + let mut carry = 0; + let r5 = ::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); + let r6 = carry; + let tmp0 = r1 >> 63; + let r1 = r1 << 1; + let tmp1 = r2 >> 63; + let r2 = r2 << 1; + let r2 = r2 | tmp0; + let tmp0 = tmp1; + let tmp1 = r3 >> 63; + let r3 = r3 << 1; + let r3 = r3 | tmp0; + let tmp0 = tmp1; + let tmp1 = r4 >> 63; + let r4 = r4 << 1; + let r4 = r4 | tmp0; + let tmp0 = tmp1; + let tmp1 = r5 >> 63; + let r5 = r5 << 1; + let r5 = r5 | tmp0; + let tmp0 = tmp1; + let tmp1 = r6 >> 63; + let r6 = r6 << 1; + let r6 = r6 | tmp0; + let tmp0 = tmp1; + let r7 = tmp0; + let mut carry = 0; + let r0 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); + let r1 = ::adc(r1, 0, &mut carry); + let r2 = ::mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); + let r3 = ::adc(r3, 0, &mut carry); + let r4 = ::mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); + let r5 = ::adc(r5, 0, &mut carry); + let r6 = ::mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); + let r7 = ::adc(r7, 0, &mut carry); + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + } +} + +impl Fr { + /// Determines if the element is really in the field. This is only used + /// internally. + #[inline(always)] + fn is_valid(&self) -> bool { + self.0 < MODULUS + } + + /// Subtracts the modulus from this element if this element is not in the + /// field. Only used interally. + #[inline(always)] + fn reduce(&mut self) { + if !self.is_valid() { + self.0.sub_noborrow(&MODULUS); + } + } + + #[inline(always)] + fn mont_reduce( + &mut self, + r0: u64, + mut r1: u64, + mut r2: u64, + mut r3: u64, + mut r4: u64, + mut r5: u64, + mut r6: u64, + mut r7: u64 + ) + { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = r0.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r0, k, MODULUS.0[0], &mut carry); + r1 = ::mac_with_carry(r1, k, MODULUS.0[1], &mut carry); + r2 = ::mac_with_carry(r2, k, MODULUS.0[2], &mut carry); + r3 = ::mac_with_carry(r3, k, MODULUS.0[3], &mut carry); + r4 = ::adc(r4, 0, &mut carry); + let carry2 = carry; + let k = r1.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r1, k, MODULUS.0[0], &mut carry); + r2 = ::mac_with_carry(r2, k, MODULUS.0[1], &mut carry); + r3 = ::mac_with_carry(r3, k, MODULUS.0[2], &mut carry); + r4 = ::mac_with_carry(r4, k, MODULUS.0[3], &mut carry); + r5 = ::adc(r5, carry2, &mut carry); + let carry2 = carry; + let k = r2.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r2, k, MODULUS.0[0], &mut carry); + r3 = ::mac_with_carry(r3, k, MODULUS.0[1], &mut carry); + r4 = ::mac_with_carry(r4, k, MODULUS.0[2], &mut carry); + r5 = ::mac_with_carry(r5, k, MODULUS.0[3], &mut carry); + r6 = ::adc(r6, carry2, &mut carry); + let carry2 = carry; + let k = r3.wrapping_mul(INV); + let mut carry = 0; + ::mac_with_carry(r3, k, MODULUS.0[0], &mut carry); + r4 = ::mac_with_carry(r4, k, MODULUS.0[1], &mut carry); + r5 = ::mac_with_carry(r5, k, MODULUS.0[2], &mut carry); + r6 = ::mac_with_carry(r6, k, MODULUS.0[3], &mut carry); + r7 = ::adc(r7, carry2, &mut carry); + (self.0).0[0] = r4; + (self.0).0[1] = r5; + (self.0).0[2] = r6; + (self.0).0[3] = r7; + self.reduce(); + } +} + +impl SqrtField for Fr { + fn sqrt(&self) -> Option { + // Tonelli-Shank's algorithm for q mod 16 = 1 + // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) + + if self.is_zero() { + return Some(*self); + } + + // if self^((r - 1) // 2) != 1 + if self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]) != Self::one() { + None + } else { + let mut c = Fr(ROOT_OF_UNITY); + // r = self^((t + 1) // 2) + let mut r = self.pow([0x7fff2dff80000000, 0x4d0ec02a9ded201, 0x94cebea4199cec04, 0x39f6d3a9]); + // t = self^t + let mut t = self.pow([0xfffe5bfeffffffff, 0x9a1d80553bda402, 0x299d7d483339d808, 0x73eda753]); + let mut m = S; + + while t != Self::one() { + let mut i = 1; + { + let mut t2i = t; + t2i.square(); + loop { + if t2i == Self::one() { + break; + } + t2i.square(); + i += 1; + } + } + + for _ in 0..(m - i - 1) { + c.square(); + } + r.mul_assign(&c); + c.square(); + t.mul_assign(&c); + m = i; + } + + Some(r) + } + } +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng, Rand}; + +#[test] +fn test_fr_repr_ordering() { + fn assert_equality(a: FrRepr, b: FrRepr) { + assert_eq!(a, b); + assert!(a.cmp(&b) == ::std::cmp::Ordering::Equal); + } + + fn assert_lt(a: FrRepr, b: FrRepr) { + assert!(a < b); + assert!(b > a); + } + + assert_equality(FrRepr([9999, 9999, 9999, 9999]), FrRepr([9999, 9999, 9999, 9999])); + assert_equality(FrRepr([9999, 9998, 9999, 9999]), FrRepr([9999, 9998, 9999, 9999])); + assert_equality(FrRepr([9999, 9999, 9999, 9997]), FrRepr([9999, 9999, 9999, 9997])); + assert_lt(FrRepr([9999, 9997, 9999, 9998]), FrRepr([9999, 9997, 9999, 9999])); + assert_lt(FrRepr([9999, 9997, 9998, 9999]), FrRepr([9999, 9997, 9999, 9999])); + assert_lt(FrRepr([9, 9999, 9999, 9997]), FrRepr([9999, 9999, 9999, 9997])); +} + +#[test] +fn test_fr_repr_from() { + assert_eq!(FrRepr::from(100), FrRepr([100, 0, 0, 0])); +} + +#[test] +fn test_fr_repr_is_odd() { + assert!(!FrRepr::from(0).is_odd()); + assert!(FrRepr::from(0).is_even()); + assert!(FrRepr::from(1).is_odd()); + assert!(!FrRepr::from(1).is_even()); + assert!(!FrRepr::from(324834872).is_odd()); + assert!(FrRepr::from(324834872).is_even()); + assert!(FrRepr::from(324834873).is_odd()); + assert!(!FrRepr::from(324834873).is_even()); +} + +#[test] +fn test_fr_repr_is_zero() { + assert!(FrRepr::from(0).is_zero()); + assert!(!FrRepr::from(1).is_zero()); + assert!(!FrRepr([0, 0, 1, 0]).is_zero()); +} + +#[test] +fn test_fr_repr_div2() { + let mut a = FrRepr([0xbd2920b19c972321, 0x174ed0466a3be37e, 0xd468d5e3b551f0b5, 0xcb67c072733beefc]); + a.div2(); + assert_eq!(a, FrRepr([0x5e949058ce4b9190, 0x8ba76823351df1bf, 0x6a346af1daa8f85a, 0x65b3e039399df77e])); + for _ in 0..10 { + a.div2(); + } + assert_eq!(a, FrRepr([0x6fd7a524163392e4, 0x16a2e9da08cd477c, 0xdf9a8d1abc76aa3e, 0x196cf80e4e677d])); + for _ in 0..200 { + a.div2(); + } + assert_eq!(a, FrRepr([0x196cf80e4e67, 0x0, 0x0, 0x0])); + for _ in 0..40 { + a.div2(); + } + assert_eq!(a, FrRepr([0x19, 0x0, 0x0, 0x0])); + for _ in 0..4 { + a.div2(); + } + assert_eq!(a, FrRepr([0x1, 0x0, 0x0, 0x0])); + a.div2(); + assert!(a.is_zero()); +} + +#[test] +fn test_fr_repr_divn() { + let mut a = FrRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]); + a.divn(0); + assert_eq!( + a, + FrRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]) + ); + a.divn(1); + assert_eq!( + a, + FrRepr([0xd99fdd762415141f, 0xccbef069d44659ef, 0xcd7b16954d072a92, 0x1b001d5846f386d0]) + ); + a.divn(50); + assert_eq!( + a, + FrRepr([0xbc1a7511967bf667, 0xc5a55341caa4b32f, 0x75611bce1b4335e, 0x6c0]) + ); + a.divn(130); + assert_eq!( + a, + FrRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0]) + ); + a.divn(64); + assert_eq!( + a, + FrRepr([0x1b0, 0x0, 0x0, 0x0]) + ); +} + +#[test] +fn test_fr_repr_mul2() { + let mut a = FrRepr::from(23712937547); + a.mul2(); + assert_eq!(a, FrRepr([0xb0acd6c96, 0x0, 0x0, 0x0])); + for _ in 0..60 { + a.mul2(); + } + assert_eq!(a, FrRepr([0x6000000000000000, 0xb0acd6c9, 0x0, 0x0])); + for _ in 0..128 { + a.mul2(); + } + assert_eq!(a, FrRepr([0x0, 0x0, 0x6000000000000000, 0xb0acd6c9])); + for _ in 0..60 { + a.mul2(); + } + assert_eq!(a, FrRepr([0x0, 0x0, 0x0, 0x9600000000000000])); + for _ in 0..7 { + a.mul2(); + } + assert!(a.is_zero()); +} + +#[test] +fn test_fr_repr_num_bits() { + let mut a = FrRepr::from(0); + assert_eq!(0, a.num_bits()); + a = FrRepr::from(1); + for i in 1..257 { + assert_eq!(i, a.num_bits()); + a.mul2(); + } + assert_eq!(0, a.num_bits()); +} + +#[test] +fn test_fr_repr_sub_noborrow() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut t = FrRepr([0x8e62a7e85264e2c3, 0xb23d34c1941d3ca, 0x5976930b7502dd15, 0x600f3fb517bf5495]); + t.sub_noborrow(&FrRepr([0xd64f669809cbc6a4, 0xfa76cb9d90cf7637, 0xfefb0df9038d43b3, 0x298a30c744b31acf])); + assert!(t == FrRepr([0xb813415048991c1f, 0x10ad07ae88725d92, 0x5a7b851271759961, 0x36850eedd30c39c5])); + + for _ in 0..1000 { + let mut a = FrRepr::rand(&mut rng); + a.0[3] >>= 30; + let mut b = a; + for _ in 0..10 { + b.mul2(); + } + let mut c = b; + for _ in 0..10 { + c.mul2(); + } + + assert!(a < b); + assert!(b < c); + + let mut csub_ba = c; + csub_ba.sub_noborrow(&b); + csub_ba.sub_noborrow(&a); + + let mut csub_ab = c; + csub_ab.sub_noborrow(&a); + csub_ab.sub_noborrow(&b); + + assert_eq!(csub_ab, csub_ba); + } + + // Subtracting r+1 from r should produce a borrow + let mut qplusone = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(qplusone.sub_noborrow(&FrRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))); + + // Subtracting x from x should produce no borrow + let mut x = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(!x.sub_noborrow(&FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))) +} + +#[test] +fn test_fr_repr_add_nocarry() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut t = FrRepr([0xd64f669809cbc6a4, 0xfa76cb9d90cf7637, 0xfefb0df9038d43b3, 0x298a30c744b31acf]); + t.add_nocarry(&FrRepr([0x8e62a7e85264e2c3, 0xb23d34c1941d3ca, 0x5976930b7502dd15, 0x600f3fb517bf5495])); + assert_eq!(t, FrRepr([0x64b20e805c30a967, 0x59a9ee9aa114a02, 0x5871a104789020c9, 0x8999707c5c726f65])); + + // Test for the associativity of addition. + for _ in 0..1000 { + let mut a = FrRepr::rand(&mut rng); + let mut b = FrRepr::rand(&mut rng); + let mut c = FrRepr::rand(&mut rng); + + // Unset the first few bits, so that overflow won't occur. + a.0[3] >>= 3; + b.0[3] >>= 3; + c.0[3] >>= 3; + + let mut abc = a; + abc.add_nocarry(&b); + abc.add_nocarry(&c); + + let mut acb = a; + acb.add_nocarry(&c); + acb.add_nocarry(&b); + + let mut bac = b; + bac.add_nocarry(&a); + bac.add_nocarry(&c); + + let mut bca = b; + bca.add_nocarry(&c); + bca.add_nocarry(&a); + + let mut cab = c; + cab.add_nocarry(&a); + cab.add_nocarry(&b); + + let mut cba = c; + cba.add_nocarry(&b); + cba.add_nocarry(&a); + + assert_eq!(abc, acb); + assert_eq!(abc, bac); + assert_eq!(abc, bca); + assert_eq!(abc, cab); + assert_eq!(abc, cba); + } + + // Adding 1 to (2^256 - 1) should produce a carry + let mut x = FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); + assert!(x.add_nocarry(&FrRepr::from(1))); + + // Adding 1 to r should not produce a carry + let mut x = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(!x.add_nocarry(&FrRepr::from(1))); +} + +#[bench] +fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES).map(|_| { + let mut tmp1 = FrRepr::rand(&mut rng); + let mut tmp2 = FrRepr::rand(&mut rng); + // Shave a few bits off to avoid overflow. + for _ in 0..3 { + tmp1.div2(); + tmp2.div2(); + } + (tmp1, tmp2) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_nocarry(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_sub_noborrow(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES).map(|_| { + let tmp1 = FrRepr::rand(&mut rng); + let mut tmp2 = tmp1; + // Ensure tmp2 is smaller than tmp1. + for _ in 0..10 { + tmp2.div2(); + } + (tmp1, tmp2) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_noborrow(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_num_bits(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].num_bits(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_mul2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.mul2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_div2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.div2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn test_fr_is_valid() { + let mut a = Fr(MODULUS); + assert!(!a.is_valid()); + a.0.sub_noborrow(&FrRepr::from(1)); + assert!(a.is_valid()); + assert!(Fr(FrRepr::from(0)).is_valid()); + assert!(Fr(FrRepr([0xffffffff00000000, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48])).is_valid()); + assert!(!Fr(FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])).is_valid()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = Fr::rand(&mut rng); + assert!(a.is_valid()); + } +} + +#[test] +fn test_fr_add_assign() { + { + // Random number + let mut tmp = Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca])); + assert!(tmp.is_valid()); + // Test that adding zero has no effect. + tmp.add_assign(&Fr(FrRepr::from(0))); + assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); + // Add one and test for the result. + tmp.add_assign(&Fr(FrRepr::from(1))); + assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580766, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); + // Add another random number that exercises the reduction. + tmp.add_assign(&Fr(FrRepr([0x946f435944f7dc79, 0xb55e7ee6533a9b9b, 0x1e43b84c2f6194ca, 0x58717ab525463496]))); + assert_eq!(tmp, Fr(FrRepr([0xd7ec2abbb24fe3de, 0x35cdf7ae7d0d62f7, 0xd899557c477cd0e9, 0x3371b52bc43de018]))); + // Add one to (r - 1) and test for the result. + tmp = Fr(FrRepr([0xffffffff00000000, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48])); + tmp.add_assign(&Fr(FrRepr::from(1))); + assert!(tmp.0.is_zero()); + // Add a random number to another one such that the result is r - 1 + tmp = Fr(FrRepr([0xade5adacdccb6190, 0xaa21ee0f27db3ccd, 0x2550f4704ae39086, 0x591d1902e7c5ba27])); + tmp.add_assign(&Fr(FrRepr([0x521a525223349e70, 0xa99bb5f3d8231f31, 0xde8e397bebe477e, 0x1ad08e5041d7c321]))); + assert_eq!(tmp, Fr(FrRepr([0xffffffff00000000, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))); + // Add one to the result and test for it. + tmp.add_assign(&Fr(FrRepr::from(1))); + assert!(tmp.0.is_zero()); + } + + // Test associativity + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Generate a, b, c and ensure (a + b) + c == a + (b + c). + let a = Fr::rand(&mut rng); + let b = Fr::rand(&mut rng); + let c = Fr::rand(&mut rng); + + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.add_assign(&c); + + let mut tmp2 = b; + tmp2.add_assign(&c); + tmp2.add_assign(&a); + + assert!(tmp1.is_valid()); + assert!(tmp2.is_valid()); + assert_eq!(tmp1, tmp2); + } +} + +#[test] +fn test_fr_sub_assign() { + { + // Test arbitrary subtraction that tests reduction. + let mut tmp = Fr(FrRepr([0x6a68c64b6f735a2b, 0xd5f4d143fe0a1972, 0x37c17f3829267c62, 0xa2f37391f30915c])); + tmp.sub_assign(&Fr(FrRepr([0xade5adacdccb6190, 0xaa21ee0f27db3ccd, 0x2550f4704ae39086, 0x591d1902e7c5ba27]))); + assert_eq!(tmp, Fr(FrRepr([0xbc83189d92a7f89c, 0x7f908737d62d38a3, 0x45aa62cfe7e4c3e1, 0x24ffc5896108547d]))); + + // Test the opposite subtraction which doesn't test reduction. + tmp = Fr(FrRepr([0xade5adacdccb6190, 0xaa21ee0f27db3ccd, 0x2550f4704ae39086, 0x591d1902e7c5ba27])); + tmp.sub_assign(&Fr(FrRepr([0x6a68c64b6f735a2b, 0xd5f4d143fe0a1972, 0x37c17f3829267c62, 0xa2f37391f30915c]))); + assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); + + // Test for sensible results with zero + tmp = Fr(FrRepr::from(0)); + tmp.sub_assign(&Fr(FrRepr::from(0))); + assert!(tmp.is_zero()); + + tmp = Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca])); + tmp.sub_assign(&Fr(FrRepr::from(0))); + assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); + } + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure that (a - b) + (b - a) = 0. + let a = Fr::rand(&mut rng); + let b = Fr::rand(&mut rng); + + let mut tmp1 = a; + tmp1.sub_assign(&b); + + let mut tmp2 = b; + tmp2.sub_assign(&a); + + tmp1.add_assign(&tmp2); + assert!(tmp1.is_zero()); + } +} + +#[bench] +fn bench_fr_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn test_fr_mul_assign() { + let mut tmp = Fr(FrRepr([0x6b7e9b8faeefc81a, 0xe30a8463f348ba42, 0xeff3cb67a8279c9c, 0x3d303651bd7c774d])); + tmp.mul_assign(&Fr(FrRepr([0x13ae28e3bc35ebeb, 0xa10f4488075cae2c, 0x8160e95a853c3b5d, 0x5ae3f03b561a841d]))); + assert!(tmp == Fr(FrRepr([0x23717213ce710f71, 0xdbee1fe53a16e1af, 0xf565d3e1c2a48000, 0x4426507ee75df9d7]))); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000000 { + // Ensure that (a * b) * c = a * (b * c) + let a = Fr::rand(&mut rng); + let b = Fr::rand(&mut rng); + let c = Fr::rand(&mut rng); + + let mut tmp1 = a; + tmp1.mul_assign(&b); + tmp1.mul_assign(&c); + + let mut tmp2 = b; + tmp2.mul_assign(&c); + tmp2.mul_assign(&a); + + assert_eq!(tmp1, tmp2); + } + + for _ in 0..1000000 { + // Ensure that r * (a + b + c) = r*a + r*b + r*c + + let r = Fr::rand(&mut rng); + let mut a = Fr::rand(&mut rng); + let mut b = Fr::rand(&mut rng); + let mut c = Fr::rand(&mut rng); + + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.add_assign(&c); + tmp1.mul_assign(&r); + + a.mul_assign(&r); + b.mul_assign(&r); + c.mul_assign(&r); + + a.add_assign(&b); + a.add_assign(&c); + + assert_eq!(tmp1, a); + } +} + +#[bench] +fn bench_fr_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[test] +fn test_fr_squaring() { + let mut a = Fr(FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x73eda753299d7d47])); + assert!(a.is_valid()); + a.square(); + assert_eq!(a, Fr::from_repr(FrRepr([0xc0d698e7bde077b8, 0xb79a310579e76ec2, 0xac1da8d0a9af4e5f, 0x13f629c49bf23e97])).unwrap()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000000 { + // Ensure that (a * a) = a^2 + let a = Fr::rand(&mut rng); + + let mut tmp = a; + tmp.square(); + + let mut tmp2 = a; + tmp2.mul_assign(&a); + + assert_eq!(tmp, tmp2); + } +} + +#[bench] +fn bench_fr_square(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + + +#[test] +fn test_fr_inverse() { + assert!(Fr::zero().inverse().is_none()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let one = Fr::one(); + + for _ in 0..1000 { + // Ensure that a * a^-1 = 1 + let mut a = Fr::rand(&mut rng); + let ainv = a.inverse().unwrap(); + a.mul_assign(&ainv); + assert_eq!(a, one); + } +} + +#[bench] +fn bench_fr_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].inverse() + }); +} + +#[test] +fn test_fr_double() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure doubling a is equivalent to adding a to itself. + let mut a = Fr::rand(&mut rng); + let mut b = a; + b.add_assign(&a); + a.double(); + assert_eq!(a, b); + } +} + +#[test] +fn test_fr_negate() { + { + let mut a = Fr::zero(); + a.negate(); + + assert!(a.is_zero()); + } + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure (a - (-a)) = 0. + let mut a = Fr::rand(&mut rng); + let mut b = a; + b.negate(); + a.add_assign(&b); + + assert!(a.is_zero()); + } +} + +#[bench] +fn bench_fr_negate(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.negate(); + count = (count + 1) % SAMPLES; + tmp + }); +} + + +#[test] +fn test_fr_pow() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for i in 0..1000 { + // Exponentiate by various small numbers and ensure it consists with repeated + // multiplication. + let a = Fr::rand(&mut rng); + let target = a.pow(&[i]); + let mut c = Fr::one(); + for _ in 0..i { + c.mul_assign(&a); + } + assert_eq!(c, target); + } + + for _ in 0..1000 { + // Exponentiating by the modulus should have no effect in a prime field. + let a = Fr::rand(&mut rng); + + assert_eq!(a, a.pow(Fr::char())); + } +} + +#[test] +fn test_fr_sqrt() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + assert_eq!(Fr::zero().sqrt().unwrap(), Fr::zero()); + + for _ in 0..1000 { + // Ensure sqrt(a^2) = a or -a + let a = Fr::rand(&mut rng); + let mut nega = a; + nega.negate(); + let mut b = a; + b.square(); + + let b = b.sqrt().unwrap(); + + assert!(a == b || nega == b); + } + + for _ in 0..1000 { + // Ensure sqrt(a)^2 = a for random a + let a = Fr::rand(&mut rng); + + if let Some(mut tmp) = a.sqrt() { + tmp.square(); + + assert_eq!(a, tmp); + } + } +} + +#[bench] +fn bench_fr_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + let mut tmp = Fr::rand(&mut rng); + tmp.square(); + tmp + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].sqrt() + }); +} + +#[test] +fn test_fr_from_into_repr() { + // r + 1 should not be in the field + assert!(Fr::from_repr(FrRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48])).is_err()); + + // r should not be in the field + assert!(Fr::from_repr(Fr::char()).is_err()); + + // Multiply some arbitrary representations to see if the result is as expected. + let a = FrRepr([0x25ebe3a3ad3c0c6a, 0x6990e39d092e817c, 0x941f900d42f5658e, 0x44f8a103b38a71e0]); + let mut a_fr = Fr::from_repr(a).unwrap(); + let b = FrRepr([0x264e9454885e2475, 0x46f7746bb0308370, 0x4683ef5347411f9, 0x58838d7f208d4492]); + let b_fr = Fr::from_repr(b).unwrap(); + let c = FrRepr([0x48a09ab93cfc740d, 0x3a6600fbfc7a671, 0x838567017501d767, 0x7161d6da77745512]); + a_fr.mul_assign(&b_fr); + assert_eq!(a_fr.into_repr(), c); + + // Zero should be in the field. + assert!(Fr::from_repr(FrRepr::from(0)).unwrap().is_zero()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Try to turn Fr elements into representations and back again, and compare. + let a = Fr::rand(&mut rng); + let a_repr = a.into_repr(); + let b_repr = FrRepr::from(a); + assert_eq!(a_repr, b_repr); + let a_again = Fr::from_repr(a_repr).unwrap(); + + assert_eq!(a, a_again); + } +} + +#[bench] +fn bench_fr_into_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fr::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].into_repr() + }); +} + +#[bench] +fn bench_fr_from_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| { + Fr::rand(&mut rng).into_repr() + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + Fr::from_repr(v[count]) + }); +} + +#[test] +fn test_fr_repr_debug() { + assert_eq!( + format!("{:?}", FrRepr([0x2829c242fa826143, 0x1f32cf4dd4330917, 0x932e4e479d168cd9, 0x513c77587f563f64])), + "0x513c77587f563f64932e4e479d168cd91f32cf4dd43309172829c242fa826143".to_string() + ); + assert_eq!( + format!("{:?}", FrRepr([0x25ebe3a3ad3c0c6a, 0x6990e39d092e817c, 0x941f900d42f5658e, 0x44f8a103b38a71e0])), + "0x44f8a103b38a71e0941f900d42f5658e6990e39d092e817c25ebe3a3ad3c0c6a".to_string() + ); + assert_eq!( + format!("{:?}", FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string() + ); + assert_eq!( + format!("{:?}", FrRepr([0, 0, 0, 0])), + "0x0000000000000000000000000000000000000000000000000000000000000000".to_string() + ); +} + +#[test] +fn test_fr_debug() { + assert_eq!( + format!("{:?}", Fr::from_repr(FrRepr([0xc3cae746a3b5ecc7, 0x185ec8eb3f5b5aee, 0x684499ffe4b9dd99, 0x7c9bba7afb68faa])).unwrap()), + "Fr(0x07c9bba7afb68faa684499ffe4b9dd99185ec8eb3f5b5aeec3cae746a3b5ecc7)".to_string() + ); + assert_eq!( + format!("{:?}", Fr::from_repr(FrRepr([0x44c71298ff198106, 0xb0ad10817df79b6a, 0xd034a80a2b74132b, 0x41cf9a1336f50719])).unwrap()), + "Fr(0x41cf9a1336f50719d034a80a2b74132bb0ad10817df79b6a44c71298ff198106)".to_string() + ); +} + +#[test] +fn test_fr_num_bits() { + assert_eq!(Fr::num_bits(), 255); + assert_eq!(Fr::capacity(), 254); +} + +#[test] +fn test_fr_root_of_unity() { + assert_eq!(Fr::s(), 32); + assert_eq!(Fr::multiplicative_generator(), Fr::from_repr(FrRepr::from(7)).unwrap()); + assert_eq!( + Fr::multiplicative_generator().pow([0xfffe5bfeffffffff, 0x9a1d80553bda402, 0x299d7d483339d808, 0x73eda753]), + Fr::root_of_unity() + ); + assert_eq!( + Fr::root_of_unity().pow([1 << Fr::s()]), + Fr::one() + ); + assert!(Fr::multiplicative_generator().sqrt().is_none()); +} + +#[test] +fn fr_field_tests() { + ::tests::field::random_field_tests::(); + ::tests::field::random_sqrt_tests::(); + ::tests::field::random_frobenius_tests::(Fr::char(), 13); +} diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs new file mode 100644 index 0000000..43290fd --- /dev/null +++ b/src/bls12_381/mod.rs @@ -0,0 +1,459 @@ +mod fq; +mod fr; +mod fq2; +mod fq6; +mod fq12; +mod ec; + +pub use self::fr::{Fr, FrRepr}; +pub use self::fq::{Fq, FqRepr}; +pub use self::fq2::Fq2; +pub use self::fq12::Fq12; +pub use self::ec::{G1, G2, G1Affine, G2Affine, G1Prepared, G2Prepared}; + +use super::{Engine, CurveAffine, Field, BitIterator}; + +// The BLS parameter x for BLS12-381 is -0xd201000000010000 +const BLS_X: u64 = 0xd201000000010000; +const BLS_X_IS_NEGATIVE: bool = true; + +pub struct Bls12; + +impl Engine for Bls12 { + type Fr = Fr; + type G1 = G1; + type G1Affine = G1Affine; + type G2 = G2; + type G2Affine = G2Affine; + type Fq = Fq; + type Fqe = Fq2; + type Fqk = Fq12; + + fn miller_loop<'a, I>(i: I) -> Self::Fqk + where I: IntoIterator::Prepared, + &'a ::Prepared + )> + { + let mut pairs = vec![]; + for &(p, q) in i { + if !p.is_zero() && !q.is_zero() { + pairs.push((p, q.coeffs.iter())); + } + } + + // Twisting isomorphism from E to E' + fn ell( + f: &mut Fq12, + coeffs: &(Fq2, Fq2, Fq2), + p: &G1Affine + ) + { + let mut c0 = coeffs.0; + let mut c1 = coeffs.1; + + c0.c0.mul_assign(&p.y); + c0.c1.mul_assign(&p.y); + + c1.c0.mul_assign(&p.x); + c1.c1.mul_assign(&p.x); + + // Sparse multiplication in Fq12 + f.mul_by_014(&coeffs.2, &c1, &c0); + } + + let mut f = Fq12::one(); + + let mut found_one = false; + for i in BitIterator::new(&[BLS_X >> 1]) { + if !found_one { + found_one = i; + continue; + } + + for &mut (p, ref mut coeffs) in &mut pairs { + ell(&mut f, coeffs.next().unwrap(), &p.0); + } + + if i { + for &mut (p, ref mut coeffs) in &mut pairs { + ell(&mut f, coeffs.next().unwrap(), &p.0); + } + } + + f.square(); + } + + for &mut (p, ref mut coeffs) in &mut pairs { + ell(&mut f, coeffs.next().unwrap(), &p.0); + } + + f + } + + fn final_exponentiation(r: &Fq12) -> Option { + let mut f1 = *r; + f1.unitary_inverse(); + + match r.inverse() { + Some(mut f2) => { + let mut r = f1; + r.mul_assign(&f2); + f2 = r; + r.frobenius_map(2); + r.mul_assign(&f2); + + fn exp_by_x(f: &mut Fq12, x: u64) + { + *f = f.pow(&[x]); + if BLS_X_IS_NEGATIVE { + f.unitary_inverse(); + } + } + + let mut x = BLS_X; + let mut y0 = r; + y0.square(); + let mut y1 = y0; + exp_by_x(&mut y1, x); + x >>= 1; + let mut y2 = y1; + exp_by_x(&mut y2, x); + x <<= 1; + let mut y3 = r; + y3.unitary_inverse(); + y1.mul_assign(&y3); + y1.unitary_inverse(); + y1.mul_assign(&y2); + y2 = y1; + exp_by_x(&mut y2, x); + y3 = y2; + exp_by_x(&mut y3, x); + y1.unitary_inverse(); + y3.mul_assign(&y1); + y1.unitary_inverse(); + y1.frobenius_map(3); + y2.frobenius_map(2); + y1.mul_assign(&y2); + y2 = y3; + exp_by_x(&mut y2, x); + y2.mul_assign(&y0); + y2.mul_assign(&r); + y1.mul_assign(&y2); + y2 = y3; + y2.frobenius_map(1); + y1.mul_assign(&y2); + Some(y1) + }, + None => None + } + } +} + +impl G2Prepared { + pub fn is_zero(&self) -> bool { + self.infinity + } + + pub fn from_affine(q: G2Affine) -> Self { + if q.is_zero() { + return G2Prepared { + coeffs: vec![], + infinity: true + } + } + + fn doubling_step( + r: &mut G2 + ) -> (Fq2, Fq2, Fq2) + { + // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf + let mut tmp0 = r.x; + tmp0.square(); + + let mut tmp1 = r.y; + tmp1.square(); + + let mut tmp2 = tmp1; + tmp2.square(); + + let mut tmp3 = tmp1; + tmp3.add_assign(&r.x); + tmp3.square(); + tmp3.sub_assign(&tmp0); + tmp3.sub_assign(&tmp2); + tmp3.double(); + + let mut tmp4 = tmp0; + tmp4.double(); + tmp4.add_assign(&tmp0); + + let mut tmp6 = r.x; + tmp6.add_assign(&tmp4); + + let mut tmp5 = tmp4; + tmp5.square(); + + let mut zsquared = r.z; + zsquared.square(); + + r.x = tmp5; + r.x.sub_assign(&tmp3); + r.x.sub_assign(&tmp3); + + r.z.add_assign(&r.y); + r.z.square(); + r.z.sub_assign(&tmp1); + r.z.sub_assign(&zsquared); + + r.y = tmp3; + r.y.sub_assign(&r.x); + r.y.mul_assign(&tmp4); + + tmp2.double(); + tmp2.double(); + tmp2.double(); + + r.y.sub_assign(&tmp2); + + tmp3 = tmp4; + tmp3.mul_assign(&zsquared); + tmp3.double(); + tmp3.negate(); + + tmp6.square(); + tmp6.sub_assign(&tmp0); + tmp6.sub_assign(&tmp5); + + tmp1.double(); + tmp1.double(); + + tmp6.sub_assign(&tmp1); + + tmp0 = r.z; + tmp0.mul_assign(&zsquared); + tmp0.double(); + + (tmp0, tmp3, tmp6) + } + + fn addition_step( + r: &mut G2, + q: &G2Affine + ) -> (Fq2, Fq2, Fq2) + { + // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf + let mut zsquared = r.z; + zsquared.square(); + + let mut ysquared = q.y; + ysquared.square(); + + let mut t0 = zsquared; + t0.mul_assign(&q.x); + + let mut t1 = q.y; + t1.add_assign(&r.z); + t1.square(); + t1.sub_assign(&ysquared); + t1.sub_assign(&zsquared); + t1.mul_assign(&zsquared); + + let mut t2 = t0; + t2.sub_assign(&r.x); + + let mut t3 = t2; + t3.square(); + + let mut t4 = t3; + t4.double(); + t4.double(); + + let mut t5 = t4; + t5.mul_assign(&t2); + + let mut t6 = t1; + t6.sub_assign(&r.y); + t6.sub_assign(&r.y); + + let mut t9 = t6; + t9.mul_assign(&q.x); + + let mut t7 = t4; + t7.mul_assign(&r.x); + + r.x = t6; + r.x.square(); + r.x.sub_assign(&t5); + r.x.sub_assign(&t7); + r.x.sub_assign(&t7); + + r.z.add_assign(&t2); + r.z.square(); + r.z.sub_assign(&zsquared); + r.z.sub_assign(&t3); + + let mut t10 = q.y; + t10.add_assign(&r.z); + + let mut t8 = t7; + t8.sub_assign(&r.x); + t8.mul_assign(&t6); + + t0 = r.y; + t0.mul_assign(&t5); + t0.double(); + + r.y = t8; + r.y.sub_assign(&t0); + + t10.square(); + t10.sub_assign(&ysquared); + + let mut ztsquared = r.z; + ztsquared.square(); + + t10.sub_assign(&ztsquared); + + t9.double(); + t9.sub_assign(&t10); + + t10 = r.z; + t10.double(); + + t6.negate(); + + t1 = t6; + t1.double(); + + (t10, t1, t9) + } + + let mut coeffs = vec![]; + let mut r: G2 = q.into(); + + let mut found_one = false; + for i in BitIterator::new([BLS_X >> 1]) { + if !found_one { + found_one = i; + continue; + } + + coeffs.push(doubling_step(&mut r)); + + if i { + coeffs.push(addition_step(&mut r, &q)); + } + } + + coeffs.push(doubling_step(&mut r)); + + G2Prepared { + coeffs: coeffs, + infinity: false + } + } +} + +#[test] +fn bls12_engine_tests() { + ::tests::engine::engine_tests::(); +} + +#[cfg(test)] +use rand::{Rand, SeedableRng, XorShiftRng}; + +#[bench] +fn bench_pairing_g1_preparation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| G1::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = G1Affine::from(v[count]).prepare(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_g2_preparation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| G2::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = G2Affine::from(v[count]).prepare(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_miller_loop(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1Prepared, G2Prepared)> = (0..SAMPLES).map(|_| + ( + G1Affine::from(G1::rand(&mut rng)).prepare(), + G2Affine::from(G2::rand(&mut rng)).prepare() + ) + ).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bls12::miller_loop(&[(&v[count].0, &v[count].1)]); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_final_exponentiation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec = (0..SAMPLES).map(|_| + ( + G1Affine::from(G1::rand(&mut rng)).prepare(), + G2Affine::from(G2::rand(&mut rng)).prepare() + ) + ).map(|(ref p, ref q)| Bls12::miller_loop(&[(p, q)])).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bls12::final_exponentiation(&v[count]); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_full(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G2)> = (0..SAMPLES).map(|_| + ( + G1::rand(&mut rng), + G2::rand(&mut rng) + ) + ).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bls12::pairing(v[count].0, v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ebdfa73 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,409 @@ +#![feature(i128_type)] + +#![cfg_attr(test, feature(test))] +#[cfg(test)] +extern crate test; + +extern crate rand; + +#[cfg(test)] +pub mod tests; + +pub mod bls12_381; + +use std::fmt; + +/// An "engine" is a collection of types (fields, elliptic curve groups, etc.) +/// with well-defined relationships. In particular, the G1/G2 curve groups are +/// of prime order `r`, and are equipped with a bilinear pairing function. +pub trait Engine { + /// This is the scalar field of the G1/G2 groups. + type Fr: PrimeField; + + /// The projective representation of an element in G1. + type G1: CurveProjective + From; + + /// The affine representation of an element in G1. + type G1Affine: CurveAffine + From; + + /// The projective representation of an element in G2. + type G2: CurveProjective + From; + + /// The affine representation of an element in G2. + type G2Affine: CurveAffine + From; + + /// The base field that hosts G1. + type Fq: PrimeField + SqrtField; + + /// The extension field that hosts G2. + type Fqe: SqrtField; + + /// The extension field that hosts the target group of the pairing. + type Fqk: Field; + + /// Perform a miller loop with some number of (G1, G2) pairs. + fn miller_loop<'a, I>(i: I) -> Self::Fqk + where I: IntoIterator::Prepared, + &'a ::Prepared + )>; + + /// Perform final exponentiation of the result of a miller loop. + fn final_exponentiation(&Self::Fqk) -> Option; + + /// Performs a complete pairing operation `(p, q)`. + fn pairing(p: G1, q: G2) -> Self::Fqk + where G1: Into, + G2: Into + { + Self::final_exponentiation(&Self::miller_loop( + [( + &(p.into().prepare()), + &(q.into().prepare()) + )].into_iter() + )).unwrap() + } +} + +/// Projective representation of an elliptic curve point guaranteed to be +/// in the correct prime order subgroup. +pub trait CurveProjective: PartialEq + + Eq + + Sized + + Copy + + Clone + + Send + + Sync + + fmt::Debug + + rand::Rand + + 'static +{ + type Scalar: PrimeField; + type Base: SqrtField; + type Affine: CurveAffine; + + /// Returns the additive identity. + fn zero() -> Self; + + /// Returns a fixed generator of unknown exponent. + fn one() -> Self; + + /// Determines if this point is the point at infinity. + fn is_zero(&self) -> bool; + + /// Normalizes a slice of projective elements so that + /// conversion to affine is cheap. + fn batch_normalization(v: &mut [Self]); + + /// Checks if the point is already "normalized" so that + /// cheap affine conversion is possible. + fn is_normalized(&self) -> bool; + + /// Doubles this element. + fn double(&mut self); + + /// Adds another element to this element. + fn add_assign(&mut self, other: &Self); + + /// Subtracts another element from this element. + fn sub_assign(&mut self, other: &Self) { + let mut tmp = *other; + tmp.negate(); + self.add_assign(&tmp); + } + + /// Adds an affine element to this element. + fn add_assign_mixed(&mut self, other: &Self::Affine); + + /// Negates this element. + fn negate(&mut self); + + /// Performs scalar multiplication of this element. + fn mul_assign::Repr>>(&mut self, other: S); + + /// Converts this element into its affine representation. + fn to_affine(&self) -> Self::Affine; +} + +/// Affine representation of an elliptic curve point guaranteed to be +/// in the correct prime order subgroup. +pub trait CurveAffine: Copy + + Clone + + Sized + + Send + + Sync + + fmt::Debug + + PartialEq + + Eq + + 'static +{ + type Scalar: PrimeField; + type Base: SqrtField; + type Projective: CurveProjective; + type Prepared: Clone + Send + Sync + 'static; + + /// Returns the additive identity. + fn zero() -> Self; + + /// Returns a fixed generator of unknown exponent. + fn one() -> Self; + + /// Determines if this point represents the point at infinity; the + /// additive identity. + fn is_zero(&self) -> bool; + + /// Determines if this point is on the curve and in the correct subgroup. + fn is_valid(&self) -> bool; + + /// Negates this element. + fn negate(&mut self); + + /// Performs scalar multiplication of this element with mixed addition. + fn mul::Repr>>(&self, other: S) -> Self::Projective; + + /// Prepares this element for pairing purposes. + fn prepare(&self) -> Self::Prepared; + + /// Converts this element into its affine representation. + fn to_projective(&self) -> Self::Projective; +} + +/// This trait represents an element of a field. +pub trait Field: Sized + + Eq + + Copy + + Clone + + Send + + Sync + + fmt::Debug + + 'static + + rand::Rand +{ + /// Returns the zero element of the field, the additive identity. + fn zero() -> Self; + + /// Returns the one element of the field, the multiplicative identity. + fn one() -> Self; + + /// Returns true iff this element is zero. + fn is_zero(&self) -> bool; + + /// Squares this element. + fn square(&mut self); + + /// Doubles this element. + fn double(&mut self); + + /// Negates this element. + fn negate(&mut self); + + /// Adds another element to this element. + fn add_assign(&mut self, other: &Self); + + /// Subtracts another element from this element. + fn sub_assign(&mut self, other: &Self); + + /// Multiplies another element by this element. + fn mul_assign(&mut self, other: &Self); + + /// Computes the multiplicative inverse of this element, if nonzero. + fn inverse(&self) -> Option; + + /// Exponentiates this element by a power of the base prime modulus via + /// the Frobenius automorphism. + fn frobenius_map(&mut self, power: usize); + + /// Exponentiates this element by a number represented with `u64` limbs, + /// least significant digit first. + fn pow>(&self, exp: S) -> Self + { + let mut res = Self::one(); + + for i in BitIterator::new(exp) { + res.square(); + if i { + res.mul_assign(self); + } + } + + res + } +} + +/// This trait represents an element of a field that has a square root operation described for it. +pub trait SqrtField: Field +{ + /// Returns the square root of the field element, if it is + /// quadratic residue. + fn sqrt(&self) -> Option; +} + +/// This trait represents a wrapper around a biginteger which can encode any element of a particular +/// prime field. It is a smart wrapper around a sequence of `u64` limbs, least-significant digit +/// first. +pub trait PrimeFieldRepr: Sized + + Copy + + Clone + + Eq + + Ord + + Send + + Sync + + fmt::Debug + + 'static + + rand::Rand + + AsRef<[u64]> + + From +{ + /// Subtract another reprensetation from this one, returning the borrow bit. + fn sub_noborrow(&mut self, other: &Self) -> bool; + + /// Add another representation to this one, returning the carry bit. + fn add_nocarry(&mut self, other: &Self) -> bool; + + /// Compute the number of bits needed to encode this number. + fn num_bits(&self) -> u32; + + /// Returns true iff this number is zero. + fn is_zero(&self) -> bool; + + /// Returns true iff this number is odd. + fn is_odd(&self) -> bool; + + /// Returns true iff this number is even. + fn is_even(&self) -> bool; + + /// Performs a rightwise bitshift of this number, effectively dividing + /// it by 2. + fn div2(&mut self); + + /// Performs a rightwise bitshift of this number by some amount. + fn divn(&mut self, amt: usize); + + /// Performs a leftwise bitshift of this number, effectively multiplying + /// it by 2. Overflow is ignored. + fn mul2(&mut self); +} + +/// This represents an element of a prime field. +pub trait PrimeField: Field +{ + /// The prime field can be converted back and forth into this biginteger + /// representation. + type Repr: PrimeFieldRepr + From; + + /// Convert this prime field element into a biginteger representation. + fn from_repr(Self::Repr) -> Result; + + /// Convert a biginteger reprensentation into a prime field element, if + /// the number is an element of the field. + fn into_repr(&self) -> Self::Repr; + + /// Returns the field characteristic; the modulus. + fn char() -> Self::Repr; + + /// Returns how many bits are needed to represent an element of this + /// field. + fn num_bits() -> u32; + + /// Returns how many bits of information can be reliably stored in the + /// field element. + fn capacity() -> u32; + + /// Returns the multiplicative generator of `char()` - 1 order. This element + /// must also be quadratic nonresidue. + fn multiplicative_generator() -> Self; + + /// Returns s such that 2^s * t = `char()` - 1 with t odd. + fn s() -> usize; + + /// Returns the 2^s root of unity computed by exponentiating the `multiplicative_generator()` + /// by t. + fn root_of_unity() -> Self; +} + +pub struct BitIterator { + t: E, + n: usize +} + +impl> BitIterator { + pub fn new(t: E) -> Self { + let n = t.as_ref().len() * 64; + + BitIterator { + t: t, + n: n + } + } +} + +impl> Iterator for BitIterator { + type Item = bool; + + fn next(&mut self) -> Option { + if self.n == 0 { + None + } else { + self.n -= 1; + let part = self.n / 64; + let bit = self.n - (64 * part); + + Some(self.t.as_ref()[part] & (1 << bit) > 0) + } + } +} + +#[test] +fn test_bit_iterator() { + let mut a = BitIterator::new([0xa953d79b83f6ab59, 0x6dea2059e200bd39]); + let expected = "01101101111010100010000001011001111000100000000010111101001110011010100101010011110101111001101110000011111101101010101101011001"; + + for e in expected.chars() { + assert!(a.next().unwrap() == (e == '1')); + } + + assert!(a.next().is_none()); + + let expected = "1010010101111110101010000101101011101000011101110101001000011001100100100011011010001011011011010001011011101100110100111011010010110001000011110100110001100110011101101000101100011100100100100100001010011101010111110011101011000011101000111011011101011001"; + + let mut a = BitIterator::new([0x429d5f3ac3a3b759, 0xb10f4c66768b1c92, 0x92368b6d16ecd3b4, 0xa57ea85ae8775219]); + + for e in expected.chars() { + assert!(a.next().unwrap() == (e == '1')); + } + + assert!(a.next().is_none()); +} + +/// Calculate a - b - borrow, returning the result and modifying +/// the borrow value. +#[inline(always)] +pub(crate) fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { + let tmp = (1u128 << 64) + (a as u128) - (b as u128) - (*borrow as u128); + + *borrow = if tmp >> 64 == 0 { 1 } else { 0 }; + + tmp as u64 +} + +/// Calculate a + b + carry, returning the sum and modifying the +/// carry value. +#[inline(always)] +pub(crate) fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { + let tmp = (a as u128) + (b as u128) + (*carry as u128); + + *carry = (tmp >> 64) as u64; + + tmp as u64 +} + +/// Calculate a + (b * c) + carry, returning the least significant digit +/// and setting carry to the most significant digit. +#[inline(always)] +pub(crate) fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { + let tmp = (a as u128) + (b as u128) * (c as u128) + (*carry as u128); + + *carry = (tmp >> 64) as u64; + + tmp as u64 +} diff --git a/src/tests/curve.rs b/src/tests/curve.rs new file mode 100644 index 0000000..9e7c8dd --- /dev/null +++ b/src/tests/curve.rs @@ -0,0 +1,267 @@ +use rand::{SeedableRng, XorShiftRng, Rand}; + +use ::{CurveProjective, CurveAffine, Field}; + +pub fn curve_tests() +{ + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + // Negation edge case with zero. + { + let mut z = G::zero(); + z.negate(); + assert!(z.is_zero()); + } + + // Doubling edge case with zero. + { + let mut z = G::zero(); + z.double(); + assert!(z.is_zero()); + } + + // Addition edge cases with zero + { + let mut r = G::rand(&mut rng); + let rcopy = r; + r.add_assign(&G::zero()); + assert_eq!(r, rcopy); + r.add_assign_mixed(&G::Affine::zero()); + assert_eq!(r, rcopy); + + let mut z = G::zero(); + z.add_assign(&G::zero()); + assert!(z.is_zero()); + z.add_assign_mixed(&G::Affine::zero()); + assert!(z.is_zero()); + + let mut z2 = z; + z2.add_assign(&r); + + z.add_assign_mixed(&r.to_affine()); + + assert_eq!(z, z2); + assert_eq!(z, r); + } + + // Transformations + { + let a = G::rand(&mut rng); + let b = a.to_affine().to_projective(); + let c = a.to_affine().to_projective().to_affine().to_projective(); + assert_eq!(a, b); + assert_eq!(b, c); + } + + random_addition_tests::(); + random_multiplication_tests::(); + random_doubling_tests::(); + random_negation_tests::(); + random_transformation_tests::(); +} + +fn random_negation_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let r = G::rand(&mut rng); + + let s = G::Scalar::rand(&mut rng); + let mut sneg = s; + sneg.negate(); + + let mut t1 = r; + t1.mul_assign(s); + + let mut t2 = r; + t2.mul_assign(sneg); + + let mut t3 = t1; + t3.add_assign(&t2); + assert!(t3.is_zero()); + + let mut t4 = t1; + t4.add_assign_mixed(&t2.to_affine()); + assert!(t4.is_zero()); + + t1.negate(); + assert_eq!(t1, t2); + } +} + +fn random_doubling_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut a = G::rand(&mut rng); + let mut b = G::rand(&mut rng); + + // 2(a + b) + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.double(); + + // 2a + 2b + a.double(); + b.double(); + + let mut tmp2 = a; + tmp2.add_assign(&b); + + let mut tmp3 = a; + tmp3.add_assign_mixed(&b.to_affine()); + + assert_eq!(tmp1, tmp2); + assert_eq!(tmp1, tmp3); + } +} + +fn random_multiplication_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut a = G::rand(&mut rng); + let mut b = G::rand(&mut rng); + let a_affine = a.to_affine(); + let b_affine = b.to_affine(); + + let s = G::Scalar::rand(&mut rng); + + // s ( a + b ) + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.mul_assign(s); + + // sa + sb + a.mul_assign(s); + b.mul_assign(s); + + let mut tmp2 = a; + tmp2.add_assign(&b); + + // Affine multiplication + let mut tmp3 = a_affine.mul(s); + tmp3.add_assign(&b_affine.mul(s)); + + assert_eq!(tmp1, tmp2); + assert_eq!(tmp1, tmp3); + } +} + +fn random_addition_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = G::rand(&mut rng); + let b = G::rand(&mut rng); + let c = G::rand(&mut rng); + let a_affine = a.to_affine(); + let b_affine = b.to_affine(); + let c_affine = c.to_affine(); + + // a + a should equal the doubling + { + let mut aplusa = a; + aplusa.add_assign(&a); + + let mut aplusamixed = a; + aplusamixed.add_assign_mixed(&a.to_affine()); + + let mut adouble = a; + adouble.double(); + + assert_eq!(aplusa, adouble); + assert_eq!(aplusa, aplusamixed); + } + + let mut tmp = vec![G::zero(); 6]; + + // (a + b) + c + tmp[0] = a; + tmp[0].add_assign(&b); + tmp[0].add_assign(&c); + + // a + (b + c) + tmp[1] = b; + tmp[1].add_assign(&c); + tmp[1].add_assign(&a); + + // (a + c) + b + tmp[2] = a; + tmp[2].add_assign(&c); + tmp[2].add_assign(&b); + + // Mixed addition + + // (a + b) + c + tmp[3] = a_affine.to_projective(); + tmp[3].add_assign_mixed(&b_affine); + tmp[3].add_assign_mixed(&c_affine); + + // a + (b + c) + tmp[4] = b_affine.to_projective(); + tmp[4].add_assign_mixed(&c_affine); + tmp[4].add_assign_mixed(&a_affine); + + // (a + c) + b + tmp[5] = a_affine.to_projective(); + tmp[5].add_assign_mixed(&c_affine); + tmp[5].add_assign_mixed(&b_affine); + + // Comparisons + for i in 0..6 { + for j in 0..6 { + assert_eq!(tmp[i], tmp[j]); + assert_eq!(tmp[i].to_affine(), tmp[j].to_affine()); + } + + assert!(tmp[i] != a); + assert!(tmp[i] != b); + assert!(tmp[i] != c); + + assert!(a != tmp[i]); + assert!(b != tmp[i]); + assert!(c != tmp[i]); + } + } +} + +fn random_transformation_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let g = G::rand(&mut rng); + let g_affine = g.to_affine(); + let g_projective = g_affine.to_projective(); + assert_eq!(g, g_projective); + } + + // Batch normalization + for _ in 0..10 { + let mut v = (0..1000).map(|_| G::rand(&mut rng)).collect::>(); + + for i in &v { + assert!(!i.is_normalized()); + } + + use rand::distributions::{IndependentSample, Range}; + let between = Range::new(0, 1000); + // Sprinkle in some normalized points + for _ in 0..5 { + v[between.ind_sample(&mut rng)] = G::zero(); + } + for _ in 0..5 { + let s = between.ind_sample(&mut rng); + v[s] = v[s].to_affine().to_projective(); + } + + let expected_v = v.iter().map(|v| v.to_affine().to_projective()).collect::>(); + G::batch_normalization(&mut v); + + for i in &v { + assert!(i.is_normalized()); + } + + assert_eq!(v, expected_v); + } +} diff --git a/src/tests/engine.rs b/src/tests/engine.rs new file mode 100644 index 0000000..5e2c07b --- /dev/null +++ b/src/tests/engine.rs @@ -0,0 +1,120 @@ +use rand::{SeedableRng, XorShiftRng, Rand}; + +use ::{Engine, CurveProjective, CurveAffine, Field, PrimeField}; + +pub fn engine_tests() +{ + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let z1 = E::G1Affine::zero().prepare(); + let z2 = E::G2Affine::zero().prepare(); + + let a = E::G1::rand(&mut rng).to_affine().prepare(); + let b = E::G2::rand(&mut rng).to_affine().prepare(); + let c = E::G1::rand(&mut rng).to_affine().prepare(); + let d = E::G2::rand(&mut rng).to_affine().prepare(); + + assert_eq!( + E::Fqk::one(), + E::final_exponentiation(&E::miller_loop(&[(&z1, &b)])).unwrap() + ); + + assert_eq!( + E::Fqk::one(), + E::final_exponentiation(&E::miller_loop(&[(&a, &z2)])).unwrap() + ); + + assert_eq!( + E::final_exponentiation(&E::miller_loop(&[(&z1, &b), (&c, &d)])).unwrap(), + E::final_exponentiation(&E::miller_loop(&[(&a, &z2), (&c, &d)])).unwrap() + ); + + assert_eq!( + E::final_exponentiation(&E::miller_loop(&[(&a, &b), (&z1, &d)])).unwrap(), + E::final_exponentiation(&E::miller_loop(&[(&a, &b), (&c, &z2)])).unwrap() + ); + } + + random_bilinearity_tests::(); + random_miller_loop_tests::(); +} + +fn random_miller_loop_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + // Exercise the miller loop for a reduced pairing + for _ in 0..1000 { + let a = E::G1::rand(&mut rng); + let b = E::G2::rand(&mut rng); + + let p2 = E::pairing(a, b); + + let a = a.to_affine().prepare(); + let b = b.to_affine().prepare(); + + let p1 = E::final_exponentiation(&E::miller_loop(&[(&a, &b)])).unwrap(); + + assert_eq!(p1, p2); + } + + // Exercise a double miller loop + for _ in 0..1000 { + let a = E::G1::rand(&mut rng); + let b = E::G2::rand(&mut rng); + let c = E::G1::rand(&mut rng); + let d = E::G2::rand(&mut rng); + + let ab = E::pairing(a, b); + let cd = E::pairing(c, d); + + let mut abcd = ab; + abcd.mul_assign(&cd); + + let a = a.to_affine().prepare(); + let b = b.to_affine().prepare(); + let c = c.to_affine().prepare(); + let d = d.to_affine().prepare(); + + let abcd_with_double_loop = E::final_exponentiation( + &E::miller_loop(&[(&a, &b), (&c, &d)]) + ).unwrap(); + + assert_eq!(abcd, abcd_with_double_loop); + } +} + +fn random_bilinearity_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = E::G1::rand(&mut rng); + let b = E::G2::rand(&mut rng); + + let c = E::Fr::rand(&mut rng); + let d = E::Fr::rand(&mut rng); + + let mut ac = a; + ac.mul_assign(c); + + let mut ad = a; + ad.mul_assign(d); + + let mut bc = b; + bc.mul_assign(c); + + let mut bd = b; + bd.mul_assign(d); + + let acbd = E::pairing(ac, bd); + let adbc = E::pairing(ad, bc); + + let mut cd = c; + cd.mul_assign(&d); + + let abcd = E::pairing(a, b).pow(cd.into_repr()); + + assert_eq!(acbd, adbc); + assert_eq!(acbd, abcd); + } +} diff --git a/src/tests/field.rs b/src/tests/field.rs new file mode 100644 index 0000000..dbc9f8c --- /dev/null +++ b/src/tests/field.rs @@ -0,0 +1,229 @@ +use rand::{Rng, SeedableRng, XorShiftRng}; +use ::{SqrtField, Field}; + +pub fn random_frobenius_tests>(characteristic: C, maxpower: usize) { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + for i in 0..(maxpower+1) { + let mut a = F::rand(&mut rng); + let mut b = a; + + for _ in 0..i { + a = a.pow(&characteristic); + } + b.frobenius_map(i); + + assert_eq!(a, b); + } + } +} + +pub fn random_sqrt_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..10000 { + let a = F::rand(&mut rng); + let mut b = a; + b.square(); + + let b = b.sqrt().unwrap(); + let mut negb = b; + negb.negate(); + + assert!(a == b || a == negb); + } + + let mut c = F::one(); + for _ in 0..10000 { + let mut b = c; + b.square(); + b = b.sqrt().unwrap(); + + if b != c { + b.negate(); + } + + assert_eq!(b, c); + + c.add_assign(&F::one()); + } +} + +pub fn random_field_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + random_multiplication_tests::(&mut rng); + random_addition_tests::(&mut rng); + random_subtraction_tests::(&mut rng); + random_negation_tests::(&mut rng); + random_doubling_tests::(&mut rng); + random_squaring_tests::(&mut rng); + random_inversion_tests::(&mut rng); + random_expansion_tests::(&mut rng); + + assert!(F::zero().is_zero()); + { + let mut z = F::zero(); + z.negate(); + assert!(z.is_zero()); + } + + assert!(F::zero().inverse().is_none()); + + // Multiplication by zero + { + let mut a = F::rand(&mut rng); + a.mul_assign(&F::zero()); + assert!(a.is_zero()); + } + + // Addition by zero + { + let mut a = F::rand(&mut rng); + let copy = a; + a.add_assign(&F::zero()); + assert_eq!(a, copy); + } +} + +fn random_multiplication_tests(rng: &mut R) { + for _ in 0..10000 { + let a = F::rand(rng); + let b = F::rand(rng); + let c = F::rand(rng); + + let mut t0 = a; // (a * b) * c + t0.mul_assign(&b); + t0.mul_assign(&c); + + let mut t1 = a; // (a * c) * b + t1.mul_assign(&c); + t1.mul_assign(&b); + + let mut t2 = b; // (b * c) * a + t2.mul_assign(&c); + t2.mul_assign(&a); + + assert_eq!(t0, t1); + assert_eq!(t1, t2); + } +} + +fn random_addition_tests(rng: &mut R) { + for _ in 0..10000 { + let a = F::rand(rng); + let b = F::rand(rng); + let c = F::rand(rng); + + let mut t0 = a; // (a + b) + c + t0.add_assign(&b); + t0.add_assign(&c); + + let mut t1 = a; // (a + c) + b + t1.add_assign(&c); + t1.add_assign(&b); + + let mut t2 = b; // (b + c) + a + t2.add_assign(&c); + t2.add_assign(&a); + + assert_eq!(t0, t1); + assert_eq!(t1, t2); + } +} + +fn random_subtraction_tests(rng: &mut R) { + for _ in 0..10000 { + let a = F::rand(rng); + let b = F::rand(rng); + + let mut t0 = a; // (a - b) + t0.sub_assign(&b); + + let mut t1 = b; // (b - a) + t1.sub_assign(&a); + + let mut t2 = t0; // (a - b) + (b - a) = 0 + t2.add_assign(&t1); + + assert!(t2.is_zero()); + } +} + +fn random_negation_tests(rng: &mut R) { + for _ in 0..10000 { + let a = F::rand(rng); + let mut b = a; + b.negate(); + b.add_assign(&a); + + assert!(b.is_zero()); + } +} + +fn random_doubling_tests(rng: &mut R) { + for _ in 0..10000 { + let mut a = F::rand(rng); + let mut b = a; + a.add_assign(&b); + b.double(); + + assert_eq!(a, b); + } +} + +fn random_squaring_tests(rng: &mut R) { + for _ in 0..10000 { + let mut a = F::rand(rng); + let mut b = a; + a.mul_assign(&b); + b.square(); + + assert_eq!(a, b); + } +} + +fn random_inversion_tests(rng: &mut R) { + assert!(F::zero().inverse().is_none()); + + for _ in 0..10000 { + let mut a = F::rand(rng); + let b = a.inverse().unwrap(); // probablistically nonzero + a.mul_assign(&b); + + assert_eq!(a, F::one()); + } +} + +fn random_expansion_tests(rng: &mut R) { + for _ in 0..10000 { + // Compare (a + b)(c + d) and (a*c + b*c + a*d + b*d) + + let a = F::rand(rng); + let b = F::rand(rng); + let c = F::rand(rng); + let d = F::rand(rng); + + let mut t0 = a; + t0.add_assign(&b); + let mut t1 = c; + t1.add_assign(&d); + t0.mul_assign(&t1); + + let mut t2 = a; + t2.mul_assign(&c); + let mut t3 = b; + t3.mul_assign(&c); + let mut t4 = a; + t4.mul_assign(&d); + let mut t5 = b; + t5.mul_assign(&d); + + t2.add_assign(&t3); + t2.add_assign(&t4); + t2.add_assign(&t5); + + assert_eq!(t0, t2); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..2cb00a7 --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,3 @@ +pub mod curve; +pub mod field; +pub mod engine; From f2b1b0632da0b23b54cf0fdf4c31faa7ee9aa08f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 8 Jul 2017 23:26:38 -0600 Subject: [PATCH 002/140] Correct README description of E'. --- src/bls12_381/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bls12_381/README.md b/src/bls12_381/README.md index f54fac3..ae968ce 100644 --- a/src/bls12_381/README.md +++ b/src/bls12_381/README.md @@ -34,7 +34,7 @@ Our extension field tower is constructed as follows: 2. Fq6 is constructed as Fq2(v) / (v3 - ξ) where ξ = u + 1 3. Fq12 is constructed as Fq6(w) / (w2 - γ) where γ = v -Now, we instantiate the elliptic curve E(Fq) : y2 = x3 + 4, and the elliptic curve E'(Fq2) : y2 = x3 + 4v. +Now, we instantiate the elliptic curve E(Fq) : y2 = x3 + 4, and the elliptic curve E'(Fq2) : y2 = x3 + 4(u + 1). The group G1 is the *r* order subgroup of E, which has cofactor (x - 1)2 / 3. The group G2 is the *r* order subgroup of E', which has cofactor (x8 - 4x7 + 5x6 - 4x4 + 6x3 - 4x2 - 4x + 13) / 9. From b965c58ac1e939d41d66e6c3946552b54db7e53e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 9 Jul 2017 21:11:29 -0600 Subject: [PATCH 003/140] For performance, don't double/square until we've seen a bit. --- src/bls12_381/ec.rs | 8 +++++++- src/lib.rs | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 4f0daf9..8195002 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -475,9 +475,15 @@ macro_rules! curve_impl { fn mul_assign::Repr>>(&mut self, other: S) { let mut res = Self::zero(); + let mut found_one = false; + for i in BitIterator::new(other.into()) { - res.double(); + if found_one { + res.double(); + } else { + found_one = i; + } if i { res.add_assign(self); diff --git a/src/lib.rs b/src/lib.rs index ebdfa73..0affbbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -219,8 +219,15 @@ pub trait Field: Sized + { let mut res = Self::one(); + let mut found_one = false; + for i in BitIterator::new(exp) { - res.square(); + if found_one { + res.square(); + } else { + found_one = i; + } + if i { res.mul_assign(self); } From 021077b56b753adf5d8308ef3c571aea53e6aee6 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 10 Jul 2017 00:39:38 -0600 Subject: [PATCH 004/140] Added wNAF scalar multiplication. --- src/bls12_381/ec.rs | 84 ++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 10 ++++++ src/tests/curve.rs | 26 ++++++++++++++ src/wnaf.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 src/wnaf.rs diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 8195002..5570ade 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -496,6 +496,14 @@ macro_rules! curve_impl { fn to_affine(&self) -> $affine { (*self).into() } + + fn recommended_wnaf_for_scalar(scalar: ::Repr) -> Option { + Self::empirical_recommended_wnaf_for_scalar(scalar) + } + + fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + Self::empirical_recommended_wnaf_for_num_scalars(num_scalars) + } } // The affine point X, Y is represented in the jacobian @@ -555,8 +563,8 @@ macro_rules! curve_impl { pub mod g1 { use rand::{Rand, Rng}; - use super::super::{Fq, Fr}; - use ::{CurveProjective, CurveAffine, PrimeField, Field, BitIterator}; + use super::super::{Fq, Fr, FrRepr}; + use ::{CurveProjective, CurveAffine, PrimeField, PrimeFieldRepr, Field, BitIterator}; curve_impl!(G1, G1Affine, G1Prepared, Fq, Fr); @@ -574,6 +582,40 @@ pub mod g1 { } } + impl G1 { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> Option + { + const RECOMMENDATIONS: [usize; 3] = [12, 34, 130]; + + let mut ret = None; + let num_bits = scalar.num_bits() as usize; + + for (i, r) in RECOMMENDATIONS.iter().enumerate() { + if *r >= num_bits { + ret = Some(i + 2) + } + } + + ret + } + + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize + { + const RECOMMENDATIONS: [usize; 12] = [1, 3, 7, 20, 43, 120, 273, 563, 1630, 3128, 7933, 62569]; + + let mut ret = 4; + for r in RECOMMENDATIONS.iter() { + if num_scalars > *r { + ret += 1; + } else { + break + } + } + + ret + } + } + #[derive(Clone)] pub struct G1Prepared(pub(crate) G1Affine); @@ -838,8 +880,8 @@ pub mod g1 { pub mod g2 { use rand::{Rand, Rng}; - use super::super::{Fq2, Fr}; - use ::{CurveProjective, CurveAffine, PrimeField, Field, BitIterator}; + use super::super::{Fq2, Fr, FrRepr}; + use ::{CurveProjective, CurveAffine, PrimeField, PrimeFieldRepr, Field, BitIterator}; curve_impl!(G2, G2Affine, G2Prepared, Fq2, Fr); @@ -866,6 +908,40 @@ pub mod g2 { } } + impl G2 { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> Option + { + const RECOMMENDATIONS: [usize; 3] = [13, 37, 103]; + + let mut ret = None; + let num_bits = scalar.num_bits() as usize; + + for (i, r) in RECOMMENDATIONS.iter().enumerate() { + if *r >= num_bits { + ret = Some(i + 2) + } + } + + ret + } + + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize + { + const RECOMMENDATIONS: [usize; 11] = [1, 3, 8, 20, 47, 126, 260, 826, 1501, 4555, 84071]; + + let mut ret = 4; + for r in RECOMMENDATIONS.iter() { + if num_scalars > *r { + ret += 1; + } else { + break + } + } + + ret + } + } + #[derive(Clone)] pub struct G2Prepared { pub(crate) coeffs: Vec<(Fq2, Fq2, Fq2)>, diff --git a/src/lib.rs b/src/lib.rs index 0affbbf..3b9c47a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate rand; pub mod tests; pub mod bls12_381; +pub mod wnaf; use std::fmt; @@ -123,6 +124,15 @@ pub trait CurveProjective: PartialEq + /// Converts this element into its affine representation. fn to_affine(&self) -> Self::Affine; + + /// Recommends a wNAF window table size given a scalar. Returns `None` if normal + /// scalar multiplication is encouraged. If `Some` is returned, it will be between + /// 2 and 22, inclusive. + fn recommended_wnaf_for_scalar(scalar: ::Repr) -> Option; + + /// Recommends a wNAF window size given the number of scalars you intend to multiply + /// a base by. Always returns a number between 2 and 22, inclusive. + fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize; } /// Affine representation of an elliptic curve point guaranteed to be diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 9e7c8dd..0356b64 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -58,6 +58,32 @@ pub fn curve_tests() random_doubling_tests::(); random_negation_tests::(); random_transformation_tests::(); + random_wnaf_tests::(); +} + +fn random_wnaf_tests() { + use ::wnaf::*; + use ::PrimeField; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut table = vec![]; + let mut wnaf = vec![]; + + for w in 2..14 { + for _ in 0..100 { + let g = G::rand(&mut rng); + let s = G::Scalar::rand(&mut rng).into_repr(); + let mut g1 = g; + g1.mul_assign(s); + + wnaf_table(&mut table, g, w); + wnaf_form(&mut wnaf, s, w); + let g2 = wnaf_exp(&table, &wnaf); + + assert_eq!(g1, g2); + } + } } fn random_negation_tests() { diff --git a/src/wnaf.rs b/src/wnaf.rs new file mode 100644 index 0000000..0c5ae35 --- /dev/null +++ b/src/wnaf.rs @@ -0,0 +1,84 @@ +use super::{CurveProjective, PrimeFieldRepr}; + +/// Replaces the contents of `table` with a wNAF window table for the given window size. +/// +/// This function will panic if provided a window size below two, or above 22. +pub fn wnaf_table(table: &mut Vec, mut base: G, window: usize) +{ + assert!(window < 23); + assert!(window > 1); + + table.truncate(0); + table.reserve(1 << (window-1)); + + let mut dbl = base; + dbl.double(); + + for _ in 0..(1 << (window-1)) { + table.push(base); + base.add_assign(&dbl); + } +} + +/// Replaces the contents of `wnaf` with the wNAF representation of a scalar. +/// +/// This function will panic if provided a window size below two, or above 22. +pub fn wnaf_form(wnaf: &mut Vec, mut c: S, window: usize) +{ + assert!(window < 23); + assert!(window > 1); + + wnaf.truncate(0); + + while !c.is_zero() { + let mut u; + if c.is_odd() { + u = (c.as_ref()[0] % (1 << (window+1))) as i64; + + if u > (1 << window) { + u -= 1 << (window+1); + } + + if u > 0 { + c.sub_noborrow(&S::from(u as u64)); + } else { + c.add_nocarry(&S::from((-u) as u64)); + } + } else { + u = 0; + } + + wnaf.push(u); + + c.div2(); + } +} + +/// Performs wNAF exponentiation with the provided window table and wNAF-form scalar. +/// +/// This function must be provided a `table` and `wnaf` that were constructed with +/// the same window size; otherwise, it may panic or produce invalid results. +pub fn wnaf_exp(table: &[G], wnaf: &[i64]) -> G +{ + let mut result = G::zero(); + + let mut found_one = false; + + for n in wnaf.iter().rev() { + if found_one { + result.double(); + } + + if *n != 0 { + found_one = true; + + if *n > 0 { + result.add_assign(&table[(n/2) as usize]); + } else { + result.sub_assign(&table[((-n)/2) as usize]); + } + } + } + + result +} From 3faf8c526afc56ae9e2a995433c315e96fdfb80f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 11 Jul 2017 15:01:31 -0600 Subject: [PATCH 005/140] Encoding of G1/G2 elements in compressed or uncompressed form. --- Cargo.toml | 1 + src/bls12_381/ec.rs | 426 +++++++++++++++++- src/bls12_381/fq2.rs | 46 +- src/bls12_381/mod.rs | 5 +- .../tests/g1_compressed_test_vectors.dat | Bin 0 -> 48000 bytes .../tests/g1_uncompressed_test_vectors.dat | Bin 0 -> 96000 bytes .../tests/g2_compressed_test_vectors.dat | Bin 0 -> 96000 bytes .../tests/g2_uncompressed_test_vectors.dat | Bin 0 -> 192000 bytes src/bls12_381/tests/mod.rs | 48 ++ src/lib.rs | 53 +++ src/tests/curve.rs | 29 +- 11 files changed, 588 insertions(+), 20 deletions(-) create mode 100644 src/bls12_381/tests/g1_compressed_test_vectors.dat create mode 100644 src/bls12_381/tests/g1_uncompressed_test_vectors.dat create mode 100644 src/bls12_381/tests/g2_compressed_test_vectors.dat create mode 100644 src/bls12_381/tests/g2_uncompressed_test_vectors.dat create mode 100644 src/bls12_381/tests/mod.rs diff --git a/Cargo.toml b/Cargo.toml index f024106..aefc602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" +byteorder = "1.1" diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 5570ade..5d20d4c 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -4,7 +4,9 @@ macro_rules! curve_impl { $affine:ident, $prepared:ident, $basefield:ident, - $scalarfield:ident + $scalarfield:ident, + $uncompressed:ident, + $compressed:ident ) => { #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct $affine { @@ -98,6 +100,8 @@ macro_rules! curve_impl { type Base = $basefield; type Prepared = $prepared; type Projective = $projective; + type Uncompressed = $uncompressed; + type Compressed = $compressed; fn zero() -> Self { $affine { @@ -563,10 +567,190 @@ macro_rules! curve_impl { pub mod g1 { use rand::{Rand, Rng}; - use super::super::{Fq, Fr, FrRepr}; - use ::{CurveProjective, CurveAffine, PrimeField, PrimeFieldRepr, Field, BitIterator}; + use super::super::{Fq, Fr, FrRepr, FqRepr}; + use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint}; - curve_impl!(G1, G1Affine, G1Prepared, Fq, Fr); + curve_impl!(G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed); + + pub struct G1Uncompressed([u8; 96]); + + impl AsRef<[u8]> for G1Uncompressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for G1Uncompressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl EncodedPoint for G1Uncompressed { + type Affine = G1Affine; + + fn empty() -> Self { G1Uncompressed([0; 96]) } + fn size() -> usize { 96 } + fn into_affine_unchecked(&self) -> Result { + use byteorder::{ReadBytesExt, BigEndian}; + + let mut x = FqRepr([0; 6]); + let mut y = FqRepr([0; 6]); + + { + let mut reader = &self.0[..]; + + for b in x.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in y.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + } + + Ok(G1Affine { + x: Fq::from_repr(x)?, + y: Fq::from_repr(y)?, + infinity: false + }) + } + fn from_affine(affine: G1Affine) -> Result { + use byteorder::{WriteBytesExt, BigEndian}; + + if affine.is_zero() { + return Err(()) + } + + let mut res = Self::empty(); + + { + let mut writer = &mut res.0[..]; + + for digit in affine.x.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + + for digit in affine.y.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + } + + Ok(res) + } + } + + pub struct G1Compressed([u8; 48]); + + impl AsRef<[u8]> for G1Compressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for G1Compressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl EncodedPoint for G1Compressed { + type Affine = G1Affine; + + fn empty() -> Self { G1Compressed([0; 48]) } + fn size() -> usize { 48 } + fn into_affine_unchecked(&self) -> Result { + use byteorder::{ReadBytesExt, BigEndian}; + + // Create a copy of this representation. + let mut copy = self.0; + + if copy[0] & (1 << 7) == 0 { + // Distinguisher bit isn't set. + return Err(()) + } + + // Determine if the intended y coordinate must be greater + // lexicographically. + let greatest = copy[0] & (1 << 6) != 0; + + // Unset the two most significant bits. + copy[0] &= 0x3f; + + let mut x = FqRepr([0; 6]); + + { + let mut reader = ©[..]; + + for b in x.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + } + + // Interpret as Fq element. + let x = Fq::from_repr(x)?; + + // Compute x^3 + b + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&G1Affine::get_coeff_b()); + + // Attempt to compute y + match x3b.sqrt() { + Some(y) => { + let mut negy = y; + negy.negate(); + + // Get the parity of the sqrt we found. + let parity = y.into_repr() > negy.into_repr(); + + Ok(G1Affine { + x: x, + y: if parity == greatest { y } else { negy }, + infinity: false + }) + }, + None => { + // Point must not be on the curve. + Err(()) + } + } + } + fn from_affine(affine: G1Affine) -> Result { + use byteorder::{WriteBytesExt, BigEndian}; + + if affine.is_zero() { + return Err(()) + } + + let mut res = Self::empty(); + + { + let mut writer = &mut res.0[..]; + + for digit in affine.x.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + } + + // Distinguish this from an uncompressed element. + res.0[0] |= 1 << 7; // Set highest bit. + + { + let mut negy = affine.y; + negy.negate(); + + // If the correct y coordinate is the largest (lexicographically), + // the bit should be set. + if affine.y.into_repr() > negy.into_repr() { + res.0[0] |= 1 << 6; // Set second highest bit. + } + } + + Ok(res) + } + } impl G1Affine { fn get_generator() -> Self { @@ -629,9 +813,6 @@ pub mod g1 { } } - #[cfg(test)] - use super::super::{FqRepr}; - #[test] fn g1_generator() { use ::SqrtField; @@ -880,10 +1061,226 @@ pub mod g1 { pub mod g2 { use rand::{Rand, Rng}; - use super::super::{Fq2, Fr, FrRepr}; - use ::{CurveProjective, CurveAffine, PrimeField, PrimeFieldRepr, Field, BitIterator}; + use super::super::{Fq2, Fr, Fq, FrRepr, FqRepr}; + use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint}; - curve_impl!(G2, G2Affine, G2Prepared, Fq2, Fr); + curve_impl!(G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed); + + pub struct G2Uncompressed([u8; 192]); + + impl AsRef<[u8]> for G2Uncompressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for G2Uncompressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl EncodedPoint for G2Uncompressed { + type Affine = G2Affine; + + fn empty() -> Self { G2Uncompressed([0; 192]) } + fn size() -> usize { 192 } + fn into_affine_unchecked(&self) -> Result { + use byteorder::{ReadBytesExt, BigEndian}; + + let mut x_c1 = FqRepr([0; 6]); + let mut x_c0 = FqRepr([0; 6]); + let mut y_c1 = FqRepr([0; 6]); + let mut y_c0 = FqRepr([0; 6]); + + { + let mut reader = &self.0[..]; + + for b in x_c1.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in x_c0.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in y_c1.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in y_c0.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + } + + Ok(G2Affine { + x: Fq2 { + c0: Fq::from_repr(x_c0)?, + c1: Fq::from_repr(x_c1)? + }, + y: Fq2 { + c0: Fq::from_repr(y_c0)?, + c1: Fq::from_repr(y_c1)? + }, + infinity: false + }) + } + fn from_affine(affine: G2Affine) -> Result { + use byteorder::{WriteBytesExt, BigEndian}; + + if affine.is_zero() { + return Err(()) + } + + let mut res = Self::empty(); + + { + let mut writer = &mut res.0[..]; + + for digit in affine.x.c1.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + + for digit in affine.x.c0.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + + for digit in affine.y.c1.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + + for digit in affine.y.c0.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + } + + Ok(res) + } + } + + pub struct G2Compressed([u8; 96]); + + impl AsRef<[u8]> for G2Compressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for G2Compressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl EncodedPoint for G2Compressed { + type Affine = G2Affine; + + fn empty() -> Self { G2Compressed([0; 96]) } + fn size() -> usize { 96 } + fn into_affine_unchecked(&self) -> Result { + use byteorder::{ReadBytesExt, BigEndian}; + + // Create a copy of this representation. + let mut copy = self.0; + + if copy[0] & (1 << 7) == 0 { + // Distinguisher bit isn't set. + return Err(()) + } + + // Determine if the intended y coordinate must be greater + // lexicographically. + let greatest = copy[0] & (1 << 6) != 0; + + // Unset the two most significant bits. + copy[0] &= 0x3f; + + let mut x_c1 = FqRepr([0; 6]); + let mut x_c0 = FqRepr([0; 6]); + + { + let mut reader = ©[..]; + + for b in x_c1.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in x_c0.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + } + + // Interpret as Fq element. + let x = Fq2 { + c0: Fq::from_repr(x_c0)?, + c1: Fq::from_repr(x_c1)? + }; + + // Compute x^3 + b + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&G2Affine::get_coeff_b()); + + // Attempt to compute y + match x3b.sqrt() { + Some(y) => { + let mut negy = y; + negy.negate(); + + // Get the parity of the sqrt we found. + let parity = y > negy; + + Ok(G2Affine { + x: x, + y: if parity == greatest { y } else { negy }, + infinity: false + }) + }, + None => { + // Point must not be on the curve. + Err(()) + } + } + } + fn from_affine(affine: G2Affine) -> Result { + use byteorder::{WriteBytesExt, BigEndian}; + + if affine.is_zero() { + return Err(()) + } + + let mut res = Self::empty(); + + { + let mut writer = &mut res.0[..]; + + for digit in affine.x.c1.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + + for digit in affine.x.c0.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + } + + // Distinguish this from an uncompressed element. + res.0[0] |= 1 << 7; // Set highest bit. + + { + let mut negy = affine.y; + negy.negate(); + + // If the correct y coordinate is the largest (lexicographically), + // the bit should be set. + if affine.y > negy { + res.0[0] |= 1 << 6; // Set second highest bit. + } + } + + Ok(res) + } + } impl G2Affine { fn get_generator() -> Self { @@ -948,9 +1345,6 @@ pub mod g2 { pub(crate) infinity: bool } - #[cfg(test)] - use super::super::{Fq, FqRepr}; - #[test] fn g2_generator() { use ::SqrtField; @@ -965,14 +1359,12 @@ pub mod g2 { rhs.add_assign(&G2Affine::get_coeff_b()); if let Some(y) = rhs.sqrt() { - let yrepr = y.c1.into_repr(); let mut negy = y; negy.negate(); - let negyrepr = negy.c1.into_repr(); - + let p = G2Affine { x: x, - y: if yrepr < negyrepr { y } else { negy }, + y: if y < negy { y } else { negy }, infinity: false }; diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 55da940..a7a66d1 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -1,7 +1,9 @@ use rand::{Rng, Rand}; -use ::{Field, SqrtField}; +use ::{Field, SqrtField, PrimeField}; use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; +use std::cmp::Ordering; + /// An element of F_{q^2}, represented by c0 + c1 * u. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq2 { @@ -9,6 +11,24 @@ pub struct Fq2 { pub c1: Fq } +impl Ord for Fq2 { + #[inline(always)] + fn cmp(&self, other: &Fq2) -> Ordering { + match self.c1.into_repr().cmp(&other.c1.into_repr()) { + Ordering::Greater => Ordering::Greater, + Ordering::Less => Ordering::Less, + Ordering::Equal => self.c0.into_repr().cmp(&other.c0.into_repr()) + } + } +} + +impl PartialOrd for Fq2 { + #[inline(always)] + fn partial_cmp(&self, other: &Fq2) -> Option { + Some(self.cmp(other)) + } +} + impl Fq2 { /// Multiply this element by the cubic and quadratic nonresidue 1 + u. pub fn mul_by_nonresidue(&mut self) { @@ -157,6 +177,30 @@ impl SqrtField for Fq2 { } } +#[test] +fn test_fq2_ordering() { + let mut a = Fq2 { + c0: Fq::zero(), + c1: Fq::zero() + }; + + let mut b = a.clone(); + + assert!(a.cmp(&b) == Ordering::Equal); + b.c0.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Less); + a.c0.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Equal); + b.c1.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Less); + a.c0.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Less); + a.c1.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Greater); + b.c0.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Equal); +} + #[test] fn test_fq2_basics() { assert_eq!(Fq2 { c0: Fq::zero(), c1: Fq::zero() }, Fq2::zero()); diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 43290fd..f0ef0b9 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -5,11 +5,14 @@ mod fq6; mod fq12; mod ec; +#[cfg(test)] +mod tests; + pub use self::fr::{Fr, FrRepr}; pub use self::fq::{Fq, FqRepr}; pub use self::fq2::Fq2; pub use self::fq12::Fq12; -pub use self::ec::{G1, G2, G1Affine, G2Affine, G1Prepared, G2Prepared}; +pub use self::ec::{G1, G2, G1Affine, G2Affine, G1Prepared, G2Prepared, G1Uncompressed, G2Uncompressed, G1Compressed, G2Compressed}; use super::{Engine, CurveAffine, Field, BitIterator}; diff --git a/src/bls12_381/tests/g1_compressed_test_vectors.dat b/src/bls12_381/tests/g1_compressed_test_vectors.dat new file mode 100644 index 0000000000000000000000000000000000000000..36bf17d9d2fe50c0fcbf8b8b118b1c0288f6410a GIT binary patch literal 48000 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%0DmNB)mjpnkUN?(=);js1H*V>?GsILXcg}D!` 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 YwSDhi*gK>PbmdTG@GCW{9Z*a_OD8;B=>Px# literal 0 HcmV?d00001 diff --git a/src/bls12_381/tests/g1_uncompressed_test_vectors.dat b/src/bls12_381/tests/g1_uncompressed_test_vectors.dat new file mode 100644 index 0000000000000000000000000000000000000000..5194b62b60cc57a469de0e89de252a393fe0eaa5 GIT binary patch literal 96000 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 literal 0 HcmV?d00001 diff --git a/src/bls12_381/tests/g2_compressed_test_vectors.dat b/src/bls12_381/tests/g2_compressed_test_vectors.dat new file mode 100644 index 0000000000000000000000000000000000000000..42b6ee9c01f3e839729bbe3dc3310201afe6e2e0 GIT binary patch literal 96000 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�uc*gWwz@d=S2~bA~ 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%0DJH2`)#|uM*o9m5qzl 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%0DYLq3Ox@%Fh#WI zT*}xm1#69b5r(|@0bEG{X1vnO-C2ALK_sq1xT>twav5N6Cy?OI6pf~GR>Bwfdl!A;%Jw)H7w{Lebrx8h~gC&Pmr4fN%S**5SQ6Ei^c4HJ?F1#*1ts zNg#~dQnL^J|bpEh*{_ZM9pAW94B$1fi zI@U!^N2gLAhzZyOZWr#w<2|$E_Hxbl9G{ol8N@|my*CP{O#Jn_Q%x1UnsYMHt!j+5 zzQ6$}MGA+pc%&5&eF|Uz0M6Nm=d89%3;EKcD5x)+&1q30~51o zJ1ZThfjC3M8Dd2nGaC9eH_dB5a2a7;F98#OZ|@va>jcl^+k2xJxcbLB#GeZ}kD5?W zDWP@`HEU93vQNYCXh8H~*LqmIO)DgLwShrc!RhcTj}iYfFY>_YEg=!Z3V4CXL9S}Y zSv^7lR2zmf1d-lVR|?L`M6J>#?Ca>$tmceXuXp*q260S%z+s3=4ZFqDK�aFiR@p zTIhmL#J*^T&f}bGC-W+Pp1@Qe75L@=Xp7=1Avu^~U5BD|#2}M6 zQL&%&Xiqex{`p0UKR*OBZb)feH3qv$$CB)V5PiUlF?>qVbFpJvkn|n%uB+kkA4u~S z3L{GMW5-|6tbrZ@%phdMN=qY>>+9MAtJ+KO)6r~dii4`99OQ%FKtcHZF$?%}gi_}r zjFR&s8KVqC9Nne`Y7X+M|77ymfc0D+4I-?BpU`MkzBdtVE`7@GvAh4a749O9YOpx% z_pJLKi6`!gZtVAxJt?OLJ7)Jx*1V!UwV)?43we&8YgV$01Y#&_8lv45 zzw-Ii)Z?xs4uyQ>*1j+}MNSFA>l-XQ>KN1(cI>WL0XT!G5rQq76MS5Lue=P;uvnGv%oiXXfLHeqmR;Mkwr@??`(`AWV0vra5?gzW%uq4>7M>xFpBR!$Hqtpx!|yF1)nGsu>iMT6M@MPy+* zP{e!~1*gst(G<(Up|?V3Wrsl>8(T>APm14g3YPvPM8JH-+Tp3toG@NSz7O|;QEpPz z$Z~!9fnKoo`we(zENR|Ky7vsA;wT)eeA0V$V+FCYNWhhSD+7-hd^Cve0a!y4!%Sz; z_d`Vf>Eg6^0~=!!&1f$A+INIvWJ}7LxKgoFvo1ciS~I=fUMxTj)y^B4Ubm<1&mtLP zkIOc2*T(2@F={r$_vAM;;zQRxtn~LI4DNK9Zf%OzvB>`i^`W;G@$Z43ly$@7fr6c6 zgIKfJ*WtuqvC?@15Qun+L0J$oxc*>=9eQ21G!>o}Tm^y8$H(KBE3$fVFMybeMkkIk z9Tt>F9U2w<+BfeTgU~EOMi;VUi|w5BF|pv_mA<8k6TnfQE3eyt35?*Hts zEYtrk1&AK2cBr*hMZB+~;G8vX8w))dp*j@8dX8tg6^ICyfuYW0o^XWar5Qmq4)2bi ztSaSU%2FbmNkgQJLqwfqEnvZTQz5i|SPlFg5L zOocOyY-BE}!@|13l*Ld4LcC!^1fShq#>5TZO)hnrr5gM1dsKVJ!<1W@rV72EGOQc6 zI9r~NtE)WEMkgw=d42=>WeXKRDsR5@W^H@w}A+$-(^I0Ix z@338%Q3H10&38hZDcnc|T^P9DT4PGWt2~rgm5e>8uT~GY8uk^4AhI%-ee`T4Al{Y+ zI_!^*tq8iCSHp>ARx8HQKVwcW7cpf1_xBZ_% zoc_(Mcr6%<;byccQN*7@9NBfr6F4j$F(kKZMlVabiUCT-r{G= z{j>R?vI;W$?eQRUyd8P2n5y?zHZmVH4FHt_)=zWbF~HXg9*RWCNm(pQ?;`giHg?)6%%mkk^@Eb}0SffKx+g3NdO4 zu6UCXBpQ;cPWh_}{Hx9Vc}%Ag<1EkiN~tb|f&T@HAt+H_L68=5KLTbz8%MA}Pj9)k z9U@QGrn2~%o_(`bb5VwGxQ>oc9-mJ739?PP?+`O`*^~eE@Wvkaxj#DD+35#8rP%}T zDia_MUYb=Ct}bcAtE}de1W=oCqmmEQqGpiE9i{&p;`^32zlshc^ zJ*Zx#1FX{9QWHb=%%ou$onVS!MC55G1zA@~-v%4|d{X%)?QT>1TFy_5$T()5sBaqH zk!?fea{1=j4mSBJmI|_ODneZ!9JEolmO2g7;vu>m-l837_Pw)Jq!e4|1hQuBnW)r+ zR21pm7V$t2imOB+k+tr9j$ZX`OSMvymE>Bh`(dgMS}{-YW3^nKJW9NvhLZ3)s4x*T zk274{OGvR)*LJhnxivSWeMQUx6I~={HAJ(QelmrJ^Brps3ST?IH+zv-NoDQ&!?Z+& z>3m1Ng%xXmngSnMN)WjJDk z@Tq?13CHF)5Gon8+f^PAQe}M5%TWLMV%YdOdBoj;#^~R4=G1Du?>;*)`lR!f>s(iy zq1i%&_tdOf9s2UdEBTQXog&E~(WMTJWc`i-^U>%F8di-GDGkPpI_-j}yXAQJlG|f; z#a+|qvS^r(X47YvdX;>wddIrG&BBg&beioXNE`sL(zPe*bg5%%vPf(yZJlqr1Hj5& zM+2$vj1`Ut;#D;lH9TI10ZCtFaNYcN8+#FgAdpU3O$o8K+Hi$1HFITrG?W%sQLNim zuw7%uH_@G%%HMFpO?hU9CvA;Lh4GdID0Y+R#UWXzS8+ypfsTpol- zuo?wD54ewtmbuJj7ptdR_I|uJG~MUSZiJ!-@w;{gL)jSnY+M_&XhEokWD-CT*g`ll16T?*0KUc3qnQ+#rGRgELRJ|MFVYURRT>)s)ROe=aYi}X&(WrV)O2;i<)9~&VqoQfBW#l)mk*5j zW!S`HzC%WzSD~eBp#C9+z{+x~p%ImkLGHIQIK{l74nW)%f$9GSWJ`|bY`>8A7dwo9 z%#SNcVQg(H@>dS)FN4I)g6nqlUY<5=>T&Ri)aGN1aUfcWTSh6dq3i~9QWW8 z4(F`=ShJacC$Tb?y{XTh5A$ZCL*AH3Ig2$EycRNXcoRq0OzRj?ZK9UJ$WZ0}F7d{g zM|8e6^UHAAD5S9W3)BD?^Rr^RL(VA5U(-f3=aqhMW-1ToyzSoXgn(7PCIfV*f92#P z50x=Xwa?rUMYA=?CLBro^U$&mcYiZ7e~j0%W6HAV=aP(5IwH!crc5V$W5kV1m2yb? zoi~IhAdVZQSW(?Qt3&eCYR22r`nV`xYPbDotPlok|B0#iv9u?b$$M znoD&TH)`>l}$j*wa@{M4jFq`O;*BvP*jQFTHW%AH-G2}8o$X!9Oh#Ye+P za}!$xFi!8YycTor^TS{{6)*Q61sWzXBUGw*JVju`B25tTET+|`YrJG8k~dnUtH==0 zWmt1(Fh@vQzSy+ikNXtAA7k zMz(dQ(cAGN*#&r5O!98P3y&z0GC~vyt8_14$eXNT#9^WRCZEl0f1p zaT_)?#F+Aq2kyfjM5(o@W?w&NxdLzUMYx{RB|K#afIcoH3kT{+)j0&c89uF<5t$Wd zq!wTQrL7=aiZ+SP6xKdF39V?dALREDI@pR4*NZ{EJ!Qx`I@5I3exs$&--hgKoqpn? znkK~#omZw#a}ptnz|KDS>)AQg2k0Dk1=-a<66i4gs+Bnfw9J{u_vZsP0=M5qj(>N&@)#SA(n(c zz*;`g?NopPoz?C#F0@F5cwJ=L@G@s38`Q;eQf!_9@!s5t7R15hz#>jcludmO79udO z9t*I+30rg#V4z}PjmK&NJX4%y-MPUl($KYuHi-aC9Pcs@w6U;9g9jv z!YA{5+J4z2PGNKlfqlh-a9}Yh6ap%5aa{E*PUq$Dscoa@$;BDbN@dW%=}1Gn>|~Mp zA@D^aA-Be*K9Y3BZ4mpYdK-#lxYs*iEs6!zTL5G?Ao)-RJliAU3hNgJZUk%>OvOd3 z*V_lnsN-9x&W{^otzWHn_~ws}Hs`wBdDD>pCy-Fic8S=kLN|wn*m+ED3rYbL1J;a* z_p7alz7Un}0g+!Gz-=Xfi8S6IR10)ydqqzL+i<8cXYlrY>6H@#fF#c9Ljc67szxqU zl(Ht}_z|sQ^=uf=K*V{c!H4zInpVWhc8@2@1Yi6vn}uslh<&^_UHi&UuKcLxyPZ;Mfa!)nz{mO1_WN#eKZUb z7yG$pPA79~FfF}j-`pL_oc1T)6;~3tT@}rHg4V}6KZX)#8bbtPX?|gn5@P#UVIH)4Ae+xUc|sDeAl zyeIJpayS-I2YT`o$f}C&2onLlYHy9hxMQnSotrA+#@gL4O z^v=BP@O7w@=we$(eOU(v!|DYqY!uCx`24b4cTW}AE?7ZYk2|~>iGECCfUDl&#+l_5 zkf6<57+PH=dR|A1T4F-a0OX+Zi&Nic1sD5pd!|u62s=;5)ak1WWk`e~7xM9-GqeQ| z4g^4&E@TIgv`Ldwa8bndX_RADjnOy6Dl;R3ck$m0wijSvLRlilJx{MyTD+GtgL5oT z#J5an9qf2kk!TyOzb0qvVRi1~5r;|H%QIw(5opI(TMarJv}Lz)2IX_fd{wR?Lggr4 z^f=;02mn{1FD#l zkg&4F_$+HuzzfJiX?$A)x1?m9i9xi-eFg#mk3(l(uyIY&G&6mt$2d&ycJbxw#_<7b-_4d*#6iXC3DL zWj>J2+CXhe;1*Aaji-DEqv(VcK^y_kUEyb9#Ylj?+TiGZft+RH+pk*Ig#0! zOG|+f%yYZEPwnpj?JRIdf4&@Wg-!)*nVlXB#?5#qfwl|94XC!Jt_LwF9#;Wm^uz^#9Jg)2jf9sQ(<`Knn&V?fwtgn9kU9W*KD z_4yAMDOap91#vH=@$A~a-vuZ~97jQgT(fiejtLi8fCiAx4fO2+a*XMXKwUdwTMi1> zz-5jj%t@1c0v8AJ?|i8{3gB(U2R83jujv9liHj*$q9HO(2P-nbU3>w9YV0%*lxE2v zIRSwhJTRrDuC_rm(%qIiX5Xv&7vn)h&OKSODrATENeGW{a5-7O48H)39+n>&C1D#3 z-zcTh$n`aLY~iL3J6PYwBR7c*-(3~yzHNPi(A?lWy-lsSScy0ldpdvLx)28@IM0NO zrhy?W;C-J6%gf9x#iVLvl3T7KvQEEg;t5$TL!QCu2zi~35LLN!wuU{JKw4Ec#)Q=U zfC*sKIy%P|45t!!WG8aQq6t$5WdtSSJ-%K<%Z+#U8`Ejbr*yB`>5q4jT>GwzTUJ)5S;JD7PdcMpTSi(<>U*jUBa zbDY<3Dk!p7!Ec%I1&3SQBy(E-`@Sd@aI2k_98=#wC+-b{I^L%k&aFL=DceXRyZbK| zK8W9n%9}Wp5TN{wO@Lk#kom6J&mx_q#}k2kL7^;47^r<|;S>0>38rr0!32aOVHl9m zCnw(o^R-YtWsnG4`V>qC)PY;dk^1phx&|cF?^9kPOpUfC2V@f3J5V5po`xdARhms~ zBA17Yw_AM5mg{VR5bT;-#||>0MYm}=WplE!+x>#GUc=@>RXH575|6QEzuyHtI21nzTbSjKWupWy=jmhruXP+;-gjEx)L=9Z zNt3?h^44Bl^#rUj?yOQZ&@>*O{fVolVpMv#9us#9rZ&5l9(jgi)$&;umaT?tEvt2f(0>r zdV1kzZI7wl1w&>SIQ{l>BjtCP2{G1{6lk6jGgUaQB>-yJ&-i%rZ4BuJ6Cl>{T`UL9i`I9~Yd8KdX5N;$U+g*c$P zC^1y2EBfvBQFpMO;u4&M7K&2#0cB|IXj|a*Pt=2J`=5N_jnc>dTT>l`4}=vJGo2Lq zB)tP)c=P)c!hK$U!DF<-Q=PU^g>vM zprj!L4K?JhsOSoys%>KAWDw9vV#1K_z8crLP8BgCeW&??Xi{c;=iYd9?990%wQ?wJUSX}QXoZh6xP7Wgo5 zbVad1fR*(osGmkhWKBvB?0HX%4Ns9z_28Jc4f74te52bLR~=V#1qHp#9V}Vno#iod zVe&rFU>L3OYUu=wmVAU%_H%$~;9f4V1+5}41w~eI&L>GQ-LTLY+TcHK#Dg2RTs z+*Q7XUP&nf5B!Dd0eQbDh2_cHQ+qZEQuYo_T=+s{VH|Mxv0l&ywVt~X} zAO_Y_{(n$b3cU2ORQ?tE?O1KsrAbJsByHM=(Xf)hMwX)uEMV7123fOC3U_A`y?E zUT=!u1|=`p>C_A_T!cJ_GW!BMf2R|4zEZc6urB-WYE-e&y1^Y9qdr(#~} zB^<5%vhp@s&*Vf>Fa3B-jR5y=+W#e7k@}xfx}W_@?pn4g{|~&w)3X~CdT6X1Dpne_ zqIJ#+O0?PM7s-H3IL-MO;>d_MJqM~8Y?hkdvGv?f^CA|54^{V=I*{_t2q?WPy=&mx zlpg*%OBepqi@bgTnp|Z$(SrinmMIX#t8i%GT`=8nphgZB4fe2bjXv!oJLwrBQ@|>W zPYE!`SzF55WlKRBCT9qdk5U+A(10}KIqaj^A`=7CVy;_?ZsQ=9^+fPX0_{!PP;e+F z1-{wvJSMQbWSp-S*_EGvz4I375>dbf&Xcee-O{A)aR1098k5zqZ8O>njl>L3+JjGh z=&$~N`66cxXR>WqMv|s}75ea0n_d8x*zqnLXdi1ENtdmeDFVNg8 zL?dldRwK7Xhqtc{CWJt8mj3exS1{ZxB**GV!SMsWpj8>RYI4l*< z59}IBhu}ld2UvDoa*r=0c&9jK7;{y!_VN44b3+xVUT5)Gk(*POpSI4*qRnbqZY|a9i zCd716|2L3QIiLiiV26C&w~Q1T%q$6(1b`+@BZ?)tBJ5cdoYj&wU`Ol2m@xhco2HSE zOGf>@LKr6b5uCL{)eJNm|8lchR`icA>uV0*lt;71tYLNC5*u2htvM--q$bxjSt`Z( zf*vy*m%cyPjm}mQ^MRYQ+rn{$tc(me?BrD#+y!TnTS& zc@(TJpPWiTlb|{?dXr&#O@2e5{6A1)tuu7Zs$O&+ba1x_$FB&$L4Gbb5L(&X4l9&OfdZdLE?rsf87ZHx zK#+sW{UuyKlTuT_!;pEjGx$7TVaP*qgUQgos7r!13rQ6Ys843eoxMkTakCB-?Rg{s z%p*)_CUiXub9T}>X)-JaSB${?9f#2xzV+MS)+5dZ9Vxi$qc!%vMSjDdZxo2!!lWq1 zoI$aWaqd{V(C?VOZ0DTsY}4yisQ%(&GO`(c)|l5vyxS#jjd8)UYfynd&(;cU*?9#B zNx&!h62>qwh_ z?MO~4dxw;xZ&}}Zph#<#nB?&P9gbN{v@+{e#?it0YQ|=~Pxs@7F)V!k!{q8)x!yUNja#5lpkB=J%O8~yr#PN0%^Gy-8f;L z6l9#0*`TqvT?17Sdio~A4+5pU6~)=v>XCOC$)-+tzTxQ|`tL&H5Gc4{A$3!NNK{Dv z(aq^xW=awBG6z%sc^r)7;MGIgaPF9pd9n3UT?D^gZH0~^5cU(f6#gp^_P^0ceVgt` z2cq?vwkB_jc2$h2qo0X@!1GuOU$c;7$EE8dCW{)yiZdIm5AZ#C$q)@Tooxhu4UVO` zP8GQenI~Liq{=7VswoGCdPk$yEqy_5nx3j{U z$~}qK0KbXwuPO%-WV&R~uUk%mS4_Sb6}#Tf9aH|@7p~kYGvQxGCI>y!5=-D-Ab9@& z4^9;bOkuN^Xt#(KyWaVu(B>rCVX2u9K~~Z=dw{#5(uy*+!d-Zgv(QsUSh>R3Jyl|^t_dAx~?UL!S4jV%H)xPexmN43#gqgpoG^K1!Iv8 zv$ETvTk19H`V#|PWO_G@hD69q>>0oDT6_%f;>=j5=0sHR>TGv?;SOXk#KQsOb7FBpJmG1pAii2H&(xeI${Eo3l?PBHKdI(&! z>OCUN-D%k3Zl*K%&7@%C7Xja@YD*(3D$Q9XtnB;}uoZ}+{EHRmES;7J7Dq9ViFY8^ zCIoWhsjM~t$ssTvJto%N^ZxoRYakt@w$O0SWK35g*+h$T1wK=68(7;orz;cTR>h+$ zsCOvKU6o|wB$(!W;ywp23jNln zNDN@yipo%G$_48vZ?xY)s@4^OH+uma^13(#;=L$7?g<~vo$Uq>-#HHml0TYc*R2`r zuvTVGM%UzMJ!$IbDBRl(hgLtRW*eariIG;bCNOKjxob4l=VlSdt`({7&y7uXut_;H zU{sLLOPJlKeNnc8T$wpkKZwMAo1erA9ZV9Z_rhB`;`kolRvW&1| z6fZyE<(df#gh^0zNA(yWI-WUMEmO2_!u^9be4{w%uY1@-f$iDgb*=6rMv<9;rkE zL}63EdQ-Le2h~j;Adj`Rp$La1w{mxXhdCPKG1(GKSf*_x#$DT$a}@5j2g(G+?1e{j zKU#cNHzkSYm^zPQtqiqPvN!_oNT|efumn;R;%Z`EK<)_3jJTCWu*cgF^E}CSr|Z42 zbA8JVTg6zZI}8D(eiJ9W=*R{hkTSvPNu}bcfBaD*OA_kutiMc#J@HA#L|;KNIEEPp zEhTxtA*@3a=k#$`Mc>xDT~QS8k9#5eFc*2TGk8=83)YtlX1KFH0(xA3caieREWDwE zV?i}o*k%@jnGJ(2(3kb0l(oXuH>0&XV-VRH-vWG!uY8!rC{Q|5<&~hT-KNUnWVXA| zjc!++1skH*y9Zw-`YbM5E(8%7)zzVfT`QSxGi-KIGrv9pd$}Y|5FUk*Ah@Vi=u&aZVeO(?;kXR+JMZzS;s(+DHFC% zNFSy)?y-bsSqgXtGQ>(-fx-Az5JlX1@2V9DY;XGg&(!v}BA2hG4_i4V<$u>3Zns3iK( zx7SW^`My5Fa{AZ_3JE=_wg>J}4Z@XmcZiG|UHbruNl#vl!5fDI0DiS$N+olq6Y=~ev0TEF?% zZT^fIF68`jwdtco*PV&ws?sA8(|)?$Ac>UE`Gha?V!A=i(bPoD0Y@5UAaau#Liva( zZ)4C!(|%Bt8?w|T5R)DyE-fz*uOypz0Q2>*NyiHBnx`M54SBk`O41pH+dYvPy(#CD zvEhOz7BJqz26S?zq#ZNM30^WZSSlGaNfWirdfTYs204EbpN7E(STXa&2a+2mk1s)_l&)-hAwhA`_sjp788r~hQBNjby?qB_DaYKX?T60+ zMuC_)^N_~VvrE_Wlo|OF4eebJ)zqu1Uz*ghH4?Q%vPICw7ni>yrL#aG-c-C%OQz zzVFxl$x$TAa4H_z)rpxEEASH6h|!ppbD?Ffm8!S+A~^zvL$4gg>dfWG?)b7;0y^~B z4nvJ-_%}1OUG(7h4ZP3S=`+u6#y-gah##VKVuyGAGdi=c31=Ih?otL|_5 z2a!(w&pRgtbdg|cpd#Esv5u^FQ2IphaDv`3@mPMZtzJmYDaM4e>WpsDj((^kYFtG)-uBzl9pKw z+FFeJR#$6)q50?u1;YR`a7SKa2>6ZNU%G57Lc9MGu{H0WE!I{?3u%VTxidh4(>euo zyl}wH%MsY}$~!rz^`3U-$nSLeJhc#YU%$ULdP|hBo=56FAZ7Oob6@cQ<>j-gcXhx0 z)Cq1<1F2La^fYeBihvru8Q_$Sn#VsF{@=a+b9M7J0V$Qpmm(9zf4ZwI_kkhq$Jk_x zn0P8^XRQaOeZ1RO8MQYFYM!eKf>N)%v)3H!)kXXE4*Ac|FTi+cM+boB`O7+*J^03qs%!`Yl3aPGv%YhXb&Em@3A3UVgB06Pb0#Ijx!8M zGn%Kxmut*`t&cN2rUudiBe?>U;MsSx^gUJxR&friyny5k$E_xq@(cN*(I0yu#oJty zW?`Z4@-Mk8Mi;F9*G`d|HazMgcN8VS3|Y{C&X~!SGSuQq6IRnSE_DwN%5?MHqAgAv zx)yOw@CqG!!B~_h$CzwqA0wR*klL1|RO@KBF_fN3QJ^}U)^9ga?u;D9im^w;ixaZu zsp%vY2}DwN(pBCy6~ku5eYOj6*Xw#nAB8+|i0++b(6#ZPh{lf;iyTVfG<#2%d1wIG ztS)j+c0FORZ5>(}k(Ozlg}+{%1HKl)_Z7$bkLek@j@uwjV@aO_mJ2V)0ByAeP9~t= zi=8TNBmGXnkVl}kZ9HG_XU1G;Pp)L=4OhHb)uH}sk*sjBEA>|c`6uxy)K?q$Rz$4X zS1$Yk@a_$ze#c!&0y5PTo0{$CP8#d%+9LG}Z(6|6hS)rvd4JYpmk|keMG+R<@@*uNk!Vi zHm<}S<%opCprGl)lcK+#>++x>eY|bZ@e1HX95_g83|m+;6ZuyIi9oK>K-!NMfCDM^ zGgs7Rav}wmm)_V?6-po6u+5@U(&3tk9VR zsF%(LnAhwu$Ct+T|F&yq^=s zSb#Ob1@mccXIxG^V+5j&dsxV-=VWJN_tx?MF3Q6hgK`tcQn;``Q-^pS+av7j-w zznx+QBHBtW-Sthe5QH%Gmey>yae71V60hl@AR4T0!mq#x;U)(qpOOs#@s}3j}M$`q;*7|Ab!D*a`uncnAjMDs1Q*rT<4( zPy#^^oA%P4B%w!CbrFR&!~-kd?^`&_3E207ub6Q&X^OmsNKvpaj-ID_n`hApn6~f4 zsCGvr_k88W!sFB?)GR18qA+AL-hChp?L4Gj_grD?2mwJfeRrcW+X}+CSVu zxSr?^?C#HEm;11Brc@DCGkfA5?kqcQa%HJgu zwTjTxBa}xD^v)bLp6OmwMZFBUj&Pjy{i8>nYnMr)%0o1ZcbXT4)JO7O5gU4{;5BYc z0*Mq!8QT={!z&BZlKX|!8ViceTYp_&8E7YE&1jAZa>QBwuMdjuJ&e1#&9&;e)K+Tr zRd=^GUttbh73Nc}pTs+RKh5A=$n!b68*9YK?=|6P9a*w6dEJhAO>cf&>w07>h}Ws6 zvJFJQU6RsX>zfFJR-}-2FRQ6?5cDA#9n;tT`O;|urozg!ls2@$l4Q2_zVt6NA?{jY z`|y1xJl{Cu#621Z(d%X4n(@YpAf*Ah7_ZO_YM;CkT7ybSlKvj-X~!FN!T)P$2(5bo zX8mUOa=kSc(`44=G}*AW^fPJT;lWAqs_Jp$3YW3mol&_iE0O)p3>D$d{*M-B_sH^R;{AdHqGW; zrEWsn5y`d*DJQ>Ei3uYu4JR?hz|3P(KXnKa&n`PITNe5NRNB|BA1@#IQP?ZoW5|ib z3A&JnQCpkz4jD@MQUz^`%tHauU3WpfLDYAsj<_ex_|Gt&(I?dLdB-2mBQ!K-M%9f8 z?vcXro2TKgP(RpS0>EEkapTmyh<^O%|=*r;|b|6?IC^)&QWp=sB~@q}{`m&prE=9qJkAzr5qQy{?db;p1B z1$?z0rbutOJ?RXn4qKw0wo8p6aU$iC^6;k&vaAKZ+hU{E4&hkMi)zURiM_o(1qLZT z6Bsv3|F_va%U*Bw;pmYr^|p^eU3N}$t+B~XY(vk)Fn-AufIFR`7(4ixClvn~D6A60 zkJIPadJgzl*m6r>2_8XC7qMGKWuTU3Iv1#TWU(I9$^?*zVm)Tv`Zox%>x|u)F_2p{ z<=c;m+u^pxS?XpipUac4Wi{_Jd9LsgRyE*`D^W88{|M>yJNGbtwTM|QOh4vX zAw@(Z{^znJ=s^LcbBc+^h!cJU*Doi^4;L_^a+5-5ImuJ6kdkEs+pQb}VP!iyOsQ08 zs^Hk$Zy!9krN|&t827{0Lh)eqqmz}v{__B^VTPzX9^qk7O)G#GsMNnOa`Ky!cXJ?2 z9Q92s&t#5#`5a6J8w8qromkVzp#{o)0f+}kB^JTC1^L?_4M}+Tt};eE`_5ZSrBL&e zE%m@J&hLN2BUSv7ri-H!NFcquLHIU0r3!6Nrwo_5euiW$S&WE@#a^xVl?05p>yIb6 z{UMhHVRO-fN9wmbcfX=@Uz8~zXmB2KZILMfzjdmpM?n1)W3)&6fix|YFXC<^|6x6b z?S0y1uM{}-ZWQf2P){eN1_GEOx9>_?>^^k1E3}siwJYx4%oLGr9p|B%HQE3fg)NIH z=Sb6p6%&8@)bgfG#PC!T5oHtNY`MQ7*@aFD&HQ4oR--y7+qn%3=h%01P! zRMzLeng4)l+2fS(k$gFuNqX(d)PYs&@A$dRopkzUU&v(KubuUGNAsaw4_=js}k4__d(r*KKcvb$aZf-S9PeuCgsqwKrW}mA1nF0Lt%NgP`yF0Fi z)mZYA5bi<`npu2H{=BU75STSKX+I?x&Ar#m+DTivpQrWw2GlUS&|$tpfl$fx*Cyve zXJEAp~q zW^6oLFCZg3@dH9xfwrMHGB+>78l;I(Yg{+)3q5V;enR;j64-Hm)z=vd$)+Cu0>;1J zE1P)ZjgK2Vf9)<{Shp*sA{C6yZe!a$kNQC)d?JezpNW?(R;Y<6?N^`Zs2omc?a50` z@uTBrK1UX0cD+2pyVZv62m#vf2XZW3WbR}p{cB;EMAcT7q?zjl?6p|t%~x)v7Ds0c z7R-ZH)ON9HTE5D(rnw`>%E)+#{>w!inZ-sUomnLYZ%@@+ge|J@ zi-wiqX695?&#|<80P*GQm^IJ~Vjsw6_aVvp!@j$+cL%eh4Yb2kSNeL8HRR&Gyter% zzxg`r2;cD4Pg}}pUscE}2p1U=4=X1omow&>ng79lZzZiDcV+f^3Y@RF&OS-OX&*`v zmyZ0th7mY#5MXB7%3^k_*QV&z%mJ(#A<^u@yyH2)g`%3H#MGP(u?=}`(ymiZ36i22 zv>QYuntdneN1#00s7T0f^2aGe+j8r-Wf`S}BI0cCjm!5a13=1o{159c#llXzKWwsW zTklL=fNg&PPhH-|i@%gSRk$n= zG8wx?JAJ>4BGlz`^2uXX(RW(!Q4j$m3V$H!H5F+~2{vJbSCer}b8O_PE$XstRrKf0 z)2)6#MqMOh%jEEnjARIiJ2sSH3%q;&+;DCdhQZA|Jm;b{rin0R(Q5h37zN6&%^XCL zMsJM-o{4;fEm9!yx3BHn%*JtG+XJ)Y1zm@aWAW(ZtwQ?R=dN%GRUt|Qn*Y@mu@<-? z{U$E_Plc8u8c7knA@G-Zn?5L{%`smTBO;@h^!ix)sk$-F8~f%cT=r$G%BWJ&`|-cc ztw8IjP!CyD*@n{V5GM%F6=g)FLcw7ktB8J*3C(@zd2|sSu6h^T%QFgL*!!RC;#6TtQANmk~~nT2;%G6^d3++ z;Fdv-*!Bb50V%0(Zeb9&@8eijg3MA#QOm^jC=Ae;l{vJ7v852bry((R2I%S)6bTEd zM>cLul-Mx%O!+GT+64?`JjNK(CB$xSSURoK(CTBfJ!5mA%HD*Xbfj39XY}iVmGP?t z452lTD^NK9^g62EiI_tfuJWNtI4sA1$5f=9sJKs^^?vj{&Bu_Hd%mn6lK&mhheffJ znl(D%Qr>~}EqKNmlaHVNF9Z%yscg>~63-B$0NiwI&&J|_ z*OHr3ie=TU-^4m5I9kXx`);}=Nu4SGiY&*YB9ODu+kLTN}i#4(I48MC!6_u4g{PrnqWDa#>Ows zt&LYUpc|TqHols7o$AqmM#C00pYGU%3q%z)@Q<}BeRy7!lf=Gi>=#>oHCb;#964J)!ipd zf#DYzr!?7YsAl=l1BzH*$Qve$7+;-NqkI&K_hGf_FqRhDzjGnzNLloUZWv0NefYv6 zvDTjDHb~Ih`&p^m5Mg`E#RprMd`tLi0B#f9P!<)QYQAVIY;~5|D^2QT4z&TNf?JieB_$qvVxtr%qqZ(MR4)Q2+ml!ZfgL7G38nugu>bDfXE54e{I~L|BN{vhx-pPOw z&fn@2iGqDqfEm@Pgzf}=TNP%4MbB@2o$n0BggL<(!P{qTxBvJ9zX^VCuDi!&{k= zEpajb7tQ##aed7h#)FrmN4Thu|7SGG@|l za-xP4@VDUsK$xkDBD*;)(Au=1zY9TNy#lU9}Pydl~p(u zMnyd(Ie5^TKeSD_yts`SjE#@qbmYR&-wF~{2Q7LHO}?4tSNzGFGvBr!;s{Pcu;yHS zPEBJ7rZ~&;^%hBCnn)qQ$1`zzZTTV5fnAj|9b+V*lt!r;n{1o)LMu@`1)gpF&4~DH zA_YhgHc(>dbJ1P+Na*U1NrY+i8CR_&KSxD_gHGlLlqsv8C4&~0gs4g*gOr)^n>Sfp z1Ou}-S7*m-W3l5G!%{e`m-bOO9~h7oe!e-KzOFmNl7>FAj8t%qQ7{be5+@l+rkeVS z#5x+4sw#G|$n9g~6_(tH3%pfun`KkJb5NgOy18^_`U%aw~#)d74o0=-4SOuRoB#%GI@11cqsFtx9QlR<&L^Og~F1p5bV_ZE- zc)i{#4QJ+j;5si6%*V*Od81;7_;6X5D4Pfs(qFxOvnFYGW0UtJeMUNRS`8)R)s#sP(d=!0iaa=&{SLLW`#E|LEuItoWaN-oCXAKz=VkUe$?1xXL> zT(92I=%qdJ%@8^B;tOp#FkO!Phwe$n=4@1Xv!SJ_Dc^G{UP_ama_aL!8N(PSmBgN+(W*X%_~sfxZ2p0~7Peu||>_!y0SQ zm`3gT8EOkJMd4J@gK&V(0D-jP`C{m6Y?R`4R^QdJ1@IWV8>yb)_~ol9VuN* z)8x3#;IwU?l8TZS*OK|k^q+X;#JVzK!LBTqktoO4O=3)1w+Mxyl<#Ck^k(8~nLYWC zaf7m7rp98~x_!kv%Z>l^J>M0w#X;5s ztUgfU^qKG(V}O0gsAkiON}!XvkSrsz@=-@1(y4{L4e7TN9rsWXnsniQqZq5L0+Ru~ zmq!_6B%=}z;)LSuhjoJ22ao@a{ekam^H)Q*m`J`Tu%@)#`JebIL-T?r&bExnjmw?_u@%*E-Cbz4(uK zLkSrolhT8V6+a*6xUR2ZuVlCM1|{}KUxt0=cP|*CeI`X2%=rFgrgYk^ZA6nezS_5@ zX!}2JhZl>~`C0?WHQjD*@7;wm;05t2e+|Egl%U*iPlB`Rq*=$!X%?QOQJb?D);$Qa z3`^gatA#`d$KSI6 z=UMWIPC}M;)%5(Sat?_#qt&ZDAN3Fz7qyUKX}P`cd+JG0wZfY|8RG#_7}c3?RZP`5 z!Z{QQKdFd9BM5WQEH=b4#<%r>c(Su|Q%P9+Fj3JGGh1wb~O&57S*}tKix1JAz|#1CmkBn!s;_gR3N>|0u>pdfau2nqZ*s=EdHG zA$i%p@(ucZh((+L_g~LeeYt?Z>pqjniFzs8d>?vYyk@=}@V6eo=tg9?!Us*WkkPqM zBiebD8HQV*&G0PQ7ei)C34efqTgQh~B4+yI`4sgJB6d5b1jG%NK3KpxY zbQ=+UL<}aEuP+S6+={t&B@GPng=y(y0*P4aqjPFV{{AX$~_wK>x!u0i|kB}v& zO5#<5!0NFCIwYb@-@>0aw@wGr@`jN#k!!zxF3UZDe3*EzPsRI@0rGjHfMewLUkL&p z4+- zk6Fy*G8Ry9FD=<`K_;7#BG!NC3w~JMQK_=?5C3PZ6YbrG%8Rs{X)QR!M$(y-GEpu5 z*PFBsf+1wDUK`f5hAaX0vCMBk4sE+$DVun?*tHch8gBVO{My<1DbYiAE82rJJgU-$ zc4wQ5=zMuK1nqZ`i><6t+5fE?{Lw#!1_=7Ocl5%Z7$4lNaFS`PW7|D4^H!`{C1Q>n zafYP~W_5VC)}L(pxA@UZ&wsyQnYf9srFCY#Y6vw*rV7NA%{lLMDMT2^J2ZxhXuaJ> zjmC9)aipI3E3}}aA3vi?1=a?lSU^_|&0v6a3Ubd6m4+o`0){U@o`?ZuPCN*~$#D!o zh>UV2t~c2eAf1|uC zIna?-#J(`43^Du22F(WfE3FbpXSwX@NJxv#zJOa@h_aoYmL`l~Mr-Lfq=>Y`L|3{g z8==&^%)SXmsKdE-K}aLi)OOrag zUXs(rEg!`TQg2J@d3owxgB*~PA1khVsWghS+%M(G3uqc%kIlmFkGknALPkx%>pa>J zbB+K-)Qr{*j)-cJWu*E}lRFV3)*GRgIX3R0xgC?HHxmn533Ph-K~c2G;5b<)C<)B) z4Rl_PLu3+XX(&(viKP6m6+L$;<8|;iPOfHmESCh{#gfZ|dg4}w$O2Ujv&0M5Nf75F ztl0hOZ7xNtGL?cd!=tEI$;5lhdVY)w&DW^^3dE04(8l^!e6ZPFF_N^U#nngabc?Dk zx-7D0r*Se&iYY@fMN(IF{HJn14>U)#$&`&+P!2P$DwSOTG-YYO7}F}@xShk<@;`O} zsuEvwHga7gPnoi$D(g}JDmMhG2}8-@-xu}jfKOYc=h$SJP>vm5Yvh`!pMUMySX|v3 zAuy~Vk&;?`j2zWl?>={E%B{&G+RlA0DAWOOS4p);p9|qZm>r%H>$9Yzedj_BMSi`o zEpXlegu0#kc++t{1{38;Njo2s1jXvFn3VpX22Z zk0?fL12eSaO(gtwqj>)NtAs(GWrJ=6LxDzNnU16FqkYRRYB7D z)M0DRNZ^d7c>F_vK7sO16S=A7n|f~p09pD7NPa?qxZRNyY(E|26?8F`VMzOVCJQ4V zTBl$*WYcmn1-uM{Ie45}2d*Mn#I7_FEr3df6wyRVFl}u+sh9yxw<%1-K6K3Ax8eO* z4NmS!KR@(o$@pj4h{`T)5sd@&VQ^B;6X{M0q)`0DaGU1+uZ7V#{po21M?#`S#qKQ| zC^#gQ8tcs2g=`spYTQ3XdT!MM_RFGp%s(nf~+B_I z@$b9>q=X{x5ZPU#PvnluaSZIDBAWxtAXKB-cTQRtA36<;=*C(88$b7BRnWkeNz87- zlv<^`!`qFA_pB9+yXM{3;o%Y};ZHmFuKh>p_3n5mT?4XucOL^x!S?3PR69kCNBDsG z5jEa$s!6$|eg4$DcHjM;sQ?jkDu*pH|46f`-$uaAE>5QX5jhUwQ=VB;zmph)i4xr^ z77_!+p$@4x8Yu$=Qu~WMWYYhM0osckJB3wc{SxgcvdLTJabKILg>&p}4kUr3BUI(= zMXdC9B0!t!%d?d2nA1Bk_=L*&ja2-VxW8X^)ZH%k-W=LmSj{km&r@03F>E>mk)6xO zosX&oJ4cZO`ov)n3fU$;ix-%GMR-^qcJ!QnT#5$sB{Ree1=m{IYp!Z&yF)loP|rVp zJ*u0+|FQ2RS&Lydf<%TLMQ<+`5|D`0^J;B<_Anr{h}^7qBN_O?1DZ5de;%;4r7Z~~ zzK0M?xYt7r#%R|&^tIzuyb&x0qr$z!NlK}n3^BzwRz(YDoWZgw@Pk7X5DYS`R-J;) z^Xd~g8De?;BE8yPHm4DfrukiBt(M2?{&ssqDDvpmy{c*?RVT$*dA^M60lOV3;ptp@F(cd z?A)P?q$^9X#f~r;2Vh_UrUR|RzX_f({-2jW= zU8rkz47W_(>f>VvP*%{79g_}bwi%;D+pt7SFvie0f4f{91nkc4?NTNn00=SPF0o*t z$kK~g$Pq&Q6^Ly&4;Q%n8T*b{0zjypMSiI;gobsl4(V0D_Pjbyg4)KJs#F zUcMBji5L&~ovJ?%6y5V)&(c%RC3v$A65_Xnk)5!bkq2<=Q*s#4U-0_i;(zAyVlv)k zREHp=y_eDoL#v$%1{)%Kusy;C$+b>6B<(0V4HgVHD{>6ADk942c#Pe}^cE|nyE~Rk zi!RgLG~3%~nKBqfCJZlI4WZ&Uu1M;J0Ryl` zR0Vw=gXfTy-4hxeIFs(NcX+pnXC(hjmPs3hFW25hPc!m;vhX!b{E09mrr{UQL^)u# z2E8A6D%nFqO;GR5@R=eN=!45MTYAZBghh-mEQ>S>PeAQYZ3ZTbQr~#z4c5xwj7J2r z$Cr9a?++sF0>ZHBv+_7YuBFT-tjq1N-OuuOBGR8vH04T``!A_HT ztug0TMJ%L7PWk^j1CaX>`>*N(CP9MCrTjeTBdRPFUy4?C>VDo|hC~b>wjJ;CF}L9s zBN`YQzggo#@%Z?m$ufMc{TUq&>jI}PvTuh7D|7v0AW*JJpvJ4V^Efv6{1|G2?w9U%g6`8GNx;#F@ko+CHl5~VexFQ zKXaw}loK8pD`d&W0AS*h$!#COc9*(l&+HsvAtQOd{eAKAunJVq0McLd@K|Wkta9JO z7SG;A1H@Dy>lJJWp9T|2ap7{_5Gs$VZ_gyQj+aeRnCph;rAOkqwJ!?P4u#a#$?r5{ zjVci%RX8pTGbn`}Rp4-;lUm+!!<*fwkj|}1fRcz3r5na1bcii*85~t2Ev?p%&#+;d35|AIhU$VelfkVKyUJ7=yxoWgOwR16?LSEAJtk69{#|RuH6YhN zUQa1SgJ;(GlTj|NmCmdddEt!pl6P09U1+`bYl%94W_}XoX3bx^1HEK@IcFXzN90@Q z-LL}Q3OPGeo5vSx1N2PAm z2fvW4-<9$4UNYd+Gm*E;VD8z9b%184#nI4>=2uXg&={j3ug_%-50H4gz)946{QE)J zHkdmV_XmHC%fgR+C)eW~nQGgWr`k@3>Bx?kjm>4iF7gTG)G=TIyMDrBF406>Nu)EAd2KAn+FR8YjRJ)2CWt z@@~S#L}2B4whcW&FcY#A-LHOHNKGavOCD6V3Rie^XI&n<3g#pRMT zwdD){c|qxy7l8cu)a~9oGT(l`3GM|w?AQX8Y4&}&b7wJ zl?wO=haXLZ3-rW@_F=NuG0`@4c9eBq4AB@OS=i^0$*rObepOK0X_eRhO7qblq>lus zwOH?}ilbIKq?b_Lpo6GczPR*7i#bujg(k)h(6bu2b!m=-p!53+P+rnHLeGF*Nw~}z z4B%Z#6a=`B8E+D{9HklOPR7p-V)ib4mztvtkbsZ8mzm>YT8daxj=h}qq{{UBz|Ile zt9#xUvg6Gy;>z3p=L`*mqG~B_^V5sixMFAmfU@HFDQW)wq-&IlU3+8^09o#88QdHe zZZXR4x>s>ZZRqyfbLQ)9TrA&qpA+(;lnuAO`y~46_HR;U2=v67B zam!dx6MHxAEzX0rIq|G~GUs?BSE4=z;O8$2r{DKNAKSFp$cg^$D$9&HzFOI`)6vcZ zfqTId)%S)i77OaP*889zfj25vL04ruqdp75PtLU$&Xa8^>Y1qE-vyK0S10!K5@Q)B zB6nsy4*XxpI}$`2t0d*WWlc3i+R?^DSlC82FHUwUM+MOqK$XZDy>Lwdn1rN56GxgcOXHVeJrpj zI(6=o2jhV!x9oFhy1IZEUM{{kwc2Kl$r11xneXBd!7-m0ua$}fWhIHxz2)(rT#t{ZeYueu%n#v(t%y4Mwv(^bG_w2*FRw@2UY%*8jtyi)EP;yh5r+sq8sj9sk^y z^F+?Jy1t4I2;)#4k@yk%k4|_7`(FKUxP%?i*o{#d)Ot40kej$^H?y*;l!tIhFRaQ6UMcQ5jj9!5cdw7)A4pvSxH?-6Dl2mWuz-s`}D{E{!J z0s=S3ehN}zu{&_sh>jP?mAKxO2nI@1^?12qyHM1W+NZKDOhalot8c0sFO7t0$Vo+%PQ zarZi#Bbnt))po*#)AvgIyS`uMB)4?yC{ULeCkHzY^rwx%;_`iV~d#n$E_>wy<;gAhA zs2X{FDe%KF{7QpC!A)AY+v>@D0M0jU!7D*&0u@Xm(n`hV(zW=Gn-nY;uHP)CU+dYe zlC4VxztV33N4ivtKLjlMoIPMDC_AtTY$35m0L0wLLO0w}|IcI6Ge37cJ45Bx?23W? z4Uit}y&Us6QU9JrH63u0QEoeE-sNz>%_g<2w`T$OM+emJfemTu9(F=7^A;7`aNXqq zdK-4uU^_j5Pt(hrOSx~h63D&15(Bg`h!*LWl4b)t@Zn1$ZSzF=TuD9>RSw@;r4E}F z4{!;n`Yz7<+9(N`w$7+jjU^B6D?k2~IcL|1T}y!xwbD^%2pY;X7KIq?*l7aX2Wi)9 zi&!1i%Ul2t4eFr<1&HS-P8qgKHTJejmGR&>#X}cmf8l4E zOE6hH)Lb@_ilwXAV|hW&bL2AIfm(ewRJh_N@V67>j`+Xz&9-(z!h=mCx9n(-I1^wU zo7sc_6sP8IywB{x7hD(pQu@AG{qypNUBcqiQfEMoeB%(g0M)WA)c+(weucU0O~$HA z*VsQhLaVxMJ`*q@sU^cO5ya6pbzZBVxGNpkdu3z+Q*m1xIH6Ln)8HGPEPx~p60Nr~ zdMop~A83wz8(SagK}pOG8>wh12MZqUL54fRmf7w(=Sjm8?Sanl*1KPv=$Bn`BCX zP`-u18yzwHlM%6(12x;=;evgvRE7?j`Q@#2D2A(m6f-+VW>s1!jNtZDl$g*t8b(zs zkbRZqx7HM0fE8h~k)z?@Yk5-A+1lM+49ZMkhc=cU?{0b>-#5j3{^IYRR-4Z{x>(0B z&EqRW3Gxt@`_odOFC|LxV4uIS>|VV33ri8Q6IdSr{w-(#A9%eD205Km^ZtcsnFpS* zOr#yWEZoGf5o8w%;|_YB@19k!(CbGJpA_^*JKW%dt!+cUEmb%xb}x(Su=WJtog`2x zkP3MLfAOK0JysesZ_Y`%)#R{e9>U0t#V>${ zGJjkV+XB4K0w~X5*12GsI9~l=e&~_`5jN z<=v?M*Zz~hbLcX#-snh2m~ANJ(lUiwwt6SpjwQ9TY1zyzW}RNgTw8IKt*joBp#X7c z5?I|_R)GdCr2OzczwORa9FJ;)>Sg5IR5INGUEAY!J}i2yR2bu1VtveawD|y&j-Wa` zS=bIEZJ5H2(2Qwn`KJO=+y;mbH-%)$Fu{E)b05#3Qnah$_r$-wizz820|kHA9C!9u zXz_uQ8_hLJbX_L3CnNGo&;Q=YrJN8VNdeI=B+0#DLj}AWq~5jOi>Pcsgm=^*3Lr}1 z){>qXs(rELN^jhLB8jqIz>p;`tp(hxmrZ$%nbT3l>ugdx2%fA?*)P(mhJ>VQ$sJdV zt>G!5_LfQWtRp7rAc(py$_Ww5)xYp(we6g&^uu@TF|>+{1QiMfx=mQ=oT71P zP?{NCnM2szslFnY5yxSs)*1~3 z_cBaQq;>@R#?4DxbUIGwSY#NT-*R(j6>s;Re^&3qtilL^d^*&eFy^OAW%29-sLkMO zBfq0}0Xni#Zb%HKJWu&{5OelBjd(?#Y1Q{O?BHP@rd=_0|p7pNkD&swxOFJt!6eb00gJ2>!vY)c;C0xuBy{~0p=YU} z9XXG?IRcr83gEq$Z$ZN?9o}F7E{t+Kw;6}Fgnc343RF-DujO_u8GAiN;Gs79tTYp<(nG=K@i z8*Uyp4bFu8)_#JKM>VZD9mkh9jI5T#EFaM#18KNUv0p$%*wCuPo7S1QlLex#b-MVz zLLr7OGA$t7MwKZ9)dBdV2ax)NT^?UxPQ7?zcN4#@zf;w%c7xGUI9B(Ct?1wd;&K2v zQ^vam^+IEyussz-#T-6Xft|jEfhz>Sh^P;i7&IF@9%Qm5YT7htiNv9re;1oo$j3V$ zYzB<&*M|dR5*(Wl;B|$$?(q%B(GcO3d6xlk?y7-#Kx+davU6H+D+4Mlunwbf>Ey!6 zDXUPni^tl$WS8i%kV}m?gKf-myND&M(XwMZEsWqvFKVE2Nw_#C)B-&pi&;mgC(UTK z65|B%>U(A>xQ&vIdX4iUFKq_j1|bw z2)_yDM&Q@Rv%ZpBB;`MEJsX2cB4pmre4P(!z4kUkd(6=KNm0Xajyg`Y415geR!sZ# z-vJLonLCNMx4rC$7}iR&@I$>lU>s05Rw_f_{{KkH_*pWX-k|(-fdB%8R;nM`Zg35Y za{pEeXuURk+DGPZvWbI-XLmg63hSt7zzTMslvq>Xs`ucU4&GNStM{7g!aiqa@zvr% zB5^K*F1-lO;+KAm3R6H&=pl5mloUPvIUt6p2CJsO(XL$~UbY{!0z;qm`7I6xxf&I$ z!SJSpci#Lq);E--W?XL2@ty2VJ;J+ae&tl*2%5p?^#b7CkmK=%SjW7`z=Pq*6<|`t z`wNBMxN!3h?vxx3d|!rPFD(KP3#JJ@lzd`*pUW9LUu{~;ya}uD1`vXhqoT7mUwid4 z(lUEkk96WN$#(>&>2)1oLWxTd1%{RaV!c;^Sb=+)+spXF08OFiF`MN?Z9LxfW)f{} zt~I#PC}35kmZ`MJa!U5o$APu9(?~@bV|5vDnq0_w8HZ=hWX4|r3w|KPpf8nz`*ve~ zgx13@eM~b#?|LTTfKkC; z4_gW`r5TvU(O0&?iD!Dm&tUTg*S<#YsaknqWCqDuV<>7z{|m(J30!^s1|(MRBQel2 z36u(&4KzL+tOwxQl$ED5wYQ>Ky_bjL-t7~E)uTPPn&#`%u&{F|VrvT&ud%SP5O>kJ zlLXb; zfb%*pmPyp-#-Kc}7jK^NVr8K9pT{l;$)4R240fzgn-}?bn|861{%(ElhDo3w_B@aC zlo(NHiQ=<-P?3PU0I(_RBQn+?6VtWsYSKgSIkJxMdi~Tm&)RnD79NQ&a$(9@?%Q7) zFa+FI0@bT$bR`uC<5FEs)V3$YH|Xj8)M#!LO3>RZ`G7Rhh5Rp!$F z@jzGy`>b9uF6)KHQI{kI3F~b?3301rdR*LW>l`cfOfG%Z_g;+nsGe0*Ne;<^IL}sY zYD%8)qzNj{qTv2!hCOH^$YiYCmI^#)ib^@B9;*lDF{v8bl{9jYq|uqvL%qNFVpXh*9tXf-DHie^DZEsg{DPx0UJ~{GDF<&5g9k zNun{3)sKM|;4Xz{H{Zhr-S=MwQ= z3oqE~is<6sQi0@pzhgAY9>aJB5kt;=9DqvM#0kVLt2D=rop}VPbxg8>dXvZM&`>%- zi!rz&7H?-=yOTf$hnIl*GU-XK?kKy%b`R!%fc*Qp|YJMANR0w&hrK@SIx r#5bDNxUONT(UIpyXtKX7)+y<9lj`}zyFt9_?>+W~RdA6e literal 0 HcmV?d00001 diff --git a/src/bls12_381/tests/g2_uncompressed_test_vectors.dat b/src/bls12_381/tests/g2_uncompressed_test_vectors.dat new file mode 100644 index 0000000000000000000000000000000000000000..7fcbcc9649d5d25489b29ae1ee99db6677147118 GIT binary patch literal 192000 zcmV(rK<>X2;45HKai3s)tka-~Cr@QrYtYb{AhsH{+F`rge^5y?Oz{#ElwDWltbGMt z1S@_5N}{sxj|!0{2n8oC#Sl@(qmtkSS6!i$;9J}7>Lr(>@sbdsxAE}5%8Hw`CT>>Z)1sRv*1UwOU^<<%#0 zeQptq$<4;vE;Smh{hO+cz0;>{LYiq&V9_j+I?2pCtfGnGlT?NXxF`V%PTu*!?SFEg zQVvIR3KCD(24blMx)5}7ffUgL9u;!N$cFZ{e#a_dcqoiV!!>snI8!-S)j+AzHxC!g ze$|FMz`2#Uf?f{c>K-0V0NbvC@k>cY(W;606%&Y!wB_4E*hYvoQY?w-u;ViHq7$`X!}GBg0N!>>y64QQdW4HX4+}L zkpP^(=Di{wN2-eGjqK@mOY^;%ot4)fOqiJoI0ALM$mQJXeZ<{<1yKJg#DI`fmhGib zVr37FB^;LPNCtD0e8$<KHmpfLP~Zwb{F0uJ~iB(JUr@;SOPn>lCxNY@ynZ2n(jr{pdR@xkqJK~SM)zrAbE zYdcuEb-C^8R@FZvpy){pA=-IWmY!h&40;;88gBtMmK`2DAhD-1YR5SK^~WJco&A)H zQvLEpUPOUD&)AX&0w>e)HiZ4R&M~+KTcfPAUwhtf;@$rGHJ^($(2AP5Q+q?=Pfz=D z3t9`>DuVJ3dN7xoh*O|StEUMK6BAGnRKAB@~xTW!lMN1d}`-6A{ z+o&NkDceS1i)<1dmosC*BT9zd7(1`=5^$XPaF)gCp{H=}oqfhZsFc69YsT#;6R4GK z)=vbOY3Qeaenyi5K<1cAz9#x=iDi!ni?xF^+Vkwerjt528z(TjN_a?EycUk{=wc z7DUv&u|{+xax};UoYC8|(!*FDRKSQ6U+*?hT4*EI8-pSS(~Ud@?D;lSx1oqum8$Ny%JdroIow6z0xRWRHxU~0uFm*(nPZFWS1ssjbT^5P z`fwl!x2{dUfGiiLWQanNER<*DV5c02)06Ic^H$mtIxjXE=nOmD$l|fuQuxi86U^Nb zzGWI;rXVy8L5MU`Z8xaNGG!x0KnqOS)I5(tz<~4`ZIxINj=rm%LTI~l=uqJT6e?J{ z;&oO^ZDY%8)+TJGYwri9tBj+2oeQGW@B7UCiNR@PpM4IBKClLaWqGXz{H(L?W>BF7 zNK)1uoFKm3j_C@mm)?#fX9}M6bc5!2+Y#NBLWva!jdtSW<%CfkW(Sry13rSVd?`dKavNZ{1@KuX}J@dtFpTAqW8 zu$?=ug|+IYv1<=a!Zkx^5q~$C!+$? zI-olDF-&avfchidJGk33SZO-YU=jAFv8DeJ->;H^kR8)j{9XReA&H8U*{z;VByHB= zjXvaUSgU?_KpH-c1O7$`=YAaaGRT3zd{c#qh6iE~wq^CFRh+Q;695E?4sRN@RB|ThZ;o{>(L5;aq!xsd z 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

3Zx=* ztG5KuNlM>$gQcSx+5koUR|^L)so8Y}^h9G7zZIGVlV%}ItCRWnK9-^M0fTBHD6^W# z$?LIHi+CK^_X7ow>IHiIdcQ7_37mPWcR$Led3NL|9D~>$SWQWE70YX^=t-Nt7Uh&! zJow|YadEL!!*IZR@~wEO=;fX=4fcPK-TEbdS|KQ~dUE5g7zO(*@(?zg5ciWa+N7Ii zat`XKOrfE%d&De+%(1h-wgWH1#B(7)3r}UEnQy>x1T{TJN325s$_7t9MhMEKo8{zP zG@)@os2mS2jS~bE{7zqHw!1JaI5%=!7Z^%|4@Z-JDSWQGasI+uwa;bfwv5l%%H4U; zqc$VrfSaWzUw#nzYanrDNRL&$fGZPe7AT8SQ)d$@D;mHXGi;P4&p0jHRwK+*2p=u; z)8M3?4%ev=#*tayo6esKoukp!W?R#6b_|5zrY(J4ssWwCy7QDdl z){*4;tL{<~7Zz(MU7QmB>-QuArbBDMq7z-yOZZ*2$)c)F#-&_+H5k5BXpNooP11tz5 z*QTJW)q#_H@sPxbBeaqD%^vmMWESO>I|0A_&V*XE&!eX`J~)vo0Z`W`60&OFfO{z+ zid{8+Eg^j#sTgfpV=!+?nh3n4)lUZ$UaGF&>||73(|x|n`h_rW86Copi7^YZA`ghc zPI&ihLKQz51zBD?H4C06uApgjrh7+lNrW^`Dn)NdTr8|R7OBvQ(i{-A0M{=c1nDZ9 zA7WIy`YWi|ZUUq)o`WC{1xs=t5YYr2RZl$ps=GRm1eR{no9L*cA7nuY>dY0q)$gyI zLq~aAxiS!AO~9anjB)n0m_%^$_O&i!gfF)C7fFNUB8vf!0nu)A@xAqDnOcmIiaRP0 zmc9GsmK0^~yG<~f27J&EX}OYV88c|t5Hl}CA&>W0kGony+$n>-Ep}$dU2JDbIw470UQOqJ3c|R<9IINUcZQtPkpJPj9Yga$ND>|0>;EJU7hD!WS#U zYj|EbW{`&D1p!JOD`ZX@7~M_g6fzH&h}HoA0Gec;T69H%n{Dx4N${kt0tN{^O!8az zNv4nv5#zns6G2Cjj5VMOClKoo=DEAb`-0NLY?ncvNbV*-kLDU;V@ACqePpe!RA8Fv=M zOjpE;OcsO|-L#WviSeusUZ_tpT=BTwKY8d6-OFN@>6u3E>tx^?#@T*61$Lh)XeQ|4 z*Mb`Eehy5__o4N&E2=P?=N)s|?JV_59TypT>+c5^&fELzn<*$t2TNBRsEYYjnIzAF zL!5uIt3g%tY~&D8?2sTSuoGBiHD9 z^hv~03FbABdP<5eOlAN1d`8eX&-FT)O`>x#3dmSy32I&w{XV}=6b>f#-TKC6QQ%Hf z4q!!BD$2K0yBp5=kp2|3LTs+&5Eid#(-UK^RuN!`H0!%0lr@#bk zfs6rS={IH0CBj=HL8<$5yQd87AZx`yF*_7o1bm8u$gtJ!0>G4tJ5}s;j8Z5E%6KzShiRowAOhg##s$bvnzf(Q>QfSqQNhJM;#DF;K zG?`eu4z6LN#UBS z6}}@QK2n?{6Z&C=8BJm^un-v(hhHu$p%(TfocMo__)H!q@uInJ^lOtI`D^%eFe0TMo%aB;1_y3ik5G^pbtr*~~q2A()z)^#%_0K>ze3=8V*=$E3cy1@`lN21FuS|jb*OF+6^8sn@A^O3%?7~R425=`T!?d`P0JFh`-Mu+Laf|%S3DobP z_ULg^lll1r;R*wHvB;F|D;)LTz@;Ze3(oz#r2oTdfNs?2UDLAoT1C!QL4udG#}IQ? zVJ+X}O6~Z~!dzPZ?KU2btzV$<2F@#-RNEU7n5BVn_8KBblTz9<>kccCdOn_E`rtKF2%Vs(>E@km*1%xv zt}$FT=M3VV5+9r0ajgVJX6kryjpFnw`#ctJLsBdUs5$yxF?A3r#{QnP3Cfbf`+ni? z48WNf1O3IZ2OAI8VwGPxoX@7L*NAs7CG|E2$o!hF(nY`WrR`c%RVJB$Q;=W{fqyhh z6^qq>Xvi737eh^)uaFl%`d>t85P0`N%?dvN$e0~^;;4s3EifO8fif>Ox<`|w1){An z(Q(8{z*cD;%jb`8uMd_;eD*-wK|$GStF2#KjJyG2>76_C+2he9tZHd+?prvQT7-nw_#y;;hEF^~DXP%C)4l~zC2IcZ@Pk|)ptDT-kD?ShY zI_IXNfCj^Tv-~IHkR_GR0;?=%jypP#GbL!S5X3QgG_HC%>Y`d;br3a)um!=O|CY@U zOlnd`{pTIvMB0Ck_ILR3+4J6Hqv2i>5(cvv$r_P19>vnu-~Ki(C^lVPQ-zOijtc9p zz3>{2+lFQ{!YhA-94v*=ZxZ6#7Mw?SZ=R>6iwU-v+A9?DqI*nqY3#2qGji)*^IWXr zSZ#9v3s)iV}Oig1Bb(`@A1|9?d`m{5w*|A!NMdfG@lt@VSaT2fu6{m?Cn=q z?Qk_a`T{%g_iT=#7cNpP8i()S1Vy#{iMXp=J3`^aIYzuFD7hxex6H(#`+3=TufCU@ zRFjTTiF}C98G|XOln9l&PUmua5c|saQe;eZGM)zkp7E7?`!1k&?skl#!SCv!-5Gljm8fx~Xdoa%4hn*pr~a9 z3a>Ui13{uL`j?oKUPv%srJs3S2S1r&0lFLaRxsYJI=k?5OC}?}%;LBLG$l+ZlSDL| zN^5njHgl$MLrpK+ycTpb!!&~73i0x9s=*_X2uaR1ZF$Sf6S%B4bHUXe@ZRPwTFn7# z54u2Ig(4i}_u&VgYfp@M3<%DSAjFg2o^bSKc2Vk@x7Lkmo@^dtd0XkyrfeRPtu^@pK35If)=8Qv0@cu* zHC+q+nl>SexF`K9G43GLnm9Pzlk(=M5XA4(xG}-2djcKc zzSS>XLac2{v?|UCfU)lqY5ew4VwamSa&!NP6iD6PkU{!}3!CZ{IGuD3l zi_P+?T}>xhXGL-tE(^O5)|W^?0YEolm^dK=7HJ5os6d>ZG{IeB(`6bkl1W7{-0_Si zUe6@5jJ2*Vv^Mo@v}U{}b@7v!KaQ#!270Y?We^?NYs33*t>y4K=VI4Vz9r=D629<* zF2jhql{E2W@`Iu$24#adVLfxc6lW-*T^uJH?k)OzN0|1jI>K)QA=uf-^-$YrILp|h zRZS1Kr=@`O7q>v?i%LjE75;%0wi>);=Aq~39wHXY-ba9>1M057k*Dkig;b>*;m~q2DS`QjsYdY<* z8YYGQYv0Rg1oU*DFz#hL2FjD%kZr|}*|u4j)!Z|32VxGvRmUxg@9h9k;*3G@7-l2R z;{B3$1>b8oPqC|DB;XXl42lJxi1u>(=F*dxh4uk&nNw&tJg739s2gG;7`eqC6zW`z z3Q2#G=L?L@DElgh6VFLFqM}`^Qv}0E#H;q@C1^L);VU6-C}T9eu!qd?^TY z4sN4}2mmLxQZQy+vDRsg3h6Fb@)AALqF6|kxaq${4PIkp0K3S8VrCd+wi7sR;giq^ zH5xJL02tm8>`iz=mzcAEHmDq}pAp;9UDVd5&!P&=2DU;);uB7M>vJQ9k@)QZy5XT< zvU?Agr=>C5%0B7+?FOdlBtqnnT95y^W2k4d7M%>iihvnI7TvyBx7`VSx!Qg!%jm#U zF(T7|yEvIzTU3EbyhuyKkBF=mzTHSy1xm2dy8-(G=dk;+QcBs$+V?}V?%4Q#B>FAc zx7;>~5{%f3^r*)gGME#5eIeYI2L8v;Kk!zt53>v(`=C4VfoGp~RwAFnvd(1uS2TK{~o+&9E*dx$szc%Bzqemoj;PUcqKJ|`%p1N4{@+gCr zzL_?^5F=lmY(QF+5BRv5eDS*}3ga}FKR<>jsO)h4p!%3yY&Lr^xri}$e9+Wj!c#F% zZ;zG^_#&u33d(IOoJ;OuJyTDa!-mstAZ7S5JaTXP{E};35QoCp_ZR7@Mzu__N15V1 z<88SC1iv$}R4eB@{{lz5pG|#&_+*8DCYm7z8c`|BYnMJCo^BPQ($ z5fc^hzwwE#qhSkyJruQq=+3D9O1psran5BbGPA=g%?SmYj|!~(F=1eky0L0!03se^ z9bQLVF|pretdfx)izjqv=*KFLiuQ^o5jnP!ZW;2YCEn{W6bSqadTm*c1jx2#!gs;! zg1x>o(7V?*{7zVJ1nI2jJM%%fbEIM0f}<%N2%?5i6dk!iEd)*ZM$Pthdp$S3k996;% zgiS7E5;{;!*IIHS>Bm^nbF@Cd$of68#+dfY6kE$?a=#{zs^&yQLK(LipC(-AAJOjc zO&34Pp^KycQA6ndTgS+y+Ug4i#e=1X62bipD~-{~az_GgVnN9_<0aj6&@sboSyHpN zmyEzH%G4dF9HPfe9b@J!Sdrg#5dcAsp6Nq;wo6DKQqG03W?A+x1{f%Y?@~WP-2}OtEGLa9a;wxu| zggMUCo9xq6-sX?RNJ$3J(NRszvc75IiGSAd<1U!KMSI~ol zehH&J34Uew-PCpjsEkrxIY-(@jQs=C5mdq;KV}whL|B|Mg;zfU9veg))HUWDWMz3b z3$~t+nD{aIN5^vbGhKn?9SG}0MQF;7ADhjmbmc%3PsS#K_6SuKgeY%D1;70l87Sv- z1JfBL{6)qjWK-!Ud@1@00(P zH;(wMFG47y%arQ)oK48V z3rYu6hXEIap|eA&8;%SG(dQILpL+wz3Lw{ztS?yg=5IAJLNhMxb9FrS!}T5gsezB; z8;ET?a(+@a?e!mb^Z+QBAB_%F8UI~@>wye9;eVKYx~%nMnLGGsSfv$J)vk>eia+~) z^Lf=092sRf#W;LvueQib15h|wxn=2Tfk_r(j`d|y^MSqSz5n3%H-KRHDezjU@0mc( zb<1jom6?V_ZP5_j)1MANJR{^f5~r~DiP7+%1RXby|O{?+ZX<;G<*awvC(8;gAQ z6nJX(Ls6a)4qsam!9m3bnqxCq^#$+~LmnBL!UW+{1#ICWGp9%K#GO89R)Ri}q5s!Q zc&$H7+F*KysWPlRog;Om3 zG(=!|km`uAcPmWW4UUc8ir*HyFvv!P5qYlj1o_=0B}B^1nY8u8(;ZCYzxQaRX`Xz> zddy9%?mHK*t)!~L{i6@#p5fBZ7_7~yD$U;5^;2<@vDyn4T~M+tOuM)!IRQn{n%znE zf<-PT?DuoT;7lu#9=-eV5C=RM2zh{`xh{aQGlYuyXYDon-!lN}&ivSm-zT9(Cwt}f z#i_>dC0&c3yaclr&1Q6lyj9_FV!zWpZ!n;b(>`OXpys(xYuIHl)hNk^(w0IRQ%^B~aSdMNhZ8A@u^B~2+=k@|Z;{FIvm}AeY2mHN z6jReyi1iq=s`s0-KID#Qt7kkctyU~JMF2daJlSU$$HwValhcx?89?uViIDy{UB2bxuV34$MT6zl`~=Jtp~ncazvlte z8!)!FCwYJ*rk}(?G;^M*U-{Me%8d8XnR4{l;$}43KlKJH$h3*C zrPpnb?!lGCr;>x|3(32OBKQ&;(z4Pj;(iKCrp~0bu8E_$opbF7Pea33Pm#{iS%x zVWNGa3J;KPvho}1kgyMW+(OZ0KCulohD{p+PeJKaEdgk%+etE?Q<+~n0d;Z?y?67> z1*=knx4dxb$!uikh@PY;_#%3KpAbl5aR=Z)hhTi~?z2SW-4<0`P`us6p`4`=zq z!45f7tGr{vuo0EcNrSq?oSNMVtH7~THI3TgxmubAt?MZJnV3${CuJq`4d$AmosY(> z_WxA6qdeWm4X+gWMy_w~a8CQQj{Sr%J!+Dt7*UF+QzsO1z|W&K1b+Iayu|VFp0URn z;k?>?_?+q?6|Q&->zl7OpMRpT{;?#{5ndXghK_2=!R7T63hoCU{g+KmTx`uCRJ^2r z9HS;&rsEm5sC^18LR{1kNuyuX{1!llG zXHFjL%M=*F+#|JK8O`hGt2AE>?}w-ze4fhv)w=qv@zXSwg+i=uX3J6A&*nF&qI~ko z>8&#y^|zI_2Vpnetu=X=*%f__#JWEsfY#(*bNs%&Zv||J|Av36dDE?FU%UGH;uZR1 z;Arxb6pi-Vrd-RhcS8V-l^JzWJW}ekF%SD@WpuQ8H(262AUk_rVdPnFdL$=e8dO?$ z2fexhIcBOt3rUF{Vsc9cSGzB<3%O>-!-GpF@{;tk)q#Q<82z55IY`cq znA9^ZunOdT!hiLK45?;ZRoLg+tPItgEnu}l&L$)QFLx0@m|Qb65BdGJeIa;;b(_H0 zz~uZorjowYVWLnG3L3x}E6T~`0jv!g;A2^(bWy`|J%>S(8RSF7u0$(=maS(vqpE(l zpEGPHl7bPIB7{4xo>37-jEf?R%fKuO&m*~SHu-oIwJUa@B^*JqsR zDXIbt&<{6#6$NkD<7kA}nB)ux4*pzfkd#cL$cH42TR8FZYEkOQ1XU6nkDs%9izOl1 z{&_vzr0VGAi#^>ppDP_c7-&n(_H_aTQpwm`L1O(9v6#u;d)VPqyg9!G5#EvetY%g7 zKE;@sonxl!Ra?S`8DOrU$`OT{WRZmUJQ6`?u- zW>EKVWYSId4)RMh^>x)Ysffr9LoNYoEpD=gBS!X&98X=t;5brVYxwR!vtU?pT2rI* zuB$((3me0oOO!mqGZ_!eGrXm2xs|sHa^q|Hr^8;@x(;hV5iO#g0WPleU<*aPM&PSa z804gR8WZ2HWTS!-2ykoRJt%9C8;o@%5v7!J*G=B|RL$57(w2|_f;O)fLp5WT0GNq| z&Mo8AtFI%YFE9IN#ifi)issIUJZoP@-Aa|C^@$3AJU^>Nh=FfhN22cNyW=d7=6@bvy zbC8N&P_@rH*ol5>$q&m;MY`B(1F>@$?3ZW`f$7oO_;%?S1IcyujQcxT==tB{Uotd# z1~`^Sa(9Q{J9h4{hq!$|O|dBUMuj%5DYk5tpm7o`009AWF$8UF2*M5Zqm84zpZ&zB5b8l3wPW;K@C{2}LI_1Bp( zRXnRKD&#+;Pqmz7& zs+l*Go28IBXd=wo_n<(-7%w{DqLj0th0H8`x}?L`B#=wc1p+_?rvYQ)~G`C%GAg#c+Wx5$@)eZf{%xViaTLEm8L2? ztMNS7&4T=|3S%Wgi{PbBVn7fN9zrrIzZZv-|Jm`68v;!8hVm#|wNUJW4RE7vtK02d z(g9&}10aI(afzD%DT#2|E@=P5l8eGiLJ+hAED9Lrs-iAx=``D}^M4~1Fy=eEh^Io0 z6BP1U9_O-8WlmM`4ojdM?jo?Pxhh~WVi&J8Q~erZ8RWDJq(6a&UqZkIlx7=V0;lsk z@r~gpTY#>065Qzt5dC=?eJHPaK?J$t-KfkN@>R~3{03h^$pLDOsUu&-o-nIr2>GV|)pYd~W;A>? z`%oPC|KZ2eZDTGLo)J#}Jkgv{33ts7T4dPhLbvqN!IjUBe(Lpj z?=PaZrdQQ9hiBRJ2euwXJ@H6v*Q|?{vN9@Jk5bEy6C-EtYUL8SwG^th%m@f2g|`vk zdQx0wffdiM0{0_dI2-7N=OyG6nK=A*S`kow8J8|T@!h_VISj{N;`+>*?OR&I?)5*B z4q*>}+iSiSsG!5#Iusg;!RCV~vp{d66bLz`JWdNNHa+$7w>LiUuYffWH+FP7I?3pt zn{%X((19CebncbKtbcNotm3d@3IMRA+@U3vZSPPqJ6#j(1q6YZWOD6G01PC&2(}>H zX^x&iM{!=^%oa(!FLYvK6j-Uy@NRX=$pwaAny7JLccjovxf46j_@AKbj0uYRpB~xl z5I0XP>rur6p;4XQ281p9Hu(|;PyITqDa}T+Z&U-5A)Z6PmhiPD@nJ(=@1%BJe{GUH z8sQ`F?quEB3YbQ=UNqSr4P^<^Z82Z~RqTBl?`Lq5Q$lEeQI~9E6#*6{m-Nbe6DEfk zCaQ8a3Or-?dE7d5EM^qNA!!G?y$yeJ%{=kELfSnFSD6(R8{rLQx>f%itFegh?gCn0 z04$KwpvN!?icZ}SGAtCMR_2%76OZ{Z3F)0^7mWAwJc?ZbE#^uRVIJ@476pBw? z^d(M(gT5C0S%b9DnU~D9m2ZJidno%h=^Nu_;y-7Q>Y1UG3BH%I?RD$b7NM;d7+X9M z)JEjv-W9xH=|4TVxdC}CC8_k~(wF3`p!?!-4A&LXfCnA%Z2b{T4{Q_$*@uIBkaZCB zW;*ij&@x@{l^@j52aYAdg+8YE_iQWDS`}Y_sp{@p%st$@3zi-iqz4G1bEX;g5D(AP zDJp1J_R;WBdrqRutVYK$zdmS% z;-tED0e~1hVh8kmFXvU*p$ZmDezA$A_&Dw4(d~?fJ`pmz{C>Vj(F2c7!GriS7Ic9A zPGujo6*&tj)hhcuPfK2eV4QiZK^AQFhQk=zeh~5$p$%|yDp@{kVegBzlyHAv!s~p)&ep0zTHH1#?@4< zy&ei}jtWX=*Z|bP1s$9^qzcBTL5^n|ELlWvbDg%{y%6lSt9FCkSPU5T(Ib2I#zGgB zx6#gjQ+M%8(})evlmtz=w$zDoE~5>GZh5~ek27lgP?MD{?CeC3hKpti>-oddtwU4Y zq#Ak-^v4-5UIB!Ix>h>z&USr7rU}u4JD+se`QQQ<)9HufinLc!+2Pwdc?7mY zw=wNE1Pv$wW!)Syb%{6}8a?lU%^d5!wd^+wiW0XYcFb5!k#6sxT3yL>@lvNxYwHD) z#1(4xb!0w|6t|xT6f)MtC8saCm&?c913`^ROvi^RYC?^qT!0bDo#HA--{T0Q#}i_J zgZ*HnFbq)zSyg?j2@MGn_l@NbF;N6G7B zcXmWYY#Qz~7~@Y|lg|cC;In827EYJS6G+PaSfirwmLBb$XD6jm2n0Q1_ea*zG5fdX zcaNt)SSu9J$cX*?&Xu+ICN$w0;p#q%!An);CS(CzGt({%;|BY`TpFnjvOB_|8A+_P zLF-gAtM-nQn z+rbrvKUR(qwseh)qJMSua9R^Zr5QfeAqG`{l_=2&-&{yL)s3D?CVmXr!hx&9K_?gz zV&7+5Ab!`t_1N-pa}_&Q5DRYYDAvzHy@K&x@pQ&Z5F(TtwURe$_w$h~Z1kz4O+o;L z_&|5E(%x%d8%?(+wcj3*ULe95|3Mjg00bo14*prXocEav*j3C5MEh7M@k z+;ZU>t=dJvnj0+wASZYI%$*J2h9$~M>Fz~Rn5WHE8Is}6@HHS1v7jrVKoQSBRlG@_ z(ZwzD^CD<IGpH{dhGydYJ;K zt9QPhQxFh_kF4Ie7VZyWU1Z&9`!?uBHnuqgQQaNV$z7L3st0%J@qDK1x2XDQ!BjZe zU{3b8I_MdR)wr2@y=_ngebC$GEEMilO+=D3!r$x-PXIvUPZ7+1SKbdWA{0ddLVYt%TonK=Sg(fp;QUt0gvkDKZHOA9q!OSDRkuPS06GHpD*##J7Q@dAG@ zD+~NS_=^-AQNom-$Pc%aM+(Q8`=^yaSQup6Y9O{J2PK^V;oz_AGG=RuUmhLFP;n;U z($)GQ9Vcu>)%jNNB{Gl%WhuDu1Qm!=J;;b?%dgQ*Uz0FbaF60vw_iU3_UVTfwzzJ6 zr(2fbBwK@t5bEglt&M0`x&>adHt@Y%50Uoq{8WGoFaE^eB172*TRsP9wVHuQ@E$fc z*$NtCk?HNasAliP?-+d7X>NH0(Qkko)O4U0jTo(hFNn+2q^od)O4b9GYbRWuZV_sA zg=;n2DYe=rQ4ORGgtjwnh<)@TIoQ;_E%Z&)yV73S-K#Y~w@?fnEn2Ne~Rb zujW>vLkLzy@EdmppX~p!9fOrLWrFRkGmko4Q1ePc$@z~o?j1{>+o0~lTGaCcMS*K=7+2^rzB`uq&PYpTfaZIeW%Jp-}pSv$Lwuq%^FW9zMo3kcC1 zhUpyav!Ljc!9K7L)~y(vwTC`xW!LOM!3F*!lng0dk)ZV*;uW9`>JUvL(r#JAhb++J zS;S~10J2GAZd`_z_uY4syYEDtD@rn!-W15{)tkLMJ4ZHLRK1;G0TSxGpYnL0EK{er zFAv9|#}j3P1e696P(cJJJ140T))+pHYWR$<%=gR-&ac8tFzn{~B?3PMi=!DuMK}^) zb^|p$7z~mesN@4kttqnD?mRR(iyAV8lHPB)3s+ zNdMxzSr+j*oh~-6b-q|`o+hhVv8JOo2BYC*%HHI72*W!K24oUCE(ptTlY~>da}g3C z*#`9V5yb}bB;sYRE9JtndA;R3L7N4C5Z%&0sQbDRN2N59Xq8LO?3a(swFvoyh&{F% z@ma$kirf}8%k3ao5Ofq_^ph~BWRYiOvT~{HnZ!Sn?|J^O1EYk5I|eMc$fAFA_U~A& z1D!t@JHE&~eXkdoSPJq8kMwfCT3iw9m_eQd)$WXHZ}*U$>khh?^~H}B+aWm;Pe$>3 zc8XFBuCcF6!`F2>q^;a!Ye;6_b>{N0YB``fR8#YB7yyu#g#pA+ev>65#tSsa;6Z z`HzE@%QK+k!ky2#2s+d0gRmM~1FL+ousXWb5*V-t>yA z0L1$jH=!X(NZwe629}J?FJ7|WxCgF=@_40;fX0s6i)MSPXRoK~tLw!+bXsyy7=wJo zEX#RaqchXiK&#%3ncA_gnh((_$)4*Sq zcvUpoRRTVtn#OUuEP}Ta=tbhD=0UyHt2M3FUWDTvV^z(!c~nYu%x=_GcuMA`EBUi; zm<}HF$u}L2L{?3wJYj$8;J&ZDh6Y{h#V(BQrG9G}nS#b-24=#!_6e&%=k!TwV+U%n z<>wZl4+_Qc$ys`sqLsU}26x&hrfIN!Ykvi6gP5lXwPP*zE5?tlSwV-?*A9gKW(mI7 zc6EpYst}A;R6xl*>DyhZd2w@sHB0EHqL*=9%HzE-M9jM)a(e_d76?m^@>uGZFl8M z>p!c_Nb-Fx1j@b`nM}2j@=*=8=J%DanJM}378#w0tuSlWbl@- zSHUyYkiD$up26fnSCKaj!%Cj>%mYY*8xqil6Zj%~Ooff%7oa%14j0?L!^3-%uWP2< zwcRhNr9oT)Z4M_v;A~9s59Id(!Nf*<}AdN<;^0k*Oy>@sp?VN0!noF8WaXcq;t{by179@9&zz`jQ zOjXZk584QD6bYq!P>O|*jwVqySztVbtLv3cim)DF`_#+ka0`8geP~ctUPR(r<;Lu; zkWRQ}EevtcfM;MeTEz!m?T88C&$WoTw{7btUz_?t*6ev7n4{cITx?ePaJbH75_3)v zE)Sh&m~Z(&YX#iLu(}u>y)zT6FN znXNzwc+;9<+9FQj&EvU;=VZAM6iy%m=U@HYO+Gf{sWM2zS_uw&mI{{l&3@RtXRsw$ zv4E(IIRUT4tA=^6x_eKj0;Ck7N7(V2Rtw&Qp$#0Xdi0^AbuAh}772vwkQQ7G zbs-d*4xn{S*({!sp(r3$ZR2=gqtV<%S{O*_HhT<<5=bE8JMCz8vNyjV6IJ%SL(g z0YiQ2l9I`dp!f^2)BcJKMG5_V-5~!EX?gJmh4+-jK{WBJ{_$$ItFDY%YMCW0AZ-Po zV4uIOYyAMlCcPJq+6y{L5yrpI6}q z(RPe_njSCl-~qI7@e@5lxd2@*QGMolgB{LF*Ib-?l>KGz6-^pZ6wxu>zd6W2#;-wc zMJRg&qzyjYr}OA15RuqaYPGcvY~9A?Z^zQ$*KZ&7aLBuKqnq?_`CnSucQadm+nbx* zxeX0fV+cG~rn0^;(iqFgCluy@@&)5}WU&-ZPQ!_}P4i$XFcpr+i+%7lWDx1{It_k7 z`t-JIEG7NKJ})Ltwt59y#ahg`eSF*RhNJfR;QvN60W|c)))YiEz%j`!ViMEi5D-pJ zgH_pLbloHLp4iTPJWi2>veLD6pUIH!)MJRpy2X4fp&z`CGl8O`}xCWX0Gn(PpK36?YXzZfZ zx|Fd<9YfRY>A+Et-xI#PHFbY)xbpYViMcsPZ&6iVHWQ>8eBpk8t2c?}LhM$8+vN*q zV}DeXCuw;J78Q{$3v?EzIf^I>eWH(gZ(QeG#cX4;AR;R5$B-LW}03Vz(rE7z!uj_o@*r5NiMf-zk(Pfl(}{6p>;cpd|GsECBWqICw8X7;`!mje5R zm*qddZ=~){ChvRfRsdf~amEWJ`e}^+4Pk{fz2Gw=yEsM-?w<-Rc(JcCggyQ-XXnZ9 z_wBur*vbp6unGikd4I!YG261PZ=zFw(kmHVH0A)2|ZZ-`8up-Fn`a(bCtI>J|h4ehBhSf4K-?Je7@$)eZa# ztT4ai*((A(y&D`nSQE1dZ;&<_(Qc_!hM<)2MZfr1GFmZB`C&SzaMqT}trxf?*T$c*C$jWO)Zv?*< z1O_xT2pPWCv^QdA7N|Pz<7OG@H3LkvK;Lf%<8^pAUpVHGjo6|M0d;W<($fWi?&l!1C92@k|G_oJQg)0xqoa11W!#EtRlcD$h1cj=53 z_v}KrDi$iwKu${GQYDvi1q)77_z{59Y>A>s^m}v|o{19*sO5}zv!zLGHf3^Yrn(l- zXP((S_zl6ga$0zuLF%wt0SqO9*VAdR4pDuP)ww|*rs)mYgu}xUs>1li(Si1+;)fCC zNy_-XfZug|dv`MOrU-ZH-)sXjihoKL7gE0YX!=!8vDz#dh5-%~BO2)y;3mm+QafnK zuDoJ%mxFtJ3;?ye@;xcwJmmBdjJYI1VQq%cPgyF3eu;EulunN~jxZXSlYrmy!ssg2 zbu=nAhz>cL##9oP9)dOB9&?M@;L{EcB>28z^`X19x)Et$`F@w5V7K&hk15iTcVV&e zpaI#cj27EVw{$#rRzh=f3<*5}Z`1@-1(7o8hn9t>Lu`1~RN2HHgQ=*kks*L!K0KD-OO@ECEF}NAZ%0QUBto#CN{w4z82zTl5g|Q zv&(y+ZkGQ*oRFz`bya;Qr)fkvynSu(KX&Z{gYeLB@ccpbfbRO#e**^tVFaItpma*| z!~Ods;3=zoSJI3xmk45I!F2c?5-Ha1b9h1^5!pHcMN|GWsR^L#pE#sgE}{segWim_ zgYl}pxnM{NYx%=0$HiSu1~?IczuWhx3M8Ku3TBb*83j%t8?_5in>1~$p*c)11ZfA| zb7Cx)Ki0e3aJsfBkU`kE>bpw+HbgZCu;hB3ZVO2#3#tt3X)#oa&WKxPKNb-!mKmCKJ2sD_puA=Bjls{k($CW`4J;#*crwFz->~b}Cf( zII6xFlthHUV8~a@SPTxA%ozkk%U*`8s+z5szj?UVlbcM(wYz4HnZ^>4gnnRIUb@^q znQ-^nva^iy&~Hcw2MEsx9d2z-dKUghRqo{I(xB%*7R#ziHIph*a8$Qz?b@olRPU=u z5&ay4eOW?)cohRt##F8`i9D^K=z$utqctuwdRET-cy(BdoE{WxK=)C1^L?e&7jy|( zpVmLdI1&j4laWo!P0&jB%b#xCcSBKRY?M}-ebhZO08LRfeA0nZMmhC)Phv|FXonA1 z!w62BIU{uR^Q@;UQ8yi0-oCV{v>pc#(uecDPu%m*jN|Qmst*Y~d4o6PwOdR!xe_f7 zxvu5_GgOQjY(6HZS?TNk!SDP)onxN1OziXay_=(uP72 zX^Hk`3+A!IIo5SRRR*K{Dkgz!Qeu%~0g33aTiP3=P`*b@=5Nwi-U(t5V*@|{)o2Sb z;LIIqEjjTT+WXQi3Y4z*UAp7+>%X)mL2Y_}h}i3n1$9vmJ{4gs2*PmKr*rln??|}4 zcIBwGVHW4xbmo31Q7KS%Z3DXJSg@ju&o$E~0Wv0ana zV&|9Xq42VHyAKjOx|-lo+H;&3Z<#vpO#wkmQ&zKBQQCc@-$7!MDuBf0CX2e~Ie;&O zJdof34{zbSmDjqmRzdq>>YPCJ%^4gHX9XX z&>KbPItjdF3&)*Mn4lVoei?psRWd}j`=mE&;AVQ8HeOtWFa#!Xg@}yVRI)j!de}*EI~Y(&AvJlV z%#0^>Ewy$VOsDW3k;Hjx9rY@$7$eGBINg|D(r2*?S5J2agSKh%y%sBBpb>328O$Kw z<32nld&?hdCOK_DFDJs75`&ML2Acg$UA9=TM0}U&LfR8v4<0E(Y@#$nP zPkHZ7HygLKAPGTzL-g-!Ltp~@U2L`gnyWzr!yBRhM-M3J_EqrwTi{-DExpQU=Vd?A z_44)CI(f3sviHkgL=JpP%YQ<8zZC!T1GHF90x>-~+q~lK0Z(?{u zV9Y&|Ei-i$q;1trv#ge0WH|n@fCUQW6a)~69T{6iWce1v{tgn4R-(=no=1gBPA8wK zg5u9Bj`Ah49e$R-F$cLj=K*R}S~F^W+q-~P+=fK?cf3ch3qqV&f>PFf&m(^c^}@&) zj18U(AT6Ff_jw&iLIFoc|%TY_fY3b_c2CPeK!Iuy6=Fm7%zqIX_Xe7Rleu%ixa^ zt7G>20~uAw!uhk&x%>4RYz43K%a{y!qUe4fs@Rw7#pD<(4&$=eO}Y~>9^8pPSvXVV zei%ztxGo+ewcTD8U%yK|_@5oBOuoZo%IEjV%n)O?Y_{mpE)#q?QI#9kD3=gRV zo)(+K&VGH~M2l`=p`piQ)+P=G+yY#usG<3V(w11cw7? z?cgB)&L8TJ5*mImta?5k#`@*);nx_kc@Ig&yxJ3DAQ76wlh3Ipy}`PZ*HWZHbnzgxz!UC^WB}u-4WZSFbxu zSAI7!>*QY?>t zbQW?nQG-a(>t#h!&Lv}P&WznuHxavPxgBZq_@8+=>+;+e&P(ocTv*aCxb*Qn4+Zfo9sG^bxgc8RmY;bke z@KAn@f6k8%97JNke*nx@+_#fK7dS&ijv*uXYaV8IavadRql?i|f6smK4n`YJiW zs}k9Be$%JK-4PRh(`Tg)q|9ksb&@zDPEJIaCrSYDUg#GQV?rErOg>lv*}d%nv)7Qs z#@Pl#wix|qo6v;<%hn&2D*Gh{TMI0MSzV7kIa_0w$hioKOT>l^nBVxHiI}R9($q>? zKNKn0SF`JDHkRvz<~~^{Uf70a&eDxb+5w@(ZP7ohRs zzGx!=t!xbx@^F1j8s8D=<44rCr5_d<($cxc;L5&4!61hjLE_D zO%4?%6%6K2cM86a%vzod>Mj(?i`{)mz(;LFG%7ualM;pHeAMihP?R2?B?B|YL!@U7 zdIJfSS=cj3#%ui+8rux#x@>szGy9W1eiHHxl@|Pi&usi&*BH4s;ml1WB_~uWr#ln1qf5H6ssA!Z-0%SwxdLiQQ(Vna zFnl+MDK=@hpC%Q|_W_O5yhF4%6ildJ6@XF77iuYnAUgT?%$r$xD2JTX5*L9Kr0zzK^f+$8Hy|#t-uV%@~E08au z2jz~4y#!{y#CY+J=xH7T$0b`Gh~f`+$V_O zd=E|2{Zpj?Lbn)lgJv?s=#@$zlsgtX=mi9Kb2va;kQgkH=B4}nj+(4bGJ99^>;aLE z`PG-OEh)cBKk8vY-`)`#8BMJmv@4gps-syZSujI>219nkv5&iK?Q@z@5E;FKD|nip zqoYc}(f{O$?mSr_&3Ccbmx^aBy{$w_{M~KK%iOyN#T!LZO~nabQ3|Nfp*w%O`Ciyq zwt2r2r@2UjjX{wwK213vWtXM_4Oy-0#_Ru*9wpM99KD`@f&t*$U%dn_5r`P(r`gz3 z+UHIr+Ww~nHh=842pkV3M;$F&6Onj2nMZHd@bo=!lo5Og(zlzhi2Z*-9v*lxPjOwU zNN|~e^IWagsQj8Kp_k(RH+^=06=Di~R8IZo@78EH(FmMP?iECr9c>Cl~U)mer2 zHviHhAv1^r4D7x%sCy{jE@0ac76b#%4CI=bk@R96nntJq z^63hyTeN8$>UotTJ{JUQrlB>=vHtXR=v`J2O#6S(4xLs`!U75pZ-zd-JKk?iI<~>L zh(@!5=AB;8R`|&6cZJZQ93V9x~YL7)Ad9qh-^BG1TC;X90OoJ zIkBZ3-+M&k$8Hnahwca-x{C)D41`0r9%&EI&$N*0dAtn^e4BZzHNwewHV$q2drAmN zMPa|-WB$KGIbw6mB+`4s$P$h~dFaH4*VuVxq|Ed;%F^2Eu4^d}v=T+Z?ueLN;H9$~ zxcWSI?L2p!Ag(sDkg5)+OclPYy~ShD!|%86YeDDZ7*xccwVu4lfDAqaJkFOzz7PWTNE84G-Sm@+1t`=k_5#ySg_{Q#lOIfh>g5f z*lQ5R1b9~SM;pNj4=&u`cTKg&vIFQ~VYa}pbWC}d+Q{p_kBKT8>=nMZc+P1gw7516 zr7~xTmL8*7N$O`fd$S^bpb#x4sKJ1tSF8|-z3}gbMLI^Vlich+YFr@+p@o@L9^FaLhd@}3hlUNSsN;{7;S(hu zdH)1vZ!*(8Hv-hLiDo1b<5#fw6L`G*JE6PO8z=_>{79dSq33mb;CF;ND0x^5~?mDo<^lZd&ZiVQL+Y42&O@ z8Wub}WI}0Pxz)sjc^@a4-fby@5AWdg(6rRag)l$G^*3vJG+d8};p(CT>P`!`BkkyPtAMWBT|&`g2HOUb$>jQ(ih#_5om)skO{rqzq!w&9Me zurKflo>T8n#f~||#lEj|uz)M6ylO%6j3cxyvBy_8F|OK@9~N2JyX|-2qRv4Ah!&|%K%d-7gQ?dP+yNHW zZZQEn2+^FM{7&BFoSYhlNafC;f)alK1w!Pnk$dCFQb&9-BzJuJlm zheN2BQ=2Dyt$xz6L7q2-LJe9f>i%HNe{`K~2+tinRGScaHWrQV`QD7GQ-!pr3=lsuUv9W1}vc#?9x1YY#lhD^(L&H|$|g7pr!G zTqbF0-f2rR>I~R`tBDuLKEd?F|G6w(T&r@y&zTcB;14-(7kV<}V#5;cP%{fP9Ca)X zrSXDny%OFGPvhpvdgMzPJ3;Qau@>;?@U^%!=2h+x{hKHa9GW+d2)YU%&m|JSt~_4Z z_8F~+z2X$-!=KIA+?E`b&np0L7P6G}MIK#}Hqi@{jm^IkCF6SUg+6N(9-vAV(-1SI zY~XHg^Ik0$N)0G|&l)2v!_b^HmzTHo-h`c8y_yIwGjpYf2E1%dNq?Mpk_qf0nb_@D z6|t)|%9lieO=^>satB0Lt@SF5e)xRrpCHIJ1iP=%6V7QvS$=Hm#}iSF?cdm}o--i9 zK75w!$%9i}n)h~OEm2}E3$$D>XIVOO?EnQujWV3ALpV*e4+Dj?3VP4V$@e9AyXBu2 znxJ|0YoS(OHjes%Eal_%#$4qZNO9SuEo>WY@BeGDAuq}1xeHB~G6+k@&5>}iS&WlyL> z?jwt&@NYl$N2#oKqD`>d{^7a_^oF7@-U&ziY6Eq~JDr<;N%l7e9VhC|7$G~~_f267 z!_=bBY>-c+Hf@dZA-#Ba@XVT|s|0DxRtbc|@RleZ=iZd01iGo9^djS*vjPq>(w?DR zFw1vd4JCu&<_Tm*T%n&N^9TV>ZS=X!yzZ0(W!|L++I#}aeBp1&80Z50x$9Ri_|HIE zLX!%~cI<5N8pd;P+70f7I>h8fc~FPr2P3S>G=0p!$!p&mAh%m%*1XGp&q%2aZL0z- z?8jKcC8!lPdI-P%>FO@_@4!bbFp27BoYxkx1)<30I=6_+(XasOn>zL4)&n*ck zDxwrE{{!$`sKl4e+k`UNV zeQG5{33paal*@E1!?nOTir~1+0I5<*JjXeM!57*p~dn$QBGet>EGyT=J@p+w_j?4w1YDrUkl1@5s zoOJ{g4~sb|RKh{HDt3J>`bpr;|u?YNjDH;UV_b|>b0(w0V9iz*un6~piJSIpUs zAIAA%wVc(Ol=56ZLkX_Wk-+byumD(=S9wwussw$*zPO#=NR*8R8^pATG~12Opg3n= zV_DhdJCMowzYTb8y*mESD^xZV3S8TuOS2?GT8`!CDdFpG(>vEUhf?khd`sy8Un$4X z42P|3X%UN2W^&E3IWmr7uo`ja&pZb)xvZ#BiIm3^a6U_^FcHDCX!L;PanYgpbF0P= zIty*ZeSaPspcW#(a1ZY>ETG6e+BwLXduxRttnhe9xCY*Ghe(r^MK=b8(hP!cFA*zU ze=6C~d{kA|oVMYS>zp<6G^be;;@1VLasWYL_0Ea@SLP)Z*|r#S428Fv{{)FjClAT~ zJl1r zh5YEJZUwZpR{CoBucoQpR~W_Dh4lO5Tot3Zb@YX^0p$6eff@GHMNr!k>+91oCjy?)rA=t73wH@_SM!-B+M?j{$QGrfwN>A{FRto_W8$rUqRI zSn3bk#k(Xq!ZGBQ`2%R04e~%YsD}aIYB6JM6bIG5?**)rmiJ)Yz$uX*$k8L2FkkEq^^s2w`pk}~gUf2T%lyIRvq

-CZ1yYw3vW@BWF^k_k3B`MJAEHQzZGPjv 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%0DYLq3Ox@%Fh#WIT*}xm1#69b5r(|@0bEG{X1vnO z-C2ALK_sq1xT>rcw-8RKM7{wzpIS>lZRiJ5{!QNU1`?Nc3_It$m2IB$X8D{9F}*#c z2w7;XGnmy9f=JNG{R%H8Cm{;(AdN2D7grS(pA^jwCawy|nnKnc8v)IC6h+Uf!wVS* z;Y3Olav5N6Cy?OI6pf~GR>Bwfdl!A;%Jw)H7w{Lebrx8h~gC z&Pmr4fN%S**5SQ6Ei^c4HJ?F1#*1tsNg#~dQhmO7A?=ym7zvxhA9@{dS9Ep@UwHO{OZF2`hSiHJTS@;SU%fWw@CZuh`dg;)B*N zc>y16xe6CdnN^gUdjMMq%fnL^J| zbpEh*{_ZM9pAW94B$1fiI@U!^N2gLAhzZyOZWrzsOBjxQUd<@26RA|Wf4M(_IXLv| zzi_{q>CVt$G>Iw?cbL?0*FKVit+aAcu=rmJTtBRBqMNiSfNYTE1=IMDyFeu2jq~s) z%%s7MuP@OwvxBQLCB6l5n6$4wArF=X<2|$E_Hxbl9G{ol8N@|my*CP{O#Jn_Q%x1U znsYMHt!j+5zQ6$}MGA+pc%&5&eF|Uz0M6Nm=d89%3;EKcD5x)+&1q30~51oJ1ZThfjC1ICn;Z{2%SlO#O4*ix8M$Sxv}3klB-4E<~w+B!4RmuEfl+S ziK`d;$=Kp)Y`Zx(!o*tAhgt+{tt`l5Pv}h?BZjM#G zGS^m@kK~pE8Dd2nGaC9eH_dB5a2a7;F98#OZ|@va>jcl^+k2xJxcbLB#GeZ}kD5?W zDWP@`HEU93vQNYCXh8H~*LqmIO)DgLwShrc!RhcTj}iYfFY>_YEg=!Z3V4CXL9S{Q z@#-hS+r;>2So%q*yv#s8DO>h=7`ercrZoe~=dSv^7lR2zmf z1d-lVR|?L`M6J>#?Ca>$tmceXuXp*q260S%z+s3=4ZFqDK�aFiR@pTIhmL#J*^T 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%0Dei3l$!vtvR$qnUnyL7WcJk%`>Q-{ITnUQ=jAI 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 literal 0 HcmV?d00001 diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs new file mode 100644 index 0000000..f499d96 --- /dev/null +++ b/src/bls12_381/tests/mod.rs @@ -0,0 +1,48 @@ +use super::*; +use ::*; + +fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected: &[u8]) +{ + let mut e = G::one(); + + let mut v = vec![]; + { + let mut expected = expected; + for _ in 0..1000 { + let e_affine = e.to_affine(); + let encoded = E::from_affine(e_affine).unwrap(); + v.extend_from_slice(encoded.as_ref()); + + let mut decoded = E::empty(); + decoded.as_mut().copy_from_slice(&expected[0..E::size()]); + expected = &expected[E::size()..]; + let decoded = decoded.into_affine().unwrap(); + assert_eq!(e_affine, decoded); + + e.add_assign(&G::one()); + } + } + + assert_eq!(&v[..], expected); +} + +#[test] +fn test_g1_uncompressed_vectors() { + test_vectors::<G1, G1Uncompressed>(include_bytes!("g1_uncompressed_test_vectors.dat")); +} + +#[test] +fn test_g1_compressed_vectors() { + test_vectors::<G1, G1Compressed>(include_bytes!("g1_compressed_test_vectors.dat")); +} + +#[test] +fn test_g2_uncompressed_vectors() { + test_vectors::<G2, G2Uncompressed>(include_bytes!("g2_uncompressed_test_vectors.dat")); +} + +#[test] +fn test_g2_compressed_vectors() { + test_vectors::<G2, G2Compressed>(include_bytes!("g2_compressed_test_vectors.dat")); +} + diff --git a/src/lib.rs b/src/lib.rs index 3b9c47a..4e27e94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ extern crate test; extern crate rand; +extern crate byteorder; #[cfg(test)] pub mod tests; @@ -151,6 +152,8 @@ pub trait CurveAffine: Copy + type Base: SqrtField; type Projective: CurveProjective<Affine=Self, Scalar=Self::Scalar>; type Prepared: Clone + Send + Sync + 'static; + type Uncompressed: EncodedPoint<Affine=Self>; + type Compressed: EncodedPoint<Affine=Self>; /// Returns the additive identity. fn zero() -> Self; @@ -176,6 +179,56 @@ pub trait CurveAffine: Copy + /// Converts this element into its affine representation. fn to_projective(&self) -> Self::Projective; + + /// Converts this element into its compressed encoding, so long as it's not + /// the point at infinity. + fn to_compressed(&self) -> Result<Self::Compressed, ()> { + <Self::Compressed as EncodedPoint>::from_affine(*self) + } + + /// Converts this element into its uncompressed encoding, so long as it's not + /// the point at infinity. + fn to_uncompressed(&self) -> Result<Self::Uncompressed, ()> { + <Self::Uncompressed as EncodedPoint>::from_affine(*self) + } +} + +/// An encoded elliptic curve point, which should essentially wrap a `[u8; N]`. +pub trait EncodedPoint: Sized + + Send + + Sync + + AsRef<[u8]> + + AsMut<[u8]> + + 'static +{ + type Affine: CurveAffine; + + /// Creates an empty representation. + fn empty() -> Self; + + /// Returns the number of bytes consumed by this representation. + fn size() -> usize; + + /// Converts an `EncodedPoint` into a `CurveAffine` element, + /// if the point is valid. + fn into_affine(&self) -> Result<Self::Affine, ()> { + let affine = self.into_affine_unchecked()?; + + if affine.is_valid() { + Ok(affine) + } else { + Err(()) + } + } + + /// Converts an `EncodedPoint` into a `CurveAffine` element, + /// without checking if it's a valid point. Caller must be careful + /// when using this, as misuse can violate API invariants. + fn into_affine_unchecked(&self) -> Result<Self::Affine, ()>; + + /// Creates an `EncodedPoint` from an affine point, as long as the + /// point is not the point at infinity. + fn from_affine(affine: Self::Affine) -> Result<Self, ()>; } /// This trait represents an element of a field. diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 0356b64..61ea6c5 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -1,6 +1,6 @@ use rand::{SeedableRng, XorShiftRng, Rand}; -use ::{CurveProjective, CurveAffine, Field}; +use ::{CurveProjective, CurveAffine, Field, EncodedPoint}; pub fn curve_tests<G: CurveProjective>() { @@ -59,6 +59,7 @@ pub fn curve_tests<G: CurveProjective>() random_negation_tests::<G>(); random_transformation_tests::<G>(); random_wnaf_tests::<G>(); + random_encoding_tests::<G::Affine>(); } fn random_wnaf_tests<G: CurveProjective>() { @@ -291,3 +292,29 @@ fn random_transformation_tests<G: CurveProjective>() { assert_eq!(v, expected_v); } } + +fn random_encoding_tests<G: CurveAffine>() +{ + assert!(G::zero().to_compressed().is_err()); + assert!(G::zero().to_uncompressed().is_err()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut r = G::Projective::rand(&mut rng).to_affine(); + + let uncompressed = r.to_uncompressed().unwrap(); + let de_uncompressed = uncompressed.into_affine().unwrap(); + assert_eq!(de_uncompressed, r); + + let compressed = r.to_compressed().unwrap(); + let de_compressed = compressed.into_affine().unwrap(); + assert_eq!(de_compressed, r); + + r.negate(); + + let compressed = r.to_compressed().unwrap(); + let de_compressed = compressed.into_affine().unwrap(); + assert_eq!(de_compressed, r); + } +} From ae69eb01b3f6016c738627e6ce1b6cff73fd875d Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 11 Jul 2017 17:10:32 -0600 Subject: [PATCH 006/140] Small adjustments to satisfy clippy. --- src/bls12_381/ec.rs | 16 ++++------------ src/bls12_381/fq.rs | 2 +- src/bls12_381/fq12.rs | 2 +- src/bls12_381/fq2.rs | 4 ++-- src/bls12_381/fq6.rs | 2 +- src/bls12_381/fr.rs | 2 +- src/wnaf.rs | 6 +++--- 7 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 5d20d4c..acbdaab 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -78,20 +78,12 @@ macro_rules! curve_impl { x3b.mul_assign(&self.x); x3b.add_assign(&Self::get_coeff_b()); - if y2 == x3b { - true - } else { - false - } + y2 == x3b } } fn is_in_correct_subgroup(&self) -> bool { - if self.mul($scalarfield::char()).is_zero() { - true - } else { - false - } + self.mul($scalarfield::char()).is_zero() } } @@ -788,7 +780,7 @@ pub mod g1 { const RECOMMENDATIONS: [usize; 12] = [1, 3, 7, 20, 43, 120, 273, 563, 1630, 3128, 7933, 62569]; let mut ret = 4; - for r in RECOMMENDATIONS.iter() { + for r in &RECOMMENDATIONS { if num_scalars > *r { ret += 1; } else { @@ -1327,7 +1319,7 @@ pub mod g2 { const RECOMMENDATIONS: [usize; 11] = [1, 3, 8, 20, 47, 126, 260, 826, 1501, 4555, 84071]; let mut ret = 4; - for r in RECOMMENDATIONS.iter() { + for r in &RECOMMENDATIONS { if num_scalars > *r { ret += 1; } else { diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 503d737..403faed 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -309,7 +309,7 @@ impl PrimeFieldRepr for FqRepr { #[inline(always)] fn mul2(&mut self) { let mut last = 0; - for i in self.0.iter_mut() { + for i in &mut self.0 { let tmp = *i >> 63; *i <<= 1; *i |= last; diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index 4df2282..3a9dcc5 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -4,7 +4,7 @@ use super::fq6::Fq6; use super::fq2::Fq2; use super::fq::{FROBENIUS_COEFF_FQ12_C1}; -/// An element of F_{q^12}, represented by c0 + c1 * w. +/// An element of Fq12, represented by c0 + c1 * w. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq12 { pub c0: Fq6, diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index a7a66d1..cfec0a8 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -4,7 +4,7 @@ use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; use std::cmp::Ordering; -/// An element of F_{q^2}, represented by c0 + c1 * u. +/// An element of Fq2, represented by c0 + c1 * u. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq2 { pub c0: Fq, @@ -141,7 +141,7 @@ impl SqrtField for Fq2 { // Algorithm 9, https://eprint.iacr.org/2012/685.pdf if self.is_zero() { - return Some(Self::zero()); + Some(Self::zero()) } else { // a1 = self^((q - 3) / 4) let mut a1 = self.pow([0xee7fbfffffffeaaa, 0x7aaffffac54ffff, 0xd9cc34a83dac3d89, 0xd91dd2e13ce144af, 0x92c6e9ed90d2eb35, 0x680447a8e5ff9a6]); diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs index 81226da..1a31497 100644 --- a/src/bls12_381/fq6.rs +++ b/src/bls12_381/fq6.rs @@ -3,7 +3,7 @@ use ::{Field}; use super::fq2::Fq2; use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; -/// An element of F_{q^6}, represented by c0 + c1 * v + c2 * v^2. +/// An element of Fq6, represented by c0 + c1 * v + c2 * v^2. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq6 { pub c0: Fq2, diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 7c274ab..98c9303 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -146,7 +146,7 @@ impl PrimeFieldRepr for FrRepr { #[inline(always)] fn mul2(&mut self) { let mut last = 0; - for i in self.0.iter_mut() { + for i in &mut self.0 { let tmp = *i >> 63; *i <<= 1; *i |= last; diff --git a/src/wnaf.rs b/src/wnaf.rs index 0c5ae35..ecc9409 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -1,6 +1,6 @@ use super::{CurveProjective, PrimeFieldRepr}; -/// Replaces the contents of `table` with a wNAF window table for the given window size. +/// Replaces the contents of `table` with a w-NAF window table for the given window size. /// /// This function will panic if provided a window size below two, or above 22. pub fn wnaf_table<G: CurveProjective>(table: &mut Vec<G>, mut base: G, window: usize) @@ -20,7 +20,7 @@ pub fn wnaf_table<G: CurveProjective>(table: &mut Vec<G>, mut base: G, window: u } } -/// Replaces the contents of `wnaf` with the wNAF representation of a scalar. +/// Replaces the contents of `wnaf` with the w-NAF representation of a scalar. /// /// This function will panic if provided a window size below two, or above 22. pub fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window: usize) @@ -54,7 +54,7 @@ pub fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window: usize } } -/// Performs wNAF exponentiation with the provided window table and wNAF-form scalar. +/// Performs w-NAF exponentiation with the provided window table and w-NAF form scalar. /// /// This function must be provided a `table` and `wnaf` that were constructed with /// the same window size; otherwise, it may panic or produce invalid results. From 647f83b53b7a88dd5f75ac1436857c05e0a1be1b Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 11 Jul 2017 23:37:29 -0600 Subject: [PATCH 007/140] Rename many .to_* methods to .into_*. --- src/bls12_381/ec.rs | 18 +++++++------- src/bls12_381/tests/mod.rs | 2 +- src/lib.rs | 8 +++--- src/tests/curve.rs | 50 +++++++++++++++++++------------------- src/tests/engine.rs | 20 +++++++-------- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index acbdaab..b2b18a9 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -140,7 +140,7 @@ macro_rules! curve_impl { $prepared::from_affine(*self) } - fn to_projective(&self) -> $projective { + fn into_projective(&self) -> $projective { (*self).into() } } @@ -489,7 +489,7 @@ macro_rules! curve_impl { *self = res; } - fn to_affine(&self) -> $affine { + fn into_affine(&self) -> $affine { (*self).into() } @@ -980,15 +980,15 @@ pub mod g1 { assert!(b.is_valid()); assert!(c.is_valid()); - let mut tmp1 = a.to_projective(); - tmp1.add_assign(&b.to_projective()); - assert_eq!(tmp1.to_affine(), c); - assert_eq!(tmp1, c.to_projective()); + let mut tmp1 = a.into_projective(); + tmp1.add_assign(&b.into_projective()); + assert_eq!(tmp1.into_affine(), c); + assert_eq!(tmp1, c.into_projective()); - let mut tmp2 = a.to_projective(); + let mut tmp2 = a.into_projective(); tmp2.add_assign_mixed(&b); - assert_eq!(tmp2.to_affine(), c); - assert_eq!(tmp2, c.to_projective()); + assert_eq!(tmp2.into_affine(), c); + assert_eq!(tmp2, c.into_projective()); } #[test] diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs index f499d96..6baef49 100644 --- a/src/bls12_381/tests/mod.rs +++ b/src/bls12_381/tests/mod.rs @@ -9,7 +9,7 @@ fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected: { let mut expected = expected; for _ in 0..1000 { - let e_affine = e.to_affine(); + let e_affine = e.into_affine(); let encoded = E::from_affine(e_affine).unwrap(); v.extend_from_slice(encoded.as_ref()); diff --git a/src/lib.rs b/src/lib.rs index 4e27e94..e8c55e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,7 +124,7 @@ pub trait CurveProjective: PartialEq + fn mul_assign<S: Into<<Self::Scalar as PrimeField>::Repr>>(&mut self, other: S); /// Converts this element into its affine representation. - fn to_affine(&self) -> Self::Affine; + fn into_affine(&self) -> Self::Affine; /// Recommends a wNAF window table size given a scalar. Returns `None` if normal /// scalar multiplication is encouraged. If `Some` is returned, it will be between @@ -178,17 +178,17 @@ pub trait CurveAffine: Copy + fn prepare(&self) -> Self::Prepared; /// Converts this element into its affine representation. - fn to_projective(&self) -> Self::Projective; + fn into_projective(&self) -> Self::Projective; /// Converts this element into its compressed encoding, so long as it's not /// the point at infinity. - fn to_compressed(&self) -> Result<Self::Compressed, ()> { + fn into_compressed(&self) -> Result<Self::Compressed, ()> { <Self::Compressed as EncodedPoint>::from_affine(*self) } /// Converts this element into its uncompressed encoding, so long as it's not /// the point at infinity. - fn to_uncompressed(&self) -> Result<Self::Uncompressed, ()> { + fn into_uncompressed(&self) -> Result<Self::Uncompressed, ()> { <Self::Uncompressed as EncodedPoint>::from_affine(*self) } } diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 61ea6c5..86dd81a 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -38,7 +38,7 @@ pub fn curve_tests<G: CurveProjective>() let mut z2 = z; z2.add_assign(&r); - z.add_assign_mixed(&r.to_affine()); + z.add_assign_mixed(&r.into_affine()); assert_eq!(z, z2); assert_eq!(z, r); @@ -47,8 +47,8 @@ pub fn curve_tests<G: CurveProjective>() // Transformations { let a = G::rand(&mut rng); - let b = a.to_affine().to_projective(); - let c = a.to_affine().to_projective().to_affine().to_projective(); + let b = a.into_affine().into_projective(); + let c = a.into_affine().into_projective().into_affine().into_projective(); assert_eq!(a, b); assert_eq!(b, c); } @@ -108,7 +108,7 @@ fn random_negation_tests<G: CurveProjective>() { assert!(t3.is_zero()); let mut t4 = t1; - t4.add_assign_mixed(&t2.to_affine()); + t4.add_assign_mixed(&t2.into_affine()); assert!(t4.is_zero()); t1.negate(); @@ -136,7 +136,7 @@ fn random_doubling_tests<G: CurveProjective>() { tmp2.add_assign(&b); let mut tmp3 = a; - tmp3.add_assign_mixed(&b.to_affine()); + tmp3.add_assign_mixed(&b.into_affine()); assert_eq!(tmp1, tmp2); assert_eq!(tmp1, tmp3); @@ -149,8 +149,8 @@ fn random_multiplication_tests<G: CurveProjective>() { for _ in 0..1000 { let mut a = G::rand(&mut rng); let mut b = G::rand(&mut rng); - let a_affine = a.to_affine(); - let b_affine = b.to_affine(); + let a_affine = a.into_affine(); + let b_affine = b.into_affine(); let s = G::Scalar::rand(&mut rng); @@ -182,9 +182,9 @@ fn random_addition_tests<G: CurveProjective>() { let a = G::rand(&mut rng); let b = G::rand(&mut rng); let c = G::rand(&mut rng); - let a_affine = a.to_affine(); - let b_affine = b.to_affine(); - let c_affine = c.to_affine(); + let a_affine = a.into_affine(); + let b_affine = b.into_affine(); + let c_affine = c.into_affine(); // a + a should equal the doubling { @@ -192,7 +192,7 @@ fn random_addition_tests<G: CurveProjective>() { aplusa.add_assign(&a); let mut aplusamixed = a; - aplusamixed.add_assign_mixed(&a.to_affine()); + aplusamixed.add_assign_mixed(&a.into_affine()); let mut adouble = a; adouble.double(); @@ -221,17 +221,17 @@ fn random_addition_tests<G: CurveProjective>() { // Mixed addition // (a + b) + c - tmp[3] = a_affine.to_projective(); + tmp[3] = a_affine.into_projective(); tmp[3].add_assign_mixed(&b_affine); tmp[3].add_assign_mixed(&c_affine); // a + (b + c) - tmp[4] = b_affine.to_projective(); + tmp[4] = b_affine.into_projective(); tmp[4].add_assign_mixed(&c_affine); tmp[4].add_assign_mixed(&a_affine); // (a + c) + b - tmp[5] = a_affine.to_projective(); + tmp[5] = a_affine.into_projective(); tmp[5].add_assign_mixed(&c_affine); tmp[5].add_assign_mixed(&b_affine); @@ -239,7 +239,7 @@ fn random_addition_tests<G: CurveProjective>() { for i in 0..6 { for j in 0..6 { assert_eq!(tmp[i], tmp[j]); - assert_eq!(tmp[i].to_affine(), tmp[j].to_affine()); + assert_eq!(tmp[i].into_affine(), tmp[j].into_affine()); } assert!(tmp[i] != a); @@ -258,8 +258,8 @@ fn random_transformation_tests<G: CurveProjective>() { for _ in 0..1000 { let g = G::rand(&mut rng); - let g_affine = g.to_affine(); - let g_projective = g_affine.to_projective(); + let g_affine = g.into_affine(); + let g_projective = g_affine.into_projective(); assert_eq!(g, g_projective); } @@ -279,10 +279,10 @@ fn random_transformation_tests<G: CurveProjective>() { } for _ in 0..5 { let s = between.ind_sample(&mut rng); - v[s] = v[s].to_affine().to_projective(); + v[s] = v[s].into_affine().into_projective(); } - let expected_v = v.iter().map(|v| v.to_affine().to_projective()).collect::<Vec<_>>(); + let expected_v = v.iter().map(|v| v.into_affine().into_projective()).collect::<Vec<_>>(); G::batch_normalization(&mut v); for i in &v { @@ -295,25 +295,25 @@ fn random_transformation_tests<G: CurveProjective>() { fn random_encoding_tests<G: CurveAffine>() { - assert!(G::zero().to_compressed().is_err()); - assert!(G::zero().to_uncompressed().is_err()); + assert!(G::zero().into_compressed().is_err()); + assert!(G::zero().into_uncompressed().is_err()); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); for _ in 0..1000 { - let mut r = G::Projective::rand(&mut rng).to_affine(); + let mut r = G::Projective::rand(&mut rng).into_affine(); - let uncompressed = r.to_uncompressed().unwrap(); + let uncompressed = r.into_uncompressed().unwrap(); let de_uncompressed = uncompressed.into_affine().unwrap(); assert_eq!(de_uncompressed, r); - let compressed = r.to_compressed().unwrap(); + let compressed = r.into_compressed().unwrap(); let de_compressed = compressed.into_affine().unwrap(); assert_eq!(de_compressed, r); r.negate(); - let compressed = r.to_compressed().unwrap(); + let compressed = r.into_compressed().unwrap(); let de_compressed = compressed.into_affine().unwrap(); assert_eq!(de_compressed, r); } diff --git a/src/tests/engine.rs b/src/tests/engine.rs index 5e2c07b..b1bb754 100644 --- a/src/tests/engine.rs +++ b/src/tests/engine.rs @@ -10,10 +10,10 @@ pub fn engine_tests<E: Engine>() let z1 = E::G1Affine::zero().prepare(); let z2 = E::G2Affine::zero().prepare(); - let a = E::G1::rand(&mut rng).to_affine().prepare(); - let b = E::G2::rand(&mut rng).to_affine().prepare(); - let c = E::G1::rand(&mut rng).to_affine().prepare(); - let d = E::G2::rand(&mut rng).to_affine().prepare(); + let a = E::G1::rand(&mut rng).into_affine().prepare(); + let b = E::G2::rand(&mut rng).into_affine().prepare(); + let c = E::G1::rand(&mut rng).into_affine().prepare(); + let d = E::G2::rand(&mut rng).into_affine().prepare(); assert_eq!( E::Fqk::one(), @@ -50,8 +50,8 @@ fn random_miller_loop_tests<E: Engine>() { let p2 = E::pairing(a, b); - let a = a.to_affine().prepare(); - let b = b.to_affine().prepare(); + let a = a.into_affine().prepare(); + let b = b.into_affine().prepare(); let p1 = E::final_exponentiation(&E::miller_loop(&[(&a, &b)])).unwrap(); @@ -71,10 +71,10 @@ fn random_miller_loop_tests<E: Engine>() { let mut abcd = ab; abcd.mul_assign(&cd); - let a = a.to_affine().prepare(); - let b = b.to_affine().prepare(); - let c = c.to_affine().prepare(); - let d = d.to_affine().prepare(); + let a = a.into_affine().prepare(); + let b = b.into_affine().prepare(); + let c = c.into_affine().prepare(); + let d = d.into_affine().prepare(); let abcd_with_double_loop = E::final_exponentiation( &E::miller_loop(&[(&a, &b), (&c, &d)]) From bafb273a6c420ed150908387c3a220e6a3fc1a89 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 11 Jul 2017 23:49:33 -0600 Subject: [PATCH 008/140] Simplify doubling code during squaring routine. --- src/bls12_381/fq.rs | 50 +++++++++++---------------------------------- src/bls12_381/fr.rs | 30 ++++++++------------------- 2 files changed, 20 insertions(+), 60 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 403faed..67fe66c 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -627,45 +627,19 @@ impl Field for Fq { let mut carry = 0; let r9 = ::mac_with_carry(r9, (self.0).0[4], (self.0).0[5], &mut carry); let r10 = carry; - let tmp0 = r1 >> 63; + + let r11 = r10 >> 63; + let r10 = (r10 << 1) | (r9 >> 63); + let r9 = (r9 << 1) | (r8 >> 63); + let r8 = (r8 << 1) | (r7 >> 63); + let r7 = (r7 << 1) | (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 tmp1 = r2 >> 63; - let r2 = r2 << 1; - let r2 = r2 | tmp0; - let tmp0 = tmp1; - let tmp1 = r3 >> 63; - let r3 = r3 << 1; - let r3 = r3 | tmp0; - let tmp0 = tmp1; - let tmp1 = r4 >> 63; - let r4 = r4 << 1; - let r4 = r4 | tmp0; - let tmp0 = tmp1; - let tmp1 = r5 >> 63; - let r5 = r5 << 1; - let r5 = r5 | tmp0; - let tmp0 = tmp1; - let tmp1 = r6 >> 63; - let r6 = r6 << 1; - let r6 = r6 | tmp0; - let tmp0 = tmp1; - let tmp1 = r7 >> 63; - let r7 = r7 << 1; - let r7 = r7 | tmp0; - let tmp0 = tmp1; - let tmp1 = r8 >> 63; - let r8 = r8 << 1; - let r8 = r8 | tmp0; - let tmp0 = tmp1; - let tmp1 = r9 >> 63; - let r9 = r9 << 1; - let r9 = r9 | tmp0; - let tmp0 = tmp1; - let tmp1 = r10 >> 63; - let r10 = r10 << 1; - let r10 = r10 | tmp0; - let tmp0 = tmp1; - let r11 = tmp0; + let mut carry = 0; let r0 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); let r1 = ::adc(r1, 0, &mut carry); diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 98c9303..55b7b58 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -426,29 +426,15 @@ impl Field for Fr { let mut carry = 0; let r5 = ::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); let r6 = carry; - let tmp0 = r1 >> 63; + + 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 tmp1 = r2 >> 63; - let r2 = r2 << 1; - let r2 = r2 | tmp0; - let tmp0 = tmp1; - let tmp1 = r3 >> 63; - let r3 = r3 << 1; - let r3 = r3 | tmp0; - let tmp0 = tmp1; - let tmp1 = r4 >> 63; - let r4 = r4 << 1; - let r4 = r4 | tmp0; - let tmp0 = tmp1; - let tmp1 = r5 >> 63; - let r5 = r5 << 1; - let r5 = r5 | tmp0; - let tmp0 = tmp1; - let tmp1 = r6 >> 63; - let r6 = r6 << 1; - let r6 = r6 | tmp0; - let tmp0 = tmp1; - let r7 = tmp0; + let mut carry = 0; let r0 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); let r1 = ::adc(r1, 0, &mut carry); From d438f166280fd75d753717467a43348d7c20142e Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Fri, 14 Jul 2017 11:41:40 -0600 Subject: [PATCH 009/140] Use `divn` when rightshifting during rand(). --- src/bls12_381/fq.rs | 6 ++---- src/bls12_381/fr.rs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 67fe66c..0505f68 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -9,7 +9,7 @@ const MODULUS_BITS: u32 = 381; // The number of bits that must be shaved from the beginning of // the representation when randomly sampling. -const REPR_SHAVE_BITS: u32 = 3; +const REPR_SHAVE_BITS: usize = 3; // R = 2**384 % q const R: FqRepr = FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493]); @@ -368,9 +368,7 @@ impl ::rand::Rand for Fq { fn rand<R: ::rand::Rng>(rng: &mut R) -> Self { loop { let mut tmp = Fq(FqRepr::rand(rng)); - for _ in 0..REPR_SHAVE_BITS { - tmp.0.div2(); - } + tmp.0.divn(REPR_SHAVE_BITS); if tmp.is_valid() { return tmp } diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 55b7b58..c5c80b2 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -8,7 +8,7 @@ const MODULUS_BITS: u32 = 255; // The number of bits that must be shaved from the beginning of // the representation when randomly sampling. -const REPR_SHAVE_BITS: u32 = 1; +const REPR_SHAVE_BITS: usize = 1; // R = 2**256 % r const R: FrRepr = FrRepr([0x1fffffffe, 0x5884b7fa00034802, 0x998c4fefecbc4ff5, 0x1824b159acc5056f]); @@ -205,9 +205,7 @@ impl ::rand::Rand for Fr { fn rand<R: ::rand::Rng>(rng: &mut R) -> Self { loop { let mut tmp = Fr(FrRepr::rand(rng)); - for _ in 0..REPR_SHAVE_BITS { - tmp.0.div2(); - } + tmp.0.divn(REPR_SHAVE_BITS); if tmp.is_valid() { return tmp } From 806d34b39a0b243a189656673d3a44e8074813be Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Fri, 14 Jul 2017 11:45:07 -0600 Subject: [PATCH 010/140] Rename `unitary_inverse` to `conjugate`. Closes #12. --- src/bls12_381/fq12.rs | 2 +- src/bls12_381/mod.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index 3a9dcc5..354045e 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -21,7 +21,7 @@ impl Rand for Fq12 { } impl Fq12 { - pub fn unitary_inverse(&mut self) + pub fn conjugate(&mut self) { self.c1.negate(); } diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index f0ef0b9..6454877 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -96,7 +96,7 @@ impl Engine for Bls12 { fn final_exponentiation(r: &Fq12) -> Option<Fq12> { let mut f1 = *r; - f1.unitary_inverse(); + f1.conjugate(); match r.inverse() { Some(mut f2) => { @@ -110,7 +110,7 @@ impl Engine for Bls12 { { *f = f.pow(&[x]); if BLS_X_IS_NEGATIVE { - f.unitary_inverse(); + f.conjugate(); } } @@ -124,17 +124,17 @@ impl Engine for Bls12 { exp_by_x(&mut y2, x); x <<= 1; let mut y3 = r; - y3.unitary_inverse(); + y3.conjugate(); y1.mul_assign(&y3); - y1.unitary_inverse(); + y1.conjugate(); y1.mul_assign(&y2); y2 = y1; exp_by_x(&mut y2, x); y3 = y2; exp_by_x(&mut y3, x); - y1.unitary_inverse(); + y1.conjugate(); y3.mul_assign(&y1); - y1.unitary_inverse(); + y1.conjugate(); y1.frobenius_map(3); y2.frobenius_map(2); y1.mul_assign(&y2); From 2090e7679489a7a9bb765cc3ee1488ef64174881 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Fri, 14 Jul 2017 11:56:55 -0600 Subject: [PATCH 011/140] Mark `wnaf` module unstable via feature `unstable-wnaf`. Closes #13. --- Cargo.toml | 3 +++ src/lib.rs | 2 ++ src/tests/curve.rs | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index aefc602..527b14e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,6 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1.1" + +[features] +unstable-wnaf = [] diff --git a/src/lib.rs b/src/lib.rs index e8c55e1..2d600d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ extern crate byteorder; pub mod tests; pub mod bls12_381; + +#[cfg(feature = "unstable-wnaf")] pub mod wnaf; use std::fmt; diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 86dd81a..ea21f90 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -62,6 +62,10 @@ pub fn curve_tests<G: CurveProjective>() random_encoding_tests::<G::Affine>(); } +#[cfg(not(feature = "unstable-wnaf"))] +fn random_wnaf_tests<G: CurveProjective>() { } + +#[cfg(feature = "unstable-wnaf")] fn random_wnaf_tests<G: CurveProjective>() { use ::wnaf::*; use ::PrimeField; From e72660056e00c93d6b054dfb08ff34a1c67cb799 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sat, 15 Jul 2017 15:32:37 -0600 Subject: [PATCH 012/140] Ordering cleanup for Fq/Fq2, with documentation. Closes #9. --- src/bls12_381/ec.rs | 4 ++-- src/bls12_381/fq.rs | 37 +++++++++++++++++++++++++++++++------ src/bls12_381/fq2.rs | 7 ++++--- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index b2b18a9..4350b12 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -695,7 +695,7 @@ pub mod g1 { negy.negate(); // Get the parity of the sqrt we found. - let parity = y.into_repr() > negy.into_repr(); + let parity = y > negy; Ok(G1Affine { x: x, @@ -735,7 +735,7 @@ pub mod g1 { // If the correct y coordinate is the largest (lexicographically), // the bit should be set. - if affine.y.into_repr() > negy.into_repr() { + if affine.y > negy { res.0[0] |= 1 << 6; // Set second highest bit. } } diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 0505f68..b7f8198 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,4 +1,5 @@ use ::{Field, PrimeField, SqrtField, PrimeFieldRepr}; +use std::cmp::Ordering; use super::fq2::Fq2; // q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 @@ -233,22 +234,22 @@ impl From<u64> for FqRepr { impl Ord for FqRepr { #[inline(always)] - fn cmp(&self, other: &FqRepr) -> ::std::cmp::Ordering { + fn cmp(&self, other: &FqRepr) -> Ordering { for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { if a < b { - return ::std::cmp::Ordering::Less + return Ordering::Less } else if a > b { - return ::std::cmp::Ordering::Greater + return Ordering::Greater } } - ::std::cmp::Ordering::Equal + Ordering::Equal } } impl PartialOrd for FqRepr { #[inline(always)] - fn partial_cmp(&self, other: &FqRepr) -> Option<::std::cmp::Ordering> { + fn partial_cmp(&self, other: &FqRepr) -> Option<Ordering> { Some(self.cmp(other)) } } @@ -357,6 +358,21 @@ impl PrimeFieldRepr for FqRepr { #[derive(Copy, Clone, PartialEq, Eq)] pub struct Fq(FqRepr); +/// `Fq` elements are ordered lexicographically. +impl Ord for Fq { + #[inline(always)] + fn cmp(&self, other: &Fq) -> Ordering { + self.into_repr().cmp(&other.into_repr()) + } +} + +impl PartialOrd for Fq { + #[inline(always)] + fn partial_cmp(&self, other: &Fq) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + impl ::std::fmt::Debug for Fq { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { @@ -847,7 +863,7 @@ use rand::{SeedableRng, XorShiftRng, Rand}; fn test_fq_repr_ordering() { fn assert_equality(a: FqRepr, b: FqRepr) { assert_eq!(a, b); - assert!(a.cmp(&b) == ::std::cmp::Ordering::Equal); + assert!(a.cmp(&b) == Ordering::Equal); } fn assert_lt(a: FqRepr, b: FqRepr) { @@ -1718,3 +1734,12 @@ fn fq_field_tests() { ::tests::field::random_sqrt_tests::<Fq>(); ::tests::field::random_frobenius_tests::<Fq, _>(Fq::char(), 13); } + +#[test] +fn test_fq_ordering() { + // FqRepr's ordering is well-tested, but we still need to make sure the Fq + // elements aren't being compared in Montgomery form. + for i in 0..100 { + assert!(Fq::from_repr(FqRepr::from(i+1)) > Fq::from_repr(FqRepr::from(i))); + } +} diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index cfec0a8..ec22782 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -1,5 +1,5 @@ use rand::{Rng, Rand}; -use ::{Field, SqrtField, PrimeField}; +use ::{Field, SqrtField}; use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; use std::cmp::Ordering; @@ -11,13 +11,14 @@ pub struct Fq2 { pub c1: Fq } +/// `Fq2` elements are ordered lexicographically. impl Ord for Fq2 { #[inline(always)] fn cmp(&self, other: &Fq2) -> Ordering { - match self.c1.into_repr().cmp(&other.c1.into_repr()) { + match self.c1.cmp(&other.c1) { Ordering::Greater => Ordering::Greater, Ordering::Less => Ordering::Less, - Ordering::Equal => self.c0.into_repr().cmp(&other.c0.into_repr()) + Ordering::Equal => self.c0.cmp(&other.c0) } } } From c618240c91aeb4ca950d947af90566a65e580ee7 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 16 Jul 2017 20:52:00 -0600 Subject: [PATCH 013/140] Implements and documents serialization, closes #11. --- src/bls12_381/README.md | 14 + src/bls12_381/ec.rs | 486 +++++++++++------- .../tests/g1_compressed_test_vectors.dat | Bin 48000 -> 0 bytes .../g1_compressed_valid_test_vectors.dat | Bin 0 -> 48000 bytes .../g1_uncompressed_invalid_test_vectors.dat | 0 ...=> g1_uncompressed_valid_test_vectors.dat} | Bin 96000 -> 96000 bytes ...t => g2_compressed_valid_test_vectors.dat} | Bin 96000 -> 96000 bytes ...=> g2_uncompressed_valid_test_vectors.dat} | Bin 192000 -> 192000 bytes src/bls12_381/tests/mod.rs | 20 +- src/lib.rs | 6 +- src/tests/curve.rs | 19 +- 11 files changed, 326 insertions(+), 219 deletions(-) delete mode 100644 src/bls12_381/tests/g1_compressed_test_vectors.dat create mode 100644 src/bls12_381/tests/g1_compressed_valid_test_vectors.dat create mode 100644 src/bls12_381/tests/g1_uncompressed_invalid_test_vectors.dat rename src/bls12_381/tests/{g1_uncompressed_test_vectors.dat => g1_uncompressed_valid_test_vectors.dat} (99%) rename src/bls12_381/tests/{g2_compressed_test_vectors.dat => g2_compressed_valid_test_vectors.dat} (66%) rename src/bls12_381/tests/{g2_uncompressed_test_vectors.dat => g2_uncompressed_valid_test_vectors.dat} (99%) diff --git a/src/bls12_381/README.md b/src/bls12_381/README.md index ae968ce..d3811a1 100644 --- a/src/bls12_381/README.md +++ b/src/bls12_381/README.md @@ -55,3 +55,17 @@ y = 1339506544944476473020471379941921221584933875938349620426543736416511423956 x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 y = 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582*u + 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 ``` + +### Serialization + +* Fq elements are encoded in big-endian form. They occupy 48 bytes in this form. +* Fq2 elements are encoded in big-endian form, meaning that the Fq element c0 + c1 * u is represented by the Fq element c1 followed by the Fq element c0. This means Fq2 elements occupy 96 bytes in this form. +* The group G1 uses Fq elements for coordinates. The group G2 uses Fq2 elements for coordinates. +* G1 and G2 elements can be encoded in uncompressed form (the x-coordinate followed by the y-coordinate) or in compressed form (just the x-coordinate). G1 elements occupy 96 bytes in uncompressed form, and 48 bytes in compressed form. G2 elements occupy 192 bytes in uncompressed form, and 96 bytes in compressed form. + +The most-significant three bits of a G1 or G2 encoding should be masked away before the coordinate(s) are interpreted. These bits are used to unambiguously represent the underlying element: + +* The most significant bit, when set, indicates that the point is in compressed form. Otherwise, the point is in uncompressed form. +* The second-most significant bit indicates that the point is at infinity. If this bit is set, the remaining bits of the group element's encoding should be set to zero. +* The third-most significant bit is set if (and only if) this point is in compressed form _and_ it is not the point at infinity _and_ its y-coordinate is the lexicographically largest of the two associated with the encoded x-coordinate. + diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 4350b12..09824c6 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -586,37 +586,67 @@ pub mod g1 { fn into_affine_unchecked(&self) -> Result<G1Affine, ()> { use byteorder::{ReadBytesExt, BigEndian}; - let mut x = FqRepr([0; 6]); - let mut y = FqRepr([0; 6]); + // Create a copy of this representation. + let mut copy = self.0; - { - let mut reader = &self.0[..]; - - for b in x.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in y.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - } - - Ok(G1Affine { - x: Fq::from_repr(x)?, - y: Fq::from_repr(y)?, - infinity: false - }) - } - fn from_affine(affine: G1Affine) -> Result<Self, ()> { - use byteorder::{WriteBytesExt, BigEndian}; - - if affine.is_zero() { + if copy[0] & (1 << 7) != 0 { + // Distinguisher bit is set, but this should be uncompressed! return Err(()) } + if copy[0] & (1 << 6) != 0 { + // This is the point at infinity, which means that if we mask away + // the first two bits, the entire representation should consist + // of zeroes. + copy[0] &= 0x3f; + + if copy.iter().all(|b| *b == 0) { + Ok(G1Affine::zero()) + } else { + Err(()) + } + } else { + if copy[0] & (1 << 5) != 0 { + // The bit indicating the y-coordinate should be lexicographically + // largest is set, but this is an uncompressed element. + return Err(()) + } + + // Unset the three most significant bits. + copy[0] &= 0x1f; + + let mut x = FqRepr([0; 6]); + let mut y = FqRepr([0; 6]); + + { + let mut reader = &copy[..]; + + for b in x.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + + for b in y.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + } + + Ok(G1Affine { + x: Fq::from_repr(x)?, + y: Fq::from_repr(y)?, + infinity: false + }) + } + } + fn from_affine(affine: G1Affine) -> Self { + use byteorder::{WriteBytesExt, BigEndian}; + let mut res = Self::empty(); - { + if affine.is_zero() { + // Set the second-most significant bit to indicate this point + // is at infinity. + res.0[0] |= 1 << 6; + } else { let mut writer = &mut res.0[..]; for digit in affine.x.into_repr().as_ref().iter().rev() { @@ -628,7 +658,7 @@ pub mod g1 { } } - Ok(res) + res } } @@ -662,85 +692,98 @@ pub mod g1 { return Err(()) } - // Determine if the intended y coordinate must be greater - // lexicographically. - let greatest = copy[0] & (1 << 6) != 0; + if copy[0] & (1 << 6) != 0 { + // This is the point at infinity, which means that if we mask away + // the first two bits, the entire representation should consist + // of zeroes. + copy[0] &= 0x3f; - // Unset the two most significant bits. - copy[0] &= 0x3f; - - let mut x = FqRepr([0; 6]); - - { - let mut reader = &copy[..]; - - for b in x.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - } - - // Interpret as Fq element. - let x = Fq::from_repr(x)?; - - // Compute x^3 + b - let mut x3b = x; - x3b.square(); - x3b.mul_assign(&x); - x3b.add_assign(&G1Affine::get_coeff_b()); - - // Attempt to compute y - match x3b.sqrt() { - Some(y) => { - let mut negy = y; - negy.negate(); - - // Get the parity of the sqrt we found. - let parity = y > negy; - - Ok(G1Affine { - x: x, - y: if parity == greatest { y } else { negy }, - infinity: false - }) - }, - None => { - // Point must not be on the curve. + if copy.iter().all(|b| *b == 0) { + Ok(G1Affine::zero()) + } else { Err(()) } + } else { + // Determine if the intended y coordinate must be greater + // lexicographically. + let greatest = copy[0] & (1 << 5) != 0; + + // Unset the three most significant bits. + copy[0] &= 0x1f; + + let mut x = FqRepr([0; 6]); + + { + let mut reader = &copy[..]; + + for b in x.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + } + + // Interpret as Fq element. + let x = Fq::from_repr(x)?; + + // Compute x^3 + b + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&G1Affine::get_coeff_b()); + + // Attempt to compute y + match x3b.sqrt() { + Some(y) => { + let mut negy = y; + negy.negate(); + + // Get the parity of the sqrt we found. + let parity = y > negy; + + Ok(G1Affine { + x: x, + y: if parity == greatest { y } else { negy }, + infinity: false + }) + }, + None => { + // Point must not be on the curve. + Err(()) + } + } } } - fn from_affine(affine: G1Affine) -> Result<Self, ()> { + fn from_affine(affine: G1Affine) -> Self { use byteorder::{WriteBytesExt, BigEndian}; - if affine.is_zero() { - return Err(()) - } - let mut res = Self::empty(); - { - let mut writer = &mut res.0[..]; + if affine.is_zero() { + // Set the second-most significant bit to indicate this point + // is at infinity. + res.0[0] |= 1 << 6; + } else { + { + let mut writer = &mut res.0[..]; - for digit in affine.x.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); + for digit in affine.x.into_repr().as_ref().iter().rev() { + writer.write_u64::<BigEndian>(*digit).unwrap(); + } } - } - // Distinguish this from an uncompressed element. - res.0[0] |= 1 << 7; // Set highest bit. - - { let mut negy = affine.y; negy.negate(); - // If the correct y coordinate is the largest (lexicographically), - // the bit should be set. + // Set the third most significant bit if the correct y-coordinate + // is lexicographically largest. if affine.y > negy { - res.0[0] |= 1 << 6; // Set second highest bit. + res.0[0] |= 1 << 5; } } - Ok(res) + // Set highest bit to distinguish this as a compressed element. + res.0[0] |= 1 << 7; + + res } } @@ -1080,53 +1123,83 @@ pub mod g2 { fn into_affine_unchecked(&self) -> Result<G2Affine, ()> { use byteorder::{ReadBytesExt, BigEndian}; - let mut x_c1 = FqRepr([0; 6]); - let mut x_c0 = FqRepr([0; 6]); - let mut y_c1 = FqRepr([0; 6]); - let mut y_c0 = FqRepr([0; 6]); + // Create a copy of this representation. + let mut copy = self.0; - { - let mut reader = &self.0[..]; - - for b in x_c1.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in x_c0.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in y_c1.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in y_c0.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - } - - Ok(G2Affine { - x: Fq2 { - c0: Fq::from_repr(x_c0)?, - c1: Fq::from_repr(x_c1)? - }, - y: Fq2 { - c0: Fq::from_repr(y_c0)?, - c1: Fq::from_repr(y_c1)? - }, - infinity: false - }) - } - fn from_affine(affine: G2Affine) -> Result<Self, ()> { - use byteorder::{WriteBytesExt, BigEndian}; - - if affine.is_zero() { + if copy[0] & (1 << 7) != 0 { + // Distinguisher bit is set, but this should be uncompressed! return Err(()) } + if copy[0] & (1 << 6) != 0 { + // This is the point at infinity, which means that if we mask away + // the first two bits, the entire representation should consist + // of zeroes. + copy[0] &= 0x3f; + + if copy.iter().all(|b| *b == 0) { + Ok(G2Affine::zero()) + } else { + Err(()) + } + } else { + if copy[0] & (1 << 5) != 0 { + // The bit indicating the y-coordinate should be lexicographically + // largest is set, but this is an uncompressed element. + return Err(()) + } + + // Unset the three most significant bits. + copy[0] &= 0x1f; + + let mut x_c0 = FqRepr([0; 6]); + let mut x_c1 = FqRepr([0; 6]); + let mut y_c0 = FqRepr([0; 6]); + let mut y_c1 = FqRepr([0; 6]); + + { + let mut reader = &copy[..]; + + for b in x_c1.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + + for b in x_c0.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + + for b in y_c1.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + + for b in y_c0.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + } + + Ok(G2Affine { + x: Fq2 { + c0: Fq::from_repr(x_c0)?, + c1: Fq::from_repr(x_c1)? + }, + y: Fq2 { + c0: Fq::from_repr(y_c0)?, + c1: Fq::from_repr(y_c1)? + }, + infinity: false + }) + } + } + fn from_affine(affine: G2Affine) -> Self { + use byteorder::{WriteBytesExt, BigEndian}; + let mut res = Self::empty(); - { + if affine.is_zero() { + // Set the second-most significant bit to indicate this point + // is at infinity. + res.0[0] |= 1 << 6; + } else { let mut writer = &mut res.0[..]; for digit in affine.x.c1.into_repr().as_ref().iter().rev() { @@ -1146,7 +1219,7 @@ pub mod g2 { } } - Ok(res) + res } } @@ -1180,97 +1253,110 @@ pub mod g2 { return Err(()) } - // Determine if the intended y coordinate must be greater - // lexicographically. - let greatest = copy[0] & (1 << 6) != 0; + if copy[0] & (1 << 6) != 0 { + // This is the point at infinity, which means that if we mask away + // the first two bits, the entire representation should consist + // of zeroes. + copy[0] &= 0x3f; - // Unset the two most significant bits. - copy[0] &= 0x3f; - - let mut x_c1 = FqRepr([0; 6]); - let mut x_c0 = FqRepr([0; 6]); - - { - let mut reader = &copy[..]; - - for b in x_c1.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in x_c0.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - } - - // Interpret as Fq element. - let x = Fq2 { - c0: Fq::from_repr(x_c0)?, - c1: Fq::from_repr(x_c1)? - }; - - // Compute x^3 + b - let mut x3b = x; - x3b.square(); - x3b.mul_assign(&x); - x3b.add_assign(&G2Affine::get_coeff_b()); - - // Attempt to compute y - match x3b.sqrt() { - Some(y) => { - let mut negy = y; - negy.negate(); - - // Get the parity of the sqrt we found. - let parity = y > negy; - - Ok(G2Affine { - x: x, - y: if parity == greatest { y } else { negy }, - infinity: false - }) - }, - None => { - // Point must not be on the curve. + if copy.iter().all(|b| *b == 0) { + Ok(G2Affine::zero()) + } else { Err(()) } + } else { + // Determine if the intended y coordinate must be greater + // lexicographically. + let greatest = copy[0] & (1 << 5) != 0; + + // Unset the three most significant bits. + copy[0] &= 0x1f; + + let mut x_c1 = FqRepr([0; 6]); + let mut x_c0 = FqRepr([0; 6]); + + { + let mut reader = &copy[..]; + + for b in x_c1.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + + for b in x_c0.0.iter_mut().rev() { + *b = reader.read_u64::<BigEndian>().unwrap(); + } + } + + // Interpret as Fq element. + let x = Fq2 { + c0: Fq::from_repr(x_c0)?, + c1: Fq::from_repr(x_c1)? + }; + + // Compute x^3 + b + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&G2Affine::get_coeff_b()); + + // Attempt to compute y + match x3b.sqrt() { + Some(y) => { + let mut negy = y; + negy.negate(); + + // Get the parity of the sqrt we found. + let parity = y > negy; + + Ok(G2Affine { + x: x, + y: if parity == greatest { y } else { negy }, + infinity: false + }) + }, + None => { + // Point must not be on the curve. + Err(()) + } + } } } - fn from_affine(affine: G2Affine) -> Result<Self, ()> { + fn from_affine(affine: G2Affine) -> Self { use byteorder::{WriteBytesExt, BigEndian}; - if affine.is_zero() { - return Err(()) - } - let mut res = Self::empty(); - { - let mut writer = &mut res.0[..]; + if affine.is_zero() { + // Set the second-most significant bit to indicate this point + // is at infinity. + res.0[0] |= 1 << 6; + } else { + { + let mut writer = &mut res.0[..]; - for digit in affine.x.c1.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); + for digit in affine.x.c1.into_repr().as_ref().iter().rev() { + writer.write_u64::<BigEndian>(*digit).unwrap(); + } + + for digit in affine.x.c0.into_repr().as_ref().iter().rev() { + writer.write_u64::<BigEndian>(*digit).unwrap(); + } } - for digit in affine.x.c0.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } - } - - // Distinguish this from an uncompressed element. - res.0[0] |= 1 << 7; // Set highest bit. - - { let mut negy = affine.y; negy.negate(); - // If the correct y coordinate is the largest (lexicographically), - // the bit should be set. + // Set the third most significant bit if the correct y-coordinate + // is lexicographically largest. if affine.y > negy { - res.0[0] |= 1 << 6; // Set second highest bit. + res.0[0] |= 1 << 5; } } - Ok(res) + // Set highest bit to distinguish this as a compressed element. + res.0[0] |= 1 << 7; + + res } } diff --git a/src/bls12_381/tests/g1_compressed_test_vectors.dat b/src/bls12_381/tests/g1_compressed_test_vectors.dat deleted file mode 100644 index 36bf17d9d2fe50c0fcbf8b8b118b1c0288f6410a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48000 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%0DmNB)mjpnkUN?(=);js1H*V>?GsILXcg}D!` z@e|`o{>m19b@;VTg)pJhg+5daUq(qcZ5RB!C2QR13()_XMa5_J_`wu&Vzae?CpUVh zoj0jAaEMe)jy!F8kt#%FT&)ilk{IK#{W?>65J_jFBrTE9kl>RRkiCET0UULT7xd$T zE^DE!j{xA%Klt@O*H&bm80K;_D&S(#u`O~DjvW|=ytxoPnf++f+20S>Q@3@Wwwg49 ziRic_ONwwZ;K-}&<=fHU1Qu$))JV$(Pw7Dv4p+WQHg6``ogtnZODKsP=Kzp=!-E;U zU&2s3&t@Rs20x_aj`G<}>ow#+`pUVN7U^1uP)pc~pjGcb{+)=yakLxmmG!FCq=<|m z4g22^NHre*l**MHWeA)M$|*ads?k5C+D%}PdurdB(GIxTG#}^xjCQFzq{0G$*9i!8 z%r17b=v^r<Tvf)QMnoCthAOtV?2#r6UjqmWo`Pos<JK(Iz}1vKI^FE7UDg4`Wlz8> zOwUA+#(@+rJLl??Icpf!1M9Cq_UO9uoO?jAm?G}y&Nuh`ym$BYa2NsZacLnFwBjum z)R_<<hlM4r47|<Jl>dy0P!LvDj3Tem(r;I>$#sn`YI)IVI#aI}bpg{ZmFj3cwbSk( zo4Jr1|G)SZ9+^p&vt%<LpToU)yhFvDh^T;jItNOTGChS$Bgs(DSQqW9Xule5WBUe2 zc7LHDyE|)&c}+;ERr)-AD0<s9gBc3YqwJz=7d$tjQdP&qNUYT}7^7^pc_3mKZD#^$ zHQVK}k}^s`tu_helSz=jhHecg53mb2*duP!YP%+KWdWjEYof|(o;n1Uz3T}P8*_!c z#rh^FvLNevL|Gnjj<A7Nkf<?8U-iwdf)V1Rjy8pqJ!Odi-;H>o$H0NzeqOMvPQl;+ z_f8eNwB3!=%WUW9WyBHxGaz2K#R}k`^i?5q*%N_C3;6z0&eRl1@J7y$fc0r6nlJ(; z*bB$e!`hS&R6w-BBrfJyr^?v;h1te9Hg1l(EGJu$Bwn2jirht173z2i@d}&4nyp5y zfKz#uK_{kRuVq5onmzz~m6F<&3;eF-j>V)Mi3#T-g1z=9MZ?pw*;J#h)AAAz(|C=> z^rnT5^6-BO^F?o`$9i)D(I7f3Z43T9c{<9g#>yGA#I5i(U#HgL7vI|<j+5A?brAn% zQd&oToj<M*y+HS>mAE~R>;74G+%+R;()Uwf+|!+Tgdh!GRn@(Znc~iRa<HymCv^er zzY&Xa$zHQ|t{vx*hL@q;V3)&%Y2}8($s@Xu6?MJO8Mvt%hyQX?NYc(EM)_*TcK*0F z3%-P!AvJkU3SoKEtRp&8X%Yjvqy@u#ldFVbas_E_!@^_@$f)V-NI)y5hlp3C3L=*p z6Y0r&`!v>1cg%G6AE&u`FUK<e^=~dP8qVJ`)G?x`W@dHzSf)Oe=jJm^6uE{Nf>o-# z#$e1HM6y5<t)Dn1OSh8u|1kTS&)U*Flilr0_G#Ffz(vE~Xelk8jmSxBU^3u*TbA;~ zrh%CL@G*YW6e7r^fCz!r^k@#K)>>y`pn1TeWlr82PtX%E8zqx!!ajfCf4n*JyfNOa zKH*IP9^PF4shfj7Md@<p%zHG8ETg=_YkwLT>$K@Kg|MGM`>T^^)s*{)U!5L;a%l3w zv#q7Ik5yedlB0I{*@5i+L!x78-e@&v+Sb3(6N8H878^DH55}`t<Sco~*waWJZR>w* z;Pjas2nZ#QiddxgTOr!KKP^5cHw|er&K^w0)V_hWGi42FqnI+9l<-j!oyLc_fY~E} zE8H(|);~BI(B@4dKUbB$V5g4-e4N(61jm1%HMsnI!!&R*2=~hoR(&3O3VMOSXC$FY z$DYmR$CL(@8&%utNIOd5S%r%0`M6+uy=W6fZXU)3{<Dz=Ra>7<GKde>L+l*Q)2)$% zDnkX(z}efzbqIg=l`g6$g^l}089Hump}~1=6c6Ph6iv&9S}trRpw4l{iW_`1bX5c% zrDl%BH3siCNiEo5;8d`!u|?1PQj3sYAyQn+S<+?REPKxevi)=e&FmMHl<bOlH^0N` zHBspvc3P#GA!cRM8hrVDft|AL<q-3SS(3R>pe=f+Y~Ny`m97gf(WC}au1NTuRXk~g zRNI$go%$)Z5Ef=-r1j6?nb&`#WbZ|WjNc@JwL2Qy&np2wJHf;~=lDH6Qr$<I<ruoY zU4m#zfb}Rui+?!*T2zVe8IOT>Qh|1LHuJ(Y#AEhsiHJyz+}+yLeYvS;uw3Si+2jiV zCKliUAL0#GjCsWP<l<SGj;7B)Z@6NQ4i7Y7nbx$$ax>3${X+j%2mO*(&^eV>Q;gt; zL9pf}Es4fF#P@M?mI1?#X9^YPb!J{dpOp9{l{e+qv?SK*W`PgMAx^c`T)FDajmS`i zV^3{WG?Jje2>9smXcxYfk)j9JSBcvuB`)@<(jP5@Hf)bISP<LPYO;ZkIPgm9kOL>8 z7-a70cCg$4NX4wot-2zE4Tl+%vlV?Mfz}9HH*xo%Ht%0<SV!)uI_or`YU$H>b<0IA z#AnJcY=%A+icDZJw!;ERcP~n&tL#tGZ!U;SNXXSjw_xncAS-PvG-U1``y|17S`9FG zpMnCLA}UY)Dam8w0Buqw%L{^5bv&5~r8EgN5b1Wz-bViDF`jA_2#dSS|Cm$e;0Uc? zfsW9EmeTekcfZ4RX#92Vb`e@CIX}dU<`EMtHYxU-L3#hzXTq(1n+hI>0BNWcmuN;f zWRuj_-Yx#A=?Pl1r>8OXvB1`)FNaWKfRv{o8uZijHq5%)ZpL*Em3gAa6H^e`)XY)K zp$w$W##`6einSC+bW1zMxA#zpxeMb=o?V<rx>e7q)z&4oW%cNWLsS<CG?-5nDPG+I z4J1IOAE6w;V~1`sLZ-34*FpmWnl6f3<|bx^CfG5<Wn=V-wx+<g=*w0WS6LCrQAYzh z;{}aN{k7G!iB_Vjl_FZRk)dS)G?nBkldLfLC;6(WaE<MMESSq&=4!OUDyQI0Nw$*C z9am+4M4`%N_!~v{63R>^ge5v87VR^i4~ho@+yJq+tB7Ii{I4sxO^81npU$15Q_Wcw zdXJ|?2Rz3M;QUVsxkDgpiuDA}dj+(K)u%>8>%>LO_r+adUd=p<O9l)sWpzSjqiEW- zX&i+rVU_y<vgT9I!@k$LV@aJLfad1aX_-(vqY0elV&$b|okyK;5dIRhJMQ|s&=(bb zF%S*b>>a{mH3!9xUY>C(Ac?7|mcrGSZAmd@Xl$9One$l2af9+S8uob~L<mEEnD$5w zW~>Hef6dhHSILNCs|Xs&)Y;WjMnfMI<K~br%XX<M^Y;48dYS;`8Y~6<<KCUm(*U^~ zL*+%fVs{;73E)cs(ar+lQ5wMdb@|veTOllzO=RBF+651&<fg}U)LkeNY^OQ9)h*2? z_c<Ef^TV%9=!DpEQoD^TzBjgVV3XCX@~1$7w7u8PDgG&oIhET<v3VzNx4i-BRgXch zh@>-o<dB`scX{f;^G3lM0J-;9{|l`3`u=%K+uFA-AWE)tEAXyMKM2zwY}QQSSp0S$ z$b;`dn@-;482}(q2<mG0rD<&r91fb@Mq8y4`61vZZjh&7KnRrSKQ=g4%vAn^!i?Im zOe>w8TQ!uJr_@yj`nYPfIxi05;0{S)d;QbW_pbQ&8I^EaiiI!ip`;JYW7a!m6q&e^ zw0YYk*m5xr9WSM)VYTWNh>0`TTgs7MnKmASStR)7aGkgtr*J@$WRaNA*{v%RRE#3% z0~Sm$K&g*rFNbCQzWr1+09y-i*~<9L1Qc>N3B^c1q7uUSIf<x?7{Zk{ATx_-r{*Q` zlvgG1*8oGviUCXjgoKXg2Ug3UJf`t<hT+YZMyl!J<<xHFJzvu%$M_K#71+(t7Xn){ zyqb0y0eRX&79w?!IXKnxRys}m4t%v)S*UlOB9?zE1vzZ(_A3hd+i>pV?B4>9t6+x; zbyZ}|c^E?XQi=(8DWh3Ri)y=W#Q*K#50s(9l14+T^z75i{bqloUI%PTl}L1^S`dTi zX90@l*Hpp8eKc}C8G4~xWvLb^ddkcxny+avFisG(-EsqZzH&L-Xx8gznoA2D?}nf6 z2mTFjbYcDU%RzH|N!Pw^m%w@7Pr*ELr&%Gm{AX(pJ4TijEu8eKw+ViL948Z*WU_-% zT&vF;`@1~roKhgJ$krMhHynu!UP*d}Y{WU|n~Jj-79a7}lD+S`Rzaf3t{De=h0Jfj z;|9?Apc6yDbH}Vh{quFRBXeh))TeLc_4;E2Tz`V-z}=MNE%cnJ@?o1mtTWew+Ssxi z$Z;#GU4ljE8U|7On9x?nIDgj3)?!g;_@(??T4<hU@bm^AW`NL;YQw}#r~7~Zb&Nzn zsME&^+fuy;Cd}2<(H!d}Yu5fC7Ewu2RDyQE>?-7+#J`)G^az<Y4F*WYQkfICWrgk^ z-(>Gf&~lV%FzHkgmE3RJY?ffmLc8w6iF>gm8O4*fd4CDiMD>39r2u=rSk4ju{>nId zftyN`h^2E~Q}LouXcuGc{5Fh3zc6;F(*8qIk_?T1OiKK&H>ehZb<dtFXrw|T@<PQB zal8**Rm$BG;b7wmDmqAR%^1(Ok$5aq<`nZ51L<d0wmBuUdXjLR^8<i#6Jg_a^qM-Z z*$84c-nMg%PRyRQ%#o65cIREtZjfXEPDj3aRoBek4uOHB_W<;a5eg)dg$=<^$Ivkj z+d})33k}ErQbAx_(TgX3S9KY7%`PiQhkuXDBGUs#BFFUm0H=}7P4NFF5T8zyC`k>W zDkNj5=ErN0IMOcDicT0r7B9T7P`t^BACMBDR_T)IsoGG0#>_rj^n?DDl1*5>Kz3=u zXXYDykMJtRSYLA}{QoWZA6g4QRg(ocv11bS0coM)>UbUZF^PYt^XnJ&sOjLH_~1nB z2dmq09P27vM(gi=flaMPQO+8Ftz{dpzPbZ?zDNFBGrHTZ^2#zrfca=`>vK7aWL@dV zAXfa9d<0h-YhH6Dvw<(vjiQpd^ib+m@AU$Y;$(hY?Nwsz)F&f%r_A|IauBhX5}(CE zNOg`+Y%S8KG0~@*nJOGo7+$$(E&e@St!YKV&S2QFv$iJpAS9W2QxX^QCYqL%3{n1I z|4nos(Ti&0$rfxp^O2mqR*sr|O|vj=l>eKjI^ovn_NU?!Ffp5m#v=kr`e$O6(kc%< zgP2deUtNfHWTyBuACTC7%<``Iib;%(DI+!#wX7ZP57i_FNG^5b7cY*74y*_8c8Y}f zasu&yGIa#ZBAItw*%Lt!+upgZ0>Ftj;d(gEQN7j>>`LJ9S8P#+iIw(He9ueW=CFgg znQMjT2p&bvZ&M^MTRU{chD?jQ1I_E;aAcVi;1i-NMyoADJpLZ}o`IH<3t-X=XK*%b z(qghe`)z0DKd;9)w{-r%+goYI)1S=$8J!u+E`2}-bK6Z#seufD2)H%oYOe^KkFu%l zd9gp8Wq&0NX44XiM@F}l!xO4pUs-B0X88?ibc?n;_tf2q14~dd&)%cXB|ETt6ZQF} zVb}f`$Z}{IDz&J&Om?grIbnRbw8-_4aN18Dsmc9LvZKJq*(!p!*uW|Tf=2gBnZGD` znsJ@YohsAv%<y1uwpDq~m_#1OjO18EdVwI7rM2gh71#Ttu9o*m<zH+07yZqCd<+)w zW|`xKSk|_(Rh64n|9_u1gqrlwnZfi{H!*!Xh@e|*OF7~q@%{Xjm+zpSr^M;NL4VAJ z>BO6j=GH0)Vs4XqLy9)?o`mRO;6c*KECAAkM{#Kj$x+uc(gs^5;>dA|F@{4-cM<Nd z_2k?KHe6hQ3^g!zUX&NF2!!ZJUy2TX5;GI<e@{a!7Qil*21r}@ST<k)C%D18Kge@K zpP68Ib*!}vxPz!_j;Z}DC29S(9+h|WvpJ4S{!Nkk=AMo^XjxxPZtricP;#w&l|YIl ze;kJ=tj0<v&A)xsADgT2HZSk2DtZ<C$ToP%GSjTIG+4aFjpU*B@exL;+>#YDuRQjr zGs)-23{Q@lW>s0WIju>fU5}B>FmYWajACrP2(>ddsfJr1+rC^iyE=N!Q(AmM)xsuI zn(7$!%n`IMR~Abno{M<VxPVuF(}hY@!Vf#NOeGNVN9vD)LXdVZe-EiI--ik@&ARtL zG`z+zXLOoq>FYINoYOF9k0==TJekb3Y7OaL;d#Rf)ogf+|EZ}C)rr1YA5?2W%mo9Q z$d+<wI2>13_jsW*$4)=73Y?zp(!ysFPKh?Oy1PmU+FG9^h1CS`Ob|Ae(VDf=bS}FX z-$Wu+SSl)+zl6AxyH4raLqbZ4h$p8Ne08_VPDj|Qz13Fq6?$uuLGHzTPgQAORAz2) z_p8_HIezU@YcGhXeSA?24hqJFUufNH7b7ch^nf;ol<5oxcmD&Rx6aOd?Zm@;Zmxw` z(P}=4dSou^go$EgRN>PER<(S=PXYi6^U3z;GlvSj%1C+agN$%~g~voKJ~NXQ(f1m~ z^)Eq{?@BeVwlXlgjZB9oV7FGv7Rlx$BoW#VVi82%G)_N#u95@RBb$}Y*&PiN_6ZVF z`f|~5#GtYc>qiyXH8&(*s?*@R(+&kBnOEL?H1|i8@>n}#4e-_&ojb=;*EB_rclvE{ zk)9KhhmN%E{cfdcQPk*>bUUay((u~amdtJqyG+bVE+d{Cf4UgeWJAPlLB<cbQF2%z zaLjI;v_YyBY>Vu;JnkI?i_ipbIkood{RpPy)>yy9brq+WG}qSuO~0v^s)M15&qH{L z0s#`$rpSIDvZtAtoj3hBcGV82?A^#uc-O2SUhkjvzsrxBD3LrVmvyezAfB?s*|2@# ziO4rSdd7P3m{E)^V7jwnVnRsCOmd;ZK6E`(tc*L;8or}D)+(0-Fa2MQ_cHW0l*<>U zOVN>GO+m&FWhz%7jLh}q!xmVye5@*ri=C2eS>jAE=b}P+NH+R9w-h?9z0AZI`v5yF z@<r+4)v8zeAMfWoLeVTV!+`ORk6kt`+pt$4?ckeHFhiICMIRpfs@2s5y$EDn`9Nu| z$v|09#K64`dyBp~IJi_$yT}r?Ws@@VmT2AASivr;6;&!y8Q5vaZI#)~pbvGJ;AMB? zvqy+#CdZ}FuZn+OqoN}HW+Dq})JJIgMwYszOM6~k-IzSIF`6|IzGV5a-7E~5SG9hZ zl_!RLk4~wyLo>fMFP?r6q_BaSI!9o5L`UR13DNQ7yZGIqJ?YBb5eQMHe%H(f-Fmz? zed_&fi`VtXk-U0tq=rALo4m!-Jc&^dmcRmc6%@RZN1T$}db#ryaS==F16n^S#HG-* zXPvkDcQs)Xs#K_I8gHI8xjEodoyT~t<8Y|I+SM&K@IIt7NXEgegeb3arLR-s_pSJ3 zZEhSQ3BJ=8t4Pd=q$vtZG0~xkS+w)Ov)gMy5J=duj{ae6StVR;z?8UoA+^J-YB}R- zvqpSQklsQ=nsE<UHOqiM8O%4~#@Xot?sm@2&S0BJoz;~eqN>657wZ?aD=a-)!4AEN z@Dpy_&~&vhh}i!M`=;U{Y4Is8)HJSch9Wvpxt$1aaa`Lr=9XLRhFx9^U_GV`3D$qD zQW?sc)7d9N5EOFyVEcXMW0SJ2*yM(Im?W=5C8#NboY-gaBSR3lyGgyH_?a|t6*tk9 zq>1LOvdb%k#Nr};3wqK0+D*Rn{2F=1lU`p(3GtWW_Uax^v;HejtFTZgKbyfAxXkoL za-sCGYYo3aU^sxC#_W|*bK7ame&$~3SC;E6MSCpqYQZJe$uI-bJxddUA%*k4xHK0H z{_q5ZQBnhX-e$YJCCnrOGwip))()jbl@{6xPnRjyA-$6q(xUjOT+Bys{yWYBYS(mh zdVm6$b<~7fDp@}CE4-xwG0Vt61+a3|ZdR&R9epN@l-ZH0w3NTaUNe1ayr{4Iba8O} zG4Wmt2AB2GJ0bRwir(DasF+L>u8=#u*QqlvWjigNyNXWBL@q?=Z!L$87F}A=yVXxb z#_`O&uQK4~OxyM4S&7}SU1oGLu38#i*Zg6u{DTPofd1O(&!0$hPX+EWefL%GLW}r{ zUDS}Sj~XXhl80lrWUKd<a|L2x&y(Le56(;nU2f7}q;Eyz{}k5cIb1u-kMh8Se}I?S zSKd~HjxV(8{-2gNIYl`k3G=Jz*^B~+nCeE7^-pOfEV}JUlbc$NBHL9%k99)(F+ET_ zAm0UW2nqGLY~-Mayp)mxUWq{Z`t<!)YArFOSl_L;=9Ye^TTqiOuF_m?X)$4y9iwC( zkbGi%rTTWT_yoq)<JS+P!urT|J{ftV0z*IZHM(xG&-6Z-e-I0_c%;6_&VnmJAp&9N zlrF)i2(ReC70<fRJRKeadAq3gQ;x2kLC%3py*DqX2*FJ3a+nce(*Mx~!|!MbB~%rX zt4o-Ei<O1Cnwz0<C6>FvGgz{!OO|2~-Rh9ft{G0iHs|;VZ!jRM|68Z)?@i3dvzDYz zw8dJt07O_-#;!s{nie*yo97vxMEqTSu=j7`cX6IK*#{=pi~mf>q3jxIT~OOO%#W(S zaWId~L}RIT?Lzq&BQ7giOJ`zsnLvLs(Z<{JST3i)DW(D{aLJ<-rb6HK{fn83uP&cE zX3dv$Ep3v)O+M(jcO5>Do$s_mDd>Uwwd!FC|B8v=Zf7-7tlxvNgT|&IpO2ccQM~6s zx1~SfwSt`#AW`LWw1QUuwB(h0SZPTkgL_quM)&oFFriMhdV)>fuCnDcDtiJ1&W!}$ zo0owzIku4hfg)D{C~Wwv5`CuO;yiUt28N&dnYuKE{lv8hnDJwWM4NOF`Uu=k5umg+ zCd<HTq8U4Cx?+q}EQ&C05z?{Wk@AQ!Zid<jf{~aC#~;}x-V!cvkEJig!=dB5%>L)j z_r(3`R5%{m#C$Y$s3e=gj(AvPOB{iCCeMyDnlI5&ukjCTkKXgIgZV2%A_JC73KlWd z=5?M*tNILqB!A}pHytc(9<Vo|<H)rbKDKFKf3MY+a#L9HdNql7v#Hs&i*E%_QZ}n# zc=)o$CC{gf_A0#u+W{4&JC+<kYv$1~aHZVKZ>uIB0Tu>J{|I7z-xB{<xCPq;P0~24 zQrsvudhq$H4ma@H6^+2*vo*|iumuF8H8f?&tz*~XcLxw6#0~0}o}kwWUcuYbx{^dR zaQ{2!^I}CcgOzyGmZcu0DQGsjSbsl;r*Nb#DHrx*zojZ&l?_OL{pmMrgoS*IpgV5Q zQhrq9%-E|?mU5^xi`ka`c^H7JdI7@I8Ed2uZ4g`!k21ek&>tT|y*-}s?MHjo)#v@q zB+yBk&2|_I#icuc5;+z0nvoQ>+ksSq;>4g@=-(TjpMoG`HLv&`?6-WGS^-N8yUq-( z04W_?4{d-ES6FUpC)n=S$4a|XZ%1tK{NYT(kB$e>atX=zM<zcTo%I-H3WKN?4e?XF zm+cb;5&Z6<-x_pPfV4x<8h0xG6VrC}`9ZXgi(k{%owU%cbwLV1B-kmw^_QRv_o94t zF?;MI^mtCii`K#r&AqktG3({oUC_b1h)5CwbZ(yZ#0@R_mBWhrj~5`~#GZrmCr#MX zb>Qkl(bixU^(o>W4diJc!GiE2>0MJ84H=*JYJWCE&&Gpob+59T*8m=Qw7E`OIhO}y z!O0gbXhj^+v4dE&`CRQPQyop(^)tI^r*CEbj)!>oI~vQVg^7C6Bl7CM=0ro?og<ko ze-H@nplqiPo5{M?2Hep$G9qlq-cNA>htR~JdDK#Qb{MN}yDGq*Y*p}-{cQ??0DtQ6 z#P5Y+*t-pFHPm|1|F`HNY}->zxr4eZ>1}?;#*N<y=HA5dUmfysz`$8V7INrzdV$66 z5T85lfTqb-(H@*SxYi?M29ttoyIKtnEKwf84Vp86$OHXG<#%J}WOyOqma@<2-|N;0 zz>`<mC9FyJI8UTExRhcS3-m&<J^{NXJm@oZeMLn$U#XW}r434MXq*t6N=9$_^IzG_ zYTSHm2nO|dpT)yDqWrmF_WLqXk1E=kAdNM`fx2~1ogZRQ5N~I+y0y2djn?`Zr<&!s zCGo%Cn2D~R-WoBP(IRhFg%pJH(w@Zl-%}Fk@BYy1q^0`lFi^BoheKkv(b)17QFw!l zMwELPGZg?KePxnft><c#V_6o?s<Fbm9e3sY;*+6sPzDh#20=S}ikJg<dy5m=J<@*t z?!M6x0m4iogxc_f`wZS>krO4){`${+z5$9`eqjrDt1~jX$QYogynz<Tzrzswd2r6m z;(nn_(_QzCtK+|jbSF)0GL5>6mP+Gn29ToP8?I<UWOVxc2$>tLVw6H|h&j2T8DlV? zI5+YGc~Ta*LB<HbOrmpoZdQ*-Msl3I1hMsy?B<VdrYNFVWQKz`c$u_P1$VQF=&_c^ z&)@!|gs2E$vZ($6se4}(3Sm^-PR)Yl7SE{_B(0YL|Ba_IWZ({Nv~j6t8|;c&(qkk* zTaR7R*sQJ+mDe-^j>JlcZ0x_Ib^bfyE*Lz(Y}nB~>t;f*2}D2goifMN<Y#=<*!qsE zI0@z~meJl#aBZp+;;<irt8nXs1k<;zP?AndCF+f1+z+os*&f4?nEb856)FBco*Wns zL7N_{=-|AI&BdLzE^^ZFSz@xGBZ_%{um+!3fETjSuiKEjxRs&EPj2{MxlcC=aHbL* zeaXI@Y>PXs1hEv<!{y5!8_zKW42x)ZA;23<^~6yUm&)0xdo8Hw%Y?AOb0fPcIUwdR ztSNhkjxH+u<QwcUv(3H_h&-CidJ!4PmYe=`N1XL`rI3OE5Kn<ckY1-K_x-GcGcHE_ z^wSA>ub?5BQN&LS=$BYw=y)IZEIVaK3YV-bMG_0wRL)ocre)}E1=W8}_-cwfvz>P| zTb^(07QnB?g3SV~Jd-!j2}AgKT7$7JiArY@>Nkg6Sp=Q)`#<@tYdvsy)Z{~*C|Lv- zw9Zkg!Q%d+dk581c{W0_GNtHHE1&A>yTGxofEUuUYralw#rIxLs`A{fUQ0|wW{B10 z_ZZT}dK@Y^HS$PM2-F)a1p|Cu^{tQo#L!M|SHK2Tfdf@!q=$ew&OtM*+sZG^jPq3M zZ|G+rQrO9Tg?<#_#?<a%;TTbUt1nq<1{U;+$=}k%3?ao4<D>}5FU=BJ!E6!;U@;cM z!bs5U{qXUYi8}->i=e0AKRz$39*~R@(2AhWf}(UhFv`U)?~8)O%H$uq(8Y70HY~n} z>UQ<=(7a>8s`XSB6U$Y6D8!F+L+LV9vcFKUVM}8*NYWFfHH3Km$HLbFxiYYNHC2vJ zXWR^-=V2R1jkhbJsC9iTV|p6K#q-$G=UXbe7M9@%sH}RZQY=JA@R9e7+!z$y^Bq2^ z`fgxaKkzhmH%b0e+{0I)>A_Z08uovdG0Ik?#U@2ZjAGF=5hDPI3L{l&Zrh^yLH$97 zz&T;N-x$VhqO}9nA|H`@0dO015YVwwZ7E8g_ttZ^Nti<0^L6Xz)RH62`JYckD6=wi zQu}nh3obqWG_?-lG78SgHOu`3-L<67?Rg`s293(}R<sG7!+Q1i7U9VYtvO{=qs&;e z60H+~bInWnhnt7E%8mVr<Y_<nH!+9`0*--L&VUozY5`2XSwPo{_KojlSPE8cd4Cqo z5o#z(6xg201(4xWh+Wjm2rpjaLGRcrDo`;80y!CAo82vmaQTm02T`Y({9(#LFrHRR zz=gX;V98vh?lUFI?Lc38=%~#0K%CoHth=)_wRC-S6%rRUe%N>i6F*dYF+RDNs$VTa zH-!>XJb(Obl6F$5@NDt~QpkEw4}CMAo{&;8d}5^N*Eb$6j3H-m7d-q=EYmY?5Y99V zfxiiqKi90-E9s@J`wP(QFMPb4qI_O#JmjV&rb+o~7s`=fIqZIe=tOKo@|e6f!9ibG zSeK0~tnJfIsm2R3{?r>_CdUOvH%U1*npjxO$|I9ErSo9gu<8<*1kZ>gtRZr?=t{dZ z^PdaTxi8)JP8-1rl&^*v4)&CE`EhETl5)=afUt0Ey?;PsLd@x(Z$tg4t|#ofhtUD- z5ZliEq<6dD&26!G9l6k>LBE%cou=wHM^Bn}Uo*Z)vDnmvvzoqsBkY15$YxSKW1-qu zm4d(P3U+SV37kpIjamxD7JENkWQ)^H6PG6(l-b-<Cs=%eprbtGL06m(sjwt_yCcU- zLdI7%*)k{J6r7R3^U5wP#4(|4gbyh(;LX*L{p!>-$2lSsR5599dP~|JE^E2eX06zV zzrlsno*tnL?t{j8#<lSr?(i&YHNpZk1g<#@lQjy9k`a#B!<Z!K(UEFO_UsX(hdzb~ zjFf-I!^+VS%|DQ^gxFQZ_?WdwH>K-!=6ZH-@%s7R?~n+_7S&nUkGv+j1WBfg#~b_S zoozTjxl{Kmnm#AfHGw)$p5Uv!qtd0#hV<xY(E$Ef9#rnJJV)1_5VUb`-)vkn6;Kps zp)21*DC|-%V#zOWeCIikqNb66`#?+nPQZvI{?7A1l&%T1%h~=p?;o2K^&M-kYl?>- z0zDz{euQgtY|TWU-Xld(3*Xmi%HOO&w~^8}6LIiW&?tbj$kN|$db|2ynqksZXc%Xt zH<6_c@t#I6TB>1XC+Hpdiu%|7ja52ZV~(sCE)6A-)gk>+3;K8o)we-F@^nrdQGi^3 z8m!U3G%?XSVD@2vTs3VTl5Y6XIc)zAAK1w9c<&YG;c3cjo)U+i?yC}X!N71V!~+F8 zJI4<O&O`Phy(%<|kRW*-+6eGlt(5)v(G@c0mb77I!j_?KMk2`+9@%=`e*uZUN*_j> zAjii5wW5mswUUF?_bEy?kyndzn#iK%bLL+tgmt|nDHH5{XwwGr32J%i)Ng>Jbn*ZG zF__b$RHj3l40DLG!0U*|be^*D+#1D`wFi=9!c+}QjKbTcB*IxeCT_U;x<c~_6s<b2 zM@o-i3=hX5l+DVnLH@e8O8xdo*ZP`!K~kw$6@gnMv5Fs*O?iYErY0D!1G0h?6n*6~ z49&xWv$D%;4A<m-*h66!uybs;UV2|%@R!1fb4RhQ*N#8%{tUw#R{P+B$@qavhQzTf z@l#m+W8aENt<33|Ikkqb`}83YsE7B7V!k=YXRZmtaKD>Jvo}-cs=NmJ@dq$OMmu9o z-_t#yY{84fI75(@+3^UgQiq^zKgs@BlF}<75n&k6u8+QbJgG{;<axKk6R&lWrVeQD z?r|tQ`pP;m?G`y&-it@1$;?_afX|69Qpr|D=YOEloMz*OWHid``Zz9-xga}8w{(|w zs4>ZX5kOm}>6OIa_XH$#$Mfi{bW$g|bubHiVimFSUvYXeu18g@mO}2l9tZp1-FZVY zwZIAue%9i2fL}d<;H8u)93p}i(f+VU=ZU8Wez0Exl5B-i6^N58=j;UlSGTl20t}G$ zGthkJ62XW^^HKw!V3{Dwfb3OB-^0I(Ix(P_l{SjHt8zp05$B+@fMR<|<HK$Tg_rOH z5S+H2_E9;R8a@NjJTyM^HH^K^!8}21H!{1L*=Dnc#HHhs6O}fkp~Y6UNW}g5K0<l5 zF7ejIM)M_?ymfWQtFvUUuQeddD%dUDJx@K1XM9Q|)Uwa1&AojbbI6ZPwRIqRvlIk4 zj9F_M^>x$rV?E|rV`gEGR~61ZtGq5MtOJHE=z#<a`Vf?Baf~xooE|^P!wF2I{*-%% zt@;V%b-nu^k$3)vpw^tOv*QbQRCf(k=*e+}t%OO|sE4BjxX)di(Ka^+7O5V*Bbh_8 zV>d7C)|BZjJ&9Dla~Jg+1kU<}5z_U8u`+Or_tCB-;Aa-3C$sz-dG<o-i*NF3VzlFQ zS~7YI2PMB$VA<(~(ZxPuq~1*19O{<GVU@MX{?fE?E+{r4Nb|g{|7mB$I!8|`b)u1g z%J{{yn~TkO)@eS_yci0P7`ibv#eo@bNu{>$LRWY(3<(NX(3msCw)N6%w)Ns4tnLW~ zKJer(>l<@TzUYsO!~2v(M#G%R_B;?fw#1C;lUslY^IAZF^TuUiOYBr8;L-g&E1DW7 z+^a;ixr~fWo0Z(??AKB+_HudDB|aMr%Hc21RhOEI-rO4i&@LPHc7t(joYM4@HV%G> zzgJFMG;GwPYsi<)tEI+Yo<d#gmn9`!i+`<e?kbtpU9sLs)yc^~f0o@>S0d5e`Mi@1 z2vSP_q`_m36ywMY=g>d>^<l8>S)q-4%<S8z)`wPRnwE^KD86^#g;7H`%P_Wnc>}&O zILVf@y*e3G>%|*)V<8$FvXl7-L^XdeACj(A!GR)YEAux4`+TGPf_ahzOOmHZca&(E zZ;PC6$(m=X^BRZSERfMhOVDEMf4$Cj;iVY?Fr{Qb@3EZGEZd^Jv5=43ao838&E{Np zo`1j&37Y_@id758VT{cZNRnpY&Jb2I!On@!7#6E0r>G6b145V1NQucbg!S3gw(SF4 z8SToNyoo8e{&iHtGiG)DCl}+QisFTg#)u_kyCx&UiMYR^^OF)<PbqImIsi*{{MaPX zo8AG~V&w8+4{}89XS|o;N_I|PzF^JLbgT$4r%wFZMv>b$9MH4(jXf^qCVoTk=hc0^ z7pGIhl&Qe-c$6WYmfT8g2u$MvS8$6L_whO*;nCN@6l@t_!2)Re#j|ZJDC(LBe+YBa zf?dYy&t{Pu2FZq1)MP|Z0b!26dmYf1$d&SM_$;yOi%?XQpa?0Wcm1ua;WgMYq=0>F zooGM;!tym(5E|W_;9m4g8<B!Hq{6US$i|;1kJYH~*6V{uPv50ymNsk~`IkFDjMb;M z!}LIEbW-(?K-7ctFIA(f358xXfe^9aB-)Eb+>yru>s_1Yr3FaCkvxeM1LLKpe~C`! zYzt1(IV@!(Y@xwFvdbYPM$P&KdmgLs(Ef=ae@aymObE`Q{Q?2edT$l6`QS?@QX~%> zGeLxX7{GPQ*p^P@ONn(_zy<XZnna9Ic=&jJcs^2<`&UJ9Jno1t!&9_MQ_7+Ey*)sf zq5x5`#E7}l((*AU-hHe!LlZ2_ylMBuX=CAi&w)Ws0=&B=E}PAT-CvSOR*NtU#LEXo z71X-GGf+F;CCBGH@OzC%=4-9XB&gukS##9?q20_#Myr7m(O9>mDMrXsCdUZz{?9PB z4t2uWFg|PN1sm_$@Y1bK%RaLRYI0+5FoG?SOlwDMEE2w%7*?AEW`w|^LzjwE`XXi5 z+`MNKPy1LUtb9D>+mku2n~r#HckAo|0<gkMwgD6FRazL_m4FQFREqHRAxx}>ZUNw^ z{1epjw&wJQ9^F`~%oo&>CdP0%=iJ+Ya}3Hh7j)A4m!gp|g_ao6Ugd6Zxd75Bn856k zw^b*pcP_9esLc&Ss>(_w*=%Sfff4%`8$NP3&_@N{bIH85-%*w^WJzQgUDTrVvY2Y+ zYRKXlJeYu39A_c`LG~x(*v%Sc62;$(Pe^o`z}Y_@cnYpSUgR#U2Qw()3W!JqtXVrk zkABiYw^@#N@kYSw^ww|GlSE-&K+se#cFrx~JX}(BSF5)?)DjB*gaPd*wp}2usW8<| zd_M{_I<i`DeSm8pH(WtCSr?wcq==<|qrofXpPhY~L;w^c@ph}~TNpl1E>25o+<4t4 zQ}z-be#<gl8c$f1B-Cb5_mx-!e<8ovAF_&gf|<pgiF`5bFh04s=;WNUB0ks%JiSp^ zzuxu#!HiW^$>+Q%^TwE>VbWe4J@v~^`{p$&y{M1^;qNxBHvq#BRe4=+&y2YcXhSpg zL=($ijKh28mcxL&+iVarqxZbMBAYJZL*b3#L{c4Ptw)I2bT>DaCCA!h7#wg^8{4-V zxay8Vl8R|$a;fV^I(nWe)(d3aV$LF*4Z$zk!7Llgvn1Z}8WvpI`bXPuix(zbo2K&Z z4%S~>YTFRmbvGs0kwIL41w#xYbcAe?=E7FJ`+*MqD!C%;t4w8|C6C_xymztoABOfR zG(_8nKN#k(M(O`vya<%+87Mu`y4Rbj1*^kuqE->Cb*T<Vy$M(B;xZ{Ign$#;N;06b zP$I5gZEa%&laKQJxHjkQGW@CWy%_rv1r!MUWPS{&WSS3Vo_{b+%b8V+j|duisoy1= zK&iC}=?u-kOE?mu11DMOsa4DYYb=YPe*5C8=eCJEUl5AE!iVo(*#xzRA-J)W6O$R% z(fvT7hKUF+B7uygZ+R<ySxv1*0_j6@uXl_Scd|NrzSTP*s8_y4%j+71>%0=KPqqS! z-{hD-p;oMX&LiFNh0&U&)4o*?m<xGmKCX=`mzFdFu=WJ-+@61mZ{jR$zW5>hgRj)Q zg3<m4UX6(q!h7Ne!&UdmXq4q4j*8w|;*3IqzF~2AsN)AY#_mjB2B^XlpV7K$H_WD{ zMAb!X%G29sF0L;nm;fxVH5V<LY=B!V1wi(Gfpi;pk<dxnC?K6g!xNF)KOB<3ow38~ zp;gEXD=~RNj;Kaf0@d6?yVo^+WM*#W5gw_9L>LoGq<D-1d@DK==Kax1DSwBc71_;w z(6m-Uw%s9d@Um;07njTLw9&^@G+YgFA>B<IpQbVzL(1(2#!dIhlOivM`x$mB0Q82W zCTk(G+_4(MK+7ww=HeMy!({)2HUFZ-roP6bGUY&bUr|BT={VluP1xKl-Y9l}0X}12 z_)c~|-+|$-e6W<le@U#Rk#)2hI&ch<#^8RA>rS9PPOzaTCQVDV8`0HD@WYd#Gn@}N zScqOV?(86yf1iA8QXr;m*b%DX$M8<57gsAY>Qn9;ebx}SqMuScp-`6R#2QX5u*VCT z>R<1$i1Sa#5gUG{{hQTRmXhTb<tPAh+R+**264CwJR4pLAD&7P<*=;}wt-ldK{P0V zk=TZwN1XOwi2&J+;JRi@+v4U4jwPP4wAfASUuK+5ulkeR9e+5c;K#&i#mNB~=ysJB zhQw~faVYbtv{`vLAl!lRRGOkkzH({NXRc!7Npu%k_;gm}a@ADBO`*bgG9|KLpUcp? z<l8^f`YRl|+>M$6WX%_e-L{z-8Ery&Ph<|M|EyHtr_M@8vt9}TZB>&WQ`N1`(vzYV zR8o+h*0TrACHyK4S<}LLPaWf308G^V<E96aqlY(r;`fYY7_z(z7bq&Dl2Fg!5tseD z_`IYx_<h-lfTGk`E>}tMMZ-<EuShYq{)e+As@#RoVnQo5LIApMK}Z9QQV3OVG+0&} zGdFu$0SEF<g<k^^{j@YPVO2=_7150r<L6=5cuS1)+{M7y-g6<byGHnMJRF~YK_3<W zw68|*WQMkyS|LGrEeofAKFtw6z;D_#vsbAAe14azrs;(K`hG<`W%ddDn_{bF<q_3) zI^0<~VNQr4g$NX?`k8yzy%^@K&IM`SB^0<Jyo|z*k0)>MzX$d!NN4F8sS6$KmfL(9 z=j(YG6mubn!o|mw#AtOBtACT&IS(dp``eO7sO8AT=<nLZi<yuQFz<3h?sEedSpHv0 zJUX4uPsSVK;Lv=v6!nTy&>sZfxf<za<g45?9~~#1H!IouHvf!sey5b~#kMD5%pX^| zyMkdJmOYU2T?JBS`O`N&IlP$x8P0o{_CNGFccd|Z=)g}(@*$U^*B0nC8p*h@H^RPX z%C4Jch9lv6RtYBqVN1Z8a_ST*Py>Xo>t7XXH$Z%SFFnQ-yp+n~!p0)ei-AKNH-}4` zXiscx*Wa63eAtfPQj%YWk&N{BW;3~R+=KnjyR*#b0}VidO*X?JNS8+Qb4&`BKGw^g z7D2Nd3;z}!Db8Am8xDq~GG+DTCoH{2e!sDn&&6D&O0X^0o0Oa@4j#BrJXT4VI#2mc zSNtxfqF~mY(d+_w60xWA(Y2O`pGhq}j*F`3Il~+UB$$~w**;rk;qt#4P{5-v5w01M zLbZjP7jaJQ0>>^`2(fFkC<LiM75eOFcZj>^hmZya6e<-4`iTQnWJr+I^Rd4Lm6E5X zRKMZQK!Nk?bR^YkoZiTUM7BB2%1)vZl8q~0*m~A6zo!}JmJ`lV@k9PaMDt1%%r0T9 zjZ?|e&>y;#D`!d}2_G#fOR&z{HHWnux#>;(m-hf0TnT^?Y5;sTl$PhxK1Z^Y#t#Qb z7bE#e@h}7RkYqc&B7FWyZPcU{s!IB^b?V<Fa$uy-N<Gh0yPaGzgy^Spi;=~Tp%ECh z+YUHFJT^56ZLaws?9_-6Ac}9E<3#1arPw!X%CO}EAllA)P=4(%*o|x5HhB_1cFB%i zQ8eu_wL~GMbJ*gU178tN^WVkCPoS%?sB1&vlv)W;TcE{wFn$mnl$`U+&pba9joAQl zr6Sywqln;<{Um$R7}MKxDNyA%wa)SCv>H*Z3pAz{ySJUhyp)M~EcIfUKVmO7bA#iy zcEwK}yTu4WT2J~6wgZT0uvOSzRg-hB*o~LlWiO0Aui(H#6kJrg{?rthOX6@^lEXg) z@L`Vv-DGR{fJ1M_dWB(l@hNX%JKIop5k$8h&W-9T5`2HM_Wo&AIs}@x-G&x9X~CP) zC&FQljHVb|&k#H3ZN3$xLQP@fXe((_5;k+Ti5U|usyKMANe|6N&eE9qd})ciZnqvg z7mR1=;UHpdPg=u~p`uXg972Lyb43uLriI8VHBe^$MmEOuiIlvI5oUs0+yNy{$aJb5 z(@v}#G^c78M#F9i=yL0Ts!*10y9}9`GD(t>3D(&-n#_%`lXzK>ugmA8W1oQ6mrqGM z6Ik41wx*{Uc(v0V#PFBOJiWsJomSUN2Bhpc(3A;5Oh&DDQx=r)HSvx|RcxRr9+^US z&NtZVUf>cA^{`1R9B6JpFq!DZlGlW&<&|1sgJMxfPk&pSC|1=^a3ThhRQ`;~o`({# z6s*@;t**8Y1sz7}P!XtYREOqhl7y8Xv~YwGB#8(8ap_dKLzabZ+sgK%hDoVTuGH{f z@iu^yJGClEa|yiyz*Vy1u!vHxzPMTHr;Z9~rCDxcWTfDP+liEP-hlXXwz>&epPFw) zY+W%uvbtBY6!!L`Pl1x&8dWMx(yfkWbm6Zc-5p9w(s}8~ut)qhT;BBxM96u`k!i|i zhLQiZq$r+8CdD{R&H>Eor4`-(pm@FH9MQ{_QfO07kGBSRW9t(>F(h$ldf<)KAqKIG z{GI-k(#NZ;>|HS(2IQ3*BT|gd>V`qePJz1PTf6%nG{iL(2K#vnmR8PMa!!~ZMOw(& zuES&^WGO~0TS>&Lwk^pesSOwqq}nLaB~iDMnz#NV>_2Gwmu_nu5frJ>WQ#w=Q`3<> zz|0cNZL2?1w}^J=Hk7O#v-v|s9?{Eb<`1!{ul6lf-kv{C3gn-6%~by}WX%f?whZ;| zX=k1kqpP}r&S|@%!$+`@$<GVZ{}Ep9SYXab?u8qpZ}wB=xo9RLmyzU@XRT(>n^+=! z)9LXFjgbDaR6)Rw_y9~t5c70@)W|%LF}p`)o(Qw;_5_n5Xp$({N&@*y%o?MS{(3(r zF59h(!Z(;e=Y2m?U`+h2;uUV5?EsuEjd8?Th&WA9x6r3&vCLJyX%8cnTg=L~nzAJt z`HY^RY&=Zfm~2>bphbYx16|yPw$pavomF;N_?gR5m?+Lm^u{P7^utUr+i5>@dvUPh z+MwLXhLFXbj!--cgSRfk@X3^9OS@H~3xh_w=F|Vim|?OA8~}^nsOG;K3AcHI)=nYM zG>Z@1ttfnK^)$LeF{!)>%sFh<8IN;R?<49trd-ym52c5_zJN0R`kI#8W>RHVmKf86 zJWKg-U~qJ5Q{E*XTawz!P}JO^Rf+OY#QnEwRakLF&E&S3XU>)B5TU~(`jj!2m%xT^ zObm8wzuRqCVJBl4^28Uo=BB8p&brDw{-p<yb|P7SAbd?@4^b77Nrv9L(ZA~aXny$t zZ56Q2OY*Rk-Ht5xY??DC2O}W?xZ*W24wJ*p7(ldDw%2hR!xw$t0EX=f4EU6eTleL8 z1nGX@bk4|T=P6}z10tY~#<b}K_o6dXksGg|iFv$YTwS^ozimJpFcuAC+4AQg7I(sh zkBp*S|5N^F?q~aQINaFhg;wPrGo-!BB#s02DA%EBeP(kG8olAH9#SUR4($>TIm%&V zpjLBDD#?8N@DYJr%b<p!-z=+4_CmCCWkAab*zfj7Wc2Q@Ylk%-%YAMn?v_jzv39$J zZ!m;{bCxO;&p&H{802EW$a2FYAfY`}bp7a1<eG4ZkxoLkwaX4Pbif&EdE=oNHB-!i z%`hdlqP*6-pf?j{HK$rPz4*ON*Sl?TlR0T0?#;zK0IzpeNsaYhBC<n0kJkZdm5EWh zGA%+v@uxWIRF@u$ERN0x%MB@ND*xCSHdUBp_@9Ct0*=Z)J^*S))jvuQxVvQb_g^Eg zmnMhtEhIiYl*PFi$H1IuCM6o=k`4^)#eZkV{LKyWQdpMMM-mI2%z(`R_zs8Y;euMl zz{}<Dhj0ADmAc7$;4JA=MJ0sC+7XwdyMvHtWSRPyv(9rdkNj_SOVK72P~zptiI$N7 zL>EC!5_+z5q=7Sj0ij*HJwtjrwSbMJuVT5s#Ri%Px`z$I{PcrfD&XR&NEb??z#;61 z%FL6GbecKT9$VDYk1CD>#F(?tvNe?WJWHM)qGt8B22H3;k<>w%620RNu38K(dJ>3V zBf9kfE1akbptjR;A<YKC4(Y^%m8bc_&>JUoIKuW}omKU9UU&C?Jx_{-hVw~|3f-J3 z_JaTIJ*2!LynuTcgHN@UUDv!$fS;b^d{J{nB7iO~EdU~*uhR;`#hM21Nzc~~-Bb@6 zA@!@eLCXOG4!hA4#B*w4_1su>=&jb@kKX9pE%?vqIyOJ^`3*4<&ah<u?RQh7yIB}6 zyn@sq++R6xBPE?K{WS?I;odnPx5n2dNN#$Kkkoouq$w>n&fjd?WqV;O@{jUT?3n~m zkg^G%K*5vj*?B52{UI9?8hxJ<cKyhQ&nI0@XN@e=HT+@la%0b$m4hyA%##%HPn8aa zYV+=_+QWujFffn1P}o&FPDWoH|DlFz<^Ouw86*p+<abRa>v@-L2f;0jMToT8vA4U$ zh2N;~hiSotlD1a<5ir|RD;XbhiFw#~uy4eE=T@?jvpQ-dM8;etNCB7flMFX8zwO4? z=`O^hKFz61>gGYK5l^lyk4>L~v`^2!3(4V1_VNOzc$tk(X3)i<<?d;{IijFr4n}A< z3S@OZmY&|5cT&Mou1oR#JM#+I>f+k`n(b}jr^gV<UiNoG?R>;TXN$wsHr9}r?=Os9 zMYMy_s#=^GZ9u27zvmi*7ZBV}a~Dr+^T*Ts<Oc@;7gy_iAZmUGIBFU+-<MpFT62%M zVVb!Y*%`LxK9~R`5ACK}%+culy@R5GPyEz|YxYo3TDc?;m?bjWuTCWq&Mzg55+#2U zH678hqZdXWIwf(5x+=^vgN@bWW5JFTe4HmgTS5OY=wA<SSpI7JUN2XZEgb6()Z1ty z2<7o~U%pM9pSzvF6q8cRb@NTsPTzI;6N>g}G7k)AsVi)VZjAYb_RL~2nd?&=HP5d{ z%;p#iG3e&4C)K`4)zdQ(PQ27Yld`!tkCjHV1Ep?#WJ!*!RN-~-7L^^@QFR8ogTuF~ zAUQ8g{KKcl(^@$|WaNVR`lAEUcLkU<?V2S-_^<!g#doz<#-Yg)yJr6J*7Li1!b`S! zH`yG*j|rZBi4Bl$JUZXhY(nA8P$g=uO`liolgzBG()HCYvI~lhFZgL)RbXPLsmt_+ z(jT&*XUKUAhY4MbQJctSGb05##@O0B@2a<<b~fgTvs<uXetitb{^E&5-q^M<rxfd< ziIKgqT$0~%c}1CE8zXF5dIuBC@ur0Wp-ghs@2i>&5mfC`n$AV~+=-L^|Dr1l*A9wR z3MwStI_Pmydkeo}=Ot2u%X)7=Ng}9p2Mhx%DqzK%mHo}PHO7U<?0SdgB5tQokIUQg znihr`5BZ7XfR)JW+_*rh1WZs1IJYUnNw|5fZ*yhn;@ONXHr_WkWf>g!kF3zY1nAWi zVwdGdSz-1g^#b{D`lBTC0(EY_A6Q`m=2ZiPuGvebRYrtsL!R6Gov2J4Eh_WS?dZsk z#ShLd96xw2lOhF}CWG*RDoxHYHW0hA+r|!c84hB$r9nc`M<|+eTuk8_otbq*+Ebc6 YwSDhi*gK>PbmdTG@GCW{9Z*a_OD8;B=>Px# diff --git a/src/bls12_381/tests/g1_compressed_valid_test_vectors.dat b/src/bls12_381/tests/g1_compressed_valid_test_vectors.dat new file mode 100644 index 0000000000000000000000000000000000000000..ea8cd67652d133010e79df8488452450b6f5cb17 GIT binary patch literal 48000 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 Rm?nepfGSPSF*XppvfIk?3d#Tg literal 0 HcmV?d00001 diff --git a/src/bls12_381/tests/g1_uncompressed_invalid_test_vectors.dat b/src/bls12_381/tests/g1_uncompressed_invalid_test_vectors.dat new file mode 100644 index 0000000..e69de29 diff --git a/src/bls12_381/tests/g1_uncompressed_test_vectors.dat b/src/bls12_381/tests/g1_uncompressed_valid_test_vectors.dat similarity index 99% rename from src/bls12_381/tests/g1_uncompressed_test_vectors.dat rename to src/bls12_381/tests/g1_uncompressed_valid_test_vectors.dat index 5194b62b60cc57a469de0e89de252a393fe0eaa5..86abfba945c7b701750d637bffe6701330e10e88 100644 GIT binary patch delta 108 XcmZp8$J+3YHNk;_wxD4P<AO2(dBX+L delta 110 zcmV-!0FnQI?gfDE1%Y6-U;&_ZU<M9#84hB$r9nc`M<|+eTuk8_otbq*+Ebc6wSDhi z*gK>PbmdTG@GCW{9Z*a_OBd|0jZ3o|e(Pm{=75Z9zgU%sMY$K?g}SP+n7OE<?8wUY Q@t|x*u=jy0393@(px|RRs{jB1 diff --git a/src/bls12_381/tests/g2_compressed_test_vectors.dat b/src/bls12_381/tests/g2_compressed_valid_test_vectors.dat similarity index 66% rename from src/bls12_381/tests/g2_compressed_test_vectors.dat rename to src/bls12_381/tests/g2_compressed_valid_test_vectors.dat index 42b6ee9c01f3e839729bbe3dc3310201afe6e2e0..a40bbe251d90e576060adb7eaa2456197878804c 100644 GIT binary patch delta 3513 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 delta 3515 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 diff --git a/src/bls12_381/tests/g2_uncompressed_test_vectors.dat b/src/bls12_381/tests/g2_uncompressed_valid_test_vectors.dat similarity index 99% rename from src/bls12_381/tests/g2_uncompressed_test_vectors.dat rename to src/bls12_381/tests/g2_uncompressed_valid_test_vectors.dat index 7fcbcc9649d5d25489b29ae1ee99db6677147118..92e4bc528e8937a311b502e4940e5e363a1f7c57 100644 GIT binary patch delta 207 dcmZp;!`*O)yWW9eK!GF0wl}meZeenG3jp$l2DbnJ delta 210 zcmV;@04@K3+zWu*3xU9ezyY<u0zmD55KuZni!rz&7H?-=yOTf$hnIl*GU-XK?kKy% zb`R!%f<RNi)8`>c*Qp|YJMANR0w&hrK@SIx#5bDNxUONT(UIpyXtKX7)+y<9lj`}z z<dY7qdhr<>yFt9_?>+W~6x&Z9Le$4De01=zbDpoX^P^O#O?&&2Ob@AxSwJ2^se(f5 z;L6V7LwKOwruEA5u?@Fd-7F*3ieeh?vxRtzs=RD{#K8{ntpsZe$_$io>UJ>@Ip&UW M((_fqPlYz*S?hake*gdg diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs index 6baef49..41a54d7 100644 --- a/src/bls12_381/tests/mod.rs +++ b/src/bls12_381/tests/mod.rs @@ -3,14 +3,14 @@ use ::*; fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected: &[u8]) { - let mut e = G::one(); + let mut e = G::zero(); let mut v = vec![]; { let mut expected = expected; for _ in 0..1000 { let e_affine = e.into_affine(); - let encoded = E::from_affine(e_affine).unwrap(); + let encoded = E::from_affine(e_affine); v.extend_from_slice(encoded.as_ref()); let mut decoded = E::empty(); @@ -27,22 +27,22 @@ fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected: } #[test] -fn test_g1_uncompressed_vectors() { - test_vectors::<G1, G1Uncompressed>(include_bytes!("g1_uncompressed_test_vectors.dat")); +fn test_g1_uncompressed_valid_vectors() { + test_vectors::<G1, G1Uncompressed>(include_bytes!("g1_uncompressed_valid_test_vectors.dat")); } #[test] -fn test_g1_compressed_vectors() { - test_vectors::<G1, G1Compressed>(include_bytes!("g1_compressed_test_vectors.dat")); +fn test_g1_compressed_valid_vectors() { + test_vectors::<G1, G1Compressed>(include_bytes!("g1_compressed_valid_test_vectors.dat")); } #[test] -fn test_g2_uncompressed_vectors() { - test_vectors::<G2, G2Uncompressed>(include_bytes!("g2_uncompressed_test_vectors.dat")); +fn test_g2_uncompressed_valid_vectors() { + test_vectors::<G2, G2Uncompressed>(include_bytes!("g2_uncompressed_valid_test_vectors.dat")); } #[test] -fn test_g2_compressed_vectors() { - test_vectors::<G2, G2Compressed>(include_bytes!("g2_compressed_test_vectors.dat")); +fn test_g2_compressed_valid_vectors() { + test_vectors::<G2, G2Compressed>(include_bytes!("g2_compressed_valid_test_vectors.dat")); } diff --git a/src/lib.rs b/src/lib.rs index 2d600d9..210da01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,13 +184,13 @@ pub trait CurveAffine: Copy + /// Converts this element into its compressed encoding, so long as it's not /// the point at infinity. - fn into_compressed(&self) -> Result<Self::Compressed, ()> { + fn into_compressed(&self) -> Self::Compressed { <Self::Compressed as EncodedPoint>::from_affine(*self) } /// Converts this element into its uncompressed encoding, so long as it's not /// the point at infinity. - fn into_uncompressed(&self) -> Result<Self::Uncompressed, ()> { + fn into_uncompressed(&self) -> Self::Uncompressed { <Self::Uncompressed as EncodedPoint>::from_affine(*self) } } @@ -230,7 +230,7 @@ pub trait EncodedPoint: Sized + /// Creates an `EncodedPoint` from an affine point, as long as the /// point is not the point at infinity. - fn from_affine(affine: Self::Affine) -> Result<Self, ()>; + fn from_affine(affine: Self::Affine) -> Self; } /// This trait represents an element of a field. diff --git a/src/tests/curve.rs b/src/tests/curve.rs index ea21f90..e3eb0dc 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -299,25 +299,32 @@ fn random_transformation_tests<G: CurveProjective>() { fn random_encoding_tests<G: CurveAffine>() { - assert!(G::zero().into_compressed().is_err()); - assert!(G::zero().into_uncompressed().is_err()); - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + assert_eq!( + G::zero().into_uncompressed().into_affine().unwrap(), + G::zero() + ); + + assert_eq!( + G::zero().into_compressed().into_affine().unwrap(), + G::zero() + ); + for _ in 0..1000 { let mut r = G::Projective::rand(&mut rng).into_affine(); - let uncompressed = r.into_uncompressed().unwrap(); + let uncompressed = r.into_uncompressed(); let de_uncompressed = uncompressed.into_affine().unwrap(); assert_eq!(de_uncompressed, r); - let compressed = r.into_compressed().unwrap(); + let compressed = r.into_compressed(); let de_compressed = compressed.into_affine().unwrap(); assert_eq!(de_compressed, r); r.negate(); - let compressed = r.into_compressed().unwrap(); + let compressed = r.into_compressed(); let de_compressed = compressed.into_affine().unwrap(); assert_eq!(de_compressed, r); } From 451b2c30ad034d6bed397c786050bdbfb3b03a7e Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 16 Jul 2017 21:50:03 -0600 Subject: [PATCH 014/140] Enable clippy linting. --- Cargo.toml | 4 +++- src/lib.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 527b14e..3d517d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,9 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" -byteorder = "1.1" +byteorder = "1.1.0" +clippy = { version = "0.0.144", optional = true } [features] unstable-wnaf = [] +unstable-features = ["unstable-wnaf"] diff --git a/src/lib.rs b/src/lib.rs index 210da01..b3df2a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,17 @@ +// This library relies on the Rust nightly compiler's `i128_type` feature. #![feature(i128_type)] +// `clippy` is a code linting tool for improving code quality by catching +// common mistakes or strange code patterns. If the `clippy` feature is +// provided, it is enabled and all compiler warnings are prohibited. +#![cfg_attr(feature = "clippy", deny(warnings))] +#![cfg_attr(feature = "clippy", feature(plugin))] +#![cfg_attr(feature = "clippy", plugin(clippy))] +#![cfg_attr(feature = "clippy", allow(inline_always))] +#![cfg_attr(feature = "clippy", allow(too_many_arguments))] + +// The compiler provides `test` (on nightly) for benchmarking tools, but +// it's hidden behind a feature flag. Enable it if we're testing. #![cfg_attr(test, feature(test))] #[cfg(test)] extern crate test; From 09531d081003e4fc8790d5009870e535449bea5e Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 17 Jul 2017 09:06:03 -0600 Subject: [PATCH 015/140] Add error logic to decoding methods. --- src/bls12_381/ec.rs | 122 ++++++++++++++++++++++++++++---------------- src/bls12_381/fq.rs | 8 +-- src/bls12_381/fr.rs | 6 +-- src/lib.rs | 87 +++++++++++++++++++++++++------ 4 files changed, 155 insertions(+), 68 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 09824c6..6823f73 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -111,10 +111,6 @@ macro_rules! curve_impl { self.infinity } - fn is_valid(&self) -> bool { - self.is_on_curve() && self.is_in_correct_subgroup() - } - fn mul<S: Into<<Self::Scalar as PrimeField>::Repr>>(&self, by: S) -> $projective { let mut res = $projective::zero(); @@ -560,7 +556,7 @@ macro_rules! curve_impl { pub mod g1 { use rand::{Rand, Rng}; use super::super::{Fq, Fr, FrRepr, FqRepr}; - use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint}; + use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError}; curve_impl!(G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed); @@ -583,7 +579,18 @@ pub mod g1 { fn empty() -> Self { G1Uncompressed([0; 96]) } fn size() -> usize { 96 } - fn into_affine_unchecked(&self) -> Result<G1Affine, ()> { + fn into_affine(&self) -> Result<G1Affine, GroupDecodingError> { + let affine = self.into_affine_unchecked()?; + + if !affine.is_on_curve() { + Err(GroupDecodingError::NotOnCurve) + } else if !affine.is_in_correct_subgroup() { + Err(GroupDecodingError::NotInSubgroup) + } else { + Ok(affine) + } + } + fn into_affine_unchecked(&self) -> Result<G1Affine, GroupDecodingError> { use byteorder::{ReadBytesExt, BigEndian}; // Create a copy of this representation. @@ -591,7 +598,7 @@ pub mod g1 { if copy[0] & (1 << 7) != 0 { // Distinguisher bit is set, but this should be uncompressed! - return Err(()) + return Err(GroupDecodingError::UnexpectedCompressionMode) } if copy[0] & (1 << 6) != 0 { @@ -603,13 +610,13 @@ pub mod g1 { if copy.iter().all(|b| *b == 0) { Ok(G1Affine::zero()) } else { - Err(()) + Err(GroupDecodingError::UnexpectedInformation) } } else { if copy[0] & (1 << 5) != 0 { // The bit indicating the y-coordinate should be lexicographically // largest is set, but this is an uncompressed element. - return Err(()) + return Err(GroupDecodingError::UnexpectedInformation) } // Unset the three most significant bits. @@ -631,8 +638,8 @@ pub mod g1 { } Ok(G1Affine { - x: Fq::from_repr(x)?, - y: Fq::from_repr(y)?, + x: Fq::from_repr(x).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?, + y: Fq::from_repr(y).map_err(|e| GroupDecodingError::CoordinateDecodingError("y coordinate", e))?, infinity: false }) } @@ -681,7 +688,18 @@ pub mod g1 { fn empty() -> Self { G1Compressed([0; 48]) } fn size() -> usize { 48 } - fn into_affine_unchecked(&self) -> Result<G1Affine, ()> { + fn into_affine(&self) -> Result<G1Affine, GroupDecodingError> { + let affine = self.into_affine_unchecked()?; + + // NB: Decompression guarantees that it is on the curve already. + + if !affine.is_in_correct_subgroup() { + Err(GroupDecodingError::NotInSubgroup) + } else { + Ok(affine) + } + } + fn into_affine_unchecked(&self) -> Result<G1Affine, GroupDecodingError> { use byteorder::{ReadBytesExt, BigEndian}; // Create a copy of this representation. @@ -689,7 +707,7 @@ pub mod g1 { if copy[0] & (1 << 7) == 0 { // Distinguisher bit isn't set. - return Err(()) + return Err(GroupDecodingError::UnexpectedCompressionMode) } if copy[0] & (1 << 6) != 0 { @@ -701,7 +719,7 @@ pub mod g1 { if copy.iter().all(|b| *b == 0) { Ok(G1Affine::zero()) } else { - Err(()) + Err(GroupDecodingError::UnexpectedInformation) } } else { // Determine if the intended y coordinate must be greater @@ -722,7 +740,7 @@ pub mod g1 { } // Interpret as Fq element. - let x = Fq::from_repr(x)?; + let x = Fq::from_repr(x).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?; // Compute x^3 + b let mut x3b = x; @@ -747,7 +765,7 @@ pub mod g1 { }, None => { // Point must not be on the curve. - Err(()) + Err(GroupDecodingError::NotOnCurve) } } } @@ -873,7 +891,7 @@ pub mod g1 { infinity: false }; - assert!(!p.is_valid()); + assert!(!p.is_in_correct_subgroup()); let mut g1 = G1::zero(); @@ -895,7 +913,7 @@ pub mod g1 { assert_eq!(i, 4); let g1 = G1Affine::from(g1); - assert!(g1.is_valid()); + assert!(g1.is_in_correct_subgroup()); assert_eq!(g1, G1Affine::one()); break; @@ -918,7 +936,6 @@ pub mod g1 { }; assert!(!p.is_on_curve()); assert!(p.is_in_correct_subgroup()); - assert!(!p.is_valid()); } // Reject point on a twist (b = 3) @@ -930,7 +947,6 @@ pub mod g1 { }; assert!(!p.is_on_curve()); assert!(!p.is_in_correct_subgroup()); - assert!(!p.is_valid()); } // Reject point in an invalid subgroup @@ -943,7 +959,6 @@ pub mod g1 { }; assert!(p.is_on_curve()); assert!(!p.is_in_correct_subgroup()); - assert!(!p.is_valid()); } } @@ -1019,9 +1034,9 @@ pub mod g1 { infinity: false }; - assert!(a.is_valid()); - assert!(b.is_valid()); - assert!(c.is_valid()); + assert!(a.is_on_curve() && a.is_in_correct_subgroup()); + assert!(b.is_on_curve() && b.is_in_correct_subgroup()); + assert!(c.is_on_curve() && c.is_in_correct_subgroup()); let mut tmp1 = a.into_projective(); tmp1.add_assign(&b.into_projective()); @@ -1097,7 +1112,7 @@ pub mod g1 { pub mod g2 { use rand::{Rand, Rng}; use super::super::{Fq2, Fr, Fq, FrRepr, FqRepr}; - use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint}; + use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError}; curve_impl!(G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed); @@ -1120,7 +1135,18 @@ pub mod g2 { fn empty() -> Self { G2Uncompressed([0; 192]) } fn size() -> usize { 192 } - fn into_affine_unchecked(&self) -> Result<G2Affine, ()> { + fn into_affine(&self) -> Result<G2Affine, GroupDecodingError> { + let affine = self.into_affine_unchecked()?; + + if !affine.is_on_curve() { + Err(GroupDecodingError::NotOnCurve) + } else if !affine.is_in_correct_subgroup() { + Err(GroupDecodingError::NotInSubgroup) + } else { + Ok(affine) + } + } + fn into_affine_unchecked(&self) -> Result<G2Affine, GroupDecodingError> { use byteorder::{ReadBytesExt, BigEndian}; // Create a copy of this representation. @@ -1128,7 +1154,7 @@ pub mod g2 { if copy[0] & (1 << 7) != 0 { // Distinguisher bit is set, but this should be uncompressed! - return Err(()) + return Err(GroupDecodingError::UnexpectedCompressionMode) } if copy[0] & (1 << 6) != 0 { @@ -1140,13 +1166,13 @@ pub mod g2 { if copy.iter().all(|b| *b == 0) { Ok(G2Affine::zero()) } else { - Err(()) + Err(GroupDecodingError::UnexpectedInformation) } } else { if copy[0] & (1 << 5) != 0 { // The bit indicating the y-coordinate should be lexicographically // largest is set, but this is an uncompressed element. - return Err(()) + return Err(GroupDecodingError::UnexpectedInformation) } // Unset the three most significant bits. @@ -1179,12 +1205,12 @@ pub mod g2 { Ok(G2Affine { x: Fq2 { - c0: Fq::from_repr(x_c0)?, - c1: Fq::from_repr(x_c1)? + c0: Fq::from_repr(x_c0).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c0)", e))?, + c1: Fq::from_repr(x_c1).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e))?, }, y: Fq2 { - c0: Fq::from_repr(y_c0)?, - c1: Fq::from_repr(y_c1)? + c0: Fq::from_repr(y_c0).map_err(|e| GroupDecodingError::CoordinateDecodingError("y coordinate (c0)", e))?, + c1: Fq::from_repr(y_c1).map_err(|e| GroupDecodingError::CoordinateDecodingError("y coordinate (c1)", e))?, }, infinity: false }) @@ -1242,7 +1268,18 @@ pub mod g2 { fn empty() -> Self { G2Compressed([0; 96]) } fn size() -> usize { 96 } - fn into_affine_unchecked(&self) -> Result<G2Affine, ()> { + fn into_affine(&self) -> Result<G2Affine, GroupDecodingError> { + let affine = self.into_affine_unchecked()?; + + // NB: Decompression guarantees that it is on the curve already. + + if !affine.is_in_correct_subgroup() { + Err(GroupDecodingError::NotInSubgroup) + } else { + Ok(affine) + } + } + fn into_affine_unchecked(&self) -> Result<G2Affine, GroupDecodingError> { use byteorder::{ReadBytesExt, BigEndian}; // Create a copy of this representation. @@ -1250,7 +1287,7 @@ pub mod g2 { if copy[0] & (1 << 7) == 0 { // Distinguisher bit isn't set. - return Err(()) + return Err(GroupDecodingError::UnexpectedCompressionMode) } if copy[0] & (1 << 6) != 0 { @@ -1262,7 +1299,7 @@ pub mod g2 { if copy.iter().all(|b| *b == 0) { Ok(G2Affine::zero()) } else { - Err(()) + Err(GroupDecodingError::UnexpectedInformation) } } else { // Determine if the intended y coordinate must be greater @@ -1289,8 +1326,8 @@ pub mod g2 { // Interpret as Fq element. let x = Fq2 { - c0: Fq::from_repr(x_c0)?, - c1: Fq::from_repr(x_c1)? + c0: Fq::from_repr(x_c0).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c0)", e))?, + c1: Fq::from_repr(x_c1).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e))? }; // Compute x^3 + b @@ -1316,7 +1353,7 @@ pub mod g2 { }, None => { // Point must not be on the curve. - Err(()) + Err(GroupDecodingError::NotOnCurve) } } } @@ -1446,7 +1483,7 @@ pub mod g2 { infinity: false }; - assert!(!p.is_valid()); + assert!(!p.is_in_correct_subgroup()); let mut g2 = G2::zero(); @@ -1468,7 +1505,7 @@ pub mod g2 { assert_eq!(i, 2); let g2 = G2Affine::from(g2); - assert!(g2.is_valid()); + assert!(g2.is_in_correct_subgroup()); assert_eq!(g2, G2Affine::one()); break; @@ -1497,7 +1534,6 @@ pub mod g2 { }; assert!(!p.is_on_curve()); assert!(p.is_in_correct_subgroup()); - assert!(!p.is_valid()); } // Reject point on a twist (b = 2 * (u + 1)) @@ -1515,7 +1551,6 @@ pub mod g2 { }; assert!(!p.is_on_curve()); assert!(!p.is_in_correct_subgroup()); - assert!(!p.is_valid()); } // Reject point in an invalid subgroup @@ -1534,7 +1569,6 @@ pub mod g2 { }; assert!(p.is_on_curve()); assert!(!p.is_in_correct_subgroup()); - assert!(!p.is_valid()); } } diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index b7f8198..e38a7ea 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,4 +1,4 @@ -use ::{Field, PrimeField, SqrtField, PrimeFieldRepr}; +use ::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; use std::cmp::Ordering; use super::fq2::Fq2; @@ -401,14 +401,14 @@ impl From<Fq> for FqRepr { impl PrimeField for Fq { type Repr = FqRepr; - fn from_repr(r: FqRepr) -> Result<Fq, ()> { + fn from_repr(r: FqRepr) -> Result<Fq, PrimeFieldDecodingError> { let mut r = Fq(r); if r.is_valid() { r.mul_assign(&Fq(R2)); Ok(r) } else { - Err(()) + Err(PrimeFieldDecodingError::NotInField) } } @@ -1740,6 +1740,6 @@ fn test_fq_ordering() { // FqRepr's ordering is well-tested, but we still need to make sure the Fq // elements aren't being compared in Montgomery form. for i in 0..100 { - assert!(Fq::from_repr(FqRepr::from(i+1)) > Fq::from_repr(FqRepr::from(i))); + assert!(Fq::from_repr(FqRepr::from(i+1)).unwrap() > Fq::from_repr(FqRepr::from(i)).unwrap()); } } diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index c5c80b2..7561a56 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,4 +1,4 @@ -use ::{Field, PrimeField, SqrtField, PrimeFieldRepr}; +use ::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; // r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 const MODULUS: FrRepr = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); @@ -222,14 +222,14 @@ impl From<Fr> for FrRepr { impl PrimeField for Fr { type Repr = FrRepr; - fn from_repr(r: FrRepr) -> Result<Fr, ()> { + fn from_repr(r: FrRepr) -> Result<Fr, PrimeFieldDecodingError> { let mut r = Fr(r); if r.is_valid() { r.mul_assign(&Fr(R2)); Ok(r) } else { - Err(()) + Err(PrimeFieldDecodingError::NotInField) } } diff --git a/src/lib.rs b/src/lib.rs index b3df2a0..a4988d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ pub mod bls12_381; pub mod wnaf; use std::fmt; +use std::error::Error; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are @@ -179,9 +180,6 @@ pub trait CurveAffine: Copy + /// additive identity. fn is_zero(&self) -> bool; - /// Determines if this point is on the curve and in the correct subgroup. - fn is_valid(&self) -> bool; - /// Negates this element. fn negate(&mut self); @@ -224,21 +222,17 @@ pub trait EncodedPoint: Sized + fn size() -> usize; /// Converts an `EncodedPoint` into a `CurveAffine` element, - /// if the point is valid. - fn into_affine(&self) -> Result<Self::Affine, ()> { - let affine = self.into_affine_unchecked()?; - - if affine.is_valid() { - Ok(affine) - } else { - Err(()) - } - } + /// if the encoding represents a valid element. + fn into_affine(&self) -> Result<Self::Affine, GroupDecodingError>; /// Converts an `EncodedPoint` into a `CurveAffine` element, - /// without checking if it's a valid point. Caller must be careful - /// when using this, as misuse can violate API invariants. - fn into_affine_unchecked(&self) -> Result<Self::Affine, ()>; + /// without guaranteeing that the encoding represents a valid + /// element. This is useful when the caller knows the encoding is + /// valid already. + /// + /// If the encoding is invalid, this can break API invariants, + /// so caution is strongly encouraged. + fn into_affine_unchecked(&self) -> Result<Self::Affine, GroupDecodingError>; /// Creates an `EncodedPoint` from an affine point, as long as the /// point is not the point at infinity. @@ -368,6 +362,65 @@ pub trait PrimeFieldRepr: Sized + fn mul2(&mut self); } +#[derive(Debug)] +pub enum PrimeFieldDecodingError { + // The encoded value is not in the field + NotInField +} + +impl Error for PrimeFieldDecodingError { + fn description(&self) -> &str { + match self { + &PrimeFieldDecodingError::NotInField => "not an element in the field" + } + } +} + +impl fmt::Display for PrimeFieldDecodingError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", self.description()) + } +} + +#[derive(Debug)] +pub enum GroupDecodingError { + /// The coordinate(s) do not lie on the curve. + NotOnCurve, + /// The element is not part of the r-order subgroup. + NotInSubgroup, + /// One of the coordinates could not be decoded + CoordinateDecodingError(&'static str, PrimeFieldDecodingError), + /// The compression mode of the encoded elemnet was not as expected + UnexpectedCompressionMode, + /// The encoding contained bits that should not have been set + UnexpectedInformation +} + +impl Error for GroupDecodingError { + fn description(&self) -> &str { + match self { + &GroupDecodingError::NotOnCurve => "coordinate(s) do not lie on the curve", + &GroupDecodingError::NotInSubgroup => "the element is not part of an r-order subgroup", + &GroupDecodingError::CoordinateDecodingError(..) => "coordinate(s) could not be decoded", + &GroupDecodingError::UnexpectedCompressionMode => "encoding has unexpected compression mode", + &GroupDecodingError::UnexpectedInformation => "encoding has unexpected information" + } + } +} + +impl fmt::Display for GroupDecodingError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + &GroupDecodingError::CoordinateDecodingError(description, ref err) => { + write!(f, "{} decoding error: {}", description, err) + }, + _ => { + write!(f, "{}", self.description()) + } + } + } +} + /// This represents an element of a prime field. pub trait PrimeField: Field { @@ -376,7 +429,7 @@ pub trait PrimeField: Field type Repr: PrimeFieldRepr + From<Self>; /// Convert this prime field element into a biginteger representation. - fn from_repr(Self::Repr) -> Result<Self, ()>; + fn from_repr(Self::Repr) -> Result<Self, PrimeFieldDecodingError>; /// Convert a biginteger reprensentation into a prime field element, if /// the number is an element of the field. From 1027dda4323370199d26876abe4faed69e99a10f Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 17 Jul 2017 10:31:22 -0600 Subject: [PATCH 016/140] Carry the interpreted value of the encoding through the error. --- src/bls12_381/fq.rs | 2 +- src/bls12_381/fr.rs | 2 +- src/lib.rs | 10 +++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index e38a7ea..177b3d7 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -408,7 +408,7 @@ impl PrimeField for Fq { Ok(r) } else { - Err(PrimeFieldDecodingError::NotInField) + Err(PrimeFieldDecodingError::NotInField(format!("{:?}", r.0))) } } diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 7561a56..2c2128f 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -229,7 +229,7 @@ impl PrimeField for Fr { Ok(r) } else { - Err(PrimeFieldDecodingError::NotInField) + Err(PrimeFieldDecodingError::NotInField(format!("{:?}", r.0))) } } diff --git a/src/lib.rs b/src/lib.rs index a4988d2..c7aa7d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -365,20 +365,24 @@ pub trait PrimeFieldRepr: Sized + #[derive(Debug)] pub enum PrimeFieldDecodingError { // The encoded value is not in the field - NotInField + NotInField(String) } impl Error for PrimeFieldDecodingError { fn description(&self) -> &str { match self { - &PrimeFieldDecodingError::NotInField => "not an element in the field" + &PrimeFieldDecodingError::NotInField(..) => "not an element of the field" } } } impl fmt::Display for PrimeFieldDecodingError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "{}", self.description()) + match self { + &PrimeFieldDecodingError::NotInField(ref repr) => { + write!(f, "{} is not an element of the field", repr) + } + } } } From 1d4710a39e358d82469378ee9864a515b581ceb2 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 17 Jul 2017 12:20:01 -0600 Subject: [PATCH 017/140] Consistently use `Debug` and `Display`. --- src/bls12_381/ec.rs | 25 ++++++++++++++++++++++--- src/bls12_381/fq.rs | 28 ++++++++++++++-------------- src/bls12_381/fq12.rs | 9 ++++++++- src/bls12_381/fq2.rs | 9 ++++++++- src/bls12_381/fq6.rs | 9 ++++++++- src/bls12_381/fr.rs | 28 ++++++++++++++-------------- src/lib.rs | 4 ++++ 7 files changed, 78 insertions(+), 34 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 6823f73..a3f5c91 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -1,5 +1,6 @@ macro_rules! curve_impl { ( + $name:expr, $projective:ident, $affine:ident, $prepared:ident, @@ -15,13 +16,31 @@ macro_rules! curve_impl { pub(crate) infinity: bool } - #[derive(Copy, Clone, Debug, Eq)] + impl ::std::fmt::Display for $affine + { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + if self.infinity { + write!(f, "{}(Infinity)", $name) + } else { + write!(f, "{}(x={}, y={})", $name, self.x, self.y) + } + } + } + + #[derive(Copy, Clone, Eq, Debug)] pub struct $projective { pub(crate) x: $basefield, pub(crate) y: $basefield, pub(crate) z: $basefield } + impl ::std::fmt::Display for $projective + { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self.into_affine()) + } + } + impl PartialEq for $projective { fn eq(&self, other: &$projective) -> bool { if self.is_zero() { @@ -558,7 +577,7 @@ pub mod g1 { use super::super::{Fq, Fr, FrRepr, FqRepr}; use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError}; - curve_impl!(G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed); + curve_impl!("E", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed); pub struct G1Uncompressed([u8; 96]); @@ -1114,7 +1133,7 @@ pub mod g2 { use super::super::{Fq2, Fr, Fq, FrRepr, FqRepr}; use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError}; - curve_impl!(G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed); + curve_impl!("E'", G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed); pub struct G2Uncompressed([u8; 192]); diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 177b3d7..2c512c8 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -192,7 +192,7 @@ pub const FROBENIUS_COEFF_FQ12_C1: [Fq2; 12] = [ // -((2**384) mod q) mod q pub const NEGATIVE_ONE: Fq = Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])); -#[derive(Copy, Clone, PartialEq, Eq, Default)] +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct FqRepr(pub [u64; 6]); impl ::rand::Rand for FqRepr { @@ -202,7 +202,7 @@ impl ::rand::Rand for FqRepr { } } -impl ::std::fmt::Debug for FqRepr +impl ::std::fmt::Display for FqRepr { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { try!(write!(f, "0x")); @@ -355,7 +355,7 @@ impl PrimeFieldRepr for FqRepr { } } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Fq(FqRepr); /// `Fq` elements are ordered lexicographically. @@ -373,10 +373,10 @@ impl PartialOrd for Fq { } } -impl ::std::fmt::Debug for Fq +impl ::std::fmt::Display for Fq { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "Fq({:?})", self.into_repr()) + write!(f, "Fq({})", self.into_repr()) } } @@ -408,7 +408,7 @@ impl PrimeField for Fq { Ok(r) } else { - Err(PrimeFieldDecodingError::NotInField(format!("{:?}", r.0))) + Err(PrimeFieldDecodingError::NotInField(format!("{}", r.0))) } } @@ -1676,33 +1676,33 @@ fn bench_fq_from_repr(b: &mut ::test::Bencher) { } #[test] -fn test_fq_repr_debug() { +fn test_fq_repr_display() { assert_eq!( - format!("{:?}", FqRepr([0xa956babf9301ea24, 0x39a8f184f3535c7b, 0xb38d35b3f6779585, 0x676cc4eef4c46f2c, 0xb1d4aad87651e694, 0x1947f0d5f4fe325a])), + format!("{}", FqRepr([0xa956babf9301ea24, 0x39a8f184f3535c7b, 0xb38d35b3f6779585, 0x676cc4eef4c46f2c, 0xb1d4aad87651e694, 0x1947f0d5f4fe325a])), "0x1947f0d5f4fe325ab1d4aad87651e694676cc4eef4c46f2cb38d35b3f677958539a8f184f3535c7ba956babf9301ea24".to_string() ); assert_eq!( - format!("{:?}", FqRepr([0xb4171485fd8622dd, 0x864229a6edec7ec5, 0xc57f7bdcf8dfb707, 0x6db7ff0ecea4584a, 0xf8d8578c4a57132d, 0x6eb66d42d9fcaaa])), + format!("{}", FqRepr([0xb4171485fd8622dd, 0x864229a6edec7ec5, 0xc57f7bdcf8dfb707, 0x6db7ff0ecea4584a, 0xf8d8578c4a57132d, 0x6eb66d42d9fcaaa])), "0x06eb66d42d9fcaaaf8d8578c4a57132d6db7ff0ecea4584ac57f7bdcf8dfb707864229a6edec7ec5b4171485fd8622dd".to_string() ); assert_eq!( - format!("{:?}", FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), + format!("{}", FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string() ); assert_eq!( - format!("{:?}", FqRepr([0, 0, 0, 0, 0, 0])), + format!("{}", FqRepr([0, 0, 0, 0, 0, 0])), "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string() ); } #[test] -fn test_fq_debug() { +fn test_fq_display() { assert_eq!( - format!("{:?}", Fq::from_repr(FqRepr([0xa956babf9301ea24, 0x39a8f184f3535c7b, 0xb38d35b3f6779585, 0x676cc4eef4c46f2c, 0xb1d4aad87651e694, 0x1947f0d5f4fe325a])).unwrap()), + format!("{}", Fq::from_repr(FqRepr([0xa956babf9301ea24, 0x39a8f184f3535c7b, 0xb38d35b3f6779585, 0x676cc4eef4c46f2c, 0xb1d4aad87651e694, 0x1947f0d5f4fe325a])).unwrap()), "Fq(0x1947f0d5f4fe325ab1d4aad87651e694676cc4eef4c46f2cb38d35b3f677958539a8f184f3535c7ba956babf9301ea24)".to_string() ); assert_eq!( - format!("{:?}", Fq::from_repr(FqRepr([0xe28e79396ac2bbf8, 0x413f6f7f06ea87eb, 0xa4b62af4a792a689, 0xb7f89f88f59c1dc5, 0x9a551859b1e43a9a, 0x6c9f5a1060de974])).unwrap()), + format!("{}", Fq::from_repr(FqRepr([0xe28e79396ac2bbf8, 0x413f6f7f06ea87eb, 0xa4b62af4a792a689, 0xb7f89f88f59c1dc5, 0x9a551859b1e43a9a, 0x6c9f5a1060de974])).unwrap()), "Fq(0x06c9f5a1060de9749a551859b1e43a9ab7f89f88f59c1dc5a4b62af4a792a689413f6f7f06ea87ebe28e79396ac2bbf8)".to_string() ); } diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index 354045e..42ad3b4 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -5,12 +5,19 @@ use super::fq2::Fq2; use super::fq::{FROBENIUS_COEFF_FQ12_C1}; /// An element of Fq12, represented by c0 + c1 * w. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct Fq12 { pub c0: Fq6, pub c1: Fq6 } +impl ::std::fmt::Display for Fq12 +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fq12({} + {} * w)", self.c0, self.c1) + } +} + impl Rand for Fq12 { fn rand<R: Rng>(rng: &mut R) -> Self { Fq12 { diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index ec22782..fed09f6 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -5,12 +5,19 @@ use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; use std::cmp::Ordering; /// An element of Fq2, represented by c0 + c1 * u. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct Fq2 { pub c0: Fq, pub c1: Fq } +impl ::std::fmt::Display for Fq2 +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fq2({} + {} * u)", self.c0, self.c1) + } +} + /// `Fq2` elements are ordered lexicographically. impl Ord for Fq2 { #[inline(always)] diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs index 1a31497..419681b 100644 --- a/src/bls12_381/fq6.rs +++ b/src/bls12_381/fq6.rs @@ -4,13 +4,20 @@ use super::fq2::Fq2; use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; /// An element of Fq6, represented by c0 + c1 * v + c2 * v^2. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct Fq6 { pub c0: Fq2, pub c1: Fq2, pub c2: Fq2 } +impl ::std::fmt::Display for Fq6 +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fq6({} + {} * v, {} * v^2)", self.c0, self.c1, self.c2) + } +} + impl Rand for Fq6 { fn rand<R: Rng>(rng: &mut R) -> Self { Fq6 { diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 2c2128f..b1927bf 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -28,7 +28,7 @@ const S: usize = 32; // 2^s root of unity computed by GENERATOR^t const ROOT_OF_UNITY: FrRepr = FrRepr([0xb9b58d8c5f0e466a, 0x5b1b4c801819d7ec, 0xaf53ae352a31e64, 0x5bf3adda19e9b27b]); -#[derive(Copy, Clone, PartialEq, Eq, Default)] +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct FrRepr(pub [u64; 4]); impl ::rand::Rand for FrRepr { @@ -38,7 +38,7 @@ impl ::rand::Rand for FrRepr { } } -impl ::std::fmt::Debug for FrRepr +impl ::std::fmt::Display for FrRepr { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { try!(write!(f, "0x")); @@ -191,13 +191,13 @@ impl PrimeFieldRepr for FrRepr { } } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Fr(FrRepr); -impl ::std::fmt::Debug for Fr +impl ::std::fmt::Display for Fr { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "Fr({:?})", self.into_repr()) + write!(f, "Fr({})", self.into_repr()) } } @@ -229,7 +229,7 @@ impl PrimeField for Fr { Ok(r) } else { - Err(PrimeFieldDecodingError::NotInField(format!("{:?}", r.0))) + Err(PrimeFieldDecodingError::NotInField(format!("{}", r.0))) } } @@ -1388,33 +1388,33 @@ fn bench_fr_from_repr(b: &mut ::test::Bencher) { } #[test] -fn test_fr_repr_debug() { +fn test_fr_repr_display() { assert_eq!( - format!("{:?}", FrRepr([0x2829c242fa826143, 0x1f32cf4dd4330917, 0x932e4e479d168cd9, 0x513c77587f563f64])), + format!("{}", FrRepr([0x2829c242fa826143, 0x1f32cf4dd4330917, 0x932e4e479d168cd9, 0x513c77587f563f64])), "0x513c77587f563f64932e4e479d168cd91f32cf4dd43309172829c242fa826143".to_string() ); assert_eq!( - format!("{:?}", FrRepr([0x25ebe3a3ad3c0c6a, 0x6990e39d092e817c, 0x941f900d42f5658e, 0x44f8a103b38a71e0])), + format!("{}", FrRepr([0x25ebe3a3ad3c0c6a, 0x6990e39d092e817c, 0x941f900d42f5658e, 0x44f8a103b38a71e0])), "0x44f8a103b38a71e0941f900d42f5658e6990e39d092e817c25ebe3a3ad3c0c6a".to_string() ); assert_eq!( - format!("{:?}", FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), + format!("{}", FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string() ); assert_eq!( - format!("{:?}", FrRepr([0, 0, 0, 0])), + format!("{}", FrRepr([0, 0, 0, 0])), "0x0000000000000000000000000000000000000000000000000000000000000000".to_string() ); } #[test] -fn test_fr_debug() { +fn test_fr_display() { assert_eq!( - format!("{:?}", Fr::from_repr(FrRepr([0xc3cae746a3b5ecc7, 0x185ec8eb3f5b5aee, 0x684499ffe4b9dd99, 0x7c9bba7afb68faa])).unwrap()), + format!("{}", Fr::from_repr(FrRepr([0xc3cae746a3b5ecc7, 0x185ec8eb3f5b5aee, 0x684499ffe4b9dd99, 0x7c9bba7afb68faa])).unwrap()), "Fr(0x07c9bba7afb68faa684499ffe4b9dd99185ec8eb3f5b5aeec3cae746a3b5ecc7)".to_string() ); assert_eq!( - format!("{:?}", Fr::from_repr(FrRepr([0x44c71298ff198106, 0xb0ad10817df79b6a, 0xd034a80a2b74132b, 0x41cf9a1336f50719])).unwrap()), + format!("{}", Fr::from_repr(FrRepr([0x44c71298ff198106, 0xb0ad10817df79b6a, 0xd034a80a2b74132b, 0x41cf9a1336f50719])).unwrap()), "Fr(0x41cf9a1336f50719d034a80a2b74132bb0ad10817df79b6a44c71298ff198106)".to_string() ); } diff --git a/src/lib.rs b/src/lib.rs index c7aa7d1..4813c1b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,7 @@ pub trait CurveProjective: PartialEq + Send + Sync + fmt::Debug + + fmt::Display + rand::Rand + 'static { @@ -159,6 +160,7 @@ pub trait CurveAffine: Copy + Send + Sync + fmt::Debug + + fmt::Display + PartialEq + Eq + 'static @@ -247,6 +249,7 @@ pub trait Field: Sized + Send + Sync + fmt::Debug + + fmt::Display + 'static + rand::Rand { @@ -327,6 +330,7 @@ pub trait PrimeFieldRepr: Sized + Send + Sync + fmt::Debug + + fmt::Display + 'static + rand::Rand + AsRef<[u64]> + From 7b1cd7f211ba66192b98052c98efdb18c9a799d1 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 17 Jul 2017 12:24:00 -0600 Subject: [PATCH 018/140] Improve documentation for errors a bit. --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4813c1b..b882037 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -366,9 +366,11 @@ pub trait PrimeFieldRepr: Sized + fn mul2(&mut self); } +/// An error that may occur when trying to interpret a `PrimeFieldRepr` as a +/// `PrimeField` element. #[derive(Debug)] pub enum PrimeFieldDecodingError { - // The encoded value is not in the field + /// The encoded value is not in the field NotInField(String) } @@ -390,6 +392,7 @@ impl fmt::Display for PrimeFieldDecodingError { } } +/// An error that may occur when trying to decode an `EncodedPoint`. #[derive(Debug)] pub enum GroupDecodingError { /// The coordinate(s) do not lie on the curve. From d67109d5d3c0f9eb2e75f07caa90c91d0a2fb266 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 17 Jul 2017 13:05:16 -0600 Subject: [PATCH 019/140] EncodedPoint should be Copy/Clone. --- src/bls12_381/ec.rs | 28 ++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 2 files changed, 30 insertions(+) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index a3f5c91..fe12374 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -579,8 +579,15 @@ pub mod g1 { curve_impl!("E", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed); + #[derive(Copy)] pub struct G1Uncompressed([u8; 96]); + impl Clone for G1Uncompressed { + fn clone(&self) -> G1Uncompressed { + G1Uncompressed(self.0) + } + } + impl AsRef<[u8]> for G1Uncompressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -688,8 +695,15 @@ pub mod g1 { } } + #[derive(Copy)] pub struct G1Compressed([u8; 48]); + impl Clone for G1Compressed { + fn clone(&self) -> G1Compressed { + G1Compressed(self.0) + } + } + impl AsRef<[u8]> for G1Compressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -1135,8 +1149,15 @@ pub mod g2 { curve_impl!("E'", G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed); + #[derive(Copy)] pub struct G2Uncompressed([u8; 192]); + impl Clone for G2Uncompressed { + fn clone(&self) -> G2Uncompressed { + G2Uncompressed(self.0) + } + } + impl AsRef<[u8]> for G2Uncompressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -1268,8 +1289,15 @@ pub mod g2 { } } + #[derive(Copy)] pub struct G2Compressed([u8; 96]); + impl Clone for G2Compressed { + fn clone(&self) -> G2Compressed { + G2Compressed(self.0) + } + } + impl AsRef<[u8]> for G2Compressed { fn as_ref(&self) -> &[u8] { &self.0 diff --git a/src/lib.rs b/src/lib.rs index b882037..54f61b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -213,6 +213,8 @@ pub trait EncodedPoint: Sized + Sync + AsRef<[u8]> + AsMut<[u8]> + + Clone + + Copy + 'static { type Affine: CurveAffine; From 2bfce59d8ebcc1a9e12cf477aa3f3483bf56e040 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 17 Jul 2017 13:35:12 -0600 Subject: [PATCH 020/140] Offer `read_be` and `write_be` utilities to simplify code, and for testing. --- src/bls12_381/ec.rs | 98 +++++++++------------------------------------ src/bls12_381/fq.rs | 7 ++++ src/bls12_381/fr.rs | 7 ++++ src/lib.rs | 26 ++++++++++++ 4 files changed, 58 insertions(+), 80 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index fe12374..2f39940 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -617,8 +617,6 @@ pub mod g1 { } } fn into_affine_unchecked(&self) -> Result<G1Affine, GroupDecodingError> { - use byteorder::{ReadBytesExt, BigEndian}; - // Create a copy of this representation. let mut copy = self.0; @@ -654,13 +652,8 @@ pub mod g1 { { let mut reader = &copy[..]; - for b in x.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in y.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } + x.read_be(&mut reader).unwrap(); + y.read_be(&mut reader).unwrap(); } Ok(G1Affine { @@ -671,8 +664,6 @@ pub mod g1 { } } fn from_affine(affine: G1Affine) -> Self { - use byteorder::{WriteBytesExt, BigEndian}; - let mut res = Self::empty(); if affine.is_zero() { @@ -682,13 +673,8 @@ pub mod g1 { } else { let mut writer = &mut res.0[..]; - for digit in affine.x.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } - - for digit in affine.y.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } + affine.x.into_repr().write_be(&mut writer).unwrap(); + affine.y.into_repr().write_be(&mut writer).unwrap(); } res @@ -733,8 +719,6 @@ pub mod g1 { } } fn into_affine_unchecked(&self) -> Result<G1Affine, GroupDecodingError> { - use byteorder::{ReadBytesExt, BigEndian}; - // Create a copy of this representation. let mut copy = self.0; @@ -767,9 +751,7 @@ pub mod g1 { { let mut reader = &copy[..]; - for b in x.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } + x.read_be(&mut reader).unwrap(); } // Interpret as Fq element. @@ -804,8 +786,6 @@ pub mod g1 { } } fn from_affine(affine: G1Affine) -> Self { - use byteorder::{WriteBytesExt, BigEndian}; - let mut res = Self::empty(); if affine.is_zero() { @@ -816,9 +796,7 @@ pub mod g1 { { let mut writer = &mut res.0[..]; - for digit in affine.x.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } + affine.x.into_repr().write_be(&mut writer).unwrap(); } let mut negy = affine.y; @@ -1187,8 +1165,6 @@ pub mod g2 { } } fn into_affine_unchecked(&self) -> Result<G2Affine, GroupDecodingError> { - use byteorder::{ReadBytesExt, BigEndian}; - // Create a copy of this representation. let mut copy = self.0; @@ -1226,21 +1202,10 @@ pub mod g2 { { let mut reader = &copy[..]; - for b in x_c1.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in x_c0.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in y_c1.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in y_c0.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } + x_c1.read_be(&mut reader).unwrap(); + x_c0.read_be(&mut reader).unwrap(); + y_c1.read_be(&mut reader).unwrap(); + y_c0.read_be(&mut reader).unwrap(); } Ok(G2Affine { @@ -1257,8 +1222,6 @@ pub mod g2 { } } fn from_affine(affine: G2Affine) -> Self { - use byteorder::{WriteBytesExt, BigEndian}; - let mut res = Self::empty(); if affine.is_zero() { @@ -1268,21 +1231,10 @@ pub mod g2 { } else { let mut writer = &mut res.0[..]; - for digit in affine.x.c1.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } - - for digit in affine.x.c0.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } - - for digit in affine.y.c1.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } - - for digit in affine.y.c0.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } + affine.x.c1.into_repr().write_be(&mut writer).unwrap(); + affine.x.c0.into_repr().write_be(&mut writer).unwrap(); + affine.y.c1.into_repr().write_be(&mut writer).unwrap(); + affine.y.c0.into_repr().write_be(&mut writer).unwrap(); } res @@ -1327,8 +1279,6 @@ pub mod g2 { } } fn into_affine_unchecked(&self) -> Result<G2Affine, GroupDecodingError> { - use byteorder::{ReadBytesExt, BigEndian}; - // Create a copy of this representation. let mut copy = self.0; @@ -1362,13 +1312,8 @@ pub mod g2 { { let mut reader = &copy[..]; - for b in x_c1.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } - - for b in x_c0.0.iter_mut().rev() { - *b = reader.read_u64::<BigEndian>().unwrap(); - } + x_c1.read_be(&mut reader).unwrap(); + x_c0.read_be(&mut reader).unwrap(); } // Interpret as Fq element. @@ -1406,8 +1351,6 @@ pub mod g2 { } } fn from_affine(affine: G2Affine) -> Self { - use byteorder::{WriteBytesExt, BigEndian}; - let mut res = Self::empty(); if affine.is_zero() { @@ -1418,13 +1361,8 @@ pub mod g2 { { let mut writer = &mut res.0[..]; - for digit in affine.x.c1.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } - - for digit in affine.x.c0.into_repr().as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit).unwrap(); - } + affine.x.c1.into_repr().write_be(&mut writer).unwrap(); + affine.x.c0.into_repr().write_be(&mut writer).unwrap(); } let mut negy = affine.y; diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 2c512c8..00b0405 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -221,6 +221,13 @@ impl AsRef<[u64]> for FqRepr { } } +impl AsMut<[u64]> for FqRepr { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u64] { + &mut self.0 + } +} + impl From<u64> for FqRepr { #[inline(always)] fn from(val: u64) -> FqRepr { diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index b1927bf..c321b76 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -57,6 +57,13 @@ impl AsRef<[u64]> for FrRepr { } } +impl AsMut<[u64]> for FrRepr { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u64] { + &mut self.0 + } +} + impl From<u64> for FrRepr { #[inline(always)] fn from(val: u64) -> FrRepr { diff --git a/src/lib.rs b/src/lib.rs index 54f61b1..326e436 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ pub mod wnaf; use std::fmt; use std::error::Error; +use std::io::{self, Read, Write}; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are @@ -336,6 +337,7 @@ pub trait PrimeFieldRepr: Sized + 'static + rand::Rand + AsRef<[u64]> + + AsMut<[u64]> + From<u64> { /// Subtract another reprensetation from this one, returning the borrow bit. @@ -366,6 +368,30 @@ pub trait PrimeFieldRepr: Sized + /// Performs a leftwise bitshift of this number, effectively multiplying /// it by 2. Overflow is ignored. fn mul2(&mut self); + + /// Writes this `PrimeFieldRepr` as a big endian integer. Always writes + /// `(num_bits` / 8) bytes. + fn write_be<W: Write>(&self, mut writer: W) -> io::Result<()> { + use byteorder::{WriteBytesExt, BigEndian}; + + for digit in self.as_ref().iter().rev() { + writer.write_u64::<BigEndian>(*digit)?; + } + + Ok(()) + } + + /// Reads a big endian integer occupying (`num_bits` / 8) bytes into this + /// representation. + fn read_be<R: Read>(&mut self, mut reader: R) -> io::Result<()> { + use byteorder::{ReadBytesExt, BigEndian}; + + for digit in self.as_mut().iter_mut().rev() { + *digit = reader.read_u64::<BigEndian>()?; + } + + Ok(()) + } } /// An error that may occur when trying to interpret a `PrimeFieldRepr` as a From 0e877810f72d505abecfcb108e71db26e6231636 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 17 Jul 2017 18:24:57 -0600 Subject: [PATCH 021/140] Add tests for rejected encoded G1/G2 points. --- src/bls12_381/tests/mod.rs | 505 +++++++++++++++++++++++++++++++++++++ 1 file changed, 505 insertions(+) diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs index 41a54d7..08de508 100644 --- a/src/bls12_381/tests/mod.rs +++ b/src/bls12_381/tests/mod.rs @@ -46,3 +46,508 @@ fn test_g2_compressed_valid_vectors() { test_vectors::<G2, G2Compressed>(include_bytes!("g2_compressed_valid_test_vectors.dat")); } +#[test] +fn test_g1_uncompressed_invalid_vectors() { + { + let z = G1Affine::zero().into_uncompressed(); + + { + let mut z = z; + z.as_mut()[0] |= 0b1000_0000; + if let Err(GroupDecodingError::UnexpectedCompressionMode) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because we expected an uncompressed point"); + } + } + + { + let mut z = z; + z.as_mut()[0] |= 0b0010_0000; + if let Err(GroupDecodingError::UnexpectedInformation) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because the parity bit should not be set if the point is at infinity"); + } + } + + for i in 0..G1Uncompressed::size() { + let mut z = z; + z.as_mut()[i] |= 0b0000_0001; + if let Err(GroupDecodingError::UnexpectedInformation) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because the coordinates should be zeroes at the point at infinity"); + } + } + } + + let o = G1Affine::one().into_uncompressed(); + + { + let mut o = o; + o.as_mut()[0] |= 0b1000_0000; + if let Err(GroupDecodingError::UnexpectedCompressionMode) = o.into_affine() { + // :) + } else { + panic!("should have rejected the point because we expected an uncompressed point"); + } + } + + let m = Fq::char(); + + { + let mut o = o; + m.write_be(&mut o.as_mut()[0..]).unwrap(); + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "x coordinate"); + } else { + panic!("should have rejected the point") + } + } + + { + let mut o = o; + m.write_be(&mut o.as_mut()[48..]).unwrap(); + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "y coordinate"); + } else { + panic!("should have rejected the point") + } + } + + { + let m = Fq::zero().into_repr(); + + let mut o = o; + m.write_be(&mut o.as_mut()[0..]).unwrap(); + + if let Err(GroupDecodingError::NotOnCurve) = o.into_affine() { + // :) + } else { + panic!("should have rejected the point because it isn't on the curve") + } + } + + { + let mut o = o; + let mut x = Fq::one(); + + loop { + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API? + + if let Some(y) = x3b.sqrt() { + // We know this is on the curve, but it's likely not going to be in the correct subgroup. + x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); + y.into_repr().write_be(&mut o.as_mut()[48..]).unwrap(); + + if let Err(GroupDecodingError::NotInSubgroup) = o.into_affine() { + break + } else { + panic!("should have rejected the point because it isn't in the correct subgroup") + } + } else { + x.add_assign(&Fq::one()); + } + } + } +} + +#[test] +fn test_g2_uncompressed_invalid_vectors() { + { + let z = G2Affine::zero().into_uncompressed(); + + { + let mut z = z; + z.as_mut()[0] |= 0b1000_0000; + if let Err(GroupDecodingError::UnexpectedCompressionMode) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because we expected an uncompressed point"); + } + } + + { + let mut z = z; + z.as_mut()[0] |= 0b0010_0000; + if let Err(GroupDecodingError::UnexpectedInformation) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because the parity bit should not be set if the point is at infinity"); + } + } + + for i in 0..G2Uncompressed::size() { + let mut z = z; + z.as_mut()[i] |= 0b0000_0001; + if let Err(GroupDecodingError::UnexpectedInformation) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because the coordinates should be zeroes at the point at infinity"); + } + } + } + + let o = G2Affine::one().into_uncompressed(); + + { + let mut o = o; + o.as_mut()[0] |= 0b1000_0000; + if let Err(GroupDecodingError::UnexpectedCompressionMode) = o.into_affine() { + // :) + } else { + panic!("should have rejected the point because we expected an uncompressed point"); + } + } + + let m = Fq::char(); + + { + let mut o = o; + m.write_be(&mut o.as_mut()[0..]).unwrap(); + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "x coordinate (c1)"); + } else { + panic!("should have rejected the point") + } + } + + { + let mut o = o; + m.write_be(&mut o.as_mut()[48..]).unwrap(); + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "x coordinate (c0)"); + } else { + panic!("should have rejected the point") + } + } + + { + let mut o = o; + m.write_be(&mut o.as_mut()[96..]).unwrap(); + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "y coordinate (c1)"); + } else { + panic!("should have rejected the point") + } + } + + { + let mut o = o; + m.write_be(&mut o.as_mut()[144..]).unwrap(); + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "y coordinate (c0)"); + } else { + panic!("should have rejected the point") + } + } + + { + let m = Fq::zero().into_repr(); + + let mut o = o; + m.write_be(&mut o.as_mut()[0..]).unwrap(); + m.write_be(&mut o.as_mut()[48..]).unwrap(); + + if let Err(GroupDecodingError::NotOnCurve) = o.into_affine() { + // :) + } else { + panic!("should have rejected the point because it isn't on the curve") + } + } + + { + let mut o = o; + let mut x = Fq2::one(); + + loop { + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&Fq2 { + c0: Fq::from_repr(FqRepr::from(4)).unwrap(), + c1: Fq::from_repr(FqRepr::from(4)).unwrap() + }); // TODO: perhaps expose coeff_b through API? + + if let Some(y) = x3b.sqrt() { + // We know this is on the curve, but it's likely not going to be in the correct subgroup. + x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); + x.c0.into_repr().write_be(&mut o.as_mut()[48..]).unwrap(); + y.c1.into_repr().write_be(&mut o.as_mut()[96..]).unwrap(); + y.c0.into_repr().write_be(&mut o.as_mut()[144..]).unwrap(); + + if let Err(GroupDecodingError::NotInSubgroup) = o.into_affine() { + break + } else { + panic!("should have rejected the point because it isn't in the correct subgroup") + } + } else { + x.add_assign(&Fq2::one()); + } + } + } +} + +#[test] +fn test_g1_compressed_invalid_vectors() { + { + let z = G1Affine::zero().into_compressed(); + + { + let mut z = z; + z.as_mut()[0] &= 0b0111_1111; + if let Err(GroupDecodingError::UnexpectedCompressionMode) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because we expected a compressed point"); + } + } + + { + let mut z = z; + z.as_mut()[0] |= 0b0010_0000; + if let Err(GroupDecodingError::UnexpectedInformation) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because the parity bit should not be set if the point is at infinity"); + } + } + + for i in 0..G1Compressed::size() { + let mut z = z; + z.as_mut()[i] |= 0b0000_0001; + if let Err(GroupDecodingError::UnexpectedInformation) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because the coordinates should be zeroes at the point at infinity"); + } + } + } + + let o = G1Affine::one().into_compressed(); + + { + let mut o = o; + o.as_mut()[0] &= 0b0111_1111; + if let Err(GroupDecodingError::UnexpectedCompressionMode) = o.into_affine() { + // :) + } else { + panic!("should have rejected the point because we expected a compressed point"); + } + } + + let m = Fq::char(); + + { + let mut o = o; + m.write_be(&mut o.as_mut()[0..]).unwrap(); + o.as_mut()[0] |= 0b1000_0000; + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "x coordinate"); + } else { + panic!("should have rejected the point") + } + } + + { + let mut o = o; + let mut x = Fq::one(); + + loop { + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API? + + if let Some(_) = x3b.sqrt() { + x.add_assign(&Fq::one()); + } else { + x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); + o.as_mut()[0] |= 0b1000_0000; + + if let Err(GroupDecodingError::NotOnCurve) = o.into_affine() { + break + } else { + panic!("should have rejected the point because it isn't on the curve") + } + } + } + } + + { + let mut o = o; + let mut x = Fq::one(); + + loop { + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&Fq::from_repr(FqRepr::from(4)).unwrap()); // TODO: perhaps expose coeff_b through API? + + if let Some(_) = x3b.sqrt() { + // We know this is on the curve, but it's likely not going to be in the correct subgroup. + x.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); + o.as_mut()[0] |= 0b1000_0000; + + if let Err(GroupDecodingError::NotInSubgroup) = o.into_affine() { + break + } else { + panic!("should have rejected the point because it isn't in the correct subgroup") + } + } else { + x.add_assign(&Fq::one()); + } + } + } +} + +#[test] +fn test_g2_compressed_invalid_vectors() { + { + let z = G2Affine::zero().into_compressed(); + + { + let mut z = z; + z.as_mut()[0] &= 0b0111_1111; + if let Err(GroupDecodingError::UnexpectedCompressionMode) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because we expected a compressed point"); + } + } + + { + let mut z = z; + z.as_mut()[0] |= 0b0010_0000; + if let Err(GroupDecodingError::UnexpectedInformation) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because the parity bit should not be set if the point is at infinity"); + } + } + + for i in 0..G2Compressed::size() { + let mut z = z; + z.as_mut()[i] |= 0b0000_0001; + if let Err(GroupDecodingError::UnexpectedInformation) = z.into_affine() { + // :) + } else { + panic!("should have rejected the point because the coordinates should be zeroes at the point at infinity"); + } + } + } + + let o = G2Affine::one().into_compressed(); + + { + let mut o = o; + o.as_mut()[0] &= 0b0111_1111; + if let Err(GroupDecodingError::UnexpectedCompressionMode) = o.into_affine() { + // :) + } else { + panic!("should have rejected the point because we expected a compressed point"); + } + } + + let m = Fq::char(); + + { + let mut o = o; + m.write_be(&mut o.as_mut()[0..]).unwrap(); + o.as_mut()[0] |= 0b1000_0000; + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "x coordinate (c1)"); + } else { + panic!("should have rejected the point") + } + } + + { + let mut o = o; + m.write_be(&mut o.as_mut()[48..]).unwrap(); + o.as_mut()[0] |= 0b1000_0000; + + if let Err(GroupDecodingError::CoordinateDecodingError(coordinate, _)) = o.into_affine() { + assert_eq!(coordinate, "x coordinate (c0)"); + } else { + panic!("should have rejected the point") + } + } + + { + let mut o = o; + let mut x = Fq2 { + c0: Fq::one(), + c1: Fq::one() + }; + + loop { + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&Fq2 { + c0: Fq::from_repr(FqRepr::from(4)).unwrap(), + c1: Fq::from_repr(FqRepr::from(4)).unwrap(), + }); // TODO: perhaps expose coeff_b through API? + + if let Some(_) = x3b.sqrt() { + x.add_assign(&Fq2::one()); + } else { + x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); + x.c0.into_repr().write_be(&mut o.as_mut()[48..]).unwrap(); + o.as_mut()[0] |= 0b1000_0000; + + if let Err(GroupDecodingError::NotOnCurve) = o.into_affine() { + break + } else { + panic!("should have rejected the point because it isn't on the curve") + } + } + } + } + + { + let mut o = o; + let mut x = Fq2 { + c0: Fq::one(), + c1: Fq::one() + }; + + loop { + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&Fq2 { + c0: Fq::from_repr(FqRepr::from(4)).unwrap(), + c1: Fq::from_repr(FqRepr::from(4)).unwrap(), + }); // TODO: perhaps expose coeff_b through API? + + if let Some(_) = x3b.sqrt() { + // We know this is on the curve, but it's likely not going to be in the correct subgroup. + x.c1.into_repr().write_be(&mut o.as_mut()[0..]).unwrap(); + x.c0.into_repr().write_be(&mut o.as_mut()[48..]).unwrap(); + o.as_mut()[0] |= 0b1000_0000; + + if let Err(GroupDecodingError::NotInSubgroup) = o.into_affine() { + break + } else { + panic!("should have rejected the point because it isn't in the correct subgroup") + } + } else { + x.add_assign(&Fq2::one()); + } + } + } +} From 19efad5ba6ec1a0a38bb99e63aaf2b1734cc2b94 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 18 Jul 2017 09:00:02 -0600 Subject: [PATCH 022/140] Small fixups to reduce diff. --- src/bls12_381/ec.rs | 6 +++--- src/bls12_381/fq12.rs | 2 +- src/bls12_381/fq2.rs | 2 +- src/bls12_381/fq6.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 2f39940..973bdbf 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -27,7 +27,7 @@ macro_rules! curve_impl { } } - #[derive(Copy, Clone, Eq, Debug)] + #[derive(Copy, Clone, Debug, Eq)] pub struct $projective { pub(crate) x: $basefield, pub(crate) y: $basefield, @@ -577,7 +577,7 @@ pub mod g1 { use super::super::{Fq, Fr, FrRepr, FqRepr}; use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError}; - curve_impl!("E", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed); + curve_impl!("G1", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed); #[derive(Copy)] pub struct G1Uncompressed([u8; 96]); @@ -1125,7 +1125,7 @@ pub mod g2 { use super::super::{Fq2, Fr, Fq, FrRepr, FqRepr}; use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError}; - curve_impl!("E'", G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed); + curve_impl!("G2", G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed); #[derive(Copy)] pub struct G2Uncompressed([u8; 192]); diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index 42ad3b4..e918505 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -5,7 +5,7 @@ use super::fq2::Fq2; use super::fq::{FROBENIUS_COEFF_FQ12_C1}; /// An element of Fq12, represented by c0 + c1 * w. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq12 { pub c0: Fq6, pub c1: Fq6 diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index fed09f6..a4d8e7c 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -5,7 +5,7 @@ use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; use std::cmp::Ordering; /// An element of Fq2, represented by c0 + c1 * u. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq2 { pub c0: Fq, pub c1: Fq diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs index 419681b..2c62a92 100644 --- a/src/bls12_381/fq6.rs +++ b/src/bls12_381/fq6.rs @@ -4,7 +4,7 @@ use super::fq2::Fq2; use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; /// An element of Fq6, represented by c0 + c1 * v + c2 * v^2. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq6 { pub c0: Fq2, pub c1: Fq2, From 9e5f70f12614071b497209d06cd72a8f9bc2abf3 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 18 Jul 2017 10:16:49 -0600 Subject: [PATCH 023/140] (clippy) More idomatic reference matching. --- src/lib.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 326e436..04bde0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -404,16 +404,16 @@ pub enum PrimeFieldDecodingError { impl Error for PrimeFieldDecodingError { fn description(&self) -> &str { - match self { - &PrimeFieldDecodingError::NotInField(..) => "not an element of the field" + match *self { + PrimeFieldDecodingError::NotInField(..) => "not an element of the field" } } } impl fmt::Display for PrimeFieldDecodingError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - &PrimeFieldDecodingError::NotInField(ref repr) => { + match *self { + PrimeFieldDecodingError::NotInField(ref repr) => { write!(f, "{} is not an element of the field", repr) } } @@ -437,20 +437,20 @@ pub enum GroupDecodingError { impl Error for GroupDecodingError { fn description(&self) -> &str { - match self { - &GroupDecodingError::NotOnCurve => "coordinate(s) do not lie on the curve", - &GroupDecodingError::NotInSubgroup => "the element is not part of an r-order subgroup", - &GroupDecodingError::CoordinateDecodingError(..) => "coordinate(s) could not be decoded", - &GroupDecodingError::UnexpectedCompressionMode => "encoding has unexpected compression mode", - &GroupDecodingError::UnexpectedInformation => "encoding has unexpected information" + match *self { + GroupDecodingError::NotOnCurve => "coordinate(s) do not lie on the curve", + GroupDecodingError::NotInSubgroup => "the element is not part of an r-order subgroup", + GroupDecodingError::CoordinateDecodingError(..) => "coordinate(s) could not be decoded", + GroupDecodingError::UnexpectedCompressionMode => "encoding has unexpected compression mode", + GroupDecodingError::UnexpectedInformation => "encoding has unexpected information" } } } impl fmt::Display for GroupDecodingError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - &GroupDecodingError::CoordinateDecodingError(description, ref err) => { + match *self { + GroupDecodingError::CoordinateDecodingError(description, ref err) => { write!(f, "{} decoding error: {}", description, err) }, _ => { From 176c77d60297f36085be6201f8db5f9da3a1f6a5 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 18 Jul 2017 11:18:42 -0600 Subject: [PATCH 024/140] Random encoding/decoding tests for `PrimeFieldRepr`. Closes #3. --- src/bls12_381/fq.rs | 5 +++++ src/bls12_381/fr.rs | 5 +++++ src/lib.rs | 1 + src/tests/mod.rs | 1 + src/tests/repr.rs | 21 +++++++++++++++++++++ 5 files changed, 33 insertions(+) create mode 100644 src/tests/repr.rs diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 00b0405..e7be0ec 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1750,3 +1750,8 @@ fn test_fq_ordering() { assert!(Fq::from_repr(FqRepr::from(i+1)).unwrap() > Fq::from_repr(FqRepr::from(i)).unwrap()); } } + +#[test] +fn fq_repr_tests() { + ::tests::repr::random_repr_tests::<FqRepr>(); +} diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index c321b76..63cc264 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1453,3 +1453,8 @@ fn fr_field_tests() { ::tests::field::random_sqrt_tests::<Fr>(); ::tests::field::random_frobenius_tests::<Fr, _>(Fr::char(), 13); } + +#[test] +fn fr_repr_tests() { + ::tests::repr::random_repr_tests::<FrRepr>(); +} diff --git a/src/lib.rs b/src/lib.rs index 04bde0f..ca38451 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,6 +332,7 @@ pub trait PrimeFieldRepr: Sized + Ord + Send + Sync + + Default + fmt::Debug + fmt::Display + 'static + diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 2cb00a7..cf00add 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,3 +1,4 @@ pub mod curve; pub mod field; pub mod engine; +pub mod repr; diff --git a/src/tests/repr.rs b/src/tests/repr.rs new file mode 100644 index 0000000..b0c119c --- /dev/null +++ b/src/tests/repr.rs @@ -0,0 +1,21 @@ +use rand::{SeedableRng, XorShiftRng}; +use ::{PrimeFieldRepr}; + +pub fn random_repr_tests<R: PrimeFieldRepr>() { + random_encoding_tests::<R>(); +} + +fn random_encoding_tests<R: PrimeFieldRepr>() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let r = R::rand(&mut rng); + let mut rdecoded = R::default(); + + let mut v: Vec<u8> = vec![]; + r.write_be(&mut v).unwrap(); + rdecoded.read_be(&v[0..]).unwrap(); + + assert_eq!(r, rdecoded); + } +} From 59765c5830fdb0c80b3fe1abcb1e3d8a685273d7 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 18 Jul 2017 13:36:18 -0600 Subject: [PATCH 025/140] Version bump. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3d517d0..c7dcb2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pairing" -version = "0.9.0" +version = "0.10.0" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" From 41834bc4b235d0aae1261bee273b71394a4c9c46 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 20 Jul 2017 20:52:36 -0600 Subject: [PATCH 026/140] Add pairing_with API. --- src/bls12_381/ec.rs | 31 ++++++++++++++++++++++++------- src/lib.rs | 9 +++++++-- src/tests/engine.rs | 8 ++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 973bdbf..a4eaa1a 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -7,7 +7,8 @@ macro_rules! curve_impl { $basefield:ident, $scalarfield:ident, $uncompressed:ident, - $compressed:ident + $compressed:ident, + $pairing:ident ) => { #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct $affine { @@ -113,6 +114,8 @@ macro_rules! curve_impl { type Projective = $projective; type Uncompressed = $uncompressed; type Compressed = $compressed; + type Pair = $pairing; + type PairingResult = Fq12; fn zero() -> Self { $affine { @@ -155,6 +158,10 @@ macro_rules! curve_impl { $prepared::from_affine(*self) } + fn pairing_with(&self, other: &Self::Pair) -> Self::PairingResult { + self.perform_pairing(other) + } + fn into_projective(&self) -> $projective { (*self).into() } @@ -574,10 +581,11 @@ macro_rules! curve_impl { pub mod g1 { use rand::{Rand, Rng}; - use super::super::{Fq, Fr, FrRepr, FqRepr}; - use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError}; + use super::g2::G2Affine; + use super::super::{Fq, Fr, FrRepr, FqRepr, Fq12}; + use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError, Engine}; - curve_impl!("G1", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed); + curve_impl!("G1", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed, G2Affine); #[derive(Copy)] pub struct G1Uncompressed([u8; 96]); @@ -828,6 +836,10 @@ pub mod g1 { fn get_coeff_b() -> Fq { super::super::fq::B_COEFF } + + fn perform_pairing(&self, other: &G2Affine) -> Fq12 { + super::super::Bls12::pairing(*self, *other) + } } impl G1 { @@ -1122,10 +1134,11 @@ pub mod g1 { pub mod g2 { use rand::{Rand, Rng}; - use super::super::{Fq2, Fr, Fq, FrRepr, FqRepr}; - use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError}; + use super::super::{Fq2, Fr, Fq, FrRepr, FqRepr, Fq12}; + use super::g1::G1Affine; + use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError, Engine}; - curve_impl!("G2", G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed); + curve_impl!("G2", G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed, G1Affine); #[derive(Copy)] pub struct G2Uncompressed([u8; 192]); @@ -1403,6 +1416,10 @@ pub mod g2 { c1: super::super::fq::B_COEFF } } + + fn perform_pairing(&self, other: &G1Affine) -> Fq12 { + super::super::Bls12::pairing(*other, *self) + } } impl G2 { diff --git a/src/lib.rs b/src/lib.rs index ca38451..6e98aa6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,13 +42,13 @@ pub trait Engine { type G1: CurveProjective<Base=Self::Fq, Scalar=Self::Fr, Affine=Self::G1Affine> + From<Self::G1Affine>; /// The affine representation of an element in G1. - type G1Affine: CurveAffine<Base=Self::Fq, Scalar=Self::Fr, Projective=Self::G1> + From<Self::G1>; + type G1Affine: CurveAffine<Base=Self::Fq, Scalar=Self::Fr, Projective=Self::G1, Pair=Self::G2Affine, PairingResult=Self::Fqk> + From<Self::G1>; /// The projective representation of an element in G2. type G2: CurveProjective<Base=Self::Fqe, Scalar=Self::Fr, Affine=Self::G2Affine> + From<Self::G2Affine>; /// The affine representation of an element in G2. - type G2Affine: CurveAffine<Base=Self::Fqe, Scalar=Self::Fr, Projective=Self::G2> + From<Self::G2>; + type G2Affine: CurveAffine<Base=Self::Fqe, Scalar=Self::Fr, Projective=Self::G2, Pair=Self::G1Affine, PairingResult=Self::Fqk> + From<Self::G2>; /// The base field that hosts G1. type Fq: PrimeField + SqrtField; @@ -172,6 +172,8 @@ pub trait CurveAffine: Copy + type Prepared: Clone + Send + Sync + 'static; type Uncompressed: EncodedPoint<Affine=Self>; type Compressed: EncodedPoint<Affine=Self>; + type Pair: CurveAffine<Pair=Self>; + type PairingResult: Field; /// Returns the additive identity. fn zero() -> Self; @@ -192,6 +194,9 @@ pub trait CurveAffine: Copy + /// Prepares this element for pairing purposes. fn prepare(&self) -> Self::Prepared; + /// Perform a pairing + fn pairing_with(&self, other: &Self::Pair) -> Self::PairingResult; + /// Converts this element into its affine representation. fn into_projective(&self) -> Self::Projective; diff --git a/src/tests/engine.rs b/src/tests/engine.rs index b1bb754..03ffc6e 100644 --- a/src/tests/engine.rs +++ b/src/tests/engine.rs @@ -6,6 +6,14 @@ pub fn engine_tests<E: Engine>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + for _ in 0..10 { + let a = E::G1::rand(&mut rng).into_affine(); + let b = E::G2::rand(&mut rng).into_affine(); + + assert!(a.pairing_with(&b) == b.pairing_with(&a)); + assert!(a.pairing_with(&b) == E::pairing(a, b)); + } + for _ in 0..1000 { let z1 = E::G1Affine::zero().prepare(); let z2 = E::G2Affine::zero().prepare(); From f111008d6634d5113ccfd340f7db098258194a01 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 23 Jul 2017 20:21:08 -0600 Subject: [PATCH 027/140] Version bump to 0.10.1. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c7dcb2f..bb8295a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pairing" -version = "0.10.0" +version = "0.10.1" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" From 7960f69ba9e110ae27296512c33afa2e00be20cc Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 24 Jul 2017 09:51:20 -0600 Subject: [PATCH 028/140] Use newer version of clippy. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bb8295a..677fe6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1.1.0" -clippy = { version = "0.0.144", optional = true } +clippy = { version = "0.0.145", optional = true } [features] unstable-wnaf = [] From 1a481bc7410f008476f70bf69a8d306531647dd8 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sat, 29 Jul 2017 22:47:06 -0600 Subject: [PATCH 029/140] PrimeField::s() should return u32. --- src/bls12_381/fq.rs | 4 ++-- src/bls12_381/fr.rs | 4 ++-- src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index e7be0ec..a59d068 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -25,7 +25,7 @@ const INV: u64 = 0x89f3fffcfffcfffd; const GENERATOR: FqRepr = FqRepr([0x321300000006554f, 0xb93c0018d6c40005, 0x57605e0db0ddbb51, 0x8b256521ed1f9bcb, 0x6cf28d7901622c03, 0x11ebab9dbb81e28c]); // 2^s * t = MODULUS - 1 with t odd -const S: usize = 1; +const S: u32 = 1; // 2^s root of unity computed by GENERATOR^t const ROOT_OF_UNITY: FqRepr = FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206]); @@ -444,7 +444,7 @@ impl PrimeField for Fq { Fq(GENERATOR) } - fn s() -> usize { + fn s() -> u32 { S } diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 63cc264..d62f265 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -23,7 +23,7 @@ const INV: u64 = 0xfffffffeffffffff; const GENERATOR: FrRepr = FrRepr([0xefffffff1, 0x17e363d300189c0f, 0xff9c57876f8457b0, 0x351332208fc5a8c4]); // 2^s * t = MODULUS - 1 with t odd -const S: usize = 32; +const S: u32 = 32; // 2^s root of unity computed by GENERATOR^t const ROOT_OF_UNITY: FrRepr = FrRepr([0xb9b58d8c5f0e466a, 0x5b1b4c801819d7ec, 0xaf53ae352a31e64, 0x5bf3adda19e9b27b]); @@ -264,7 +264,7 @@ impl PrimeField for Fr { Fr(GENERATOR) } - fn s() -> usize { + fn s() -> u32 { S } diff --git a/src/lib.rs b/src/lib.rs index 6e98aa6..12b4af6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -496,7 +496,7 @@ pub trait PrimeField: Field fn multiplicative_generator() -> Self; /// Returns s such that 2^s * t = `char()` - 1 with t odd. - fn s() -> usize; + fn s() -> u32; /// Returns the 2^s root of unity computed by exponentiating the `multiplicative_generator()` /// by t. From 9af0c7dd30b3f6ab6b6536c0957b2df1b5864349 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sat, 29 Jul 2017 22:50:48 -0600 Subject: [PATCH 030/140] PrimeFieldRepr::divn() should accept u32. --- src/bls12_381/fq.rs | 4 ++-- src/bls12_381/fr.rs | 4 ++-- src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index a59d068..0ef6706 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -10,7 +10,7 @@ const MODULUS_BITS: u32 = 381; // The number of bits that must be shaved from the beginning of // the representation when randomly sampling. -const REPR_SHAVE_BITS: usize = 3; +const REPR_SHAVE_BITS: u32 = 3; // R = 2**384 % q const R: FqRepr = FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493]); @@ -278,7 +278,7 @@ impl PrimeFieldRepr for FqRepr { } #[inline(always)] - fn divn(&mut self, mut n: usize) { + fn divn(&mut self, mut n: u32) { if n >= 64 * 6 { *self = Self::from(0); return; diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index d62f265..a91c49c 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -8,7 +8,7 @@ const MODULUS_BITS: u32 = 255; // The number of bits that must be shaved from the beginning of // the representation when randomly sampling. -const REPR_SHAVE_BITS: usize = 1; +const REPR_SHAVE_BITS: u32 = 1; // R = 2**256 % r const R: FrRepr = FrRepr([0x1fffffffe, 0x5884b7fa00034802, 0x998c4fefecbc4ff5, 0x1824b159acc5056f]); @@ -114,7 +114,7 @@ impl PrimeFieldRepr for FrRepr { } #[inline(always)] - fn divn(&mut self, mut n: usize) { + fn divn(&mut self, mut n: u32) { if n >= 64 * 4 { *self = Self::from(0); return; diff --git a/src/lib.rs b/src/lib.rs index 12b4af6..0d58c68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -369,7 +369,7 @@ pub trait PrimeFieldRepr: Sized + fn div2(&mut self); /// Performs a rightwise bitshift of this number by some amount. - fn divn(&mut self, amt: usize); + fn divn(&mut self, amt: u32); /// Performs a leftwise bitshift of this number, effectively multiplying /// it by 2. Overflow is ignored. From dcca363d1b83853d51e256362195569b734b049a Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sat, 29 Jul 2017 23:20:59 -0600 Subject: [PATCH 031/140] Add muln() to PrimeFieldRepr along with tests for muln/divn. --- src/bls12_381/fq.rs | 26 ++++++++++++++++++ src/bls12_381/fr.rs | 26 ++++++++++++++++++ src/lib.rs | 6 ++++- src/tests/repr.rs | 64 ++++++++++++++++++++++++++++++++++++++------- 4 files changed, 111 insertions(+), 11 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 0ef6706..ea69d0e 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -325,6 +325,32 @@ impl PrimeFieldRepr for FqRepr { } } + #[inline(always)] + fn muln(&mut self, mut n: u32) { + if n >= 64 * 6 { + *self = Self::from(0); + return; + } + + while n >= 64 { + let mut t = 0; + for i in self.0.iter_mut() { + ::std::mem::swap(&mut t, i); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + for i in &mut self.0 { + let t2 = *i >> (64 - n); + *i <<= n; + *i |= t; + t = t2; + } + } + } + #[inline(always)] fn num_bits(&self) -> u32 { let mut ret = (6 as u32) * 64; diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index a91c49c..6d6c9ef 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -161,6 +161,32 @@ impl PrimeFieldRepr for FrRepr { } } + #[inline(always)] + fn muln(&mut self, mut n: u32) { + if n >= 64 * 4 { + *self = Self::from(0); + return; + } + + while n >= 64 { + let mut t = 0; + for i in self.0.iter_mut() { + ::std::mem::swap(&mut t, i); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + for i in &mut self.0 { + let t2 = *i >> (64 - n); + *i <<= n; + *i |= t; + t = t2; + } + } + } + #[inline(always)] fn num_bits(&self) -> u32 { let mut ret = (4 as u32) * 64; diff --git a/src/lib.rs b/src/lib.rs index 0d58c68..a8d72ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,7 +352,8 @@ pub trait PrimeFieldRepr: Sized + /// Add another representation to this one, returning the carry bit. fn add_nocarry(&mut self, other: &Self) -> bool; - /// Compute the number of bits needed to encode this number. + /// Compute the number of bits needed to encode this number. Always a + /// multiple of 64. fn num_bits(&self) -> u32; /// Returns true iff this number is zero. @@ -375,6 +376,9 @@ pub trait PrimeFieldRepr: Sized + /// it by 2. Overflow is ignored. fn mul2(&mut self); + /// Performs a leftwise bitshift of this number by some amount. + fn muln(&mut self, amt: u32); + /// Writes this `PrimeFieldRepr` as a big endian integer. Always writes /// `(num_bits` / 8) bytes. fn write_be<W: Write>(&self, mut writer: W) -> io::Result<()> { diff --git a/src/tests/repr.rs b/src/tests/repr.rs index b0c119c..9403df0 100644 --- a/src/tests/repr.rs +++ b/src/tests/repr.rs @@ -2,20 +2,64 @@ use rand::{SeedableRng, XorShiftRng}; use ::{PrimeFieldRepr}; pub fn random_repr_tests<R: PrimeFieldRepr>() { - random_encoding_tests::<R>(); + random_encoding_tests::<R>(); + random_muln_tests::<R>(); + random_divn_tests::<R>(); } fn random_encoding_tests<R: PrimeFieldRepr>() { - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - for _ in 0..1000 { - let r = R::rand(&mut rng); - let mut rdecoded = R::default(); + for _ in 0..1000 { + let r = R::rand(&mut rng); + let mut rdecoded = R::default(); - let mut v: Vec<u8> = vec![]; - r.write_be(&mut v).unwrap(); - rdecoded.read_be(&v[0..]).unwrap(); + let mut v: Vec<u8> = vec![]; + r.write_be(&mut v).unwrap(); + rdecoded.read_be(&v[0..]).unwrap(); - assert_eq!(r, rdecoded); - } + assert_eq!(r, rdecoded); + } +} + +fn random_muln_tests<R: PrimeFieldRepr>() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let r = R::rand(&mut rng); + + for shift in 0..(r.num_bits()+1) { + let mut r1 = r; + let mut r2 = r; + + for _ in 0..shift { + r1.mul2(); + } + + r2.muln(shift); + + assert_eq!(r1, r2); + } + } +} + +fn random_divn_tests<R: PrimeFieldRepr>() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let r = R::rand(&mut rng); + + for shift in 0..(r.num_bits()+1) { + let mut r1 = r; + let mut r2 = r; + + for _ in 0..shift { + r1.div2(); + } + + r2.divn(shift); + + assert_eq!(r1, r2); + } + } } From 40ec9891848e4c49a27aa696812ed216c0cde320 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 30 Jul 2017 00:11:34 -0600 Subject: [PATCH 032/140] Add (not particularly efficient) from_str to PrimeField. --- src/bls12_381/fq.rs | 1 + src/bls12_381/fr.rs | 1 + src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/tests/field.rs | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index ea69d0e..cf81975 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1766,6 +1766,7 @@ fn fq_field_tests() { ::tests::field::random_field_tests::<Fq>(); ::tests::field::random_sqrt_tests::<Fq>(); ::tests::field::random_frobenius_tests::<Fq, _>(Fq::char(), 13); + ::tests::field::from_str_tests::<Fq>(); } #[test] diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 6d6c9ef..dcfc11b 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1478,6 +1478,7 @@ fn fr_field_tests() { ::tests::field::random_field_tests::<Fr>(); ::tests::field::random_sqrt_tests::<Fr>(); ::tests::field::random_frobenius_tests::<Fr, _>(Fr::char(), 13); + ::tests::field::from_str_tests::<Fr>(); } #[test] diff --git a/src/lib.rs b/src/lib.rs index a8d72ce..96ea04b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -477,6 +477,46 @@ pub trait PrimeField: Field /// representation. type Repr: PrimeFieldRepr + From<Self>; + /// Interpret a string of numbers as a (congruent) prime field element. + /// Does not accept unnecessary leading zeroes or a blank string. + fn from_str(s: &str) -> Option<Self> { + if s.len() == 0 { + return None; + } + + if s == "0" { + return Some(Self::zero()); + } + + let mut res = Self::zero(); + + let ten = Self::from_repr(Self::Repr::from(10)).unwrap(); + + let mut first_digit = true; + + for c in s.chars() { + match c.to_digit(10) { + Some(c) => { + if first_digit { + if c == 0 { + return None; + } + + first_digit = false; + } + + res.mul_assign(&ten); + res.add_assign(&Self::from_repr(Self::Repr::from(c as u64)).unwrap()); + }, + None => { + return None; + } + } + } + + Some(res) + } + /// Convert this prime field element into a biginteger representation. fn from_repr(Self::Repr) -> Result<Self, PrimeFieldDecodingError>; diff --git a/src/tests/field.rs b/src/tests/field.rs index dbc9f8c..5f99992 100644 --- a/src/tests/field.rs +++ b/src/tests/field.rs @@ -1,5 +1,5 @@ use rand::{Rng, SeedableRng, XorShiftRng}; -use ::{SqrtField, Field}; +use ::{SqrtField, Field, PrimeField}; pub fn random_frobenius_tests<F: Field, C: AsRef<[u64]>>(characteristic: C, maxpower: usize) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -87,6 +87,40 @@ pub fn random_field_tests<F: Field>() { } } +pub fn from_str_tests<F: PrimeField>() { + { + let a = "84395729384759238745923745892374598234705297301958723458712394587103249587213984572934750213947582345792304758273458972349582734958273495872304598234"; + let b = "38495729084572938457298347502349857029384609283450692834058293405982304598230458230495820394850293845098234059823049582309485203948502938452093482039"; + let c = "3248875134290623212325429203829831876024364170316860259933542844758450336418538569901990710701240661702808867062612075657861768196242274635305077449545396068598317421057721935408562373834079015873933065667961469731886739181625866970316226171512545167081793907058686908697431878454091011239990119126"; + + let mut a = F::from_str(a).unwrap(); + let b = F::from_str(b).unwrap(); + let c = F::from_str(c).unwrap(); + + a.mul_assign(&b); + + assert_eq!(a, c); + } + + { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let n: u64 = rng.gen(); + + let a = F::from_str(&format!("{}", n)).unwrap(); + let b = F::from_repr(n.into()).unwrap(); + + assert_eq!(a, b); + } + } + + assert!(F::from_str("").is_none()); + assert!(F::from_str("0").unwrap().is_zero()); + assert!(F::from_str("00").is_none()); + assert!(F::from_str("00000000000").is_none()); +} + fn random_multiplication_tests<F: Field, R: Rng>(rng: &mut R) { for _ in 0..10000 { let a = F::rand(rng); From 053698eefb59278bc2e4e4ff5fff9786a96acd02 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 30 Jul 2017 00:54:23 -0600 Subject: [PATCH 033/140] Add `Engine` associated type to CurveProject/CurveAffine. --- src/bls12_381/ec.rs | 6 ++++-- src/lib.rs | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index a4eaa1a..16deca5 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -108,6 +108,7 @@ macro_rules! curve_impl { } impl CurveAffine for $affine { + type Engine = Bls12; type Scalar = $scalarfield; type Base = $basefield; type Prepared = $prepared; @@ -174,6 +175,7 @@ macro_rules! curve_impl { } impl CurveProjective for $projective { + type Engine = Bls12; type Scalar = $scalarfield; type Base = $basefield; type Affine = $affine; @@ -582,7 +584,7 @@ macro_rules! curve_impl { pub mod g1 { use rand::{Rand, Rng}; use super::g2::G2Affine; - use super::super::{Fq, Fr, FrRepr, FqRepr, Fq12}; + use super::super::{Bls12, Fq, Fr, FrRepr, FqRepr, Fq12}; use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError, Engine}; curve_impl!("G1", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed, G2Affine); @@ -1134,7 +1136,7 @@ pub mod g1 { pub mod g2 { use rand::{Rand, Rng}; - use super::super::{Fq2, Fr, Fq, FrRepr, FqRepr, Fq12}; + use super::super::{Bls12, Fq2, Fr, Fq, FrRepr, FqRepr, Fq12}; use super::g1::G1Affine; use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError, Engine}; diff --git a/src/lib.rs b/src/lib.rs index 96ea04b..55b0c19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,21 +34,21 @@ use std::io::{self, Read, Write}; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are /// of prime order `r`, and are equipped with a bilinear pairing function. -pub trait Engine { +pub trait Engine: Sized { /// This is the scalar field of the G1/G2 groups. type Fr: PrimeField; /// The projective representation of an element in G1. - type G1: CurveProjective<Base=Self::Fq, Scalar=Self::Fr, Affine=Self::G1Affine> + From<Self::G1Affine>; + type G1: CurveProjective<Engine=Self, Base=Self::Fq, Scalar=Self::Fr, Affine=Self::G1Affine> + From<Self::G1Affine>; /// The affine representation of an element in G1. - type G1Affine: CurveAffine<Base=Self::Fq, Scalar=Self::Fr, Projective=Self::G1, Pair=Self::G2Affine, PairingResult=Self::Fqk> + From<Self::G1>; + type G1Affine: CurveAffine<Engine=Self, Base=Self::Fq, Scalar=Self::Fr, Projective=Self::G1, Pair=Self::G2Affine, PairingResult=Self::Fqk> + From<Self::G1>; /// The projective representation of an element in G2. - type G2: CurveProjective<Base=Self::Fqe, Scalar=Self::Fr, Affine=Self::G2Affine> + From<Self::G2Affine>; + type G2: CurveProjective<Engine=Self, Base=Self::Fqe, Scalar=Self::Fr, Affine=Self::G2Affine> + From<Self::G2Affine>; /// The affine representation of an element in G2. - type G2Affine: CurveAffine<Base=Self::Fqe, Scalar=Self::Fr, Projective=Self::G2, Pair=Self::G1Affine, PairingResult=Self::Fqk> + From<Self::G2>; + type G2Affine: CurveAffine<Engine=Self, Base=Self::Fqe, Scalar=Self::Fr, Projective=Self::G2, Pair=Self::G1Affine, PairingResult=Self::Fqk> + From<Self::G2>; /// The base field that hosts G1. type Fq: PrimeField + SqrtField; @@ -97,6 +97,7 @@ pub trait CurveProjective: PartialEq + rand::Rand + 'static { + type Engine: Engine; type Scalar: PrimeField; type Base: SqrtField; type Affine: CurveAffine<Projective=Self, Scalar=Self::Scalar>; @@ -166,6 +167,7 @@ pub trait CurveAffine: Copy + Eq + 'static { + type Engine: Engine; type Scalar: PrimeField; type Base: SqrtField; type Projective: CurveProjective<Affine=Self, Scalar=Self::Scalar>; From 05183c5348c2746d9bb1ccd3b4355a28ff02be2f Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 30 Jul 2017 00:55:18 -0600 Subject: [PATCH 034/140] Make Fq6 public. --- src/bls12_381/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 6454877..28af9d8 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -11,6 +11,7 @@ mod tests; pub use self::fr::{Fr, FrRepr}; pub use self::fq::{Fq, FqRepr}; pub use self::fq2::Fq2; +pub use self::fq6::Fq6; pub use self::fq12::Fq12; pub use self::ec::{G1, G2, G1Affine, G2Affine, G1Prepared, G2Prepared, G1Uncompressed, G2Uncompressed, G1Compressed, G2Compressed}; From 964fad5a67ab9d149ded86bdb3283a72589cd93d Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 30 Jul 2017 00:56:24 -0600 Subject: [PATCH 035/140] Version bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 677fe6a..34e3073 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pairing" -version = "0.10.1" +version = "0.10.2" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" From ba2c9c7bf0a19d359b845146c91485eea557d8f2 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 30 Jul 2017 01:12:35 -0600 Subject: [PATCH 036/140] Clippy update. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 34e3073..bd46ece 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1.1.0" -clippy = { version = "0.0.145", optional = true } +clippy = { version = "0.0.146", optional = true } [features] unstable-wnaf = [] From 34aa52b0f7bef705917252ea63e5a13fa01af551 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 31 Jul 2017 08:41:51 -0600 Subject: [PATCH 037/140] Another clippy update. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bd46ece..9a068ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1.1.0" -clippy = { version = "0.0.146", optional = true } +clippy = { version = "0.0.147", optional = true } [features] unstable-wnaf = [] From 6410bdf998d48a5b4f9753ad8030e83a655ae50c Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 31 Jul 2017 09:39:57 -0600 Subject: [PATCH 038/140] Adopt idiomatic code suggestions. --- src/bls12_381/fq.rs | 2 +- src/bls12_381/fr.rs | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index cf81975..edfe150 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -334,7 +334,7 @@ impl PrimeFieldRepr for FqRepr { while n >= 64 { let mut t = 0; - for i in self.0.iter_mut() { + for i in &mut self.0 { ::std::mem::swap(&mut t, i); } n -= 64; diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index dcfc11b..9fc90ee 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -170,7 +170,7 @@ impl PrimeFieldRepr for FrRepr { while n >= 64 { let mut t = 0; - for i in self.0.iter_mut() { + for i in &mut self.0 { ::std::mem::swap(&mut t, i); } n -= 64; diff --git a/src/lib.rs b/src/lib.rs index 55b0c19..35e9a63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -482,7 +482,7 @@ pub trait PrimeField: Field /// Interpret a string of numbers as a (congruent) prime field element. /// Does not accept unnecessary leading zeroes or a blank string. fn from_str(s: &str) -> Option<Self> { - if s.len() == 0 { + if s.is_empty() { return None; } From ca5efdcebefb0f7ff2f0096c14e66cbd316ad8bc Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 1 Aug 2017 11:23:42 -0600 Subject: [PATCH 039/140] Update clippy again. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9a068ae..a19bc00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1.1.0" -clippy = { version = "0.0.147", optional = true } +clippy = { version = "0.0.148", optional = true } [features] unstable-wnaf = [] From 766c902d8c0650e7c72870aeb6168024added33c Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 1 Aug 2017 11:37:51 -0600 Subject: [PATCH 040/140] Allow `unreadable_literal` lint. --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 35e9a63..62abed9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ #![cfg_attr(feature = "clippy", plugin(clippy))] #![cfg_attr(feature = "clippy", allow(inline_always))] #![cfg_attr(feature = "clippy", allow(too_many_arguments))] +#![cfg_attr(feature = "clippy", allow(unreadable_literal))] // The compiler provides `test` (on nightly) for benchmarking tools, but // it's hidden behind a feature flag. Enable it if we're testing. From 21b0384f2848410f114fc05ba04d8ce7e42f9d7c Mon Sep 17 00:00:00 2001 From: Jason Davies <jason@jasondavies.com> Date: Mon, 14 Aug 2017 00:22:54 +0100 Subject: [PATCH 041/140] Fix for superscript period appearing in docs. --- src/bls12_381/fq6.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs index 2c62a92..4d1e470 100644 --- a/src/bls12_381/fq6.rs +++ b/src/bls12_381/fq6.rs @@ -3,7 +3,7 @@ use ::{Field}; use super::fq2::Fq2; use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; -/// An element of Fq6, represented by c0 + c1 * v + c2 * v^2. +/// An element of Fq6, represented by c0 + c1 * v + c2 * v^(2). #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq6 { pub c0: Fq2, From d2f47cfa2d3306f0367eb373cb04a70c85b0a38a Mon Sep 17 00:00:00 2001 From: Jason Davies <jason@jasondavies.com> Date: Mon, 14 Aug 2017 09:46:55 +0100 Subject: [PATCH 042/140] Fix minor typos in comments/docs. --- src/bls12_381/fq.rs | 2 +- src/bls12_381/fr.rs | 2 +- src/lib.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index edfe150..6ebd8d4 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -713,7 +713,7 @@ impl Fq { } /// Subtracts the modulus from this element if this element is not in the - /// field. Only used interally. + /// field. Only used internally. #[inline(always)] fn reduce(&mut self) { if !self.is_valid() { diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 9fc90ee..8a1fbd5 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -488,7 +488,7 @@ impl Fr { } /// Subtracts the modulus from this element if this element is not in the - /// field. Only used interally. + /// field. Only used internally. #[inline(always)] fn reduce(&mut self) { if !self.is_valid() { diff --git a/src/lib.rs b/src/lib.rs index 62abed9..37f5111 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -349,7 +349,7 @@ pub trait PrimeFieldRepr: Sized + AsMut<[u64]> + From<u64> { - /// Subtract another reprensetation from this one, returning the borrow bit. + /// Subtract another represetation from this one, returning the borrow bit. fn sub_noborrow(&mut self, other: &Self) -> bool; /// Add another representation to this one, returning the carry bit. @@ -442,7 +442,7 @@ pub enum GroupDecodingError { NotInSubgroup, /// One of the coordinates could not be decoded CoordinateDecodingError(&'static str, PrimeFieldDecodingError), - /// The compression mode of the encoded elemnet was not as expected + /// The compression mode of the encoded element was not as expected UnexpectedCompressionMode, /// The encoding contained bits that should not have been set UnexpectedInformation @@ -523,7 +523,7 @@ pub trait PrimeField: Field /// Convert this prime field element into a biginteger representation. fn from_repr(Self::Repr) -> Result<Self, PrimeFieldDecodingError>; - /// Convert a biginteger reprensentation into a prime field element, if + /// Convert a biginteger representation into a prime field element, if /// the number is an element of the field. fn into_repr(&self) -> Self::Repr; From 122d2b161d380c23da15f4ee6fc6a648917a25b7 Mon Sep 17 00:00:00 2001 From: Jason Davies <jason@jasondavies.com> Date: Mon, 14 Aug 2017 16:41:55 +0100 Subject: [PATCH 043/140] Update clippy to 0.0.151. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a19bc00..9cb5e3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1.1.0" -clippy = { version = "0.0.148", optional = true } +clippy = { version = "0.0.151", optional = true } [features] unstable-wnaf = [] From b1d981e9165b779d2afe2519a452dcce87b6e70a Mon Sep 17 00:00:00 2001 From: Jason Davies <jason@jasondavies.com> Date: Mon, 14 Aug 2017 17:16:21 +0100 Subject: [PATCH 044/140] Replace inline value with const. --- src/bls12_381/fq.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 6ebd8d4..51d18b7 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -822,8 +822,7 @@ impl SqrtField for Fq { a0.square(); a0.mul_assign(self); - // if a0 == -1 - if a0.0 == FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206]) + if a0 == NEGATIVE_ONE { None } From b893aa17aca00e7a9fade944e8c5988ae0e66698 Mon Sep 17 00:00:00 2001 From: Jason Davies <jason@jasondavies.com> Date: Mon, 14 Aug 2017 22:15:24 +0100 Subject: [PATCH 045/140] Remove redundant `use`. --- src/bls12_381/fq.rs | 2 -- src/bls12_381/fr.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 51d18b7..7f07dfd 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -231,8 +231,6 @@ impl AsMut<[u64]> for FqRepr { impl From<u64> for FqRepr { #[inline(always)] fn from(val: u64) -> FqRepr { - use std::default::Default; - let mut repr = Self::default(); repr.0[0] = val; repr diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 8a1fbd5..3796c51 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -67,8 +67,6 @@ impl AsMut<[u64]> for FrRepr { impl From<u64> for FrRepr { #[inline(always)] fn from(val: u64) -> FrRepr { - use std::default::Default; - let mut repr = Self::default(); repr.0[0] = val; repr From 3908552fb93cc692f01e6418181ddf2c74f210cd Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 15 Aug 2017 12:53:22 -0600 Subject: [PATCH 046/140] Introduce stable arithmetic to avoid nightly/unstable features for some platforms. --- Cargo.toml | 2 + src/lib.rs | 129 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 106 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9cb5e3d..d13ce4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,5 @@ clippy = { version = "0.0.151", optional = true } [features] unstable-wnaf = [] unstable-features = ["unstable-wnaf"] +u128-support = [] +default = ["u128-support"] diff --git a/src/lib.rs b/src/lib.rs index 37f5111..ba745fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -604,35 +604,114 @@ fn test_bit_iterator() { assert!(a.next().is_none()); } -/// Calculate a - b - borrow, returning the result and modifying -/// the borrow value. -#[inline(always)] -pub(crate) fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { - let tmp = (1u128 << 64) + (a as u128) - (b as u128) - (*borrow as u128); +use self::arith::*; - *borrow = if tmp >> 64 == 0 { 1 } else { 0 }; +#[cfg(feature = "u128-support")] +mod arith { - tmp as u64 + /// Calculate a - b - borrow, returning the result and modifying + /// the borrow value. + #[inline(always)] + pub(crate) fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { + let tmp = (1u128 << 64) + (a as u128) - (b as u128) - (*borrow as u128); + + *borrow = if tmp >> 64 == 0 { 1 } else { 0 }; + + tmp as u64 + } + + /// Calculate a + b + carry, returning the sum and modifying the + /// carry value. + #[inline(always)] + pub(crate) fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { + let tmp = (a as u128) + (b as u128) + (*carry as u128); + + *carry = (tmp >> 64) as u64; + + tmp as u64 + } + + /// Calculate a + (b * c) + carry, returning the least significant digit + /// and setting carry to the most significant digit. + #[inline(always)] + pub(crate) fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { + let tmp = (a as u128) + (b as u128) * (c as u128) + (*carry as u128); + + *carry = (tmp >> 64) as u64; + + tmp as u64 + } } -/// Calculate a + b + carry, returning the sum and modifying the -/// carry value. -#[inline(always)] -pub(crate) fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { - let tmp = (a as u128) + (b as u128) + (*carry as u128); +#[cfg(not(feature = "u128-support"))] +mod arith { + #[inline(always)] + fn split_u64(i: u64) -> (u64, u64) { + (i >> 32, i & 0xFFFFFFFF) + } - *carry = (tmp >> 64) as u64; + #[inline(always)] + fn combine_u64(hi: u64, lo: u64) -> u64 { + (hi << 32) | lo + } - tmp as u64 -} - -/// Calculate a + (b * c) + carry, returning the least significant digit -/// and setting carry to the most significant digit. -#[inline(always)] -pub(crate) fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { - let tmp = (a as u128) + (b as u128) * (c as u128) + (*carry as u128); - - *carry = (tmp >> 64) as u64; - - tmp as u64 + #[inline(always)] + pub(crate) fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { + let (a_hi, a_lo) = split_u64(a); + let (b_hi, b_lo) = split_u64(b); + let (b, r0) = split_u64((1 << 32) + a_lo - b_lo - *borrow); + let (b, r1) = split_u64((1 << 32) + a_hi - b_hi - ((b == 0) as u64)); + + *borrow = (b == 0) as u64; + + combine_u64(r1, r0) + } + + #[inline(always)] + pub(crate) fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { + let (a_hi, a_lo) = split_u64(a); + let (b_hi, b_lo) = split_u64(b); + let (carry_hi, carry_lo) = split_u64(*carry); + + let (t, r0) = split_u64(a_lo + b_lo + carry_lo); + let (t, r1) = split_u64(t + a_hi + b_hi + carry_hi); + + *carry = t; + + combine_u64(r1, r0) + } + + #[inline(always)] + pub(crate) fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { + /* + [ b_hi | b_lo ] + [ c_hi | c_lo ] * + ------------------------------------------- + [ b_lo * c_lo ] <-- w + [ b_hi * c_lo ] <-- x + [ b_lo * c_hi ] <-- y + [ b_hi * c_lo ] <-- z + [ a_hi | a_lo ] + [ C_hi | C_lo ] + */ + + let (a_hi, a_lo) = split_u64(a); + let (b_hi, b_lo) = split_u64(b); + let (c_hi, c_lo) = split_u64(c); + let (carry_hi, carry_lo) = split_u64(*carry); + + let (w_hi, w_lo) = split_u64(b_lo * c_lo); + let (x_hi, x_lo) = split_u64(b_hi * c_lo); + let (y_hi, y_lo) = split_u64(b_lo * c_hi); + let (z_hi, z_lo) = split_u64(b_hi * c_hi); + + let (t, r0) = split_u64(w_lo + a_lo + carry_lo); + let (t, r1) = split_u64(t + w_hi + x_lo + y_lo + a_hi + carry_hi); + let (t, r2) = split_u64(t + x_hi + y_hi + z_lo); + let (_, r3) = split_u64(t + z_hi); + + *carry = combine_u64(r3, r2); + + combine_u64(r1, r0) + } } From 51b16c52b305fb10e8bbe15d5b4ff087f6e46082 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 15 Aug 2017 13:54:51 -0600 Subject: [PATCH 047/140] Only enable `i128_type` when necessary. --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ba745fa..1f7e724 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ // This library relies on the Rust nightly compiler's `i128_type` feature. -#![feature(i128_type)] +// If that's not okay for you, disable the u128-support feature. (Pass +// --no-default-features for example.) +#![cfg_attr(feature = "u128-support", feature(i128_type))] // `clippy` is a code linting tool for improving code quality by catching // common mistakes or strange code patterns. If the `clippy` feature is From ec49fcc63801b663b46a7b57f342716e02dd80c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Wed, 16 Aug 2017 12:08:52 +0200 Subject: [PATCH 048/140] s/is_in_correct_subgroup/is_in_correct_subgroup_assuming_on_curve/ Literelly run: $ sed -i s/is_in_correct_subgroup/is_in_correct_subgroup_assuming_on_curve/g bls12_381/ec.rs --- src/bls12_381/ec.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 16deca5..ae6e9c2 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -102,7 +102,7 @@ macro_rules! curve_impl { } } - fn is_in_correct_subgroup(&self) -> bool { + fn is_in_correct_subgroup_assuming_on_curve(&self) -> bool { self.mul($scalarfield::char()).is_zero() } } @@ -620,7 +620,7 @@ pub mod g1 { if !affine.is_on_curve() { Err(GroupDecodingError::NotOnCurve) - } else if !affine.is_in_correct_subgroup() { + } else if !affine.is_in_correct_subgroup_assuming_on_curve() { Err(GroupDecodingError::NotInSubgroup) } else { Ok(affine) @@ -722,7 +722,7 @@ pub mod g1 { // NB: Decompression guarantees that it is on the curve already. - if !affine.is_in_correct_subgroup() { + if !affine.is_in_correct_subgroup_assuming_on_curve() { Err(GroupDecodingError::NotInSubgroup) } else { Ok(affine) @@ -916,7 +916,7 @@ pub mod g1 { infinity: false }; - assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); let mut g1 = G1::zero(); @@ -938,7 +938,7 @@ pub mod g1 { assert_eq!(i, 4); let g1 = G1Affine::from(g1); - assert!(g1.is_in_correct_subgroup()); + assert!(g1.is_in_correct_subgroup_assuming_on_curve()); assert_eq!(g1, G1Affine::one()); break; @@ -960,7 +960,7 @@ pub mod g1 { infinity: false }; assert!(!p.is_on_curve()); - assert!(p.is_in_correct_subgroup()); + assert!(p.is_in_correct_subgroup_assuming_on_curve()); } // Reject point on a twist (b = 3) @@ -971,7 +971,7 @@ pub mod g1 { infinity: false }; assert!(!p.is_on_curve()); - assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); } // Reject point in an invalid subgroup @@ -983,7 +983,7 @@ pub mod g1 { infinity: false }; assert!(p.is_on_curve()); - assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); } } @@ -1059,9 +1059,9 @@ pub mod g1 { infinity: false }; - assert!(a.is_on_curve() && a.is_in_correct_subgroup()); - assert!(b.is_on_curve() && b.is_in_correct_subgroup()); - assert!(c.is_on_curve() && c.is_in_correct_subgroup()); + assert!(a.is_on_curve() && a.is_in_correct_subgroup_assuming_on_curve()); + assert!(b.is_on_curve() && b.is_in_correct_subgroup_assuming_on_curve()); + assert!(c.is_on_curve() && c.is_in_correct_subgroup_assuming_on_curve()); let mut tmp1 = a.into_projective(); tmp1.add_assign(&b.into_projective()); @@ -1173,7 +1173,7 @@ pub mod g2 { if !affine.is_on_curve() { Err(GroupDecodingError::NotOnCurve) - } else if !affine.is_in_correct_subgroup() { + } else if !affine.is_in_correct_subgroup_assuming_on_curve() { Err(GroupDecodingError::NotInSubgroup) } else { Ok(affine) @@ -1287,7 +1287,7 @@ pub mod g2 { // NB: Decompression guarantees that it is on the curve already. - if !affine.is_in_correct_subgroup() { + if !affine.is_in_correct_subgroup_assuming_on_curve() { Err(GroupDecodingError::NotInSubgroup) } else { Ok(affine) @@ -1487,7 +1487,7 @@ pub mod g2 { infinity: false }; - assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); let mut g2 = G2::zero(); @@ -1509,7 +1509,7 @@ pub mod g2 { assert_eq!(i, 2); let g2 = G2Affine::from(g2); - assert!(g2.is_in_correct_subgroup()); + assert!(g2.is_in_correct_subgroup_assuming_on_curve()); assert_eq!(g2, G2Affine::one()); break; @@ -1537,7 +1537,7 @@ pub mod g2 { infinity: false }; assert!(!p.is_on_curve()); - assert!(p.is_in_correct_subgroup()); + assert!(p.is_in_correct_subgroup_assuming_on_curve()); } // Reject point on a twist (b = 2 * (u + 1)) @@ -1554,7 +1554,7 @@ pub mod g2 { infinity: false }; assert!(!p.is_on_curve()); - assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); } // Reject point in an invalid subgroup @@ -1572,7 +1572,7 @@ pub mod g2 { infinity: false }; assert!(p.is_on_curve()); - assert!(!p.is_in_correct_subgroup()); + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); } } From 1239b7648d11fdb30187632dba9ad32326d1511c Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 22 Aug 2017 15:35:02 -0600 Subject: [PATCH 049/140] Release 0.11. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d13ce4b..b9beff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pairing" -version = "0.10.2" +version = "0.11.0" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" From 1b6cf8525118398f1f37c4073db61de67253fe89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Sun, 6 Aug 2017 15:24:01 +0200 Subject: [PATCH 050/140] Add Legendre symbol for Fq and Fq2. --- src/bls12_381/fq.rs | 45 +++++++++++++++++++++++++++++++++++++++++++- src/bls12_381/fq2.rs | 30 ++++++++++++++++++++++++++++- src/bls12_381/fr.rs | 29 +++++++++++++++++++++++++--- src/lib.rs | 8 ++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 7f07dfd..73d1e16 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,4 +1,4 @@ -use ::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; +use ::{Field, LegendreField, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; use std::cmp::Ordering; use super::fq2::Fq2; @@ -832,6 +832,23 @@ impl SqrtField for Fq { } } +impl LegendreField for Fq { + fn legendre(&self) -> i32 { + // (q - 1) / 2 = + // 2001204777610833696708894912867952078278441409969503942666029068062015825245418932221343814564507832018947136279893 + let x = self.pow([0xcff7fffffffd555, 0xf55ffff58a9ffffd, + 0x39869507b587b120, 0x23ba5c279c2895fb, + 0x58dd3db21a5d66bb, 0xd0088f51cbff34d2]); + if x == Self::one() { + 1 + } else if x == Self::zero() { + 0 + } else { + -1 + } + } +} + #[test] fn test_b_coeff() { assert_eq!(Fq::from_repr(FqRepr::from(4)).unwrap(), B_COEFF); @@ -1779,3 +1796,29 @@ fn test_fq_ordering() { fn fq_repr_tests() { ::tests::repr::random_repr_tests::<FqRepr>(); } +#[test] +fn test_fq_legendre() { + assert_eq!(1, Fq::one().legendre()); + assert_eq!(0, Fq::zero().legendre()); + + let e = Fq(FqRepr([0x914577fdcc41112, 0x1a6c20f3392c28e2, 0xd53f75da0c40fd21, + 0xb747c10d13caf0d0, 0x0de1adc19c24d8d2, 0x2103f924191033d2])); + assert_eq!(-1, e.legendre()); + + + // f + let e = Fq(FqRepr([0xe51d5292ae8126f, 0x382d60874f48db82, 0xb0dde25abc614254, + 0x34f4456bd18813df, 0x2c668010247ee04c, 0x44cb8bbdd7c4f1b0])); + assert_eq!(-1, e.legendre()); + + // f ** 9 + let e = Fq(FqRepr([0x69fc8eb1b590c712, 0xd73f4fb6fd34042e, 0xb5677ef2ed0eede7, + 0x367d831c592848c8, 0xb60615cc44e533f5, 0x127da624461b200e])); + + assert_eq!(-1, e.legendre()); + + let e = Fq(FqRepr([0x83c7ad9e29b7facc, 0x97b3c8fbdb50cc39, 0x9e2ccd0eb5db5e72, + 0xc74a00d90e1b247d, 0x90e38ef46c8d7eb7, 0x16882d6aa70bb469])); + assert_eq!(1, e.legendre()); + +} diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index a4d8e7c..ecb2965 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -1,5 +1,5 @@ use rand::{Rng, Rand}; -use ::{Field, SqrtField}; +use ::{Field, LegendreField, SqrtField}; use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; use std::cmp::Ordering; @@ -44,6 +44,17 @@ impl Fq2 { self.c0.sub_assign(&self.c1); self.c1.add_assign(&t0); } + + /// Norm of Fq2 as extension field in i over Fq + pub fn norm(&self) -> Fq { + let mut t0 = self.c0; + let mut t1 = self.c1; + t0.square(); + t1.square(); + t1.add_assign(&t0); + + t1 + } } impl Rand for Fq2 { @@ -185,6 +196,12 @@ impl SqrtField for Fq2 { } } +impl LegendreField for Fq2 { + fn legendre(&self) -> i32 { + Fq2::norm(&self).legendre() + } +} + #[test] fn test_fq2_ordering() { let mut a = Fq2 { @@ -412,6 +429,17 @@ fn test_fq2_sqrt() { ); } +#[test] +fn test_fq2_legendre() { + // i^2 = -1 + let mut m1 = Fq2::one(); + m1.negate(); + assert_eq!(1, m1.legendre()); + + m1.mul_by_nonresidue(); + assert_eq!(-1, m1.legendre()); +} + #[cfg(test)] use rand::{SeedableRng, XorShiftRng}; diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 3796c51..4081011 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,4 +1,4 @@ -use ::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; +use ::{Field, LegendreField, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; // r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 const MODULUS: FrRepr = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); @@ -559,8 +559,7 @@ impl SqrtField for Fr { return Some(*self); } - // if self^((r - 1) // 2) != 1 - if self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]) != Self::one() { + if self.legendre() != 1 { None } else { let mut c = Fr(ROOT_OF_UNITY); @@ -598,6 +597,19 @@ impl SqrtField for Fr { } } +impl LegendreField for Fr { + fn legendre(&self) -> i32 { + // (q - 1) / 2 = 26217937587563095239723870254092982918845276250263818911301829349969290592256 + let x = self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]); + if x == Self::one() { + 1 + } else if x == Self::zero() { + 0 + } else { + -1 + } + } +} #[cfg(test)] use rand::{SeedableRng, XorShiftRng, Rand}; @@ -778,6 +790,17 @@ fn test_fr_repr_sub_noborrow() { assert!(!x.sub_noborrow(&FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))) } +#[test] +fn test_fr_legendre() { + assert_eq!(1, Fr::one().legendre()); + assert_eq!(0, Fr::zero().legendre()); + + let e = Fr(FrRepr([0x0dbc5349cd5664da, 0x8ac5b6296e3ae29d, 0x127cb819feceaa3b, 0x3a6b21fb03867191])); + assert_eq!(1, e.legendre()); + let e = Fr(FrRepr([0x96341aefd047c045, 0x9b5f4254500a4d65, 0x1ee08223b68ac240, 0x31d9cd545c0ec7c6])); + assert_eq!(-1, e.legendre()); +} + #[test] fn test_fr_repr_add_nocarry() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/lib.rs b/src/lib.rs index 1f7e724..abd7328 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,6 +332,14 @@ pub trait SqrtField: Field fn sqrt(&self) -> Option<Self>; } +/// This trait represents an element of a field that has a Legendre symbol described for it. +pub trait LegendreField: Field +{ + /// Returns the legendre symbol of the field element. + fn legendre(&self) -> i32; +} + + /// This trait represents a wrapper around a biginteger which can encode any element of a particular /// prime field. It is a smart wrapper around a sequence of `u64` limbs, least-significant digit /// first. From 57b4e7362720b3a2969469ecb16c50058db5083e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Wed, 9 Aug 2017 14:09:54 +0200 Subject: [PATCH 051/140] Create enum for LegendreSymbol, fix test. --- src/bls12_381/ec.rs | 2 +- src/bls12_381/fq.rs | 57 +++++++++++++++++--------------------------- src/bls12_381/fq2.rs | 9 +++---- src/bls12_381/fr.rs | 34 ++++++++++++-------------- src/lib.rs | 9 ++++++- 5 files changed, 51 insertions(+), 60 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index ae6e9c2..9116844 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -1480,7 +1480,7 @@ pub mod g2 { if let Some(y) = rhs.sqrt() { let mut negy = y; negy.negate(); - + let p = G2Affine { x: x, y: if y < negy { y } else { negy }, diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 73d1e16..d2efa0d 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -833,19 +833,14 @@ impl SqrtField for Fq { } impl LegendreField for Fq { - fn legendre(&self) -> i32 { - // (q - 1) / 2 = - // 2001204777610833696708894912867952078278441409969503942666029068062015825245418932221343814564507832018947136279893 - let x = self.pow([0xcff7fffffffd555, 0xf55ffff58a9ffffd, - 0x39869507b587b120, 0x23ba5c279c2895fb, - 0x58dd3db21a5d66bb, 0xd0088f51cbff34d2]); - if x == Self::one() { - 1 - } else if x == Self::zero() { - 0 - } else { - -1 - } + fn legendre(&self) -> ::LegendreSymbol { + use ::LegendreSymbol::*; + + let s = self.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, + 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]); + if s == Fq::zero() { Zero } + else if s == Fq::one() { QResidue } + else { QNonResidue } } } @@ -1320,12 +1315,12 @@ fn test_fq_sub_assign() { let mut tmp = Fq(FqRepr([0x531221a410efc95b, 0x72819306027e9717, 0x5ecefb937068b746, 0x97de59cd6feaefd7, 0xdc35c51158644588, 0xb2d176c04f2100])); tmp.sub_assign(&Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806]))); assert_eq!(tmp, Fq(FqRepr([0x748014838971292c, 0xfd20fad49fddde5c, 0xcf87f198e3d3f336, 0x3d62d6e6e41883db, 0x45a3443cd88dc61b, 0x151d57aaf755ff94]))); - + // Test the opposite subtraction which doesn't test reduction. tmp = Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806])); tmp.sub_assign(&Fq(FqRepr([0x531221a410efc95b, 0x72819306027e9717, 0x5ecefb937068b746, 0x97de59cd6feaefd7, 0xdc35c51158644588, 0xb2d176c04f2100]))); assert_eq!(tmp, Fq(FqRepr([0x457eeb7c768e817f, 0x218b052a117621a3, 0x97a8e10812dd02ed, 0x2714749e0f6c8ee3, 0x57863796abde6bc, 0x4e3ba3f4229e706]))); - + // Test for sensible results with zero tmp = Fq(FqRepr::from(0)); tmp.sub_assign(&Fq(FqRepr::from(0))); @@ -1796,29 +1791,21 @@ fn test_fq_ordering() { fn fq_repr_tests() { ::tests::repr::random_repr_tests::<FqRepr>(); } + #[test] fn test_fq_legendre() { - assert_eq!(1, Fq::one().legendre()); - assert_eq!(0, Fq::zero().legendre()); + use ::LegendreSymbol::*; - let e = Fq(FqRepr([0x914577fdcc41112, 0x1a6c20f3392c28e2, 0xd53f75da0c40fd21, - 0xb747c10d13caf0d0, 0x0de1adc19c24d8d2, 0x2103f924191033d2])); - assert_eq!(-1, e.legendre()); + assert_eq!(QResidue, Fq::one().legendre()); + assert_eq!(Zero, Fq::zero().legendre()); + assert_eq!(QNonResidue, Fq::from_repr(FqRepr::from(2)).unwrap().legendre()); + assert_eq!(QResidue, Fq::from_repr(FqRepr::from(4)).unwrap().legendre()); - // f - let e = Fq(FqRepr([0xe51d5292ae8126f, 0x382d60874f48db82, 0xb0dde25abc614254, - 0x34f4456bd18813df, 0x2c668010247ee04c, 0x44cb8bbdd7c4f1b0])); - assert_eq!(-1, e.legendre()); - - // f ** 9 - let e = Fq(FqRepr([0x69fc8eb1b590c712, 0xd73f4fb6fd34042e, 0xb5677ef2ed0eede7, - 0x367d831c592848c8, 0xb60615cc44e533f5, 0x127da624461b200e])); - - assert_eq!(-1, e.legendre()); - - let e = Fq(FqRepr([0x83c7ad9e29b7facc, 0x97b3c8fbdb50cc39, 0x9e2ccd0eb5db5e72, - 0xc74a00d90e1b247d, 0x90e38ef46c8d7eb7, 0x16882d6aa70bb469])); - assert_eq!(1, e.legendre()); - + let e = FqRepr([0x52a112f249778642, 0xd0bedb989b7991f, 0xdad3b6681aa63c05, + 0xf2efc0bb4721b283, 0x6057a98f18c24733, 0x1022c2fd122889e4]); + assert_eq!(QNonResidue, Fq::from_repr(e).unwrap().legendre()); + let e = FqRepr([0x6dae594e53a96c74, 0x19b16ca9ba64b37b, 0x5c764661a59bfc68, + 0xaa346e9b31c60a, 0x346059f9d87a9fa9, 0x1d61ac6bfd5c88b]); + assert_eq!(QResidue, Fq::from_repr(e).unwrap().legendre()); } diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index ecb2965..0c9218c 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -197,7 +197,7 @@ impl SqrtField for Fq2 { } impl LegendreField for Fq2 { - fn legendre(&self) -> i32 { + fn legendre(&self) -> ::LegendreSymbol { Fq2::norm(&self).legendre() } } @@ -431,13 +431,14 @@ fn test_fq2_sqrt() { #[test] fn test_fq2_legendre() { + use ::LegendreSymbol::*; + // i^2 = -1 let mut m1 = Fq2::one(); m1.negate(); - assert_eq!(1, m1.legendre()); - + assert_eq!(QResidue, m1.legendre()); m1.mul_by_nonresidue(); - assert_eq!(-1, m1.legendre()); + assert_eq!(QNonResidue, m1.legendre()); } #[cfg(test)] diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 4081011..7842de5 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,4 +1,5 @@ use ::{Field, LegendreField, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; +use ::LegendreSymbol::*; // r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 const MODULUS: FrRepr = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); @@ -559,7 +560,7 @@ impl SqrtField for Fr { return Some(*self); } - if self.legendre() != 1 { + if let QNonResidue = self.legendre() { None } else { let mut c = Fr(ROOT_OF_UNITY); @@ -598,16 +599,11 @@ impl SqrtField for Fr { } impl LegendreField for Fr { - fn legendre(&self) -> i32 { - // (q - 1) / 2 = 26217937587563095239723870254092982918845276250263818911301829349969290592256 - let x = self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]); - if x == Self::one() { - 1 - } else if x == Self::zero() { - 0 - } else { - -1 - } + fn legendre(&self) -> ::LegendreSymbol { + let s = self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]); + if s == Self::zero() { Zero } + else if s == Self::one() { QResidue } + else { QNonResidue } } } #[cfg(test)] @@ -792,13 +788,13 @@ fn test_fr_repr_sub_noborrow() { #[test] fn test_fr_legendre() { - assert_eq!(1, Fr::one().legendre()); - assert_eq!(0, Fr::zero().legendre()); + assert_eq!(QResidue, Fr::one().legendre()); + assert_eq!(Zero, Fr::zero().legendre()); - let e = Fr(FrRepr([0x0dbc5349cd5664da, 0x8ac5b6296e3ae29d, 0x127cb819feceaa3b, 0x3a6b21fb03867191])); - assert_eq!(1, e.legendre()); - let e = Fr(FrRepr([0x96341aefd047c045, 0x9b5f4254500a4d65, 0x1ee08223b68ac240, 0x31d9cd545c0ec7c6])); - assert_eq!(-1, e.legendre()); + let e = FrRepr([0x0dbc5349cd5664da, 0x8ac5b6296e3ae29d, 0x127cb819feceaa3b, 0x3a6b21fb03867191]); + assert_eq!(QResidue, Fr::from_repr(e).unwrap().legendre()); + let e = FrRepr([0x96341aefd047c045, 0x9b5f4254500a4d65, 0x1ee08223b68ac240, 0x31d9cd545c0ec7c6]); + assert_eq!(QNonResidue, Fr::from_repr(e).unwrap().legendre()); } #[test] @@ -1038,12 +1034,12 @@ fn test_fr_sub_assign() { let mut tmp = Fr(FrRepr([0x6a68c64b6f735a2b, 0xd5f4d143fe0a1972, 0x37c17f3829267c62, 0xa2f37391f30915c])); tmp.sub_assign(&Fr(FrRepr([0xade5adacdccb6190, 0xaa21ee0f27db3ccd, 0x2550f4704ae39086, 0x591d1902e7c5ba27]))); assert_eq!(tmp, Fr(FrRepr([0xbc83189d92a7f89c, 0x7f908737d62d38a3, 0x45aa62cfe7e4c3e1, 0x24ffc5896108547d]))); - + // Test the opposite subtraction which doesn't test reduction. tmp = Fr(FrRepr([0xade5adacdccb6190, 0xaa21ee0f27db3ccd, 0x2550f4704ae39086, 0x591d1902e7c5ba27])); tmp.sub_assign(&Fr(FrRepr([0x6a68c64b6f735a2b, 0xd5f4d143fe0a1972, 0x37c17f3829267c62, 0xa2f37391f30915c]))); assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); - + // Test for sensible results with zero tmp = Fr(FrRepr::from(0)); tmp.sub_assign(&Fr(FrRepr::from(0))); diff --git a/src/lib.rs b/src/lib.rs index abd7328..0ba79aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -336,7 +336,7 @@ pub trait SqrtField: Field pub trait LegendreField: Field { /// Returns the legendre symbol of the field element. - fn legendre(&self) -> i32; + fn legendre(&self) -> LegendreSymbol; } @@ -417,6 +417,13 @@ pub trait PrimeFieldRepr: Sized + } } +#[derive(Debug, PartialEq)] +pub enum LegendreSymbol { + Zero = 0, + QResidue = 1, + QNonResidue = -1 +} + /// An error that may occur when trying to interpret a `PrimeFieldRepr` as a /// `PrimeField` element. #[derive(Debug)] From 2d3f498e751dc4fda452994f6f5c64eaec2adc94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Thu, 10 Aug 2017 18:18:46 +0200 Subject: [PATCH 052/140] Polish sqrt in fr.rs: use pattern matching with Legendre enums. --- src/bls12_381/fr.rs | 60 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 7842de5..f914942 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -555,45 +555,42 @@ impl SqrtField for Fr { fn sqrt(&self) -> Option<Self> { // Tonelli-Shank's algorithm for q mod 16 = 1 // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) + match self.legendre() { + Zero => Some(*self), + QNonResidue => None, + QResidue => { + let mut c = Fr(ROOT_OF_UNITY); + // r = self^((t + 1) // 2) + let mut r = self.pow([0x7fff2dff80000000, 0x4d0ec02a9ded201, 0x94cebea4199cec04, 0x39f6d3a9]); + // t = self^t + let mut t = self.pow([0xfffe5bfeffffffff, 0x9a1d80553bda402, 0x299d7d483339d808, 0x73eda753]); + let mut m = S; - if self.is_zero() { - return Some(*self); - } - - if let QNonResidue = self.legendre() { - None - } else { - let mut c = Fr(ROOT_OF_UNITY); - // r = self^((t + 1) // 2) - let mut r = self.pow([0x7fff2dff80000000, 0x4d0ec02a9ded201, 0x94cebea4199cec04, 0x39f6d3a9]); - // t = self^t - let mut t = self.pow([0xfffe5bfeffffffff, 0x9a1d80553bda402, 0x299d7d483339d808, 0x73eda753]); - let mut m = S; - - while t != Self::one() { + while t != Self::one() { let mut i = 1; - { - let mut t2i = t; - t2i.square(); - loop { - if t2i == Self::one() { - break; - } + { + let mut t2i = t; t2i.square(); - i += 1; + loop { + if t2i == Self::one() { + break; + } + t2i.square(); + i += 1; + } } - } - for _ in 0..(m - i - 1) { + for _ in 0..(m - i - 1) { + c.square(); + } + r.mul_assign(&c); c.square(); + t.mul_assign(&c); + m = i; } - r.mul_assign(&c); - c.square(); - t.mul_assign(&c); - m = i; - } - Some(r) + Some(r) + } } } } @@ -606,6 +603,7 @@ impl LegendreField for Fr { else { QNonResidue } } } + #[cfg(test)] use rand::{SeedableRng, XorShiftRng, Rand}; From 6feb0f802f1d8e1e8b092ac1d734b7b4d2294208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Thu, 10 Aug 2017 18:28:17 +0200 Subject: [PATCH 053/140] Merge traits SqrtField and LegendreField into SqrtField. --- src/bls12_381/fq.rs | 24 ++++++++++++------------ src/bls12_381/fq2.rs | 13 ++++++------- src/bls12_381/fr.rs | 19 +++++++++---------- src/lib.rs | 10 +++------- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index d2efa0d..5f92b1d 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,4 +1,4 @@ -use ::{Field, LegendreField, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; +use ::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; use std::cmp::Ordering; use super::fq2::Fq2; @@ -810,6 +810,17 @@ impl Fq { } impl SqrtField for Fq { + + fn legendre(&self) -> ::LegendreSymbol { + use ::LegendreSymbol::*; + + let s = self.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, + 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]); + if s == Fq::zero() { Zero } + else if s == Fq::one() { QResidue } + else { QNonResidue } + } + fn sqrt(&self) -> Option<Self> { // Shank's algorithm for q mod 4 = 3 // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) @@ -832,17 +843,6 @@ impl SqrtField for Fq { } } -impl LegendreField for Fq { - fn legendre(&self) -> ::LegendreSymbol { - use ::LegendreSymbol::*; - - let s = self.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, - 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]); - if s == Fq::zero() { Zero } - else if s == Fq::one() { QResidue } - else { QNonResidue } - } -} #[test] fn test_b_coeff() { diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 0c9218c..fb385d7 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -1,5 +1,5 @@ use rand::{Rng, Rand}; -use ::{Field, LegendreField, SqrtField}; +use ::{Field, SqrtField}; use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; use std::cmp::Ordering; @@ -156,6 +156,11 @@ impl Field for Fq2 { } impl SqrtField for Fq2 { + + fn legendre(&self) -> ::LegendreSymbol { + Fq2::norm(&self).legendre() + } + fn sqrt(&self) -> Option<Self> { // Algorithm 9, https://eprint.iacr.org/2012/685.pdf @@ -196,12 +201,6 @@ impl SqrtField for Fq2 { } } -impl LegendreField for Fq2 { - fn legendre(&self) -> ::LegendreSymbol { - Fq2::norm(&self).legendre() - } -} - #[test] fn test_fq2_ordering() { let mut a = Fq2 { diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index f914942..7d98625 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,4 +1,4 @@ -use ::{Field, LegendreField, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; +use ::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; use ::LegendreSymbol::*; // r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 @@ -552,6 +552,14 @@ impl Fr { } impl SqrtField for Fr { + + fn legendre(&self) -> ::LegendreSymbol { + let s = self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]); + if s == Self::zero() { Zero } + else if s == Self::one() { QResidue } + else { QNonResidue } + } + fn sqrt(&self) -> Option<Self> { // Tonelli-Shank's algorithm for q mod 16 = 1 // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) @@ -595,15 +603,6 @@ impl SqrtField for Fr { } } -impl LegendreField for Fr { - fn legendre(&self) -> ::LegendreSymbol { - let s = self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]); - if s == Self::zero() { Zero } - else if s == Self::one() { QResidue } - else { QNonResidue } - } -} - #[cfg(test)] use rand::{SeedableRng, XorShiftRng, Rand}; diff --git a/src/lib.rs b/src/lib.rs index 0ba79aa..a0fc2b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,18 +327,14 @@ pub trait Field: Sized + /// This trait represents an element of a field that has a square root operation described for it. pub trait SqrtField: Field { + /// Returns the legendre symbol of the field element. + fn legendre(&self) -> LegendreSymbol; + /// Returns the square root of the field element, if it is /// quadratic residue. fn sqrt(&self) -> Option<Self>; } -/// This trait represents an element of a field that has a Legendre symbol described for it. -pub trait LegendreField: Field -{ - /// Returns the legendre symbol of the field element. - fn legendre(&self) -> LegendreSymbol; -} - /// This trait represents a wrapper around a biginteger which can encode any element of a particular /// prime field. It is a smart wrapper around a sequence of `u64` limbs, least-significant digit From a86d0b72706b466115c01be9a1ec637c220c2e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Sat, 12 Aug 2017 11:50:30 +0200 Subject: [PATCH 054/140] s/Q/Quadratic/ in LegendreSymbol enum. Proposed by @ebfull. --- src/bls12_381/fq.rs | 14 +++++++------- src/bls12_381/fq2.rs | 4 ++-- src/bls12_381/fr.rs | 14 +++++++------- src/lib.rs | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 5f92b1d..8bb2186 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -817,8 +817,8 @@ impl SqrtField for Fq { let s = self.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]); if s == Fq::zero() { Zero } - else if s == Fq::one() { QResidue } - else { QNonResidue } + else if s == Fq::one() { QuadraticResidue } + else { QuadraticNonResidue } } fn sqrt(&self) -> Option<Self> { @@ -1796,16 +1796,16 @@ fn fq_repr_tests() { fn test_fq_legendre() { use ::LegendreSymbol::*; - assert_eq!(QResidue, Fq::one().legendre()); + assert_eq!(QuadraticResidue, Fq::one().legendre()); assert_eq!(Zero, Fq::zero().legendre()); - assert_eq!(QNonResidue, Fq::from_repr(FqRepr::from(2)).unwrap().legendre()); - assert_eq!(QResidue, Fq::from_repr(FqRepr::from(4)).unwrap().legendre()); + assert_eq!(QuadraticNonResidue, Fq::from_repr(FqRepr::from(2)).unwrap().legendre()); + assert_eq!(QuadraticResidue, Fq::from_repr(FqRepr::from(4)).unwrap().legendre()); let e = FqRepr([0x52a112f249778642, 0xd0bedb989b7991f, 0xdad3b6681aa63c05, 0xf2efc0bb4721b283, 0x6057a98f18c24733, 0x1022c2fd122889e4]); - assert_eq!(QNonResidue, Fq::from_repr(e).unwrap().legendre()); + assert_eq!(QuadraticNonResidue, Fq::from_repr(e).unwrap().legendre()); let e = FqRepr([0x6dae594e53a96c74, 0x19b16ca9ba64b37b, 0x5c764661a59bfc68, 0xaa346e9b31c60a, 0x346059f9d87a9fa9, 0x1d61ac6bfd5c88b]); - assert_eq!(QResidue, Fq::from_repr(e).unwrap().legendre()); + assert_eq!(QuadraticResidue, Fq::from_repr(e).unwrap().legendre()); } diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index fb385d7..6863a35 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -435,9 +435,9 @@ fn test_fq2_legendre() { // i^2 = -1 let mut m1 = Fq2::one(); m1.negate(); - assert_eq!(QResidue, m1.legendre()); + assert_eq!(QuadraticResidue, m1.legendre()); m1.mul_by_nonresidue(); - assert_eq!(QNonResidue, m1.legendre()); + assert_eq!(QuadraticNonResidue, m1.legendre()); } #[cfg(test)] diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 7d98625..0f159de 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -556,8 +556,8 @@ impl SqrtField for Fr { fn legendre(&self) -> ::LegendreSymbol { let s = self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]); if s == Self::zero() { Zero } - else if s == Self::one() { QResidue } - else { QNonResidue } + else if s == Self::one() { QuadraticResidue } + else { QuadraticNonResidue } } fn sqrt(&self) -> Option<Self> { @@ -565,8 +565,8 @@ impl SqrtField for Fr { // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) match self.legendre() { Zero => Some(*self), - QNonResidue => None, - QResidue => { + QuadraticNonResidue => None, + QuadraticResidue => { let mut c = Fr(ROOT_OF_UNITY); // r = self^((t + 1) // 2) let mut r = self.pow([0x7fff2dff80000000, 0x4d0ec02a9ded201, 0x94cebea4199cec04, 0x39f6d3a9]); @@ -785,13 +785,13 @@ fn test_fr_repr_sub_noborrow() { #[test] fn test_fr_legendre() { - assert_eq!(QResidue, Fr::one().legendre()); + assert_eq!(QuadraticResidue, Fr::one().legendre()); assert_eq!(Zero, Fr::zero().legendre()); let e = FrRepr([0x0dbc5349cd5664da, 0x8ac5b6296e3ae29d, 0x127cb819feceaa3b, 0x3a6b21fb03867191]); - assert_eq!(QResidue, Fr::from_repr(e).unwrap().legendre()); + assert_eq!(QuadraticResidue, Fr::from_repr(e).unwrap().legendre()); let e = FrRepr([0x96341aefd047c045, 0x9b5f4254500a4d65, 0x1ee08223b68ac240, 0x31d9cd545c0ec7c6]); - assert_eq!(QNonResidue, Fr::from_repr(e).unwrap().legendre()); + assert_eq!(QuadraticNonResidue, Fr::from_repr(e).unwrap().legendre()); } #[test] diff --git a/src/lib.rs b/src/lib.rs index a0fc2b5..e25ae49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -416,8 +416,8 @@ pub trait PrimeFieldRepr: Sized + #[derive(Debug, PartialEq)] pub enum LegendreSymbol { Zero = 0, - QResidue = 1, - QNonResidue = -1 + QuadraticResidue = 1, + QuadraticNonResidue = -1 } /// An error that may occur when trying to interpret a `PrimeFieldRepr` as a From 2ac2d1213d2a2cbafea6ba1645231b319f9d030c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Fri, 11 Aug 2017 15:12:15 +0200 Subject: [PATCH 055/140] Some (easy) cleanups as suggested from @daira. Thanks! --- src/bls12_381/ec.rs | 1 + src/bls12_381/fq.rs | 1 + src/bls12_381/fq2.rs | 1 + src/lib.rs | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 9116844..b7041c3 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -166,6 +166,7 @@ macro_rules! curve_impl { fn into_projective(&self) -> $projective { (*self).into() } + } impl Rand for $projective { diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 8bb2186..cb4f44b 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -814,6 +814,7 @@ impl SqrtField for Fq { fn legendre(&self) -> ::LegendreSymbol { use ::LegendreSymbol::*; + // s = self^((q - 1) // 2) let s = self.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]); if s == Fq::zero() { Zero } diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 6863a35..3095661 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -432,6 +432,7 @@ fn test_fq2_sqrt() { fn test_fq2_legendre() { use ::LegendreSymbol::*; + assert_eq!(Zero, Fq2::zero().legendre()); // i^2 = -1 let mut m1 = Fq2::one(); m1.negate(); diff --git a/src/lib.rs b/src/lib.rs index e25ae49..9798d0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,7 +327,7 @@ pub trait Field: Sized + /// This trait represents an element of a field that has a square root operation described for it. pub trait SqrtField: Field { - /// Returns the legendre symbol of the field element. + /// Returns the Legendre symbol of the field element. fn legendre(&self) -> LegendreSymbol; /// Returns the square root of the field element, if it is From 9846ad2d177863c03f524a79aff04bb2f7734ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Wed, 23 Aug 2017 20:26:56 +0200 Subject: [PATCH 056/140] Some (easy) cleanups as suggested from @ebfull. Thanks! --- src/bls12_381/fq2.rs | 4 ++-- src/bls12_381/fr.rs | 1 + src/tests/field.rs | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 3095661..aa6ccde 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -158,7 +158,7 @@ impl Field for Fq2 { impl SqrtField for Fq2 { fn legendre(&self) -> ::LegendreSymbol { - Fq2::norm(&self).legendre() + self.norm().legendre() } fn sqrt(&self) -> Option<Self> { @@ -578,7 +578,7 @@ fn bench_fq2_sqrt(b: &mut ::test::Bencher) { #[test] fn fq2_field_tests() { use ::PrimeField; - + ::tests::field::random_field_tests::<Fq2>(); ::tests::field::random_sqrt_tests::<Fq2>(); ::tests::field::random_frobenius_tests::<Fq2, _>(super::fq::Fq::char(), 13); diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 0f159de..058cb6a 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -554,6 +554,7 @@ impl Fr { impl SqrtField for Fr { fn legendre(&self) -> ::LegendreSymbol { + // s = self^((r - 1) // 2) let s = self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]); if s == Self::zero() { Zero } else if s == Self::one() { QuadraticResidue } diff --git a/src/tests/field.rs b/src/tests/field.rs index 5f99992..bddb93e 100644 --- a/src/tests/field.rs +++ b/src/tests/field.rs @@ -1,5 +1,5 @@ use rand::{Rng, SeedableRng, XorShiftRng}; -use ::{SqrtField, Field, PrimeField}; +use ::{SqrtField, Field, PrimeField, LegendreSymbol}; pub fn random_frobenius_tests<F: Field, C: AsRef<[u64]>>(characteristic: C, maxpower: usize) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -26,6 +26,7 @@ pub fn random_sqrt_tests<F: SqrtField>() { let a = F::rand(&mut rng); let mut b = a; b.square(); + assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); let b = b.sqrt().unwrap(); let mut negb = b; @@ -38,6 +39,8 @@ pub fn random_sqrt_tests<F: SqrtField>() { for _ in 0..10000 { let mut b = c; b.square(); + assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + b = b.sqrt().unwrap(); if b != c { From 39920186b337f05aac338acefc62607f8067cc7a Mon Sep 17 00:00:00 2001 From: str4d <str4d@mail.i2p> Date: Tue, 26 Sep 2017 15:59:50 +0100 Subject: [PATCH 057/140] Force public structures to implement Debug Closes #23. --- src/bls12_381/ec.rs | 30 ++++++++++++++++++++++++++++-- src/bls12_381/mod.rs | 1 + src/lib.rs | 4 ++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index b7041c3..bc00cbc 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -584,6 +584,7 @@ macro_rules! curve_impl { pub mod g1 { use rand::{Rand, Rng}; + use std::fmt; use super::g2::G2Affine; use super::super::{Bls12, Fq, Fr, FrRepr, FqRepr, Fq12}; use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError, Engine}; @@ -611,6 +612,12 @@ pub mod g1 { } } + impl fmt::Debug for G1Uncompressed { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0[..].fmt(formatter) + } + } + impl EncodedPoint for G1Uncompressed { type Affine = G1Affine; @@ -713,6 +720,12 @@ pub mod g1 { } } + impl fmt::Debug for G1Compressed { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0[..].fmt(formatter) + } + } + impl EncodedPoint for G1Compressed { type Affine = G1Affine; @@ -879,7 +892,7 @@ pub mod g1 { } } - #[derive(Clone)] + #[derive(Clone, Debug)] pub struct G1Prepared(pub(crate) G1Affine); impl G1Prepared { @@ -1137,6 +1150,7 @@ pub mod g1 { pub mod g2 { use rand::{Rand, Rng}; + use std::fmt; use super::super::{Bls12, Fq2, Fr, Fq, FrRepr, FqRepr, Fq12}; use super::g1::G1Affine; use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError, Engine}; @@ -1164,6 +1178,12 @@ pub mod g2 { } } + impl fmt::Debug for G2Uncompressed { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0[..].fmt(formatter) + } + } + impl EncodedPoint for G2Uncompressed { type Affine = G2Affine; @@ -1278,6 +1298,12 @@ pub mod g2 { } } + impl fmt::Debug for G2Compressed { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0[..].fmt(formatter) + } + } + impl EncodedPoint for G2Compressed { type Affine = G2Affine; @@ -1459,7 +1485,7 @@ pub mod g2 { } } - #[derive(Clone)] + #[derive(Clone, Debug)] pub struct G2Prepared { pub(crate) coeffs: Vec<(Fq2, Fq2, Fq2)>, pub(crate) infinity: bool diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 28af9d8..2c55c22 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -21,6 +21,7 @@ use super::{Engine, CurveAffine, Field, BitIterator}; const BLS_X: u64 = 0xd201000000010000; const BLS_X_IS_NEGATIVE: bool = true; +#[derive(Debug)] pub struct Bls12; impl Engine for Bls12 { diff --git a/src/lib.rs b/src/lib.rs index 9798d0d..8bbd2f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,9 @@ #![cfg_attr(feature = "clippy", allow(too_many_arguments))] #![cfg_attr(feature = "clippy", allow(unreadable_literal))] +// Force public structures to implement Debug +#![deny(missing_debug_implementations)] + // The compiler provides `test` (on nightly) for benchmarking tools, but // it's hidden behind a feature flag. Enable it if we're testing. #![cfg_attr(test, feature(test))] @@ -563,6 +566,7 @@ pub trait PrimeField: Field fn root_of_unity() -> Self; } +#[derive(Debug)] pub struct BitIterator<E> { t: E, n: usize From 3ef34b750c0003dfae8fde0765d9b64cd279175a Mon Sep 17 00:00:00 2001 From: str4d <str4d@mail.i2p> Date: Wed, 27 Sep 2017 00:42:15 +0100 Subject: [PATCH 058/140] Move benchmarks under benches/ so tests compile on stable Closes #36. --- benches/bls12_381/ec.rs | 115 +++++++++++++++++ benches/bls12_381/fq.rs | 258 +++++++++++++++++++++++++++++++++++++ benches/bls12_381/fq12.rs | 98 ++++++++++++++ benches/bls12_381/fq2.rs | 116 +++++++++++++++++ benches/bls12_381/fr.rs | 258 +++++++++++++++++++++++++++++++++++++ benches/bls12_381/mod.rs | 105 +++++++++++++++ benches/pairing_benches.rs | 21 +++ src/bls12_381/ec.rs | 108 ---------------- src/bls12_381/fq.rs | 255 ------------------------------------ src/bls12_381/fq12.rs | 94 -------------- src/bls12_381/fq2.rs | 112 ---------------- src/bls12_381/fr.rs | 256 ------------------------------------ src/bls12_381/mod.rs | 98 -------------- src/lib.rs | 6 - 14 files changed, 971 insertions(+), 929 deletions(-) create mode 100644 benches/bls12_381/ec.rs create mode 100644 benches/bls12_381/fq.rs create mode 100644 benches/bls12_381/fq12.rs create mode 100644 benches/bls12_381/fq2.rs create mode 100644 benches/bls12_381/fr.rs create mode 100644 benches/bls12_381/mod.rs create mode 100644 benches/pairing_benches.rs diff --git a/benches/bls12_381/ec.rs b/benches/bls12_381/ec.rs new file mode 100644 index 0000000..608e7f0 --- /dev/null +++ b/benches/bls12_381/ec.rs @@ -0,0 +1,115 @@ +mod g1 { + use rand::{Rand, SeedableRng, XorShiftRng}; + + use pairing::CurveProjective; + use pairing::bls12_381::*; + + #[bench] + fn bench_g1_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, Fr)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g1_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G1)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), G1::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g1_add_assign_mixed(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G1Affine)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), G1::rand(&mut rng).into())).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign_mixed(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } +} + +mod g2 { + use rand::{Rand, SeedableRng, XorShiftRng}; + + use pairing::CurveProjective; + use pairing::bls12_381::*; + + #[bench] + fn bench_g2_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, Fr)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g2_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, G2)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), G2::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g2_add_assign_mixed(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, G2Affine)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), G2::rand(&mut rng).into())).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign_mixed(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } +} diff --git a/benches/bls12_381/fq.rs b/benches/bls12_381/fq.rs new file mode 100644 index 0000000..9b2a08e --- /dev/null +++ b/benches/bls12_381/fq.rs @@ -0,0 +1,258 @@ +use rand::{Rand, SeedableRng, XorShiftRng}; + +use pairing::{Field, PrimeField, PrimeFieldRepr, SqrtField}; +use pairing::bls12_381::*; + +#[bench] +fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES).map(|_| { + let mut tmp1 = FqRepr::rand(&mut rng); + let mut tmp2 = FqRepr::rand(&mut rng); + // Shave a few bits off to avoid overflow. + for _ in 0..3 { + tmp1.div2(); + tmp2.div2(); + } + (tmp1, tmp2) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_nocarry(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_sub_noborrow(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES).map(|_| { + let tmp1 = FqRepr::rand(&mut rng); + let mut tmp2 = tmp1; + // Ensure tmp2 is smaller than tmp1. + for _ in 0..10 { + tmp2.div2(); + } + (tmp1, tmp2) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_noborrow(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_num_bits(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].num_bits(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_mul2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.mul2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_div2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.div2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_square(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].inverse() + }); +} + +#[bench] +fn bench_fq_negate(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.negate(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| { + let mut tmp = Fq::rand(&mut rng); + tmp.square(); + tmp + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].sqrt() + }); +} + +#[bench] +fn bench_fq_into_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| { + Fq::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].into_repr() + }); +} + +#[bench] +fn bench_fq_from_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FqRepr> = (0..SAMPLES).map(|_| { + Fq::rand(&mut rng).into_repr() + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + Fq::from_repr(v[count]) + }); +} diff --git a/benches/bls12_381/fq12.rs b/benches/bls12_381/fq12.rs new file mode 100644 index 0000000..26c33c8 --- /dev/null +++ b/benches/bls12_381/fq12.rs @@ -0,0 +1,98 @@ +use rand::{Rand, SeedableRng, XorShiftRng}; + +use pairing::Field; +use pairing::bls12_381::*; + +#[bench] +fn bench_fq12_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { + (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { + (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { + (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_squaring(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq12> = (0..SAMPLES).map(|_| { + Fq12::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq12> = (0..SAMPLES).map(|_| { + Fq12::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].inverse(); + count = (count + 1) % SAMPLES; + tmp + }); +} diff --git a/benches/bls12_381/fq2.rs b/benches/bls12_381/fq2.rs new file mode 100644 index 0000000..d5f835d --- /dev/null +++ b/benches/bls12_381/fq2.rs @@ -0,0 +1,116 @@ +use rand::{Rand, SeedableRng, XorShiftRng}; + +use pairing::{Field, SqrtField}; +use pairing::bls12_381::*; + +#[bench] +fn bench_fq2_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { + (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { + (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { + (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_squaring(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq2> = (0..SAMPLES).map(|_| { + Fq2::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq2> = (0..SAMPLES).map(|_| { + Fq2::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].inverse(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq2> = (0..SAMPLES).map(|_| { + Fq2::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].sqrt(); + count = (count + 1) % SAMPLES; + tmp + }); +} diff --git a/benches/bls12_381/fr.rs b/benches/bls12_381/fr.rs new file mode 100644 index 0000000..58575b6 --- /dev/null +++ b/benches/bls12_381/fr.rs @@ -0,0 +1,258 @@ +use rand::{Rand, SeedableRng, XorShiftRng}; + +use pairing::{Field, PrimeField, PrimeFieldRepr, SqrtField}; +use pairing::bls12_381::*; + +#[bench] +fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES).map(|_| { + let mut tmp1 = FrRepr::rand(&mut rng); + let mut tmp2 = FrRepr::rand(&mut rng); + // Shave a few bits off to avoid overflow. + for _ in 0..3 { + tmp1.div2(); + tmp2.div2(); + } + (tmp1, tmp2) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_nocarry(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_sub_noborrow(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES).map(|_| { + let tmp1 = FrRepr::rand(&mut rng); + let mut tmp2 = tmp1; + // Ensure tmp2 is smaller than tmp1. + for _ in 0..10 { + tmp2.div2(); + } + (tmp1, tmp2) + }).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_noborrow(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_num_bits(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].num_bits(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_mul2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.mul2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_div2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.div2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_square(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].inverse() + }); +} + +#[bench] +fn bench_fr_negate(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.negate(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| { + let mut tmp = Fr::rand(&mut rng); + tmp.square(); + tmp + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].sqrt() + }); +} + +#[bench] +fn bench_fr_into_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| { + Fr::rand(&mut rng) + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].into_repr() + }); +} + +#[bench] +fn bench_fr_from_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FrRepr> = (0..SAMPLES).map(|_| { + Fr::rand(&mut rng).into_repr() + }).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + Fr::from_repr(v[count]) + }); +} diff --git a/benches/bls12_381/mod.rs b/benches/bls12_381/mod.rs new file mode 100644 index 0000000..64a90cc --- /dev/null +++ b/benches/bls12_381/mod.rs @@ -0,0 +1,105 @@ +mod fq; +mod fr; +mod fq2; +mod fq12; +mod ec; + +use rand::{Rand, SeedableRng, XorShiftRng}; + +use pairing::{Engine, CurveAffine}; +use pairing::bls12_381::*; + +#[bench] +fn bench_pairing_g1_preparation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<G1> = (0..SAMPLES).map(|_| G1::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = G1Affine::from(v[count]).prepare(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_g2_preparation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<G2> = (0..SAMPLES).map(|_| G2::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = G2Affine::from(v[count]).prepare(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_miller_loop(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1Prepared, G2Prepared)> = (0..SAMPLES).map(|_| + ( + G1Affine::from(G1::rand(&mut rng)).prepare(), + G2Affine::from(G2::rand(&mut rng)).prepare() + ) + ).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bls12::miller_loop(&[(&v[count].0, &v[count].1)]); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_final_exponentiation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq12> = (0..SAMPLES).map(|_| + ( + G1Affine::from(G1::rand(&mut rng)).prepare(), + G2Affine::from(G2::rand(&mut rng)).prepare() + ) + ).map(|(ref p, ref q)| Bls12::miller_loop(&[(p, q)])).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bls12::final_exponentiation(&v[count]); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_full(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G2)> = (0..SAMPLES).map(|_| + ( + G1::rand(&mut rng), + G2::rand(&mut rng) + ) + ).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bls12::pairing(v[count].0, v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} \ No newline at end of file diff --git a/benches/pairing_benches.rs b/benches/pairing_benches.rs new file mode 100644 index 0000000..43f3c6a --- /dev/null +++ b/benches/pairing_benches.rs @@ -0,0 +1,21 @@ +// `clippy` is a code linting tool for improving code quality by catching +// common mistakes or strange code patterns. If the `clippy` feature is +// provided, it is enabled and all compiler warnings are prohibited. +#![cfg_attr(feature = "clippy", deny(warnings))] +#![cfg_attr(feature = "clippy", feature(plugin))] +#![cfg_attr(feature = "clippy", plugin(clippy))] +#![cfg_attr(feature = "clippy", allow(inline_always))] +#![cfg_attr(feature = "clippy", allow(too_many_arguments))] +#![cfg_attr(feature = "clippy", allow(unreadable_literal))] + +// The compiler provides `test` (on nightly) for benchmarking tools, but +// it's hidden behind a feature flag. Enable it if we're testing. +#![cfg_attr(test, feature(test))] +#[cfg(test)] +extern crate test; + +extern crate rand; + +extern crate pairing; + +mod bls12_381; diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index bc00cbc..f2aefa2 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -1092,60 +1092,6 @@ pub mod g1 { fn g1_curve_tests() { ::tests::curve::curve_tests::<G1>(); } - - #[cfg(test)] - use rand::{SeedableRng, XorShiftRng}; - - #[bench] - fn bench_g1_mul_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(G1, Fr)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), Fr::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); - } - - #[bench] - fn bench_g1_add_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(G1, G1)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), G1::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); - } - - #[bench] - fn bench_g1_add_assign_mixed(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(G1, G1Affine)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), G1::rand(&mut rng).into())).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign_mixed(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); - } } pub mod g2 { @@ -1679,60 +1625,6 @@ pub mod g2 { fn g2_curve_tests() { ::tests::curve::curve_tests::<G2>(); } - - #[cfg(test)] - use rand::{SeedableRng, XorShiftRng}; - - #[bench] - fn bench_g2_mul_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(G2, Fr)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), Fr::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); - } - - #[bench] - fn bench_g2_add_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(G2, G2)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), G2::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); - } - - #[bench] - fn bench_g2_add_assign_mixed(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(G2, G2Affine)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), G2::rand(&mut rng).into())).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign_mixed(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); - } } pub use self::g1::*; diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index cb4f44b..8440b1d 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1138,107 +1138,6 @@ fn test_fq_repr_add_nocarry() { assert!(!x.add_nocarry(&FqRepr::from(1))); } -#[bench] -fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES).map(|_| { - let mut tmp1 = FqRepr::rand(&mut rng); - let mut tmp2 = FqRepr::rand(&mut rng); - // Shave a few bits off to avoid overflow. - for _ in 0..3 { - tmp1.div2(); - tmp2.div2(); - } - (tmp1, tmp2) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_nocarry(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq_repr_sub_noborrow(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES).map(|_| { - let tmp1 = FqRepr::rand(&mut rng); - let mut tmp2 = tmp1; - // Ensure tmp2 is smaller than tmp1. - for _ in 0..10 { - tmp2.div2(); - } - (tmp1, tmp2) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_noborrow(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq_repr_num_bits(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = v[count].num_bits(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq_repr_mul2(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.mul2(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq_repr_div2(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.div2(); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn test_fq_is_valid() { let mut a = Fq(MODULUS); @@ -1350,40 +1249,6 @@ fn test_fq_sub_assign() { } } -#[bench] -fn bench_fq_add_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq_sub_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn test_fq_mul_assign() { let mut tmp = Fq(FqRepr([0xcc6200000020aa8a, 0x422800801dd8001a, 0x7f4f5e619041c62c, 0x8a55171ac70ed2ba, 0x3f69cc3a3d07d58b, 0xb972455fd09b8ef])); @@ -1433,23 +1298,6 @@ fn test_fq_mul_assign() { } } -#[bench] -fn bench_fq_mul_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn test_fq_squaring() { let mut a = Fq(FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x19ffffffffffffff])); @@ -1473,23 +1321,6 @@ fn test_fq_squaring() { } } -#[bench] -fn bench_fq_square(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.square(); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn test_fq_inverse() { assert!(Fq::zero().inverse().is_none()); @@ -1507,21 +1338,6 @@ fn test_fq_inverse() { } } -#[bench] -fn bench_fq_inverse(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].inverse() - }); -} - #[test] fn test_fq_double() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1558,24 +1374,6 @@ fn test_fq_negate() { } } -#[bench] -fn bench_fq_negate(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.negate(); - count = (count + 1) % SAMPLES; - tmp - }); -} - - #[test] fn test_fq_pow() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1631,25 +1429,6 @@ fn test_fq_sqrt() { } } -#[bench] -fn bench_fq_sqrt(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq> = (0..SAMPLES).map(|_| { - let mut tmp = Fq::rand(&mut rng); - tmp.square(); - tmp - }).collect(); - - let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].sqrt() - }); -} - #[test] fn test_fq_from_into_repr() { // q + 1 should not be in the field @@ -1684,40 +1463,6 @@ fn test_fq_from_into_repr() { } } -#[bench] -fn bench_fq_into_repr(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq> = (0..SAMPLES).map(|_| { - Fq::rand(&mut rng) - }).collect(); - - let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].into_repr() - }); -} - -#[bench] -fn bench_fq_from_repr(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<FqRepr> = (0..SAMPLES).map(|_| { - Fq::rand(&mut rng).into_repr() - }).collect(); - - let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - Fq::from_repr(v[count]) - }); -} - #[test] fn test_fq_repr_display() { assert_eq!( diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index e918505..9bdb111 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -193,100 +193,6 @@ fn test_fq12_mul_by_014() { } } -#[bench] -fn bench_fq12_add_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { - (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq12_sub_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { - (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq12_mul_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { - (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq12_squaring(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq12> = (0..SAMPLES).map(|_| { - Fq12::rand(&mut rng) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.square(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq12_inverse(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq12> = (0..SAMPLES).map(|_| { - Fq12::rand(&mut rng) - }).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = v[count].inverse(); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn fq12_field_tests() { use ::PrimeField; diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index aa6ccde..2082be0 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -463,118 +463,6 @@ fn test_fq2_mul_nonresidue() { } } -#[bench] -fn bench_fq2_add_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { - (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq2_sub_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { - (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq2_mul_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { - (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq2_squaring(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq2> = (0..SAMPLES).map(|_| { - Fq2::rand(&mut rng) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.square(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq2_inverse(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq2> = (0..SAMPLES).map(|_| { - Fq2::rand(&mut rng) - }).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = v[count].inverse(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fq2_sqrt(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq2> = (0..SAMPLES).map(|_| { - Fq2::rand(&mut rng) - }).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = v[count].sqrt(); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn fq2_field_tests() { use ::PrimeField; diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 058cb6a..0571563 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -854,107 +854,6 @@ fn test_fr_repr_add_nocarry() { assert!(!x.add_nocarry(&FrRepr::from(1))); } -#[bench] -fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES).map(|_| { - let mut tmp1 = FrRepr::rand(&mut rng); - let mut tmp2 = FrRepr::rand(&mut rng); - // Shave a few bits off to avoid overflow. - for _ in 0..3 { - tmp1.div2(); - tmp2.div2(); - } - (tmp1, tmp2) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_nocarry(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fr_repr_sub_noborrow(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES).map(|_| { - let tmp1 = FrRepr::rand(&mut rng); - let mut tmp2 = tmp1; - // Ensure tmp2 is smaller than tmp1. - for _ in 0..10 { - tmp2.div2(); - } - (tmp1, tmp2) - }).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_noborrow(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fr_repr_num_bits(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = v[count].num_bits(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fr_repr_mul2(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.mul2(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fr_repr_div2(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.div2(); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn test_fr_is_valid() { let mut a = Fr(MODULUS); @@ -1066,40 +965,6 @@ fn test_fr_sub_assign() { } } -#[bench] -fn bench_fr_add_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.add_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_fr_sub_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.sub_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn test_fr_mul_assign() { let mut tmp = Fr(FrRepr([0x6b7e9b8faeefc81a, 0xe30a8463f348ba42, 0xeff3cb67a8279c9c, 0x3d303651bd7c774d])); @@ -1149,23 +1014,6 @@ fn test_fr_mul_assign() { } } -#[bench] -fn bench_fr_mul_assign(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count].0; - tmp.mul_assign(&v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} - #[test] fn test_fr_squaring() { let mut a = Fr(FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x73eda753299d7d47])); @@ -1189,24 +1037,6 @@ fn test_fr_squaring() { } } -#[bench] -fn bench_fr_square(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.square(); - count = (count + 1) % SAMPLES; - tmp - }); -} - - #[test] fn test_fr_inverse() { assert!(Fr::zero().inverse().is_none()); @@ -1224,21 +1054,6 @@ fn test_fr_inverse() { } } -#[bench] -fn bench_fr_inverse(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].inverse() - }); -} - #[test] fn test_fr_double() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1275,24 +1090,6 @@ fn test_fr_negate() { } } -#[bench] -fn bench_fr_negate(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let mut tmp = v[count]; - tmp.negate(); - count = (count + 1) % SAMPLES; - tmp - }); -} - - #[test] fn test_fr_pow() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1348,25 +1145,6 @@ fn test_fr_sqrt() { } } -#[bench] -fn bench_fr_sqrt(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fr> = (0..SAMPLES).map(|_| { - let mut tmp = Fr::rand(&mut rng); - tmp.square(); - tmp - }).collect(); - - let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].sqrt() - }); -} - #[test] fn test_fr_from_into_repr() { // r + 1 should not be in the field @@ -1401,40 +1179,6 @@ fn test_fr_from_into_repr() { } } -#[bench] -fn bench_fr_into_repr(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fr> = (0..SAMPLES).map(|_| { - Fr::rand(&mut rng) - }).collect(); - - let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - v[count].into_repr() - }); -} - -#[bench] -fn bench_fr_from_repr(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<FrRepr> = (0..SAMPLES).map(|_| { - Fr::rand(&mut rng).into_repr() - }).collect(); - - let mut count = 0; - b.iter(|| { - count = (count + 1) % SAMPLES; - Fr::from_repr(v[count]) - }); -} - #[test] fn test_fr_repr_display() { assert_eq!( diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 2c55c22..4c2a76f 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -364,101 +364,3 @@ impl G2Prepared { fn bls12_engine_tests() { ::tests::engine::engine_tests::<Bls12>(); } - -#[cfg(test)] -use rand::{Rand, SeedableRng, XorShiftRng}; - -#[bench] -fn bench_pairing_g1_preparation(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<G1> = (0..SAMPLES).map(|_| G1::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = G1Affine::from(v[count]).prepare(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_pairing_g2_preparation(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<G2> = (0..SAMPLES).map(|_| G2::rand(&mut rng)).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = G2Affine::from(v[count]).prepare(); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_pairing_miller_loop(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(G1Prepared, G2Prepared)> = (0..SAMPLES).map(|_| - ( - G1Affine::from(G1::rand(&mut rng)).prepare(), - G2Affine::from(G2::rand(&mut rng)).prepare() - ) - ).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = Bls12::miller_loop(&[(&v[count].0, &v[count].1)]); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_pairing_final_exponentiation(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<Fq12> = (0..SAMPLES).map(|_| - ( - G1Affine::from(G1::rand(&mut rng)).prepare(), - G2Affine::from(G2::rand(&mut rng)).prepare() - ) - ).map(|(ref p, ref q)| Bls12::miller_loop(&[(p, q)])).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = Bls12::final_exponentiation(&v[count]); - count = (count + 1) % SAMPLES; - tmp - }); -} - -#[bench] -fn bench_pairing_full(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; - - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let v: Vec<(G1, G2)> = (0..SAMPLES).map(|_| - ( - G1::rand(&mut rng), - G2::rand(&mut rng) - ) - ).collect(); - - let mut count = 0; - b.iter(|| { - let tmp = Bls12::pairing(v[count].0, v[count].1); - count = (count + 1) % SAMPLES; - tmp - }); -} diff --git a/src/lib.rs b/src/lib.rs index 8bbd2f6..5790c04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,12 +16,6 @@ // Force public structures to implement Debug #![deny(missing_debug_implementations)] -// The compiler provides `test` (on nightly) for benchmarking tools, but -// it's hidden behind a feature flag. Enable it if we're testing. -#![cfg_attr(test, feature(test))] -#[cfg(test)] -extern crate test; - extern crate rand; extern crate byteorder; From d230603190174d3f9ac278faddf2de86b3fcfb3b Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Wed, 27 Sep 2017 19:09:59 -0600 Subject: [PATCH 059/140] Introduce a more typesafe wNAF API, and remove the unstable-wnaf feature. --- Cargo.toml | 3 +- src/lib.rs | 4 +- src/tests/curve.rs | 106 +++++++++++++++++++++++++++++++++++----- src/wnaf.rs | 117 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 214 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b9beff6..ae2eed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ byteorder = "1.1.0" clippy = { version = "0.0.151", optional = true } [features] -unstable-wnaf = [] -unstable-features = ["unstable-wnaf"] +unstable-features = [] u128-support = [] default = ["u128-support"] diff --git a/src/lib.rs b/src/lib.rs index 5790c04..ae67b99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,8 @@ pub mod tests; pub mod bls12_381; -#[cfg(feature = "unstable-wnaf")] -pub mod wnaf; +mod wnaf; +pub use self::wnaf::Wnaf; use std::fmt; use std::error::Error; diff --git a/src/tests/curve.rs b/src/tests/curve.rs index e3eb0dc..e6deec1 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -1,4 +1,4 @@ -use rand::{SeedableRng, XorShiftRng, Rand}; +use rand::{SeedableRng, XorShiftRng, Rand, Rng}; use ::{CurveProjective, CurveAffine, Field, EncodedPoint}; @@ -62,31 +62,115 @@ pub fn curve_tests<G: CurveProjective>() random_encoding_tests::<G::Affine>(); } -#[cfg(not(feature = "unstable-wnaf"))] -fn random_wnaf_tests<G: CurveProjective>() { } - -#[cfg(feature = "unstable-wnaf")] fn random_wnaf_tests<G: CurveProjective>() { use ::wnaf::*; use ::PrimeField; let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let mut table = vec![]; - let mut wnaf = vec![]; + { + let mut table = vec![]; + let mut wnaf = vec![]; + + for w in 2..14 { + for _ in 0..100 { + let g = G::rand(&mut rng); + let s = G::Scalar::rand(&mut rng).into_repr(); + let mut g1 = g; + g1.mul_assign(s); + + wnaf_table(&mut table, g, w); + wnaf_form(&mut wnaf, s, w); + let g2 = wnaf_exp(&table, &wnaf); + + assert_eq!(g1, g2); + } + } + } + + { + fn only_compiles_if_send<S: Send>(_: &S) { } - for w in 2..14 { for _ in 0..100 { let g = G::rand(&mut rng); let s = G::Scalar::rand(&mut rng).into_repr(); let mut g1 = g; g1.mul_assign(s); - wnaf_table(&mut table, g, w); - wnaf_form(&mut wnaf, s, w); - let g2 = wnaf_exp(&table, &wnaf); + let g2 = { + let mut wnaf = Wnaf::new(); + wnaf.base(g, 1).scalar(s) + }; + let g3 = { + let mut wnaf = Wnaf::new(); + wnaf.scalar(s).base(g) + }; + let g4 = { + let mut wnaf = Wnaf::new(); + let mut shared = wnaf.base(g, 1).shared(); + + only_compiles_if_send(&shared); + + shared.scalar(s) + }; + let g5 = { + let mut wnaf = Wnaf::new(); + let mut shared = wnaf.scalar(s).shared(); + + only_compiles_if_send(&shared); + + shared.base(g) + }; + + let g6 = { + let mut wnaf = Wnaf::new(); + { + // Populate the vectors. + wnaf.base(rng.gen(), 1).scalar(rng.gen()); + } + wnaf.base(g, 1).scalar(s) + }; + let g7 = { + let mut wnaf = Wnaf::new(); + { + // Populate the vectors. + wnaf.base(rng.gen(), 1).scalar(rng.gen()); + } + wnaf.scalar(s).base(g) + }; + let g8 = { + let mut wnaf = Wnaf::new(); + { + // Populate the vectors. + wnaf.base(rng.gen(), 1).scalar(rng.gen()); + } + let mut shared = wnaf.base(g, 1).shared(); + + only_compiles_if_send(&shared); + + shared.scalar(s) + }; + let g9 = { + let mut wnaf = Wnaf::new(); + { + // Populate the vectors. + wnaf.base(rng.gen(), 1).scalar(rng.gen()); + } + let mut shared = wnaf.scalar(s).shared(); + + only_compiles_if_send(&shared); + + shared.base(g) + }; assert_eq!(g1, g2); + assert_eq!(g1, g3); + assert_eq!(g1, g4); + assert_eq!(g1, g5); + assert_eq!(g1, g6); + assert_eq!(g1, g7); + assert_eq!(g1, g8); + assert_eq!(g1, g9); } } } diff --git a/src/wnaf.rs b/src/wnaf.rs index ecc9409..45419aa 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -1,4 +1,4 @@ -use super::{CurveProjective, PrimeFieldRepr}; +use super::{CurveProjective, PrimeFieldRepr, PrimeField}; /// Replaces the contents of `table` with a w-NAF window table for the given window size. /// @@ -82,3 +82,118 @@ pub fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G result } + +/// A wNAF exponentiation context. +#[derive(Debug)] +pub struct Wnaf<W, B, S> { + base: B, + scalar: S, + window_size: W +} + +impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { + /// Construct a new wNAF context without allocating. + pub fn new() -> Self { + Wnaf { + base: vec![], + scalar: vec![], + window_size: () + } + } + + /// Given a base and a number of scalars, compute a window table and return a `Wnaf` object that + /// can perform exponentiations with `.scalar(..)`. + pub fn base<'a>( + &'a mut self, + base: G, + num_scalars: usize + ) -> Wnaf<usize, &'a [G], &'a mut Vec<i64>> + { + // Compute the appropriate window size based on the number of scalars. + let window_size = G::recommended_wnaf_for_num_scalars(num_scalars); + + // Compute a wNAF table for the provided base and window size. + wnaf_table(&mut self.base, base, window_size); + + // Return a Wnaf object that immutably borrows the computed base storage location, + // but mutably borrows the scalar storage location. + Wnaf { + base: &self.base, + scalar: &mut self.scalar, + window_size: window_size + } + } + + /// Given a scalar, compute its wNAF representation and return a `Wnaf` object that can perform + /// exponentiations with `.base(..)`. + pub fn scalar<'a>( + &'a mut self, + scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr + ) -> Wnaf<usize, &'a mut Vec<G>, &'a [i64]> + { + // Compute the appropriate window size for the scalar. + let window_size = G::recommended_wnaf_for_scalar(scalar).unwrap_or(2); // TODO + + // Compute the wNAF form of the scalar. + wnaf_form(&mut self.scalar, scalar, window_size); + + // Return a Wnaf object that mutably borrows the base storage location, but + // immutably borrows the computed wNAF form scalar location. + Wnaf { + base: &mut self.base, + scalar: &self.scalar, + window_size: window_size + } + } +} + +impl<'a, G: CurveProjective> Wnaf<usize, &'a [G], &'a mut Vec<i64>> { + /// Constructs new space for the scalar representation while borrowing + /// the computed window table, for sending the window table across threads. + pub fn shared(&self) -> Wnaf<usize, &'a [G], Vec<i64>> { + Wnaf { + base: self.base, + scalar: vec![], + window_size: self.window_size + } + } +} + +impl<'a, G: CurveProjective> Wnaf<usize, &'a mut Vec<G>, &'a [i64]> { + /// Constructs new space for the window table while borrowing + /// the computed scalar representation, for sending the scalar representation + /// across threads. + pub fn shared(&self) -> Wnaf<usize, Vec<G>, &'a [i64]> { + Wnaf { + base: vec![], + scalar: self.scalar, + window_size: self.window_size + } + } +} + +impl<B, S: AsRef<[i64]>> Wnaf<usize, B, S> { + /// Performs exponentiation given a base. + pub fn base<G: CurveProjective>( + &mut self, + base: G + ) -> G + where B: AsMut<Vec<G>> + { + wnaf_table(self.base.as_mut(), base, self.window_size); + wnaf_exp(self.base.as_mut(), self.scalar.as_ref()) + } +} + +impl<B, S: AsMut<Vec<i64>>> Wnaf<usize, B, S> { + /// Performs exponentiation given a scalar. + pub fn scalar<G: CurveProjective>( + &mut self, + scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr + ) -> G + where B: AsRef<[G]> + { + wnaf_form(self.scalar.as_mut(), scalar, self.window_size); + wnaf_exp(self.base.as_ref(), self.scalar.as_mut()) + } +} From 06f6334679fc09e4bad207955722decb2111757e Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Wed, 27 Sep 2017 20:06:51 -0600 Subject: [PATCH 060/140] Change to docs for satisfying clippy. --- src/wnaf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wnaf.rs b/src/wnaf.rs index 45419aa..d4061aa 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -83,7 +83,7 @@ pub fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G result } -/// A wNAF exponentiation context. +/// A "w-ary non-adjacent form" exponentiation context. #[derive(Debug)] pub struct Wnaf<W, B, S> { base: B, From 894b44d0347253bdf3235a17a5dff64eb328e619 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 08:12:37 -0600 Subject: [PATCH 061/140] These structures are no longer exported outside the crate, and these assertions are unnecessary now that the external API can enforce them. --- src/lib.rs | 1 + src/wnaf.rs | 16 +++------------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ae67b99..8b6c4b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ #![cfg_attr(feature = "clippy", allow(inline_always))] #![cfg_attr(feature = "clippy", allow(too_many_arguments))] #![cfg_attr(feature = "clippy", allow(unreadable_literal))] +#![cfg_attr(feature = "clippy", allow(new_without_default_derive))] // Force public structures to implement Debug #![deny(missing_debug_implementations)] diff --git a/src/wnaf.rs b/src/wnaf.rs index d4061aa..9b00989 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -1,13 +1,8 @@ use super::{CurveProjective, PrimeFieldRepr, PrimeField}; /// Replaces the contents of `table` with a w-NAF window table for the given window size. -/// -/// This function will panic if provided a window size below two, or above 22. -pub fn wnaf_table<G: CurveProjective>(table: &mut Vec<G>, mut base: G, window: usize) +pub(crate) fn wnaf_table<G: CurveProjective>(table: &mut Vec<G>, mut base: G, window: usize) { - assert!(window < 23); - assert!(window > 1); - table.truncate(0); table.reserve(1 << (window-1)); @@ -21,13 +16,8 @@ pub fn wnaf_table<G: CurveProjective>(table: &mut Vec<G>, mut base: G, window: u } /// Replaces the contents of `wnaf` with the w-NAF representation of a scalar. -/// -/// This function will panic if provided a window size below two, or above 22. -pub fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window: usize) +pub(crate) fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window: usize) { - assert!(window < 23); - assert!(window > 1); - wnaf.truncate(0); while !c.is_zero() { @@ -58,7 +48,7 @@ pub fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window: usize /// /// This function must be provided a `table` and `wnaf` that were constructed with /// the same window size; otherwise, it may panic or produce invalid results. -pub fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G +pub(crate) fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G { let mut result = G::zero(); From bda22db9d5907d64782f30fa4caebf5d511fddfa Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 08:38:13 -0600 Subject: [PATCH 062/140] Always recommend a window table size. --- src/bls12_381/ec.rs | 36 +++++++++++++++--------------------- src/lib.rs | 7 +++---- src/wnaf.rs | 2 +- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index f2aefa2..f441cca 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -518,7 +518,7 @@ macro_rules! curve_impl { (*self).into() } - fn recommended_wnaf_for_scalar(scalar: <Self::Scalar as PrimeField>::Repr) -> Option<usize> { + fn recommended_wnaf_for_scalar(scalar: <Self::Scalar as PrimeField>::Repr) -> usize { Self::empirical_recommended_wnaf_for_scalar(scalar) } @@ -859,20 +859,17 @@ pub mod g1 { } impl G1 { - fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> Option<usize> + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize { - const RECOMMENDATIONS: [usize; 3] = [12, 34, 130]; - - let mut ret = None; let num_bits = scalar.num_bits() as usize; - for (i, r) in RECOMMENDATIONS.iter().enumerate() { - if *r >= num_bits { - ret = Some(i + 2) - } + if num_bits >= 130 { + 4 + } else if num_bits >= 34 { + 3 + } else { + 2 } - - ret } fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize @@ -1398,20 +1395,17 @@ pub mod g2 { } impl G2 { - fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> Option<usize> + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize { - const RECOMMENDATIONS: [usize; 3] = [13, 37, 103]; - - let mut ret = None; let num_bits = scalar.num_bits() as usize; - for (i, r) in RECOMMENDATIONS.iter().enumerate() { - if *r >= num_bits { - ret = Some(i + 2) - } + if num_bits >= 103 { + 4 + } else if num_bits >= 37 { + 3 + } else { + 2 } - - ret } fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize diff --git a/src/lib.rs b/src/lib.rs index 8b6c4b0..a3c4a2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,10 +145,9 @@ pub trait CurveProjective: PartialEq + /// Converts this element into its affine representation. fn into_affine(&self) -> Self::Affine; - /// Recommends a wNAF window table size given a scalar. Returns `None` if normal - /// scalar multiplication is encouraged. If `Some` is returned, it will be between - /// 2 and 22, inclusive. - fn recommended_wnaf_for_scalar(scalar: <Self::Scalar as PrimeField>::Repr) -> Option<usize>; + /// Recommends a wNAF window table size given a scalar. Always returns a number + /// between 2 and 22, inclusive. + fn recommended_wnaf_for_scalar(scalar: <Self::Scalar as PrimeField>::Repr) -> usize; /// Recommends a wNAF window size given the number of scalars you intend to multiply /// a base by. Always returns a number between 2 and 22, inclusive. diff --git a/src/wnaf.rs b/src/wnaf.rs index 9b00989..03c0274 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -122,7 +122,7 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { ) -> Wnaf<usize, &'a mut Vec<G>, &'a [i64]> { // Compute the appropriate window size for the scalar. - let window_size = G::recommended_wnaf_for_scalar(scalar).unwrap_or(2); // TODO + let window_size = G::recommended_wnaf_for_scalar(scalar); // Compute the wNAF form of the scalar. wnaf_form(&mut self.scalar, scalar, window_size); From c38cb324f69440141cebd271a94179c46f679c5f Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 11:31:46 -0600 Subject: [PATCH 063/140] Simplify `pairing_benches`. --- benches/pairing_benches.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/benches/pairing_benches.rs b/benches/pairing_benches.rs index 43f3c6a..3ae12e0 100644 --- a/benches/pairing_benches.rs +++ b/benches/pairing_benches.rs @@ -1,21 +1,7 @@ -// `clippy` is a code linting tool for improving code quality by catching -// common mistakes or strange code patterns. If the `clippy` feature is -// provided, it is enabled and all compiler warnings are prohibited. -#![cfg_attr(feature = "clippy", deny(warnings))] -#![cfg_attr(feature = "clippy", feature(plugin))] -#![cfg_attr(feature = "clippy", plugin(clippy))] -#![cfg_attr(feature = "clippy", allow(inline_always))] -#![cfg_attr(feature = "clippy", allow(too_many_arguments))] -#![cfg_attr(feature = "clippy", allow(unreadable_literal))] +#![feature(test)] -// The compiler provides `test` (on nightly) for benchmarking tools, but -// it's hidden behind a feature flag. Enable it if we're testing. -#![cfg_attr(test, feature(test))] -#[cfg(test)] extern crate test; - extern crate rand; - extern crate pairing; mod bls12_381; From 6708878f4cdebbd424d9dc2f3fc5506288cd8b53 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 11:39:53 -0600 Subject: [PATCH 064/140] Elide these lifetimes. --- src/wnaf.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wnaf.rs b/src/wnaf.rs index 03c0274..de5021d 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -93,11 +93,11 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { /// Given a base and a number of scalars, compute a window table and return a `Wnaf` object that /// can perform exponentiations with `.scalar(..)`. - pub fn base<'a>( - &'a mut self, + pub fn base( + &mut self, base: G, num_scalars: usize - ) -> Wnaf<usize, &'a [G], &'a mut Vec<i64>> + ) -> Wnaf<usize, &[G], &mut Vec<i64>> { // Compute the appropriate window size based on the number of scalars. let window_size = G::recommended_wnaf_for_num_scalars(num_scalars); @@ -116,10 +116,10 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { /// Given a scalar, compute its wNAF representation and return a `Wnaf` object that can perform /// exponentiations with `.base(..)`. - pub fn scalar<'a>( - &'a mut self, + pub fn scalar( + &mut self, scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr - ) -> Wnaf<usize, &'a mut Vec<G>, &'a [i64]> + ) -> Wnaf<usize, &mut Vec<G>, &[i64]> { // Compute the appropriate window size for the scalar. let window_size = G::recommended_wnaf_for_scalar(scalar); From 636a037bb19d89c30a9ffc934e47b3e5f4d101f9 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 12:52:14 -0600 Subject: [PATCH 065/140] Make `u128-support` feature opt-in rather than default. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ae2eed5..778a0c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,4 @@ clippy = { version = "0.0.151", optional = true } [features] unstable-features = [] u128-support = [] -default = ["u128-support"] +default = [] From 291fa719147d5b293833f904fc5fb9f084b50b18 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 13:03:01 -0600 Subject: [PATCH 066/140] This coercion doesn't take place on stable yet. --- src/wnaf.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wnaf.rs b/src/wnaf.rs index de5021d..0cdae3b 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -108,7 +108,7 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { // Return a Wnaf object that immutably borrows the computed base storage location, // but mutably borrows the scalar storage location. Wnaf { - base: &self.base, + base: &self.base[..], scalar: &mut self.scalar, window_size: window_size } @@ -131,7 +131,7 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { // immutably borrows the computed wNAF form scalar location. Wnaf { base: &mut self.base, - scalar: &self.scalar, + scalar: &self.scalar[..], window_size: window_size } } From 93e2a132b5229fc0414dfca07102513eebbee12a Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 13:15:29 -0600 Subject: [PATCH 067/140] Mask rather than divn, closes #50. --- src/bls12_381/fq.rs | 5 ++++- src/bls12_381/fr.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 8440b1d..569b57a 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -415,7 +415,10 @@ impl ::rand::Rand for Fq { fn rand<R: ::rand::Rng>(rng: &mut R) -> Self { loop { let mut tmp = Fq(FqRepr::rand(rng)); - tmp.0.divn(REPR_SHAVE_BITS); + + // Mask away the unused bits at the beginning. + tmp.0.as_mut()[5] &= 0xffffffffffffffff >> REPR_SHAVE_BITS; + if tmp.is_valid() { return tmp } diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 0571563..d10ba93 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -237,7 +237,10 @@ impl ::rand::Rand for Fr { fn rand<R: ::rand::Rng>(rng: &mut R) -> Self { loop { let mut tmp = Fr(FrRepr::rand(rng)); - tmp.0.divn(REPR_SHAVE_BITS); + + // Mask away the unused bits at the beginning. + tmp.0.as_mut()[3] &= 0xffffffffffffffff >> REPR_SHAVE_BITS; + if tmp.is_valid() { return tmp } From 4aa51bd3d4dcd35f66c7fbeec9bc3cb35b1a852c Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 13:32:34 -0600 Subject: [PATCH 068/140] Add security warnings and some instructions to README.md. --- Cargo.toml | 2 ++ README.md | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 778a0c6..c0457a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,7 @@ [package] name = "pairing" + +# Remember to change version string in README.md. version = "0.11.0" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" diff --git a/README.md b/README.md index 538a5c5..21086e8 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ This is a Rust crate for using pairing-friendly elliptic curves. Currently, only ## [Documentation](https://docs.rs/pairing/) +Bring the `pairing` crate into your project just as you normally would. + +If you're using a supported platform and the nightly Rust compiler, you can enable the `u128-support` feature for faster arithmetic. + +```toml +[dependencies.pairing] +version = "0.11" +features = ["u128-support"] +``` + +## Security Warnings + +This library does not make any guarantees about constant-time operations, memory access patterns, or resistance to side-channel attacks. + ## License Licensed under either of From ce875c902b72ba03ad81409f33d432d3c86793bb Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 15:24:31 -0600 Subject: [PATCH 069/140] LICENSE-MIT: Remove inaccurate (misattributed) copyright notice LICENSE-MIT contains the line "Copyright (c) 2017 Sean Bowe", which implies that an entity called "Sean Bowe" holds copyrights in the pairing Rust library. Pairing library contributors retain their copyrights, and do not assign them to anyone by contributing. Remove the inaccurate notice. --- LICENSE-MIT | 4 ---- 1 file changed, 4 deletions(-) diff --git a/LICENSE-MIT b/LICENSE-MIT index ed3a13f..89de354 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,7 +1,3 @@ -The MIT License (MIT) - -Copyright (c) 2017 Sean Bowe - 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 From 35bf7f63ea863592c510149c72eb4956e99f569a Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 15:29:58 -0600 Subject: [PATCH 070/140] Modify the LICENSE-APACHE and LICENSE-MIT files. This modifies the files so that they are exactly the same as the Rust project's license files. This does not change the wording of the license. --- LICENSE-APACHE | 1 - LICENSE-MIT | 36 +++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 1e5006d..16fe87b 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -199,4 +199,3 @@ 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 index 89de354..31aa793 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,17 +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: +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 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. +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. From 8dbee4197708525ffbbe6b8e0e069e9cbf5626d3 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 15:35:30 -0600 Subject: [PATCH 071/140] Add COPYRIGHT file to inform of conditions for contributing and license information. --- COPYRIGHT | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 COPYRIGHT diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..c3876a4 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,14 @@ +Copyrights in the "pairing" library are retained by their contributors. No +copyright assignment is required to contribute to the "pairing" library. + +The "pairing" 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. From 4fe3e1d6e3d91c035687c931eb9254ade405d3f7 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 16:08:56 -0600 Subject: [PATCH 072/140] Use associated constants for simple constants like these. (Closes #39.) --- src/bls12_381/fq.rs | 20 +++++++------------- src/bls12_381/fr.rs | 20 +++++++------------- src/lib.rs | 14 ++++++-------- 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 569b57a..6c0b196 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -459,21 +459,15 @@ impl PrimeField for Fq { MODULUS } - fn num_bits() -> u32 { - MODULUS_BITS - } + const NUM_BITS: u32 = MODULUS_BITS; - fn capacity() -> u32 { - Self::num_bits() - 1 - } + const CAPACITY: u32 = Self::NUM_BITS - 1; fn multiplicative_generator() -> Self { Fq(GENERATOR) } - fn s() -> u32 { - S - } + const S: u32 = S; fn root_of_unity() -> Self { Fq(ROOT_OF_UNITY) @@ -1500,20 +1494,20 @@ fn test_fq_display() { #[test] fn test_fq_num_bits() { - assert_eq!(Fq::num_bits(), 381); - assert_eq!(Fq::capacity(), 380); + assert_eq!(Fq::NUM_BITS, 381); + assert_eq!(Fq::CAPACITY, 380); } #[test] fn test_fq_root_of_unity() { - assert_eq!(Fq::s(), 1); + assert_eq!(Fq::S, 1); assert_eq!(Fq::multiplicative_generator(), Fq::from_repr(FqRepr::from(2)).unwrap()); assert_eq!( Fq::multiplicative_generator().pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]), Fq::root_of_unity() ); assert_eq!( - Fq::root_of_unity().pow([1 << Fq::s()]), + Fq::root_of_unity().pow([1 << Fq::S]), Fq::one() ); assert!(Fq::multiplicative_generator().sqrt().is_none()); diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index d10ba93..629984d 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -280,21 +280,15 @@ impl PrimeField for Fr { MODULUS } - fn num_bits() -> u32 { - MODULUS_BITS - } + const NUM_BITS: u32 = MODULUS_BITS; - fn capacity() -> u32 { - Self::num_bits() - 1 - } + const CAPACITY: u32 = Self::NUM_BITS - 1; fn multiplicative_generator() -> Self { Fr(GENERATOR) } - fn s() -> u32 { - S - } + const S: u32 = S; fn root_of_unity() -> Self { Fr(ROOT_OF_UNITY) @@ -1216,20 +1210,20 @@ fn test_fr_display() { #[test] fn test_fr_num_bits() { - assert_eq!(Fr::num_bits(), 255); - assert_eq!(Fr::capacity(), 254); + assert_eq!(Fr::NUM_BITS, 255); + assert_eq!(Fr::CAPACITY, 254); } #[test] fn test_fr_root_of_unity() { - assert_eq!(Fr::s(), 32); + assert_eq!(Fr::S, 32); assert_eq!(Fr::multiplicative_generator(), Fr::from_repr(FrRepr::from(7)).unwrap()); assert_eq!( Fr::multiplicative_generator().pow([0xfffe5bfeffffffff, 0x9a1d80553bda402, 0x299d7d483339d808, 0x73eda753]), Fr::root_of_unity() ); assert_eq!( - Fr::root_of_unity().pow([1 << Fr::s()]), + Fr::root_of_unity().pow([1 << Fr::S]), Fr::one() ); assert!(Fr::multiplicative_generator().sqrt().is_none()); diff --git a/src/lib.rs b/src/lib.rs index a3c4a2e..dc5d330 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -540,20 +540,18 @@ pub trait PrimeField: Field /// Returns the field characteristic; the modulus. fn char() -> Self::Repr; - /// Returns how many bits are needed to represent an element of this - /// field. - fn num_bits() -> u32; + /// How many bits are needed to represent an element of this field. + const NUM_BITS: u32; - /// Returns how many bits of information can be reliably stored in the - /// field element. - fn capacity() -> u32; + /// How many bits of information can be reliably stored in the field element. + const CAPACITY: u32; /// Returns the multiplicative generator of `char()` - 1 order. This element /// must also be quadratic nonresidue. fn multiplicative_generator() -> Self; - /// Returns s such that 2^s * t = `char()` - 1 with t odd. - fn s() -> u32; + /// 2^s * t = `char()` - 1 with t odd. + const S: u32; /// Returns the 2^s root of unity computed by exponentiating the `multiplicative_generator()` /// by t. From 927febe4e893046758ee1d378be5b8faebece7fa Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 16:52:12 -0600 Subject: [PATCH 073/140] Bump version to 0.12.0. --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0457a2..4c76c63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.11.0" +version = "0.12.0" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" diff --git a/README.md b/README.md index 21086e8..9b3ae6f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ If you're using a supported platform and the nightly Rust compiler, you can enab ```toml [dependencies.pairing] -version = "0.11" +version = "0.12" features = ["u128-support"] ``` From 05339414cc132555d3e4c93ba7d5ca18c3e94954 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 17:10:06 -0600 Subject: [PATCH 074/140] Update clippy and compensate for new lints. --- Cargo.toml | 2 +- src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c76c63..bac829e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1.1.0" -clippy = { version = "0.0.151", optional = true } +clippy = { version = "0.0.165", optional = true } [features] unstable-features = [] diff --git a/src/lib.rs b/src/lib.rs index dc5d330..8b80ef3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ #![cfg_attr(feature = "clippy", allow(too_many_arguments))] #![cfg_attr(feature = "clippy", allow(unreadable_literal))] #![cfg_attr(feature = "clippy", allow(new_without_default_derive))] +#![cfg_attr(feature = "clippy", allow(expl_impl_clone_on_copy))] // TODO // Force public structures to implement Debug #![deny(missing_debug_implementations)] @@ -519,7 +520,7 @@ pub trait PrimeField: Field } res.mul_assign(&ten); - res.add_assign(&Self::from_repr(Self::Repr::from(c as u64)).unwrap()); + res.add_assign(&Self::from_repr(Self::Repr::from(u64::from(c))).unwrap()); }, None => { return None; From 67f5fbc94c1ef45cb6c1ae3d428dccc4b2561399 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 28 Sep 2017 17:37:54 -0600 Subject: [PATCH 075/140] More modifications to satisfy clippy. --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8b80ef3..c39615c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -623,7 +623,7 @@ mod arith { /// the borrow value. #[inline(always)] pub(crate) fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { - let tmp = (1u128 << 64) + (a as u128) - (b as u128) - (*borrow as u128); + let tmp = (1u128 << 64) + u128::from(a) - u128::from(b) - u128::from(*borrow); *borrow = if tmp >> 64 == 0 { 1 } else { 0 }; @@ -634,7 +634,7 @@ mod arith { /// carry value. #[inline(always)] pub(crate) fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { - let tmp = (a as u128) + (b as u128) + (*carry as u128); + let tmp = u128::from(a) + u128::from(b) + u128::from(*carry); *carry = (tmp >> 64) as u64; @@ -645,7 +645,7 @@ mod arith { /// and setting carry to the most significant digit. #[inline(always)] pub(crate) fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { - let tmp = (a as u128) + (b as u128) * (c as u128) + (*carry as u128); + let tmp = (u128::from(a)) + u128::from(b) * u128::from(c) + u128::from(*carry); *carry = (tmp >> 64) as u64; From 18c75f11abb9ede4917f711c6f722c61b9236cd8 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 3 Oct 2017 14:13:02 -0600 Subject: [PATCH 076/140] Account for negative curve parameter to be compatible with RELIC. --- src/bls12_381/mod.rs | 5 ++++ src/bls12_381/tests/mod.rs | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 4c2a76f..341df67 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -93,6 +93,10 @@ impl Engine for Bls12 { ell(&mut f, coeffs.next().unwrap(), &p.0); } + if BLS_X_IS_NEGATIVE { + f.conjugate(); + } + f } @@ -148,6 +152,7 @@ impl Engine for Bls12 { y2 = y3; y2.frobenius_map(1); y1.mul_assign(&y2); + Some(y1) }, None => None diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs index 08de508..1b3d521 100644 --- a/src/bls12_381/tests/mod.rs +++ b/src/bls12_381/tests/mod.rs @@ -1,6 +1,57 @@ use super::*; use ::*; +#[test] +fn test_pairing_result_against_relic() { + /* + Sent to me from Diego Aranha (author of RELIC library): + + 1250EBD871FC0A92 A7B2D83168D0D727 272D441BEFA15C50 3DD8E90CE98DB3E7 B6D194F60839C508 A84305AACA1789B6 + 089A1C5B46E5110B 86750EC6A5323488 68A84045483C92B7 AF5AF689452EAFAB F1A8943E50439F1D 59882A98EAA0170F + 1368BB445C7C2D20 9703F239689CE34C 0378A68E72A6B3B2 16DA0E22A5031B54 DDFF57309396B38C 881C4C849EC23E87 + 193502B86EDB8857 C273FA075A505129 37E0794E1E65A761 7C90D8BD66065B1F FFE51D7A579973B1 315021EC3C19934F + 01B2F522473D1713 91125BA84DC4007C FBF2F8DA752F7C74 185203FCCA589AC7 19C34DFFBBAAD843 1DAD1C1FB597AAA5 + 018107154F25A764 BD3C79937A45B845 46DA634B8F6BE14A 8061E55CCEBA478B 23F7DACAA35C8CA7 8BEAE9624045B4B6 + 19F26337D205FB46 9CD6BD15C3D5A04D C88784FBB3D0B2DB DEA54D43B2B73F2C BB12D58386A8703E 0F948226E47EE89D + 06FBA23EB7C5AF0D 9F80940CA771B6FF D5857BAAF222EB95 A7D2809D61BFE02E 1BFD1B68FF02F0B8 102AE1C2D5D5AB1A + 11B8B424CD48BF38 FCEF68083B0B0EC5 C81A93B330EE1A67 7D0D15FF7B984E89 78EF48881E32FAC9 1B93B47333E2BA57 + 03350F55A7AEFCD3 C31B4FCB6CE5771C C6A0E9786AB59733 20C806AD36082910 7BA810C5A09FFDD9 BE2291A0C25A99A2 + 04C581234D086A99 02249B64728FFD21 A189E87935A95405 1C7CDBA7B3872629 A4FAFC05066245CB 9108F0242D0FE3EF + 0F41E58663BF08CF 068672CBD01A7EC7 3BACA4D72CA93544 DEFF686BFD6DF543 D48EAA24AFE47E1E FDE449383B676631 + */ + + assert_eq!(Bls12::pairing(G1::one(), G2::one()), Fq12 { + c0: Fq6 { + c0: Fq2 { + c0: Fq::from_str("2819105605953691245277803056322684086884703000473961065716485506033588504203831029066448642358042597501014294104502").unwrap(), + c1: Fq::from_str("1323968232986996742571315206151405965104242542339680722164220900812303524334628370163366153839984196298685227734799").unwrap() + }, + c1: Fq2 { + c0: Fq::from_str("2987335049721312504428602988447616328830341722376962214011674875969052835043875658579425548512925634040144704192135").unwrap(), + c1: Fq::from_str("3879723582452552452538684314479081967502111497413076598816163759028842927668327542875108457755966417881797966271311").unwrap() + }, + c2: Fq2 { + c0: Fq::from_str("261508182517997003171385743374653339186059518494239543139839025878870012614975302676296704930880982238308326681253").unwrap(), + c1: Fq::from_str("231488992246460459663813598342448669854473942105054381511346786719005883340876032043606739070883099647773793170614").unwrap() + } + }, + c1: Fq6 { + c0: Fq2 { + c0: Fq::from_str("3993582095516422658773669068931361134188738159766715576187490305611759126554796569868053818105850661142222948198557").unwrap(), + c1: Fq::from_str("1074773511698422344502264006159859710502164045911412750831641680783012525555872467108249271286757399121183508900634").unwrap() + }, + c1: Fq2 { + c0: Fq::from_str("2727588299083545686739024317998512740561167011046940249988557419323068809019137624943703910267790601287073339193943").unwrap(), + c1: Fq::from_str("493643299814437640914745677854369670041080344349607504656543355799077485536288866009245028091988146107059514546594").unwrap() + }, + c2: Fq2 { + c0: Fq::from_str("734401332196641441839439105942623141234148957972407782257355060229193854324927417865401895596108124443575283868655").unwrap(), + c1: Fq::from_str("2348330098288556420918672502923664952620152483128593484301759394583320358354186482723629999370241674973832318248497").unwrap() + } + } + }); +} + fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected: &[u8]) { let mut e = G::zero(); From 60887521b6bd2dc18052b2bea750a7c825b62801 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 3 Oct 2017 14:35:00 -0600 Subject: [PATCH 077/140] Derive `Clone` for {G1|G2}{Uncompressed|Compressed}. --- src/bls12_381/ec.rs | 32 ++++---------------------------- src/lib.rs | 1 - 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index f441cca..c5486d6 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -591,15 +591,9 @@ pub mod g1 { curve_impl!("G1", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed, G2Affine); - #[derive(Copy)] + #[derive(Copy, Clone)] pub struct G1Uncompressed([u8; 96]); - impl Clone for G1Uncompressed { - fn clone(&self) -> G1Uncompressed { - G1Uncompressed(self.0) - } - } - impl AsRef<[u8]> for G1Uncompressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -699,15 +693,9 @@ pub mod g1 { } } - #[derive(Copy)] + #[derive(Copy, Clone)] pub struct G1Compressed([u8; 48]); - impl Clone for G1Compressed { - fn clone(&self) -> G1Compressed { - G1Compressed(self.0) - } - } - impl AsRef<[u8]> for G1Compressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -1100,15 +1088,9 @@ pub mod g2 { curve_impl!("G2", G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed, G1Affine); - #[derive(Copy)] + #[derive(Copy, Clone)] pub struct G2Uncompressed([u8; 192]); - impl Clone for G2Uncompressed { - fn clone(&self) -> G2Uncompressed { - G2Uncompressed(self.0) - } - } - impl AsRef<[u8]> for G2Uncompressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -1220,15 +1202,9 @@ pub mod g2 { } } - #[derive(Copy)] + #[derive(Copy, Clone)] pub struct G2Compressed([u8; 96]); - impl Clone for G2Compressed { - fn clone(&self) -> G2Compressed { - G2Compressed(self.0) - } - } - impl AsRef<[u8]> for G2Compressed { fn as_ref(&self) -> &[u8] { &self.0 diff --git a/src/lib.rs b/src/lib.rs index c39615c..e2090b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,6 @@ #![cfg_attr(feature = "clippy", allow(too_many_arguments))] #![cfg_attr(feature = "clippy", allow(unreadable_literal))] #![cfg_attr(feature = "clippy", allow(new_without_default_derive))] -#![cfg_attr(feature = "clippy", allow(expl_impl_clone_on_copy))] // TODO // Force public structures to implement Debug #![deny(missing_debug_implementations)] From 931257599dbe64298a2a4a74105dc9bdd94296bf Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Wed, 4 Oct 2017 11:43:42 -0600 Subject: [PATCH 078/140] Refactor code for finding affine points from x-coordinates. --- src/bls12_381/ec.rs | 77 +++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index f441cca..16c5401 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -85,6 +85,33 @@ macro_rules! curve_impl { } impl $affine { + /// Constructs an affine point with the lexicographically smallest + /// y-coordinate, given an x-coordinate, so long as the x-coordinate + /// exists on the curve. The point is not guaranteed to be in the + /// prime order subgroup. + fn get_point_from_x(x: $basefield) -> Option<$affine> { + // Compute x^3 + b + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&$affine::get_coeff_b()); + + x3b.sqrt().map(|y| { + let mut negy = y; + negy.negate(); + + $affine { + x: x, + y: if y < negy { + y + } else { + negy + }, + infinity: false + } + }) + } + fn is_on_curve(&self) -> bool { if self.is_zero() { true @@ -781,26 +808,13 @@ pub mod g1 { // Interpret as Fq element. let x = Fq::from_repr(x).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?; - // Compute x^3 + b - let mut x3b = x; - x3b.square(); - x3b.mul_assign(&x); - x3b.add_assign(&G1Affine::get_coeff_b()); + match G1Affine::get_point_from_x(x) { + Some(mut p) => { + if greatest { + p.negate(); + } - // Attempt to compute y - match x3b.sqrt() { - Some(y) => { - let mut negy = y; - negy.negate(); - - // Get the parity of the sqrt we found. - let parity = y > negy; - - Ok(G1Affine { - x: x, - y: if parity == greatest { y } else { negy }, - infinity: false - }) + Ok(p) }, None => { // Point must not be on the curve. @@ -1307,26 +1321,13 @@ pub mod g2 { c1: Fq::from_repr(x_c1).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e))? }; - // Compute x^3 + b - let mut x3b = x; - x3b.square(); - x3b.mul_assign(&x); - x3b.add_assign(&G2Affine::get_coeff_b()); + match G2Affine::get_point_from_x(x) { + Some(mut p) => { + if greatest { + p.negate(); + } - // Attempt to compute y - match x3b.sqrt() { - Some(y) => { - let mut negy = y; - negy.negate(); - - // Get the parity of the sqrt we found. - let parity = y > negy; - - Ok(G2Affine { - x: x, - y: if parity == greatest { y } else { negy }, - infinity: false - }) + Ok(p) }, None => { // Point must not be on the curve. From 85b95750e2b0ed6cf7288f4ce16d6e0cb060fdaf Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Wed, 4 Oct 2017 14:09:40 -0600 Subject: [PATCH 079/140] Fix comment about u128-support. --- src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c39615c..b02eb2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ -// This library relies on the Rust nightly compiler's `i128_type` feature. -// If that's not okay for you, disable the u128-support feature. (Pass -// --no-default-features for example.) +// If the "u128-support" feature is enabled, this library can use +// more efficient arithmetic. Only available in the nightly compiler. #![cfg_attr(feature = "u128-support", feature(i128_type))] // `clippy` is a code linting tool for improving code quality by catching From 683f21a4d54ecf836c6ba002e903236c91880ffd Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Wed, 4 Oct 2017 14:53:42 -0600 Subject: [PATCH 080/140] Remove spurious newline. --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b02eb2a..7acff09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -617,7 +617,6 @@ use self::arith::*; #[cfg(feature = "u128-support")] mod arith { - /// Calculate a - b - borrow, returning the result and modifying /// the borrow value. #[inline(always)] From dbac57c27bc4628abe0170d73ea5d2b67193bfff Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 5 Oct 2017 12:35:04 -0600 Subject: [PATCH 081/140] Further refactoring of get_point_from_x() --- src/bls12_381/ec.rs | 41 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 16c5401..f459e0b 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -85,11 +85,12 @@ macro_rules! curve_impl { } impl $affine { - /// Constructs an affine point with the lexicographically smallest - /// y-coordinate, given an x-coordinate, so long as the x-coordinate - /// exists on the curve. The point is not guaranteed to be in the - /// prime order subgroup. - fn get_point_from_x(x: $basefield) -> Option<$affine> { + /// Attempts to construct an affine point given an x-coordinate. The + /// point is not guaranteed to be in the prime order subgroup. + /// + /// If and only if `greatest` is set will the lexicographically + /// largest y-coordinate be selected. + fn get_point_from_x(x: $basefield, greatest: bool) -> Option<$affine> { // Compute x^3 + b let mut x3b = x; x3b.square(); @@ -102,7 +103,7 @@ macro_rules! curve_impl { $affine { x: x, - y: if y < negy { + y: if (y < negy) ^ greatest { y } else { negy @@ -808,19 +809,7 @@ pub mod g1 { // Interpret as Fq element. let x = Fq::from_repr(x).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?; - match G1Affine::get_point_from_x(x) { - Some(mut p) => { - if greatest { - p.negate(); - } - - Ok(p) - }, - None => { - // Point must not be on the curve. - Err(GroupDecodingError::NotOnCurve) - } - } + G1Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve) } } fn from_affine(affine: G1Affine) -> Self { @@ -1321,19 +1310,7 @@ pub mod g2 { c1: Fq::from_repr(x_c1).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e))? }; - match G2Affine::get_point_from_x(x) { - Some(mut p) => { - if greatest { - p.negate(); - } - - Ok(p) - }, - None => { - // Point must not be on the curve. - Err(GroupDecodingError::NotOnCurve) - } - } + G2Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve) } } fn from_affine(affine: G2Affine) -> Self { From e5607bb52855e7bf5a854e9e921e4f6992344891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michele=20Orr=C3=B9?= <maker@tumbolandia.net> Date: Sat, 7 Oct 2017 15:09:24 +0200 Subject: [PATCH 082/140] Add "scale_by_cofactor". Add a function for Affine types that multiplies point by the cofactor of the group. --- src/bls12_381/ec.rs | 77 ++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index f459e0b..676ab1a 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -85,6 +85,17 @@ macro_rules! curve_impl { } impl $affine { + + fn mul_bits<S: AsRef<[u64]>>(&self, bits: BitIterator<S>) -> $projective { + let mut res = $projective::zero(); + for i in bits { + res.double(); + if i { res.add_assign_mixed(self) } + } + res + } + + /// Attempts to construct an affine point given an x-coordinate. The /// point is not guaranteed to be in the prime order subgroup. /// @@ -163,18 +174,8 @@ macro_rules! curve_impl { } fn mul<S: Into<<Self::Scalar as PrimeField>::Repr>>(&self, by: S) -> $projective { - let mut res = $projective::zero(); - - for i in BitIterator::new(by.into()) - { - res.double(); - - if i { - res.add_assign_mixed(self); - } - } - - res + let bits = BitIterator::new(by.into()); + self.mul_bits(bits) } fn negate(&mut self) { @@ -844,6 +845,13 @@ pub mod g1 { } impl G1Affine { + + fn scale_by_cofactor(&self) -> G1 { + // G1 cofactor = (x - 1)^2 / 3 = 76329603384216526031706109802092473003 + let cofactor = BitIterator::new([0x8c00aaab0000aaab, 0x396c8c005555e156]); + self.mul_bits(cofactor) + } + fn get_generator() -> Self { G1Affine { x: super::super::fq::G1_GENERATOR_X, @@ -929,25 +937,9 @@ pub mod g1 { y: if yrepr < negyrepr { y } else { negy }, infinity: false }; - assert!(!p.is_in_correct_subgroup_assuming_on_curve()); - let mut g1 = G1::zero(); - - // Cofactor of G1 is 76329603384216526031706109802092473003. - // Calculated by: ((x-1)**2) // 3 - // where x is the BLS parameter. - for b in "111001011011001000110000000000010101010101010111100001010101101000110000000000101010101010101100000000000000001010101010101011" - .chars() - .map(|c| c == '1') - { - g1.double(); - - if b { - g1.add_assign_mixed(&p); - } - } - + let g1 = p.scale_by_cofactor(); if !g1.is_zero() { assert_eq!(i, 4); let g1 = G1Affine::from(g1); @@ -1367,6 +1359,15 @@ pub mod g2 { } } + fn scale_by_cofactor(&self) -> G2 { + // G2 cofactor = (x^8 - 4 x^7 + 5 x^6) - (4 x^4 + 6 x^3 - 4 x^2 - 4 x + 13) // 9 + // 0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5 + let cofactor = BitIterator::new([0xcf1c38e31c7238e5, 0x1616ec6e786f0c70, 0x21537e293a6691ae, + 0xa628f1cb4d9e82ef, 0xa68a205b2e5a7ddf, 0xcd91de4547085aba, + 0x91d50792876a202, 0x5d543a95414e7f1]); + self.mul_bits(cofactor) + } + fn perform_pairing(&self, other: &G1Affine) -> Fq12 { super::super::Bls12::pairing(*other, *self) } @@ -1434,28 +1435,12 @@ pub mod g2 { assert!(!p.is_in_correct_subgroup_assuming_on_curve()); - let mut g2 = G2::zero(); - - // Cofactor of G2 is 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109. - // Calculated by: ((x**8) - (4 * (x**7)) + (5 * (x**6)) - (4 * (x**4)) + (6 * (x**3)) - (4 * (x**2)) - (4*x) + 13) // 9 - // where x is the BLS parameter. - for b in "101110101010100001110101001010101000001010011100111111100010000100100011101010100000111100100101000011101101010001000000010110011011001000111011110010001010100011100001000010110101011101010100110100010100010000001011011001011100101101001111101110111111010011000101000111100011100101101001101100111101000001011101111001000010101001101111110001010010011101001100110100100011010111000010110000101101110110001101110011110000110111100001100011100001100111100011100001110001110001100011100011100100011100011100101" - .chars() - .map(|c| c == '1') - { - g2.double(); - - if b { - g2.add_assign_mixed(&p); - } - } - + let g2 = p.scale_by_cofactor(); if !g2.is_zero() { assert_eq!(i, 2); let g2 = G2Affine::from(g2); assert!(g2.is_in_correct_subgroup_assuming_on_curve()); - assert_eq!(g2, G2Affine::one()); break; } From 65e50a9e5a4b81179222fd5bd76d8ff3f984c495 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 9 Oct 2017 17:14:36 -0600 Subject: [PATCH 083/140] Allow `scale_by_cofactor` to be dead code temporarily. --- src/bls12_381/ec.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 676ab1a..1620564 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -845,7 +845,7 @@ pub mod g1 { } impl G1Affine { - + #[allow(dead_code)] fn scale_by_cofactor(&self) -> G1 { // G1 cofactor = (x - 1)^2 / 3 = 76329603384216526031706109802092473003 let cofactor = BitIterator::new([0x8c00aaab0000aaab, 0x396c8c005555e156]); @@ -1359,6 +1359,7 @@ pub mod g2 { } } + #[allow(dead_code)] fn scale_by_cofactor(&self) -> G2 { // G2 cofactor = (x^8 - 4 x^7 + 5 x^6) - (4 x^4 + 6 x^3 - 4 x^2 - 4 x + 13) // 9 // 0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5 From 12b9606b9c6d73c4d116d5816967ad01906ffc67 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 10 Oct 2017 01:13:35 -0600 Subject: [PATCH 084/140] G1/G2 rand() should produce elements of unknown exponent. --- src/bls12_381/ec.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 1620564..f3223f7 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -85,7 +85,6 @@ macro_rules! curve_impl { } impl $affine { - fn mul_bits<S: AsRef<[u64]>>(&self, bits: BitIterator<S>) -> $projective { let mut res = $projective::zero(); for i in bits { @@ -95,7 +94,6 @@ macro_rules! curve_impl { res } - /// Attempts to construct an affine point given an x-coordinate. The /// point is not guaranteed to be in the prime order subgroup. /// @@ -200,7 +198,18 @@ macro_rules! curve_impl { impl Rand for $projective { fn rand<R: Rng>(rng: &mut R) -> Self { - $affine::one().mul($scalarfield::rand(rng)) + loop { + let x = rng.gen(); + let greatest = rng.gen(); + + if let Some(p) = $affine::get_point_from_x(x, greatest) { + let p = p.scale_by_cofactor(); + + if !p.is_zero() { + return p; + } + } + } } } @@ -845,7 +854,6 @@ pub mod g1 { } impl G1Affine { - #[allow(dead_code)] fn scale_by_cofactor(&self) -> G1 { // G1 cofactor = (x - 1)^2 / 3 = 76329603384216526031706109802092473003 let cofactor = BitIterator::new([0x8c00aaab0000aaab, 0x396c8c005555e156]); @@ -1359,7 +1367,6 @@ pub mod g2 { } } - #[allow(dead_code)] fn scale_by_cofactor(&self) -> G2 { // G2 cofactor = (x^8 - 4 x^7 + 5 x^6) - (4 x^4 + 6 x^3 - 4 x^2 - 4 x + 13) // 9 // 0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5 From 39c25cd506268088cd6ad700901986273ec70daa Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 10 Oct 2017 01:54:53 -0600 Subject: [PATCH 085/140] Bump version to 0.13.0 --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bac829e..746af9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.12.0" +version = "0.13.0" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" diff --git a/README.md b/README.md index 9b3ae6f..d9ef0b4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ If you're using a supported platform and the nightly Rust compiler, you can enab ```toml [dependencies.pairing] -version = "0.12" +version = "0.13" features = ["u128-support"] ``` From 342b94c76bf8257e14bcf94914a5eea33d5de4e5 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 12 Oct 2017 11:20:57 -0600 Subject: [PATCH 086/140] Use "1" for the byteorder crate's version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bac829e..6e99add 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" -byteorder = "1.1.0" +byteorder = "1" clippy = { version = "0.0.165", optional = true } [features] From 471db6ab27cca71ba372f579fc3f8046e07e8343 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 12 Nov 2017 16:19:08 -0700 Subject: [PATCH 087/140] Enforce that Fr of Engine is the scalar for curve points, for simpler downstream abstractions. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c6d5536..18cc288 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ pub trait CurveProjective: PartialEq + rand::Rand + 'static { - type Engine: Engine; + type Engine: Engine<Fr=Self::Scalar>; type Scalar: PrimeField; type Base: SqrtField; type Affine: CurveAffine<Projective=Self, Scalar=Self::Scalar>; @@ -166,7 +166,7 @@ pub trait CurveAffine: Copy + Eq + 'static { - type Engine: Engine; + type Engine: Engine<Fr=Self::Scalar>; type Scalar: PrimeField; type Base: SqrtField; type Projective: CurveProjective<Affine=Self, Scalar=Self::Scalar>; From f32cb409292416e4b4f02363a63790541affea6c Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 20 Nov 2017 12:22:51 -0700 Subject: [PATCH 088/140] Engine should always be 'static, for flexibility in downstream code. --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c6d5536..f58df58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,8 @@ use std::io::{self, Read, Write}; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are /// of prime order `r`, and are equipped with a bilinear pairing function. -pub trait Engine: Sized { +pub trait Engine: Sized + 'static +{ /// This is the scalar field of the G1/G2 groups. type Fr: PrimeField; From 4b366a143dcc45ff031a261f7b21c6115f51be36 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 20 Nov 2017 23:20:11 -0700 Subject: [PATCH 089/140] Ensure `Engine`'s are always Clone. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f58df58..c695f3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ use std::io::{self, Read, Write}; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are /// of prime order `r`, and are equipped with a bilinear pairing function. -pub trait Engine: Sized + 'static +pub trait Engine: Sized + 'static + Clone { /// This is the scalar field of the G1/G2 groups. type Fr: PrimeField; From 4a1ac947995f9e38b783e0b987cdcb391ed4c9a8 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 20 Nov 2017 23:53:58 -0700 Subject: [PATCH 090/140] Implement `Clone` for Bls12. --- src/bls12_381/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 341df67..9213b88 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -21,7 +21,7 @@ use super::{Engine, CurveAffine, Field, BitIterator}; const BLS_X: u64 = 0xd201000000010000; const BLS_X_IS_NEGATIVE: bool = true; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Bls12; impl Engine for Bls12 { From aa0cc06e5e6a06228d0e83c9ca45e1abe7c1614a Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sat, 25 Nov 2017 20:57:10 -0700 Subject: [PATCH 091/140] Scalar field should be guaranteed to be SqrtField. --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4b81613..29c82be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ use std::io::{self, Read, Write}; pub trait Engine: Sized + 'static + Clone { /// This is the scalar field of the G1/G2 groups. - type Fr: PrimeField; + type Fr: PrimeField + SqrtField; /// The projective representation of an element in G1. type G1: CurveProjective<Engine=Self, Base=Self::Fq, Scalar=Self::Fr, Affine=Self::G1Affine> + From<Self::G1Affine>; @@ -99,7 +99,7 @@ pub trait CurveProjective: PartialEq + 'static { type Engine: Engine<Fr=Self::Scalar>; - type Scalar: PrimeField; + type Scalar: PrimeField + SqrtField; type Base: SqrtField; type Affine: CurveAffine<Projective=Self, Scalar=Self::Scalar>; @@ -168,7 +168,7 @@ pub trait CurveAffine: Copy + 'static { type Engine: Engine<Fr=Self::Scalar>; - type Scalar: PrimeField; + type Scalar: PrimeField + SqrtField; type Base: SqrtField; type Projective: CurveProjective<Affine=Self, Scalar=Self::Scalar>; type Prepared: Clone + Send + Sync + 'static; From 566f1004801ae28af3a22c400db41c6de5d9859f Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sat, 25 Nov 2017 21:01:55 -0700 Subject: [PATCH 092/140] Release of 0.13.1. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7c85129..9e7cef6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.13.0" +version = "0.13.1" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" From bb1ced0bd7a1e73ce6123fbb1625d14f915069c0 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sat, 25 Nov 2017 21:32:14 -0700 Subject: [PATCH 093/140] Update clippy version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9e7cef6..f0fe6aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1" -clippy = { version = "0.0.165", optional = true } +clippy = { version = "0.0.174", optional = true } [features] unstable-features = [] From b8394bf14dca69c2105cf7d34f1a76dd78db283e Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 26 Nov 2017 03:09:06 -0700 Subject: [PATCH 094/140] Fix some comments. --- src/bls12_381/fq.rs | 4 ++-- src/bls12_381/fr.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 6c0b196..749f251 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -18,7 +18,7 @@ const R: FqRepr = FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c7 // R2 = R^2 % q const R2: FqRepr = FqRepr([0xf4df1f341c341746, 0xa76e6a609d104f1, 0x8de5476c4c95b6d5, 0x67eb88a9939d83c0, 0x9a793e85b519952d, 0x11988fe592cae3aa]); -// INV = -(q^{-1} mod q) mod q +// INV = -(q^{-1} mod 2^64) mod 2^64 const INV: u64 = 0x89f3fffcfffcfffd; // GENERATOR = 2 (multiplicative generator of q-1 order, that is also quadratic nonresidue) @@ -823,7 +823,7 @@ impl SqrtField for Fq { // Shank's algorithm for q mod 4 = 3 // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) - // a1 = self^((q - 3) // 2) + // a1 = self^((q - 3) // 4) let mut a1 = self.pow([0xee7fbfffffffeaaa, 0x7aaffffac54ffff, 0xd9cc34a83dac3d89, 0xd91dd2e13ce144af, 0x92c6e9ed90d2eb35, 0x680447a8e5ff9a6]); let mut a0 = a1; a0.square(); diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 629984d..96ef599 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -17,7 +17,7 @@ const R: FrRepr = FrRepr([0x1fffffffe, 0x5884b7fa00034802, 0x998c4fefecbc4ff5, 0 // R2 = R^2 % r const R2: FrRepr = FrRepr([0xc999e990f3f29c6d, 0x2b6cedcb87925c23, 0x5d314967254398f, 0x748d9d99f59ff11]); -// INV = -(r^{-1} mod r) mod r +// INV = -(r^{-1} mod 2^64) mod 2^64 const INV: u64 = 0xfffffffeffffffff; // GENERATOR = 7 (multiplicative generator of r-1 order, that is also quadratic nonresidue) From 04a32fb4435f747572bf5e86b19a6c1a881e8a99 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 4 Dec 2017 21:47:45 -0700 Subject: [PATCH 095/140] Introduce `expose-arith` unstable feature for exposing arithmetic functions downstream. --- Cargo.toml | 3 ++- src/lib.rs | 28 +++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0fe6aa..df9bd52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ byteorder = "1" clippy = { version = "0.0.174", optional = true } [features] -unstable-features = [] +unstable-features = ["expose-arith"] +expose-arith = [] u128-support = [] default = [] diff --git a/src/lib.rs b/src/lib.rs index 29c82be..b61fbc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -613,14 +613,18 @@ fn test_bit_iterator() { assert!(a.next().is_none()); } -use self::arith::*; +#[cfg(not(feature = "expose-arith"))] +use self::arith_impl::*; + +#[cfg(feature = "expose-arith")] +pub use self::arith_impl::*; #[cfg(feature = "u128-support")] -mod arith { +mod arith_impl { /// Calculate a - b - borrow, returning the result and modifying /// the borrow value. #[inline(always)] - pub(crate) fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { + pub fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { let tmp = (1u128 << 64) + u128::from(a) - u128::from(b) - u128::from(*borrow); *borrow = if tmp >> 64 == 0 { 1 } else { 0 }; @@ -631,7 +635,7 @@ mod arith { /// Calculate a + b + carry, returning the sum and modifying the /// carry value. #[inline(always)] - pub(crate) fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { + pub fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { let tmp = u128::from(a) + u128::from(b) + u128::from(*carry); *carry = (tmp >> 64) as u64; @@ -642,7 +646,7 @@ mod arith { /// Calculate a + (b * c) + carry, returning the least significant digit /// and setting carry to the most significant digit. #[inline(always)] - pub(crate) fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { + pub fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { let tmp = (u128::from(a)) + u128::from(b) * u128::from(c) + u128::from(*carry); *carry = (tmp >> 64) as u64; @@ -652,7 +656,7 @@ mod arith { } #[cfg(not(feature = "u128-support"))] -mod arith { +mod arith_impl { #[inline(always)] fn split_u64(i: u64) -> (u64, u64) { (i >> 32, i & 0xFFFFFFFF) @@ -663,8 +667,10 @@ mod arith { (hi << 32) | lo } + /// Calculate a - b - borrow, returning the result and modifying + /// the borrow value. #[inline(always)] - pub(crate) fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { + pub fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { let (a_hi, a_lo) = split_u64(a); let (b_hi, b_lo) = split_u64(b); let (b, r0) = split_u64((1 << 32) + a_lo - b_lo - *borrow); @@ -675,8 +681,10 @@ mod arith { combine_u64(r1, r0) } + /// Calculate a + b + carry, returning the sum and modifying the + /// carry value. #[inline(always)] - pub(crate) fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { + pub fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { let (a_hi, a_lo) = split_u64(a); let (b_hi, b_lo) = split_u64(b); let (carry_hi, carry_lo) = split_u64(*carry); @@ -689,8 +697,10 @@ mod arith { combine_u64(r1, r0) } + /// Calculate a + (b * c) + carry, returning the least significant digit + /// and setting carry to the most significant digit. #[inline(always)] - pub(crate) fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { + pub fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { /* [ b_hi | b_lo ] [ c_hi | c_lo ] * From fb679470dbbdf4cf6931b37d0da17a29947bd5e8 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Mon, 4 Dec 2017 21:48:22 -0700 Subject: [PATCH 096/140] Bump version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index df9bd52..80d0b05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.13.1" +version = "0.13.2" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" From 23381742446a9c97ef8d027d3dcbd7fec366ed92 Mon Sep 17 00:00:00 2001 From: Jason Davies <jason@jasondavies.com> Date: Tue, 13 Feb 2018 15:51:16 +0000 Subject: [PATCH 097/140] Update "rand" dependency to 0.4. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 80d0b05..50b35fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://github.com/ebfull/pairing" repository = "https://github.com/ebfull/pairing" [dependencies] -rand = "0.3" +rand = "0.4" byteorder = "1" clippy = { version = "0.0.174", optional = true } From bce9f5d639dc5d45435435674abbdb6dc1417d2f Mon Sep 17 00:00:00 2001 From: Jason Davies <jason@jasondavies.com> Date: Tue, 13 Feb 2018 16:02:30 +0000 Subject: [PATCH 098/140] Update clippy and fix code indentation. (The code indentation issue was not caught by clippy -- I noticed it by chance!) --- Cargo.toml | 2 +- src/bls12_381/fr.rs | 2 +- src/lib.rs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80d0b05..bfd5723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.3" byteorder = "1" -clippy = { version = "0.0.174", optional = true } +clippy = { version = "0.0.186", optional = true } [features] unstable-features = ["expose-arith"] diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 96ef599..90961de 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -573,7 +573,7 @@ impl SqrtField for Fr { let mut m = S; while t != Self::one() { - let mut i = 1; + let mut i = 1; { let mut t2i = t; t2i.square(); diff --git a/src/lib.rs b/src/lib.rs index b61fbc7..c165a74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ #![cfg_attr(feature = "clippy", allow(inline_always))] #![cfg_attr(feature = "clippy", allow(too_many_arguments))] #![cfg_attr(feature = "clippy", allow(unreadable_literal))] +#![cfg_attr(feature = "clippy", allow(many_single_char_names))] #![cfg_attr(feature = "clippy", allow(new_without_default_derive))] // Force public structures to implement Debug From a0fcf717c82a5d1587f36a9d309b1b2de4b4a3b8 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 13 Feb 2018 16:41:10 -0700 Subject: [PATCH 099/140] add_nocarry and sub_noborrow should no longer return anything. --- src/bls12_381/fq.rs | 26 ++++++++------------------ src/bls12_381/fr.rs | 26 ++++++++------------------ src/lib.rs | 8 ++++---- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 749f251..ffb2356 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -364,25 +364,21 @@ impl PrimeFieldRepr for FqRepr { } #[inline(always)] - fn add_nocarry(&mut self, other: &FqRepr) -> bool { + fn add_nocarry(&mut self, other: &FqRepr) { let mut carry = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { *a = ::adc(*a, *b, &mut carry); } - - carry != 0 } #[inline(always)] - fn sub_noborrow(&mut self, other: &FqRepr) -> bool { + fn sub_noborrow(&mut self, other: &FqRepr) { let mut borrow = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { *a = ::sbb(*a, *b, &mut borrow); } - - borrow != 0 } } @@ -1067,13 +1063,10 @@ fn test_fq_repr_sub_noborrow() { assert_eq!(csub_ab, csub_ba); } - // Subtracting q+1 from q should produce a borrow + // Subtracting q+1 from q should produce -1 (mod 2**384) let mut qplusone = FqRepr([0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); - assert!(qplusone.sub_noborrow(&FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]))); - - // Subtracting x from x should produce no borrow - let mut x = FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); - assert!(!x.sub_noborrow(&FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]))) + qplusone.sub_noborrow(&FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])); + assert_eq!(qplusone, FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])); } #[test] @@ -1126,13 +1119,10 @@ fn test_fq_repr_add_nocarry() { assert_eq!(abc, cba); } - // Adding 1 to (2^384 - 1) should produce a carry + // Adding 1 to (2^384 - 1) should produce zero let mut x = FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); - assert!(x.add_nocarry(&FqRepr::from(1))); - - // Adding 1 to q should not produce a carry - let mut x = FqRepr([0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); - assert!(!x.add_nocarry(&FqRepr::from(1))); + x.add_nocarry(&FqRepr::from(1)); + assert!(x.is_zero()); } #[test] diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 96ef599..9966a67 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -201,25 +201,21 @@ impl PrimeFieldRepr for FrRepr { } #[inline(always)] - fn add_nocarry(&mut self, other: &FrRepr) -> bool { + fn add_nocarry(&mut self, other: &FrRepr) { let mut carry = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { *a = ::adc(*a, *b, &mut carry); } - - carry != 0 } #[inline(always)] - fn sub_noborrow(&mut self, other: &FrRepr) -> bool { + fn sub_noborrow(&mut self, other: &FrRepr) { let mut borrow = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { *a = ::sbb(*a, *b, &mut borrow); } - - borrow != 0 } } @@ -772,13 +768,10 @@ fn test_fr_repr_sub_noborrow() { assert_eq!(csub_ab, csub_ba); } - // Subtracting r+1 from r should produce a borrow + // Subtracting r+1 from r should produce -1 (mod 2**256) let mut qplusone = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(qplusone.sub_noborrow(&FrRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))); - - // Subtracting x from x should produce no borrow - let mut x = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(!x.sub_noborrow(&FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))) + qplusone.sub_noborrow(&FrRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48])); + assert_eq!(qplusone, FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])); } #[test] @@ -842,13 +835,10 @@ fn test_fr_repr_add_nocarry() { assert_eq!(abc, cba); } - // Adding 1 to (2^256 - 1) should produce a carry + // Adding 1 to (2^256 - 1) should produce zero let mut x = FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); - assert!(x.add_nocarry(&FrRepr::from(1))); - - // Adding 1 to r should not produce a carry - let mut x = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(!x.add_nocarry(&FrRepr::from(1))); + x.add_nocarry(&FrRepr::from(1)); + assert!(x.is_zero()); } #[test] diff --git a/src/lib.rs b/src/lib.rs index b61fbc7..dab1277 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,11 +352,11 @@ pub trait PrimeFieldRepr: Sized + AsMut<[u64]> + From<u64> { - /// Subtract another represetation from this one, returning the borrow bit. - fn sub_noborrow(&mut self, other: &Self) -> bool; + /// Subtract another represetation from this one. + fn sub_noborrow(&mut self, other: &Self); - /// Add another representation to this one, returning the carry bit. - fn add_nocarry(&mut self, other: &Self) -> bool; + /// Add another representation to this one. + fn add_nocarry(&mut self, other: &Self); /// Compute the number of bits needed to encode this number. Always a /// multiple of 64. From b971bdedda504b40fa3ae5859940ccf8d81401bf Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 13 Feb 2018 17:07:10 -0700 Subject: [PATCH 100/140] Rename divn/muln to shr/shl. --- src/bls12_381/fq.rs | 16 ++++++++-------- src/bls12_381/fr.rs | 16 ++++++++-------- src/lib.rs | 4 ++-- src/tests/repr.rs | 12 ++++++------ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index ffb2356..81eb256 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -276,7 +276,7 @@ impl PrimeFieldRepr for FqRepr { } #[inline(always)] - fn divn(&mut self, mut n: u32) { + fn shr(&mut self, mut n: u32) { if n >= 64 * 6 { *self = Self::from(0); return; @@ -324,7 +324,7 @@ impl PrimeFieldRepr for FqRepr { } #[inline(always)] - fn muln(&mut self, mut n: u32) { + fn shl(&mut self, mut n: u32) { if n >= 64 * 6 { *self = Self::from(0); return; @@ -965,29 +965,29 @@ fn test_fq_repr_div2() { } #[test] -fn test_fq_repr_divn() { +fn test_fq_repr_shr() { let mut a = FqRepr([0xaa5cdd6172847ffd, 0x43242c06aed55287, 0x9ddd5b312f3dd104, 0xc5541fd48046b7e7, 0x16080cf4071e0b05, 0x1225f2901aea514e]); - a.divn(0); + a.shr(0); assert_eq!( a, FqRepr([0xaa5cdd6172847ffd, 0x43242c06aed55287, 0x9ddd5b312f3dd104, 0xc5541fd48046b7e7, 0x16080cf4071e0b05, 0x1225f2901aea514e]) ); - a.divn(1); + a.shr(1); assert_eq!( a, FqRepr([0xd52e6eb0b9423ffe, 0x21921603576aa943, 0xceeead98979ee882, 0xe2aa0fea40235bf3, 0xb04067a038f0582, 0x912f9480d7528a7]) ); - a.divn(50); + a.shr(50); assert_eq!( a, FqRepr([0x8580d5daaa50f54b, 0xab6625e7ba208864, 0x83fa9008d6fcf3bb, 0x19e80e3c160b8aa, 0xbe52035d4a29c2c1, 0x244]) ); - a.divn(130); + a.shr(130); assert_eq!( a, FqRepr([0xa0fea40235bf3cee, 0x4067a038f0582e2a, 0x2f9480d7528a70b0, 0x91, 0x0, 0x0]) ); - a.divn(64); + a.shr(64); assert_eq!( a, FqRepr([0x4067a038f0582e2a, 0x2f9480d7528a70b0, 0x91, 0x0, 0x0, 0x0]) diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 9966a67..f6d28b9 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -113,7 +113,7 @@ impl PrimeFieldRepr for FrRepr { } #[inline(always)] - fn divn(&mut self, mut n: u32) { + fn shr(&mut self, mut n: u32) { if n >= 64 * 4 { *self = Self::from(0); return; @@ -161,7 +161,7 @@ impl PrimeFieldRepr for FrRepr { } #[inline(always)] - fn muln(&mut self, mut n: u32) { + fn shl(&mut self, mut n: u32) { if n >= 64 * 4 { *self = Self::from(0); return; @@ -670,29 +670,29 @@ fn test_fr_repr_div2() { } #[test] -fn test_fr_repr_divn() { +fn test_fr_repr_shr() { let mut a = FrRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]); - a.divn(0); + a.shr(0); assert_eq!( a, FrRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]) ); - a.divn(1); + a.shr(1); assert_eq!( a, FrRepr([0xd99fdd762415141f, 0xccbef069d44659ef, 0xcd7b16954d072a92, 0x1b001d5846f386d0]) ); - a.divn(50); + a.shr(50); assert_eq!( a, FrRepr([0xbc1a7511967bf667, 0xc5a55341caa4b32f, 0x75611bce1b4335e, 0x6c0]) ); - a.divn(130); + a.shr(130); assert_eq!( a, FrRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0]) ); - a.divn(64); + a.shr(64); assert_eq!( a, FrRepr([0x1b0, 0x0, 0x0, 0x0]) diff --git a/src/lib.rs b/src/lib.rs index dab1277..8d8f473 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -376,14 +376,14 @@ pub trait PrimeFieldRepr: Sized + fn div2(&mut self); /// Performs a rightwise bitshift of this number by some amount. - fn divn(&mut self, amt: u32); + fn shr(&mut self, amt: u32); /// Performs a leftwise bitshift of this number, effectively multiplying /// it by 2. Overflow is ignored. fn mul2(&mut self); /// Performs a leftwise bitshift of this number by some amount. - fn muln(&mut self, amt: u32); + fn shl(&mut self, amt: u32); /// Writes this `PrimeFieldRepr` as a big endian integer. Always writes /// `(num_bits` / 8) bytes. diff --git a/src/tests/repr.rs b/src/tests/repr.rs index 9403df0..ed07eae 100644 --- a/src/tests/repr.rs +++ b/src/tests/repr.rs @@ -3,8 +3,8 @@ use ::{PrimeFieldRepr}; pub fn random_repr_tests<R: PrimeFieldRepr>() { random_encoding_tests::<R>(); - random_muln_tests::<R>(); - random_divn_tests::<R>(); + random_shl_tests::<R>(); + random_shr_tests::<R>(); } fn random_encoding_tests<R: PrimeFieldRepr>() { @@ -22,7 +22,7 @@ fn random_encoding_tests<R: PrimeFieldRepr>() { } } -fn random_muln_tests<R: PrimeFieldRepr>() { +fn random_shl_tests<R: PrimeFieldRepr>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); for _ in 0..100 { @@ -36,14 +36,14 @@ fn random_muln_tests<R: PrimeFieldRepr>() { r1.mul2(); } - r2.muln(shift); + r2.shl(shift); assert_eq!(r1, r2); } } } -fn random_divn_tests<R: PrimeFieldRepr>() { +fn random_shr_tests<R: PrimeFieldRepr>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); for _ in 0..100 { @@ -57,7 +57,7 @@ fn random_divn_tests<R: PrimeFieldRepr>() { r1.div2(); } - r2.divn(shift); + r2.shr(shift); assert_eq!(r1, r2); } From 541fda758008330cc5c0093f9f7a8edb029e3eb9 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Tue, 13 Feb 2018 17:14:17 -0700 Subject: [PATCH 101/140] Fix misleading comments on write_be and read_be. --- src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8d8f473..dba5d10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,8 +385,7 @@ pub trait PrimeFieldRepr: Sized + /// Performs a leftwise bitshift of this number by some amount. fn shl(&mut self, amt: u32); - /// Writes this `PrimeFieldRepr` as a big endian integer. Always writes - /// `(num_bits` / 8) bytes. + /// Writes this `PrimeFieldRepr` as a big endian integer. fn write_be<W: Write>(&self, mut writer: W) -> io::Result<()> { use byteorder::{WriteBytesExt, BigEndian}; @@ -397,8 +396,7 @@ pub trait PrimeFieldRepr: Sized + Ok(()) } - /// Reads a big endian integer occupying (`num_bits` / 8) bytes into this - /// representation. + /// Reads a big endian integer into this representation. fn read_be<R: Read>(&mut self, mut reader: R) -> io::Result<()> { use byteorder::{ReadBytesExt, BigEndian}; From 53083f4290c3fd4967285677e35ebd24d2f4f4ae Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Wed, 21 Feb 2018 11:08:58 -0700 Subject: [PATCH 102/140] Apply rustfmt to the codebase. --- src/bls12_381/ec.rs | 740 ++++++++++++--- src/bls12_381/fq.rs | 1798 ++++++++++++++++++++++++++++++++---- src/bls12_381/fq12.rs | 47 +- src/bls12_381/fq2.rs | 679 +++++++++++--- src/bls12_381/fq6.rs | 43 +- src/bls12_381/fr.rs | 591 +++++++++--- src/bls12_381/mod.rs | 47 +- src/bls12_381/tests/mod.rs | 37 +- src/lib.rs | 221 ++--- src/tests/curve.rs | 25 +- src/tests/engine.rs | 14 +- src/tests/field.rs | 4 +- src/tests/repr.rs | 6 +- src/wnaf.rs | 60 +- 14 files changed, 3485 insertions(+), 827 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index fa1419d..5f651ca 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -624,10 +624,21 @@ pub mod g1 { use rand::{Rand, Rng}; use std::fmt; use super::g2::G2Affine; - use super::super::{Bls12, Fq, Fr, FrRepr, FqRepr, Fq12}; - use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError, Engine}; + use super::super::{Bls12, Fq, Fq12, FqRepr, Fr, FrRepr}; + use {BitIterator, CurveAffine, CurveProjective, EncodedPoint, Engine, Field, + GroupDecodingError, PrimeField, PrimeFieldRepr, SqrtField}; - curve_impl!("G1", G1, G1Affine, G1Prepared, Fq, Fr, G1Uncompressed, G1Compressed, G2Affine); + curve_impl!( + "G1", + G1, + G1Affine, + G1Prepared, + Fq, + Fr, + G1Uncompressed, + G1Compressed, + G2Affine + ); #[derive(Copy, Clone)] pub struct G1Uncompressed([u8; 96]); @@ -653,8 +664,12 @@ pub mod g1 { impl EncodedPoint for G1Uncompressed { type Affine = G1Affine; - fn empty() -> Self { G1Uncompressed([0; 96]) } - fn size() -> usize { 96 } + fn empty() -> Self { + G1Uncompressed([0; 96]) + } + fn size() -> usize { + 96 + } fn into_affine(&self) -> Result<G1Affine, GroupDecodingError> { let affine = self.into_affine_unchecked()?; @@ -672,7 +687,7 @@ pub mod g1 { if copy[0] & (1 << 7) != 0 { // Distinguisher bit is set, but this should be uncompressed! - return Err(GroupDecodingError::UnexpectedCompressionMode) + return Err(GroupDecodingError::UnexpectedCompressionMode); } if copy[0] & (1 << 6) != 0 { @@ -690,7 +705,7 @@ pub mod g1 { if copy[0] & (1 << 5) != 0 { // The bit indicating the y-coordinate should be lexicographically // largest is set, but this is an uncompressed element. - return Err(GroupDecodingError::UnexpectedInformation) + return Err(GroupDecodingError::UnexpectedInformation); } // Unset the three most significant bits. @@ -707,9 +722,13 @@ pub mod g1 { } Ok(G1Affine { - x: Fq::from_repr(x).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?, - y: Fq::from_repr(y).map_err(|e| GroupDecodingError::CoordinateDecodingError("y coordinate", e))?, - infinity: false + x: Fq::from_repr(x).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate", e) + })?, + y: Fq::from_repr(y).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("y coordinate", e) + })?, + infinity: false, }) } } @@ -755,8 +774,12 @@ pub mod g1 { impl EncodedPoint for G1Compressed { type Affine = G1Affine; - fn empty() -> Self { G1Compressed([0; 48]) } - fn size() -> usize { 48 } + fn empty() -> Self { + G1Compressed([0; 48]) + } + fn size() -> usize { + 48 + } fn into_affine(&self) -> Result<G1Affine, GroupDecodingError> { let affine = self.into_affine_unchecked()?; @@ -774,7 +797,7 @@ pub mod g1 { if copy[0] & (1 << 7) == 0 { // Distinguisher bit isn't set. - return Err(GroupDecodingError::UnexpectedCompressionMode) + return Err(GroupDecodingError::UnexpectedCompressionMode); } if copy[0] & (1 << 6) != 0 { @@ -805,7 +828,8 @@ pub mod g1 { } // Interpret as Fq element. - let x = Fq::from_repr(x).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?; + let x = Fq::from_repr(x) + .map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?; G1Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve) } @@ -852,7 +876,7 @@ pub mod g1 { G1Affine { x: super::super::fq::G1_GENERATOR_X, y: super::super::fq::G1_GENERATOR_Y, - infinity: false + infinity: false, } } @@ -866,8 +890,7 @@ pub mod g1 { } impl G1 { - fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize - { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize { let num_bits = scalar.num_bits() as usize; if num_bits >= 130 { @@ -879,16 +902,16 @@ pub mod g1 { } } - fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize - { - const RECOMMENDATIONS: [usize; 12] = [1, 3, 7, 20, 43, 120, 273, 563, 1630, 3128, 7933, 62569]; + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + const RECOMMENDATIONS: [usize; 12] = + [1, 3, 7, 20, 43, 120, 273, 563, 1630, 3128, 7933, 62569]; let mut ret = 4; for r in &RECOMMENDATIONS { if num_scalars > *r { ret += 1; } else { - break + break; } } @@ -911,7 +934,7 @@ pub mod g1 { #[test] fn g1_generator() { - use ::SqrtField; + use SqrtField; let mut x = Fq::zero(); let mut i = 0; @@ -931,7 +954,7 @@ pub mod g1 { let p = G1Affine { x: x, y: if yrepr < negyrepr { y } else { negy }, - infinity: false + infinity: false, }; assert!(!p.is_in_correct_subgroup_assuming_on_curve()); @@ -957,9 +980,23 @@ pub mod g1 { // Reject point on isomorphic twist (b = 24) { let p = G1Affine { - x: Fq::from_repr(FqRepr([0xc58d887b66c035dc, 0x10cbfd301d553822, 0xaf23e064f1131ee5, 0x9fe83b1b4a5d648d, 0xf583cc5a508f6a40, 0xc3ad2aefde0bb13])).unwrap(), - y: Fq::from_repr(FqRepr([0x60aa6f9552f03aae, 0xecd01d5181300d35, 0x8af1cdb8aa8ce167, 0xe760f57922998c9d, 0x953703f5795a39e5, 0xfe3ae0922df702c])).unwrap(), - infinity: false + x: Fq::from_repr(FqRepr([ + 0xc58d887b66c035dc, + 0x10cbfd301d553822, + 0xaf23e064f1131ee5, + 0x9fe83b1b4a5d648d, + 0xf583cc5a508f6a40, + 0xc3ad2aefde0bb13, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0x60aa6f9552f03aae, + 0xecd01d5181300d35, + 0x8af1cdb8aa8ce167, + 0xe760f57922998c9d, + 0x953703f5795a39e5, + 0xfe3ae0922df702c, + ])).unwrap(), + infinity: false, }; assert!(!p.is_on_curve()); assert!(p.is_in_correct_subgroup_assuming_on_curve()); @@ -968,9 +1005,23 @@ pub mod g1 { // Reject point on a twist (b = 3) { let p = G1Affine { - x: Fq::from_repr(FqRepr([0xee6adf83511e15f5, 0x92ddd328f27a4ba6, 0xe305bd1ac65adba7, 0xea034ee2928b30a8, 0xbd8833dc7c79a7f7, 0xe45c9f0c0438675])).unwrap(), - y: Fq::from_repr(FqRepr([0x3b450eb1ab7b5dad, 0xa65cb81e975e8675, 0xaa548682b21726e5, 0x753ddf21a2601d20, 0x532d0b640bd3ff8b, 0x118d2c543f031102])).unwrap(), - infinity: false + x: Fq::from_repr(FqRepr([ + 0xee6adf83511e15f5, + 0x92ddd328f27a4ba6, + 0xe305bd1ac65adba7, + 0xea034ee2928b30a8, + 0xbd8833dc7c79a7f7, + 0xe45c9f0c0438675, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0x3b450eb1ab7b5dad, + 0xa65cb81e975e8675, + 0xaa548682b21726e5, + 0x753ddf21a2601d20, + 0x532d0b640bd3ff8b, + 0x118d2c543f031102, + ])).unwrap(), + infinity: false, }; assert!(!p.is_on_curve()); assert!(!p.is_in_correct_subgroup_assuming_on_curve()); @@ -980,9 +1031,23 @@ pub mod g1 { // There is only one r-order subgroup, as r does not divide the cofactor. { let p = G1Affine { - x: Fq::from_repr(FqRepr([0x76e1c971c6db8fe8, 0xe37e1a610eff2f79, 0x88ae9c499f46f0c0, 0xf35de9ce0d6b4e84, 0x265bddd23d1dec54, 0x12a8778088458308])).unwrap(), - y: Fq::from_repr(FqRepr([0x8a22defa0d526256, 0xc57ca55456fcb9ae, 0x1ba194e89bab2610, 0x921beef89d4f29df, 0x5b6fda44ad85fa78, 0xed74ab9f302cbe0])).unwrap(), - infinity: false + x: Fq::from_repr(FqRepr([ + 0x76e1c971c6db8fe8, + 0xe37e1a610eff2f79, + 0x88ae9c499f46f0c0, + 0xf35de9ce0d6b4e84, + 0x265bddd23d1dec54, + 0x12a8778088458308, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0x8a22defa0d526256, + 0xc57ca55456fcb9ae, + 0x1ba194e89bab2610, + 0x921beef89d4f29df, + 0x5b6fda44ad85fa78, + 0xed74ab9f302cbe0, + ])).unwrap(), + infinity: false, }; assert!(p.is_on_curve()); assert!(!p.is_in_correct_subgroup_assuming_on_curve()); @@ -992,43 +1057,119 @@ pub mod g1 { #[test] fn test_g1_addition_correctness() { let mut p = G1 { - x: Fq::from_repr(FqRepr([0x47fd1f891d6e8bbf, 0x79a3b0448f31a2aa, 0x81f3339e5f9968f, 0x485e77d50a5df10d, 0x4c6fcac4b55fd479, 0x86ed4d9906fb064])).unwrap(), - y: Fq::from_repr(FqRepr([0xd25ee6461538c65, 0x9f3bbb2ecd3719b9, 0xa06fd3f1e540910d, 0xcefca68333c35288, 0x570c8005f8573fa6, 0x152ca696fe034442])).unwrap(), - z: Fq::one() + x: Fq::from_repr(FqRepr([ + 0x47fd1f891d6e8bbf, + 0x79a3b0448f31a2aa, + 0x81f3339e5f9968f, + 0x485e77d50a5df10d, + 0x4c6fcac4b55fd479, + 0x86ed4d9906fb064, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0xd25ee6461538c65, + 0x9f3bbb2ecd3719b9, + 0xa06fd3f1e540910d, + 0xcefca68333c35288, + 0x570c8005f8573fa6, + 0x152ca696fe034442, + ])).unwrap(), + z: Fq::one(), }; p.add_assign(&G1 { - x: Fq::from_repr(FqRepr([0xeec78f3096213cbf, 0xa12beb1fea1056e6, 0xc286c0211c40dd54, 0x5f44314ec5e3fb03, 0x24e8538737c6e675, 0x8abd623a594fba8])).unwrap(), - y: Fq::from_repr(FqRepr([0x6b0528f088bb7044, 0x2fdeb5c82917ff9e, 0x9a5181f2fac226ad, 0xd65104c6f95a872a, 0x1f2998a5a9c61253, 0xe74846154a9e44])).unwrap(), - z: Fq::one() + x: Fq::from_repr(FqRepr([ + 0xeec78f3096213cbf, + 0xa12beb1fea1056e6, + 0xc286c0211c40dd54, + 0x5f44314ec5e3fb03, + 0x24e8538737c6e675, + 0x8abd623a594fba8, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0x6b0528f088bb7044, + 0x2fdeb5c82917ff9e, + 0x9a5181f2fac226ad, + 0xd65104c6f95a872a, + 0x1f2998a5a9c61253, + 0xe74846154a9e44, + ])).unwrap(), + z: Fq::one(), }); let p = G1Affine::from(p); - assert_eq!(p, G1Affine { - x: Fq::from_repr(FqRepr([0x6dd3098f22235df, 0xe865d221c8090260, 0xeb96bb99fa50779f, 0xc4f9a52a428e23bb, 0xd178b28dd4f407ef, 0x17fb8905e9183c69])).unwrap(), - y: Fq::from_repr(FqRepr([0xd0de9d65292b7710, 0xf6a05f2bcf1d9ca7, 0x1040e27012f20b64, 0xeec8d1a5b7466c58, 0x4bc362649dce6376, 0x430cbdc5455b00a])).unwrap(), - infinity: false - }); + assert_eq!( + p, + G1Affine { + x: Fq::from_repr(FqRepr([ + 0x6dd3098f22235df, + 0xe865d221c8090260, + 0xeb96bb99fa50779f, + 0xc4f9a52a428e23bb, + 0xd178b28dd4f407ef, + 0x17fb8905e9183c69 + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0xd0de9d65292b7710, + 0xf6a05f2bcf1d9ca7, + 0x1040e27012f20b64, + 0xeec8d1a5b7466c58, + 0x4bc362649dce6376, + 0x430cbdc5455b00a + ])).unwrap(), + infinity: false, + } + ); } #[test] fn test_g1_doubling_correctness() { let mut p = G1 { - x: Fq::from_repr(FqRepr([0x47fd1f891d6e8bbf, 0x79a3b0448f31a2aa, 0x81f3339e5f9968f, 0x485e77d50a5df10d, 0x4c6fcac4b55fd479, 0x86ed4d9906fb064])).unwrap(), - y: Fq::from_repr(FqRepr([0xd25ee6461538c65, 0x9f3bbb2ecd3719b9, 0xa06fd3f1e540910d, 0xcefca68333c35288, 0x570c8005f8573fa6, 0x152ca696fe034442])).unwrap(), - z: Fq::one() + x: Fq::from_repr(FqRepr([ + 0x47fd1f891d6e8bbf, + 0x79a3b0448f31a2aa, + 0x81f3339e5f9968f, + 0x485e77d50a5df10d, + 0x4c6fcac4b55fd479, + 0x86ed4d9906fb064, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0xd25ee6461538c65, + 0x9f3bbb2ecd3719b9, + 0xa06fd3f1e540910d, + 0xcefca68333c35288, + 0x570c8005f8573fa6, + 0x152ca696fe034442, + ])).unwrap(), + z: Fq::one(), }; p.double(); let p = G1Affine::from(p); - assert_eq!(p, G1Affine { - x: Fq::from_repr(FqRepr([0xf939ddfe0ead7018, 0x3b03942e732aecb, 0xce0e9c38fdb11851, 0x4b914c16687dcde0, 0x66c8baf177d20533, 0xaf960cff3d83833])).unwrap(), - y: Fq::from_repr(FqRepr([0x3f0675695f5177a8, 0x2b6d82ae178a1ba0, 0x9096380dd8e51b11, 0x1771a65b60572f4e, 0x8b547c1313b27555, 0x135075589a687b1e])).unwrap(), - infinity: false - }); + assert_eq!( + p, + G1Affine { + x: Fq::from_repr(FqRepr([ + 0xf939ddfe0ead7018, + 0x3b03942e732aecb, + 0xce0e9c38fdb11851, + 0x4b914c16687dcde0, + 0x66c8baf177d20533, + 0xaf960cff3d83833 + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0x3f0675695f5177a8, + 0x2b6d82ae178a1ba0, + 0x9096380dd8e51b11, + 0x1771a65b60572f4e, + 0x8b547c1313b27555, + 0x135075589a687b1e + ])).unwrap(), + infinity: false, + } + ); } #[test] @@ -1041,24 +1182,66 @@ pub mod g1 { // y = 2291134451313223670499022936083127939567618746216464377735567679979105510603740918204953301371880765657042046687078 let a = G1Affine { - x: Fq::from_repr(FqRepr([0xea431f2cc38fc94d, 0x3ad2354a07f5472b, 0xfe669f133f16c26a, 0x71ffa8021531705, 0x7418d484386d267, 0xd5108d8ff1fbd6])).unwrap(), - y: Fq::from_repr(FqRepr([0xa776ccbfe9981766, 0x255632964ff40f4a, 0xc09744e650b00499, 0x520f74773e74c8c3, 0x484c8fc982008f0, 0xee2c3d922008cc6])).unwrap(), - infinity: false + x: Fq::from_repr(FqRepr([ + 0xea431f2cc38fc94d, + 0x3ad2354a07f5472b, + 0xfe669f133f16c26a, + 0x71ffa8021531705, + 0x7418d484386d267, + 0xd5108d8ff1fbd6, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0xa776ccbfe9981766, + 0x255632964ff40f4a, + 0xc09744e650b00499, + 0x520f74773e74c8c3, + 0x484c8fc982008f0, + 0xee2c3d922008cc6, + ])).unwrap(), + infinity: false, }; let b = G1Affine { - x: Fq::from_repr(FqRepr([0xe06cdb156b6356b6, 0xd9040b2d75448ad9, 0xe702f14bb0e2aca5, 0xc6e05201e5f83991, 0xf7c75910816f207c, 0x18d4043e78103106])).unwrap(), - y: Fq::from_repr(FqRepr([0xa776ccbfe9981766, 0x255632964ff40f4a, 0xc09744e650b00499, 0x520f74773e74c8c3, 0x484c8fc982008f0, 0xee2c3d922008cc6])).unwrap(), - infinity: false + x: Fq::from_repr(FqRepr([ + 0xe06cdb156b6356b6, + 0xd9040b2d75448ad9, + 0xe702f14bb0e2aca5, + 0xc6e05201e5f83991, + 0xf7c75910816f207c, + 0x18d4043e78103106, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0xa776ccbfe9981766, + 0x255632964ff40f4a, + 0xc09744e650b00499, + 0x520f74773e74c8c3, + 0x484c8fc982008f0, + 0xee2c3d922008cc6, + ])).unwrap(), + infinity: false, }; // Expected // x = 52901198670373960614757979459866672334163627229195745167587898707663026648445040826329033206551534205133090753192 // y = 1711275103908443722918766889652776216989264073722543507596490456144926139887096946237734327757134898380852225872709 let c = G1Affine { - x: Fq::from_repr(FqRepr([0xef4f05bdd10c8aa8, 0xad5bf87341a2df9, 0x81c7424206b78714, 0x9676ff02ec39c227, 0x4c12c15d7e55b9f3, 0x57fd1e317db9bd])).unwrap(), - y: Fq::from_repr(FqRepr([0x1288334016679345, 0xf955cd68615ff0b5, 0xa6998dbaa600f18a, 0x1267d70db51049fb, 0x4696deb9ab2ba3e7, 0xb1e4e11177f59d4])).unwrap(), - infinity: false + x: Fq::from_repr(FqRepr([ + 0xef4f05bdd10c8aa8, + 0xad5bf87341a2df9, + 0x81c7424206b78714, + 0x9676ff02ec39c227, + 0x4c12c15d7e55b9f3, + 0x57fd1e317db9bd, + ])).unwrap(), + y: Fq::from_repr(FqRepr([ + 0x1288334016679345, + 0xf955cd68615ff0b5, + 0xa6998dbaa600f18a, + 0x1267d70db51049fb, + 0x4696deb9ab2ba3e7, + 0xb1e4e11177f59d4, + ])).unwrap(), + infinity: false, }; assert!(a.is_on_curve() && a.is_in_correct_subgroup_assuming_on_curve()); @@ -1085,11 +1268,22 @@ pub mod g1 { pub mod g2 { use rand::{Rand, Rng}; use std::fmt; - use super::super::{Bls12, Fq2, Fr, Fq, FrRepr, FqRepr, Fq12}; + use super::super::{Bls12, Fq, Fq12, Fq2, FqRepr, Fr, FrRepr}; use super::g1::G1Affine; - use ::{CurveProjective, CurveAffine, PrimeField, SqrtField, PrimeFieldRepr, Field, BitIterator, EncodedPoint, GroupDecodingError, Engine}; + use {BitIterator, CurveAffine, CurveProjective, EncodedPoint, Engine, Field, + GroupDecodingError, PrimeField, PrimeFieldRepr, SqrtField}; - curve_impl!("G2", G2, G2Affine, G2Prepared, Fq2, Fr, G2Uncompressed, G2Compressed, G1Affine); + curve_impl!( + "G2", + G2, + G2Affine, + G2Prepared, + Fq2, + Fr, + G2Uncompressed, + G2Compressed, + G1Affine + ); #[derive(Copy, Clone)] pub struct G2Uncompressed([u8; 192]); @@ -1115,8 +1309,12 @@ pub mod g2 { impl EncodedPoint for G2Uncompressed { type Affine = G2Affine; - fn empty() -> Self { G2Uncompressed([0; 192]) } - fn size() -> usize { 192 } + fn empty() -> Self { + G2Uncompressed([0; 192]) + } + fn size() -> usize { + 192 + } fn into_affine(&self) -> Result<G2Affine, GroupDecodingError> { let affine = self.into_affine_unchecked()?; @@ -1134,7 +1332,7 @@ pub mod g2 { if copy[0] & (1 << 7) != 0 { // Distinguisher bit is set, but this should be uncompressed! - return Err(GroupDecodingError::UnexpectedCompressionMode) + return Err(GroupDecodingError::UnexpectedCompressionMode); } if copy[0] & (1 << 6) != 0 { @@ -1152,7 +1350,7 @@ pub mod g2 { if copy[0] & (1 << 5) != 0 { // The bit indicating the y-coordinate should be lexicographically // largest is set, but this is an uncompressed element. - return Err(GroupDecodingError::UnexpectedInformation) + return Err(GroupDecodingError::UnexpectedInformation); } // Unset the three most significant bits. @@ -1174,14 +1372,22 @@ pub mod g2 { Ok(G2Affine { x: Fq2 { - c0: Fq::from_repr(x_c0).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c0)", e))?, - c1: Fq::from_repr(x_c1).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e))?, + c0: Fq::from_repr(x_c0).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate (c0)", e) + })?, + c1: Fq::from_repr(x_c1).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e) + })?, }, y: Fq2 { - c0: Fq::from_repr(y_c0).map_err(|e| GroupDecodingError::CoordinateDecodingError("y coordinate (c0)", e))?, - c1: Fq::from_repr(y_c1).map_err(|e| GroupDecodingError::CoordinateDecodingError("y coordinate (c1)", e))?, + c0: Fq::from_repr(y_c0).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("y coordinate (c0)", e) + })?, + c1: Fq::from_repr(y_c1).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("y coordinate (c1)", e) + })?, }, - infinity: false + infinity: false, }) } } @@ -1229,8 +1435,12 @@ pub mod g2 { impl EncodedPoint for G2Compressed { type Affine = G2Affine; - fn empty() -> Self { G2Compressed([0; 96]) } - fn size() -> usize { 96 } + fn empty() -> Self { + G2Compressed([0; 96]) + } + fn size() -> usize { + 96 + } fn into_affine(&self) -> Result<G2Affine, GroupDecodingError> { let affine = self.into_affine_unchecked()?; @@ -1248,7 +1458,7 @@ pub mod g2 { if copy[0] & (1 << 7) == 0 { // Distinguisher bit isn't set. - return Err(GroupDecodingError::UnexpectedCompressionMode) + return Err(GroupDecodingError::UnexpectedCompressionMode); } if copy[0] & (1 << 6) != 0 { @@ -1282,8 +1492,12 @@ pub mod g2 { // Interpret as Fq element. let x = Fq2 { - c0: Fq::from_repr(x_c0).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c0)", e))?, - c1: Fq::from_repr(x_c1).map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e))? + c0: Fq::from_repr(x_c0).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate (c0)", e) + })?, + c1: Fq::from_repr(x_c1).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e) + })?, }; G2Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve) @@ -1326,29 +1540,36 @@ pub mod g2 { G2Affine { x: Fq2 { c0: super::super::fq::G2_GENERATOR_X_C0, - c1: super::super::fq::G2_GENERATOR_X_C1 + c1: super::super::fq::G2_GENERATOR_X_C1, }, y: Fq2 { c0: super::super::fq::G2_GENERATOR_Y_C0, - c1: super::super::fq::G2_GENERATOR_Y_C1 + c1: super::super::fq::G2_GENERATOR_Y_C1, }, - infinity: false + infinity: false, } } fn get_coeff_b() -> Fq2 { Fq2 { c0: super::super::fq::B_COEFF, - c1: super::super::fq::B_COEFF + c1: super::super::fq::B_COEFF, } } fn scale_by_cofactor(&self) -> G2 { // G2 cofactor = (x^8 - 4 x^7 + 5 x^6) - (4 x^4 + 6 x^3 - 4 x^2 - 4 x + 13) // 9 // 0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5 - let cofactor = BitIterator::new([0xcf1c38e31c7238e5, 0x1616ec6e786f0c70, 0x21537e293a6691ae, - 0xa628f1cb4d9e82ef, 0xa68a205b2e5a7ddf, 0xcd91de4547085aba, - 0x91d50792876a202, 0x5d543a95414e7f1]); + let cofactor = BitIterator::new([ + 0xcf1c38e31c7238e5, + 0x1616ec6e786f0c70, + 0x21537e293a6691ae, + 0xa628f1cb4d9e82ef, + 0xa68a205b2e5a7ddf, + 0xcd91de4547085aba, + 0x91d50792876a202, + 0x5d543a95414e7f1, + ]); self.mul_bits(cofactor) } @@ -1358,8 +1579,7 @@ pub mod g2 { } impl G2 { - fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize - { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize { let num_bits = scalar.num_bits() as usize; if num_bits >= 103 { @@ -1371,16 +1591,16 @@ pub mod g2 { } } - fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize - { - const RECOMMENDATIONS: [usize; 11] = [1, 3, 8, 20, 47, 126, 260, 826, 1501, 4555, 84071]; + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + const RECOMMENDATIONS: [usize; 11] = + [1, 3, 8, 20, 47, 126, 260, 826, 1501, 4555, 84071]; let mut ret = 4; for r in &RECOMMENDATIONS { if num_scalars > *r { ret += 1; } else { - break + break; } } @@ -1391,12 +1611,12 @@ pub mod g2 { #[derive(Clone, Debug)] pub struct G2Prepared { pub(crate) coeffs: Vec<(Fq2, Fq2, Fq2)>, - pub(crate) infinity: bool + pub(crate) infinity: bool, } #[test] fn g2_generator() { - use ::SqrtField; + use SqrtField; let mut x = Fq2::zero(); let mut i = 0; @@ -1414,7 +1634,7 @@ pub mod g2 { let p = G2Affine { x: x, y: if y < negy { y } else { negy }, - infinity: false + infinity: false, }; assert!(!p.is_in_correct_subgroup_assuming_on_curve()); @@ -1441,14 +1661,42 @@ pub mod g2 { { let p = G2Affine { x: Fq2 { - c0: Fq::from_repr(FqRepr([0xa757072d9fa35ba9, 0xae3fb2fb418f6e8a, 0xc1598ec46faa0c7c, 0x7a17a004747e3dbe, 0xcc65406a7c2e5a73, 0x10b8c03d64db4d0c])).unwrap(), - c1: Fq::from_repr(FqRepr([0xd30e70fe2f029778, 0xda30772df0f5212e, 0x5b47a9ff9a233a50, 0xfb777e5b9b568608, 0x789bac1fec71a2b9, 0x1342f02e2da54405])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xa757072d9fa35ba9, + 0xae3fb2fb418f6e8a, + 0xc1598ec46faa0c7c, + 0x7a17a004747e3dbe, + 0xcc65406a7c2e5a73, + 0x10b8c03d64db4d0c, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xd30e70fe2f029778, + 0xda30772df0f5212e, + 0x5b47a9ff9a233a50, + 0xfb777e5b9b568608, + 0x789bac1fec71a2b9, + 0x1342f02e2da54405, + ])).unwrap(), }, y: Fq2 { - c0: Fq::from_repr(FqRepr([0xfe0812043de54dca, 0xe455171a3d47a646, 0xa493f36bc20be98a, 0x663015d9410eb608, 0x78e82a79d829a544, 0x40a00545bb3c1e])).unwrap(), - c1: Fq::from_repr(FqRepr([0x4709802348e79377, 0xb5ac4dc9204bcfbd, 0xda361c97d02f42b2, 0x15008b1dc399e8df, 0x68128fd0548a3829, 0x16a613db5c873aaa])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xfe0812043de54dca, + 0xe455171a3d47a646, + 0xa493f36bc20be98a, + 0x663015d9410eb608, + 0x78e82a79d829a544, + 0x40a00545bb3c1e, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x4709802348e79377, + 0xb5ac4dc9204bcfbd, + 0xda361c97d02f42b2, + 0x15008b1dc399e8df, + 0x68128fd0548a3829, + 0x16a613db5c873aaa, + ])).unwrap(), }, - infinity: false + infinity: false, }; assert!(!p.is_on_curve()); assert!(p.is_in_correct_subgroup_assuming_on_curve()); @@ -1458,14 +1706,42 @@ pub mod g2 { { let p = G2Affine { x: Fq2 { - c0: Fq::from_repr(FqRepr([0xf4fdfe95a705f917, 0xc2914df688233238, 0x37c6b12cca35a34b, 0x41abba710d6c692c, 0xffcc4b2b62ce8484, 0x6993ec01b8934ed])).unwrap(), - c1: Fq::from_repr(FqRepr([0xb94e92d5f874e26, 0x44516408bc115d95, 0xe93946b290caa591, 0xa5a0c2b7131f3555, 0x83800965822367e7, 0x10cf1d3ad8d90bfa])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xf4fdfe95a705f917, + 0xc2914df688233238, + 0x37c6b12cca35a34b, + 0x41abba710d6c692c, + 0xffcc4b2b62ce8484, + 0x6993ec01b8934ed, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xb94e92d5f874e26, + 0x44516408bc115d95, + 0xe93946b290caa591, + 0xa5a0c2b7131f3555, + 0x83800965822367e7, + 0x10cf1d3ad8d90bfa, + ])).unwrap(), }, y: Fq2 { - c0: Fq::from_repr(FqRepr([0xbf00334c79701d97, 0x4fe714f9ff204f9a, 0xab70b28002f3d825, 0x5a9171720e73eb51, 0x38eb4fd8d658adb7, 0xb649051bbc1164d])).unwrap(), - c1: Fq::from_repr(FqRepr([0x9225814253d7df75, 0xc196c2513477f887, 0xe05e2fbd15a804e0, 0x55f2b8efad953e04, 0x7379345eda55265e, 0x377f2e6208fd4cb])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xbf00334c79701d97, + 0x4fe714f9ff204f9a, + 0xab70b28002f3d825, + 0x5a9171720e73eb51, + 0x38eb4fd8d658adb7, + 0xb649051bbc1164d, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x9225814253d7df75, + 0xc196c2513477f887, + 0xe05e2fbd15a804e0, + 0x55f2b8efad953e04, + 0x7379345eda55265e, + 0x377f2e6208fd4cb, + ])).unwrap(), }, - infinity: false + infinity: false, }; assert!(!p.is_on_curve()); assert!(!p.is_in_correct_subgroup_assuming_on_curve()); @@ -1476,14 +1752,42 @@ pub mod g2 { { let p = G2Affine { x: Fq2 { - c0: Fq::from_repr(FqRepr([0x262cea73ea1906c, 0x2f08540770fabd6, 0x4ceb92d0a76057be, 0x2199bc19c48c393d, 0x4a151b732a6075bf, 0x17762a3b9108c4a7])).unwrap(), - c1: Fq::from_repr(FqRepr([0x26f461e944bbd3d1, 0x298f3189a9cf6ed6, 0x74328ad8bc2aa150, 0x7e147f3f9e6e241, 0x72a9b63583963fff, 0x158b0083c000462])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x262cea73ea1906c, + 0x2f08540770fabd6, + 0x4ceb92d0a76057be, + 0x2199bc19c48c393d, + 0x4a151b732a6075bf, + 0x17762a3b9108c4a7, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x26f461e944bbd3d1, + 0x298f3189a9cf6ed6, + 0x74328ad8bc2aa150, + 0x7e147f3f9e6e241, + 0x72a9b63583963fff, + 0x158b0083c000462, + ])).unwrap(), }, y: Fq2 { - c0: Fq::from_repr(FqRepr([0x91fb0b225ecf103b, 0x55d42edc1dc46ba0, 0x43939b11997b1943, 0x68cad19430706b4d, 0x3ccfb97b924dcea8, 0x1660f93434588f8d])).unwrap(), - c1: Fq::from_repr(FqRepr([0xaaed3985b6dcb9c7, 0xc1e985d6d898d9f4, 0x618bd2ac3271ac42, 0x3940a2dbb914b529, 0xbeb88137cf34f3e7, 0x1699ee577c61b694])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x91fb0b225ecf103b, + 0x55d42edc1dc46ba0, + 0x43939b11997b1943, + 0x68cad19430706b4d, + 0x3ccfb97b924dcea8, + 0x1660f93434588f8d, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xaaed3985b6dcb9c7, + 0xc1e985d6d898d9f4, + 0x618bd2ac3271ac42, + 0x3940a2dbb914b529, + 0xbeb88137cf34f3e7, + 0x1699ee577c61b694, + ])).unwrap(), }, - infinity: false + infinity: false, }; assert!(p.is_on_curve()); assert!(!p.is_in_correct_subgroup_assuming_on_curve()); @@ -1494,72 +1798,218 @@ pub mod g2 { fn test_g2_addition_correctness() { let mut p = G2 { x: Fq2 { - c0: Fq::from_repr(FqRepr([0x6c994cc1e303094e, 0xf034642d2c9e85bd, 0x275094f1352123a9, 0x72556c999f3707ac, 0x4617f2e6774e9711, 0x100b2fe5bffe030b])).unwrap(), - c1: Fq::from_repr(FqRepr([0x7a33555977ec608, 0xe23039d1fe9c0881, 0x19ce4678aed4fcb5, 0x4637c4f417667e2e, 0x93ebe7c3e41f6acc, 0xde884f89a9a371b])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x6c994cc1e303094e, + 0xf034642d2c9e85bd, + 0x275094f1352123a9, + 0x72556c999f3707ac, + 0x4617f2e6774e9711, + 0x100b2fe5bffe030b, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x7a33555977ec608, + 0xe23039d1fe9c0881, + 0x19ce4678aed4fcb5, + 0x4637c4f417667e2e, + 0x93ebe7c3e41f6acc, + 0xde884f89a9a371b, + ])).unwrap(), }, y: Fq2 { - c0: Fq::from_repr(FqRepr([0xe073119472e1eb62, 0x44fb3391fe3c9c30, 0xaa9b066d74694006, 0x25fd427b4122f231, 0xd83112aace35cae, 0x191b2432407cbb7f])).unwrap(), - c1: Fq::from_repr(FqRepr([0xf68ae82fe97662f5, 0xe986057068b50b7d, 0x96c30f0411590b48, 0x9eaa6d19de569196, 0xf6a03d31e2ec2183, 0x3bdafaf7ca9b39b])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xe073119472e1eb62, + 0x44fb3391fe3c9c30, + 0xaa9b066d74694006, + 0x25fd427b4122f231, + 0xd83112aace35cae, + 0x191b2432407cbb7f, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xf68ae82fe97662f5, + 0xe986057068b50b7d, + 0x96c30f0411590b48, + 0x9eaa6d19de569196, + 0xf6a03d31e2ec2183, + 0x3bdafaf7ca9b39b, + ])).unwrap(), }, - z: Fq2::one() + z: Fq2::one(), }; p.add_assign(&G2 { x: Fq2 { - c0: Fq::from_repr(FqRepr([0xa8c763d25910bdd3, 0x408777b30ca3add4, 0x6115fcc12e2769e, 0x8e73a96b329ad190, 0x27c546f75ee1f3ab, 0xa33d27add5e7e82])).unwrap(), - c1: Fq::from_repr(FqRepr([0x93b1ebcd54870dfe, 0xf1578300e1342e11, 0x8270dca3a912407b, 0x2089faf462438296, 0x828e5848cd48ea66, 0x141ecbac1deb038b])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xa8c763d25910bdd3, + 0x408777b30ca3add4, + 0x6115fcc12e2769e, + 0x8e73a96b329ad190, + 0x27c546f75ee1f3ab, + 0xa33d27add5e7e82, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x93b1ebcd54870dfe, + 0xf1578300e1342e11, + 0x8270dca3a912407b, + 0x2089faf462438296, + 0x828e5848cd48ea66, + 0x141ecbac1deb038b, + ])).unwrap(), }, y: Fq2 { - c0: Fq::from_repr(FqRepr([0xf5d2c28857229c3f, 0x8c1574228757ca23, 0xe8d8102175f5dc19, 0x2767032fc37cc31d, 0xd5ee2aba84fd10fe, 0x16576ccd3dd0a4e8])).unwrap(), - c1: Fq::from_repr(FqRepr([0x4da9b6f6a96d1dd2, 0x9657f7da77f1650e, 0xbc150712f9ffe6da, 0x31898db63f87363a, 0xabab040ddbd097cc, 0x11ad236b9ba02990])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xf5d2c28857229c3f, + 0x8c1574228757ca23, + 0xe8d8102175f5dc19, + 0x2767032fc37cc31d, + 0xd5ee2aba84fd10fe, + 0x16576ccd3dd0a4e8, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x4da9b6f6a96d1dd2, + 0x9657f7da77f1650e, + 0xbc150712f9ffe6da, + 0x31898db63f87363a, + 0xabab040ddbd097cc, + 0x11ad236b9ba02990, + ])).unwrap(), }, - z: Fq2::one() + z: Fq2::one(), }); let p = G2Affine::from(p); - assert_eq!(p, G2Affine { - x: Fq2 { - c0: Fq::from_repr(FqRepr([0xcde7ee8a3f2ac8af, 0xfc642eb35975b069, 0xa7de72b7dd0e64b7, 0xf1273e6406eef9cc, 0xababd760ff05cb92, 0xd7c20456617e89])).unwrap(), - c1: Fq::from_repr(FqRepr([0xd1a50b8572cbd2b8, 0x238f0ac6119d07df, 0x4dbe924fe5fd6ac2, 0x8b203284c51edf6b, 0xc8a0b730bbb21f5e, 0x1a3b59d29a31274])).unwrap() - }, - y: Fq2 { - c0: Fq::from_repr(FqRepr([0x9e709e78a8eaa4c9, 0xd30921c93ec342f4, 0x6d1ef332486f5e34, 0x64528ab3863633dc, 0x159384333d7cba97, 0x4cb84741f3cafe8])).unwrap(), - c1: Fq::from_repr(FqRepr([0x242af0dc3640e1a4, 0xe90a73ad65c66919, 0x2bd7ca7f4346f9ec, 0x38528f92b689644d, 0xb6884deec59fb21f, 0x3c075d3ec52ba90])).unwrap() - }, - infinity: false - }); + assert_eq!( + p, + G2Affine { + x: Fq2 { + c0: Fq::from_repr(FqRepr([ + 0xcde7ee8a3f2ac8af, + 0xfc642eb35975b069, + 0xa7de72b7dd0e64b7, + 0xf1273e6406eef9cc, + 0xababd760ff05cb92, + 0xd7c20456617e89 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xd1a50b8572cbd2b8, + 0x238f0ac6119d07df, + 0x4dbe924fe5fd6ac2, + 0x8b203284c51edf6b, + 0xc8a0b730bbb21f5e, + 0x1a3b59d29a31274 + ])).unwrap(), + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x9e709e78a8eaa4c9, + 0xd30921c93ec342f4, + 0x6d1ef332486f5e34, + 0x64528ab3863633dc, + 0x159384333d7cba97, + 0x4cb84741f3cafe8 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x242af0dc3640e1a4, + 0xe90a73ad65c66919, + 0x2bd7ca7f4346f9ec, + 0x38528f92b689644d, + 0xb6884deec59fb21f, + 0x3c075d3ec52ba90 + ])).unwrap(), + }, + infinity: false, + } + ); } #[test] fn test_g2_doubling_correctness() { let mut p = G2 { x: Fq2 { - c0: Fq::from_repr(FqRepr([0x6c994cc1e303094e, 0xf034642d2c9e85bd, 0x275094f1352123a9, 0x72556c999f3707ac, 0x4617f2e6774e9711, 0x100b2fe5bffe030b])).unwrap(), - c1: Fq::from_repr(FqRepr([0x7a33555977ec608, 0xe23039d1fe9c0881, 0x19ce4678aed4fcb5, 0x4637c4f417667e2e, 0x93ebe7c3e41f6acc, 0xde884f89a9a371b])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x6c994cc1e303094e, + 0xf034642d2c9e85bd, + 0x275094f1352123a9, + 0x72556c999f3707ac, + 0x4617f2e6774e9711, + 0x100b2fe5bffe030b, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x7a33555977ec608, + 0xe23039d1fe9c0881, + 0x19ce4678aed4fcb5, + 0x4637c4f417667e2e, + 0x93ebe7c3e41f6acc, + 0xde884f89a9a371b, + ])).unwrap(), }, y: Fq2 { - c0: Fq::from_repr(FqRepr([0xe073119472e1eb62, 0x44fb3391fe3c9c30, 0xaa9b066d74694006, 0x25fd427b4122f231, 0xd83112aace35cae, 0x191b2432407cbb7f])).unwrap(), - c1: Fq::from_repr(FqRepr([0xf68ae82fe97662f5, 0xe986057068b50b7d, 0x96c30f0411590b48, 0x9eaa6d19de569196, 0xf6a03d31e2ec2183, 0x3bdafaf7ca9b39b])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xe073119472e1eb62, + 0x44fb3391fe3c9c30, + 0xaa9b066d74694006, + 0x25fd427b4122f231, + 0xd83112aace35cae, + 0x191b2432407cbb7f, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xf68ae82fe97662f5, + 0xe986057068b50b7d, + 0x96c30f0411590b48, + 0x9eaa6d19de569196, + 0xf6a03d31e2ec2183, + 0x3bdafaf7ca9b39b, + ])).unwrap(), }, - z: Fq2::one() + z: Fq2::one(), }; p.double(); let p = G2Affine::from(p); - assert_eq!(p, G2Affine { - x: Fq2 { - c0: Fq::from_repr(FqRepr([0x91ccb1292727c404, 0x91a6cb182438fad7, 0x116aee59434de902, 0xbcedcfce1e52d986, 0x9755d4a3926e9862, 0x18bab73760fd8024])).unwrap(), - c1: Fq::from_repr(FqRepr([0x4e7c5e0a2ae5b99e, 0x96e582a27f028961, 0xc74d1cf4ef2d5926, 0xeb0cf5e610ef4fe7, 0x7b4c2bae8db6e70b, 0xf136e43909fca0])).unwrap() - }, - y: Fq2 { - c0: Fq::from_repr(FqRepr([0x954d4466ab13e58, 0x3ee42eec614cf890, 0x853bb1d28877577e, 0xa5a2a51f7fde787b, 0x8b92866bc6384188, 0x81a53fe531d64ef])).unwrap(), - c1: Fq::from_repr(FqRepr([0x4c5d607666239b34, 0xeddb5f48304d14b3, 0x337167ee6e8e3cb6, 0xb271f52f12ead742, 0x244e6c2015c83348, 0x19e2deae6eb9b441])).unwrap() - }, - infinity: false - }); + assert_eq!( + p, + G2Affine { + x: Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x91ccb1292727c404, + 0x91a6cb182438fad7, + 0x116aee59434de902, + 0xbcedcfce1e52d986, + 0x9755d4a3926e9862, + 0x18bab73760fd8024 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x4e7c5e0a2ae5b99e, + 0x96e582a27f028961, + 0xc74d1cf4ef2d5926, + 0xeb0cf5e610ef4fe7, + 0x7b4c2bae8db6e70b, + 0xf136e43909fca0 + ])).unwrap(), + }, + y: Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x954d4466ab13e58, + 0x3ee42eec614cf890, + 0x853bb1d28877577e, + 0xa5a2a51f7fde787b, + 0x8b92866bc6384188, + 0x81a53fe531d64ef + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x4c5d607666239b34, + 0xeddb5f48304d14b3, + 0x337167ee6e8e3cb6, + 0xb271f52f12ead742, + 0x244e6c2015c83348, + 0x19e2deae6eb9b441 + ])).unwrap(), + }, + infinity: false, + } + ); } #[test] diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 81eb256..2f08eea 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,9 +1,16 @@ -use ::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; +use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; use std::cmp::Ordering; use super::fq2::Fq2; // q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 -const MODULUS: FqRepr = FqRepr([0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); +const MODULUS: FqRepr = FqRepr([ + 0xb9feffffffffaaab, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a, +]); // The number of bits needed to represent the modulus. const MODULUS_BITS: u32 = 381; @@ -13,25 +20,60 @@ const MODULUS_BITS: u32 = 381; const REPR_SHAVE_BITS: u32 = 3; // R = 2**384 % q -const R: FqRepr = FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493]); +const R: FqRepr = FqRepr([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, +]); // R2 = R^2 % q -const R2: FqRepr = FqRepr([0xf4df1f341c341746, 0xa76e6a609d104f1, 0x8de5476c4c95b6d5, 0x67eb88a9939d83c0, 0x9a793e85b519952d, 0x11988fe592cae3aa]); +const R2: FqRepr = FqRepr([ + 0xf4df1f341c341746, + 0xa76e6a609d104f1, + 0x8de5476c4c95b6d5, + 0x67eb88a9939d83c0, + 0x9a793e85b519952d, + 0x11988fe592cae3aa, +]); // INV = -(q^{-1} mod 2^64) mod 2^64 const INV: u64 = 0x89f3fffcfffcfffd; // GENERATOR = 2 (multiplicative generator of q-1 order, that is also quadratic nonresidue) -const GENERATOR: FqRepr = FqRepr([0x321300000006554f, 0xb93c0018d6c40005, 0x57605e0db0ddbb51, 0x8b256521ed1f9bcb, 0x6cf28d7901622c03, 0x11ebab9dbb81e28c]); +const GENERATOR: FqRepr = FqRepr([ + 0x321300000006554f, + 0xb93c0018d6c40005, + 0x57605e0db0ddbb51, + 0x8b256521ed1f9bcb, + 0x6cf28d7901622c03, + 0x11ebab9dbb81e28c, +]); // 2^s * t = MODULUS - 1 with t odd const S: u32 = 1; // 2^s root of unity computed by GENERATOR^t -const ROOT_OF_UNITY: FqRepr = FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206]); +const ROOT_OF_UNITY: FqRepr = FqRepr([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x7e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x40ab3263eff0206, +]); // B coefficient of BLS12-381 curve, 4. -pub const B_COEFF: Fq = Fq(FqRepr([0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478fe97a6b0a807f, 0xb1d37ebee6ba24d7, 0x8ec9733bbf78ab2f, 0x9d645513d83de7e])); +pub const B_COEFF: Fq = Fq(FqRepr([ + 0xaa270000000cfff3, + 0x53cc0032fc34000a, + 0x478fe97a6b0a807f, + 0xb1d37ebee6ba24d7, + 0x8ec9733bbf78ab2f, + 0x9d645513d83de7e, +])); // The generators of G1/G2 are computed by finding the lexicographically smallest valid x coordinate, // and its lexicographically smallest y coordinate and multiplying it by the cofactor such that the @@ -40,157 +82,430 @@ pub const B_COEFF: Fq = Fq(FqRepr([0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478 // Generator of G1 // x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507 // y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569 -pub const G1_GENERATOR_X: Fq = Fq(FqRepr([0x5cb38790fd530c16, 0x7817fc679976fff5, 0x154f95c7143ba1c1, 0xf0ae6acdf3d0e747, 0xedce6ecc21dbf440, 0x120177419e0bfb75])); -pub const G1_GENERATOR_Y: Fq = Fq(FqRepr([0xbaac93d50ce72271, 0x8c22631a7918fd8e, 0xdd595f13570725ce, 0x51ac582950405194, 0xe1c8c3fad0059c0, 0xbbc3efc5008a26a])); +pub const G1_GENERATOR_X: Fq = Fq(FqRepr([ + 0x5cb38790fd530c16, + 0x7817fc679976fff5, + 0x154f95c7143ba1c1, + 0xf0ae6acdf3d0e747, + 0xedce6ecc21dbf440, + 0x120177419e0bfb75, +])); +pub const G1_GENERATOR_Y: Fq = Fq(FqRepr([ + 0xbaac93d50ce72271, + 0x8c22631a7918fd8e, + 0xdd595f13570725ce, + 0x51ac582950405194, + 0xe1c8c3fad0059c0, + 0xbbc3efc5008a26a, +])); // Generator of G2 // x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 // y = 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582*u + 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 -pub const G2_GENERATOR_X_C0: Fq = Fq(FqRepr([0xf5f28fa202940a10, 0xb3f5fb2687b4961a, 0xa1a893b53e2ae580, 0x9894999d1a3caee9, 0x6f67b7631863366b, 0x58191924350bcd7])); -pub const G2_GENERATOR_X_C1: Fq = Fq(FqRepr([0xa5a9c0759e23f606, 0xaaa0c59dbccd60c3, 0x3bb17e18e2867806, 0x1b1ab6cc8541b367, 0xc2b6ed0ef2158547, 0x11922a097360edf3])); -pub const G2_GENERATOR_Y_C0: Fq = Fq(FqRepr([0x4c730af860494c4a, 0x597cfa1f5e369c5a, 0xe7e6856caa0a635a, 0xbbefb5e96e0d495f, 0x7d3a975f0ef25a2, 0x83fd8e7e80dae5])); -pub const G2_GENERATOR_Y_C1: Fq = Fq(FqRepr([0xadc0fc92df64b05d, 0x18aa270a2b1461dc, 0x86adac6a3be4eba0, 0x79495c4ec93da33a, 0xe7175850a43ccaed, 0xb2bc2a163de1bf2])); +pub const G2_GENERATOR_X_C0: Fq = Fq(FqRepr([ + 0xf5f28fa202940a10, + 0xb3f5fb2687b4961a, + 0xa1a893b53e2ae580, + 0x9894999d1a3caee9, + 0x6f67b7631863366b, + 0x58191924350bcd7, +])); +pub const G2_GENERATOR_X_C1: Fq = Fq(FqRepr([ + 0xa5a9c0759e23f606, + 0xaaa0c59dbccd60c3, + 0x3bb17e18e2867806, + 0x1b1ab6cc8541b367, + 0xc2b6ed0ef2158547, + 0x11922a097360edf3, +])); +pub const G2_GENERATOR_Y_C0: Fq = Fq(FqRepr([ + 0x4c730af860494c4a, + 0x597cfa1f5e369c5a, + 0xe7e6856caa0a635a, + 0xbbefb5e96e0d495f, + 0x7d3a975f0ef25a2, + 0x83fd8e7e80dae5, +])); +pub const G2_GENERATOR_Y_C1: Fq = Fq(FqRepr([ + 0xadc0fc92df64b05d, + 0x18aa270a2b1461dc, + 0x86adac6a3be4eba0, + 0x79495c4ec93da33a, + 0xe7175850a43ccaed, + 0xb2bc2a163de1bf2, +])); // Coefficients for the Frobenius automorphism. pub const FROBENIUS_COEFF_FQ2_C1: [Fq; 2] = [ // Fq(-1)**(((q^0) - 1) / 2) - Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])), + Fq(FqRepr([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ])), // Fq(-1)**(((q^1) - 1) / 2) - Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])) + Fq(FqRepr([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x7e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x40ab3263eff0206, + ])), ]; pub const FROBENIUS_COEFF_FQ6_C1: [Fq2; 6] = [ // Fq2(u + 1)**(((q^0) - 1) / 3) Fq2 { - c0: Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^1) - 1) / 3) Fq2 { c0: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), - c1: Fq(FqRepr([0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x3f97d6e83d050d2, 0x18f0206554638741])) + c1: Fq(FqRepr([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x3f97d6e83d050d2, + 0x18f0206554638741, + ])), }, // Fq2(u + 1)**(((q^2) - 1) / 3) Fq2 { - c0: Fq(FqRepr([0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x51ba4ab241b6160])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x30f1361b798a64e8, + 0xf3b8ddab7ece5a2a, + 0x16a8ca3ac61577f7, + 0xc26a2ff874fd029b, + 0x3636b76660701c6e, + 0x51ba4ab241b6160, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^3) - 1) / 3) Fq2 { c0: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), - c1: Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])) + c1: Fq(FqRepr([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ])), }, // Fq2(u + 1)**(((q^4) - 1) / 3) Fq2 { - c0: Fq(FqRepr([0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x3f97d6e83d050d2, 0x18f0206554638741])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x3f97d6e83d050d2, + 0x18f0206554638741, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^5) - 1) / 3) Fq2 { c0: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), - c1: Fq(FqRepr([0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x51ba4ab241b6160])) - } + c1: Fq(FqRepr([ + 0x30f1361b798a64e8, + 0xf3b8ddab7ece5a2a, + 0x16a8ca3ac61577f7, + 0xc26a2ff874fd029b, + 0x3636b76660701c6e, + 0x51ba4ab241b6160, + ])), + }, ]; pub const FROBENIUS_COEFF_FQ6_C2: [Fq2; 6] = [ // Fq2(u + 1)**(((2q^0) - 2) / 3) Fq2 { - c0: Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((2q^1) - 2) / 3) Fq2 { - c0: Fq(FqRepr([0x890dc9e4867545c3, 0x2af322533285a5d5, 0x50880866309b7e2c, 0xa20d1b8c7e881024, 0x14e4f04fe2db9068, 0x14e56d3f1564853a])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x890dc9e4867545c3, + 0x2af322533285a5d5, + 0x50880866309b7e2c, + 0xa20d1b8c7e881024, + 0x14e4f04fe2db9068, + 0x14e56d3f1564853a, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((2q^2) - 2) / 3) Fq2 { - c0: Fq(FqRepr([0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x3f97d6e83d050d2, 0x18f0206554638741])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x3f97d6e83d050d2, + 0x18f0206554638741, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((2q^3) - 2) / 3) Fq2 { - c0: Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x7e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x40ab3263eff0206, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((2q^4) - 2) / 3) Fq2 { - c0: Fq(FqRepr([0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x51ba4ab241b6160])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x30f1361b798a64e8, + 0xf3b8ddab7ece5a2a, + 0x16a8ca3ac61577f7, + 0xc26a2ff874fd029b, + 0x3636b76660701c6e, + 0x51ba4ab241b6160, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((2q^5) - 2) / 3) Fq2 { - c0: Fq(FqRepr([0xecfb361b798dba3a, 0xc100ddb891865a2c, 0xec08ff1232bda8e, 0xd5c13cc6f1ca4721, 0x47222a47bf7b5c04, 0x110f184e51c5f59])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) - } + c0: Fq(FqRepr([ + 0xecfb361b798dba3a, + 0xc100ddb891865a2c, + 0xec08ff1232bda8e, + 0xd5c13cc6f1ca4721, + 0x47222a47bf7b5c04, + 0x110f184e51c5f59, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), + }, ]; // non_residue^((modulus^i-1)/6) for i=0,...,11 pub const FROBENIUS_COEFF_FQ12_C1: [Fq2; 12] = [ // Fq2(u + 1)**(((q^0) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x760900000002fffd, + 0xebf4000bc40c0002, + 0x5f48985753c758ba, + 0x77ce585370525745, + 0x5c071a97a256ec6d, + 0x15f65ec3fa80e493, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^1) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x7089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, 0xa35baecab2dc29ee, 0x1ce393ea5daace4d, 0x8f2220fb0fb66eb])), - c1: Fq(FqRepr([0xb2f66aad4ce5d646, 0x5842a06bfc497cec, 0xcf4895d42599d394, 0xc11b9cba40a8e8d0, 0x2e3813cbe5a0de89, 0x110eefda88847faf])) + c0: Fq(FqRepr([ + 0x7089552b319d465, + 0xc6695f92b50a8313, + 0x97e83cccd117228f, + 0xa35baecab2dc29ee, + 0x1ce393ea5daace4d, + 0x8f2220fb0fb66eb, + ])), + c1: Fq(FqRepr([ + 0xb2f66aad4ce5d646, + 0x5842a06bfc497cec, + 0xcf4895d42599d394, + 0xc11b9cba40a8e8d0, + 0x2e3813cbe5a0de89, + 0x110eefda88847faf, + ])), }, // Fq2(u + 1)**(((q^2) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0xecfb361b798dba3a, 0xc100ddb891865a2c, 0xec08ff1232bda8e, 0xd5c13cc6f1ca4721, 0x47222a47bf7b5c04, 0x110f184e51c5f59])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0xecfb361b798dba3a, + 0xc100ddb891865a2c, + 0xec08ff1232bda8e, + 0xd5c13cc6f1ca4721, + 0x47222a47bf7b5c04, + 0x110f184e51c5f59, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^3) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x3e2f585da55c9ad1, 0x4294213d86c18183, 0x382844c88b623732, 0x92ad2afd19103e18, 0x1d794e4fac7cf0b9, 0xbd592fc7d825ec8])), - c1: Fq(FqRepr([0x7bcfa7a25aa30fda, 0xdc17dec12a927e7c, 0x2f088dd86b4ebef1, 0xd1ca2087da74d4a7, 0x2da2596696cebc1d, 0xe2b7eedbbfd87d2])) + c0: Fq(FqRepr([ + 0x3e2f585da55c9ad1, + 0x4294213d86c18183, + 0x382844c88b623732, + 0x92ad2afd19103e18, + 0x1d794e4fac7cf0b9, + 0xbd592fc7d825ec8, + ])), + c1: Fq(FqRepr([ + 0x7bcfa7a25aa30fda, + 0xdc17dec12a927e7c, + 0x2f088dd86b4ebef1, + 0xd1ca2087da74d4a7, + 0x2da2596696cebc1d, + 0xe2b7eedbbfd87d2, + ])), }, // Fq2(u + 1)**(((q^4) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x51ba4ab241b6160])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x30f1361b798a64e8, + 0xf3b8ddab7ece5a2a, + 0x16a8ca3ac61577f7, + 0xc26a2ff874fd029b, + 0x3636b76660701c6e, + 0x51ba4ab241b6160, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^5) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x3726c30af242c66c, 0x7c2ac1aad1b6fe70, 0xa04007fbba4b14a2, 0xef517c3266341429, 0x95ba654ed2226b, 0x2e370eccc86f7dd])), - c1: Fq(FqRepr([0x82d83cf50dbce43f, 0xa2813e53df9d018f, 0xc6f0caa53c65e181, 0x7525cf528d50fe95, 0x4a85ed50f4798a6b, 0x171da0fd6cf8eebd])) + c0: Fq(FqRepr([ + 0x3726c30af242c66c, + 0x7c2ac1aad1b6fe70, + 0xa04007fbba4b14a2, + 0xef517c3266341429, + 0x95ba654ed2226b, + 0x2e370eccc86f7dd, + ])), + c1: Fq(FqRepr([ + 0x82d83cf50dbce43f, + 0xa2813e53df9d018f, + 0xc6f0caa53c65e181, + 0x7525cf528d50fe95, + 0x4a85ed50f4798a6b, + 0x171da0fd6cf8eebd, + ])), }, // Fq2(u + 1)**(((q^6) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x7e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x40ab3263eff0206, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^7) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0xb2f66aad4ce5d646, 0x5842a06bfc497cec, 0xcf4895d42599d394, 0xc11b9cba40a8e8d0, 0x2e3813cbe5a0de89, 0x110eefda88847faf])), - c1: Fq(FqRepr([0x7089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, 0xa35baecab2dc29ee, 0x1ce393ea5daace4d, 0x8f2220fb0fb66eb])) + c0: Fq(FqRepr([ + 0xb2f66aad4ce5d646, + 0x5842a06bfc497cec, + 0xcf4895d42599d394, + 0xc11b9cba40a8e8d0, + 0x2e3813cbe5a0de89, + 0x110eefda88847faf, + ])), + c1: Fq(FqRepr([ + 0x7089552b319d465, + 0xc6695f92b50a8313, + 0x97e83cccd117228f, + 0xa35baecab2dc29ee, + 0x1ce393ea5daace4d, + 0x8f2220fb0fb66eb, + ])), }, // Fq2(u + 1)**(((q^8) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x3f97d6e83d050d2, 0x18f0206554638741])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x3f97d6e83d050d2, + 0x18f0206554638741, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^9) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x7bcfa7a25aa30fda, 0xdc17dec12a927e7c, 0x2f088dd86b4ebef1, 0xd1ca2087da74d4a7, 0x2da2596696cebc1d, 0xe2b7eedbbfd87d2])), - c1: Fq(FqRepr([0x3e2f585da55c9ad1, 0x4294213d86c18183, 0x382844c88b623732, 0x92ad2afd19103e18, 0x1d794e4fac7cf0b9, 0xbd592fc7d825ec8])) + c0: Fq(FqRepr([ + 0x7bcfa7a25aa30fda, + 0xdc17dec12a927e7c, + 0x2f088dd86b4ebef1, + 0xd1ca2087da74d4a7, + 0x2da2596696cebc1d, + 0xe2b7eedbbfd87d2, + ])), + c1: Fq(FqRepr([ + 0x3e2f585da55c9ad1, + 0x4294213d86c18183, + 0x382844c88b623732, + 0x92ad2afd19103e18, + 0x1d794e4fac7cf0b9, + 0xbd592fc7d825ec8, + ])), }, // Fq2(u + 1)**(((q^10) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x890dc9e4867545c3, 0x2af322533285a5d5, 0x50880866309b7e2c, 0xa20d1b8c7e881024, 0x14e4f04fe2db9068, 0x14e56d3f1564853a])), - c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])) + c0: Fq(FqRepr([ + 0x890dc9e4867545c3, + 0x2af322533285a5d5, + 0x50880866309b7e2c, + 0xa20d1b8c7e881024, + 0x14e4f04fe2db9068, + 0x14e56d3f1564853a, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0, 0x0, 0x0])), }, // Fq2(u + 1)**(((q^11) - 1) / 6) Fq2 { - c0: Fq(FqRepr([0x82d83cf50dbce43f, 0xa2813e53df9d018f, 0xc6f0caa53c65e181, 0x7525cf528d50fe95, 0x4a85ed50f4798a6b, 0x171da0fd6cf8eebd])), - c1: Fq(FqRepr([0x3726c30af242c66c, 0x7c2ac1aad1b6fe70, 0xa04007fbba4b14a2, 0xef517c3266341429, 0x95ba654ed2226b, 0x2e370eccc86f7dd])) - } + c0: Fq(FqRepr([ + 0x82d83cf50dbce43f, + 0xa2813e53df9d018f, + 0xc6f0caa53c65e181, + 0x7525cf528d50fe95, + 0x4a85ed50f4798a6b, + 0x171da0fd6cf8eebd, + ])), + c1: Fq(FqRepr([ + 0x3726c30af242c66c, + 0x7c2ac1aad1b6fe70, + 0xa04007fbba4b14a2, + 0xef517c3266341429, + 0x95ba654ed2226b, + 0x2e370eccc86f7dd, + ])), + }, ]; // -((2**384) mod q) mod q -pub const NEGATIVE_ONE: Fq = Fq(FqRepr([0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x7e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x40ab3263eff0206])); +pub const NEGATIVE_ONE: Fq = Fq(FqRepr([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x7e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x40ab3263eff0206, +])); #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct FqRepr(pub [u64; 6]); @@ -202,8 +517,7 @@ impl ::rand::Rand for FqRepr { } } -impl ::std::fmt::Display for FqRepr -{ +impl ::std::fmt::Display for FqRepr { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { try!(write!(f, "0x")); for i in self.0.iter().rev() { @@ -242,9 +556,9 @@ impl Ord for FqRepr { fn cmp(&self, other: &FqRepr) -> Ordering { for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { if a < b { - return Ordering::Less + return Ordering::Less; } else if a > b { - return Ordering::Greater + return Ordering::Greater; } } @@ -400,8 +714,7 @@ impl PartialOrd for Fq { } } -impl ::std::fmt::Display for Fq -{ +impl ::std::fmt::Display for Fq { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "Fq({})", self.into_repr()) } @@ -416,7 +729,7 @@ impl ::rand::Rand for Fq { tmp.0.as_mut()[5] &= 0xffffffffffffffff >> REPR_SHAVE_BITS; if tmp.is_valid() { - return tmp + return tmp; } } } @@ -444,10 +757,20 @@ impl PrimeField for Fq { fn into_repr(&self) -> FqRepr { let mut r = *self; - r.mont_reduce((self.0).0[0], (self.0).0[1], - (self.0).0[2], (self.0).0[3], - (self.0).0[4], (self.0).0[5], - 0, 0, 0, 0, 0, 0); + r.mont_reduce( + (self.0).0[0], + (self.0).0[1], + (self.0).0[2], + (self.0).0[3], + (self.0).0[4], + (self.0).0[5], + 0, + 0, + 0, + 0, + 0, + 0, + ); r.0 } @@ -584,8 +907,7 @@ impl Field for Fq { } #[inline] - fn mul_assign(&mut self, other: &Fq) - { + fn mul_assign(&mut self, other: &Fq) { let mut carry = 0; let r0 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); let r1 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); @@ -638,8 +960,7 @@ impl Field for Fq { } #[inline] - fn square(&mut self) - { + fn square(&mut self) { let mut carry = 0; let r1 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); let r2 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); @@ -726,9 +1047,8 @@ impl Fq { mut r8: u64, mut r9: u64, mut r10: u64, - mut r11: u64 - ) - { + mut r11: u64, + ) { // The Montgomery reduction here is based on Algorithm 14.32 in // Handbook of Applied Cryptography // <http://cacr.uwaterloo.ca/hac/about/chap14.pdf>. @@ -803,16 +1123,25 @@ impl Fq { } impl SqrtField for Fq { - fn legendre(&self) -> ::LegendreSymbol { - use ::LegendreSymbol::*; + use LegendreSymbol::*; // s = self^((q - 1) // 2) - let s = self.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, - 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]); - if s == Fq::zero() { Zero } - else if s == Fq::one() { QuadraticResidue } - else { QuadraticNonResidue } + let s = self.pow([ + 0xdcff7fffffffd555, + 0xf55ffff58a9ffff, + 0xb39869507b587b12, + 0xb23ba5c279c2895f, + 0x258dd3db21a5d66b, + 0xd0088f51cbff34d, + ]); + if s == Fq::zero() { + Zero + } else if s == Fq::one() { + QuadraticResidue + } else { + QuadraticNonResidue + } } fn sqrt(&self) -> Option<Self> { @@ -820,24 +1149,27 @@ impl SqrtField for Fq { // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) // a1 = self^((q - 3) // 4) - let mut a1 = self.pow([0xee7fbfffffffeaaa, 0x7aaffffac54ffff, 0xd9cc34a83dac3d89, 0xd91dd2e13ce144af, 0x92c6e9ed90d2eb35, 0x680447a8e5ff9a6]); + let mut a1 = self.pow([ + 0xee7fbfffffffeaaa, + 0x7aaffffac54ffff, + 0xd9cc34a83dac3d89, + 0xd91dd2e13ce144af, + 0x92c6e9ed90d2eb35, + 0x680447a8e5ff9a6, + ]); let mut a0 = a1; a0.square(); a0.mul_assign(self); - if a0 == NEGATIVE_ONE - { + if a0 == NEGATIVE_ONE { None - } - else - { + } else { a1.mul_assign(self); Some(a1) } } } - #[test] fn test_b_coeff() { assert_eq!(Fq::from_repr(FqRepr::from(4)).unwrap(), B_COEFF); @@ -849,39 +1181,709 @@ fn test_frob_coeffs() { nqr.negate(); assert_eq!(FROBENIUS_COEFF_FQ2_C1[0], Fq::one()); - assert_eq!(FROBENIUS_COEFF_FQ2_C1[1], nqr.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d])); + assert_eq!( + FROBENIUS_COEFF_FQ2_C1[1], + nqr.pow([ + 0xdcff7fffffffd555, + 0xf55ffff58a9ffff, + 0xb39869507b587b12, + 0xb23ba5c279c2895f, + 0x258dd3db21a5d66b, + 0xd0088f51cbff34d + ]) + ); let nqr = Fq2 { c0: Fq::one(), - c1: Fq::one() + c1: Fq::one(), }; assert_eq!(FROBENIUS_COEFF_FQ6_C1[0], Fq2::one()); - assert_eq!(FROBENIUS_COEFF_FQ6_C1[1], nqr.pow([0x9354ffffffffe38e, 0xa395554e5c6aaaa, 0xcd104635a790520c, 0xcc27c3d6fbd7063f, 0x190937e76bc3e447, 0x8ab05f8bdd54cde])); - assert_eq!(FROBENIUS_COEFF_FQ6_C1[2], nqr.pow([0xb78e0000097b2f68, 0xd44f23b47cbd64e3, 0x5cb9668120b069a9, 0xccea85f9bf7b3d16, 0xdba2c8d7adb356d, 0x9cd75ded75d7429, 0xfc65c31103284fab, 0xc58cb9a9b249ee24, 0xccf734c3118a2e9a, 0xa0f4304c5a256ce6, 0xc3f0d2f8e0ba61f8, 0xe167e192ebca97])); - assert_eq!(FROBENIUS_COEFF_FQ6_C1[3], nqr.pow([0xdbc6fcd6f35b9e06, 0x997dead10becd6aa, 0x9dbbd24c17206460, 0x72b97acc6057c45e, 0xf8e9a230bf0c628e, 0x647ccb1885c63a7, 0xce80264fc55bf6ee, 0x94d8d716c3939fc4, 0xad78f0eb77ee6ee1, 0xd6fe49bfe57dc5f9, 0x2656d6c15c63647, 0xdf6282f111fa903, 0x1bdba63e0632b4bb, 0x6883597bcaa505eb, 0xa56d4ec90c34a982, 0x7e4c42823bbe90b2, 0xf64728aa6dcb0f20, 0x16e57e16ef152f])); - assert_eq!(FROBENIUS_COEFF_FQ6_C1[4], nqr.pow([0x4649add3c71c6d90, 0x43caa6528972a865, 0xcda8445bbaaa0fbb, 0xc93dea665662aa66, 0x2863bc891834481d, 0x51a0c3f5d4ccbed8, 0x9210e660f90ccae9, 0xe2bd6836c546d65e, 0xf223abbaa7cf778b, 0xd4f10b222cf11680, 0xd540f5eff4a1962e, 0xa123a1f140b56526, 0x31ace500636a59f6, 0x3a82bc8c8dfa57a9, 0x648c511e217fc1f8, 0x36c17ffd53a4558f, 0x881bef5fd684eefd, 0x5d648dbdc5dbb522, 0x8fd07bf06e5e59b8, 0x8ddec8a9acaa4b51, 0x4cc1f8688e2def26, 0xa74e63cb492c03de, 0x57c968173d1349bb, 0x253674e02a866])); - assert_eq!(FROBENIUS_COEFF_FQ6_C1[5], nqr.pow([0xf896f792732eb2be, 0x49c86a6d1dc593a1, 0xe5b31e94581f91c3, 0xe3da5cc0a6b20d7f, 0x822caef950e0bfed, 0x317ed950b9ee67cd, 0xffd664016ee3f6cd, 0x77d991c88810b122, 0x62e72e635e698264, 0x905e1a1a2d22814a, 0xf5b7ab3a3f33d981, 0x175871b0bc0e25dd, 0x1e2e9a63df5c3772, 0xe888b1f7445b149d, 0x9551c19e5e7e2c24, 0xecf21939a3d2d6be, 0xd830dbfdab72dbd4, 0x7b34af8d622d40c0, 0x3df6d20a45671242, 0xaf86bee30e21d98, 0x41064c1534e5df5d, 0xf5f6cabd3164c609, 0xa5d14bdf2b7ee65, 0xa718c069defc9138, 0xdb1447e770e3110e, 0xc1b164a9e90af491, 0x7180441f9d251602, 0x1fd3a5e6a9a893e, 0x1e17b779d54d5db, 0x3c7afafe3174])); + assert_eq!( + FROBENIUS_COEFF_FQ6_C1[1], + nqr.pow([ + 0x9354ffffffffe38e, + 0xa395554e5c6aaaa, + 0xcd104635a790520c, + 0xcc27c3d6fbd7063f, + 0x190937e76bc3e447, + 0x8ab05f8bdd54cde + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ6_C1[2], + nqr.pow([ + 0xb78e0000097b2f68, + 0xd44f23b47cbd64e3, + 0x5cb9668120b069a9, + 0xccea85f9bf7b3d16, + 0xdba2c8d7adb356d, + 0x9cd75ded75d7429, + 0xfc65c31103284fab, + 0xc58cb9a9b249ee24, + 0xccf734c3118a2e9a, + 0xa0f4304c5a256ce6, + 0xc3f0d2f8e0ba61f8, + 0xe167e192ebca97 + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ6_C1[3], + nqr.pow([ + 0xdbc6fcd6f35b9e06, + 0x997dead10becd6aa, + 0x9dbbd24c17206460, + 0x72b97acc6057c45e, + 0xf8e9a230bf0c628e, + 0x647ccb1885c63a7, + 0xce80264fc55bf6ee, + 0x94d8d716c3939fc4, + 0xad78f0eb77ee6ee1, + 0xd6fe49bfe57dc5f9, + 0x2656d6c15c63647, + 0xdf6282f111fa903, + 0x1bdba63e0632b4bb, + 0x6883597bcaa505eb, + 0xa56d4ec90c34a982, + 0x7e4c42823bbe90b2, + 0xf64728aa6dcb0f20, + 0x16e57e16ef152f + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ6_C1[4], + nqr.pow([ + 0x4649add3c71c6d90, + 0x43caa6528972a865, + 0xcda8445bbaaa0fbb, + 0xc93dea665662aa66, + 0x2863bc891834481d, + 0x51a0c3f5d4ccbed8, + 0x9210e660f90ccae9, + 0xe2bd6836c546d65e, + 0xf223abbaa7cf778b, + 0xd4f10b222cf11680, + 0xd540f5eff4a1962e, + 0xa123a1f140b56526, + 0x31ace500636a59f6, + 0x3a82bc8c8dfa57a9, + 0x648c511e217fc1f8, + 0x36c17ffd53a4558f, + 0x881bef5fd684eefd, + 0x5d648dbdc5dbb522, + 0x8fd07bf06e5e59b8, + 0x8ddec8a9acaa4b51, + 0x4cc1f8688e2def26, + 0xa74e63cb492c03de, + 0x57c968173d1349bb, + 0x253674e02a866 + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ6_C1[5], + nqr.pow([ + 0xf896f792732eb2be, + 0x49c86a6d1dc593a1, + 0xe5b31e94581f91c3, + 0xe3da5cc0a6b20d7f, + 0x822caef950e0bfed, + 0x317ed950b9ee67cd, + 0xffd664016ee3f6cd, + 0x77d991c88810b122, + 0x62e72e635e698264, + 0x905e1a1a2d22814a, + 0xf5b7ab3a3f33d981, + 0x175871b0bc0e25dd, + 0x1e2e9a63df5c3772, + 0xe888b1f7445b149d, + 0x9551c19e5e7e2c24, + 0xecf21939a3d2d6be, + 0xd830dbfdab72dbd4, + 0x7b34af8d622d40c0, + 0x3df6d20a45671242, + 0xaf86bee30e21d98, + 0x41064c1534e5df5d, + 0xf5f6cabd3164c609, + 0xa5d14bdf2b7ee65, + 0xa718c069defc9138, + 0xdb1447e770e3110e, + 0xc1b164a9e90af491, + 0x7180441f9d251602, + 0x1fd3a5e6a9a893e, + 0x1e17b779d54d5db, + 0x3c7afafe3174 + ]) + ); assert_eq!(FROBENIUS_COEFF_FQ6_C2[0], Fq2::one()); - assert_eq!(FROBENIUS_COEFF_FQ6_C2[1], nqr.pow([0x26a9ffffffffc71c, 0x1472aaa9cb8d5555, 0x9a208c6b4f20a418, 0x984f87adf7ae0c7f, 0x32126fced787c88f, 0x11560bf17baa99bc])); - assert_eq!(FROBENIUS_COEFF_FQ6_C2[2], nqr.pow([0x6f1c000012f65ed0, 0xa89e4768f97ac9c7, 0xb972cd024160d353, 0x99d50bf37ef67a2c, 0x1b74591af5b66adb, 0x139aebbdaebae852, 0xf8cb862206509f56, 0x8b1973536493dc49, 0x99ee698623145d35, 0x41e86098b44ad9cd, 0x87e1a5f1c174c3f1, 0x1c2cfc325d7952f])); - assert_eq!(FROBENIUS_COEFF_FQ6_C2[3], nqr.pow([0xb78df9ade6b73c0c, 0x32fbd5a217d9ad55, 0x3b77a4982e40c8c1, 0xe572f598c0af88bd, 0xf1d344617e18c51c, 0xc8f996310b8c74f, 0x9d004c9f8ab7eddc, 0x29b1ae2d87273f89, 0x5af1e1d6efdcddc3, 0xadfc937fcafb8bf3, 0x4cadad82b8c6c8f, 0x1bec505e223f5206, 0x37b74c7c0c656976, 0xd106b2f7954a0bd6, 0x4ada9d9218695304, 0xfc988504777d2165, 0xec8e5154db961e40, 0x2dcafc2dde2a5f])); - assert_eq!(FROBENIUS_COEFF_FQ6_C2[4], nqr.pow([0x8c935ba78e38db20, 0x87954ca512e550ca, 0x9b5088b775541f76, 0x927bd4ccacc554cd, 0x50c779123068903b, 0xa34187eba9997db0, 0x2421ccc1f21995d2, 0xc57ad06d8a8dacbd, 0xe44757754f9eef17, 0xa9e2164459e22d01, 0xaa81ebdfe9432c5d, 0x424743e2816aca4d, 0x6359ca00c6d4b3ed, 0x750579191bf4af52, 0xc918a23c42ff83f0, 0x6d82fffaa748ab1e, 0x1037debfad09ddfa, 0xbac91b7b8bb76a45, 0x1fa0f7e0dcbcb370, 0x1bbd9153595496a3, 0x9983f0d11c5bde4d, 0x4e9cc796925807bc, 0xaf92d02e7a269377, 0x4a6ce9c0550cc])); - assert_eq!(FROBENIUS_COEFF_FQ6_C2[5], nqr.pow([0xf12def24e65d657c, 0x9390d4da3b8b2743, 0xcb663d28b03f2386, 0xc7b4b9814d641aff, 0x4595df2a1c17fdb, 0x62fdb2a173dccf9b, 0xffacc802ddc7ed9a, 0xefb3239110216245, 0xc5ce5cc6bcd304c8, 0x20bc34345a450294, 0xeb6f56747e67b303, 0x2eb0e361781c4bbb, 0x3c5d34c7beb86ee4, 0xd11163ee88b6293a, 0x2aa3833cbcfc5849, 0xd9e4327347a5ad7d, 0xb061b7fb56e5b7a9, 0xf6695f1ac45a8181, 0x7beda4148ace2484, 0x15f0d7dc61c43b30, 0x820c982a69cbbeba, 0xebed957a62c98c12, 0x14ba297be56fdccb, 0x4e3180d3bdf92270, 0xb6288fcee1c6221d, 0x8362c953d215e923, 0xe300883f3a4a2c05, 0x3fa74bcd535127c, 0x3c2f6ef3aa9abb6, 0x78f5f5fc62e8])); + assert_eq!( + FROBENIUS_COEFF_FQ6_C2[1], + nqr.pow([ + 0x26a9ffffffffc71c, + 0x1472aaa9cb8d5555, + 0x9a208c6b4f20a418, + 0x984f87adf7ae0c7f, + 0x32126fced787c88f, + 0x11560bf17baa99bc + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ6_C2[2], + nqr.pow([ + 0x6f1c000012f65ed0, + 0xa89e4768f97ac9c7, + 0xb972cd024160d353, + 0x99d50bf37ef67a2c, + 0x1b74591af5b66adb, + 0x139aebbdaebae852, + 0xf8cb862206509f56, + 0x8b1973536493dc49, + 0x99ee698623145d35, + 0x41e86098b44ad9cd, + 0x87e1a5f1c174c3f1, + 0x1c2cfc325d7952f + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ6_C2[3], + nqr.pow([ + 0xb78df9ade6b73c0c, + 0x32fbd5a217d9ad55, + 0x3b77a4982e40c8c1, + 0xe572f598c0af88bd, + 0xf1d344617e18c51c, + 0xc8f996310b8c74f, + 0x9d004c9f8ab7eddc, + 0x29b1ae2d87273f89, + 0x5af1e1d6efdcddc3, + 0xadfc937fcafb8bf3, + 0x4cadad82b8c6c8f, + 0x1bec505e223f5206, + 0x37b74c7c0c656976, + 0xd106b2f7954a0bd6, + 0x4ada9d9218695304, + 0xfc988504777d2165, + 0xec8e5154db961e40, + 0x2dcafc2dde2a5f + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ6_C2[4], + nqr.pow([ + 0x8c935ba78e38db20, + 0x87954ca512e550ca, + 0x9b5088b775541f76, + 0x927bd4ccacc554cd, + 0x50c779123068903b, + 0xa34187eba9997db0, + 0x2421ccc1f21995d2, + 0xc57ad06d8a8dacbd, + 0xe44757754f9eef17, + 0xa9e2164459e22d01, + 0xaa81ebdfe9432c5d, + 0x424743e2816aca4d, + 0x6359ca00c6d4b3ed, + 0x750579191bf4af52, + 0xc918a23c42ff83f0, + 0x6d82fffaa748ab1e, + 0x1037debfad09ddfa, + 0xbac91b7b8bb76a45, + 0x1fa0f7e0dcbcb370, + 0x1bbd9153595496a3, + 0x9983f0d11c5bde4d, + 0x4e9cc796925807bc, + 0xaf92d02e7a269377, + 0x4a6ce9c0550cc + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ6_C2[5], + nqr.pow([ + 0xf12def24e65d657c, + 0x9390d4da3b8b2743, + 0xcb663d28b03f2386, + 0xc7b4b9814d641aff, + 0x4595df2a1c17fdb, + 0x62fdb2a173dccf9b, + 0xffacc802ddc7ed9a, + 0xefb3239110216245, + 0xc5ce5cc6bcd304c8, + 0x20bc34345a450294, + 0xeb6f56747e67b303, + 0x2eb0e361781c4bbb, + 0x3c5d34c7beb86ee4, + 0xd11163ee88b6293a, + 0x2aa3833cbcfc5849, + 0xd9e4327347a5ad7d, + 0xb061b7fb56e5b7a9, + 0xf6695f1ac45a8181, + 0x7beda4148ace2484, + 0x15f0d7dc61c43b30, + 0x820c982a69cbbeba, + 0xebed957a62c98c12, + 0x14ba297be56fdccb, + 0x4e3180d3bdf92270, + 0xb6288fcee1c6221d, + 0x8362c953d215e923, + 0xe300883f3a4a2c05, + 0x3fa74bcd535127c, + 0x3c2f6ef3aa9abb6, + 0x78f5f5fc62e8 + ]) + ); assert_eq!(FROBENIUS_COEFF_FQ12_C1[0], Fq2::one()); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[1], nqr.pow([0x49aa7ffffffff1c7, 0x51caaaa72e35555, 0xe688231ad3c82906, 0xe613e1eb7deb831f, 0xc849bf3b5e1f223, 0x45582fc5eeaa66f])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[2], nqr.pow([0xdbc7000004bd97b4, 0xea2791da3e5eb271, 0x2e5cb340905834d4, 0xe67542fcdfbd9e8b, 0x86dd1646bd6d9ab6, 0x84e6baef6baeba14, 0x7e32e188819427d5, 0x62c65cd4d924f712, 0x667b9a6188c5174d, 0x507a18262d12b673, 0xe1f8697c705d30fc, 0x70b3f0c975e54b])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[3], nqr.pow(vec![0x6de37e6b79adcf03, 0x4cbef56885f66b55, 0x4edde9260b903230, 0x395cbd66302be22f, 0xfc74d1185f863147, 0x323e658c42e31d3, 0x67401327e2adfb77, 0xca6c6b8b61c9cfe2, 0xd6bc7875bbf73770, 0xeb7f24dff2bee2fc, 0x8132b6b60ae31b23, 0x86fb1417888fd481, 0x8dedd31f03195a5d, 0x3441acbde55282f5, 0x52b6a764861a54c1, 0x3f2621411ddf4859, 0xfb23945536e58790, 0xb72bf0b778a97])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[4], nqr.pow(vec![0xa324d6e9e38e36c8, 0xa1e5532944b95432, 0x66d4222ddd5507dd, 0xe49ef5332b315533, 0x1431de448c1a240e, 0xa8d061faea665f6c, 0x490873307c866574, 0xf15eb41b62a36b2f, 0x7911d5dd53e7bbc5, 0x6a78859116788b40, 0x6aa07af7fa50cb17, 0x5091d0f8a05ab293, 0x98d6728031b52cfb, 0x1d415e4646fd2bd4, 0xb246288f10bfe0fc, 0x9b60bffea9d22ac7, 0x440df7afeb42777e, 0x2eb246dee2edda91, 0xc7e83df8372f2cdc, 0x46ef6454d65525a8, 0x2660fc344716f793, 0xd3a731e5a49601ef, 0x2be4b40b9e89a4dd, 0x129b3a7015433])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[5], nqr.pow(vec![0xfc4b7bc93997595f, 0xa4e435368ee2c9d0, 0xf2d98f4a2c0fc8e1, 0xf1ed2e60535906bf, 0xc116577ca8705ff6, 0x98bf6ca85cf733e6, 0x7feb3200b771fb66, 0x3becc8e444085891, 0x31739731af34c132, 0xc82f0d0d169140a5, 0xfadbd59d1f99ecc0, 0xbac38d85e0712ee, 0x8f174d31efae1bb9, 0x744458fba22d8a4e, 0x4aa8e0cf2f3f1612, 0x76790c9cd1e96b5f, 0x6c186dfed5b96dea, 0x3d9a57c6b116a060, 0x1efb690522b38921, 0x857c35f718710ecc, 0xa083260a9a72efae, 0xfafb655e98b26304, 0x52e8a5ef95bf732, 0x538c6034ef7e489c, 0xed8a23f3b8718887, 0x60d8b254f4857a48, 0x38c0220fce928b01, 0x80fe9d2f354d449f, 0xf0bdbbceaa6aed, 0x1e3d7d7f18ba])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[6], nqr.pow(vec![0x21219610a012ba3c, 0xa5c19ad35375325, 0x4e9df1e497674396, 0xfb05b717c991c6ef, 0x4a1265bca93a32f2, 0xd875ff2a7bdc1f66, 0xc6d8754736c771b2, 0x2d80c759ba5a2ae7, 0x138a20df4b03cc1a, 0xc22d07fe68e93024, 0xd1dc474d3b433133, 0xc22aa5e75044e5c, 0xf657c6fbf9c17ebf, 0xc591a794a58660d, 0x2261850ee1453281, 0xd17d3bd3b7f5efb4, 0xf00cec8ec507d01, 0x2a6a775657a00ae6, 0x5f098a12ff470719, 0x409d194e7b5c5afa, 0x1d66478e982af5b, 0xda425a5b5e01ca3f, 0xf77e4f78747e903c, 0x177d49f73732c6fc, 0xa9618fecabe0e1f4, 0xba5337eac90bd080, 0x66fececdbc35d4e7, 0xa4cd583203d9206f, 0x98391632ceeca596, 0x4946b76e1236ad3f, 0xa0dec64e60e711a1, 0xfcb41ed3605013, 0x8ca8f9692ae1e3a9, 0xd3078bfc28cc1baf, 0xf0536f764e982f82, 0x3125f1a2656])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[7], nqr.pow(vec![0x742754a1f22fdb, 0x2a1955c2dec3a702, 0x9747b28c796d134e, 0xc113a0411f59db79, 0x3bb0fa929853bfc1, 0x28c3c25f8f6fb487, 0xbc2b6c99d3045b34, 0x98fb67d6badde1fd, 0x48841d76a24d2073, 0xd49891145fe93ae6, 0xc772b9c8e74d4099, 0xccf4e7b9907755bb, 0x9cf47b25d42fd908, 0x5616a0c347fc445d, 0xff93b7a7ad1b8a6d, 0xac2099256b78a77a, 0x7804a95b02892e1c, 0x5cf59ca7bfd69776, 0xa7023502acd3c866, 0xc76f4982fcf8f37, 0x51862a5a57ac986e, 0x38b80ed72b1b1023, 0x4a291812066a61e1, 0xcd8a685eff45631, 0x3f40f708764e4fa5, 0x8aa0441891285092, 0x9eff60d71cdf0a9, 0x4fdd9d56517e2bfa, 0x1f3c80d74a28bc85, 0x24617417c064b648, 0x7ddda1e4385d5088, 0xf9e132b11dd32a16, 0xcc957cb8ef66ab99, 0xd4f206d37cb752c5, 0x40de343f28ad616b, 0x8d1f24379068f0e3, 0x6f31d7947ea21137, 0x27311f9c32184061, 0x9eea0664cc78ce5f, 0x7d4151f6fea9a0da, 0x454096fa75bd571a, 0x4fe0f20ecb])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[8], nqr.pow(vec![0x802f5720d0b25710, 0x6714f0a258b85c7c, 0x31394c90afdf16e, 0xe9d2b0c64f957b19, 0xe67c0d9c5e7903ee, 0x3156fdc5443ea8ef, 0x7c4c50524d88c892, 0xc99dc8990c0ad244, 0xd37ababf3649a896, 0x76fe4b838ff7a20c, 0xcf69ee2cec728db3, 0xb83535548e5f41, 0x371147684ccb0c23, 0x194f6f4fa500db52, 0xc4571dc78a4c5374, 0xe4d46d479999ca97, 0x76b6785a615a151c, 0xcceb8bcea7eaf8c1, 0x80d87a6fbe5ae687, 0x6a97ddddb85ce85, 0xd783958f26034204, 0x7144506f2e2e8590, 0x948693d377aef166, 0x8364621ed6f96056, 0xf021777c4c09ee2d, 0xc6cf5e746ecd50b, 0xa2337b7aa22743df, 0xae753f8bbacab39c, 0xfc782a9e34d3c1cc, 0x21b827324fe494d9, 0x5692ce350ed03b38, 0xf323a2b3cd0481b0, 0xe859c97a4ccad2e3, 0x48434b70381e4503, 0x46042d62e4132ed8, 0x48c4d6f56122e2f2, 0xf87711ab9f5c1af7, 0xb14b7a054759b469, 0x8eb0a96993ffa9aa, 0x9b21fb6fc58b760c, 0xf3abdd115d2e7d25, 0xf7beac3d4d12409c, 0x40a5585cce69bf03, 0x697881e1ba22d5a8, 0x3d6c04e6ad373fd9, 0x849871bf627be886, 0x550f4b9b71b28ef9, 0x81d2e0d78])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[9], nqr.pow(vec![0x4af4accf7de0b977, 0x742485e21805b4ee, 0xee388fbc4ac36dec, 0x1e199da57ad178a, 0xc27c12b292c6726a, 0x162e6ed84505b5e8, 0xe191683f336e09df, 0x17deb7e8d1e0fce6, 0xd944f19ad06f5836, 0x4c5f5e59f6276026, 0xf1ba9c7c148a38a8, 0xd205fe2dba72b326, 0x9a2cf2a4c289824e, 0x4f47ad512c39e24d, 0xc5894d984000ea09, 0x2974c03ff7cf01fa, 0xfcd243b48cb99a22, 0x2b5150c9313ac1e8, 0x9089f37c7fc80eda, 0x989540cc9a7aea56, 0x1ab1d4e337e63018, 0x42b546c30d357e43, 0x1c6abc04f76233d9, 0x78b3b8d88bf73e47, 0x151c4e4c45dc68e6, 0x519a79c4f54397ed, 0x93f5b51535a127c5, 0x5fc51b6f52fa153e, 0x2e0504f2d4a965c3, 0xc85bd3a3da52bffe, 0x98c60957a46a89ef, 0x48c03b5976b91cae, 0xc6598040a0a61438, 0xbf0b49dc255953af, 0xb78dff905b628ab4, 0x68140b797ba74ab8, 0x116cf037991d1143, 0x2f7fe82e58acb0b8, 0xc20bf7a8f7be5d45, 0x86c2905c338d5709, 0xff13a3ae6c8ace3d, 0xb6f95e2282d08337, 0xd49f7b313e9cbf29, 0xf794517193a1ce8c, 0x39641fecb596a874, 0x411c4c4edf462fb3, 0x3f8cd55c10cf25b4, 0x2bdd7ea165e860b6, 0xacd7d2cef4caa193, 0x6558a1d09a05f96, 0x1f52b5f5b546fc20, 0x4ee22a5a8c250c12, 0xd3a63a54a205b6b3, 0xd2ff5be8])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[10], nqr.pow(vec![0xe5953a4f96cdda44, 0x336b2d734cbc32bb, 0x3f79bfe3cd7410e, 0x267ae19aaa0f0332, 0x85a9c4db78d5c749, 0x90996b046b5dc7d8, 0x8945eae9820afc6a, 0x2644ddea2b036bd, 0x39898e35ac2e3819, 0x2574eab095659ab9, 0x65953d51ac5ea798, 0xc6b8c7afe6752466, 0x40e9e993e9286544, 0x7e0ad34ad9700ea0, 0xac1015eba2c69222, 0x24f057a19239b5d8, 0x2043b48c8a3767eb, 0x1117c124a75d7ff4, 0x433cfd1a09fb3ce7, 0x25b087ce4bcf7fb, 0xbcee0dc53a3e5bdb, 0xbffda040cf028735, 0xf7cf103a25512acc, 0x31d4ecda673130b9, 0xea0906dab18461e6, 0x5a40585a5ac3050d, 0x803358fc14fd0eda, 0x3678ca654eada770, 0x7b91a1293a45e33e, 0xcd5e5b8ea8530e43, 0x21ae563ab34da266, 0xecb00dad60df8894, 0x77fe53e652facfef, 0x9b7d1ad0b00244ec, 0xe695df5ca73f801, 0x23cdb21feeab0149, 0x14de113e7ea810d9, 0x52600cd958dac7e7, 0xc83392c14667e488, 0x9f808444bc1717fc, 0x56facb4bcf7c788f, 0x8bcad53245fc3ca0, 0xdef661e83f27d81c, 0x37d4ebcac9ad87e5, 0x6fe8b24f5cdb9324, 0xee08a26c1197654c, 0xc98b22f65f237e9a, 0xf54873a908ed3401, 0x6e1cb951d41f3f3, 0x290b2250a54e8df6, 0x7f36d51eb1db669e, 0xb08c7ed81a6ee43e, 0x95e1c90fb092f680, 0x429e4afd0e8b820, 0x2c14a83ee87d715c, 0xf37267575cfc8af5, 0xb99e9afeda3c2c30, 0x8f0f69da75792d5a, 0x35074a85a533c73, 0x156ed119])); - assert_eq!(FROBENIUS_COEFF_FQ12_C1[11], nqr.pow(vec![0x107db680942de533, 0x6262b24d2052393b, 0x6136df824159ebc, 0xedb052c9970c5deb, 0xca813aea916c3777, 0xf49dacb9d76c1788, 0x624941bd372933bb, 0xa5e60c2520638331, 0xb38b661683411074, 0x1d2c9af4c43d962b, 0x17d807a0f14aa830, 0x6e6581a51012c108, 0x668a537e5b35e6f5, 0x6c396cf3782dca5d, 0x33b679d1bff536ed, 0x736cce41805d90aa, 0x8a562f369eb680bf, 0x9f61aa208a11ded8, 0x43dd89dd94d20f35, 0xcf84c6610575c10a, 0x9f318d49cf2fe8e6, 0xbbc6e5f25a6e434e, 0x6528c433d11d987b, 0xffced71cc48c0e8a, 0x4cbb1474f4cb2a26, 0x66a035c0b28b7231, 0xa6f2875faa1a82ae, 0xdd1ea3deff818b02, 0xe0cfdf0dcdecf701, 0x9aefa231f2f6d23, 0xfb251297efa06746, 0x5a40d367df985538, 0x1ea31d69ab506fed, 0xc64ea8280e89a73f, 0x969acf9f2d4496f4, 0xe84c9181ee60c52c, 0xc60f27fc19fc6ca4, 0x760b33d850154048, 0x84f69080f66c8457, 0xc0192ba0fabf640e, 0xd2c338765c23a3a8, 0xa7838c20f02cec6c, 0xb7cf01d020572877, 0xd63ffaeba0be200a, 0xf7492baeb5f041ac, 0x8602c5212170d117, 0xad9b2e83a5a42068, 0x2461829b3ba1083e, 0x7c34650da5295273, 0xdc824ba800a8265a, 0xd18d9b47836af7b2, 0x3af78945c58cbf4d, 0x7ed9575b8596906c, 0x6d0c133895009a66, 0x53bc1247ea349fe1, 0x6b3063078d41aa7a, 0x6184acd8cd880b33, 0x76f4d15503fd1b96, 0x7a9afd61eef25746, 0xce974aadece60609, 0x88ca59546a8ceafd, 0x6d29391c41a0ac07, 0x443843a60e0f46a6, 0xa1590f62fd2602c7, 0x536d5b15b514373f, 0x22d582b])); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[1], + nqr.pow([ + 0x49aa7ffffffff1c7, + 0x51caaaa72e35555, + 0xe688231ad3c82906, + 0xe613e1eb7deb831f, + 0xc849bf3b5e1f223, + 0x45582fc5eeaa66f + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[2], + nqr.pow([ + 0xdbc7000004bd97b4, + 0xea2791da3e5eb271, + 0x2e5cb340905834d4, + 0xe67542fcdfbd9e8b, + 0x86dd1646bd6d9ab6, + 0x84e6baef6baeba14, + 0x7e32e188819427d5, + 0x62c65cd4d924f712, + 0x667b9a6188c5174d, + 0x507a18262d12b673, + 0xe1f8697c705d30fc, + 0x70b3f0c975e54b + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[3], + nqr.pow(vec![ + 0x6de37e6b79adcf03, + 0x4cbef56885f66b55, + 0x4edde9260b903230, + 0x395cbd66302be22f, + 0xfc74d1185f863147, + 0x323e658c42e31d3, + 0x67401327e2adfb77, + 0xca6c6b8b61c9cfe2, + 0xd6bc7875bbf73770, + 0xeb7f24dff2bee2fc, + 0x8132b6b60ae31b23, + 0x86fb1417888fd481, + 0x8dedd31f03195a5d, + 0x3441acbde55282f5, + 0x52b6a764861a54c1, + 0x3f2621411ddf4859, + 0xfb23945536e58790, + 0xb72bf0b778a97, + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[4], + nqr.pow(vec![ + 0xa324d6e9e38e36c8, + 0xa1e5532944b95432, + 0x66d4222ddd5507dd, + 0xe49ef5332b315533, + 0x1431de448c1a240e, + 0xa8d061faea665f6c, + 0x490873307c866574, + 0xf15eb41b62a36b2f, + 0x7911d5dd53e7bbc5, + 0x6a78859116788b40, + 0x6aa07af7fa50cb17, + 0x5091d0f8a05ab293, + 0x98d6728031b52cfb, + 0x1d415e4646fd2bd4, + 0xb246288f10bfe0fc, + 0x9b60bffea9d22ac7, + 0x440df7afeb42777e, + 0x2eb246dee2edda91, + 0xc7e83df8372f2cdc, + 0x46ef6454d65525a8, + 0x2660fc344716f793, + 0xd3a731e5a49601ef, + 0x2be4b40b9e89a4dd, + 0x129b3a7015433, + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[5], + nqr.pow(vec![ + 0xfc4b7bc93997595f, + 0xa4e435368ee2c9d0, + 0xf2d98f4a2c0fc8e1, + 0xf1ed2e60535906bf, + 0xc116577ca8705ff6, + 0x98bf6ca85cf733e6, + 0x7feb3200b771fb66, + 0x3becc8e444085891, + 0x31739731af34c132, + 0xc82f0d0d169140a5, + 0xfadbd59d1f99ecc0, + 0xbac38d85e0712ee, + 0x8f174d31efae1bb9, + 0x744458fba22d8a4e, + 0x4aa8e0cf2f3f1612, + 0x76790c9cd1e96b5f, + 0x6c186dfed5b96dea, + 0x3d9a57c6b116a060, + 0x1efb690522b38921, + 0x857c35f718710ecc, + 0xa083260a9a72efae, + 0xfafb655e98b26304, + 0x52e8a5ef95bf732, + 0x538c6034ef7e489c, + 0xed8a23f3b8718887, + 0x60d8b254f4857a48, + 0x38c0220fce928b01, + 0x80fe9d2f354d449f, + 0xf0bdbbceaa6aed, + 0x1e3d7d7f18ba, + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[6], + nqr.pow(vec![ + 0x21219610a012ba3c, + 0xa5c19ad35375325, + 0x4e9df1e497674396, + 0xfb05b717c991c6ef, + 0x4a1265bca93a32f2, + 0xd875ff2a7bdc1f66, + 0xc6d8754736c771b2, + 0x2d80c759ba5a2ae7, + 0x138a20df4b03cc1a, + 0xc22d07fe68e93024, + 0xd1dc474d3b433133, + 0xc22aa5e75044e5c, + 0xf657c6fbf9c17ebf, + 0xc591a794a58660d, + 0x2261850ee1453281, + 0xd17d3bd3b7f5efb4, + 0xf00cec8ec507d01, + 0x2a6a775657a00ae6, + 0x5f098a12ff470719, + 0x409d194e7b5c5afa, + 0x1d66478e982af5b, + 0xda425a5b5e01ca3f, + 0xf77e4f78747e903c, + 0x177d49f73732c6fc, + 0xa9618fecabe0e1f4, + 0xba5337eac90bd080, + 0x66fececdbc35d4e7, + 0xa4cd583203d9206f, + 0x98391632ceeca596, + 0x4946b76e1236ad3f, + 0xa0dec64e60e711a1, + 0xfcb41ed3605013, + 0x8ca8f9692ae1e3a9, + 0xd3078bfc28cc1baf, + 0xf0536f764e982f82, + 0x3125f1a2656, + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[7], + nqr.pow(vec![ + 0x742754a1f22fdb, + 0x2a1955c2dec3a702, + 0x9747b28c796d134e, + 0xc113a0411f59db79, + 0x3bb0fa929853bfc1, + 0x28c3c25f8f6fb487, + 0xbc2b6c99d3045b34, + 0x98fb67d6badde1fd, + 0x48841d76a24d2073, + 0xd49891145fe93ae6, + 0xc772b9c8e74d4099, + 0xccf4e7b9907755bb, + 0x9cf47b25d42fd908, + 0x5616a0c347fc445d, + 0xff93b7a7ad1b8a6d, + 0xac2099256b78a77a, + 0x7804a95b02892e1c, + 0x5cf59ca7bfd69776, + 0xa7023502acd3c866, + 0xc76f4982fcf8f37, + 0x51862a5a57ac986e, + 0x38b80ed72b1b1023, + 0x4a291812066a61e1, + 0xcd8a685eff45631, + 0x3f40f708764e4fa5, + 0x8aa0441891285092, + 0x9eff60d71cdf0a9, + 0x4fdd9d56517e2bfa, + 0x1f3c80d74a28bc85, + 0x24617417c064b648, + 0x7ddda1e4385d5088, + 0xf9e132b11dd32a16, + 0xcc957cb8ef66ab99, + 0xd4f206d37cb752c5, + 0x40de343f28ad616b, + 0x8d1f24379068f0e3, + 0x6f31d7947ea21137, + 0x27311f9c32184061, + 0x9eea0664cc78ce5f, + 0x7d4151f6fea9a0da, + 0x454096fa75bd571a, + 0x4fe0f20ecb, + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[8], + nqr.pow(vec![ + 0x802f5720d0b25710, + 0x6714f0a258b85c7c, + 0x31394c90afdf16e, + 0xe9d2b0c64f957b19, + 0xe67c0d9c5e7903ee, + 0x3156fdc5443ea8ef, + 0x7c4c50524d88c892, + 0xc99dc8990c0ad244, + 0xd37ababf3649a896, + 0x76fe4b838ff7a20c, + 0xcf69ee2cec728db3, + 0xb83535548e5f41, + 0x371147684ccb0c23, + 0x194f6f4fa500db52, + 0xc4571dc78a4c5374, + 0xe4d46d479999ca97, + 0x76b6785a615a151c, + 0xcceb8bcea7eaf8c1, + 0x80d87a6fbe5ae687, + 0x6a97ddddb85ce85, + 0xd783958f26034204, + 0x7144506f2e2e8590, + 0x948693d377aef166, + 0x8364621ed6f96056, + 0xf021777c4c09ee2d, + 0xc6cf5e746ecd50b, + 0xa2337b7aa22743df, + 0xae753f8bbacab39c, + 0xfc782a9e34d3c1cc, + 0x21b827324fe494d9, + 0x5692ce350ed03b38, + 0xf323a2b3cd0481b0, + 0xe859c97a4ccad2e3, + 0x48434b70381e4503, + 0x46042d62e4132ed8, + 0x48c4d6f56122e2f2, + 0xf87711ab9f5c1af7, + 0xb14b7a054759b469, + 0x8eb0a96993ffa9aa, + 0x9b21fb6fc58b760c, + 0xf3abdd115d2e7d25, + 0xf7beac3d4d12409c, + 0x40a5585cce69bf03, + 0x697881e1ba22d5a8, + 0x3d6c04e6ad373fd9, + 0x849871bf627be886, + 0x550f4b9b71b28ef9, + 0x81d2e0d78, + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[9], + nqr.pow(vec![ + 0x4af4accf7de0b977, + 0x742485e21805b4ee, + 0xee388fbc4ac36dec, + 0x1e199da57ad178a, + 0xc27c12b292c6726a, + 0x162e6ed84505b5e8, + 0xe191683f336e09df, + 0x17deb7e8d1e0fce6, + 0xd944f19ad06f5836, + 0x4c5f5e59f6276026, + 0xf1ba9c7c148a38a8, + 0xd205fe2dba72b326, + 0x9a2cf2a4c289824e, + 0x4f47ad512c39e24d, + 0xc5894d984000ea09, + 0x2974c03ff7cf01fa, + 0xfcd243b48cb99a22, + 0x2b5150c9313ac1e8, + 0x9089f37c7fc80eda, + 0x989540cc9a7aea56, + 0x1ab1d4e337e63018, + 0x42b546c30d357e43, + 0x1c6abc04f76233d9, + 0x78b3b8d88bf73e47, + 0x151c4e4c45dc68e6, + 0x519a79c4f54397ed, + 0x93f5b51535a127c5, + 0x5fc51b6f52fa153e, + 0x2e0504f2d4a965c3, + 0xc85bd3a3da52bffe, + 0x98c60957a46a89ef, + 0x48c03b5976b91cae, + 0xc6598040a0a61438, + 0xbf0b49dc255953af, + 0xb78dff905b628ab4, + 0x68140b797ba74ab8, + 0x116cf037991d1143, + 0x2f7fe82e58acb0b8, + 0xc20bf7a8f7be5d45, + 0x86c2905c338d5709, + 0xff13a3ae6c8ace3d, + 0xb6f95e2282d08337, + 0xd49f7b313e9cbf29, + 0xf794517193a1ce8c, + 0x39641fecb596a874, + 0x411c4c4edf462fb3, + 0x3f8cd55c10cf25b4, + 0x2bdd7ea165e860b6, + 0xacd7d2cef4caa193, + 0x6558a1d09a05f96, + 0x1f52b5f5b546fc20, + 0x4ee22a5a8c250c12, + 0xd3a63a54a205b6b3, + 0xd2ff5be8, + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[10], + nqr.pow(vec![ + 0xe5953a4f96cdda44, + 0x336b2d734cbc32bb, + 0x3f79bfe3cd7410e, + 0x267ae19aaa0f0332, + 0x85a9c4db78d5c749, + 0x90996b046b5dc7d8, + 0x8945eae9820afc6a, + 0x2644ddea2b036bd, + 0x39898e35ac2e3819, + 0x2574eab095659ab9, + 0x65953d51ac5ea798, + 0xc6b8c7afe6752466, + 0x40e9e993e9286544, + 0x7e0ad34ad9700ea0, + 0xac1015eba2c69222, + 0x24f057a19239b5d8, + 0x2043b48c8a3767eb, + 0x1117c124a75d7ff4, + 0x433cfd1a09fb3ce7, + 0x25b087ce4bcf7fb, + 0xbcee0dc53a3e5bdb, + 0xbffda040cf028735, + 0xf7cf103a25512acc, + 0x31d4ecda673130b9, + 0xea0906dab18461e6, + 0x5a40585a5ac3050d, + 0x803358fc14fd0eda, + 0x3678ca654eada770, + 0x7b91a1293a45e33e, + 0xcd5e5b8ea8530e43, + 0x21ae563ab34da266, + 0xecb00dad60df8894, + 0x77fe53e652facfef, + 0x9b7d1ad0b00244ec, + 0xe695df5ca73f801, + 0x23cdb21feeab0149, + 0x14de113e7ea810d9, + 0x52600cd958dac7e7, + 0xc83392c14667e488, + 0x9f808444bc1717fc, + 0x56facb4bcf7c788f, + 0x8bcad53245fc3ca0, + 0xdef661e83f27d81c, + 0x37d4ebcac9ad87e5, + 0x6fe8b24f5cdb9324, + 0xee08a26c1197654c, + 0xc98b22f65f237e9a, + 0xf54873a908ed3401, + 0x6e1cb951d41f3f3, + 0x290b2250a54e8df6, + 0x7f36d51eb1db669e, + 0xb08c7ed81a6ee43e, + 0x95e1c90fb092f680, + 0x429e4afd0e8b820, + 0x2c14a83ee87d715c, + 0xf37267575cfc8af5, + 0xb99e9afeda3c2c30, + 0x8f0f69da75792d5a, + 0x35074a85a533c73, + 0x156ed119, + ]) + ); + assert_eq!( + FROBENIUS_COEFF_FQ12_C1[11], + nqr.pow(vec![ + 0x107db680942de533, + 0x6262b24d2052393b, + 0x6136df824159ebc, + 0xedb052c9970c5deb, + 0xca813aea916c3777, + 0xf49dacb9d76c1788, + 0x624941bd372933bb, + 0xa5e60c2520638331, + 0xb38b661683411074, + 0x1d2c9af4c43d962b, + 0x17d807a0f14aa830, + 0x6e6581a51012c108, + 0x668a537e5b35e6f5, + 0x6c396cf3782dca5d, + 0x33b679d1bff536ed, + 0x736cce41805d90aa, + 0x8a562f369eb680bf, + 0x9f61aa208a11ded8, + 0x43dd89dd94d20f35, + 0xcf84c6610575c10a, + 0x9f318d49cf2fe8e6, + 0xbbc6e5f25a6e434e, + 0x6528c433d11d987b, + 0xffced71cc48c0e8a, + 0x4cbb1474f4cb2a26, + 0x66a035c0b28b7231, + 0xa6f2875faa1a82ae, + 0xdd1ea3deff818b02, + 0xe0cfdf0dcdecf701, + 0x9aefa231f2f6d23, + 0xfb251297efa06746, + 0x5a40d367df985538, + 0x1ea31d69ab506fed, + 0xc64ea8280e89a73f, + 0x969acf9f2d4496f4, + 0xe84c9181ee60c52c, + 0xc60f27fc19fc6ca4, + 0x760b33d850154048, + 0x84f69080f66c8457, + 0xc0192ba0fabf640e, + 0xd2c338765c23a3a8, + 0xa7838c20f02cec6c, + 0xb7cf01d020572877, + 0xd63ffaeba0be200a, + 0xf7492baeb5f041ac, + 0x8602c5212170d117, + 0xad9b2e83a5a42068, + 0x2461829b3ba1083e, + 0x7c34650da5295273, + 0xdc824ba800a8265a, + 0xd18d9b47836af7b2, + 0x3af78945c58cbf4d, + 0x7ed9575b8596906c, + 0x6d0c133895009a66, + 0x53bc1247ea349fe1, + 0x6b3063078d41aa7a, + 0x6184acd8cd880b33, + 0x76f4d15503fd1b96, + 0x7a9afd61eef25746, + 0xce974aadece60609, + 0x88ca59546a8ceafd, + 0x6d29391c41a0ac07, + 0x443843a60e0f46a6, + 0xa1590f62fd2602c7, + 0x536d5b15b514373f, + 0x22d582b, + ]) + ); } #[test] @@ -893,7 +1895,7 @@ fn test_neg_one() { } #[cfg(test)] -use rand::{SeedableRng, XorShiftRng, Rand}; +use rand::{Rand, SeedableRng, XorShiftRng}; #[test] fn test_fq_repr_ordering() { @@ -907,12 +1909,30 @@ fn test_fq_repr_ordering() { assert!(b > a); } - assert_equality(FqRepr([9999, 9999, 9999, 9999, 9999, 9999]), FqRepr([9999, 9999, 9999, 9999, 9999, 9999])); - assert_equality(FqRepr([9999, 9998, 9999, 9999, 9999, 9999]), FqRepr([9999, 9998, 9999, 9999, 9999, 9999])); - assert_equality(FqRepr([9999, 9999, 9999, 9997, 9999, 9999]), FqRepr([9999, 9999, 9999, 9997, 9999, 9999])); - assert_lt(FqRepr([9999, 9999, 9999, 9997, 9999, 9998]), FqRepr([9999, 9999, 9999, 9997, 9999, 9999])); - assert_lt(FqRepr([9999, 9999, 9999, 9997, 9998, 9999]), FqRepr([9999, 9999, 9999, 9997, 9999, 9999])); - assert_lt(FqRepr([9, 9999, 9999, 9997, 9998, 9999]), FqRepr([9999, 9999, 9999, 9997, 9999, 9999])); + assert_equality( + FqRepr([9999, 9999, 9999, 9999, 9999, 9999]), + FqRepr([9999, 9999, 9999, 9999, 9999, 9999]), + ); + assert_equality( + FqRepr([9999, 9998, 9999, 9999, 9999, 9999]), + FqRepr([9999, 9998, 9999, 9999, 9999, 9999]), + ); + assert_equality( + FqRepr([9999, 9999, 9999, 9997, 9999, 9999]), + FqRepr([9999, 9999, 9999, 9997, 9999, 9999]), + ); + assert_lt( + FqRepr([9999, 9999, 9999, 9997, 9999, 9998]), + FqRepr([9999, 9999, 9999, 9997, 9999, 9999]), + ); + assert_lt( + FqRepr([9999, 9999, 9999, 9997, 9998, 9999]), + FqRepr([9999, 9999, 9999, 9997, 9999, 9999]), + ); + assert_lt( + FqRepr([9, 9999, 9999, 9997, 9998, 9999]), + FqRepr([9999, 9999, 9999, 9997, 9999, 9999]), + ); } #[test] @@ -941,13 +1961,40 @@ fn test_fq_repr_is_zero() { #[test] fn test_fq_repr_div2() { - let mut a = FqRepr([0x8b0ad39f8dd7482a, 0x147221c9a7178b69, 0x54764cb08d8a6aa0, 0x8519d708e1d83041, 0x41f82777bd13fdb, 0xf43944578f9b771b]); + let mut a = FqRepr([ + 0x8b0ad39f8dd7482a, + 0x147221c9a7178b69, + 0x54764cb08d8a6aa0, + 0x8519d708e1d83041, + 0x41f82777bd13fdb, + 0xf43944578f9b771b, + ]); a.div2(); - assert_eq!(a, FqRepr([0xc58569cfc6eba415, 0xa3910e4d38bc5b4, 0xaa3b265846c53550, 0xc28ceb8470ec1820, 0x820fc13bbde89fed, 0x7a1ca22bc7cdbb8d])); + assert_eq!( + a, + FqRepr([ + 0xc58569cfc6eba415, + 0xa3910e4d38bc5b4, + 0xaa3b265846c53550, + 0xc28ceb8470ec1820, + 0x820fc13bbde89fed, + 0x7a1ca22bc7cdbb8d + ]) + ); for _ in 0..10 { a.div2(); } - assert_eq!(a, FqRepr([0x6d31615a73f1bae9, 0x54028e443934e2f1, 0x82a8ec99611b14d, 0xfb70a33ae11c3b06, 0xe36083f04eef7a27, 0x1e87288af1f36e])); + assert_eq!( + a, + FqRepr([ + 0x6d31615a73f1bae9, + 0x54028e443934e2f1, + 0x82a8ec99611b14d, + 0xfb70a33ae11c3b06, + 0xe36083f04eef7a27, + 0x1e87288af1f36e + ]) + ); for _ in 0..300 { a.div2(); } @@ -966,26 +2013,61 @@ fn test_fq_repr_div2() { #[test] fn test_fq_repr_shr() { - let mut a = FqRepr([0xaa5cdd6172847ffd, 0x43242c06aed55287, 0x9ddd5b312f3dd104, 0xc5541fd48046b7e7, 0x16080cf4071e0b05, 0x1225f2901aea514e]); + let mut a = FqRepr([ + 0xaa5cdd6172847ffd, + 0x43242c06aed55287, + 0x9ddd5b312f3dd104, + 0xc5541fd48046b7e7, + 0x16080cf4071e0b05, + 0x1225f2901aea514e, + ]); a.shr(0); assert_eq!( a, - FqRepr([0xaa5cdd6172847ffd, 0x43242c06aed55287, 0x9ddd5b312f3dd104, 0xc5541fd48046b7e7, 0x16080cf4071e0b05, 0x1225f2901aea514e]) + FqRepr([ + 0xaa5cdd6172847ffd, + 0x43242c06aed55287, + 0x9ddd5b312f3dd104, + 0xc5541fd48046b7e7, + 0x16080cf4071e0b05, + 0x1225f2901aea514e + ]) ); a.shr(1); assert_eq!( a, - FqRepr([0xd52e6eb0b9423ffe, 0x21921603576aa943, 0xceeead98979ee882, 0xe2aa0fea40235bf3, 0xb04067a038f0582, 0x912f9480d7528a7]) + FqRepr([ + 0xd52e6eb0b9423ffe, + 0x21921603576aa943, + 0xceeead98979ee882, + 0xe2aa0fea40235bf3, + 0xb04067a038f0582, + 0x912f9480d7528a7 + ]) ); a.shr(50); assert_eq!( a, - FqRepr([0x8580d5daaa50f54b, 0xab6625e7ba208864, 0x83fa9008d6fcf3bb, 0x19e80e3c160b8aa, 0xbe52035d4a29c2c1, 0x244]) + FqRepr([ + 0x8580d5daaa50f54b, + 0xab6625e7ba208864, + 0x83fa9008d6fcf3bb, + 0x19e80e3c160b8aa, + 0xbe52035d4a29c2c1, + 0x244 + ]) ); a.shr(130); assert_eq!( a, - FqRepr([0xa0fea40235bf3cee, 0x4067a038f0582e2a, 0x2f9480d7528a70b0, 0x91, 0x0, 0x0]) + FqRepr([ + 0xa0fea40235bf3cee, + 0x4067a038f0582e2a, + 0x2f9480d7528a70b0, + 0x91, + 0x0, + 0x0 + ]) ); a.shr(64); assert_eq!( @@ -1002,7 +2084,10 @@ fn test_fq_repr_mul2() { for _ in 0..60 { a.mul2(); } - assert_eq!(a, FqRepr([0x6000000000000000, 0xb0acd6c9, 0x0, 0x0, 0x0, 0x0])); + assert_eq!( + a, + FqRepr([0x6000000000000000, 0xb0acd6c9, 0x0, 0x0, 0x0, 0x0]) + ); for _ in 0..300 { a.mul2(); } @@ -1033,9 +2118,33 @@ fn test_fq_repr_num_bits() { fn test_fq_repr_sub_noborrow() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let mut t = FqRepr([0x827a4a08041ebd9, 0x3c239f3dcc8f0d6b, 0x9ab46a912d555364, 0x196936b17b43910b, 0xad0eb3948a5c34fd, 0xd56f7b5ab8b5ce8]); - t.sub_noborrow(&FqRepr([0xc7867917187ca02b, 0x5d75679d4911ffef, 0x8c5b3e48b1a71c15, 0x6a427ae846fd66aa, 0x7a37e7265ee1eaf9, 0x7c0577a26f59d5])); - assert!(t == FqRepr([0x40a12b8967c54bae, 0xdeae37a0837d0d7b, 0xe592c487bae374e, 0xaf26bbc934462a61, 0x32d6cc6e2b7a4a03, 0xcdaf23e091c0313])); + let mut t = FqRepr([ + 0x827a4a08041ebd9, + 0x3c239f3dcc8f0d6b, + 0x9ab46a912d555364, + 0x196936b17b43910b, + 0xad0eb3948a5c34fd, + 0xd56f7b5ab8b5ce8, + ]); + t.sub_noborrow(&FqRepr([ + 0xc7867917187ca02b, + 0x5d75679d4911ffef, + 0x8c5b3e48b1a71c15, + 0x6a427ae846fd66aa, + 0x7a37e7265ee1eaf9, + 0x7c0577a26f59d5, + ])); + assert!( + t + == FqRepr([ + 0x40a12b8967c54bae, + 0xdeae37a0837d0d7b, + 0xe592c487bae374e, + 0xaf26bbc934462a61, + 0x32d6cc6e2b7a4a03, + 0xcdaf23e091c0313 + ]) + ); for _ in 0..1000 { let mut a = FqRepr::rand(&mut rng); @@ -1064,18 +2173,66 @@ fn test_fq_repr_sub_noborrow() { } // Subtracting q+1 from q should produce -1 (mod 2**384) - let mut qplusone = FqRepr([0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]); - qplusone.sub_noborrow(&FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])); - assert_eq!(qplusone, FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])); + let mut qplusone = FqRepr([ + 0xb9feffffffffaaab, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a, + ]); + qplusone.sub_noborrow(&FqRepr([ + 0xb9feffffffffaaac, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a, + ])); + assert_eq!( + qplusone, + FqRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff + ]) + ); } #[test] fn test_fq_repr_add_nocarry() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let mut t = FqRepr([0x827a4a08041ebd9, 0x3c239f3dcc8f0d6b, 0x9ab46a912d555364, 0x196936b17b43910b, 0xad0eb3948a5c34fd, 0xd56f7b5ab8b5ce8]); - t.add_nocarry(&FqRepr([0xc7867917187ca02b, 0x5d75679d4911ffef, 0x8c5b3e48b1a71c15, 0x6a427ae846fd66aa, 0x7a37e7265ee1eaf9, 0x7c0577a26f59d5])); - assert!(t == FqRepr([0xcfae1db798be8c04, 0x999906db15a10d5a, 0x270fa8d9defc6f79, 0x83abb199c240f7b6, 0x27469abae93e1ff6, 0xdd2fd2d4dfab6be])); + let mut t = FqRepr([ + 0x827a4a08041ebd9, + 0x3c239f3dcc8f0d6b, + 0x9ab46a912d555364, + 0x196936b17b43910b, + 0xad0eb3948a5c34fd, + 0xd56f7b5ab8b5ce8, + ]); + t.add_nocarry(&FqRepr([ + 0xc7867917187ca02b, + 0x5d75679d4911ffef, + 0x8c5b3e48b1a71c15, + 0x6a427ae846fd66aa, + 0x7a37e7265ee1eaf9, + 0x7c0577a26f59d5, + ])); + assert!( + t + == FqRepr([ + 0xcfae1db798be8c04, + 0x999906db15a10d5a, + 0x270fa8d9defc6f79, + 0x83abb199c240f7b6, + 0x27469abae93e1ff6, + 0xdd2fd2d4dfab6be + ]) + ); // Test for the associativity of addition. for _ in 0..1000 { @@ -1120,7 +2277,14 @@ fn test_fq_repr_add_nocarry() { } // Adding 1 to (2^384 - 1) should produce zero - let mut x = FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); + let mut x = FqRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + ]); x.add_nocarry(&FqRepr::from(1)); assert!(x.is_zero()); } @@ -1132,8 +2296,24 @@ fn test_fq_is_valid() { a.0.sub_noborrow(&FqRepr::from(1)); assert!(a.is_valid()); assert!(Fq(FqRepr::from(0)).is_valid()); - assert!(Fq(FqRepr([0xdf4671abd14dab3e, 0xe2dc0c9f534fbd33, 0x31ca6c880cc444a6, 0x257a67e70ef33359, 0xf9b29e493f899b36, 0x17c8be1800b9f059])).is_valid()); - assert!(!Fq(FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])).is_valid()); + assert!( + Fq(FqRepr([ + 0xdf4671abd14dab3e, + 0xe2dc0c9f534fbd33, + 0x31ca6c880cc444a6, + 0x257a67e70ef33359, + 0xf9b29e493f899b36, + 0x17c8be1800b9f059 + ])).is_valid() + ); + assert!(!Fq(FqRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff + ])).is_valid()); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1147,25 +2327,100 @@ fn test_fq_is_valid() { fn test_fq_add_assign() { { // Random number - let mut tmp = Fq(FqRepr([0x624434821df92b69, 0x503260c04fd2e2ea, 0xd9df726e0d16e8ce, 0xfbcb39adfd5dfaeb, 0x86b8a22b0c88b112, 0x165a2ed809e4201b])); + let mut tmp = Fq(FqRepr([ + 0x624434821df92b69, + 0x503260c04fd2e2ea, + 0xd9df726e0d16e8ce, + 0xfbcb39adfd5dfaeb, + 0x86b8a22b0c88b112, + 0x165a2ed809e4201b, + ])); assert!(tmp.is_valid()); // Test that adding zero has no effect. tmp.add_assign(&Fq(FqRepr::from(0))); - assert_eq!(tmp, Fq(FqRepr([0x624434821df92b69, 0x503260c04fd2e2ea, 0xd9df726e0d16e8ce, 0xfbcb39adfd5dfaeb, 0x86b8a22b0c88b112, 0x165a2ed809e4201b]))); + assert_eq!( + tmp, + Fq(FqRepr([ + 0x624434821df92b69, + 0x503260c04fd2e2ea, + 0xd9df726e0d16e8ce, + 0xfbcb39adfd5dfaeb, + 0x86b8a22b0c88b112, + 0x165a2ed809e4201b + ])) + ); // Add one and test for the result. tmp.add_assign(&Fq(FqRepr::from(1))); - assert_eq!(tmp, Fq(FqRepr([0x624434821df92b6a, 0x503260c04fd2e2ea, 0xd9df726e0d16e8ce, 0xfbcb39adfd5dfaeb, 0x86b8a22b0c88b112, 0x165a2ed809e4201b]))); + assert_eq!( + tmp, + Fq(FqRepr([ + 0x624434821df92b6a, + 0x503260c04fd2e2ea, + 0xd9df726e0d16e8ce, + 0xfbcb39adfd5dfaeb, + 0x86b8a22b0c88b112, + 0x165a2ed809e4201b + ])) + ); // Add another random number that exercises the reduction. - tmp.add_assign(&Fq(FqRepr([0x374d8f8ea7a648d8, 0xe318bb0ebb8bfa9b, 0x613d996f0a95b400, 0x9fac233cb7e4fef1, 0x67e47552d253c52, 0x5c31b227edf25da]))); - assert_eq!(tmp, Fq(FqRepr([0xdf92c410c59fc997, 0x149f1bd05a0add85, 0xd3ec393c20fba6ab, 0x37001165c1bde71d, 0x421b41c9f662408e, 0x21c38104f435f5b]))); + tmp.add_assign(&Fq(FqRepr([ + 0x374d8f8ea7a648d8, + 0xe318bb0ebb8bfa9b, + 0x613d996f0a95b400, + 0x9fac233cb7e4fef1, + 0x67e47552d253c52, + 0x5c31b227edf25da, + ]))); + assert_eq!( + tmp, + Fq(FqRepr([ + 0xdf92c410c59fc997, + 0x149f1bd05a0add85, + 0xd3ec393c20fba6ab, + 0x37001165c1bde71d, + 0x421b41c9f662408e, + 0x21c38104f435f5b + ])) + ); // Add one to (q - 1) and test for the result. - tmp = Fq(FqRepr([0xb9feffffffffaaaa, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])); + tmp = Fq(FqRepr([ + 0xb9feffffffffaaaa, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a, + ])); tmp.add_assign(&Fq(FqRepr::from(1))); assert!(tmp.0.is_zero()); // Add a random number to another one such that the result is q - 1 - tmp = Fq(FqRepr([0x531221a410efc95b, 0x72819306027e9717, 0x5ecefb937068b746, 0x97de59cd6feaefd7, 0xdc35c51158644588, 0xb2d176c04f2100])); - tmp.add_assign(&Fq(FqRepr([0x66ecde5bef0fe14f, 0xac2a6cf8aed568e8, 0x861d70d86483edd, 0xcc98f1b7839a22e8, 0x6ee5e2a4eae7674e, 0x194e40737930c599]))); - assert_eq!(tmp, Fq(FqRepr([0xb9feffffffffaaaa, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a]))); + tmp = Fq(FqRepr([ + 0x531221a410efc95b, + 0x72819306027e9717, + 0x5ecefb937068b746, + 0x97de59cd6feaefd7, + 0xdc35c51158644588, + 0xb2d176c04f2100, + ])); + tmp.add_assign(&Fq(FqRepr([ + 0x66ecde5bef0fe14f, + 0xac2a6cf8aed568e8, + 0x861d70d86483edd, + 0xcc98f1b7839a22e8, + 0x6ee5e2a4eae7674e, + 0x194e40737930c599, + ]))); + assert_eq!( + tmp, + Fq(FqRepr([ + 0xb9feffffffffaaaa, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a + ])) + ); // Add one to the result and test for it. tmp.add_assign(&Fq(FqRepr::from(1))); assert!(tmp.0.is_zero()); @@ -1199,23 +2454,88 @@ fn test_fq_add_assign() { fn test_fq_sub_assign() { { // Test arbitrary subtraction that tests reduction. - let mut tmp = Fq(FqRepr([0x531221a410efc95b, 0x72819306027e9717, 0x5ecefb937068b746, 0x97de59cd6feaefd7, 0xdc35c51158644588, 0xb2d176c04f2100])); - tmp.sub_assign(&Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806]))); - assert_eq!(tmp, Fq(FqRepr([0x748014838971292c, 0xfd20fad49fddde5c, 0xcf87f198e3d3f336, 0x3d62d6e6e41883db, 0x45a3443cd88dc61b, 0x151d57aaf755ff94]))); + let mut tmp = Fq(FqRepr([ + 0x531221a410efc95b, + 0x72819306027e9717, + 0x5ecefb937068b746, + 0x97de59cd6feaefd7, + 0xdc35c51158644588, + 0xb2d176c04f2100, + ])); + tmp.sub_assign(&Fq(FqRepr([ + 0x98910d20877e4ada, + 0x940c983013f4b8ba, + 0xf677dc9b8345ba33, + 0xbef2ce6b7f577eba, + 0xe1ae288ac3222c44, + 0x5968bb602790806, + ]))); + assert_eq!( + tmp, + Fq(FqRepr([ + 0x748014838971292c, + 0xfd20fad49fddde5c, + 0xcf87f198e3d3f336, + 0x3d62d6e6e41883db, + 0x45a3443cd88dc61b, + 0x151d57aaf755ff94 + ])) + ); // Test the opposite subtraction which doesn't test reduction. - tmp = Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806])); - tmp.sub_assign(&Fq(FqRepr([0x531221a410efc95b, 0x72819306027e9717, 0x5ecefb937068b746, 0x97de59cd6feaefd7, 0xdc35c51158644588, 0xb2d176c04f2100]))); - assert_eq!(tmp, Fq(FqRepr([0x457eeb7c768e817f, 0x218b052a117621a3, 0x97a8e10812dd02ed, 0x2714749e0f6c8ee3, 0x57863796abde6bc, 0x4e3ba3f4229e706]))); + tmp = Fq(FqRepr([ + 0x98910d20877e4ada, + 0x940c983013f4b8ba, + 0xf677dc9b8345ba33, + 0xbef2ce6b7f577eba, + 0xe1ae288ac3222c44, + 0x5968bb602790806, + ])); + tmp.sub_assign(&Fq(FqRepr([ + 0x531221a410efc95b, + 0x72819306027e9717, + 0x5ecefb937068b746, + 0x97de59cd6feaefd7, + 0xdc35c51158644588, + 0xb2d176c04f2100, + ]))); + assert_eq!( + tmp, + Fq(FqRepr([ + 0x457eeb7c768e817f, + 0x218b052a117621a3, + 0x97a8e10812dd02ed, + 0x2714749e0f6c8ee3, + 0x57863796abde6bc, + 0x4e3ba3f4229e706 + ])) + ); // Test for sensible results with zero tmp = Fq(FqRepr::from(0)); tmp.sub_assign(&Fq(FqRepr::from(0))); assert!(tmp.is_zero()); - tmp = Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806])); + tmp = Fq(FqRepr([ + 0x98910d20877e4ada, + 0x940c983013f4b8ba, + 0xf677dc9b8345ba33, + 0xbef2ce6b7f577eba, + 0xe1ae288ac3222c44, + 0x5968bb602790806, + ])); tmp.sub_assign(&Fq(FqRepr::from(0))); - assert_eq!(tmp, Fq(FqRepr([0x98910d20877e4ada, 0x940c983013f4b8ba, 0xf677dc9b8345ba33, 0xbef2ce6b7f577eba, 0xe1ae288ac3222c44, 0x5968bb602790806]))); + assert_eq!( + tmp, + Fq(FqRepr([ + 0x98910d20877e4ada, + 0x940c983013f4b8ba, + 0xf677dc9b8345ba33, + 0xbef2ce6b7f577eba, + 0xe1ae288ac3222c44, + 0x5968bb602790806 + ])) + ); } let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1238,9 +2558,33 @@ fn test_fq_sub_assign() { #[test] fn test_fq_mul_assign() { - let mut tmp = Fq(FqRepr([0xcc6200000020aa8a, 0x422800801dd8001a, 0x7f4f5e619041c62c, 0x8a55171ac70ed2ba, 0x3f69cc3a3d07d58b, 0xb972455fd09b8ef])); - tmp.mul_assign(&Fq(FqRepr([0x329300000030ffcf, 0x633c00c02cc40028, 0xbef70d925862a942, 0x4f7fa2a82a963c17, 0xdf1eb2575b8bc051, 0x1162b680fb8e9566]))); - assert!(tmp == Fq(FqRepr([0x9dc4000001ebfe14, 0x2850078997b00193, 0xa8197f1abb4d7bf, 0xc0309573f4bfe871, 0xf48d0923ffaf7620, 0x11d4b58c7a926e66]))); + let mut tmp = Fq(FqRepr([ + 0xcc6200000020aa8a, + 0x422800801dd8001a, + 0x7f4f5e619041c62c, + 0x8a55171ac70ed2ba, + 0x3f69cc3a3d07d58b, + 0xb972455fd09b8ef, + ])); + tmp.mul_assign(&Fq(FqRepr([ + 0x329300000030ffcf, + 0x633c00c02cc40028, + 0xbef70d925862a942, + 0x4f7fa2a82a963c17, + 0xdf1eb2575b8bc051, + 0x1162b680fb8e9566, + ]))); + assert!( + tmp + == Fq(FqRepr([ + 0x9dc4000001ebfe14, + 0x2850078997b00193, + 0xa8197f1abb4d7bf, + 0xc0309573f4bfe871, + 0xf48d0923ffaf7620, + 0x11d4b58c7a926e66 + ])) + ); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1287,10 +2631,27 @@ fn test_fq_mul_assign() { #[test] fn test_fq_squaring() { - let mut a = Fq(FqRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x19ffffffffffffff])); + let mut a = Fq(FqRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0x19ffffffffffffff, + ])); assert!(a.is_valid()); a.square(); - assert_eq!(a, Fq::from_repr(FqRepr([0x1cfb28fe7dfbbb86, 0x24cbe1731577a59, 0xcce1d4edc120e66e, 0xdc05c659b4e15b27, 0x79361e5a802c6a23, 0x24bcbe5d51b9a6f])).unwrap()); + assert_eq!( + a, + Fq::from_repr(FqRepr([ + 0x1cfb28fe7dfbbb86, + 0x24cbe1731577a59, + 0xcce1d4edc120e66e, + 0xdc05c659b4e15b27, + 0x79361e5a802c6a23, + 0x24bcbe5d51b9a6f + ])).unwrap() + ); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1387,7 +2748,7 @@ fn test_fq_pow() { #[test] fn test_fq_sqrt() { - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); assert_eq!(Fq::zero().sqrt().unwrap(), Fq::zero()); @@ -1419,17 +2780,47 @@ fn test_fq_sqrt() { #[test] fn test_fq_from_into_repr() { // q + 1 should not be in the field - assert!(Fq::from_repr(FqRepr([0xb9feffffffffaaac, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])).is_err()); + assert!( + Fq::from_repr(FqRepr([ + 0xb9feffffffffaaac, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a + ])).is_err() + ); // q should not be in the field assert!(Fq::from_repr(Fq::char()).is_err()); // Multiply some arbitrary representations to see if the result is as expected. - let a = FqRepr([0x4a49dad4ff6cde2d, 0xac62a82a8f51cd50, 0x2b1f41ab9f36d640, 0x908a387f480735f1, 0xae30740c08a875d7, 0x6c80918a365ef78]); + let a = FqRepr([ + 0x4a49dad4ff6cde2d, + 0xac62a82a8f51cd50, + 0x2b1f41ab9f36d640, + 0x908a387f480735f1, + 0xae30740c08a875d7, + 0x6c80918a365ef78, + ]); let mut a_fq = Fq::from_repr(a).unwrap(); - let b = FqRepr([0xbba57917c32f0cf0, 0xe7f878cf87f05e5d, 0x9498b4292fd27459, 0xd59fd94ee4572cfa, 0x1f607186d5bb0059, 0xb13955f5ac7f6a3]); + let b = FqRepr([ + 0xbba57917c32f0cf0, + 0xe7f878cf87f05e5d, + 0x9498b4292fd27459, + 0xd59fd94ee4572cfa, + 0x1f607186d5bb0059, + 0xb13955f5ac7f6a3, + ]); let b_fq = Fq::from_repr(b).unwrap(); - let c = FqRepr([0xf5f70713b717914c, 0x355ea5ac64cbbab1, 0xce60dd43417ec960, 0xf16b9d77b0ad7d10, 0xa44c204c1de7cdb7, 0x1684487772bc9a5a]); + let c = FqRepr([ + 0xf5f70713b717914c, + 0x355ea5ac64cbbab1, + 0xce60dd43417ec960, + 0xf16b9d77b0ad7d10, + 0xa44c204c1de7cdb7, + 0x1684487772bc9a5a, + ]); a_fq.mul_assign(&b_fq); assert_eq!(a_fq.into_repr(), c); @@ -1484,22 +2875,29 @@ fn test_fq_display() { #[test] fn test_fq_num_bits() { - assert_eq!(Fq::NUM_BITS, 381); - assert_eq!(Fq::CAPACITY, 380); + assert_eq!(Fq::NUM_BITS, 381); + assert_eq!(Fq::CAPACITY, 380); } #[test] fn test_fq_root_of_unity() { assert_eq!(Fq::S, 1); - assert_eq!(Fq::multiplicative_generator(), Fq::from_repr(FqRepr::from(2)).unwrap()); assert_eq!( - Fq::multiplicative_generator().pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]), + Fq::multiplicative_generator(), + Fq::from_repr(FqRepr::from(2)).unwrap() + ); + assert_eq!( + Fq::multiplicative_generator().pow([ + 0xdcff7fffffffd555, + 0xf55ffff58a9ffff, + 0xb39869507b587b12, + 0xb23ba5c279c2895f, + 0x258dd3db21a5d66b, + 0xd0088f51cbff34d + ]), Fq::root_of_unity() ); - assert_eq!( - Fq::root_of_unity().pow([1 << Fq::S]), - Fq::one() - ); + assert_eq!(Fq::root_of_unity().pow([1 << Fq::S]), Fq::one()); assert!(Fq::multiplicative_generator().sqrt().is_none()); } @@ -1516,7 +2914,9 @@ fn test_fq_ordering() { // FqRepr's ordering is well-tested, but we still need to make sure the Fq // elements aren't being compared in Montgomery form. for i in 0..100 { - assert!(Fq::from_repr(FqRepr::from(i+1)).unwrap() > Fq::from_repr(FqRepr::from(i)).unwrap()); + assert!( + Fq::from_repr(FqRepr::from(i + 1)).unwrap() > Fq::from_repr(FqRepr::from(i)).unwrap() + ); } } @@ -1527,18 +2927,36 @@ fn fq_repr_tests() { #[test] fn test_fq_legendre() { - use ::LegendreSymbol::*; + use LegendreSymbol::*; assert_eq!(QuadraticResidue, Fq::one().legendre()); assert_eq!(Zero, Fq::zero().legendre()); - assert_eq!(QuadraticNonResidue, Fq::from_repr(FqRepr::from(2)).unwrap().legendre()); - assert_eq!(QuadraticResidue, Fq::from_repr(FqRepr::from(4)).unwrap().legendre()); + assert_eq!( + QuadraticNonResidue, + Fq::from_repr(FqRepr::from(2)).unwrap().legendre() + ); + assert_eq!( + QuadraticResidue, + Fq::from_repr(FqRepr::from(4)).unwrap().legendre() + ); - let e = FqRepr([0x52a112f249778642, 0xd0bedb989b7991f, 0xdad3b6681aa63c05, - 0xf2efc0bb4721b283, 0x6057a98f18c24733, 0x1022c2fd122889e4]); + let e = FqRepr([ + 0x52a112f249778642, + 0xd0bedb989b7991f, + 0xdad3b6681aa63c05, + 0xf2efc0bb4721b283, + 0x6057a98f18c24733, + 0x1022c2fd122889e4, + ]); assert_eq!(QuadraticNonResidue, Fq::from_repr(e).unwrap().legendre()); - let e = FqRepr([0x6dae594e53a96c74, 0x19b16ca9ba64b37b, 0x5c764661a59bfc68, - 0xaa346e9b31c60a, 0x346059f9d87a9fa9, 0x1d61ac6bfd5c88b]); + let e = FqRepr([ + 0x6dae594e53a96c74, + 0x19b16ca9ba64b37b, + 0x5c764661a59bfc68, + 0xaa346e9b31c60a, + 0x346059f9d87a9fa9, + 0x1d61ac6bfd5c88b, + ]); assert_eq!(QuadraticResidue, Fq::from_repr(e).unwrap().legendre()); } diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index 9bdb111..ae79e42 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -1,18 +1,17 @@ -use rand::{Rng, Rand}; -use ::{Field}; +use rand::{Rand, Rng}; +use Field; use super::fq6::Fq6; use super::fq2::Fq2; -use super::fq::{FROBENIUS_COEFF_FQ12_C1}; +use super::fq::FROBENIUS_COEFF_FQ12_C1; /// An element of Fq12, represented by c0 + c1 * w. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq12 { pub c0: Fq6, - pub c1: Fq6 + pub c1: Fq6, } -impl ::std::fmt::Display for Fq12 -{ +impl ::std::fmt::Display for Fq12 { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "Fq12({} + {} * w)", self.c0, self.c1) } @@ -22,24 +21,17 @@ impl Rand for Fq12 { fn rand<R: Rng>(rng: &mut R) -> Self { Fq12 { c0: rng.gen(), - c1: rng.gen() + c1: rng.gen(), } } } impl Fq12 { - pub fn conjugate(&mut self) - { + pub fn conjugate(&mut self) { self.c1.negate(); } - pub fn mul_by_014( - &mut self, - c0: &Fq2, - c1: &Fq2, - c4: &Fq2 - ) - { + pub fn mul_by_014(&mut self, c0: &Fq2, c1: &Fq2, c4: &Fq2) { let mut aa = self.c0; aa.mul_by_01(c0, c1); let mut bb = self.c1; @@ -56,19 +48,18 @@ impl Fq12 { } } -impl Field for Fq12 -{ +impl Field for Fq12 { fn zero() -> Self { Fq12 { c0: Fq6::zero(), - c1: Fq6::zero() + c1: Fq6::zero(), } } fn one() -> Self { Fq12 { c0: Fq6::one(), - c1: Fq6::zero() + c1: Fq6::zero(), } } @@ -96,8 +87,7 @@ impl Field for Fq12 self.c1.sub_assign(&other.c1); } - fn frobenius_map(&mut self, power: usize) - { + fn frobenius_map(&mut self, power: usize) { self.c0.frobenius_map(power); self.c1.frobenius_map(power); @@ -148,10 +138,7 @@ impl Field for Fq12 c0s.sub_assign(&c1s); c0s.inverse().map(|t| { - let mut tmp = Fq12 { - c0: t, - c1: t - }; + let mut tmp = Fq12 { c0: t, c1: t }; tmp.c0.mul_assign(&self.c0); tmp.c1.mul_assign(&self.c1); tmp.c1.negate(); @@ -180,13 +167,13 @@ fn test_fq12_mul_by_014() { c0: Fq6 { c0: c0, c1: c1, - c2: Fq2::zero() + c2: Fq2::zero(), }, c1: Fq6 { c0: Fq2::zero(), c1: c5, - c2: Fq2::zero() - } + c2: Fq2::zero(), + }, }); assert_eq!(a, b); @@ -195,7 +182,7 @@ fn test_fq12_mul_by_014() { #[test] fn fq12_field_tests() { - use ::PrimeField; + use PrimeField; ::tests::field::random_field_tests::<Fq12>(); ::tests::field::random_frobenius_tests::<Fq12, _>(super::fq::Fq::char(), 13); diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 2082be0..873652c 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -1,6 +1,6 @@ -use rand::{Rng, Rand}; -use ::{Field, SqrtField}; -use super::fq::{Fq, FROBENIUS_COEFF_FQ2_C1, NEGATIVE_ONE}; +use rand::{Rand, Rng}; +use {Field, SqrtField}; +use super::fq::{FROBENIUS_COEFF_FQ2_C1, Fq, NEGATIVE_ONE}; use std::cmp::Ordering; @@ -8,11 +8,10 @@ use std::cmp::Ordering; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Fq2 { pub c0: Fq, - pub c1: Fq + pub c1: Fq, } -impl ::std::fmt::Display for Fq2 -{ +impl ::std::fmt::Display for Fq2 { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "Fq2({} + {} * u)", self.c0, self.c1) } @@ -25,7 +24,7 @@ impl Ord for Fq2 { match self.c1.cmp(&other.c1) { Ordering::Greater => Ordering::Greater, Ordering::Less => Ordering::Less, - Ordering::Equal => self.c0.cmp(&other.c0) + Ordering::Equal => self.c0.cmp(&other.c0), } } } @@ -61,18 +60,24 @@ impl Rand for Fq2 { fn rand<R: Rng>(rng: &mut R) -> Self { Fq2 { c0: rng.gen(), - c1: rng.gen() + c1: rng.gen(), } } } impl Field for Fq2 { fn zero() -> Self { - Fq2 { c0: Fq::zero(), c1: Fq::zero() } + Fq2 { + c0: Fq::zero(), + c1: Fq::zero(), + } } fn one() -> Self { - Fq2 { c0: Fq::one(), c1: Fq::zero() } + Fq2 { + c0: Fq::one(), + c1: Fq::zero(), + } } fn is_zero(&self) -> bool { @@ -139,7 +144,7 @@ impl Field for Fq2 { t0.inverse().map(|t| { let mut tmp = Fq2 { c0: self.c0, - c1: self.c1 + c1: self.c1, }; tmp.c0.mul_assign(&t); tmp.c1.mul_assign(&t); @@ -149,14 +154,12 @@ impl Field for Fq2 { }) } - fn frobenius_map(&mut self, power: usize) - { + fn frobenius_map(&mut self, power: usize) { self.c1.mul_assign(&FROBENIUS_COEFF_FQ2_C1[power % 2]); } } impl SqrtField for Fq2 { - fn legendre(&self) -> ::LegendreSymbol { self.norm().legendre() } @@ -168,7 +171,14 @@ impl SqrtField for Fq2 { Some(Self::zero()) } else { // a1 = self^((q - 3) / 4) - let mut a1 = self.pow([0xee7fbfffffffeaaa, 0x7aaffffac54ffff, 0xd9cc34a83dac3d89, 0xd91dd2e13ce144af, 0x92c6e9ed90d2eb35, 0x680447a8e5ff9a6]); + let mut a1 = self.pow([ + 0xee7fbfffffffeaaa, + 0x7aaffffac54ffff, + 0xd9cc34a83dac3d89, + 0xd91dd2e13ce144af, + 0x92c6e9ed90d2eb35, + 0x680447a8e5ff9a6, + ]); let mut alpha = a1; alpha.square(); alpha.mul_assign(self); @@ -178,7 +188,7 @@ impl SqrtField for Fq2 { let neg1 = Fq2 { c0: NEGATIVE_ONE, - c1: Fq::zero() + c1: Fq::zero(), }; if a0 == neg1 { @@ -187,11 +197,21 @@ impl SqrtField for Fq2 { a1.mul_assign(self); if alpha == neg1 { - a1.mul_assign(&Fq2{c0: Fq::zero(), c1: Fq::one()}); + a1.mul_assign(&Fq2 { + c0: Fq::zero(), + c1: Fq::one(), + }); } else { alpha.add_assign(&Fq2::one()); // alpha = alpha^((q - 1) / 2) - alpha = alpha.pow([0xdcff7fffffffd555, 0xf55ffff58a9ffff, 0xb39869507b587b12, 0xb23ba5c279c2895f, 0x258dd3db21a5d66b, 0xd0088f51cbff34d]); + alpha = alpha.pow([ + 0xdcff7fffffffd555, + 0xf55ffff58a9ffff, + 0xb39869507b587b12, + 0xb23ba5c279c2895f, + 0x258dd3db21a5d66b, + 0xd0088f51cbff34d, + ]); a1.mul_assign(&alpha); } @@ -205,7 +225,7 @@ impl SqrtField for Fq2 { fn test_fq2_ordering() { let mut a = Fq2 { c0: Fq::zero(), - c1: Fq::zero() + c1: Fq::zero(), }; let mut b = a.clone(); @@ -227,210 +247,625 @@ fn test_fq2_ordering() { #[test] fn test_fq2_basics() { - assert_eq!(Fq2 { c0: Fq::zero(), c1: Fq::zero() }, Fq2::zero()); - assert_eq!(Fq2 { c0: Fq::one(), c1: Fq::zero() }, Fq2::one()); + assert_eq!( + Fq2 { + c0: Fq::zero(), + c1: Fq::zero(), + }, + Fq2::zero() + ); + assert_eq!( + Fq2 { + c0: Fq::one(), + c1: Fq::zero(), + }, + Fq2::one() + ); assert!(Fq2::zero().is_zero()); assert!(!Fq2::one().is_zero()); - assert!(!Fq2{c0: Fq::zero(), c1: Fq::one()}.is_zero()); + assert!(!Fq2 { + c0: Fq::zero(), + c1: Fq::one(), + }.is_zero()); } #[test] fn test_fq2_squaring() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; - let mut a = Fq2 { c0: Fq::one(), c1: Fq::one() }; // u + 1 + let mut a = Fq2 { + c0: Fq::one(), + c1: Fq::one(), + }; // u + 1 a.square(); - assert_eq!(a, Fq2 { c0: Fq::zero(), c1: Fq::from_repr(FqRepr::from(2)).unwrap() }); // 2u + assert_eq!( + a, + Fq2 { + c0: Fq::zero(), + c1: Fq::from_repr(FqRepr::from(2)).unwrap(), + } + ); // 2u - let mut a = Fq2 { c0: Fq::zero(), c1: Fq::one() }; // u + let mut a = Fq2 { + c0: Fq::zero(), + c1: Fq::one(), + }; // u a.square(); assert_eq!(a, { let mut neg1 = Fq::one(); neg1.negate(); - Fq2 { c0: neg1, c1: Fq::zero() } + Fq2 { + c0: neg1, + c1: Fq::zero(), + } }); // -1 let mut a = Fq2 { - c0: Fq::from_repr(FqRepr([0x9c2c6309bbf8b598, 0x4eef5c946536f602, 0x90e34aab6fb6a6bd, 0xf7f295a94e58ae7c, 0x41b76dcc1c3fbe5e, 0x7080c5fa1d8e042])).unwrap(), - c1: Fq::from_repr(FqRepr([0x38f473b3c870a4ab, 0x6ad3291177c8c7e5, 0xdac5a4c911a4353e, 0xbfb99020604137a0, 0xfc58a7b7be815407, 0x10d1615e75250a21])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x9c2c6309bbf8b598, + 0x4eef5c946536f602, + 0x90e34aab6fb6a6bd, + 0xf7f295a94e58ae7c, + 0x41b76dcc1c3fbe5e, + 0x7080c5fa1d8e042, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x38f473b3c870a4ab, + 0x6ad3291177c8c7e5, + 0xdac5a4c911a4353e, + 0xbfb99020604137a0, + 0xfc58a7b7be815407, + 0x10d1615e75250a21, + ])).unwrap(), }; a.square(); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0xf262c28c538bcf68, 0xb9f2a66eae1073ba, 0xdc46ab8fad67ae0, 0xcb674157618da176, 0x4cf17b5893c3d327, 0x7eac81369c43361])).unwrap(), - c1: Fq::from_repr(FqRepr([0xc1579cf58e980cf8, 0xa23eb7e12dd54d98, 0xe75138bce4cec7aa, 0x38d0d7275a9689e1, 0x739c983042779a65, 0x1542a61c8a8db994])).unwrap() - }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0xf262c28c538bcf68, + 0xb9f2a66eae1073ba, + 0xdc46ab8fad67ae0, + 0xcb674157618da176, + 0x4cf17b5893c3d327, + 0x7eac81369c43361 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xc1579cf58e980cf8, + 0xa23eb7e12dd54d98, + 0xe75138bce4cec7aa, + 0x38d0d7275a9689e1, + 0x739c983042779a65, + 0x1542a61c8a8db994 + ])).unwrap(), + } + ); } #[test] fn test_fq2_mul() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; let mut a = Fq2 { - c0: Fq::from_repr(FqRepr([0x85c9f989e1461f03, 0xa2e33c333449a1d6, 0x41e461154a7354a3, 0x9ee53e7e84d7532e, 0x1c202d8ed97afb45, 0x51d3f9253e2516f])).unwrap(), - c1: Fq::from_repr(FqRepr([0xa7348a8b511aedcf, 0x143c215d8176b319, 0x4cc48081c09b8903, 0x9533e4a9a5158be, 0x7a5e1ecb676d65f9, 0x180c3ee46656b008])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x85c9f989e1461f03, + 0xa2e33c333449a1d6, + 0x41e461154a7354a3, + 0x9ee53e7e84d7532e, + 0x1c202d8ed97afb45, + 0x51d3f9253e2516f, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xa7348a8b511aedcf, + 0x143c215d8176b319, + 0x4cc48081c09b8903, + 0x9533e4a9a5158be, + 0x7a5e1ecb676d65f9, + 0x180c3ee46656b008, + ])).unwrap(), }; a.mul_assign(&Fq2 { - c0: Fq::from_repr(FqRepr([0xe21f9169805f537e, 0xfc87e62e179c285d, 0x27ece175be07a531, 0xcd460f9f0c23e430, 0x6c9110292bfa409, 0x2c93a72eb8af83e])).unwrap(), - c1: Fq::from_repr(FqRepr([0x4b1c3f936d8992d4, 0x1d2a72916dba4c8a, 0x8871c508658d1e5f, 0x57a06d3135a752ae, 0x634cd3c6c565096d, 0x19e17334d4e93558])).unwrap() - }); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x95b5127e6360c7e4, 0xde29c31a19a6937e, 0xf61a96dacf5a39bc, 0x5511fe4d84ee5f78, 0x5310a202d92f9963, 0x1751afbe166e5399])).unwrap(), - c1: Fq::from_repr(FqRepr([0x84af0e1bd630117a, 0x6c63cd4da2c2aa7, 0x5ba6e5430e883d40, 0xc975106579c275ee, 0x33a9ac82ce4c5083, 0x1ef1a36c201589d])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0xe21f9169805f537e, + 0xfc87e62e179c285d, + 0x27ece175be07a531, + 0xcd460f9f0c23e430, + 0x6c9110292bfa409, + 0x2c93a72eb8af83e, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x4b1c3f936d8992d4, + 0x1d2a72916dba4c8a, + 0x8871c508658d1e5f, + 0x57a06d3135a752ae, + 0x634cd3c6c565096d, + 0x19e17334d4e93558, + ])).unwrap(), }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x95b5127e6360c7e4, + 0xde29c31a19a6937e, + 0xf61a96dacf5a39bc, + 0x5511fe4d84ee5f78, + 0x5310a202d92f9963, + 0x1751afbe166e5399 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x84af0e1bd630117a, + 0x6c63cd4da2c2aa7, + 0x5ba6e5430e883d40, + 0xc975106579c275ee, + 0x33a9ac82ce4c5083, + 0x1ef1a36c201589d + ])).unwrap(), + } + ); } #[test] fn test_fq2_inverse() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; assert!(Fq2::zero().inverse().is_none()); let a = Fq2 { - c0: Fq::from_repr(FqRepr([0x85c9f989e1461f03, 0xa2e33c333449a1d6, 0x41e461154a7354a3, 0x9ee53e7e84d7532e, 0x1c202d8ed97afb45, 0x51d3f9253e2516f])).unwrap(), - c1: Fq::from_repr(FqRepr([0xa7348a8b511aedcf, 0x143c215d8176b319, 0x4cc48081c09b8903, 0x9533e4a9a5158be, 0x7a5e1ecb676d65f9, 0x180c3ee46656b008])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x85c9f989e1461f03, + 0xa2e33c333449a1d6, + 0x41e461154a7354a3, + 0x9ee53e7e84d7532e, + 0x1c202d8ed97afb45, + 0x51d3f9253e2516f, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xa7348a8b511aedcf, + 0x143c215d8176b319, + 0x4cc48081c09b8903, + 0x9533e4a9a5158be, + 0x7a5e1ecb676d65f9, + 0x180c3ee46656b008, + ])).unwrap(), }; let a = a.inverse().unwrap(); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x70300f9bcb9e594, 0xe5ecda5fdafddbb2, 0x64bef617d2915a8f, 0xdfba703293941c30, 0xa6c3d8f9586f2636, 0x1351ef01941b70c4])).unwrap(), - c1: Fq::from_repr(FqRepr([0x8c39fd76a8312cb4, 0x15d7b6b95defbff0, 0x947143f89faedee9, 0xcbf651a0f367afb2, 0xdf4e54f0d3ef15a6, 0x103bdf241afb0019])).unwrap() - }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x70300f9bcb9e594, + 0xe5ecda5fdafddbb2, + 0x64bef617d2915a8f, + 0xdfba703293941c30, + 0xa6c3d8f9586f2636, + 0x1351ef01941b70c4 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x8c39fd76a8312cb4, + 0x15d7b6b95defbff0, + 0x947143f89faedee9, + 0xcbf651a0f367afb2, + 0xdf4e54f0d3ef15a6, + 0x103bdf241afb0019 + ])).unwrap(), + } + ); } #[test] fn test_fq2_addition() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; let mut a = Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x977df6efcdaee0db, + 0x946ae52d684fa7ed, + 0xbe203411c66fb3a5, + 0xb3f8afc0ee248cad, + 0x4e464dea5bcfd41e, + 0x12d1137b8a6a837, + ])).unwrap(), }; a.add_assign(&Fq2 { - c0: Fq::from_repr(FqRepr([0x619a02d78dc70ef2, 0xb93adfc9119e33e8, 0x4bf0b99a9f0dca12, 0x3b88899a42a6318f, 0x986a4a62fa82a49d, 0x13ce433fa26027f5])).unwrap(), - c1: Fq::from_repr(FqRepr([0x66323bf80b58b9b9, 0xa1379b6facf6e596, 0x402aef1fb797e32f, 0x2236f55246d0d44d, 0x4c8c1800eb104566, 0x11d6e20e986c2085])).unwrap() - }); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x8e9a7adaf6eb0eb9, 0xcb207e6b3341eaba, 0xd70b0c7b481d23ff, 0xf4ef57d604b6bca2, 0x65309427b3d5d090, 0x14c715d5553f01d2])).unwrap(), - c1: Fq::from_repr(FqRepr([0xfdb032e7d9079a94, 0x35a2809d15468d83, 0xfe4b23317e0796d5, 0xd62fa51334f560fa, 0x9ad265eb46e01984, 0x1303f3465112c8bc])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x619a02d78dc70ef2, + 0xb93adfc9119e33e8, + 0x4bf0b99a9f0dca12, + 0x3b88899a42a6318f, + 0x986a4a62fa82a49d, + 0x13ce433fa26027f5, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x66323bf80b58b9b9, + 0xa1379b6facf6e596, + 0x402aef1fb797e32f, + 0x2236f55246d0d44d, + 0x4c8c1800eb104566, + 0x11d6e20e986c2085, + ])).unwrap(), }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x8e9a7adaf6eb0eb9, + 0xcb207e6b3341eaba, + 0xd70b0c7b481d23ff, + 0xf4ef57d604b6bca2, + 0x65309427b3d5d090, + 0x14c715d5553f01d2 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xfdb032e7d9079a94, + 0x35a2809d15468d83, + 0xfe4b23317e0796d5, + 0xd62fa51334f560fa, + 0x9ad265eb46e01984, + 0x1303f3465112c8bc + ])).unwrap(), + } + ); } #[test] fn test_fq2_subtraction() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; let mut a = Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x977df6efcdaee0db, + 0x946ae52d684fa7ed, + 0xbe203411c66fb3a5, + 0xb3f8afc0ee248cad, + 0x4e464dea5bcfd41e, + 0x12d1137b8a6a837, + ])).unwrap(), }; a.sub_assign(&Fq2 { - c0: Fq::from_repr(FqRepr([0x619a02d78dc70ef2, 0xb93adfc9119e33e8, 0x4bf0b99a9f0dca12, 0x3b88899a42a6318f, 0x986a4a62fa82a49d, 0x13ce433fa26027f5])).unwrap(), - c1: Fq::from_repr(FqRepr([0x66323bf80b58b9b9, 0xa1379b6facf6e596, 0x402aef1fb797e32f, 0x2236f55246d0d44d, 0x4c8c1800eb104566, 0x11d6e20e986c2085])).unwrap() - }); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x8565752bdb5c9b80, 0x7756bed7c15982e9, 0xa65a6be700b285fe, 0xe255902672ef6c43, 0x7f77a718021c342d, 0x72ba14049fe9881])).unwrap(), - c1: Fq::from_repr(FqRepr([0xeb4abaf7c255d1cd, 0x11df49bc6cacc256, 0xe52617930588c69a, 0xf63905f39ad8cb1f, 0x4cd5dd9fb40b3b8f, 0x957411359ba6e4c])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x619a02d78dc70ef2, + 0xb93adfc9119e33e8, + 0x4bf0b99a9f0dca12, + 0x3b88899a42a6318f, + 0x986a4a62fa82a49d, + 0x13ce433fa26027f5, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x66323bf80b58b9b9, + 0xa1379b6facf6e596, + 0x402aef1fb797e32f, + 0x2236f55246d0d44d, + 0x4c8c1800eb104566, + 0x11d6e20e986c2085, + ])).unwrap(), }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x8565752bdb5c9b80, + 0x7756bed7c15982e9, + 0xa65a6be700b285fe, + 0xe255902672ef6c43, + 0x7f77a718021c342d, + 0x72ba14049fe9881 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xeb4abaf7c255d1cd, + 0x11df49bc6cacc256, + 0xe52617930588c69a, + 0xf63905f39ad8cb1f, + 0x4cd5dd9fb40b3b8f, + 0x957411359ba6e4c + ])).unwrap(), + } + ); } #[test] fn test_fq2_negation() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; let mut a = Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x977df6efcdaee0db, + 0x946ae52d684fa7ed, + 0xbe203411c66fb3a5, + 0xb3f8afc0ee248cad, + 0x4e464dea5bcfd41e, + 0x12d1137b8a6a837, + ])).unwrap(), }; a.negate(); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x8cfe87fc96dbaae4, 0xcc6615c8fb0492d, 0xdc167fc04da19c37, 0xab107d49317487ab, 0x7e555df189f880e3, 0x19083f5486a10cbd])).unwrap(), - c1: Fq::from_repr(FqRepr([0x228109103250c9d0, 0x8a411ad149045812, 0xa9109e8f3041427e, 0xb07e9bc405608611, 0xfcd559cbe77bd8b8, 0x18d400b280d93e62])).unwrap() - }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x8cfe87fc96dbaae4, + 0xcc6615c8fb0492d, + 0xdc167fc04da19c37, + 0xab107d49317487ab, + 0x7e555df189f880e3, + 0x19083f5486a10cbd + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x228109103250c9d0, + 0x8a411ad149045812, + 0xa9109e8f3041427e, + 0xb07e9bc405608611, + 0xfcd559cbe77bd8b8, + 0x18d400b280d93e62 + ])).unwrap(), + } + ); } #[test] fn test_fq2_doubling() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; let mut a = Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x977df6efcdaee0db, + 0x946ae52d684fa7ed, + 0xbe203411c66fb3a5, + 0xb3f8afc0ee248cad, + 0x4e464dea5bcfd41e, + 0x12d1137b8a6a837, + ])).unwrap(), }; a.double(); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x5a00f006d247ff8e, 0x23cb3d4443476da4, 0x1634a5c1521eb3da, 0x72cd9c7784211627, 0x998c938972a657e7, 0x1f1a52b65bdb3b9])).unwrap(), - c1: Fq::from_repr(FqRepr([0x2efbeddf9b5dc1b6, 0x28d5ca5ad09f4fdb, 0x7c4068238cdf674b, 0x67f15f81dc49195b, 0x9c8c9bd4b79fa83d, 0x25a226f714d506e])).unwrap() - }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x5a00f006d247ff8e, + 0x23cb3d4443476da4, + 0x1634a5c1521eb3da, + 0x72cd9c7784211627, + 0x998c938972a657e7, + 0x1f1a52b65bdb3b9 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x2efbeddf9b5dc1b6, + 0x28d5ca5ad09f4fdb, + 0x7c4068238cdf674b, + 0x67f15f81dc49195b, + 0x9c8c9bd4b79fa83d, + 0x25a226f714d506e + ])).unwrap(), + } + ); } #[test] fn test_fq2_frobenius_map() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; let mut a = Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc, + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x977df6efcdaee0db, + 0x946ae52d684fa7ed, + 0xbe203411c66fb3a5, + 0xb3f8afc0ee248cad, + 0x4e464dea5bcfd41e, + 0x12d1137b8a6a837, + ])).unwrap(), }; a.frobenius_map(0); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() - }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x977df6efcdaee0db, + 0x946ae52d684fa7ed, + 0xbe203411c66fb3a5, + 0xb3f8afc0ee248cad, + 0x4e464dea5bcfd41e, + 0x12d1137b8a6a837 + ])).unwrap(), + } + ); a.frobenius_map(1); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x228109103250c9d0, 0x8a411ad149045812, 0xa9109e8f3041427e, 0xb07e9bc405608611, 0xfcd559cbe77bd8b8, 0x18d400b280d93e62])).unwrap() - }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x228109103250c9d0, + 0x8a411ad149045812, + 0xa9109e8f3041427e, + 0xb07e9bc405608611, + 0xfcd559cbe77bd8b8, + 0x18d400b280d93e62 + ])).unwrap(), + } + ); a.frobenius_map(1); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() - }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x977df6efcdaee0db, + 0x946ae52d684fa7ed, + 0xbe203411c66fb3a5, + 0xb3f8afc0ee248cad, + 0x4e464dea5bcfd41e, + 0x12d1137b8a6a837 + ])).unwrap(), + } + ); a.frobenius_map(2); - assert_eq!(a, Fq2 { - c0: Fq::from_repr(FqRepr([0x2d0078036923ffc7, 0x11e59ea221a3b6d2, 0x8b1a52e0a90f59ed, 0xb966ce3bc2108b13, 0xccc649c4b9532bf3, 0xf8d295b2ded9dc])).unwrap(), - c1: Fq::from_repr(FqRepr([0x977df6efcdaee0db, 0x946ae52d684fa7ed, 0xbe203411c66fb3a5, 0xb3f8afc0ee248cad, 0x4e464dea5bcfd41e, 0x12d1137b8a6a837])).unwrap() - }); + assert_eq!( + a, + Fq2 { + c0: Fq::from_repr(FqRepr([ + 0x2d0078036923ffc7, + 0x11e59ea221a3b6d2, + 0x8b1a52e0a90f59ed, + 0xb966ce3bc2108b13, + 0xccc649c4b9532bf3, + 0xf8d295b2ded9dc + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0x977df6efcdaee0db, + 0x946ae52d684fa7ed, + 0xbe203411c66fb3a5, + 0xb3f8afc0ee248cad, + 0x4e464dea5bcfd41e, + 0x12d1137b8a6a837 + ])).unwrap(), + } + ); } #[test] fn test_fq2_sqrt() { - use ::PrimeField; - use super::fq::{FqRepr}; + use PrimeField; + use super::fq::FqRepr; assert_eq!( Fq2 { - c0: Fq::from_repr(FqRepr([0x476b4c309720e227, 0x34c2d04faffdab6, 0xa57e6fc1bab51fd9, 0xdb4a116b5bf74aa1, 0x1e58b2159dfe10e2, 0x7ca7da1f13606ac])).unwrap(), - c1: Fq::from_repr(FqRepr([0xfa8de88b7516d2c3, 0x371a75ed14f41629, 0x4cec2dca577a3eb6, 0x212611bca4e99121, 0x8ee5394d77afb3d, 0xec92336650e49d5])).unwrap() - }.sqrt().unwrap(), + c0: Fq::from_repr(FqRepr([ + 0x476b4c309720e227, + 0x34c2d04faffdab6, + 0xa57e6fc1bab51fd9, + 0xdb4a116b5bf74aa1, + 0x1e58b2159dfe10e2, + 0x7ca7da1f13606ac + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xfa8de88b7516d2c3, + 0x371a75ed14f41629, + 0x4cec2dca577a3eb6, + 0x212611bca4e99121, + 0x8ee5394d77afb3d, + 0xec92336650e49d5 + ])).unwrap(), + }.sqrt() + .unwrap(), Fq2 { - c0: Fq::from_repr(FqRepr([0x40b299b2704258c5, 0x6ef7de92e8c68b63, 0x6d2ddbe552203e82, 0x8d7f1f723d02c1d3, 0x881b3e01b611c070, 0x10f6963bbad2ebc5])).unwrap(), - c1: Fq::from_repr(FqRepr([0xc099534fc209e752, 0x7670594665676447, 0x28a20faed211efe7, 0x6b852aeaf2afcb1b, 0xa4c93b08105d71a9, 0x8d7cfff94216330])).unwrap() + c0: Fq::from_repr(FqRepr([ + 0x40b299b2704258c5, + 0x6ef7de92e8c68b63, + 0x6d2ddbe552203e82, + 0x8d7f1f723d02c1d3, + 0x881b3e01b611c070, + 0x10f6963bbad2ebc5 + ])).unwrap(), + c1: Fq::from_repr(FqRepr([ + 0xc099534fc209e752, + 0x7670594665676447, + 0x28a20faed211efe7, + 0x6b852aeaf2afcb1b, + 0xa4c93b08105d71a9, + 0x8d7cfff94216330 + ])).unwrap(), } ); assert_eq!( Fq2 { - c0: Fq::from_repr(FqRepr([0xb9f78429d1517a6b, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])).unwrap(), - c1: Fq::zero() - }.sqrt().unwrap(), + c0: Fq::from_repr(FqRepr([ + 0xb9f78429d1517a6b, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a + ])).unwrap(), + c1: Fq::zero(), + }.sqrt() + .unwrap(), Fq2 { c0: Fq::zero(), - c1: Fq::from_repr(FqRepr([0xb9fefffffd4357a3, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a])).unwrap() + c1: Fq::from_repr(FqRepr([ + 0xb9fefffffd4357a3, + 0x1eabfffeb153ffff, + 0x6730d2a0f6b0f624, + 0x64774b84f38512bf, + 0x4b1ba7b6434bacd7, + 0x1a0111ea397fe69a + ])).unwrap(), } ); } #[test] fn test_fq2_legendre() { - use ::LegendreSymbol::*; + use LegendreSymbol::*; assert_eq!(Zero, Fq2::zero().legendre()); // i^2 = -1 @@ -450,7 +885,7 @@ fn test_fq2_mul_nonresidue() { let nqr = Fq2 { c0: Fq::one(), - c1: Fq::one() + c1: Fq::one(), }; for _ in 0..1000 { @@ -465,7 +900,7 @@ fn test_fq2_mul_nonresidue() { #[test] fn fq2_field_tests() { - use ::PrimeField; + use PrimeField; ::tests::field::random_field_tests::<Fq2>(); ::tests::field::random_sqrt_tests::<Fq2>(); diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs index 4d1e470..d9a6c33 100644 --- a/src/bls12_381/fq6.rs +++ b/src/bls12_381/fq6.rs @@ -1,5 +1,5 @@ -use rand::{Rng, Rand}; -use ::{Field}; +use rand::{Rand, Rng}; +use Field; use super::fq2::Fq2; use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; @@ -8,11 +8,10 @@ use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; pub struct Fq6 { pub c0: Fq2, pub c1: Fq2, - pub c2: Fq2 + pub c2: Fq2, } -impl ::std::fmt::Display for Fq6 -{ +impl ::std::fmt::Display for Fq6 { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "Fq6({} + {} * v, {} * v^2)", self.c0, self.c1, self.c2) } @@ -23,7 +22,7 @@ impl Rand for Fq6 { Fq6 { c0: rng.gen(), c1: rng.gen(), - c2: rng.gen() + c2: rng.gen(), } } } @@ -38,8 +37,7 @@ impl Fq6 { self.c0.mul_by_nonresidue(); } - pub fn mul_by_1(&mut self, c1: &Fq2) - { + pub fn mul_by_1(&mut self, c1: &Fq2) { let mut b_b = self.c1; b_b.mul_assign(c1); @@ -67,8 +65,7 @@ impl Fq6 { self.c2 = b_b; } - pub fn mul_by_01(&mut self, c0: &Fq2, c1: &Fq2) - { + pub fn mul_by_01(&mut self, c0: &Fq2, c1: &Fq2) { let mut a_a = self.c0; let mut b_b = self.c1; a_a.mul_assign(c0); @@ -112,13 +109,12 @@ impl Fq6 { } } -impl Field for Fq6 -{ +impl Field for Fq6 { fn zero() -> Self { Fq6 { c0: Fq2::zero(), c1: Fq2::zero(), - c2: Fq2::zero() + c2: Fq2::zero(), } } @@ -126,7 +122,7 @@ impl Field for Fq6 Fq6 { c0: Fq2::one(), c1: Fq2::zero(), - c2: Fq2::zero() + c2: Fq2::zero(), } } @@ -158,8 +154,7 @@ impl Field for Fq6 self.c2.sub_assign(&other.c2); } - fn frobenius_map(&mut self, power: usize) - { + fn frobenius_map(&mut self, power: usize) { self.c0.frobenius_map(power); self.c1.frobenius_map(power); self.c2.frobenius_map(power); @@ -293,15 +288,15 @@ impl Field for Fq6 let mut tmp = Fq6 { c0: t, c1: t, - c2: t + c2: t, }; tmp.c0.mul_assign(&c0); tmp.c1.mul_assign(&c1); tmp.c2.mul_assign(&c2); Some(tmp) - }, - None => None + } + None => None, } } } @@ -316,7 +311,7 @@ fn test_fq6_mul_nonresidue() { let nqr = Fq6 { c0: Fq2::zero(), c1: Fq2::one(), - c2: Fq2::zero() + c2: Fq2::zero(), }; for _ in 0..1000 { @@ -342,7 +337,7 @@ fn test_fq6_mul_by_1() { b.mul_assign(&Fq6 { c0: Fq2::zero(), c1: c1, - c2: Fq2::zero() + c2: Fq2::zero(), }); assert_eq!(a, b); @@ -363,7 +358,7 @@ fn test_fq6_mul_by_01() { b.mul_assign(&Fq6 { c0: c0, c1: c1, - c2: Fq2::zero() + c2: Fq2::zero(), }); assert_eq!(a, b); @@ -372,8 +367,8 @@ fn test_fq6_mul_by_01() { #[test] fn fq6_field_tests() { - use ::PrimeField; - + use PrimeField; + ::tests::field::random_field_tests::<Fq6>(); ::tests::field::random_frobenius_tests::<Fq6, _>(super::fq::Fq::char(), 13); } diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 01eae14..f1bc9e6 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,8 +1,13 @@ -use ::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError}; -use ::LegendreSymbol::*; +use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; +use LegendreSymbol::*; // r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 -const MODULUS: FrRepr = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); +const MODULUS: FrRepr = FrRepr([ + 0xffffffff00000001, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48, +]); // The number of bits needed to represent the modulus. const MODULUS_BITS: u32 = 255; @@ -12,22 +17,42 @@ const MODULUS_BITS: u32 = 255; const REPR_SHAVE_BITS: u32 = 1; // R = 2**256 % r -const R: FrRepr = FrRepr([0x1fffffffe, 0x5884b7fa00034802, 0x998c4fefecbc4ff5, 0x1824b159acc5056f]); +const R: FrRepr = FrRepr([ + 0x1fffffffe, + 0x5884b7fa00034802, + 0x998c4fefecbc4ff5, + 0x1824b159acc5056f, +]); // R2 = R^2 % r -const R2: FrRepr = FrRepr([0xc999e990f3f29c6d, 0x2b6cedcb87925c23, 0x5d314967254398f, 0x748d9d99f59ff11]); +const R2: FrRepr = FrRepr([ + 0xc999e990f3f29c6d, + 0x2b6cedcb87925c23, + 0x5d314967254398f, + 0x748d9d99f59ff11, +]); // INV = -(r^{-1} mod 2^64) mod 2^64 const INV: u64 = 0xfffffffeffffffff; // GENERATOR = 7 (multiplicative generator of r-1 order, that is also quadratic nonresidue) -const GENERATOR: FrRepr = FrRepr([0xefffffff1, 0x17e363d300189c0f, 0xff9c57876f8457b0, 0x351332208fc5a8c4]); +const GENERATOR: FrRepr = FrRepr([ + 0xefffffff1, + 0x17e363d300189c0f, + 0xff9c57876f8457b0, + 0x351332208fc5a8c4, +]); // 2^s * t = MODULUS - 1 with t odd const S: u32 = 32; // 2^s root of unity computed by GENERATOR^t -const ROOT_OF_UNITY: FrRepr = FrRepr([0xb9b58d8c5f0e466a, 0x5b1b4c801819d7ec, 0xaf53ae352a31e64, 0x5bf3adda19e9b27b]); +const ROOT_OF_UNITY: FrRepr = FrRepr([ + 0xb9b58d8c5f0e466a, + 0x5b1b4c801819d7ec, + 0xaf53ae352a31e64, + 0x5bf3adda19e9b27b, +]); #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] pub struct FrRepr(pub [u64; 4]); @@ -39,8 +64,7 @@ impl ::rand::Rand for FrRepr { } } -impl ::std::fmt::Display for FrRepr -{ +impl ::std::fmt::Display for FrRepr { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { try!(write!(f, "0x")); for i in self.0.iter().rev() { @@ -79,9 +103,9 @@ impl Ord for FrRepr { fn cmp(&self, other: &FrRepr) -> ::std::cmp::Ordering { for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { if a < b { - return ::std::cmp::Ordering::Less + return ::std::cmp::Ordering::Less; } else if a > b { - return ::std::cmp::Ordering::Greater + return ::std::cmp::Ordering::Greater; } } @@ -222,8 +246,7 @@ impl PrimeFieldRepr for FrRepr { #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Fr(FrRepr); -impl ::std::fmt::Display for Fr -{ +impl ::std::fmt::Display for Fr { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "Fr({})", self.into_repr()) } @@ -238,7 +261,7 @@ impl ::rand::Rand for Fr { tmp.0.as_mut()[3] &= 0xffffffffffffffff >> REPR_SHAVE_BITS; if tmp.is_valid() { - return tmp + return tmp; } } } @@ -266,9 +289,16 @@ impl PrimeField for Fr { fn into_repr(&self) -> FrRepr { let mut r = *self; - r.mont_reduce((self.0).0[0], (self.0).0[1], - (self.0).0[2], (self.0).0[3], - 0, 0, 0, 0); + r.mont_reduce( + (self.0).0[0], + (self.0).0[1], + (self.0).0[2], + (self.0).0[3], + 0, + 0, + 0, + 0, + ); r.0 } @@ -405,8 +435,7 @@ impl Field for Fr { } #[inline] - fn mul_assign(&mut self, other: &Fr) - { + fn mul_assign(&mut self, other: &Fr) { let mut carry = 0; let r0 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); let r1 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); @@ -435,8 +464,7 @@ impl Field for Fr { } #[inline] - fn square(&mut self) - { + fn square(&mut self) { let mut carry = 0; let r1 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); let r2 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); @@ -498,9 +526,8 @@ impl Fr { mut r4: u64, mut r5: u64, mut r6: u64, - mut r7: u64 - ) - { + mut r7: u64, + ) { // The Montgomery reduction here is based on Algorithm 14.32 in // Handbook of Applied Cryptography // <http://cacr.uwaterloo.ca/hac/about/chap14.pdf>. @@ -545,13 +572,21 @@ impl Fr { } impl SqrtField for Fr { - fn legendre(&self) -> ::LegendreSymbol { // s = self^((r - 1) // 2) - let s = self.pow([0x7fffffff80000000, 0xa9ded2017fff2dff, 0x199cec0404d0ec02, 0x39f6d3a994cebea4]); - if s == Self::zero() { Zero } - else if s == Self::one() { QuadraticResidue } - else { QuadraticNonResidue } + let s = self.pow([ + 0x7fffffff80000000, + 0xa9ded2017fff2dff, + 0x199cec0404d0ec02, + 0x39f6d3a994cebea4, + ]); + if s == Self::zero() { + Zero + } else if s == Self::one() { + QuadraticResidue + } else { + QuadraticNonResidue + } } fn sqrt(&self) -> Option<Self> { @@ -563,9 +598,19 @@ impl SqrtField for Fr { QuadraticResidue => { let mut c = Fr(ROOT_OF_UNITY); // r = self^((t + 1) // 2) - let mut r = self.pow([0x7fff2dff80000000, 0x4d0ec02a9ded201, 0x94cebea4199cec04, 0x39f6d3a9]); + let mut r = self.pow([ + 0x7fff2dff80000000, + 0x4d0ec02a9ded201, + 0x94cebea4199cec04, + 0x39f6d3a9, + ]); // t = self^t - let mut t = self.pow([0xfffe5bfeffffffff, 0x9a1d80553bda402, 0x299d7d483339d808, 0x73eda753]); + let mut t = self.pow([ + 0xfffe5bfeffffffff, + 0x9a1d80553bda402, + 0x299d7d483339d808, + 0x73eda753, + ]); let mut m = S; while t != Self::one() { @@ -598,7 +643,7 @@ impl SqrtField for Fr { } #[cfg(test)] -use rand::{SeedableRng, XorShiftRng, Rand}; +use rand::{Rand, SeedableRng, XorShiftRng}; #[test] fn test_fr_repr_ordering() { @@ -612,12 +657,30 @@ fn test_fr_repr_ordering() { assert!(b > a); } - assert_equality(FrRepr([9999, 9999, 9999, 9999]), FrRepr([9999, 9999, 9999, 9999])); - assert_equality(FrRepr([9999, 9998, 9999, 9999]), FrRepr([9999, 9998, 9999, 9999])); - assert_equality(FrRepr([9999, 9999, 9999, 9997]), FrRepr([9999, 9999, 9999, 9997])); - assert_lt(FrRepr([9999, 9997, 9999, 9998]), FrRepr([9999, 9997, 9999, 9999])); - assert_lt(FrRepr([9999, 9997, 9998, 9999]), FrRepr([9999, 9997, 9999, 9999])); - assert_lt(FrRepr([9, 9999, 9999, 9997]), FrRepr([9999, 9999, 9999, 9997])); + assert_equality( + FrRepr([9999, 9999, 9999, 9999]), + FrRepr([9999, 9999, 9999, 9999]), + ); + assert_equality( + FrRepr([9999, 9998, 9999, 9999]), + FrRepr([9999, 9998, 9999, 9999]), + ); + assert_equality( + FrRepr([9999, 9999, 9999, 9997]), + FrRepr([9999, 9999, 9999, 9997]), + ); + assert_lt( + FrRepr([9999, 9997, 9999, 9998]), + FrRepr([9999, 9997, 9999, 9999]), + ); + assert_lt( + FrRepr([9999, 9997, 9998, 9999]), + FrRepr([9999, 9997, 9999, 9999]), + ); + assert_lt( + FrRepr([9, 9999, 9999, 9997]), + FrRepr([9999, 9999, 9999, 9997]), + ); } #[test] @@ -646,13 +709,34 @@ fn test_fr_repr_is_zero() { #[test] fn test_fr_repr_div2() { - let mut a = FrRepr([0xbd2920b19c972321, 0x174ed0466a3be37e, 0xd468d5e3b551f0b5, 0xcb67c072733beefc]); + let mut a = FrRepr([ + 0xbd2920b19c972321, + 0x174ed0466a3be37e, + 0xd468d5e3b551f0b5, + 0xcb67c072733beefc, + ]); a.div2(); - assert_eq!(a, FrRepr([0x5e949058ce4b9190, 0x8ba76823351df1bf, 0x6a346af1daa8f85a, 0x65b3e039399df77e])); + assert_eq!( + a, + FrRepr([ + 0x5e949058ce4b9190, + 0x8ba76823351df1bf, + 0x6a346af1daa8f85a, + 0x65b3e039399df77e + ]) + ); for _ in 0..10 { a.div2(); } - assert_eq!(a, FrRepr([0x6fd7a524163392e4, 0x16a2e9da08cd477c, 0xdf9a8d1abc76aa3e, 0x196cf80e4e677d])); + assert_eq!( + a, + FrRepr([ + 0x6fd7a524163392e4, + 0x16a2e9da08cd477c, + 0xdf9a8d1abc76aa3e, + 0x196cf80e4e677d + ]) + ); for _ in 0..200 { a.div2(); } @@ -671,32 +755,46 @@ fn test_fr_repr_div2() { #[test] fn test_fr_repr_shr() { - let mut a = FrRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]); + let mut a = FrRepr([ + 0xb33fbaec482a283f, + 0x997de0d3a88cb3df, + 0x9af62d2a9a0e5525, + 0x36003ab08de70da1, + ]); a.shr(0); assert_eq!( a, - FrRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]) + FrRepr([ + 0xb33fbaec482a283f, + 0x997de0d3a88cb3df, + 0x9af62d2a9a0e5525, + 0x36003ab08de70da1 + ]) ); a.shr(1); assert_eq!( a, - FrRepr([0xd99fdd762415141f, 0xccbef069d44659ef, 0xcd7b16954d072a92, 0x1b001d5846f386d0]) + FrRepr([ + 0xd99fdd762415141f, + 0xccbef069d44659ef, + 0xcd7b16954d072a92, + 0x1b001d5846f386d0 + ]) ); a.shr(50); assert_eq!( a, - FrRepr([0xbc1a7511967bf667, 0xc5a55341caa4b32f, 0x75611bce1b4335e, 0x6c0]) + FrRepr([ + 0xbc1a7511967bf667, + 0xc5a55341caa4b32f, + 0x75611bce1b4335e, + 0x6c0 + ]) ); a.shr(130); - assert_eq!( - a, - FrRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0]) - ); + assert_eq!(a, FrRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0])); a.shr(64); - assert_eq!( - a, - FrRepr([0x1b0, 0x0, 0x0, 0x0]) - ); + assert_eq!(a, FrRepr([0x1b0, 0x0, 0x0, 0x0])); } #[test] @@ -738,9 +836,27 @@ fn test_fr_repr_num_bits() { fn test_fr_repr_sub_noborrow() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let mut t = FrRepr([0x8e62a7e85264e2c3, 0xb23d34c1941d3ca, 0x5976930b7502dd15, 0x600f3fb517bf5495]); - t.sub_noborrow(&FrRepr([0xd64f669809cbc6a4, 0xfa76cb9d90cf7637, 0xfefb0df9038d43b3, 0x298a30c744b31acf])); - assert!(t == FrRepr([0xb813415048991c1f, 0x10ad07ae88725d92, 0x5a7b851271759961, 0x36850eedd30c39c5])); + let mut t = FrRepr([ + 0x8e62a7e85264e2c3, + 0xb23d34c1941d3ca, + 0x5976930b7502dd15, + 0x600f3fb517bf5495, + ]); + t.sub_noborrow(&FrRepr([ + 0xd64f669809cbc6a4, + 0xfa76cb9d90cf7637, + 0xfefb0df9038d43b3, + 0x298a30c744b31acf, + ])); + assert!( + t + == FrRepr([ + 0xb813415048991c1f, + 0x10ad07ae88725d92, + 0x5a7b851271759961, + 0x36850eedd30c39c5 + ]) + ); for _ in 0..1000 { let mut a = FrRepr::rand(&mut rng); @@ -769,9 +885,27 @@ fn test_fr_repr_sub_noborrow() { } // Subtracting r+1 from r should produce -1 (mod 2**256) - let mut qplusone = FrRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - qplusone.sub_noborrow(&FrRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48])); - assert_eq!(qplusone, FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])); + let mut qplusone = FrRepr([ + 0xffffffff00000001, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48, + ]); + qplusone.sub_noborrow(&FrRepr([ + 0xffffffff00000002, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48, + ])); + assert_eq!( + qplusone, + FrRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff + ]) + ); } #[test] @@ -779,9 +913,19 @@ fn test_fr_legendre() { assert_eq!(QuadraticResidue, Fr::one().legendre()); assert_eq!(Zero, Fr::zero().legendre()); - let e = FrRepr([0x0dbc5349cd5664da, 0x8ac5b6296e3ae29d, 0x127cb819feceaa3b, 0x3a6b21fb03867191]); + let e = FrRepr([ + 0x0dbc5349cd5664da, + 0x8ac5b6296e3ae29d, + 0x127cb819feceaa3b, + 0x3a6b21fb03867191, + ]); assert_eq!(QuadraticResidue, Fr::from_repr(e).unwrap().legendre()); - let e = FrRepr([0x96341aefd047c045, 0x9b5f4254500a4d65, 0x1ee08223b68ac240, 0x31d9cd545c0ec7c6]); + let e = FrRepr([ + 0x96341aefd047c045, + 0x9b5f4254500a4d65, + 0x1ee08223b68ac240, + 0x31d9cd545c0ec7c6, + ]); assert_eq!(QuadraticNonResidue, Fr::from_repr(e).unwrap().legendre()); } @@ -789,9 +933,27 @@ fn test_fr_legendre() { fn test_fr_repr_add_nocarry() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let mut t = FrRepr([0xd64f669809cbc6a4, 0xfa76cb9d90cf7637, 0xfefb0df9038d43b3, 0x298a30c744b31acf]); - t.add_nocarry(&FrRepr([0x8e62a7e85264e2c3, 0xb23d34c1941d3ca, 0x5976930b7502dd15, 0x600f3fb517bf5495])); - assert_eq!(t, FrRepr([0x64b20e805c30a967, 0x59a9ee9aa114a02, 0x5871a104789020c9, 0x8999707c5c726f65])); + let mut t = FrRepr([ + 0xd64f669809cbc6a4, + 0xfa76cb9d90cf7637, + 0xfefb0df9038d43b3, + 0x298a30c744b31acf, + ]); + t.add_nocarry(&FrRepr([ + 0x8e62a7e85264e2c3, + 0xb23d34c1941d3ca, + 0x5976930b7502dd15, + 0x600f3fb517bf5495, + ])); + assert_eq!( + t, + FrRepr([ + 0x64b20e805c30a967, + 0x59a9ee9aa114a02, + 0x5871a104789020c9, + 0x8999707c5c726f65 + ]) + ); // Test for the associativity of addition. for _ in 0..1000 { @@ -836,7 +998,12 @@ fn test_fr_repr_add_nocarry() { } // Adding 1 to (2^256 - 1) should produce zero - let mut x = FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); + let mut x = FrRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + ]); x.add_nocarry(&FrRepr::from(1)); assert!(x.is_zero()); } @@ -848,8 +1015,20 @@ fn test_fr_is_valid() { a.0.sub_noborrow(&FrRepr::from(1)); assert!(a.is_valid()); assert!(Fr(FrRepr::from(0)).is_valid()); - assert!(Fr(FrRepr([0xffffffff00000000, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48])).is_valid()); - assert!(!Fr(FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])).is_valid()); + assert!( + Fr(FrRepr([ + 0xffffffff00000000, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48 + ])).is_valid() + ); + assert!(!Fr(FrRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff + ])).is_valid()); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -863,25 +1042,82 @@ fn test_fr_is_valid() { fn test_fr_add_assign() { { // Random number - let mut tmp = Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca])); + let mut tmp = Fr(FrRepr([ + 0x437ce7616d580765, + 0xd42d1ccb29d1235b, + 0xed8f753821bd1423, + 0x4eede1c9c89528ca, + ])); assert!(tmp.is_valid()); // Test that adding zero has no effect. tmp.add_assign(&Fr(FrRepr::from(0))); - assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); + assert_eq!( + tmp, + Fr(FrRepr([ + 0x437ce7616d580765, + 0xd42d1ccb29d1235b, + 0xed8f753821bd1423, + 0x4eede1c9c89528ca + ])) + ); // Add one and test for the result. tmp.add_assign(&Fr(FrRepr::from(1))); - assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580766, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); + assert_eq!( + tmp, + Fr(FrRepr([ + 0x437ce7616d580766, + 0xd42d1ccb29d1235b, + 0xed8f753821bd1423, + 0x4eede1c9c89528ca + ])) + ); // Add another random number that exercises the reduction. - tmp.add_assign(&Fr(FrRepr([0x946f435944f7dc79, 0xb55e7ee6533a9b9b, 0x1e43b84c2f6194ca, 0x58717ab525463496]))); - assert_eq!(tmp, Fr(FrRepr([0xd7ec2abbb24fe3de, 0x35cdf7ae7d0d62f7, 0xd899557c477cd0e9, 0x3371b52bc43de018]))); + tmp.add_assign(&Fr(FrRepr([ + 0x946f435944f7dc79, + 0xb55e7ee6533a9b9b, + 0x1e43b84c2f6194ca, + 0x58717ab525463496, + ]))); + assert_eq!( + tmp, + Fr(FrRepr([ + 0xd7ec2abbb24fe3de, + 0x35cdf7ae7d0d62f7, + 0xd899557c477cd0e9, + 0x3371b52bc43de018 + ])) + ); // Add one to (r - 1) and test for the result. - tmp = Fr(FrRepr([0xffffffff00000000, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48])); + tmp = Fr(FrRepr([ + 0xffffffff00000000, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48, + ])); tmp.add_assign(&Fr(FrRepr::from(1))); assert!(tmp.0.is_zero()); // Add a random number to another one such that the result is r - 1 - tmp = Fr(FrRepr([0xade5adacdccb6190, 0xaa21ee0f27db3ccd, 0x2550f4704ae39086, 0x591d1902e7c5ba27])); - tmp.add_assign(&Fr(FrRepr([0x521a525223349e70, 0xa99bb5f3d8231f31, 0xde8e397bebe477e, 0x1ad08e5041d7c321]))); - assert_eq!(tmp, Fr(FrRepr([0xffffffff00000000, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))); + tmp = Fr(FrRepr([ + 0xade5adacdccb6190, + 0xaa21ee0f27db3ccd, + 0x2550f4704ae39086, + 0x591d1902e7c5ba27, + ])); + tmp.add_assign(&Fr(FrRepr([ + 0x521a525223349e70, + 0xa99bb5f3d8231f31, + 0xde8e397bebe477e, + 0x1ad08e5041d7c321, + ]))); + assert_eq!( + tmp, + Fr(FrRepr([ + 0xffffffff00000000, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48 + ])) + ); // Add one to the result and test for it. tmp.add_assign(&Fr(FrRepr::from(1))); assert!(tmp.0.is_zero()); @@ -915,23 +1151,72 @@ fn test_fr_add_assign() { fn test_fr_sub_assign() { { // Test arbitrary subtraction that tests reduction. - let mut tmp = Fr(FrRepr([0x6a68c64b6f735a2b, 0xd5f4d143fe0a1972, 0x37c17f3829267c62, 0xa2f37391f30915c])); - tmp.sub_assign(&Fr(FrRepr([0xade5adacdccb6190, 0xaa21ee0f27db3ccd, 0x2550f4704ae39086, 0x591d1902e7c5ba27]))); - assert_eq!(tmp, Fr(FrRepr([0xbc83189d92a7f89c, 0x7f908737d62d38a3, 0x45aa62cfe7e4c3e1, 0x24ffc5896108547d]))); + let mut tmp = Fr(FrRepr([ + 0x6a68c64b6f735a2b, + 0xd5f4d143fe0a1972, + 0x37c17f3829267c62, + 0xa2f37391f30915c, + ])); + tmp.sub_assign(&Fr(FrRepr([ + 0xade5adacdccb6190, + 0xaa21ee0f27db3ccd, + 0x2550f4704ae39086, + 0x591d1902e7c5ba27, + ]))); + assert_eq!( + tmp, + Fr(FrRepr([ + 0xbc83189d92a7f89c, + 0x7f908737d62d38a3, + 0x45aa62cfe7e4c3e1, + 0x24ffc5896108547d + ])) + ); // Test the opposite subtraction which doesn't test reduction. - tmp = Fr(FrRepr([0xade5adacdccb6190, 0xaa21ee0f27db3ccd, 0x2550f4704ae39086, 0x591d1902e7c5ba27])); - tmp.sub_assign(&Fr(FrRepr([0x6a68c64b6f735a2b, 0xd5f4d143fe0a1972, 0x37c17f3829267c62, 0xa2f37391f30915c]))); - assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); + tmp = Fr(FrRepr([ + 0xade5adacdccb6190, + 0xaa21ee0f27db3ccd, + 0x2550f4704ae39086, + 0x591d1902e7c5ba27, + ])); + tmp.sub_assign(&Fr(FrRepr([ + 0x6a68c64b6f735a2b, + 0xd5f4d143fe0a1972, + 0x37c17f3829267c62, + 0xa2f37391f30915c, + ]))); + assert_eq!( + tmp, + Fr(FrRepr([ + 0x437ce7616d580765, + 0xd42d1ccb29d1235b, + 0xed8f753821bd1423, + 0x4eede1c9c89528ca + ])) + ); // Test for sensible results with zero tmp = Fr(FrRepr::from(0)); tmp.sub_assign(&Fr(FrRepr::from(0))); assert!(tmp.is_zero()); - tmp = Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca])); + tmp = Fr(FrRepr([ + 0x437ce7616d580765, + 0xd42d1ccb29d1235b, + 0xed8f753821bd1423, + 0x4eede1c9c89528ca, + ])); tmp.sub_assign(&Fr(FrRepr::from(0))); - assert_eq!(tmp, Fr(FrRepr([0x437ce7616d580765, 0xd42d1ccb29d1235b, 0xed8f753821bd1423, 0x4eede1c9c89528ca]))); + assert_eq!( + tmp, + Fr(FrRepr([ + 0x437ce7616d580765, + 0xd42d1ccb29d1235b, + 0xed8f753821bd1423, + 0x4eede1c9c89528ca + ])) + ); } let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -954,9 +1239,27 @@ fn test_fr_sub_assign() { #[test] fn test_fr_mul_assign() { - let mut tmp = Fr(FrRepr([0x6b7e9b8faeefc81a, 0xe30a8463f348ba42, 0xeff3cb67a8279c9c, 0x3d303651bd7c774d])); - tmp.mul_assign(&Fr(FrRepr([0x13ae28e3bc35ebeb, 0xa10f4488075cae2c, 0x8160e95a853c3b5d, 0x5ae3f03b561a841d]))); - assert!(tmp == Fr(FrRepr([0x23717213ce710f71, 0xdbee1fe53a16e1af, 0xf565d3e1c2a48000, 0x4426507ee75df9d7]))); + let mut tmp = Fr(FrRepr([ + 0x6b7e9b8faeefc81a, + 0xe30a8463f348ba42, + 0xeff3cb67a8279c9c, + 0x3d303651bd7c774d, + ])); + tmp.mul_assign(&Fr(FrRepr([ + 0x13ae28e3bc35ebeb, + 0xa10f4488075cae2c, + 0x8160e95a853c3b5d, + 0x5ae3f03b561a841d, + ]))); + assert!( + tmp + == Fr(FrRepr([ + 0x23717213ce710f71, + 0xdbee1fe53a16e1af, + 0xf565d3e1c2a48000, + 0x4426507ee75df9d7 + ])) + ); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1003,10 +1306,23 @@ fn test_fr_mul_assign() { #[test] fn test_fr_squaring() { - let mut a = Fr(FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0x73eda753299d7d47])); + let mut a = Fr(FrRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0x73eda753299d7d47, + ])); assert!(a.is_valid()); a.square(); - assert_eq!(a, Fr::from_repr(FrRepr([0xc0d698e7bde077b8, 0xb79a310579e76ec2, 0xac1da8d0a9af4e5f, 0x13f629c49bf23e97])).unwrap()); + assert_eq!( + a, + Fr::from_repr(FrRepr([ + 0xc0d698e7bde077b8, + 0xb79a310579e76ec2, + 0xac1da8d0a9af4e5f, + 0x13f629c49bf23e97 + ])).unwrap() + ); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -1135,17 +1451,39 @@ fn test_fr_sqrt() { #[test] fn test_fr_from_into_repr() { // r + 1 should not be in the field - assert!(Fr::from_repr(FrRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48])).is_err()); + assert!( + Fr::from_repr(FrRepr([ + 0xffffffff00000002, + 0x53bda402fffe5bfe, + 0x3339d80809a1d805, + 0x73eda753299d7d48 + ])).is_err() + ); // r should not be in the field assert!(Fr::from_repr(Fr::char()).is_err()); // Multiply some arbitrary representations to see if the result is as expected. - let a = FrRepr([0x25ebe3a3ad3c0c6a, 0x6990e39d092e817c, 0x941f900d42f5658e, 0x44f8a103b38a71e0]); + let a = FrRepr([ + 0x25ebe3a3ad3c0c6a, + 0x6990e39d092e817c, + 0x941f900d42f5658e, + 0x44f8a103b38a71e0, + ]); let mut a_fr = Fr::from_repr(a).unwrap(); - let b = FrRepr([0x264e9454885e2475, 0x46f7746bb0308370, 0x4683ef5347411f9, 0x58838d7f208d4492]); + let b = FrRepr([ + 0x264e9454885e2475, + 0x46f7746bb0308370, + 0x4683ef5347411f9, + 0x58838d7f208d4492, + ]); let b_fr = Fr::from_repr(b).unwrap(); - let c = FrRepr([0x48a09ab93cfc740d, 0x3a6600fbfc7a671, 0x838567017501d767, 0x7161d6da77745512]); + let c = FrRepr([ + 0x48a09ab93cfc740d, + 0x3a6600fbfc7a671, + 0x838567017501d767, + 0x7161d6da77745512, + ]); a_fr.mul_assign(&b_fr); assert_eq!(a_fr.into_repr(), c); @@ -1169,15 +1507,39 @@ fn test_fr_from_into_repr() { #[test] fn test_fr_repr_display() { assert_eq!( - format!("{}", FrRepr([0x2829c242fa826143, 0x1f32cf4dd4330917, 0x932e4e479d168cd9, 0x513c77587f563f64])), + format!( + "{}", + FrRepr([ + 0x2829c242fa826143, + 0x1f32cf4dd4330917, + 0x932e4e479d168cd9, + 0x513c77587f563f64 + ]) + ), "0x513c77587f563f64932e4e479d168cd91f32cf4dd43309172829c242fa826143".to_string() ); assert_eq!( - format!("{}", FrRepr([0x25ebe3a3ad3c0c6a, 0x6990e39d092e817c, 0x941f900d42f5658e, 0x44f8a103b38a71e0])), + format!( + "{}", + FrRepr([ + 0x25ebe3a3ad3c0c6a, + 0x6990e39d092e817c, + 0x941f900d42f5658e, + 0x44f8a103b38a71e0 + ]) + ), "0x44f8a103b38a71e0941f900d42f5658e6990e39d092e817c25ebe3a3ad3c0c6a".to_string() ); assert_eq!( - format!("{}", FrRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), + format!( + "{}", + FrRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff + ]) + ), "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string() ); assert_eq!( @@ -1189,11 +1551,27 @@ fn test_fr_repr_display() { #[test] fn test_fr_display() { assert_eq!( - format!("{}", Fr::from_repr(FrRepr([0xc3cae746a3b5ecc7, 0x185ec8eb3f5b5aee, 0x684499ffe4b9dd99, 0x7c9bba7afb68faa])).unwrap()), + format!( + "{}", + Fr::from_repr(FrRepr([ + 0xc3cae746a3b5ecc7, + 0x185ec8eb3f5b5aee, + 0x684499ffe4b9dd99, + 0x7c9bba7afb68faa + ])).unwrap() + ), "Fr(0x07c9bba7afb68faa684499ffe4b9dd99185ec8eb3f5b5aeec3cae746a3b5ecc7)".to_string() ); assert_eq!( - format!("{}", Fr::from_repr(FrRepr([0x44c71298ff198106, 0xb0ad10817df79b6a, 0xd034a80a2b74132b, 0x41cf9a1336f50719])).unwrap()), + format!( + "{}", + Fr::from_repr(FrRepr([ + 0x44c71298ff198106, + 0xb0ad10817df79b6a, + 0xd034a80a2b74132b, + 0x41cf9a1336f50719 + ])).unwrap() + ), "Fr(0x41cf9a1336f50719d034a80a2b74132bb0ad10817df79b6a44c71298ff198106)".to_string() ); } @@ -1207,15 +1585,20 @@ fn test_fr_num_bits() { #[test] fn test_fr_root_of_unity() { assert_eq!(Fr::S, 32); - assert_eq!(Fr::multiplicative_generator(), Fr::from_repr(FrRepr::from(7)).unwrap()); assert_eq!( - Fr::multiplicative_generator().pow([0xfffe5bfeffffffff, 0x9a1d80553bda402, 0x299d7d483339d808, 0x73eda753]), + Fr::multiplicative_generator(), + Fr::from_repr(FrRepr::from(7)).unwrap() + ); + assert_eq!( + Fr::multiplicative_generator().pow([ + 0xfffe5bfeffffffff, + 0x9a1d80553bda402, + 0x299d7d483339d808, + 0x73eda753 + ]), Fr::root_of_unity() ); - assert_eq!( - Fr::root_of_unity().pow([1 << Fr::S]), - Fr::one() - ); + assert_eq!(Fr::root_of_unity().pow([1 << Fr::S]), Fr::one()); assert!(Fr::multiplicative_generator().sqrt().is_none()); } diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 9213b88..07339d9 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -13,9 +13,10 @@ pub use self::fq::{Fq, FqRepr}; pub use self::fq2::Fq2; pub use self::fq6::Fq6; pub use self::fq12::Fq12; -pub use self::ec::{G1, G2, G1Affine, G2Affine, G1Prepared, G2Prepared, G1Uncompressed, G2Uncompressed, G1Compressed, G2Compressed}; +pub use self::ec::{G1, G1Affine, G1Compressed, G1Prepared, G1Uncompressed, G2, G2Affine, + G2Compressed, G2Prepared, G2Uncompressed}; -use super::{Engine, CurveAffine, Field, BitIterator}; +use super::{BitIterator, CurveAffine, Engine, Field}; // The BLS parameter x for BLS12-381 is -0xd201000000010000 const BLS_X: u64 = 0xd201000000010000; @@ -35,10 +36,13 @@ impl Engine for Bls12 { type Fqk = Fq12; fn miller_loop<'a, I>(i: I) -> Self::Fqk - where I: IntoIterator<Item=&'a ( - &'a <Self::G1Affine as CurveAffine>::Prepared, - &'a <Self::G2Affine as CurveAffine>::Prepared - )> + where + I: IntoIterator< + Item = &'a ( + &'a <Self::G1Affine as CurveAffine>::Prepared, + &'a <Self::G2Affine as CurveAffine>::Prepared, + ), + >, { let mut pairs = vec![]; for &(p, q) in i { @@ -48,12 +52,7 @@ impl Engine for Bls12 { } // Twisting isomorphism from E to E' - fn ell( - f: &mut Fq12, - coeffs: &(Fq2, Fq2, Fq2), - p: &G1Affine - ) - { + fn ell(f: &mut Fq12, coeffs: &(Fq2, Fq2, Fq2), p: &G1Affine) { let mut c0 = coeffs.0; let mut c1 = coeffs.1; @@ -112,8 +111,7 @@ impl Engine for Bls12 { r.frobenius_map(2); r.mul_assign(&f2); - fn exp_by_x(f: &mut Fq12, x: u64) - { + fn exp_by_x(f: &mut Fq12, x: u64) { *f = f.pow(&[x]); if BLS_X_IS_NEGATIVE { f.conjugate(); @@ -154,8 +152,8 @@ impl Engine for Bls12 { y1.mul_assign(&y2); Some(y1) - }, - None => None + } + None => None, } } } @@ -169,14 +167,11 @@ impl G2Prepared { if q.is_zero() { return G2Prepared { coeffs: vec![], - infinity: true - } + infinity: true, + }; } - fn doubling_step( - r: &mut G2 - ) -> (Fq2, Fq2, Fq2) - { + fn doubling_step(r: &mut G2) -> (Fq2, Fq2, Fq2) { // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf let mut tmp0 = r.x; tmp0.square(); @@ -247,11 +242,7 @@ impl G2Prepared { (tmp0, tmp3, tmp6) } - fn addition_step( - r: &mut G2, - q: &G2Affine - ) -> (Fq2, Fq2, Fq2) - { + fn addition_step(r: &mut G2, q: &G2Affine) -> (Fq2, Fq2, Fq2) { // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf let mut zsquared = r.z; zsquared.square(); @@ -360,7 +351,7 @@ impl G2Prepared { G2Prepared { coeffs: coeffs, - infinity: false + infinity: false, } } } diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs index 1b3d521..41a60a4 100644 --- a/src/bls12_381/tests/mod.rs +++ b/src/bls12_381/tests/mod.rs @@ -52,8 +52,7 @@ fn test_pairing_result_against_relic() { }); } -fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected: &[u8]) -{ +fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine = G::Affine>>(expected: &[u8]) { let mut e = G::zero(); let mut v = vec![]; @@ -198,9 +197,11 @@ fn test_g1_uncompressed_invalid_vectors() { y.into_repr().write_be(&mut o.as_mut()[48..]).unwrap(); if let Err(GroupDecodingError::NotInSubgroup) = o.into_affine() { - break + break; } else { - panic!("should have rejected the point because it isn't in the correct subgroup") + panic!( + "should have rejected the point because it isn't in the correct subgroup" + ) } } else { x.add_assign(&Fq::one()); @@ -327,7 +328,7 @@ fn test_g2_uncompressed_invalid_vectors() { x3b.mul_assign(&x); x3b.add_assign(&Fq2 { c0: Fq::from_repr(FqRepr::from(4)).unwrap(), - c1: Fq::from_repr(FqRepr::from(4)).unwrap() + c1: Fq::from_repr(FqRepr::from(4)).unwrap(), }); // TODO: perhaps expose coeff_b through API? if let Some(y) = x3b.sqrt() { @@ -338,9 +339,11 @@ fn test_g2_uncompressed_invalid_vectors() { y.c0.into_repr().write_be(&mut o.as_mut()[144..]).unwrap(); if let Err(GroupDecodingError::NotInSubgroup) = o.into_affine() { - break + break; } else { - panic!("should have rejected the point because it isn't in the correct subgroup") + panic!( + "should have rejected the point because it isn't in the correct subgroup" + ) } } else { x.add_assign(&Fq2::one()); @@ -428,7 +431,7 @@ fn test_g1_compressed_invalid_vectors() { o.as_mut()[0] |= 0b1000_0000; if let Err(GroupDecodingError::NotOnCurve) = o.into_affine() { - break + break; } else { panic!("should have rejected the point because it isn't on the curve") } @@ -452,9 +455,11 @@ fn test_g1_compressed_invalid_vectors() { o.as_mut()[0] |= 0b1000_0000; if let Err(GroupDecodingError::NotInSubgroup) = o.into_affine() { - break + break; } else { - panic!("should have rejected the point because it isn't in the correct subgroup") + panic!( + "should have rejected the point because it isn't in the correct subgroup" + ) } } else { x.add_assign(&Fq::one()); @@ -541,7 +546,7 @@ fn test_g2_compressed_invalid_vectors() { let mut o = o; let mut x = Fq2 { c0: Fq::one(), - c1: Fq::one() + c1: Fq::one(), }; loop { @@ -561,7 +566,7 @@ fn test_g2_compressed_invalid_vectors() { o.as_mut()[0] |= 0b1000_0000; if let Err(GroupDecodingError::NotOnCurve) = o.into_affine() { - break + break; } else { panic!("should have rejected the point because it isn't on the curve") } @@ -573,7 +578,7 @@ fn test_g2_compressed_invalid_vectors() { let mut o = o; let mut x = Fq2 { c0: Fq::one(), - c1: Fq::one() + c1: Fq::one(), }; loop { @@ -592,9 +597,11 @@ fn test_g2_compressed_invalid_vectors() { o.as_mut()[0] |= 0b1000_0000; if let Err(GroupDecodingError::NotInSubgroup) = o.into_affine() { - break + break; } else { - panic!("should have rejected the point because it isn't in the correct subgroup") + panic!( + "should have rejected the point because it isn't in the correct subgroup" + ) } } else { x.add_assign(&Fq2::one()); diff --git a/src/lib.rs b/src/lib.rs index 6968da5..bb2f770 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ // If the "u128-support" feature is enabled, this library can use // more efficient arithmetic. Only available in the nightly compiler. #![cfg_attr(feature = "u128-support", feature(i128_type))] - // `clippy` is a code linting tool for improving code quality by catching // common mistakes or strange code patterns. If the `clippy` feature is // provided, it is enabled and all compiler warnings are prohibited. @@ -13,12 +12,11 @@ #![cfg_attr(feature = "clippy", allow(unreadable_literal))] #![cfg_attr(feature = "clippy", allow(many_single_char_names))] #![cfg_attr(feature = "clippy", allow(new_without_default_derive))] - // Force public structures to implement Debug #![deny(missing_debug_implementations)] -extern crate rand; extern crate byteorder; +extern crate rand; #[cfg(test)] pub mod tests; @@ -35,22 +33,49 @@ use std::io::{self, Read, Write}; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are /// of prime order `r`, and are equipped with a bilinear pairing function. -pub trait Engine: Sized + 'static + Clone -{ +pub trait Engine: Sized + 'static + Clone { /// This is the scalar field of the G1/G2 groups. type Fr: PrimeField + SqrtField; /// The projective representation of an element in G1. - type G1: CurveProjective<Engine=Self, Base=Self::Fq, Scalar=Self::Fr, Affine=Self::G1Affine> + From<Self::G1Affine>; + type G1: CurveProjective< + Engine = Self, + Base = Self::Fq, + Scalar = Self::Fr, + Affine = Self::G1Affine, + > + + From<Self::G1Affine>; /// The affine representation of an element in G1. - type G1Affine: CurveAffine<Engine=Self, Base=Self::Fq, Scalar=Self::Fr, Projective=Self::G1, Pair=Self::G2Affine, PairingResult=Self::Fqk> + From<Self::G1>; + type G1Affine: CurveAffine< + Engine = Self, + Base = Self::Fq, + Scalar = Self::Fr, + Projective = Self::G1, + Pair = Self::G2Affine, + PairingResult = Self::Fqk, + > + + From<Self::G1>; /// The projective representation of an element in G2. - type G2: CurveProjective<Engine=Self, Base=Self::Fqe, Scalar=Self::Fr, Affine=Self::G2Affine> + From<Self::G2Affine>; + type G2: CurveProjective< + Engine = Self, + Base = Self::Fqe, + Scalar = Self::Fr, + Affine = Self::G2Affine, + > + + From<Self::G2Affine>; /// The affine representation of an element in G2. - type G2Affine: CurveAffine<Engine=Self, Base=Self::Fqe, Scalar=Self::Fr, Projective=Self::G2, Pair=Self::G1Affine, PairingResult=Self::Fqk> + From<Self::G2>; + type G2Affine: CurveAffine< + Engine = Self, + Base = Self::Fqe, + Scalar = Self::Fr, + Projective = Self::G2, + Pair = Self::G1Affine, + PairingResult = Self::Fqk, + > + + From<Self::G2>; /// The base field that hosts G1. type Fq: PrimeField + SqrtField; @@ -63,46 +88,47 @@ pub trait Engine: Sized + 'static + Clone /// Perform a miller loop with some number of (G1, G2) pairs. fn miller_loop<'a, I>(i: I) -> Self::Fqk - where I: IntoIterator<Item=&'a ( - &'a <Self::G1Affine as CurveAffine>::Prepared, - &'a <Self::G2Affine as CurveAffine>::Prepared - )>; + where + I: IntoIterator< + Item = &'a ( + &'a <Self::G1Affine as CurveAffine>::Prepared, + &'a <Self::G2Affine as CurveAffine>::Prepared, + ), + >; /// Perform final exponentiation of the result of a miller loop. fn final_exponentiation(&Self::Fqk) -> Option<Self::Fqk>; /// Performs a complete pairing operation `(p, q)`. fn pairing<G1, G2>(p: G1, q: G2) -> Self::Fqk - where G1: Into<Self::G1Affine>, - G2: Into<Self::G2Affine> + where + G1: Into<Self::G1Affine>, + G2: Into<Self::G2Affine>, { Self::final_exponentiation(&Self::miller_loop( - [( - &(p.into().prepare()), - &(q.into().prepare()) - )].into_iter() + [(&(p.into().prepare()), &(q.into().prepare()))].into_iter(), )).unwrap() } } /// Projective representation of an elliptic curve point guaranteed to be /// in the correct prime order subgroup. -pub trait CurveProjective: PartialEq + - Eq + - Sized + - Copy + - Clone + - Send + - Sync + - fmt::Debug + - fmt::Display + - rand::Rand + - 'static -{ - type Engine: Engine<Fr=Self::Scalar>; +pub trait CurveProjective + : PartialEq + + Eq + + Sized + + Copy + + Clone + + Send + + Sync + + fmt::Debug + + fmt::Display + + rand::Rand + + 'static { + type Engine: Engine<Fr = Self::Scalar>; type Scalar: PrimeField + SqrtField; type Base: SqrtField; - type Affine: CurveAffine<Projective=Self, Scalar=Self::Scalar>; + type Affine: CurveAffine<Projective = Self, Scalar = Self::Scalar>; /// Returns the additive identity. fn zero() -> Self; @@ -157,25 +183,17 @@ pub trait CurveProjective: PartialEq + /// Affine representation of an elliptic curve point guaranteed to be /// in the correct prime order subgroup. -pub trait CurveAffine: Copy + - Clone + - Sized + - Send + - Sync + - fmt::Debug + - fmt::Display + - PartialEq + - Eq + - 'static -{ - type Engine: Engine<Fr=Self::Scalar>; +pub trait CurveAffine + : Copy + Clone + Sized + Send + Sync + fmt::Debug + fmt::Display + PartialEq + Eq + 'static + { + type Engine: Engine<Fr = Self::Scalar>; type Scalar: PrimeField + SqrtField; type Base: SqrtField; - type Projective: CurveProjective<Affine=Self, Scalar=Self::Scalar>; + type Projective: CurveProjective<Affine = Self, Scalar = Self::Scalar>; type Prepared: Clone + Send + Sync + 'static; - type Uncompressed: EncodedPoint<Affine=Self>; - type Compressed: EncodedPoint<Affine=Self>; - type Pair: CurveAffine<Pair=Self>; + type Uncompressed: EncodedPoint<Affine = Self>; + type Compressed: EncodedPoint<Affine = Self>; + type Pair: CurveAffine<Pair = Self>; type PairingResult: Field; /// Returns the additive identity. @@ -217,15 +235,8 @@ pub trait CurveAffine: Copy + } /// An encoded elliptic curve point, which should essentially wrap a `[u8; N]`. -pub trait EncodedPoint: Sized + - Send + - Sync + - AsRef<[u8]> + - AsMut<[u8]> + - Clone + - Copy + - 'static -{ +pub trait EncodedPoint + : Sized + Send + Sync + AsRef<[u8]> + AsMut<[u8]> + Clone + Copy + 'static { type Affine: CurveAffine; /// Creates an empty representation. @@ -253,17 +264,9 @@ pub trait EncodedPoint: Sized + } /// This trait represents an element of a field. -pub trait Field: Sized + - Eq + - Copy + - Clone + - Send + - Sync + - fmt::Debug + - fmt::Display + - 'static + - rand::Rand -{ +pub trait Field + : Sized + Eq + Copy + Clone + Send + Sync + fmt::Debug + fmt::Display + 'static + rand::Rand + { /// Returns the zero element of the field, the additive identity. fn zero() -> Self; @@ -300,8 +303,7 @@ pub trait Field: Sized + /// Exponentiates this element by a number represented with `u64` limbs, /// least significant digit first. - fn pow<S: AsRef<[u64]>>(&self, exp: S) -> Self - { + fn pow<S: AsRef<[u64]>>(&self, exp: S) -> Self { let mut res = Self::one(); let mut found_one = false; @@ -323,8 +325,7 @@ pub trait Field: Sized + } /// This trait represents an element of a field that has a square root operation described for it. -pub trait SqrtField: Field -{ +pub trait SqrtField: Field { /// Returns the Legendre symbol of the field element. fn legendre(&self) -> LegendreSymbol; @@ -333,26 +334,25 @@ pub trait SqrtField: Field fn sqrt(&self) -> Option<Self>; } - /// This trait represents a wrapper around a biginteger which can encode any element of a particular /// prime field. It is a smart wrapper around a sequence of `u64` limbs, least-significant digit /// first. -pub trait PrimeFieldRepr: Sized + - Copy + - Clone + - Eq + - Ord + - Send + - Sync + - Default + - fmt::Debug + - fmt::Display + - 'static + - rand::Rand + - AsRef<[u64]> + - AsMut<[u64]> + - From<u64> -{ +pub trait PrimeFieldRepr + : Sized + + Copy + + Clone + + Eq + + Ord + + Send + + Sync + + Default + + fmt::Debug + + fmt::Display + + 'static + + rand::Rand + + AsRef<[u64]> + + AsMut<[u64]> + + From<u64> { /// Subtract another represetation from this one. fn sub_noborrow(&mut self, other: &Self); @@ -388,7 +388,7 @@ pub trait PrimeFieldRepr: Sized + /// Writes this `PrimeFieldRepr` as a big endian integer. fn write_be<W: Write>(&self, mut writer: W) -> io::Result<()> { - use byteorder::{WriteBytesExt, BigEndian}; + use byteorder::{BigEndian, WriteBytesExt}; for digit in self.as_ref().iter().rev() { writer.write_u64::<BigEndian>(*digit)?; @@ -399,7 +399,7 @@ pub trait PrimeFieldRepr: Sized + /// Reads a big endian integer into this representation. fn read_be<R: Read>(&mut self, mut reader: R) -> io::Result<()> { - use byteorder::{ReadBytesExt, BigEndian}; + use byteorder::{BigEndian, ReadBytesExt}; for digit in self.as_mut().iter_mut().rev() { *digit = reader.read_u64::<BigEndian>()?; @@ -413,7 +413,7 @@ pub trait PrimeFieldRepr: Sized + pub enum LegendreSymbol { Zero = 0, QuadraticResidue = 1, - QuadraticNonResidue = -1 + QuadraticNonResidue = -1, } /// An error that may occur when trying to interpret a `PrimeFieldRepr` as a @@ -421,13 +421,13 @@ pub enum LegendreSymbol { #[derive(Debug)] pub enum PrimeFieldDecodingError { /// The encoded value is not in the field - NotInField(String) + NotInField(String), } impl Error for PrimeFieldDecodingError { fn description(&self) -> &str { match *self { - PrimeFieldDecodingError::NotInField(..) => "not an element of the field" + PrimeFieldDecodingError::NotInField(..) => "not an element of the field", } } } @@ -454,7 +454,7 @@ pub enum GroupDecodingError { /// The compression mode of the encoded element was not as expected UnexpectedCompressionMode, /// The encoding contained bits that should not have been set - UnexpectedInformation + UnexpectedInformation, } impl Error for GroupDecodingError { @@ -463,8 +463,10 @@ impl Error for GroupDecodingError { GroupDecodingError::NotOnCurve => "coordinate(s) do not lie on the curve", GroupDecodingError::NotInSubgroup => "the element is not part of an r-order subgroup", GroupDecodingError::CoordinateDecodingError(..) => "coordinate(s) could not be decoded", - GroupDecodingError::UnexpectedCompressionMode => "encoding has unexpected compression mode", - GroupDecodingError::UnexpectedInformation => "encoding has unexpected information" + GroupDecodingError::UnexpectedCompressionMode => { + "encoding has unexpected compression mode" + } + GroupDecodingError::UnexpectedInformation => "encoding has unexpected information", } } } @@ -474,17 +476,14 @@ impl fmt::Display for GroupDecodingError { match *self { GroupDecodingError::CoordinateDecodingError(description, ref err) => { write!(f, "{} decoding error: {}", description, err) - }, - _ => { - write!(f, "{}", self.description()) } + _ => write!(f, "{}", self.description()), } } } /// This represents an element of a prime field. -pub trait PrimeField: Field -{ +pub trait PrimeField: Field { /// The prime field can be converted back and forth into this biginteger /// representation. type Repr: PrimeFieldRepr + From<Self>; @@ -519,7 +518,7 @@ pub trait PrimeField: Field res.mul_assign(&ten); res.add_assign(&Self::from_repr(Self::Repr::from(u64::from(c))).unwrap()); - }, + } None => { return None; } @@ -560,17 +559,14 @@ pub trait PrimeField: Field #[derive(Debug)] pub struct BitIterator<E> { t: E, - n: usize + n: usize, } impl<E: AsRef<[u64]>> BitIterator<E> { pub fn new(t: E) -> Self { let n = t.as_ref().len() * 64; - BitIterator { - t: t, - n: n - } + BitIterator { t: t, n: n } } } @@ -603,7 +599,12 @@ fn test_bit_iterator() { let expected = "1010010101111110101010000101101011101000011101110101001000011001100100100011011010001011011011010001011011101100110100111011010010110001000011110100110001100110011101101000101100011100100100100100001010011101010111110011101011000011101000111011011101011001"; - let mut a = BitIterator::new([0x429d5f3ac3a3b759, 0xb10f4c66768b1c92, 0x92368b6d16ecd3b4, 0xa57ea85ae8775219]); + let mut a = BitIterator::new([ + 0x429d5f3ac3a3b759, + 0xb10f4c66768b1c92, + 0x92368b6d16ecd3b4, + 0xa57ea85ae8775219, + ]); for e in expected.chars() { assert!(a.next().unwrap() == (e == '1')); diff --git a/src/tests/curve.rs b/src/tests/curve.rs index e6deec1..fa656cc 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -1,9 +1,8 @@ -use rand::{SeedableRng, XorShiftRng, Rand, Rng}; +use rand::{Rand, Rng, SeedableRng, XorShiftRng}; -use ::{CurveProjective, CurveAffine, Field, EncodedPoint}; +use {CurveAffine, CurveProjective, EncodedPoint, Field}; -pub fn curve_tests<G: CurveProjective>() -{ +pub fn curve_tests<G: CurveProjective>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); // Negation edge case with zero. @@ -48,7 +47,10 @@ pub fn curve_tests<G: CurveProjective>() { let a = G::rand(&mut rng); let b = a.into_affine().into_projective(); - let c = a.into_affine().into_projective().into_affine().into_projective(); + let c = a.into_affine() + .into_projective() + .into_affine() + .into_projective(); assert_eq!(a, b); assert_eq!(b, c); } @@ -63,8 +65,8 @@ pub fn curve_tests<G: CurveProjective>() } fn random_wnaf_tests<G: CurveProjective>() { - use ::wnaf::*; - use ::PrimeField; + use wnaf::*; + use PrimeField; let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -89,7 +91,7 @@ fn random_wnaf_tests<G: CurveProjective>() { } { - fn only_compiles_if_send<S: Send>(_: &S) { } + fn only_compiles_if_send<S: Send>(_: &S) {} for _ in 0..100 { let g = G::rand(&mut rng); @@ -370,7 +372,9 @@ fn random_transformation_tests<G: CurveProjective>() { v[s] = v[s].into_affine().into_projective(); } - let expected_v = v.iter().map(|v| v.into_affine().into_projective()).collect::<Vec<_>>(); + let expected_v = v.iter() + .map(|v| v.into_affine().into_projective()) + .collect::<Vec<_>>(); G::batch_normalization(&mut v); for i in &v { @@ -381,8 +385,7 @@ fn random_transformation_tests<G: CurveProjective>() { } } -fn random_encoding_tests<G: CurveAffine>() -{ +fn random_encoding_tests<G: CurveAffine>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); assert_eq!( diff --git a/src/tests/engine.rs b/src/tests/engine.rs index 03ffc6e..52ff4e0 100644 --- a/src/tests/engine.rs +++ b/src/tests/engine.rs @@ -1,11 +1,10 @@ -use rand::{SeedableRng, XorShiftRng, Rand}; +use rand::{Rand, SeedableRng, XorShiftRng}; -use ::{Engine, CurveProjective, CurveAffine, Field, PrimeField}; +use {CurveAffine, CurveProjective, Engine, Field, PrimeField}; -pub fn engine_tests<E: Engine>() -{ +pub fn engine_tests<E: Engine>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - + for _ in 0..10 { let a = E::G1::rand(&mut rng).into_affine(); let b = E::G2::rand(&mut rng).into_affine(); @@ -84,9 +83,8 @@ fn random_miller_loop_tests<E: Engine>() { let c = c.into_affine().prepare(); let d = d.into_affine().prepare(); - let abcd_with_double_loop = E::final_exponentiation( - &E::miller_loop(&[(&a, &b), (&c, &d)]) - ).unwrap(); + let abcd_with_double_loop = + E::final_exponentiation(&E::miller_loop(&[(&a, &b), (&c, &d)])).unwrap(); assert_eq!(abcd, abcd_with_double_loop); } diff --git a/src/tests/field.rs b/src/tests/field.rs index bddb93e..74422fd 100644 --- a/src/tests/field.rs +++ b/src/tests/field.rs @@ -1,11 +1,11 @@ use rand::{Rng, SeedableRng, XorShiftRng}; -use ::{SqrtField, Field, PrimeField, LegendreSymbol}; +use {Field, LegendreSymbol, PrimeField, SqrtField}; pub fn random_frobenius_tests<F: Field, C: AsRef<[u64]>>(characteristic: C, maxpower: usize) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); for _ in 0..100 { - for i in 0..(maxpower+1) { + for i in 0..(maxpower + 1) { let mut a = F::rand(&mut rng); let mut b = a; diff --git a/src/tests/repr.rs b/src/tests/repr.rs index ed07eae..3b9d76d 100644 --- a/src/tests/repr.rs +++ b/src/tests/repr.rs @@ -1,5 +1,5 @@ use rand::{SeedableRng, XorShiftRng}; -use ::{PrimeFieldRepr}; +use PrimeFieldRepr; pub fn random_repr_tests<R: PrimeFieldRepr>() { random_encoding_tests::<R>(); @@ -28,7 +28,7 @@ fn random_shl_tests<R: PrimeFieldRepr>() { for _ in 0..100 { let r = R::rand(&mut rng); - for shift in 0..(r.num_bits()+1) { + for shift in 0..(r.num_bits() + 1) { let mut r1 = r; let mut r2 = r; @@ -49,7 +49,7 @@ fn random_shr_tests<R: PrimeFieldRepr>() { for _ in 0..100 { let r = R::rand(&mut rng); - for shift in 0..(r.num_bits()+1) { + for shift in 0..(r.num_bits() + 1) { let mut r1 = r; let mut r2 = r; diff --git a/src/wnaf.rs b/src/wnaf.rs index 0cdae3b..ef94de2 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -1,32 +1,30 @@ -use super::{CurveProjective, PrimeFieldRepr, PrimeField}; +use super::{CurveProjective, PrimeField, PrimeFieldRepr}; /// Replaces the contents of `table` with a w-NAF window table for the given window size. -pub(crate) fn wnaf_table<G: CurveProjective>(table: &mut Vec<G>, mut base: G, window: usize) -{ +pub(crate) fn wnaf_table<G: CurveProjective>(table: &mut Vec<G>, mut base: G, window: usize) { table.truncate(0); - table.reserve(1 << (window-1)); + table.reserve(1 << (window - 1)); let mut dbl = base; dbl.double(); - for _ in 0..(1 << (window-1)) { + for _ in 0..(1 << (window - 1)) { table.push(base); base.add_assign(&dbl); } } /// Replaces the contents of `wnaf` with the w-NAF representation of a scalar. -pub(crate) fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window: usize) -{ +pub(crate) fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window: usize) { wnaf.truncate(0); while !c.is_zero() { let mut u; if c.is_odd() { - u = (c.as_ref()[0] % (1 << (window+1))) as i64; + u = (c.as_ref()[0] % (1 << (window + 1))) as i64; if u > (1 << window) { - u -= 1 << (window+1); + u -= 1 << (window + 1); } if u > 0 { @@ -48,8 +46,7 @@ pub(crate) fn wnaf_form<S: PrimeFieldRepr>(wnaf: &mut Vec<i64>, mut c: S, window /// /// This function must be provided a `table` and `wnaf` that were constructed with /// the same window size; otherwise, it may panic or produce invalid results. -pub(crate) fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G -{ +pub(crate) fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G { let mut result = G::zero(); let mut found_one = false; @@ -63,9 +60,9 @@ pub(crate) fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G found_one = true; if *n > 0 { - result.add_assign(&table[(n/2) as usize]); + result.add_assign(&table[(n / 2) as usize]); } else { - result.sub_assign(&table[((-n)/2) as usize]); + result.sub_assign(&table[((-n) / 2) as usize]); } } } @@ -78,7 +75,7 @@ pub(crate) fn wnaf_exp<G: CurveProjective>(table: &[G], wnaf: &[i64]) -> G pub struct Wnaf<W, B, S> { base: B, scalar: S, - window_size: W + window_size: W, } impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { @@ -87,18 +84,13 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { Wnaf { base: vec![], scalar: vec![], - window_size: () + window_size: (), } } /// Given a base and a number of scalars, compute a window table and return a `Wnaf` object that /// can perform exponentiations with `.scalar(..)`. - pub fn base( - &mut self, - base: G, - num_scalars: usize - ) -> Wnaf<usize, &[G], &mut Vec<i64>> - { + pub fn base(&mut self, base: G, num_scalars: usize) -> Wnaf<usize, &[G], &mut Vec<i64>> { // Compute the appropriate window size based on the number of scalars. let window_size = G::recommended_wnaf_for_num_scalars(num_scalars); @@ -110,7 +102,7 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { Wnaf { base: &self.base[..], scalar: &mut self.scalar, - window_size: window_size + window_size: window_size, } } @@ -118,9 +110,8 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { /// exponentiations with `.base(..)`. pub fn scalar( &mut self, - scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr - ) -> Wnaf<usize, &mut Vec<G>, &[i64]> - { + scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr, + ) -> Wnaf<usize, &mut Vec<G>, &[i64]> { // Compute the appropriate window size for the scalar. let window_size = G::recommended_wnaf_for_scalar(scalar); @@ -132,7 +123,7 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { Wnaf { base: &mut self.base, scalar: &self.scalar[..], - window_size: window_size + window_size: window_size, } } } @@ -144,7 +135,7 @@ impl<'a, G: CurveProjective> Wnaf<usize, &'a [G], &'a mut Vec<i64>> { Wnaf { base: self.base, scalar: vec![], - window_size: self.window_size + window_size: self.window_size, } } } @@ -157,18 +148,16 @@ impl<'a, G: CurveProjective> Wnaf<usize, &'a mut Vec<G>, &'a [i64]> { Wnaf { base: vec![], scalar: self.scalar, - window_size: self.window_size + window_size: self.window_size, } } } impl<B, S: AsRef<[i64]>> Wnaf<usize, B, S> { /// Performs exponentiation given a base. - pub fn base<G: CurveProjective>( - &mut self, - base: G - ) -> G - where B: AsMut<Vec<G>> + pub fn base<G: CurveProjective>(&mut self, base: G) -> G + where + B: AsMut<Vec<G>>, { wnaf_table(self.base.as_mut(), base, self.window_size); wnaf_exp(self.base.as_mut(), self.scalar.as_ref()) @@ -179,9 +168,10 @@ impl<B, S: AsMut<Vec<i64>>> Wnaf<usize, B, S> { /// Performs exponentiation given a scalar. pub fn scalar<G: CurveProjective>( &mut self, - scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr + scalar: <<G as CurveProjective>::Scalar as PrimeField>::Repr, ) -> G - where B: AsRef<[G]> + where + B: AsRef<[G]>, { wnaf_form(self.scalar.as_mut(), scalar, self.window_size); wnaf_exp(self.base.as_ref(), self.scalar.as_mut()) From 92d2c132855a7394a61fbc24673e140dc1c3119b Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Sun, 4 Mar 2018 20:01:23 -0700 Subject: [PATCH 103/140] Release of pairing 0.14.0. --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6080757..b53db43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.13.2" +version = "0.14.0" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" diff --git a/README.md b/README.md index d9ef0b4..d71d0c5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ If you're using a supported platform and the nightly Rust compiler, you can enab ```toml [dependencies.pairing] -version = "0.13" +version = "0.14" features = ["u128-support"] ``` From 4cf5a534ec1feb9f731da115c9fa57923ab741f9 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 29 Mar 2018 09:18:26 -0600 Subject: [PATCH 104/140] i128_type feature has been stabilized in Rust. --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bb2f770..e16c8c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,3 @@ -// If the "u128-support" feature is enabled, this library can use -// more efficient arithmetic. Only available in the nightly compiler. -#![cfg_attr(feature = "u128-support", feature(i128_type))] // `clippy` is a code linting tool for improving code quality by catching // common mistakes or strange code patterns. If the `clippy` feature is // provided, it is enabled and all compiler warnings are prohibited. From bcc8379a7fcb141000e1b78668141f6fabd36faf Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 29 Mar 2018 09:23:20 -0600 Subject: [PATCH 105/140] Version bump. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b53db43..11d8a8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.14.0" +version = "0.14.1" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" From fcaddaa3564cc324c194475d5dc0f26c68367c47 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 29 Mar 2018 10:13:00 -0600 Subject: [PATCH 106/140] Update Clippy to support latest nightly, and fix some lints. --- Cargo.toml | 2 +- src/bls12_381/mod.rs | 2 +- src/lib.rs | 2 +- src/wnaf.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 11d8a8b..da8dc5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.4" byteorder = "1" -clippy = { version = "0.0.186", optional = true } +clippy = { version = "0.0.190", optional = true } [features] unstable-features = ["expose-arith"] diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 07339d9..c35b852 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -350,7 +350,7 @@ impl G2Prepared { coeffs.push(doubling_step(&mut r)); G2Prepared { - coeffs: coeffs, + coeffs, infinity: false, } } diff --git a/src/lib.rs b/src/lib.rs index e16c8c0..c3c8f31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -563,7 +563,7 @@ impl<E: AsRef<[u64]>> BitIterator<E> { pub fn new(t: E) -> Self { let n = t.as_ref().len() * 64; - BitIterator { t: t, n: n } + BitIterator { t, n } } } diff --git a/src/wnaf.rs b/src/wnaf.rs index ef94de2..69c6fd9 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -102,7 +102,7 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { Wnaf { base: &self.base[..], scalar: &mut self.scalar, - window_size: window_size, + window_size, } } @@ -123,7 +123,7 @@ impl<G: CurveProjective> Wnaf<(), Vec<G>, Vec<i64>> { Wnaf { base: &mut self.base, scalar: &self.scalar[..], - window_size: window_size, + window_size, } } } From 2d12b9a858c31bd31489d22151eeff59abd2b3d9 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 29 Mar 2018 11:18:15 -0600 Subject: [PATCH 107/140] Apply newer rustfmt rules. --- src/bls12_381/fq.rs | 51 ++++++++++++++--------------- src/bls12_381/fr.rs | 26 +++++++-------- src/lib.rs | 79 +++++++++++++++++++++++---------------------- 3 files changed, 77 insertions(+), 79 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 2f08eea..c35360a 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -2135,15 +2135,14 @@ fn test_fq_repr_sub_noborrow() { 0x7c0577a26f59d5, ])); assert!( - t - == FqRepr([ - 0x40a12b8967c54bae, - 0xdeae37a0837d0d7b, - 0xe592c487bae374e, - 0xaf26bbc934462a61, - 0x32d6cc6e2b7a4a03, - 0xcdaf23e091c0313 - ]) + t == FqRepr([ + 0x40a12b8967c54bae, + 0xdeae37a0837d0d7b, + 0xe592c487bae374e, + 0xaf26bbc934462a61, + 0x32d6cc6e2b7a4a03, + 0xcdaf23e091c0313 + ]) ); for _ in 0..1000 { @@ -2223,15 +2222,14 @@ fn test_fq_repr_add_nocarry() { 0x7c0577a26f59d5, ])); assert!( - t - == FqRepr([ - 0xcfae1db798be8c04, - 0x999906db15a10d5a, - 0x270fa8d9defc6f79, - 0x83abb199c240f7b6, - 0x27469abae93e1ff6, - 0xdd2fd2d4dfab6be - ]) + t == FqRepr([ + 0xcfae1db798be8c04, + 0x999906db15a10d5a, + 0x270fa8d9defc6f79, + 0x83abb199c240f7b6, + 0x27469abae93e1ff6, + 0xdd2fd2d4dfab6be + ]) ); // Test for the associativity of addition. @@ -2575,15 +2573,14 @@ fn test_fq_mul_assign() { 0x1162b680fb8e9566, ]))); assert!( - tmp - == Fq(FqRepr([ - 0x9dc4000001ebfe14, - 0x2850078997b00193, - 0xa8197f1abb4d7bf, - 0xc0309573f4bfe871, - 0xf48d0923ffaf7620, - 0x11d4b58c7a926e66 - ])) + tmp == Fq(FqRepr([ + 0x9dc4000001ebfe14, + 0x2850078997b00193, + 0xa8197f1abb4d7bf, + 0xc0309573f4bfe871, + 0xf48d0923ffaf7620, + 0x11d4b58c7a926e66 + ])) ); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index f1bc9e6..31fbe8b 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -849,13 +849,12 @@ fn test_fr_repr_sub_noborrow() { 0x298a30c744b31acf, ])); assert!( - t - == FrRepr([ - 0xb813415048991c1f, - 0x10ad07ae88725d92, - 0x5a7b851271759961, - 0x36850eedd30c39c5 - ]) + t == FrRepr([ + 0xb813415048991c1f, + 0x10ad07ae88725d92, + 0x5a7b851271759961, + 0x36850eedd30c39c5 + ]) ); for _ in 0..1000 { @@ -1252,13 +1251,12 @@ fn test_fr_mul_assign() { 0x5ae3f03b561a841d, ]))); assert!( - tmp - == Fr(FrRepr([ - 0x23717213ce710f71, - 0xdbee1fe53a16e1af, - 0xf565d3e1c2a48000, - 0x4426507ee75df9d7 - ])) + tmp == Fr(FrRepr([ + 0x23717213ce710f71, + 0xdbee1fe53a16e1af, + 0xf565d3e1c2a48000, + 0x4426507ee75df9d7 + ])) ); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/lib.rs b/src/lib.rs index c3c8f31..481ff26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,42 +36,42 @@ pub trait Engine: Sized + 'static + Clone { /// The projective representation of an element in G1. type G1: CurveProjective< - Engine = Self, - Base = Self::Fq, - Scalar = Self::Fr, - Affine = Self::G1Affine, - > + Engine = Self, + Base = Self::Fq, + Scalar = Self::Fr, + Affine = Self::G1Affine, + > + From<Self::G1Affine>; /// The affine representation of an element in G1. type G1Affine: CurveAffine< - Engine = Self, - Base = Self::Fq, - Scalar = Self::Fr, - Projective = Self::G1, - Pair = Self::G2Affine, - PairingResult = Self::Fqk, - > + Engine = Self, + Base = Self::Fq, + Scalar = Self::Fr, + Projective = Self::G1, + Pair = Self::G2Affine, + PairingResult = Self::Fqk, + > + From<Self::G1>; /// The projective representation of an element in G2. type G2: CurveProjective< - Engine = Self, - Base = Self::Fqe, - Scalar = Self::Fr, - Affine = Self::G2Affine, - > + Engine = Self, + Base = Self::Fqe, + Scalar = Self::Fr, + Affine = Self::G2Affine, + > + From<Self::G2Affine>; /// The affine representation of an element in G2. type G2Affine: CurveAffine< - Engine = Self, - Base = Self::Fqe, - Scalar = Self::Fr, - Projective = Self::G2, - Pair = Self::G1Affine, - PairingResult = Self::Fqk, - > + Engine = Self, + Base = Self::Fqe, + Scalar = Self::Fr, + Projective = Self::G2, + Pair = Self::G1Affine, + PairingResult = Self::Fqk, + > + From<Self::G2>; /// The base field that hosts G1. @@ -110,8 +110,8 @@ pub trait Engine: Sized + 'static + Clone { /// Projective representation of an elliptic curve point guaranteed to be /// in the correct prime order subgroup. -pub trait CurveProjective - : PartialEq +pub trait CurveProjective: + PartialEq + Eq + Sized + Copy @@ -121,7 +121,8 @@ pub trait CurveProjective + fmt::Debug + fmt::Display + rand::Rand - + 'static { + + 'static +{ type Engine: Engine<Fr = Self::Scalar>; type Scalar: PrimeField + SqrtField; type Base: SqrtField; @@ -180,9 +181,9 @@ pub trait CurveProjective /// Affine representation of an elliptic curve point guaranteed to be /// in the correct prime order subgroup. -pub trait CurveAffine - : Copy + Clone + Sized + Send + Sync + fmt::Debug + fmt::Display + PartialEq + Eq + 'static - { +pub trait CurveAffine: + Copy + Clone + Sized + Send + Sync + fmt::Debug + fmt::Display + PartialEq + Eq + 'static +{ type Engine: Engine<Fr = Self::Scalar>; type Scalar: PrimeField + SqrtField; type Base: SqrtField; @@ -232,8 +233,9 @@ pub trait CurveAffine } /// An encoded elliptic curve point, which should essentially wrap a `[u8; N]`. -pub trait EncodedPoint - : Sized + Send + Sync + AsRef<[u8]> + AsMut<[u8]> + Clone + Copy + 'static { +pub trait EncodedPoint: + Sized + Send + Sync + AsRef<[u8]> + AsMut<[u8]> + Clone + Copy + 'static +{ type Affine: CurveAffine; /// Creates an empty representation. @@ -261,9 +263,9 @@ pub trait EncodedPoint } /// This trait represents an element of a field. -pub trait Field - : Sized + Eq + Copy + Clone + Send + Sync + fmt::Debug + fmt::Display + 'static + rand::Rand - { +pub trait Field: + Sized + Eq + Copy + Clone + Send + Sync + fmt::Debug + fmt::Display + 'static + rand::Rand +{ /// Returns the zero element of the field, the additive identity. fn zero() -> Self; @@ -334,8 +336,8 @@ pub trait SqrtField: Field { /// This trait represents a wrapper around a biginteger which can encode any element of a particular /// prime field. It is a smart wrapper around a sequence of `u64` limbs, least-significant digit /// first. -pub trait PrimeFieldRepr - : Sized +pub trait PrimeFieldRepr: + Sized + Copy + Clone + Eq @@ -349,7 +351,8 @@ pub trait PrimeFieldRepr + rand::Rand + AsRef<[u64]> + AsMut<[u64]> - + From<u64> { + + From<u64> +{ /// Subtract another represetation from this one. fn sub_noborrow(&mut self, other: &Self); From 98bab6877a2f06b9b6eae1d56ece59a93767caab Mon Sep 17 00:00:00 2001 From: str4d <str4d@mail.i2p> Date: Thu, 17 May 2018 09:34:16 +1200 Subject: [PATCH 108/140] Add read_le and write_le to PrimeFieldRepr --- src/lib.rs | 22 ++++++++++++++++++++++ src/tests/repr.rs | 43 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 481ff26..930d806 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -407,6 +407,28 @@ pub trait PrimeFieldRepr: Ok(()) } + + /// Writes this `PrimeFieldRepr` as a little endian integer. + fn write_le<W: Write>(&self, mut writer: W) -> io::Result<()> { + use byteorder::{LittleEndian, WriteBytesExt}; + + for digit in self.as_ref().iter() { + writer.write_u64::<LittleEndian>(*digit)?; + } + + Ok(()) + } + + /// Reads a little endian integer into this representation. + fn read_le<R: Read>(&mut self, mut reader: R) -> io::Result<()> { + use byteorder::{LittleEndian, ReadBytesExt}; + + for digit in self.as_mut().iter_mut() { + *digit = reader.read_u64::<LittleEndian>()?; + } + + Ok(()) + } } #[derive(Debug, PartialEq)] diff --git a/src/tests/repr.rs b/src/tests/repr.rs index 3b9d76d..681a476 100644 --- a/src/tests/repr.rs +++ b/src/tests/repr.rs @@ -12,13 +12,46 @@ fn random_encoding_tests<R: PrimeFieldRepr>() { for _ in 0..1000 { let r = R::rand(&mut rng); - let mut rdecoded = R::default(); - let mut v: Vec<u8> = vec![]; - r.write_be(&mut v).unwrap(); - rdecoded.read_be(&v[0..]).unwrap(); + // Big endian + { + let mut rdecoded = R::default(); - assert_eq!(r, rdecoded); + let mut v: Vec<u8> = vec![]; + r.write_be(&mut v).unwrap(); + rdecoded.read_be(&v[0..]).unwrap(); + + assert_eq!(r, rdecoded); + } + + // Little endian + { + let mut rdecoded = R::default(); + + let mut v: Vec<u8> = vec![]; + r.write_le(&mut v).unwrap(); + rdecoded.read_le(&v[0..]).unwrap(); + + assert_eq!(r, rdecoded); + } + + { + let mut rdecoded_le = R::default(); + let mut rdecoded_be_flip = R::default(); + + let mut v: Vec<u8> = vec![]; + r.write_le(&mut v).unwrap(); + + // This reads in little-endian, so we are done. + rdecoded_le.read_le(&v[..]).unwrap(); + + // This reads in big-endian, so we perform a swap of the + // bytes beforehand. + let v: Vec<u8> = v.into_iter().rev().collect(); + rdecoded_be_flip.read_be(&v[..]).unwrap(); + + assert_eq!(rdecoded_le, rdecoded_be_flip); + } } } From 2a28b614689d3219afe36d51c75768c6e76000ca Mon Sep 17 00:00:00 2001 From: str4d <str4d@mail.i2p> Date: Thu, 17 May 2018 16:52:19 +1200 Subject: [PATCH 109/140] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index da8dc5e..f1f104c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.14.1" +version = "0.14.2" authors = ["Sean Bowe <ewillbefull@gmail.com>"] license = "MIT/Apache-2.0" From 97bdd1655f85d4d2ffef5a1e87f5e37cdf20b333 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 17 May 2018 10:44:28 -0600 Subject: [PATCH 110/140] Update clippy --- Cargo.toml | 2 +- src/lib.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f1f104c..98725aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.4" byteorder = "1" -clippy = { version = "0.0.190", optional = true } +clippy = { version = "0.0.200", optional = true } [features] unstable-features = ["expose-arith"] diff --git a/src/lib.rs b/src/lib.rs index 930d806..5343c6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ #![cfg_attr(feature = "clippy", allow(unreadable_literal))] #![cfg_attr(feature = "clippy", allow(many_single_char_names))] #![cfg_attr(feature = "clippy", allow(new_without_default_derive))] +#![cfg_attr(feature = "clippy", allow(write_literal))] // Force public structures to implement Debug #![deny(missing_debug_implementations)] From e4143a4bbc06f18e354ac276a181571eb5f9fff0 Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 17 May 2018 10:50:56 -0600 Subject: [PATCH 111/140] Apply rustfmt to benchmarks --- benches/bls12_381/ec.rs | 24 +++++++++--- benches/bls12_381/fq.rs | 78 +++++++++++++++++++++----------------- benches/bls12_381/fq12.rs | 26 ++++++------- benches/bls12_381/fq2.rs | 30 ++++++--------- benches/bls12_381/fr.rs | 76 +++++++++++++++++++++---------------- benches/bls12_381/mod.rs | 42 ++++++++++---------- benches/pairing_benches.rs | 4 +- 7 files changed, 152 insertions(+), 128 deletions(-) diff --git a/benches/bls12_381/ec.rs b/benches/bls12_381/ec.rs index 608e7f0..421881d 100644 --- a/benches/bls12_381/ec.rs +++ b/benches/bls12_381/ec.rs @@ -10,7 +10,9 @@ mod g1 { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(G1, Fr)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), Fr::rand(&mut rng))).collect(); + let v: Vec<(G1, Fr)> = (0..SAMPLES) + .map(|_| (G1::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -27,7 +29,9 @@ mod g1 { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(G1, G1)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), G1::rand(&mut rng))).collect(); + let v: Vec<(G1, G1)> = (0..SAMPLES) + .map(|_| (G1::rand(&mut rng), G1::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -44,7 +48,9 @@ mod g1 { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(G1, G1Affine)> = (0..SAMPLES).map(|_| (G1::rand(&mut rng), G1::rand(&mut rng).into())).collect(); + let v: Vec<(G1, G1Affine)> = (0..SAMPLES) + .map(|_| (G1::rand(&mut rng), G1::rand(&mut rng).into())) + .collect(); let mut count = 0; b.iter(|| { @@ -68,7 +74,9 @@ mod g2 { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(G2, Fr)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), Fr::rand(&mut rng))).collect(); + let v: Vec<(G2, Fr)> = (0..SAMPLES) + .map(|_| (G2::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -85,7 +93,9 @@ mod g2 { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(G2, G2)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), G2::rand(&mut rng))).collect(); + let v: Vec<(G2, G2)> = (0..SAMPLES) + .map(|_| (G2::rand(&mut rng), G2::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -102,7 +112,9 @@ mod g2 { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(G2, G2Affine)> = (0..SAMPLES).map(|_| (G2::rand(&mut rng), G2::rand(&mut rng).into())).collect(); + let v: Vec<(G2, G2Affine)> = (0..SAMPLES) + .map(|_| (G2::rand(&mut rng), G2::rand(&mut rng).into())) + .collect(); let mut count = 0; b.iter(|| { diff --git a/benches/bls12_381/fq.rs b/benches/bls12_381/fq.rs index 9b2a08e..8dad757 100644 --- a/benches/bls12_381/fq.rs +++ b/benches/bls12_381/fq.rs @@ -9,16 +9,18 @@ fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES).map(|_| { - let mut tmp1 = FqRepr::rand(&mut rng); - let mut tmp2 = FqRepr::rand(&mut rng); - // Shave a few bits off to avoid overflow. - for _ in 0..3 { - tmp1.div2(); - tmp2.div2(); - } - (tmp1, tmp2) - }).collect(); + let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES) + .map(|_| { + let mut tmp1 = FqRepr::rand(&mut rng); + let mut tmp2 = FqRepr::rand(&mut rng); + // Shave a few bits off to avoid overflow. + for _ in 0..3 { + tmp1.div2(); + tmp2.div2(); + } + (tmp1, tmp2) + }) + .collect(); let mut count = 0; b.iter(|| { @@ -35,15 +37,17 @@ fn bench_fq_repr_sub_noborrow(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES).map(|_| { - let tmp1 = FqRepr::rand(&mut rng); - let mut tmp2 = tmp1; - // Ensure tmp2 is smaller than tmp1. - for _ in 0..10 { - tmp2.div2(); - } - (tmp1, tmp2) - }).collect(); + let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES) + .map(|_| { + let tmp1 = FqRepr::rand(&mut rng); + let mut tmp2 = tmp1; + // Ensure tmp2 is smaller than tmp1. + for _ in 0..10 { + tmp2.div2(); + } + (tmp1, tmp2) + }) + .collect(); let mut count = 0; b.iter(|| { @@ -110,7 +114,9 @@ fn bench_fq_add_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + let v: Vec<(Fq, Fq)> = (0..SAMPLES) + .map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -127,7 +133,9 @@ fn bench_fq_sub_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + let v: Vec<(Fq, Fq)> = (0..SAMPLES) + .map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -144,7 +152,9 @@ fn bench_fq_mul_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq, Fq)> = (0..SAMPLES).map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))).collect(); + let v: Vec<(Fq, Fq)> = (0..SAMPLES) + .map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -206,15 +216,17 @@ fn bench_fq_negate(b: &mut ::test::Bencher) { #[bench] fn bench_fq_sqrt(b: &mut ::test::Bencher) { - const SAMPLES: usize = 1000; + const SAMPLES: usize = 1000; let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fq> = (0..SAMPLES).map(|_| { - let mut tmp = Fq::rand(&mut rng); - tmp.square(); - tmp - }).collect(); + let v: Vec<Fq> = (0..SAMPLES) + .map(|_| { + let mut tmp = Fq::rand(&mut rng); + tmp.square(); + tmp + }) + .collect(); let mut count = 0; b.iter(|| { @@ -229,9 +241,7 @@ fn bench_fq_into_repr(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fq> = (0..SAMPLES).map(|_| { - Fq::rand(&mut rng) - }).collect(); + let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); let mut count = 0; b.iter(|| { @@ -246,9 +256,9 @@ fn bench_fq_from_repr(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<FqRepr> = (0..SAMPLES).map(|_| { - Fq::rand(&mut rng).into_repr() - }).collect(); + let v: Vec<FqRepr> = (0..SAMPLES) + .map(|_| Fq::rand(&mut rng).into_repr()) + .collect(); let mut count = 0; b.iter(|| { diff --git a/benches/bls12_381/fq12.rs b/benches/bls12_381/fq12.rs index 26c33c8..42ae9c0 100644 --- a/benches/bls12_381/fq12.rs +++ b/benches/bls12_381/fq12.rs @@ -9,9 +9,9 @@ fn bench_fq12_add_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { - (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) - }).collect(); + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES) + .map(|_| (Fq12::rand(&mut rng), Fq12::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -28,9 +28,9 @@ fn bench_fq12_sub_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { - (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) - }).collect(); + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES) + .map(|_| (Fq12::rand(&mut rng), Fq12::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -47,9 +47,9 @@ fn bench_fq12_mul_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq12, Fq12)> = (0..SAMPLES).map(|_| { - (Fq12::rand(&mut rng), Fq12::rand(&mut rng)) - }).collect(); + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES) + .map(|_| (Fq12::rand(&mut rng), Fq12::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -66,9 +66,7 @@ fn bench_fq12_squaring(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fq12> = (0..SAMPLES).map(|_| { - Fq12::rand(&mut rng) - }).collect(); + let v: Vec<Fq12> = (0..SAMPLES).map(|_| Fq12::rand(&mut rng)).collect(); let mut count = 0; b.iter(|| { @@ -85,9 +83,7 @@ fn bench_fq12_inverse(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fq12> = (0..SAMPLES).map(|_| { - Fq12::rand(&mut rng) - }).collect(); + let v: Vec<Fq12> = (0..SAMPLES).map(|_| Fq12::rand(&mut rng)).collect(); let mut count = 0; b.iter(|| { diff --git a/benches/bls12_381/fq2.rs b/benches/bls12_381/fq2.rs index d5f835d..733a745 100644 --- a/benches/bls12_381/fq2.rs +++ b/benches/bls12_381/fq2.rs @@ -9,9 +9,9 @@ fn bench_fq2_add_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { - (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) - }).collect(); + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES) + .map(|_| (Fq2::rand(&mut rng), Fq2::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -28,9 +28,9 @@ fn bench_fq2_sub_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { - (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) - }).collect(); + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES) + .map(|_| (Fq2::rand(&mut rng), Fq2::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -47,9 +47,9 @@ fn bench_fq2_mul_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fq2, Fq2)> = (0..SAMPLES).map(|_| { - (Fq2::rand(&mut rng), Fq2::rand(&mut rng)) - }).collect(); + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES) + .map(|_| (Fq2::rand(&mut rng), Fq2::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -66,9 +66,7 @@ fn bench_fq2_squaring(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fq2> = (0..SAMPLES).map(|_| { - Fq2::rand(&mut rng) - }).collect(); + let v: Vec<Fq2> = (0..SAMPLES).map(|_| Fq2::rand(&mut rng)).collect(); let mut count = 0; b.iter(|| { @@ -85,9 +83,7 @@ fn bench_fq2_inverse(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fq2> = (0..SAMPLES).map(|_| { - Fq2::rand(&mut rng) - }).collect(); + let v: Vec<Fq2> = (0..SAMPLES).map(|_| Fq2::rand(&mut rng)).collect(); let mut count = 0; b.iter(|| { @@ -103,9 +99,7 @@ fn bench_fq2_sqrt(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fq2> = (0..SAMPLES).map(|_| { - Fq2::rand(&mut rng) - }).collect(); + let v: Vec<Fq2> = (0..SAMPLES).map(|_| Fq2::rand(&mut rng)).collect(); let mut count = 0; b.iter(|| { diff --git a/benches/bls12_381/fr.rs b/benches/bls12_381/fr.rs index 58575b6..0ee13f8 100644 --- a/benches/bls12_381/fr.rs +++ b/benches/bls12_381/fr.rs @@ -9,16 +9,18 @@ fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES).map(|_| { - let mut tmp1 = FrRepr::rand(&mut rng); - let mut tmp2 = FrRepr::rand(&mut rng); - // Shave a few bits off to avoid overflow. - for _ in 0..3 { - tmp1.div2(); - tmp2.div2(); - } - (tmp1, tmp2) - }).collect(); + let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES) + .map(|_| { + let mut tmp1 = FrRepr::rand(&mut rng); + let mut tmp2 = FrRepr::rand(&mut rng); + // Shave a few bits off to avoid overflow. + for _ in 0..3 { + tmp1.div2(); + tmp2.div2(); + } + (tmp1, tmp2) + }) + .collect(); let mut count = 0; b.iter(|| { @@ -35,15 +37,17 @@ fn bench_fr_repr_sub_noborrow(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES).map(|_| { - let tmp1 = FrRepr::rand(&mut rng); - let mut tmp2 = tmp1; - // Ensure tmp2 is smaller than tmp1. - for _ in 0..10 { - tmp2.div2(); - } - (tmp1, tmp2) - }).collect(); + let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES) + .map(|_| { + let tmp1 = FrRepr::rand(&mut rng); + let mut tmp2 = tmp1; + // Ensure tmp2 is smaller than tmp1. + for _ in 0..10 { + tmp2.div2(); + } + (tmp1, tmp2) + }) + .collect(); let mut count = 0; b.iter(|| { @@ -110,7 +114,9 @@ fn bench_fr_add_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + let v: Vec<(Fr, Fr)> = (0..SAMPLES) + .map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -127,7 +133,9 @@ fn bench_fr_sub_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + let v: Vec<(Fr, Fr)> = (0..SAMPLES) + .map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -144,7 +152,9 @@ fn bench_fr_mul_assign(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(Fr, Fr)> = (0..SAMPLES).map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))).collect(); + let v: Vec<(Fr, Fr)> = (0..SAMPLES) + .map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -210,11 +220,13 @@ fn bench_fr_sqrt(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fr> = (0..SAMPLES).map(|_| { - let mut tmp = Fr::rand(&mut rng); - tmp.square(); - tmp - }).collect(); + let v: Vec<Fr> = (0..SAMPLES) + .map(|_| { + let mut tmp = Fr::rand(&mut rng); + tmp.square(); + tmp + }) + .collect(); let mut count = 0; b.iter(|| { @@ -229,9 +241,7 @@ fn bench_fr_into_repr(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fr> = (0..SAMPLES).map(|_| { - Fr::rand(&mut rng) - }).collect(); + let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); let mut count = 0; b.iter(|| { @@ -246,9 +256,9 @@ fn bench_fr_from_repr(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<FrRepr> = (0..SAMPLES).map(|_| { - Fr::rand(&mut rng).into_repr() - }).collect(); + let v: Vec<FrRepr> = (0..SAMPLES) + .map(|_| Fr::rand(&mut rng).into_repr()) + .collect(); let mut count = 0; b.iter(|| { diff --git a/benches/bls12_381/mod.rs b/benches/bls12_381/mod.rs index 64a90cc..cf447ed 100644 --- a/benches/bls12_381/mod.rs +++ b/benches/bls12_381/mod.rs @@ -6,7 +6,7 @@ mod ec; use rand::{Rand, SeedableRng, XorShiftRng}; -use pairing::{Engine, CurveAffine}; +use pairing::{CurveAffine, Engine}; use pairing::bls12_381::*; #[bench] @@ -47,12 +47,14 @@ fn bench_pairing_miller_loop(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(G1Prepared, G2Prepared)> = (0..SAMPLES).map(|_| - ( - G1Affine::from(G1::rand(&mut rng)).prepare(), - G2Affine::from(G2::rand(&mut rng)).prepare() - ) - ).collect(); + let v: Vec<(G1Prepared, G2Prepared)> = (0..SAMPLES) + .map(|_| { + ( + G1Affine::from(G1::rand(&mut rng)).prepare(), + G2Affine::from(G2::rand(&mut rng)).prepare(), + ) + }) + .collect(); let mut count = 0; b.iter(|| { @@ -68,12 +70,15 @@ fn bench_pairing_final_exponentiation(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<Fq12> = (0..SAMPLES).map(|_| - ( - G1Affine::from(G1::rand(&mut rng)).prepare(), - G2Affine::from(G2::rand(&mut rng)).prepare() - ) - ).map(|(ref p, ref q)| Bls12::miller_loop(&[(p, q)])).collect(); + let v: Vec<Fq12> = (0..SAMPLES) + .map(|_| { + ( + G1Affine::from(G1::rand(&mut rng)).prepare(), + G2Affine::from(G2::rand(&mut rng)).prepare(), + ) + }) + .map(|(ref p, ref q)| Bls12::miller_loop(&[(p, q)])) + .collect(); let mut count = 0; b.iter(|| { @@ -89,12 +94,9 @@ fn bench_pairing_full(b: &mut ::test::Bencher) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let v: Vec<(G1, G2)> = (0..SAMPLES).map(|_| - ( - G1::rand(&mut rng), - G2::rand(&mut rng) - ) - ).collect(); + let v: Vec<(G1, G2)> = (0..SAMPLES) + .map(|_| (G1::rand(&mut rng), G2::rand(&mut rng))) + .collect(); let mut count = 0; b.iter(|| { @@ -102,4 +104,4 @@ fn bench_pairing_full(b: &mut ::test::Bencher) { count = (count + 1) % SAMPLES; tmp }); -} \ No newline at end of file +} diff --git a/benches/pairing_benches.rs b/benches/pairing_benches.rs index 3ae12e0..424c4e7 100644 --- a/benches/pairing_benches.rs +++ b/benches/pairing_benches.rs @@ -1,7 +1,7 @@ #![feature(test)] -extern crate test; -extern crate rand; extern crate pairing; +extern crate rand; +extern crate test; mod bls12_381; From da5f1d3e3705fc0dee72e9cbbb4093383964fb0c Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Thu, 17 May 2018 11:59:20 -0600 Subject: [PATCH 112/140] Update to latest rustfmt --- benches/bls12_381/ec.rs | 4 ++-- benches/bls12_381/fq.rs | 2 +- benches/bls12_381/fq12.rs | 2 +- benches/bls12_381/fq2.rs | 2 +- benches/bls12_381/fr.rs | 2 +- benches/bls12_381/mod.rs | 10 +++++----- src/bls12_381/ec.rs | 20 ++++++++++++-------- src/bls12_381/fq.rs | 4 ++-- src/bls12_381/fq12.rs | 6 +++--- src/bls12_381/fq2.rs | 20 ++++++++++---------- src/bls12_381/fq6.rs | 4 ++-- src/bls12_381/fr.rs | 2 +- src/bls12_381/mod.rs | 16 +++++++++------- src/bls12_381/tests/mod.rs | 2 +- src/lib.rs | 2 +- src/tests/curve.rs | 6 ++++-- src/tests/mod.rs | 2 +- 17 files changed, 57 insertions(+), 49 deletions(-) diff --git a/benches/bls12_381/ec.rs b/benches/bls12_381/ec.rs index 421881d..cbd0590 100644 --- a/benches/bls12_381/ec.rs +++ b/benches/bls12_381/ec.rs @@ -1,8 +1,8 @@ mod g1 { use rand::{Rand, SeedableRng, XorShiftRng}; - use pairing::CurveProjective; use pairing::bls12_381::*; + use pairing::CurveProjective; #[bench] fn bench_g1_mul_assign(b: &mut ::test::Bencher) { @@ -65,8 +65,8 @@ mod g1 { mod g2 { use rand::{Rand, SeedableRng, XorShiftRng}; - use pairing::CurveProjective; use pairing::bls12_381::*; + use pairing::CurveProjective; #[bench] fn bench_g2_mul_assign(b: &mut ::test::Bencher) { diff --git a/benches/bls12_381/fq.rs b/benches/bls12_381/fq.rs index 8dad757..af4dba4 100644 --- a/benches/bls12_381/fq.rs +++ b/benches/bls12_381/fq.rs @@ -1,7 +1,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; -use pairing::{Field, PrimeField, PrimeFieldRepr, SqrtField}; use pairing::bls12_381::*; +use pairing::{Field, PrimeField, PrimeFieldRepr, SqrtField}; #[bench] fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { diff --git a/benches/bls12_381/fq12.rs b/benches/bls12_381/fq12.rs index 42ae9c0..226b850 100644 --- a/benches/bls12_381/fq12.rs +++ b/benches/bls12_381/fq12.rs @@ -1,7 +1,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; -use pairing::Field; use pairing::bls12_381::*; +use pairing::Field; #[bench] fn bench_fq12_add_assign(b: &mut ::test::Bencher) { diff --git a/benches/bls12_381/fq2.rs b/benches/bls12_381/fq2.rs index 733a745..ec26e98 100644 --- a/benches/bls12_381/fq2.rs +++ b/benches/bls12_381/fq2.rs @@ -1,7 +1,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; -use pairing::{Field, SqrtField}; use pairing::bls12_381::*; +use pairing::{Field, SqrtField}; #[bench] fn bench_fq2_add_assign(b: &mut ::test::Bencher) { diff --git a/benches/bls12_381/fr.rs b/benches/bls12_381/fr.rs index 0ee13f8..7278629 100644 --- a/benches/bls12_381/fr.rs +++ b/benches/bls12_381/fr.rs @@ -1,7 +1,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; -use pairing::{Field, PrimeField, PrimeFieldRepr, SqrtField}; use pairing::bls12_381::*; +use pairing::{Field, PrimeField, PrimeFieldRepr, SqrtField}; #[bench] fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { diff --git a/benches/bls12_381/mod.rs b/benches/bls12_381/mod.rs index cf447ed..9b46c85 100644 --- a/benches/bls12_381/mod.rs +++ b/benches/bls12_381/mod.rs @@ -1,13 +1,13 @@ -mod fq; -mod fr; -mod fq2; -mod fq12; mod ec; +mod fq; +mod fq12; +mod fq2; +mod fr; use rand::{Rand, SeedableRng, XorShiftRng}; -use pairing::{CurveAffine, Engine}; use pairing::bls12_381::*; +use pairing::{CurveAffine, Engine}; #[bench] fn bench_pairing_g1_preparation(b: &mut ::test::Bencher) { diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 5f651ca..5cd5091 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -621,12 +621,14 @@ macro_rules! curve_impl { } pub mod g1 { + use super::super::{Bls12, Fq, Fq12, FqRepr, Fr, FrRepr}; + use super::g2::G2Affine; use rand::{Rand, Rng}; use std::fmt; - use super::g2::G2Affine; - use super::super::{Bls12, Fq, Fq12, FqRepr, Fr, FrRepr}; - use {BitIterator, CurveAffine, CurveProjective, EncodedPoint, Engine, Field, - GroupDecodingError, PrimeField, PrimeFieldRepr, SqrtField}; + use { + BitIterator, CurveAffine, CurveProjective, EncodedPoint, Engine, Field, GroupDecodingError, + PrimeField, PrimeFieldRepr, SqrtField, + }; curve_impl!( "G1", @@ -1266,12 +1268,14 @@ pub mod g1 { } pub mod g2 { - use rand::{Rand, Rng}; - use std::fmt; use super::super::{Bls12, Fq, Fq12, Fq2, FqRepr, Fr, FrRepr}; use super::g1::G1Affine; - use {BitIterator, CurveAffine, CurveProjective, EncodedPoint, Engine, Field, - GroupDecodingError, PrimeField, PrimeFieldRepr, SqrtField}; + use rand::{Rand, Rng}; + use std::fmt; + use { + BitIterator, CurveAffine, CurveProjective, EncodedPoint, Engine, Field, GroupDecodingError, + PrimeField, PrimeFieldRepr, SqrtField, + }; curve_impl!( "G2", diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index c35360a..738da38 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,6 +1,6 @@ -use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; -use std::cmp::Ordering; use super::fq2::Fq2; +use std::cmp::Ordering; +use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; // q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 const MODULUS: FqRepr = FqRepr([ diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index ae79e42..2bec0b1 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -1,8 +1,8 @@ +use super::fq::FROBENIUS_COEFF_FQ12_C1; +use super::fq2::Fq2; +use super::fq6::Fq6; use rand::{Rand, Rng}; use Field; -use super::fq6::Fq6; -use super::fq2::Fq2; -use super::fq::FROBENIUS_COEFF_FQ12_C1; /// An element of Fq12, represented by c0 + c1 * w. #[derive(Copy, Clone, Debug, Eq, PartialEq)] diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 873652c..18cd580 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -1,6 +1,6 @@ +use super::fq::{FROBENIUS_COEFF_FQ2_C1, Fq, NEGATIVE_ONE}; use rand::{Rand, Rng}; use {Field, SqrtField}; -use super::fq::{FROBENIUS_COEFF_FQ2_C1, Fq, NEGATIVE_ONE}; use std::cmp::Ordering; @@ -271,8 +271,8 @@ fn test_fq2_basics() { #[test] fn test_fq2_squaring() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; let mut a = Fq2 { c0: Fq::one(), @@ -345,8 +345,8 @@ fn test_fq2_squaring() { #[test] fn test_fq2_mul() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -409,8 +409,8 @@ fn test_fq2_mul() { #[test] fn test_fq2_inverse() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; assert!(Fq2::zero().inverse().is_none()); @@ -458,8 +458,8 @@ fn test_fq2_inverse() { #[test] fn test_fq2_addition() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -522,8 +522,8 @@ fn test_fq2_addition() { #[test] fn test_fq2_subtraction() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -586,8 +586,8 @@ fn test_fq2_subtraction() { #[test] fn test_fq2_negation() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -633,8 +633,8 @@ fn test_fq2_negation() { #[test] fn test_fq2_doubling() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -680,8 +680,8 @@ fn test_fq2_doubling() { #[test] fn test_fq2_frobenius_map() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -793,8 +793,8 @@ fn test_fq2_frobenius_map() { #[test] fn test_fq2_sqrt() { - use PrimeField; use super::fq::FqRepr; + use PrimeField; assert_eq!( Fq2 { diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs index d9a6c33..c065f27 100644 --- a/src/bls12_381/fq6.rs +++ b/src/bls12_381/fq6.rs @@ -1,7 +1,7 @@ +use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; +use super::fq2::Fq2; use rand::{Rand, Rng}; use Field; -use super::fq2::Fq2; -use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; /// An element of Fq6, represented by c0 + c1 * v + c2 * v^(2). #[derive(Copy, Clone, Debug, Eq, PartialEq)] diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 31fbe8b..4e9d6ab 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,5 +1,5 @@ -use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; use LegendreSymbol::*; +use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; // r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 const MODULUS: FrRepr = FrRepr([ diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index c35b852..a5db4b5 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -1,20 +1,22 @@ +mod ec; mod fq; -mod fr; +mod fq12; mod fq2; mod fq6; -mod fq12; -mod ec; +mod fr; #[cfg(test)] mod tests; -pub use self::fr::{Fr, FrRepr}; +pub use self::ec::{ + G1, G1Affine, G1Compressed, G1Prepared, G1Uncompressed, G2, G2Affine, G2Compressed, G2Prepared, + G2Uncompressed, +}; pub use self::fq::{Fq, FqRepr}; +pub use self::fq12::Fq12; pub use self::fq2::Fq2; pub use self::fq6::Fq6; -pub use self::fq12::Fq12; -pub use self::ec::{G1, G1Affine, G1Compressed, G1Prepared, G1Uncompressed, G2, G2Affine, - G2Compressed, G2Prepared, G2Uncompressed}; +pub use self::fr::{Fr, FrRepr}; use super::{BitIterator, CurveAffine, Engine, Field}; diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs index 41a60a4..bf6c595 100644 --- a/src/bls12_381/tests/mod.rs +++ b/src/bls12_381/tests/mod.rs @@ -1,5 +1,5 @@ use super::*; -use ::*; +use *; #[test] fn test_pairing_result_against_relic() { diff --git a/src/lib.rs b/src/lib.rs index 5343c6e..08365f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,8 @@ pub mod bls12_381; mod wnaf; pub use self::wnaf::Wnaf; -use std::fmt; use std::error::Error; +use std::fmt; use std::io::{self, Read, Write}; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) diff --git a/src/tests/curve.rs b/src/tests/curve.rs index fa656cc..1480b74 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -47,7 +47,8 @@ pub fn curve_tests<G: CurveProjective>() { { let a = G::rand(&mut rng); let b = a.into_affine().into_projective(); - let c = a.into_affine() + let c = a + .into_affine() .into_projective() .into_affine() .into_projective(); @@ -372,7 +373,8 @@ fn random_transformation_tests<G: CurveProjective>() { v[s] = v[s].into_affine().into_projective(); } - let expected_v = v.iter() + let expected_v = v + .iter() .map(|v| v.into_affine().into_projective()) .collect::<Vec<_>>(); G::batch_normalization(&mut v); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index cf00add..bc83958 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,4 +1,4 @@ pub mod curve; -pub mod field; pub mod engine; +pub mod field; pub mod repr; From 1db099f1cc9b341286394a145780ef0af41cf866 Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Sat, 30 Jun 2018 21:56:49 -0400 Subject: [PATCH 113/140] Use ff crate for Field traits --- Cargo.toml | 1 + benches/bls12_381/fq.rs | 2 +- benches/bls12_381/fq12.rs | 2 +- benches/bls12_381/fq2.rs | 2 +- benches/bls12_381/fr.rs | 2 +- benches/pairing_benches.rs | 1 + src/lib.rs | 457 +------------------------------------ 7 files changed, 8 insertions(+), 459 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98725aa..dcf7c32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ repository = "https://github.com/ebfull/pairing" rand = "0.4" byteorder = "1" clippy = { version = "0.0.200", optional = true } +ff = "0.3" [features] unstable-features = ["expose-arith"] diff --git a/benches/bls12_381/fq.rs b/benches/bls12_381/fq.rs index af4dba4..053a10c 100644 --- a/benches/bls12_381/fq.rs +++ b/benches/bls12_381/fq.rs @@ -1,7 +1,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; +use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; use pairing::bls12_381::*; -use pairing::{Field, PrimeField, PrimeFieldRepr, SqrtField}; #[bench] fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { diff --git a/benches/bls12_381/fq12.rs b/benches/bls12_381/fq12.rs index 226b850..84daca2 100644 --- a/benches/bls12_381/fq12.rs +++ b/benches/bls12_381/fq12.rs @@ -1,7 +1,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; +use ff::Field; use pairing::bls12_381::*; -use pairing::Field; #[bench] fn bench_fq12_add_assign(b: &mut ::test::Bencher) { diff --git a/benches/bls12_381/fq2.rs b/benches/bls12_381/fq2.rs index ec26e98..521b6ab 100644 --- a/benches/bls12_381/fq2.rs +++ b/benches/bls12_381/fq2.rs @@ -1,7 +1,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; +use ff::{Field, SqrtField}; use pairing::bls12_381::*; -use pairing::{Field, SqrtField}; #[bench] fn bench_fq2_add_assign(b: &mut ::test::Bencher) { diff --git a/benches/bls12_381/fr.rs b/benches/bls12_381/fr.rs index 7278629..13b0d0e 100644 --- a/benches/bls12_381/fr.rs +++ b/benches/bls12_381/fr.rs @@ -1,7 +1,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; +use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; use pairing::bls12_381::*; -use pairing::{Field, PrimeField, PrimeFieldRepr, SqrtField}; #[bench] fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { diff --git a/benches/pairing_benches.rs b/benches/pairing_benches.rs index 424c4e7..af32a8a 100644 --- a/benches/pairing_benches.rs +++ b/benches/pairing_benches.rs @@ -1,5 +1,6 @@ #![feature(test)] +extern crate ff; extern crate pairing; extern crate rand; extern crate test; diff --git a/src/lib.rs b/src/lib.rs index 08365f1..effc050 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ #![deny(missing_debug_implementations)] extern crate byteorder; +extern crate ff; extern crate rand; #[cfg(test)] @@ -24,9 +25,9 @@ pub mod bls12_381; mod wnaf; pub use self::wnaf::Wnaf; +use ff::*; use std::error::Error; use std::fmt; -use std::io::{self, Read, Write}; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are @@ -263,208 +264,6 @@ pub trait EncodedPoint: fn from_affine(affine: Self::Affine) -> Self; } -/// This trait represents an element of a field. -pub trait Field: - Sized + Eq + Copy + Clone + Send + Sync + fmt::Debug + fmt::Display + 'static + rand::Rand -{ - /// Returns the zero element of the field, the additive identity. - fn zero() -> Self; - - /// Returns the one element of the field, the multiplicative identity. - fn one() -> Self; - - /// Returns true iff this element is zero. - fn is_zero(&self) -> bool; - - /// Squares this element. - fn square(&mut self); - - /// Doubles this element. - fn double(&mut self); - - /// Negates this element. - fn negate(&mut self); - - /// Adds another element to this element. - fn add_assign(&mut self, other: &Self); - - /// Subtracts another element from this element. - fn sub_assign(&mut self, other: &Self); - - /// Multiplies another element by this element. - fn mul_assign(&mut self, other: &Self); - - /// Computes the multiplicative inverse of this element, if nonzero. - fn inverse(&self) -> Option<Self>; - - /// Exponentiates this element by a power of the base prime modulus via - /// the Frobenius automorphism. - fn frobenius_map(&mut self, power: usize); - - /// Exponentiates this element by a number represented with `u64` limbs, - /// least significant digit first. - fn pow<S: AsRef<[u64]>>(&self, exp: S) -> Self { - let mut res = Self::one(); - - let mut found_one = false; - - for i in BitIterator::new(exp) { - if found_one { - res.square(); - } else { - found_one = i; - } - - if i { - res.mul_assign(self); - } - } - - res - } -} - -/// This trait represents an element of a field that has a square root operation described for it. -pub trait SqrtField: Field { - /// Returns the Legendre symbol of the field element. - fn legendre(&self) -> LegendreSymbol; - - /// Returns the square root of the field element, if it is - /// quadratic residue. - fn sqrt(&self) -> Option<Self>; -} - -/// This trait represents a wrapper around a biginteger which can encode any element of a particular -/// prime field. It is a smart wrapper around a sequence of `u64` limbs, least-significant digit -/// first. -pub trait PrimeFieldRepr: - Sized - + Copy - + Clone - + Eq - + Ord - + Send - + Sync - + Default - + fmt::Debug - + fmt::Display - + 'static - + rand::Rand - + AsRef<[u64]> - + AsMut<[u64]> - + From<u64> -{ - /// Subtract another represetation from this one. - fn sub_noborrow(&mut self, other: &Self); - - /// Add another representation to this one. - fn add_nocarry(&mut self, other: &Self); - - /// Compute the number of bits needed to encode this number. Always a - /// multiple of 64. - fn num_bits(&self) -> u32; - - /// Returns true iff this number is zero. - fn is_zero(&self) -> bool; - - /// Returns true iff this number is odd. - fn is_odd(&self) -> bool; - - /// Returns true iff this number is even. - fn is_even(&self) -> bool; - - /// Performs a rightwise bitshift of this number, effectively dividing - /// it by 2. - fn div2(&mut self); - - /// Performs a rightwise bitshift of this number by some amount. - fn shr(&mut self, amt: u32); - - /// Performs a leftwise bitshift of this number, effectively multiplying - /// it by 2. Overflow is ignored. - fn mul2(&mut self); - - /// Performs a leftwise bitshift of this number by some amount. - fn shl(&mut self, amt: u32); - - /// Writes this `PrimeFieldRepr` as a big endian integer. - fn write_be<W: Write>(&self, mut writer: W) -> io::Result<()> { - use byteorder::{BigEndian, WriteBytesExt}; - - for digit in self.as_ref().iter().rev() { - writer.write_u64::<BigEndian>(*digit)?; - } - - Ok(()) - } - - /// Reads a big endian integer into this representation. - fn read_be<R: Read>(&mut self, mut reader: R) -> io::Result<()> { - use byteorder::{BigEndian, ReadBytesExt}; - - for digit in self.as_mut().iter_mut().rev() { - *digit = reader.read_u64::<BigEndian>()?; - } - - Ok(()) - } - - /// Writes this `PrimeFieldRepr` as a little endian integer. - fn write_le<W: Write>(&self, mut writer: W) -> io::Result<()> { - use byteorder::{LittleEndian, WriteBytesExt}; - - for digit in self.as_ref().iter() { - writer.write_u64::<LittleEndian>(*digit)?; - } - - Ok(()) - } - - /// Reads a little endian integer into this representation. - fn read_le<R: Read>(&mut self, mut reader: R) -> io::Result<()> { - use byteorder::{LittleEndian, ReadBytesExt}; - - for digit in self.as_mut().iter_mut() { - *digit = reader.read_u64::<LittleEndian>()?; - } - - Ok(()) - } -} - -#[derive(Debug, PartialEq)] -pub enum LegendreSymbol { - Zero = 0, - QuadraticResidue = 1, - QuadraticNonResidue = -1, -} - -/// An error that may occur when trying to interpret a `PrimeFieldRepr` as a -/// `PrimeField` element. -#[derive(Debug)] -pub enum PrimeFieldDecodingError { - /// The encoded value is not in the field - NotInField(String), -} - -impl Error for PrimeFieldDecodingError { - fn description(&self) -> &str { - match *self { - PrimeFieldDecodingError::NotInField(..) => "not an element of the field", - } - } -} - -impl fmt::Display for PrimeFieldDecodingError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - PrimeFieldDecodingError::NotInField(ref repr) => { - write!(f, "{} is not an element of the field", repr) - } - } - } -} - /// An error that may occur when trying to decode an `EncodedPoint`. #[derive(Debug)] pub enum GroupDecodingError { @@ -504,255 +303,3 @@ impl fmt::Display for GroupDecodingError { } } } - -/// This represents an element of a prime field. -pub trait PrimeField: Field { - /// The prime field can be converted back and forth into this biginteger - /// representation. - type Repr: PrimeFieldRepr + From<Self>; - - /// Interpret a string of numbers as a (congruent) prime field element. - /// Does not accept unnecessary leading zeroes or a blank string. - fn from_str(s: &str) -> Option<Self> { - if s.is_empty() { - return None; - } - - if s == "0" { - return Some(Self::zero()); - } - - let mut res = Self::zero(); - - let ten = Self::from_repr(Self::Repr::from(10)).unwrap(); - - let mut first_digit = true; - - for c in s.chars() { - match c.to_digit(10) { - Some(c) => { - if first_digit { - if c == 0 { - return None; - } - - first_digit = false; - } - - res.mul_assign(&ten); - res.add_assign(&Self::from_repr(Self::Repr::from(u64::from(c))).unwrap()); - } - None => { - return None; - } - } - } - - Some(res) - } - - /// Convert this prime field element into a biginteger representation. - fn from_repr(Self::Repr) -> Result<Self, PrimeFieldDecodingError>; - - /// Convert a biginteger representation into a prime field element, if - /// the number is an element of the field. - fn into_repr(&self) -> Self::Repr; - - /// Returns the field characteristic; the modulus. - fn char() -> Self::Repr; - - /// How many bits are needed to represent an element of this field. - const NUM_BITS: u32; - - /// How many bits of information can be reliably stored in the field element. - const CAPACITY: u32; - - /// Returns the multiplicative generator of `char()` - 1 order. This element - /// must also be quadratic nonresidue. - fn multiplicative_generator() -> Self; - - /// 2^s * t = `char()` - 1 with t odd. - const S: u32; - - /// Returns the 2^s root of unity computed by exponentiating the `multiplicative_generator()` - /// by t. - fn root_of_unity() -> Self; -} - -#[derive(Debug)] -pub struct BitIterator<E> { - t: E, - n: usize, -} - -impl<E: AsRef<[u64]>> BitIterator<E> { - pub fn new(t: E) -> Self { - let n = t.as_ref().len() * 64; - - BitIterator { t, n } - } -} - -impl<E: AsRef<[u64]>> Iterator for BitIterator<E> { - type Item = bool; - - fn next(&mut self) -> Option<bool> { - if self.n == 0 { - None - } else { - self.n -= 1; - let part = self.n / 64; - let bit = self.n - (64 * part); - - Some(self.t.as_ref()[part] & (1 << bit) > 0) - } - } -} - -#[test] -fn test_bit_iterator() { - let mut a = BitIterator::new([0xa953d79b83f6ab59, 0x6dea2059e200bd39]); - let expected = "01101101111010100010000001011001111000100000000010111101001110011010100101010011110101111001101110000011111101101010101101011001"; - - for e in expected.chars() { - assert!(a.next().unwrap() == (e == '1')); - } - - assert!(a.next().is_none()); - - let expected = "1010010101111110101010000101101011101000011101110101001000011001100100100011011010001011011011010001011011101100110100111011010010110001000011110100110001100110011101101000101100011100100100100100001010011101010111110011101011000011101000111011011101011001"; - - let mut a = BitIterator::new([ - 0x429d5f3ac3a3b759, - 0xb10f4c66768b1c92, - 0x92368b6d16ecd3b4, - 0xa57ea85ae8775219, - ]); - - for e in expected.chars() { - assert!(a.next().unwrap() == (e == '1')); - } - - assert!(a.next().is_none()); -} - -#[cfg(not(feature = "expose-arith"))] -use self::arith_impl::*; - -#[cfg(feature = "expose-arith")] -pub use self::arith_impl::*; - -#[cfg(feature = "u128-support")] -mod arith_impl { - /// Calculate a - b - borrow, returning the result and modifying - /// the borrow value. - #[inline(always)] - pub fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { - let tmp = (1u128 << 64) + u128::from(a) - u128::from(b) - u128::from(*borrow); - - *borrow = if tmp >> 64 == 0 { 1 } else { 0 }; - - tmp as u64 - } - - /// Calculate a + b + carry, returning the sum and modifying the - /// carry value. - #[inline(always)] - pub fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { - let tmp = u128::from(a) + u128::from(b) + u128::from(*carry); - - *carry = (tmp >> 64) as u64; - - tmp as u64 - } - - /// Calculate a + (b * c) + carry, returning the least significant digit - /// and setting carry to the most significant digit. - #[inline(always)] - pub fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { - let tmp = (u128::from(a)) + u128::from(b) * u128::from(c) + u128::from(*carry); - - *carry = (tmp >> 64) as u64; - - tmp as u64 - } -} - -#[cfg(not(feature = "u128-support"))] -mod arith_impl { - #[inline(always)] - fn split_u64(i: u64) -> (u64, u64) { - (i >> 32, i & 0xFFFFFFFF) - } - - #[inline(always)] - fn combine_u64(hi: u64, lo: u64) -> u64 { - (hi << 32) | lo - } - - /// Calculate a - b - borrow, returning the result and modifying - /// the borrow value. - #[inline(always)] - pub fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 { - let (a_hi, a_lo) = split_u64(a); - let (b_hi, b_lo) = split_u64(b); - let (b, r0) = split_u64((1 << 32) + a_lo - b_lo - *borrow); - let (b, r1) = split_u64((1 << 32) + a_hi - b_hi - ((b == 0) as u64)); - - *borrow = (b == 0) as u64; - - combine_u64(r1, r0) - } - - /// Calculate a + b + carry, returning the sum and modifying the - /// carry value. - #[inline(always)] - pub fn adc(a: u64, b: u64, carry: &mut u64) -> u64 { - let (a_hi, a_lo) = split_u64(a); - let (b_hi, b_lo) = split_u64(b); - let (carry_hi, carry_lo) = split_u64(*carry); - - let (t, r0) = split_u64(a_lo + b_lo + carry_lo); - let (t, r1) = split_u64(t + a_hi + b_hi + carry_hi); - - *carry = t; - - combine_u64(r1, r0) - } - - /// Calculate a + (b * c) + carry, returning the least significant digit - /// and setting carry to the most significant digit. - #[inline(always)] - pub fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 { - /* - [ b_hi | b_lo ] - [ c_hi | c_lo ] * - ------------------------------------------- - [ b_lo * c_lo ] <-- w - [ b_hi * c_lo ] <-- x - [ b_lo * c_hi ] <-- y - [ b_hi * c_lo ] <-- z - [ a_hi | a_lo ] - [ C_hi | C_lo ] - */ - - let (a_hi, a_lo) = split_u64(a); - let (b_hi, b_lo) = split_u64(b); - let (c_hi, c_lo) = split_u64(c); - let (carry_hi, carry_lo) = split_u64(*carry); - - let (w_hi, w_lo) = split_u64(b_lo * c_lo); - let (x_hi, x_lo) = split_u64(b_hi * c_lo); - let (y_hi, y_lo) = split_u64(b_lo * c_hi); - let (z_hi, z_lo) = split_u64(b_hi * c_hi); - - let (t, r0) = split_u64(w_lo + a_lo + carry_lo); - let (t, r1) = split_u64(t + w_hi + x_lo + y_lo + a_hi + carry_hi); - let (t, r2) = split_u64(t + x_hi + y_hi + z_lo); - let (_, r3) = split_u64(t + z_hi); - - *carry = combine_u64(r3, r2); - - combine_u64(r1, r0) - } -} From d9d711ebb76780039a27c3cd0e6171c638ae2632 Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Sun, 1 Jul 2018 07:59:34 +0100 Subject: [PATCH 114/140] Use explicit imports instead of re-exporting the ff crate --- src/bls12_381/ec.rs | 12 +-- src/bls12_381/fq.rs | 226 +++++++++++++++++++++---------------------- src/bls12_381/fq2.rs | 4 +- src/bls12_381/fr.rs | 123 +++++++++++------------ src/bls12_381/mod.rs | 4 +- src/lib.rs | 2 +- src/tests/field.rs | 2 +- 7 files changed, 185 insertions(+), 188 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 5cd5091..37fcbba 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -623,12 +623,10 @@ macro_rules! curve_impl { pub mod g1 { use super::super::{Bls12, Fq, Fq12, FqRepr, Fr, FrRepr}; use super::g2::G2Affine; + use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; use rand::{Rand, Rng}; use std::fmt; - use { - BitIterator, CurveAffine, CurveProjective, EncodedPoint, Engine, Field, GroupDecodingError, - PrimeField, PrimeFieldRepr, SqrtField, - }; + use {CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; curve_impl!( "G1", @@ -1270,12 +1268,10 @@ pub mod g1 { pub mod g2 { use super::super::{Bls12, Fq, Fq12, Fq2, FqRepr, Fr, FrRepr}; use super::g1::G1Affine; + use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; use rand::{Rand, Rng}; use std::fmt; - use { - BitIterator, CurveAffine, CurveProjective, EncodedPoint, Engine, Field, GroupDecodingError, - PrimeField, PrimeFieldRepr, SqrtField, - }; + use {CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; curve_impl!( "G2", diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 738da38..e109294 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -682,7 +682,7 @@ impl PrimeFieldRepr for FqRepr { let mut carry = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { - *a = ::adc(*a, *b, &mut carry); + *a = ::ff::adc(*a, *b, &mut carry); } } @@ -691,7 +691,7 @@ impl PrimeFieldRepr for FqRepr { let mut borrow = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { - *a = ::sbb(*a, *b, &mut borrow); + *a = ::ff::sbb(*a, *b, &mut borrow); } } } @@ -909,52 +909,52 @@ impl Field for Fq { #[inline] fn mul_assign(&mut self, other: &Fq) { let mut carry = 0; - let r0 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); - let r1 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); - let r2 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); - let r3 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); - let r4 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[4], &mut carry); - let r5 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[5], &mut carry); + let r0 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); + let r1 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); + let r2 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); + let r3 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); + let r4 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[4], &mut carry); + let r5 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[5], &mut carry); let r6 = carry; let mut carry = 0; - let r1 = ::mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); - let r2 = ::mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); - let r3 = ::mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); - let r5 = ::mac_with_carry(r5, (self.0).0[1], (other.0).0[4], &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[1], (other.0).0[5], &mut carry); + let r1 = ::ff::mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); + let r2 = ::ff::mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); + let r3 = ::ff::mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[1], (other.0).0[4], &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[1], (other.0).0[5], &mut carry); let r7 = carry; let mut carry = 0; - let r2 = ::mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); - let r3 = ::mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); - let r5 = ::mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[2], (other.0).0[4], &mut carry); - let r7 = ::mac_with_carry(r7, (self.0).0[2], (other.0).0[5], &mut carry); + let r2 = ::ff::mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); + let r3 = ::ff::mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[2], (other.0).0[4], &mut carry); + let r7 = ::ff::mac_with_carry(r7, (self.0).0[2], (other.0).0[5], &mut carry); let r8 = carry; let mut carry = 0; - let r3 = ::mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); - let r5 = ::mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); - let r7 = ::mac_with_carry(r7, (self.0).0[3], (other.0).0[4], &mut carry); - let r8 = ::mac_with_carry(r8, (self.0).0[3], (other.0).0[5], &mut carry); + let r3 = ::ff::mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); + let r7 = ::ff::mac_with_carry(r7, (self.0).0[3], (other.0).0[4], &mut carry); + let r8 = ::ff::mac_with_carry(r8, (self.0).0[3], (other.0).0[5], &mut carry); let r9 = carry; let mut carry = 0; - let r4 = ::mac_with_carry(r4, (self.0).0[4], (other.0).0[0], &mut carry); - let r5 = ::mac_with_carry(r5, (self.0).0[4], (other.0).0[1], &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[4], (other.0).0[2], &mut carry); - let r7 = ::mac_with_carry(r7, (self.0).0[4], (other.0).0[3], &mut carry); - let r8 = ::mac_with_carry(r8, (self.0).0[4], (other.0).0[4], &mut carry); - let r9 = ::mac_with_carry(r9, (self.0).0[4], (other.0).0[5], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[4], (other.0).0[0], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[4], (other.0).0[1], &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[4], (other.0).0[2], &mut carry); + let r7 = ::ff::mac_with_carry(r7, (self.0).0[4], (other.0).0[3], &mut carry); + let r8 = ::ff::mac_with_carry(r8, (self.0).0[4], (other.0).0[4], &mut carry); + let r9 = ::ff::mac_with_carry(r9, (self.0).0[4], (other.0).0[5], &mut carry); let r10 = carry; let mut carry = 0; - let r5 = ::mac_with_carry(r5, (self.0).0[5], (other.0).0[0], &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[5], (other.0).0[1], &mut carry); - let r7 = ::mac_with_carry(r7, (self.0).0[5], (other.0).0[2], &mut carry); - let r8 = ::mac_with_carry(r8, (self.0).0[5], (other.0).0[3], &mut carry); - let r9 = ::mac_with_carry(r9, (self.0).0[5], (other.0).0[4], &mut carry); - let r10 = ::mac_with_carry(r10, (self.0).0[5], (other.0).0[5], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[5], (other.0).0[0], &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[5], (other.0).0[1], &mut carry); + let r7 = ::ff::mac_with_carry(r7, (self.0).0[5], (other.0).0[2], &mut carry); + let r8 = ::ff::mac_with_carry(r8, (self.0).0[5], (other.0).0[3], &mut carry); + let r9 = ::ff::mac_with_carry(r9, (self.0).0[5], (other.0).0[4], &mut carry); + let r10 = ::ff::mac_with_carry(r10, (self.0).0[5], (other.0).0[5], &mut carry); let r11 = carry; self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11); } @@ -962,29 +962,29 @@ impl Field for Fq { #[inline] fn square(&mut self) { let mut carry = 0; - let r1 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); - let r2 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); - let r3 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); - let r4 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[4], &mut carry); - let r5 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[5], &mut carry); + let r1 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); + let r2 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); + let r3 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); + let r4 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[4], &mut carry); + let r5 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[5], &mut carry); let r6 = carry; let mut carry = 0; - let r3 = ::mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); - let r5 = ::mac_with_carry(r5, (self.0).0[1], (self.0).0[4], &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[1], (self.0).0[5], &mut carry); + let r3 = ::ff::mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[1], (self.0).0[4], &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[1], (self.0).0[5], &mut carry); let r7 = carry; let mut carry = 0; - let r5 = ::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[2], (self.0).0[4], &mut carry); - let r7 = ::mac_with_carry(r7, (self.0).0[2], (self.0).0[5], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[2], (self.0).0[4], &mut carry); + let r7 = ::ff::mac_with_carry(r7, (self.0).0[2], (self.0).0[5], &mut carry); let r8 = carry; let mut carry = 0; - let r7 = ::mac_with_carry(r7, (self.0).0[3], (self.0).0[4], &mut carry); - let r8 = ::mac_with_carry(r8, (self.0).0[3], (self.0).0[5], &mut carry); + let r7 = ::ff::mac_with_carry(r7, (self.0).0[3], (self.0).0[4], &mut carry); + let r8 = ::ff::mac_with_carry(r8, (self.0).0[3], (self.0).0[5], &mut carry); let r9 = carry; let mut carry = 0; - let r9 = ::mac_with_carry(r9, (self.0).0[4], (self.0).0[5], &mut carry); + let r9 = ::ff::mac_with_carry(r9, (self.0).0[4], (self.0).0[5], &mut carry); let r10 = carry; let r11 = r10 >> 63; @@ -1000,18 +1000,18 @@ impl Field for Fq { let r1 = r1 << 1; let mut carry = 0; - let r0 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); - let r1 = ::adc(r1, 0, &mut carry); - let r2 = ::mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); - let r3 = ::adc(r3, 0, &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); - let r5 = ::adc(r5, 0, &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); - let r7 = ::adc(r7, 0, &mut carry); - let r8 = ::mac_with_carry(r8, (self.0).0[4], (self.0).0[4], &mut carry); - let r9 = ::adc(r9, 0, &mut carry); - let r10 = ::mac_with_carry(r10, (self.0).0[5], (self.0).0[5], &mut carry); - let r11 = ::adc(r11, 0, &mut carry); + let r0 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); + let r1 = ::ff::adc(r1, 0, &mut carry); + let r2 = ::ff::mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); + let r3 = ::ff::adc(r3, 0, &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); + let r5 = ::ff::adc(r5, 0, &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); + let r7 = ::ff::adc(r7, 0, &mut carry); + let r8 = ::ff::mac_with_carry(r8, (self.0).0[4], (self.0).0[4], &mut carry); + let r9 = ::ff::adc(r9, 0, &mut carry); + let r10 = ::ff::mac_with_carry(r10, (self.0).0[5], (self.0).0[5], &mut carry); + let r11 = ::ff::adc(r11, 0, &mut carry); self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11); } } @@ -1055,63 +1055,63 @@ impl Fq { let k = r0.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r0, k, MODULUS.0[0], &mut carry); - r1 = ::mac_with_carry(r1, k, MODULUS.0[1], &mut carry); - r2 = ::mac_with_carry(r2, k, MODULUS.0[2], &mut carry); - r3 = ::mac_with_carry(r3, k, MODULUS.0[3], &mut carry); - r4 = ::mac_with_carry(r4, k, MODULUS.0[4], &mut carry); - r5 = ::mac_with_carry(r5, k, MODULUS.0[5], &mut carry); - r6 = ::adc(r6, 0, &mut carry); + ::ff::mac_with_carry(r0, k, MODULUS.0[0], &mut carry); + r1 = ::ff::mac_with_carry(r1, k, MODULUS.0[1], &mut carry); + r2 = ::ff::mac_with_carry(r2, k, MODULUS.0[2], &mut carry); + r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[3], &mut carry); + r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[4], &mut carry); + r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[5], &mut carry); + r6 = ::ff::adc(r6, 0, &mut carry); let carry2 = carry; let k = r1.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r1, k, MODULUS.0[0], &mut carry); - r2 = ::mac_with_carry(r2, k, MODULUS.0[1], &mut carry); - r3 = ::mac_with_carry(r3, k, MODULUS.0[2], &mut carry); - r4 = ::mac_with_carry(r4, k, MODULUS.0[3], &mut carry); - r5 = ::mac_with_carry(r5, k, MODULUS.0[4], &mut carry); - r6 = ::mac_with_carry(r6, k, MODULUS.0[5], &mut carry); - r7 = ::adc(r7, carry2, &mut carry); + ::ff::mac_with_carry(r1, k, MODULUS.0[0], &mut carry); + r2 = ::ff::mac_with_carry(r2, k, MODULUS.0[1], &mut carry); + r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[2], &mut carry); + r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[3], &mut carry); + r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[4], &mut carry); + r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[5], &mut carry); + r7 = ::ff::adc(r7, carry2, &mut carry); let carry2 = carry; let k = r2.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r2, k, MODULUS.0[0], &mut carry); - r3 = ::mac_with_carry(r3, k, MODULUS.0[1], &mut carry); - r4 = ::mac_with_carry(r4, k, MODULUS.0[2], &mut carry); - r5 = ::mac_with_carry(r5, k, MODULUS.0[3], &mut carry); - r6 = ::mac_with_carry(r6, k, MODULUS.0[4], &mut carry); - r7 = ::mac_with_carry(r7, k, MODULUS.0[5], &mut carry); - r8 = ::adc(r8, carry2, &mut carry); + ::ff::mac_with_carry(r2, k, MODULUS.0[0], &mut carry); + r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[1], &mut carry); + r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[2], &mut carry); + r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[3], &mut carry); + r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[4], &mut carry); + r7 = ::ff::mac_with_carry(r7, k, MODULUS.0[5], &mut carry); + r8 = ::ff::adc(r8, carry2, &mut carry); let carry2 = carry; let k = r3.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r3, k, MODULUS.0[0], &mut carry); - r4 = ::mac_with_carry(r4, k, MODULUS.0[1], &mut carry); - r5 = ::mac_with_carry(r5, k, MODULUS.0[2], &mut carry); - r6 = ::mac_with_carry(r6, k, MODULUS.0[3], &mut carry); - r7 = ::mac_with_carry(r7, k, MODULUS.0[4], &mut carry); - r8 = ::mac_with_carry(r8, k, MODULUS.0[5], &mut carry); - r9 = ::adc(r9, carry2, &mut carry); + ::ff::mac_with_carry(r3, k, MODULUS.0[0], &mut carry); + r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[1], &mut carry); + r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[2], &mut carry); + r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[3], &mut carry); + r7 = ::ff::mac_with_carry(r7, k, MODULUS.0[4], &mut carry); + r8 = ::ff::mac_with_carry(r8, k, MODULUS.0[5], &mut carry); + r9 = ::ff::adc(r9, carry2, &mut carry); let carry2 = carry; let k = r4.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r4, k, MODULUS.0[0], &mut carry); - r5 = ::mac_with_carry(r5, k, MODULUS.0[1], &mut carry); - r6 = ::mac_with_carry(r6, k, MODULUS.0[2], &mut carry); - r7 = ::mac_with_carry(r7, k, MODULUS.0[3], &mut carry); - r8 = ::mac_with_carry(r8, k, MODULUS.0[4], &mut carry); - r9 = ::mac_with_carry(r9, k, MODULUS.0[5], &mut carry); - r10 = ::adc(r10, carry2, &mut carry); + ::ff::mac_with_carry(r4, k, MODULUS.0[0], &mut carry); + r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[1], &mut carry); + r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[2], &mut carry); + r7 = ::ff::mac_with_carry(r7, k, MODULUS.0[3], &mut carry); + r8 = ::ff::mac_with_carry(r8, k, MODULUS.0[4], &mut carry); + r9 = ::ff::mac_with_carry(r9, k, MODULUS.0[5], &mut carry); + r10 = ::ff::adc(r10, carry2, &mut carry); let carry2 = carry; let k = r5.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r5, k, MODULUS.0[0], &mut carry); - r6 = ::mac_with_carry(r6, k, MODULUS.0[1], &mut carry); - r7 = ::mac_with_carry(r7, k, MODULUS.0[2], &mut carry); - r8 = ::mac_with_carry(r8, k, MODULUS.0[3], &mut carry); - r9 = ::mac_with_carry(r9, k, MODULUS.0[4], &mut carry); - r10 = ::mac_with_carry(r10, k, MODULUS.0[5], &mut carry); - r11 = ::adc(r11, carry2, &mut carry); + ::ff::mac_with_carry(r5, k, MODULUS.0[0], &mut carry); + r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[1], &mut carry); + r7 = ::ff::mac_with_carry(r7, k, MODULUS.0[2], &mut carry); + r8 = ::ff::mac_with_carry(r8, k, MODULUS.0[3], &mut carry); + r9 = ::ff::mac_with_carry(r9, k, MODULUS.0[4], &mut carry); + r10 = ::ff::mac_with_carry(r10, k, MODULUS.0[5], &mut carry); + r11 = ::ff::adc(r11, carry2, &mut carry); (self.0).0[0] = r6; (self.0).0[1] = r7; (self.0).0[2] = r8; @@ -1123,9 +1123,7 @@ impl Fq { } impl SqrtField for Fq { - fn legendre(&self) -> ::LegendreSymbol { - use LegendreSymbol::*; - + fn legendre(&self) -> ::ff::LegendreSymbol { // s = self^((q - 1) // 2) let s = self.pow([ 0xdcff7fffffffd555, @@ -1136,11 +1134,11 @@ impl SqrtField for Fq { 0xd0088f51cbff34d, ]); if s == Fq::zero() { - Zero + ::ff::LegendreSymbol::Zero } else if s == Fq::one() { - QuadraticResidue + ::ff::LegendreSymbol::QuadraticResidue } else { - QuadraticNonResidue + ::ff::LegendreSymbol::QuadraticNonResidue } } @@ -2924,7 +2922,7 @@ fn fq_repr_tests() { #[test] fn test_fq_legendre() { - use LegendreSymbol::*; + use ff::LegendreSymbol::*; assert_eq!(QuadraticResidue, Fq::one().legendre()); assert_eq!(Zero, Fq::zero().legendre()); diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 18cd580..1f3cd6f 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -160,7 +160,7 @@ impl Field for Fq2 { } impl SqrtField for Fq2 { - fn legendre(&self) -> ::LegendreSymbol { + fn legendre(&self) -> ::ff::LegendreSymbol { self.norm().legendre() } @@ -865,7 +865,7 @@ fn test_fq2_sqrt() { #[test] fn test_fq2_legendre() { - use LegendreSymbol::*; + use ff::LegendreSymbol::*; assert_eq!(Zero, Fq2::zero().legendre()); // i^2 = -1 diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 4e9d6ab..e9eee24 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,5 +1,4 @@ -use LegendreSymbol::*; -use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; +use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; // r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 const MODULUS: FrRepr = FrRepr([ @@ -229,7 +228,7 @@ impl PrimeFieldRepr for FrRepr { let mut carry = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { - *a = ::adc(*a, *b, &mut carry); + *a = ::ff::adc(*a, *b, &mut carry); } } @@ -238,7 +237,7 @@ impl PrimeFieldRepr for FrRepr { let mut borrow = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { - *a = ::sbb(*a, *b, &mut borrow); + *a = ::ff::sbb(*a, *b, &mut borrow); } } } @@ -437,28 +436,28 @@ impl Field for Fr { #[inline] fn mul_assign(&mut self, other: &Fr) { let mut carry = 0; - let r0 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); - let r1 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); - let r2 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); - let r3 = ::mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); + let r0 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); + let r1 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); + let r2 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); + let r3 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); let r4 = carry; let mut carry = 0; - let r1 = ::mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); - let r2 = ::mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); - let r3 = ::mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); + let r1 = ::ff::mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); + let r2 = ::ff::mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); + let r3 = ::ff::mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); let r5 = carry; let mut carry = 0; - let r2 = ::mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); - let r3 = ::mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); - let r5 = ::mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); + let r2 = ::ff::mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); + let r3 = ::ff::mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); let r6 = carry; let mut carry = 0; - let r3 = ::mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); - let r5 = ::mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); + let r3 = ::ff::mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); let r7 = carry; self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); } @@ -466,16 +465,16 @@ impl Field for Fr { #[inline] fn square(&mut self) { let mut carry = 0; - let r1 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); - let r2 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); - let r3 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); + let r1 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); + let r2 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); + let r3 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); let r4 = carry; let mut carry = 0; - let r3 = ::mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); + let r3 = ::ff::mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); let r5 = carry; let mut carry = 0; - let r5 = ::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); + let r5 = ::ff::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); let r6 = carry; let r7 = r6 >> 63; @@ -487,14 +486,14 @@ impl Field for Fr { let r1 = r1 << 1; let mut carry = 0; - let r0 = ::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); - let r1 = ::adc(r1, 0, &mut carry); - let r2 = ::mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); - let r3 = ::adc(r3, 0, &mut carry); - let r4 = ::mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); - let r5 = ::adc(r5, 0, &mut carry); - let r6 = ::mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); - let r7 = ::adc(r7, 0, &mut carry); + let r0 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); + let r1 = ::ff::adc(r1, 0, &mut carry); + let r2 = ::ff::mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); + let r3 = ::ff::adc(r3, 0, &mut carry); + let r4 = ::ff::mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); + let r5 = ::ff::adc(r5, 0, &mut carry); + let r6 = ::ff::mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); + let r7 = ::ff::adc(r7, 0, &mut carry); self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); } } @@ -534,35 +533,35 @@ impl Fr { let k = r0.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r0, k, MODULUS.0[0], &mut carry); - r1 = ::mac_with_carry(r1, k, MODULUS.0[1], &mut carry); - r2 = ::mac_with_carry(r2, k, MODULUS.0[2], &mut carry); - r3 = ::mac_with_carry(r3, k, MODULUS.0[3], &mut carry); - r4 = ::adc(r4, 0, &mut carry); + ::ff::mac_with_carry(r0, k, MODULUS.0[0], &mut carry); + r1 = ::ff::mac_with_carry(r1, k, MODULUS.0[1], &mut carry); + r2 = ::ff::mac_with_carry(r2, k, MODULUS.0[2], &mut carry); + r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[3], &mut carry); + r4 = ::ff::adc(r4, 0, &mut carry); let carry2 = carry; let k = r1.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r1, k, MODULUS.0[0], &mut carry); - r2 = ::mac_with_carry(r2, k, MODULUS.0[1], &mut carry); - r3 = ::mac_with_carry(r3, k, MODULUS.0[2], &mut carry); - r4 = ::mac_with_carry(r4, k, MODULUS.0[3], &mut carry); - r5 = ::adc(r5, carry2, &mut carry); + ::ff::mac_with_carry(r1, k, MODULUS.0[0], &mut carry); + r2 = ::ff::mac_with_carry(r2, k, MODULUS.0[1], &mut carry); + r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[2], &mut carry); + r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[3], &mut carry); + r5 = ::ff::adc(r5, carry2, &mut carry); let carry2 = carry; let k = r2.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r2, k, MODULUS.0[0], &mut carry); - r3 = ::mac_with_carry(r3, k, MODULUS.0[1], &mut carry); - r4 = ::mac_with_carry(r4, k, MODULUS.0[2], &mut carry); - r5 = ::mac_with_carry(r5, k, MODULUS.0[3], &mut carry); - r6 = ::adc(r6, carry2, &mut carry); + ::ff::mac_with_carry(r2, k, MODULUS.0[0], &mut carry); + r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[1], &mut carry); + r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[2], &mut carry); + r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[3], &mut carry); + r6 = ::ff::adc(r6, carry2, &mut carry); let carry2 = carry; let k = r3.wrapping_mul(INV); let mut carry = 0; - ::mac_with_carry(r3, k, MODULUS.0[0], &mut carry); - r4 = ::mac_with_carry(r4, k, MODULUS.0[1], &mut carry); - r5 = ::mac_with_carry(r5, k, MODULUS.0[2], &mut carry); - r6 = ::mac_with_carry(r6, k, MODULUS.0[3], &mut carry); - r7 = ::adc(r7, carry2, &mut carry); + ::ff::mac_with_carry(r3, k, MODULUS.0[0], &mut carry); + r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[1], &mut carry); + r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[2], &mut carry); + r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[3], &mut carry); + r7 = ::ff::adc(r7, carry2, &mut carry); (self.0).0[0] = r4; (self.0).0[1] = r5; (self.0).0[2] = r6; @@ -572,7 +571,7 @@ impl Fr { } impl SqrtField for Fr { - fn legendre(&self) -> ::LegendreSymbol { + fn legendre(&self) -> ::ff::LegendreSymbol { // s = self^((r - 1) // 2) let s = self.pow([ 0x7fffffff80000000, @@ -581,11 +580,11 @@ impl SqrtField for Fr { 0x39f6d3a994cebea4, ]); if s == Self::zero() { - Zero + ::ff::LegendreSymbol::Zero } else if s == Self::one() { - QuadraticResidue + ::ff::LegendreSymbol::QuadraticResidue } else { - QuadraticNonResidue + ::ff::LegendreSymbol::QuadraticNonResidue } } @@ -593,9 +592,9 @@ impl SqrtField for Fr { // Tonelli-Shank's algorithm for q mod 16 = 1 // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) match self.legendre() { - Zero => Some(*self), - QuadraticNonResidue => None, - QuadraticResidue => { + ::ff::LegendreSymbol::Zero => Some(*self), + ::ff::LegendreSymbol::QuadraticNonResidue => None, + ::ff::LegendreSymbol::QuadraticResidue => { let mut c = Fr(ROOT_OF_UNITY); // r = self^((t + 1) // 2) let mut r = self.pow([ @@ -909,6 +908,8 @@ fn test_fr_repr_sub_noborrow() { #[test] fn test_fr_legendre() { + use ff::LegendreSymbol::*; + assert_eq!(QuadraticResidue, Fr::one().legendre()); assert_eq!(Zero, Fr::zero().legendre()); diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index a5db4b5..c6c13c5 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -18,7 +18,9 @@ pub use self::fq2::Fq2; pub use self::fq6::Fq6; pub use self::fr::{Fr, FrRepr}; -use super::{BitIterator, CurveAffine, Engine, Field}; +use super::{CurveAffine, Engine}; + +use ff::{BitIterator, Field}; // The BLS parameter x for BLS12-381 is -0xd201000000010000 const BLS_X: u64 = 0xd201000000010000; diff --git a/src/lib.rs b/src/lib.rs index effc050..0c336ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ pub mod bls12_381; mod wnaf; pub use self::wnaf::Wnaf; -use ff::*; +use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; use std::error::Error; use std::fmt; diff --git a/src/tests/field.rs b/src/tests/field.rs index 74422fd..55396a7 100644 --- a/src/tests/field.rs +++ b/src/tests/field.rs @@ -1,5 +1,5 @@ +use ff::{Field, LegendreSymbol, PrimeField, SqrtField}; use rand::{Rng, SeedableRng, XorShiftRng}; -use {Field, LegendreSymbol, PrimeField, SqrtField}; pub fn random_frobenius_tests<F: Field, C: AsRef<[u64]>>(characteristic: C, maxpower: usize) { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); From a9d8079c2a9388d2a0f22f8f43d0dad9db877c00 Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Mon, 2 Jul 2018 06:57:05 +0100 Subject: [PATCH 115/140] Replace implementations of Fq and Fr with derives --- benches/bls12_381/fr.rs | 2 +- src/bls12_381/fq.rs | 735 +--------------------------------------- src/bls12_381/fr.rs | 649 +---------------------------------- src/lib.rs | 1 + 4 files changed, 22 insertions(+), 1365 deletions(-) diff --git a/benches/bls12_381/fr.rs b/benches/bls12_381/fr.rs index 13b0d0e..9cab671 100644 --- a/benches/bls12_381/fr.rs +++ b/benches/bls12_381/fr.rs @@ -1,6 +1,6 @@ use rand::{Rand, SeedableRng, XorShiftRng}; -use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; +use ff::{Field, PrimeField, PrimeFieldRepr}; use pairing::bls12_381::*; #[bench] diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index e109294..2b4b7a6 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,69 +1,5 @@ use super::fq2::Fq2; -use std::cmp::Ordering; -use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; - -// q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 -const MODULUS: FqRepr = FqRepr([ - 0xb9feffffffffaaab, - 0x1eabfffeb153ffff, - 0x6730d2a0f6b0f624, - 0x64774b84f38512bf, - 0x4b1ba7b6434bacd7, - 0x1a0111ea397fe69a, -]); - -// The number of bits needed to represent the modulus. -const MODULUS_BITS: u32 = 381; - -// The number of bits that must be shaved from the beginning of -// the representation when randomly sampling. -const REPR_SHAVE_BITS: u32 = 3; - -// R = 2**384 % q -const R: FqRepr = FqRepr([ - 0x760900000002fffd, - 0xebf4000bc40c0002, - 0x5f48985753c758ba, - 0x77ce585370525745, - 0x5c071a97a256ec6d, - 0x15f65ec3fa80e493, -]); - -// R2 = R^2 % q -const R2: FqRepr = FqRepr([ - 0xf4df1f341c341746, - 0xa76e6a609d104f1, - 0x8de5476c4c95b6d5, - 0x67eb88a9939d83c0, - 0x9a793e85b519952d, - 0x11988fe592cae3aa, -]); - -// INV = -(q^{-1} mod 2^64) mod 2^64 -const INV: u64 = 0x89f3fffcfffcfffd; - -// GENERATOR = 2 (multiplicative generator of q-1 order, that is also quadratic nonresidue) -const GENERATOR: FqRepr = FqRepr([ - 0x321300000006554f, - 0xb93c0018d6c40005, - 0x57605e0db0ddbb51, - 0x8b256521ed1f9bcb, - 0x6cf28d7901622c03, - 0x11ebab9dbb81e28c, -]); - -// 2^s * t = MODULUS - 1 with t odd -const S: u32 = 1; - -// 2^s root of unity computed by GENERATOR^t -const ROOT_OF_UNITY: FqRepr = FqRepr([ - 0x43f5fffffffcaaae, - 0x32b7fff2ed47fffd, - 0x7e83a49a2e99d69, - 0xeca8f3318332bb7a, - 0xef148d1ea0f4c069, - 0x40ab3263eff0206, -]); +use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; // B coefficient of BLS12-381 curve, 4. pub const B_COEFF: Fq = Fq(FqRepr([ @@ -507,667 +443,11 @@ pub const NEGATIVE_ONE: Fq = Fq(FqRepr([ 0x40ab3263eff0206, ])); -#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] -pub struct FqRepr(pub [u64; 6]); - -impl ::rand::Rand for FqRepr { - #[inline(always)] - fn rand<R: ::rand::Rng>(rng: &mut R) -> Self { - FqRepr(rng.gen()) - } -} - -impl ::std::fmt::Display for FqRepr { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - try!(write!(f, "0x")); - for i in self.0.iter().rev() { - try!(write!(f, "{:016x}", *i)); - } - - Ok(()) - } -} - -impl AsRef<[u64]> for FqRepr { - #[inline(always)] - fn as_ref(&self) -> &[u64] { - &self.0 - } -} - -impl AsMut<[u64]> for FqRepr { - #[inline(always)] - fn as_mut(&mut self) -> &mut [u64] { - &mut self.0 - } -} - -impl From<u64> for FqRepr { - #[inline(always)] - fn from(val: u64) -> FqRepr { - let mut repr = Self::default(); - repr.0[0] = val; - repr - } -} - -impl Ord for FqRepr { - #[inline(always)] - fn cmp(&self, other: &FqRepr) -> Ordering { - for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { - if a < b { - return Ordering::Less; - } else if a > b { - return Ordering::Greater; - } - } - - Ordering::Equal - } -} - -impl PartialOrd for FqRepr { - #[inline(always)] - fn partial_cmp(&self, other: &FqRepr) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl PrimeFieldRepr for FqRepr { - #[inline(always)] - fn is_odd(&self) -> bool { - self.0[0] & 1 == 1 - } - - #[inline(always)] - fn is_even(&self) -> bool { - !self.is_odd() - } - - #[inline(always)] - fn is_zero(&self) -> bool { - self.0.iter().all(|&e| e == 0) - } - - #[inline(always)] - fn shr(&mut self, mut n: u32) { - if n >= 64 * 6 { - *self = Self::from(0); - return; - } - - while n >= 64 { - let mut t = 0; - for i in self.0.iter_mut().rev() { - ::std::mem::swap(&mut t, i); - } - n -= 64; - } - - if n > 0 { - let mut t = 0; - for i in self.0.iter_mut().rev() { - let t2 = *i << (64 - n); - *i >>= n; - *i |= t; - t = t2; - } - } - } - - #[inline(always)] - fn div2(&mut self) { - let mut t = 0; - for i in self.0.iter_mut().rev() { - let t2 = *i << 63; - *i >>= 1; - *i |= t; - t = t2; - } - } - - #[inline(always)] - fn mul2(&mut self) { - let mut last = 0; - for i in &mut self.0 { - let tmp = *i >> 63; - *i <<= 1; - *i |= last; - last = tmp; - } - } - - #[inline(always)] - fn shl(&mut self, mut n: u32) { - if n >= 64 * 6 { - *self = Self::from(0); - return; - } - - while n >= 64 { - let mut t = 0; - for i in &mut self.0 { - ::std::mem::swap(&mut t, i); - } - n -= 64; - } - - if n > 0 { - let mut t = 0; - for i in &mut self.0 { - let t2 = *i >> (64 - n); - *i <<= n; - *i |= t; - t = t2; - } - } - } - - #[inline(always)] - fn num_bits(&self) -> u32 { - let mut ret = (6 as u32) * 64; - for i in self.0.iter().rev() { - let leading = i.leading_zeros(); - ret -= leading; - if leading != 64 { - break; - } - } - - ret - } - - #[inline(always)] - fn add_nocarry(&mut self, other: &FqRepr) { - let mut carry = 0; - - for (a, b) in self.0.iter_mut().zip(other.0.iter()) { - *a = ::ff::adc(*a, *b, &mut carry); - } - } - - #[inline(always)] - fn sub_noborrow(&mut self, other: &FqRepr) { - let mut borrow = 0; - - for (a, b) in self.0.iter_mut().zip(other.0.iter()) { - *a = ::ff::sbb(*a, *b, &mut borrow); - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(PrimeField)] +#[PrimeFieldModulus = "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787"] +#[PrimeFieldGenerator = "2"] pub struct Fq(FqRepr); -/// `Fq` elements are ordered lexicographically. -impl Ord for Fq { - #[inline(always)] - fn cmp(&self, other: &Fq) -> Ordering { - self.into_repr().cmp(&other.into_repr()) - } -} - -impl PartialOrd for Fq { - #[inline(always)] - fn partial_cmp(&self, other: &Fq) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl ::std::fmt::Display for Fq { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "Fq({})", self.into_repr()) - } -} - -impl ::rand::Rand for Fq { - fn rand<R: ::rand::Rng>(rng: &mut R) -> Self { - loop { - let mut tmp = Fq(FqRepr::rand(rng)); - - // Mask away the unused bits at the beginning. - tmp.0.as_mut()[5] &= 0xffffffffffffffff >> REPR_SHAVE_BITS; - - if tmp.is_valid() { - return tmp; - } - } - } -} - -impl From<Fq> for FqRepr { - fn from(e: Fq) -> FqRepr { - e.into_repr() - } -} - -impl PrimeField for Fq { - type Repr = FqRepr; - - fn from_repr(r: FqRepr) -> Result<Fq, PrimeFieldDecodingError> { - let mut r = Fq(r); - if r.is_valid() { - r.mul_assign(&Fq(R2)); - - Ok(r) - } else { - Err(PrimeFieldDecodingError::NotInField(format!("{}", r.0))) - } - } - - fn into_repr(&self) -> FqRepr { - let mut r = *self; - r.mont_reduce( - (self.0).0[0], - (self.0).0[1], - (self.0).0[2], - (self.0).0[3], - (self.0).0[4], - (self.0).0[5], - 0, - 0, - 0, - 0, - 0, - 0, - ); - r.0 - } - - fn char() -> FqRepr { - MODULUS - } - - const NUM_BITS: u32 = MODULUS_BITS; - - const CAPACITY: u32 = Self::NUM_BITS - 1; - - fn multiplicative_generator() -> Self { - Fq(GENERATOR) - } - - const S: u32 = S; - - fn root_of_unity() -> Self { - Fq(ROOT_OF_UNITY) - } -} - -impl Field for Fq { - #[inline] - fn zero() -> Self { - Fq(FqRepr::from(0)) - } - - #[inline] - fn one() -> Self { - Fq(R) - } - - #[inline] - fn is_zero(&self) -> bool { - self.0.is_zero() - } - - #[inline] - fn add_assign(&mut self, other: &Fq) { - // This cannot exceed the backing capacity. - self.0.add_nocarry(&other.0); - - // However, it may need to be reduced. - self.reduce(); - } - - #[inline] - fn double(&mut self) { - // This cannot exceed the backing capacity. - self.0.mul2(); - - // However, it may need to be reduced. - self.reduce(); - } - - #[inline] - fn sub_assign(&mut self, other: &Fq) { - // If `other` is larger than `self`, we'll need to add the modulus to self first. - if other.0 > self.0 { - self.0.add_nocarry(&MODULUS); - } - - self.0.sub_noborrow(&other.0); - } - - #[inline] - fn negate(&mut self) { - if !self.is_zero() { - let mut tmp = MODULUS; - tmp.sub_noborrow(&self.0); - self.0 = tmp; - } - } - - fn inverse(&self) -> Option<Self> { - if self.is_zero() { - None - } else { - // Guajardo Kumar Paar Pelzl - // Efficient Software-Implementation of Finite Fields with Applications to Cryptography - // Algorithm 16 (BEA for Inversion in Fp) - - let one = FqRepr::from(1); - - let mut u = self.0; - let mut v = MODULUS; - let mut b = Fq(R2); // Avoids unnecessary reduction step. - let mut c = Self::zero(); - - while u != one && v != one { - while u.is_even() { - u.div2(); - - if b.0.is_even() { - b.0.div2(); - } else { - b.0.add_nocarry(&MODULUS); - b.0.div2(); - } - } - - while v.is_even() { - v.div2(); - - if c.0.is_even() { - c.0.div2(); - } else { - c.0.add_nocarry(&MODULUS); - c.0.div2(); - } - } - - if v < u { - u.sub_noborrow(&v); - b.sub_assign(&c); - } else { - v.sub_noborrow(&u); - c.sub_assign(&b); - } - } - - if u == one { - Some(b) - } else { - Some(c) - } - } - } - - #[inline(always)] - fn frobenius_map(&mut self, _: usize) { - // This has no effect in a prime field. - } - - #[inline] - fn mul_assign(&mut self, other: &Fq) { - let mut carry = 0; - let r0 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); - let r1 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); - let r2 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); - let r3 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); - let r4 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[4], &mut carry); - let r5 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[5], &mut carry); - let r6 = carry; - let mut carry = 0; - let r1 = ::ff::mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); - let r2 = ::ff::mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); - let r3 = ::ff::mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); - let r5 = ::ff::mac_with_carry(r5, (self.0).0[1], (other.0).0[4], &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[1], (other.0).0[5], &mut carry); - let r7 = carry; - let mut carry = 0; - let r2 = ::ff::mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); - let r3 = ::ff::mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); - let r5 = ::ff::mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[2], (other.0).0[4], &mut carry); - let r7 = ::ff::mac_with_carry(r7, (self.0).0[2], (other.0).0[5], &mut carry); - let r8 = carry; - let mut carry = 0; - let r3 = ::ff::mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); - let r5 = ::ff::mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); - let r7 = ::ff::mac_with_carry(r7, (self.0).0[3], (other.0).0[4], &mut carry); - let r8 = ::ff::mac_with_carry(r8, (self.0).0[3], (other.0).0[5], &mut carry); - let r9 = carry; - let mut carry = 0; - let r4 = ::ff::mac_with_carry(r4, (self.0).0[4], (other.0).0[0], &mut carry); - let r5 = ::ff::mac_with_carry(r5, (self.0).0[4], (other.0).0[1], &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[4], (other.0).0[2], &mut carry); - let r7 = ::ff::mac_with_carry(r7, (self.0).0[4], (other.0).0[3], &mut carry); - let r8 = ::ff::mac_with_carry(r8, (self.0).0[4], (other.0).0[4], &mut carry); - let r9 = ::ff::mac_with_carry(r9, (self.0).0[4], (other.0).0[5], &mut carry); - let r10 = carry; - let mut carry = 0; - let r5 = ::ff::mac_with_carry(r5, (self.0).0[5], (other.0).0[0], &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[5], (other.0).0[1], &mut carry); - let r7 = ::ff::mac_with_carry(r7, (self.0).0[5], (other.0).0[2], &mut carry); - let r8 = ::ff::mac_with_carry(r8, (self.0).0[5], (other.0).0[3], &mut carry); - let r9 = ::ff::mac_with_carry(r9, (self.0).0[5], (other.0).0[4], &mut carry); - let r10 = ::ff::mac_with_carry(r10, (self.0).0[5], (other.0).0[5], &mut carry); - let r11 = carry; - self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11); - } - - #[inline] - fn square(&mut self) { - let mut carry = 0; - let r1 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); - let r2 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); - let r3 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); - let r4 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[4], &mut carry); - let r5 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[5], &mut carry); - let r6 = carry; - let mut carry = 0; - let r3 = ::ff::mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); - let r5 = ::ff::mac_with_carry(r5, (self.0).0[1], (self.0).0[4], &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[1], (self.0).0[5], &mut carry); - let r7 = carry; - let mut carry = 0; - let r5 = ::ff::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[2], (self.0).0[4], &mut carry); - let r7 = ::ff::mac_with_carry(r7, (self.0).0[2], (self.0).0[5], &mut carry); - let r8 = carry; - let mut carry = 0; - let r7 = ::ff::mac_with_carry(r7, (self.0).0[3], (self.0).0[4], &mut carry); - let r8 = ::ff::mac_with_carry(r8, (self.0).0[3], (self.0).0[5], &mut carry); - let r9 = carry; - let mut carry = 0; - let r9 = ::ff::mac_with_carry(r9, (self.0).0[4], (self.0).0[5], &mut carry); - let r10 = carry; - - let r11 = r10 >> 63; - let r10 = (r10 << 1) | (r9 >> 63); - let r9 = (r9 << 1) | (r8 >> 63); - let r8 = (r8 << 1) | (r7 >> 63); - let r7 = (r7 << 1) | (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 mut carry = 0; - let r0 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); - let r1 = ::ff::adc(r1, 0, &mut carry); - let r2 = ::ff::mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); - let r3 = ::ff::adc(r3, 0, &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); - let r5 = ::ff::adc(r5, 0, &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); - let r7 = ::ff::adc(r7, 0, &mut carry); - let r8 = ::ff::mac_with_carry(r8, (self.0).0[4], (self.0).0[4], &mut carry); - let r9 = ::ff::adc(r9, 0, &mut carry); - let r10 = ::ff::mac_with_carry(r10, (self.0).0[5], (self.0).0[5], &mut carry); - let r11 = ::ff::adc(r11, 0, &mut carry); - self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11); - } -} - -impl Fq { - /// Determines if the element is really in the field. This is only used - /// internally. - #[inline(always)] - fn is_valid(&self) -> bool { - self.0 < MODULUS - } - - /// Subtracts the modulus from this element if this element is not in the - /// field. Only used internally. - #[inline(always)] - fn reduce(&mut self) { - if !self.is_valid() { - self.0.sub_noborrow(&MODULUS); - } - } - - #[inline(always)] - fn mont_reduce( - &mut self, - r0: u64, - mut r1: u64, - mut r2: u64, - mut r3: u64, - mut r4: u64, - mut r5: u64, - mut r6: u64, - mut r7: u64, - mut r8: u64, - mut r9: u64, - mut r10: u64, - mut r11: u64, - ) { - // The Montgomery reduction here is based on Algorithm 14.32 in - // Handbook of Applied Cryptography - // <http://cacr.uwaterloo.ca/hac/about/chap14.pdf>. - - let k = r0.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r0, k, MODULUS.0[0], &mut carry); - r1 = ::ff::mac_with_carry(r1, k, MODULUS.0[1], &mut carry); - r2 = ::ff::mac_with_carry(r2, k, MODULUS.0[2], &mut carry); - r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[3], &mut carry); - r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[4], &mut carry); - r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[5], &mut carry); - r6 = ::ff::adc(r6, 0, &mut carry); - let carry2 = carry; - let k = r1.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r1, k, MODULUS.0[0], &mut carry); - r2 = ::ff::mac_with_carry(r2, k, MODULUS.0[1], &mut carry); - r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[2], &mut carry); - r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[3], &mut carry); - r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[4], &mut carry); - r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[5], &mut carry); - r7 = ::ff::adc(r7, carry2, &mut carry); - let carry2 = carry; - let k = r2.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r2, k, MODULUS.0[0], &mut carry); - r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[1], &mut carry); - r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[2], &mut carry); - r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[3], &mut carry); - r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[4], &mut carry); - r7 = ::ff::mac_with_carry(r7, k, MODULUS.0[5], &mut carry); - r8 = ::ff::adc(r8, carry2, &mut carry); - let carry2 = carry; - let k = r3.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r3, k, MODULUS.0[0], &mut carry); - r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[1], &mut carry); - r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[2], &mut carry); - r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[3], &mut carry); - r7 = ::ff::mac_with_carry(r7, k, MODULUS.0[4], &mut carry); - r8 = ::ff::mac_with_carry(r8, k, MODULUS.0[5], &mut carry); - r9 = ::ff::adc(r9, carry2, &mut carry); - let carry2 = carry; - let k = r4.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r4, k, MODULUS.0[0], &mut carry); - r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[1], &mut carry); - r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[2], &mut carry); - r7 = ::ff::mac_with_carry(r7, k, MODULUS.0[3], &mut carry); - r8 = ::ff::mac_with_carry(r8, k, MODULUS.0[4], &mut carry); - r9 = ::ff::mac_with_carry(r9, k, MODULUS.0[5], &mut carry); - r10 = ::ff::adc(r10, carry2, &mut carry); - let carry2 = carry; - let k = r5.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r5, k, MODULUS.0[0], &mut carry); - r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[1], &mut carry); - r7 = ::ff::mac_with_carry(r7, k, MODULUS.0[2], &mut carry); - r8 = ::ff::mac_with_carry(r8, k, MODULUS.0[3], &mut carry); - r9 = ::ff::mac_with_carry(r9, k, MODULUS.0[4], &mut carry); - r10 = ::ff::mac_with_carry(r10, k, MODULUS.0[5], &mut carry); - r11 = ::ff::adc(r11, carry2, &mut carry); - (self.0).0[0] = r6; - (self.0).0[1] = r7; - (self.0).0[2] = r8; - (self.0).0[3] = r9; - (self.0).0[4] = r10; - (self.0).0[5] = r11; - self.reduce(); - } -} - -impl SqrtField for Fq { - fn legendre(&self) -> ::ff::LegendreSymbol { - // s = self^((q - 1) // 2) - let s = self.pow([ - 0xdcff7fffffffd555, - 0xf55ffff58a9ffff, - 0xb39869507b587b12, - 0xb23ba5c279c2895f, - 0x258dd3db21a5d66b, - 0xd0088f51cbff34d, - ]); - if s == Fq::zero() { - ::ff::LegendreSymbol::Zero - } else if s == Fq::one() { - ::ff::LegendreSymbol::QuadraticResidue - } else { - ::ff::LegendreSymbol::QuadraticNonResidue - } - } - - fn sqrt(&self) -> Option<Self> { - // Shank's algorithm for q mod 4 = 3 - // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) - - // a1 = self^((q - 3) // 4) - let mut a1 = self.pow([ - 0xee7fbfffffffeaaa, - 0x7aaffffac54ffff, - 0xd9cc34a83dac3d89, - 0xd91dd2e13ce144af, - 0x92c6e9ed90d2eb35, - 0x680447a8e5ff9a6, - ]); - let mut a0 = a1; - a0.square(); - a0.mul_assign(self); - - if a0 == NEGATIVE_ONE { - None - } else { - a1.mul_assign(self); - Some(a1) - } - } -} - #[test] fn test_b_coeff() { assert_eq!(Fq::from_repr(FqRepr::from(4)).unwrap(), B_COEFF); @@ -1897,6 +1177,8 @@ use rand::{Rand, SeedableRng, XorShiftRng}; #[test] fn test_fq_repr_ordering() { + use std::cmp::Ordering; + fn assert_equality(a: FqRepr, b: FqRepr) { assert_eq!(a, b); assert!(a.cmp(&b) == Ordering::Equal); @@ -2743,6 +2025,8 @@ fn test_fq_pow() { #[test] fn test_fq_sqrt() { + use ff::SqrtField; + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); assert_eq!(Fq::zero().sqrt().unwrap(), Fq::zero()); @@ -2876,6 +2160,8 @@ fn test_fq_num_bits() { #[test] fn test_fq_root_of_unity() { + use ff::SqrtField; + assert_eq!(Fq::S, 1); assert_eq!( Fq::multiplicative_generator(), @@ -2923,6 +2209,7 @@ fn fq_repr_tests() { #[test] fn test_fq_legendre() { use ff::LegendreSymbol::*; + use ff::SqrtField; assert_eq!(QuadraticResidue, Fq::one().legendre()); assert_eq!(Zero, Fq::zero().legendre()); diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index e9eee24..6e142f0 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,646 +1,10 @@ -use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; +use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; -// r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 -const MODULUS: FrRepr = FrRepr([ - 0xffffffff00000001, - 0x53bda402fffe5bfe, - 0x3339d80809a1d805, - 0x73eda753299d7d48, -]); - -// The number of bits needed to represent the modulus. -const MODULUS_BITS: u32 = 255; - -// The number of bits that must be shaved from the beginning of -// the representation when randomly sampling. -const REPR_SHAVE_BITS: u32 = 1; - -// R = 2**256 % r -const R: FrRepr = FrRepr([ - 0x1fffffffe, - 0x5884b7fa00034802, - 0x998c4fefecbc4ff5, - 0x1824b159acc5056f, -]); - -// R2 = R^2 % r -const R2: FrRepr = FrRepr([ - 0xc999e990f3f29c6d, - 0x2b6cedcb87925c23, - 0x5d314967254398f, - 0x748d9d99f59ff11, -]); - -// INV = -(r^{-1} mod 2^64) mod 2^64 -const INV: u64 = 0xfffffffeffffffff; - -// GENERATOR = 7 (multiplicative generator of r-1 order, that is also quadratic nonresidue) -const GENERATOR: FrRepr = FrRepr([ - 0xefffffff1, - 0x17e363d300189c0f, - 0xff9c57876f8457b0, - 0x351332208fc5a8c4, -]); - -// 2^s * t = MODULUS - 1 with t odd -const S: u32 = 32; - -// 2^s root of unity computed by GENERATOR^t -const ROOT_OF_UNITY: FrRepr = FrRepr([ - 0xb9b58d8c5f0e466a, - 0x5b1b4c801819d7ec, - 0xaf53ae352a31e64, - 0x5bf3adda19e9b27b, -]); - -#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] -pub struct FrRepr(pub [u64; 4]); - -impl ::rand::Rand for FrRepr { - #[inline(always)] - fn rand<R: ::rand::Rng>(rng: &mut R) -> Self { - FrRepr(rng.gen()) - } -} - -impl ::std::fmt::Display for FrRepr { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - try!(write!(f, "0x")); - for i in self.0.iter().rev() { - try!(write!(f, "{:016x}", *i)); - } - - Ok(()) - } -} - -impl AsRef<[u64]> for FrRepr { - #[inline(always)] - fn as_ref(&self) -> &[u64] { - &self.0 - } -} - -impl AsMut<[u64]> for FrRepr { - #[inline(always)] - fn as_mut(&mut self) -> &mut [u64] { - &mut self.0 - } -} - -impl From<u64> for FrRepr { - #[inline(always)] - fn from(val: u64) -> FrRepr { - let mut repr = Self::default(); - repr.0[0] = val; - repr - } -} - -impl Ord for FrRepr { - #[inline(always)] - fn cmp(&self, other: &FrRepr) -> ::std::cmp::Ordering { - for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { - if a < b { - return ::std::cmp::Ordering::Less; - } else if a > b { - return ::std::cmp::Ordering::Greater; - } - } - - ::std::cmp::Ordering::Equal - } -} - -impl PartialOrd for FrRepr { - #[inline(always)] - fn partial_cmp(&self, other: &FrRepr) -> Option<::std::cmp::Ordering> { - Some(self.cmp(other)) - } -} - -impl PrimeFieldRepr for FrRepr { - #[inline(always)] - fn is_odd(&self) -> bool { - self.0[0] & 1 == 1 - } - - #[inline(always)] - fn is_even(&self) -> bool { - !self.is_odd() - } - - #[inline(always)] - fn is_zero(&self) -> bool { - self.0.iter().all(|&e| e == 0) - } - - #[inline(always)] - fn shr(&mut self, mut n: u32) { - if n >= 64 * 4 { - *self = Self::from(0); - return; - } - - while n >= 64 { - let mut t = 0; - for i in self.0.iter_mut().rev() { - ::std::mem::swap(&mut t, i); - } - n -= 64; - } - - if n > 0 { - let mut t = 0; - for i in self.0.iter_mut().rev() { - let t2 = *i << (64 - n); - *i >>= n; - *i |= t; - t = t2; - } - } - } - - #[inline(always)] - fn div2(&mut self) { - let mut t = 0; - for i in self.0.iter_mut().rev() { - let t2 = *i << 63; - *i >>= 1; - *i |= t; - t = t2; - } - } - - #[inline(always)] - fn mul2(&mut self) { - let mut last = 0; - for i in &mut self.0 { - let tmp = *i >> 63; - *i <<= 1; - *i |= last; - last = tmp; - } - } - - #[inline(always)] - fn shl(&mut self, mut n: u32) { - if n >= 64 * 4 { - *self = Self::from(0); - return; - } - - while n >= 64 { - let mut t = 0; - for i in &mut self.0 { - ::std::mem::swap(&mut t, i); - } - n -= 64; - } - - if n > 0 { - let mut t = 0; - for i in &mut self.0 { - let t2 = *i >> (64 - n); - *i <<= n; - *i |= t; - t = t2; - } - } - } - - #[inline(always)] - fn num_bits(&self) -> u32 { - let mut ret = (4 as u32) * 64; - for i in self.0.iter().rev() { - let leading = i.leading_zeros(); - ret -= leading; - if leading != 64 { - break; - } - } - - ret - } - - #[inline(always)] - fn add_nocarry(&mut self, other: &FrRepr) { - let mut carry = 0; - - for (a, b) in self.0.iter_mut().zip(other.0.iter()) { - *a = ::ff::adc(*a, *b, &mut carry); - } - } - - #[inline(always)] - fn sub_noborrow(&mut self, other: &FrRepr) { - let mut borrow = 0; - - for (a, b) in self.0.iter_mut().zip(other.0.iter()) { - *a = ::ff::sbb(*a, *b, &mut borrow); - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(PrimeField)] +#[PrimeFieldModulus = "52435875175126190479447740508185965837690552500527637822603658699938581184513"] +#[PrimeFieldGenerator = "7"] pub struct Fr(FrRepr); -impl ::std::fmt::Display for Fr { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "Fr({})", self.into_repr()) - } -} - -impl ::rand::Rand for Fr { - fn rand<R: ::rand::Rng>(rng: &mut R) -> Self { - loop { - let mut tmp = Fr(FrRepr::rand(rng)); - - // Mask away the unused bits at the beginning. - tmp.0.as_mut()[3] &= 0xffffffffffffffff >> REPR_SHAVE_BITS; - - if tmp.is_valid() { - return tmp; - } - } - } -} - -impl From<Fr> for FrRepr { - fn from(e: Fr) -> FrRepr { - e.into_repr() - } -} - -impl PrimeField for Fr { - type Repr = FrRepr; - - fn from_repr(r: FrRepr) -> Result<Fr, PrimeFieldDecodingError> { - let mut r = Fr(r); - if r.is_valid() { - r.mul_assign(&Fr(R2)); - - Ok(r) - } else { - Err(PrimeFieldDecodingError::NotInField(format!("{}", r.0))) - } - } - - fn into_repr(&self) -> FrRepr { - let mut r = *self; - r.mont_reduce( - (self.0).0[0], - (self.0).0[1], - (self.0).0[2], - (self.0).0[3], - 0, - 0, - 0, - 0, - ); - r.0 - } - - fn char() -> FrRepr { - MODULUS - } - - const NUM_BITS: u32 = MODULUS_BITS; - - const CAPACITY: u32 = Self::NUM_BITS - 1; - - fn multiplicative_generator() -> Self { - Fr(GENERATOR) - } - - const S: u32 = S; - - fn root_of_unity() -> Self { - Fr(ROOT_OF_UNITY) - } -} - -impl Field for Fr { - #[inline] - fn zero() -> Self { - Fr(FrRepr::from(0)) - } - - #[inline] - fn one() -> Self { - Fr(R) - } - - #[inline] - fn is_zero(&self) -> bool { - self.0.is_zero() - } - - #[inline] - fn add_assign(&mut self, other: &Fr) { - // This cannot exceed the backing capacity. - self.0.add_nocarry(&other.0); - - // However, it may need to be reduced. - self.reduce(); - } - - #[inline] - fn double(&mut self) { - // This cannot exceed the backing capacity. - self.0.mul2(); - - // However, it may need to be reduced. - self.reduce(); - } - - #[inline] - fn sub_assign(&mut self, other: &Fr) { - // If `other` is larger than `self`, we'll need to add the modulus to self first. - if other.0 > self.0 { - self.0.add_nocarry(&MODULUS); - } - - self.0.sub_noborrow(&other.0); - } - - #[inline] - fn negate(&mut self) { - if !self.is_zero() { - let mut tmp = MODULUS; - tmp.sub_noborrow(&self.0); - self.0 = tmp; - } - } - - fn inverse(&self) -> Option<Self> { - if self.is_zero() { - None - } else { - // Guajardo Kumar Paar Pelzl - // Efficient Software-Implementation of Finite Fields with Applications to Cryptography - // Algorithm 16 (BEA for Inversion in Fp) - - let one = FrRepr::from(1); - - let mut u = self.0; - let mut v = MODULUS; - let mut b = Fr(R2); // Avoids unnecessary reduction step. - let mut c = Self::zero(); - - while u != one && v != one { - while u.is_even() { - u.div2(); - - if b.0.is_even() { - b.0.div2(); - } else { - b.0.add_nocarry(&MODULUS); - b.0.div2(); - } - } - - while v.is_even() { - v.div2(); - - if c.0.is_even() { - c.0.div2(); - } else { - c.0.add_nocarry(&MODULUS); - c.0.div2(); - } - } - - if v < u { - u.sub_noborrow(&v); - b.sub_assign(&c); - } else { - v.sub_noborrow(&u); - c.sub_assign(&b); - } - } - - if u == one { - Some(b) - } else { - Some(c) - } - } - } - - #[inline(always)] - fn frobenius_map(&mut self, _: usize) { - // This has no effect in a prime field. - } - - #[inline] - fn mul_assign(&mut self, other: &Fr) { - let mut carry = 0; - let r0 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); - let r1 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); - let r2 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); - let r3 = ::ff::mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); - let r4 = carry; - let mut carry = 0; - let r1 = ::ff::mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); - let r2 = ::ff::mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); - let r3 = ::ff::mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); - let r5 = carry; - let mut carry = 0; - let r2 = ::ff::mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); - let r3 = ::ff::mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); - let r5 = ::ff::mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); - let r6 = carry; - let mut carry = 0; - let r3 = ::ff::mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); - let r5 = ::ff::mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); - let r7 = carry; - self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); - } - - #[inline] - fn square(&mut self) { - let mut carry = 0; - let r1 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); - let r2 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); - let r3 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); - let r4 = carry; - let mut carry = 0; - let r3 = ::ff::mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); - let r5 = carry; - let mut carry = 0; - let r5 = ::ff::mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); - let r6 = carry; - - 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 mut carry = 0; - let r0 = ::ff::mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); - let r1 = ::ff::adc(r1, 0, &mut carry); - let r2 = ::ff::mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); - let r3 = ::ff::adc(r3, 0, &mut carry); - let r4 = ::ff::mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); - let r5 = ::ff::adc(r5, 0, &mut carry); - let r6 = ::ff::mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); - let r7 = ::ff::adc(r7, 0, &mut carry); - self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); - } -} - -impl Fr { - /// Determines if the element is really in the field. This is only used - /// internally. - #[inline(always)] - fn is_valid(&self) -> bool { - self.0 < MODULUS - } - - /// Subtracts the modulus from this element if this element is not in the - /// field. Only used internally. - #[inline(always)] - fn reduce(&mut self) { - if !self.is_valid() { - self.0.sub_noborrow(&MODULUS); - } - } - - #[inline(always)] - fn mont_reduce( - &mut self, - r0: u64, - mut r1: u64, - mut r2: u64, - mut r3: u64, - mut r4: u64, - mut r5: u64, - mut r6: u64, - mut r7: u64, - ) { - // The Montgomery reduction here is based on Algorithm 14.32 in - // Handbook of Applied Cryptography - // <http://cacr.uwaterloo.ca/hac/about/chap14.pdf>. - - let k = r0.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r0, k, MODULUS.0[0], &mut carry); - r1 = ::ff::mac_with_carry(r1, k, MODULUS.0[1], &mut carry); - r2 = ::ff::mac_with_carry(r2, k, MODULUS.0[2], &mut carry); - r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[3], &mut carry); - r4 = ::ff::adc(r4, 0, &mut carry); - let carry2 = carry; - let k = r1.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r1, k, MODULUS.0[0], &mut carry); - r2 = ::ff::mac_with_carry(r2, k, MODULUS.0[1], &mut carry); - r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[2], &mut carry); - r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[3], &mut carry); - r5 = ::ff::adc(r5, carry2, &mut carry); - let carry2 = carry; - let k = r2.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r2, k, MODULUS.0[0], &mut carry); - r3 = ::ff::mac_with_carry(r3, k, MODULUS.0[1], &mut carry); - r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[2], &mut carry); - r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[3], &mut carry); - r6 = ::ff::adc(r6, carry2, &mut carry); - let carry2 = carry; - let k = r3.wrapping_mul(INV); - let mut carry = 0; - ::ff::mac_with_carry(r3, k, MODULUS.0[0], &mut carry); - r4 = ::ff::mac_with_carry(r4, k, MODULUS.0[1], &mut carry); - r5 = ::ff::mac_with_carry(r5, k, MODULUS.0[2], &mut carry); - r6 = ::ff::mac_with_carry(r6, k, MODULUS.0[3], &mut carry); - r7 = ::ff::adc(r7, carry2, &mut carry); - (self.0).0[0] = r4; - (self.0).0[1] = r5; - (self.0).0[2] = r6; - (self.0).0[3] = r7; - self.reduce(); - } -} - -impl SqrtField for Fr { - fn legendre(&self) -> ::ff::LegendreSymbol { - // s = self^((r - 1) // 2) - let s = self.pow([ - 0x7fffffff80000000, - 0xa9ded2017fff2dff, - 0x199cec0404d0ec02, - 0x39f6d3a994cebea4, - ]); - if s == Self::zero() { - ::ff::LegendreSymbol::Zero - } else if s == Self::one() { - ::ff::LegendreSymbol::QuadraticResidue - } else { - ::ff::LegendreSymbol::QuadraticNonResidue - } - } - - fn sqrt(&self) -> Option<Self> { - // Tonelli-Shank's algorithm for q mod 16 = 1 - // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) - match self.legendre() { - ::ff::LegendreSymbol::Zero => Some(*self), - ::ff::LegendreSymbol::QuadraticNonResidue => None, - ::ff::LegendreSymbol::QuadraticResidue => { - let mut c = Fr(ROOT_OF_UNITY); - // r = self^((t + 1) // 2) - let mut r = self.pow([ - 0x7fff2dff80000000, - 0x4d0ec02a9ded201, - 0x94cebea4199cec04, - 0x39f6d3a9, - ]); - // t = self^t - let mut t = self.pow([ - 0xfffe5bfeffffffff, - 0x9a1d80553bda402, - 0x299d7d483339d808, - 0x73eda753, - ]); - let mut m = S; - - while t != Self::one() { - let mut i = 1; - { - let mut t2i = t; - t2i.square(); - loop { - if t2i == Self::one() { - break; - } - t2i.square(); - i += 1; - } - } - - for _ in 0..(m - i - 1) { - c.square(); - } - r.mul_assign(&c); - c.square(); - t.mul_assign(&c); - m = i; - } - - Some(r) - } - } - } -} - #[cfg(test)] use rand::{Rand, SeedableRng, XorShiftRng}; @@ -909,6 +273,7 @@ fn test_fr_repr_sub_noborrow() { #[test] fn test_fr_legendre() { use ff::LegendreSymbol::*; + use ff::SqrtField; assert_eq!(QuadraticResidue, Fr::one().legendre()); assert_eq!(Zero, Fr::zero().legendre()); @@ -1418,6 +783,8 @@ fn test_fr_pow() { #[test] fn test_fr_sqrt() { + use ff::SqrtField; + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); assert_eq!(Fr::zero().sqrt().unwrap(), Fr::zero()); @@ -1583,6 +950,8 @@ fn test_fr_num_bits() { #[test] fn test_fr_root_of_unity() { + use ff::SqrtField; + assert_eq!(Fr::S, 32); assert_eq!( Fr::multiplicative_generator(), diff --git a/src/lib.rs b/src/lib.rs index 0c336ed..fefdae3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ #![deny(missing_debug_implementations)] extern crate byteorder; +#[macro_use] extern crate ff; extern crate rand; From defdf8df52dbd75fc74286a6599e019cbe5fb162 Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Mon, 2 Jul 2018 15:49:47 +0100 Subject: [PATCH 116/140] Connect ff u128-support to pairing u128-support --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dcf7c32..c11d2a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,5 +20,5 @@ ff = "0.3" [features] unstable-features = ["expose-arith"] expose-arith = [] -u128-support = [] +u128-support = ["ff/u128-support"] default = [] From bb22a167afed01195824c290be668b1450ce04e2 Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Mon, 2 Jul 2018 15:51:32 +0100 Subject: [PATCH 117/140] Update authors --- Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c11d2a9..9fe707f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,10 @@ name = "pairing" # Remember to change version string in README.md. version = "0.14.2" -authors = ["Sean Bowe <ewillbefull@gmail.com>"] +authors = [ + "Sean Bowe <ewillbefull@gmail.com>", + "Jack Grigg <jack@z.cash>", +] license = "MIT/Apache-2.0" description = "Pairing-friendly elliptic curve library" From c49590bab72161706343e584d839a55e8913f799 Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Mon, 2 Jul 2018 16:04:52 +0100 Subject: [PATCH 118/140] Change all remaining uses of *Field to reference ff crate --- src/bls12_381/fq.rs | 2 +- src/bls12_381/fq12.rs | 4 ++-- src/bls12_381/fq2.rs | 22 +++++++++++----------- src/bls12_381/fq6.rs | 4 ++-- src/tests/curve.rs | 5 +++-- src/tests/repr.rs | 2 +- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 2b4b7a6..2661aa5 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,5 +1,5 @@ use super::fq2::Fq2; -use {Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; +use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; // B coefficient of BLS12-381 curve, 4. pub const B_COEFF: Fq = Fq(FqRepr([ diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index 2bec0b1..b24fcaa 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -1,8 +1,8 @@ use super::fq::FROBENIUS_COEFF_FQ12_C1; use super::fq2::Fq2; use super::fq6::Fq6; +use ff::Field; use rand::{Rand, Rng}; -use Field; /// An element of Fq12, represented by c0 + c1 * w. #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -182,7 +182,7 @@ fn test_fq12_mul_by_014() { #[test] fn fq12_field_tests() { - use PrimeField; + use ff::PrimeField; ::tests::field::random_field_tests::<Fq12>(); ::tests::field::random_frobenius_tests::<Fq12, _>(super::fq::Fq::char(), 13); diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 1f3cd6f..7ae159f 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -1,6 +1,6 @@ use super::fq::{FROBENIUS_COEFF_FQ2_C1, Fq, NEGATIVE_ONE}; +use ff::{Field, SqrtField}; use rand::{Rand, Rng}; -use {Field, SqrtField}; use std::cmp::Ordering; @@ -272,7 +272,7 @@ fn test_fq2_basics() { #[test] fn test_fq2_squaring() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; let mut a = Fq2 { c0: Fq::one(), @@ -346,7 +346,7 @@ fn test_fq2_squaring() { #[test] fn test_fq2_mul() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -410,7 +410,7 @@ fn test_fq2_mul() { #[test] fn test_fq2_inverse() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; assert!(Fq2::zero().inverse().is_none()); @@ -459,7 +459,7 @@ fn test_fq2_inverse() { #[test] fn test_fq2_addition() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -523,7 +523,7 @@ fn test_fq2_addition() { #[test] fn test_fq2_subtraction() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -587,7 +587,7 @@ fn test_fq2_subtraction() { #[test] fn test_fq2_negation() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -634,7 +634,7 @@ fn test_fq2_negation() { #[test] fn test_fq2_doubling() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -681,7 +681,7 @@ fn test_fq2_doubling() { #[test] fn test_fq2_frobenius_map() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; let mut a = Fq2 { c0: Fq::from_repr(FqRepr([ @@ -794,7 +794,7 @@ fn test_fq2_frobenius_map() { #[test] fn test_fq2_sqrt() { use super::fq::FqRepr; - use PrimeField; + use ff::PrimeField; assert_eq!( Fq2 { @@ -900,7 +900,7 @@ fn test_fq2_mul_nonresidue() { #[test] fn fq2_field_tests() { - use PrimeField; + use ff::PrimeField; ::tests::field::random_field_tests::<Fq2>(); ::tests::field::random_sqrt_tests::<Fq2>(); diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs index c065f27..36c6e28 100644 --- a/src/bls12_381/fq6.rs +++ b/src/bls12_381/fq6.rs @@ -1,7 +1,7 @@ use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; use super::fq2::Fq2; +use ff::Field; use rand::{Rand, Rng}; -use Field; /// An element of Fq6, represented by c0 + c1 * v + c2 * v^(2). #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -367,7 +367,7 @@ fn test_fq6_mul_by_01() { #[test] fn fq6_field_tests() { - use PrimeField; + use ff::PrimeField; ::tests::field::random_field_tests::<Fq6>(); ::tests::field::random_frobenius_tests::<Fq6, _>(super::fq::Fq::char(), 13); diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 1480b74..bb0406c 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -1,6 +1,7 @@ +use ff::Field; use rand::{Rand, Rng, SeedableRng, XorShiftRng}; -use {CurveAffine, CurveProjective, EncodedPoint, Field}; +use {CurveAffine, CurveProjective, EncodedPoint}; pub fn curve_tests<G: CurveProjective>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -66,8 +67,8 @@ pub fn curve_tests<G: CurveProjective>() { } fn random_wnaf_tests<G: CurveProjective>() { + use ff::PrimeField; use wnaf::*; - use PrimeField; let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/tests/repr.rs b/src/tests/repr.rs index 681a476..09dd441 100644 --- a/src/tests/repr.rs +++ b/src/tests/repr.rs @@ -1,5 +1,5 @@ +use ff::PrimeFieldRepr; use rand::{SeedableRng, XorShiftRng}; -use PrimeFieldRepr; pub fn random_repr_tests<R: PrimeFieldRepr>() { random_encoding_tests::<R>(); From 06a152734c3bc0b36df18d2a3d531b507199ab72 Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Mon, 2 Jul 2018 18:41:55 +0100 Subject: [PATCH 119/140] Add missing SqrtField import to benches --- benches/bls12_381/fr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/bls12_381/fr.rs b/benches/bls12_381/fr.rs index 9cab671..13b0d0e 100644 --- a/benches/bls12_381/fr.rs +++ b/benches/bls12_381/fr.rs @@ -1,6 +1,6 @@ use rand::{Rand, SeedableRng, XorShiftRng}; -use ff::{Field, PrimeField, PrimeFieldRepr}; +use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; use pairing::bls12_381::*; #[bench] From cc5b83510277632852af67d896a27e0cb40f342b Mon Sep 17 00:00:00 2001 From: Sean Bowe <ewillbefull@gmail.com> Date: Wed, 4 Jul 2018 12:45:08 -0600 Subject: [PATCH 120/140] Start using cargo-clippy for CI. --- src/lib.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fefdae3..c3640c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,14 @@ // `clippy` is a code linting tool for improving code quality by catching -// common mistakes or strange code patterns. If the `clippy` feature is -// provided, it is enabled and all compiler warnings are prohibited. -#![cfg_attr(feature = "clippy", deny(warnings))] -#![cfg_attr(feature = "clippy", feature(plugin))] -#![cfg_attr(feature = "clippy", plugin(clippy))] -#![cfg_attr(feature = "clippy", allow(inline_always))] -#![cfg_attr(feature = "clippy", allow(too_many_arguments))] -#![cfg_attr(feature = "clippy", allow(unreadable_literal))] -#![cfg_attr(feature = "clippy", allow(many_single_char_names))] -#![cfg_attr(feature = "clippy", allow(new_without_default_derive))] -#![cfg_attr(feature = "clippy", allow(write_literal))] +// common mistakes or strange code patterns. If the `cargo-clippy` feature +// is provided, all compiler warnings are prohibited. +#![cfg_attr(feature = "cargo-clippy", deny(warnings))] +#![cfg_attr(feature = "cargo-clippy", allow(inline_always))] +#![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] +#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))] +#![cfg_attr(feature = "cargo-clippy", allow(many_single_char_names))] +#![cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] +#![cfg_attr(feature = "cargo-clippy", allow(write_literal))] + // Force public structures to implement Debug #![deny(missing_debug_implementations)] From 4752a9178154227da20cf230af0159394d80016f Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Mon, 2 Jul 2018 19:27:11 +0100 Subject: [PATCH 121/140] Remove clippy from dependencies. --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9fe707f..f170c84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.4" byteorder = "1" -clippy = { version = "0.0.200", optional = true } ff = "0.3" [features] From c5b883f91ee02db6bef9e225916905f520abdf6c Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Tue, 3 Jul 2018 09:05:12 +0100 Subject: [PATCH 122/140] Migrate to ff 0.4 --- Cargo.toml | 3 +-- README.md | 8 -------- src/bls12_381/mod.rs | 7 +++++-- src/lib.rs | 7 ++----- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f170c84..5f16018 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,9 @@ repository = "https://github.com/ebfull/pairing" [dependencies] rand = "0.4" byteorder = "1" -ff = "0.3" +ff = { version = "0.4", features = ["derive"] } [features] unstable-features = ["expose-arith"] expose-arith = [] -u128-support = ["ff/u128-support"] default = [] diff --git a/README.md b/README.md index d71d0c5..bf386de 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,6 @@ This is a Rust crate for using pairing-friendly elliptic curves. Currently, only Bring the `pairing` crate into your project just as you normally would. -If you're using a supported platform and the nightly Rust compiler, you can enable the `u128-support` feature for faster arithmetic. - -```toml -[dependencies.pairing] -version = "0.14" -features = ["u128-support"] -``` - ## Security Warnings This library does not make any guarantees about constant-time operations, memory access patterns, or resistance to side-channel attacks. diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index c6c13c5..106591e 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -20,7 +20,7 @@ pub use self::fr::{Fr, FrRepr}; use super::{CurveAffine, Engine}; -use ff::{BitIterator, Field}; +use ff::{BitIterator, Field, ScalarEngine}; // The BLS parameter x for BLS12-381 is -0xd201000000010000 const BLS_X: u64 = 0xd201000000010000; @@ -29,8 +29,11 @@ const BLS_X_IS_NEGATIVE: bool = true; #[derive(Clone, Debug)] pub struct Bls12; -impl Engine for Bls12 { +impl ScalarEngine for Bls12 { type Fr = Fr; +} + +impl Engine for Bls12 { type G1 = G1; type G1Affine = G1Affine; type G2 = G2; diff --git a/src/lib.rs b/src/lib.rs index c3640c4..75af6e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,17 +25,14 @@ pub mod bls12_381; mod wnaf; pub use self::wnaf::Wnaf; -use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, SqrtField}; +use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr, ScalarEngine, SqrtField}; use std::error::Error; use std::fmt; /// An "engine" is a collection of types (fields, elliptic curve groups, etc.) /// with well-defined relationships. In particular, the G1/G2 curve groups are /// of prime order `r`, and are equipped with a bilinear pairing function. -pub trait Engine: Sized + 'static + Clone { - /// This is the scalar field of the G1/G2 groups. - type Fr: PrimeField + SqrtField; - +pub trait Engine: ScalarEngine { /// The projective representation of an element in G1. type G1: CurveProjective< Engine = Self, From fa8103764a07bd273927447d434de18aace252d3 Mon Sep 17 00:00:00 2001 From: Jack Grigg <jack@z.cash> Date: Tue, 3 Jul 2018 09:08:48 +0100 Subject: [PATCH 123/140] cargo fmt --- src/bls12_381/fq.rs | 18 ++++++++++-------- src/bls12_381/fq2.rs | 10 ++++++---- src/bls12_381/fr.rs | 14 ++++++++------ src/lib.rs | 1 - 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index 2661aa5..fd0d416 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1584,14 +1584,16 @@ fn test_fq_is_valid() { 0x17c8be1800b9f059 ])).is_valid() ); - assert!(!Fq(FqRepr([ - 0xffffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff - ])).is_valid()); + assert!( + !Fq(FqRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff + ])).is_valid() + ); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 7ae159f..363439a 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -263,10 +263,12 @@ fn test_fq2_basics() { ); assert!(Fq2::zero().is_zero()); assert!(!Fq2::one().is_zero()); - assert!(!Fq2 { - c0: Fq::zero(), - c1: Fq::one(), - }.is_zero()); + assert!( + !Fq2 { + c0: Fq::zero(), + c1: Fq::one(), + }.is_zero() + ); } #[test] diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 6e142f0..5e57631 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -388,12 +388,14 @@ fn test_fr_is_valid() { 0x73eda753299d7d48 ])).is_valid() ); - assert!(!Fr(FrRepr([ - 0xffffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff - ])).is_valid()); + assert!( + !Fr(FrRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff + ])).is_valid() + ); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/lib.rs b/src/lib.rs index 75af6e2..bbced76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ #![cfg_attr(feature = "cargo-clippy", allow(many_single_char_names))] #![cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] #![cfg_attr(feature = "cargo-clippy", allow(write_literal))] - // Force public structures to implement Debug #![deny(missing_debug_implementations)] From 1363d02170f1d98f1b9c8eec0e3fc6b1eea4ef9a Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Sat, 8 Dec 2018 05:36:39 +0800 Subject: [PATCH 124/140] BN256 commit --- .gitignore | 1 + Cargo.toml | 7 +- README.md | 6 +- benches/bn256/ec.rs | 127 +++ benches/bn256/fq.rs | 268 ++++++ benches/bn256/fq12.rs | 94 +++ benches/bn256/fq2.rs | 110 +++ benches/bn256/fr.rs | 268 ++++++ benches/bn256/mod.rs | 107 +++ benches/pairing_benches.rs | 1 + src/bls12_381/ec.rs | 2 + src/bn256/README.md | 14 + src/bn256/ec.rs | 1598 ++++++++++++++++++++++++++++++++++++ src/bn256/fq.rs | 579 +++++++++++++ src/bn256/fq12.rs | 221 +++++ src/bn256/fq2.rs | 966 ++++++++++++++++++++++ src/bn256/fq6.rs | 400 +++++++++ src/bn256/fr.rs | 11 + src/bn256/mod.rs | 599 ++++++++++++++ src/lib.rs | 14 +- src/tests/curve.rs | 43 +- 21 files changed, 5422 insertions(+), 14 deletions(-) create mode 100644 benches/bn256/ec.rs create mode 100644 benches/bn256/fq.rs create mode 100644 benches/bn256/fq12.rs create mode 100644 benches/bn256/fq2.rs create mode 100644 benches/bn256/fr.rs create mode 100644 benches/bn256/mod.rs create mode 100644 src/bn256/README.md create mode 100644 src/bn256/ec.rs create mode 100644 src/bn256/fq.rs create mode 100644 src/bn256/fq12.rs create mode 100644 src/bn256/fq2.rs create mode 100644 src/bn256/fq6.rs create mode 100644 src/bn256/fr.rs create mode 100644 src/bn256/mod.rs diff --git a/.gitignore b/.gitignore index 4308d82..750c824 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ **/*.rs.bk Cargo.lock +.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 5f16018..8d3e885 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,17 +2,18 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.14.2" +version = "0.15.0" authors = [ "Sean Bowe <ewillbefull@gmail.com>", "Jack Grigg <jack@z.cash>", + "Alex Vlasov <alex.m.vlasov@gmail.com>" ] license = "MIT/Apache-2.0" description = "Pairing-friendly elliptic curve library" documentation = "https://docs.rs/pairing/" -homepage = "https://github.com/ebfull/pairing" -repository = "https://github.com/ebfull/pairing" +homepage = "https://github.com/matterinc/pairing" +repository = "https://github.com/matterinc/pairing" [dependencies] rand = "0.4" diff --git a/README.md b/README.md index bf386de..3031b61 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# pairing [![Crates.io](https://img.shields.io/crates/v/pairing.svg)](https://crates.io/crates/pairing) # +# pairing "community edition" -This is a Rust crate for using pairing-friendly elliptic curves. Currently, only the [BLS12-381](https://z.cash/blog/new-snark-curve.html) construction is implemented. +Originally developed by ZCash, with extensions from us to make it a little more pleasant. + +This is a Rust crate for using pairing-friendly elliptic curves. Currently, only the [BLS12-381](https://z.cash/blog/new-snark-curve.html) and BN256 curves are implemented. ## [Documentation](https://docs.rs/pairing/) diff --git a/benches/bn256/ec.rs b/benches/bn256/ec.rs new file mode 100644 index 0000000..b188f4c --- /dev/null +++ b/benches/bn256/ec.rs @@ -0,0 +1,127 @@ +mod g1 { + use rand::{Rand, SeedableRng, XorShiftRng}; + + use pairing::bn256::*; + use pairing::CurveProjective; + + #[bench] + fn bench_g1_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, Fr)> = (0..SAMPLES) + .map(|_| (G1::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g1_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G1)> = (0..SAMPLES) + .map(|_| (G1::rand(&mut rng), G1::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g1_add_assign_mixed(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G1Affine)> = (0..SAMPLES) + .map(|_| (G1::rand(&mut rng), G1::rand(&mut rng).into())) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign_mixed(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } +} + +mod g2 { + use rand::{Rand, SeedableRng, XorShiftRng}; + + use pairing::bls12_381::*; + use pairing::CurveProjective; + + #[bench] + fn bench_g2_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, Fr)> = (0..SAMPLES) + .map(|_| (G2::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g2_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, G2)> = (0..SAMPLES) + .map(|_| (G2::rand(&mut rng), G2::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } + + #[bench] + fn bench_g2_add_assign_mixed(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G2, G2Affine)> = (0..SAMPLES) + .map(|_| (G2::rand(&mut rng), G2::rand(&mut rng).into())) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign_mixed(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); + } +} diff --git a/benches/bn256/fq.rs b/benches/bn256/fq.rs new file mode 100644 index 0000000..85345b3 --- /dev/null +++ b/benches/bn256/fq.rs @@ -0,0 +1,268 @@ +use rand::{Rand, SeedableRng, XorShiftRng}; + +use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; +use pairing::bn256::*; + +#[bench] +fn bench_fq_repr_add_nocarry(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES) + .map(|_| { + let mut tmp1 = FqRepr::rand(&mut rng); + let mut tmp2 = FqRepr::rand(&mut rng); + // Shave a few bits off to avoid overflow. + for _ in 0..3 { + tmp1.div2(); + tmp2.div2(); + } + (tmp1, tmp2) + }) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_nocarry(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_sub_noborrow(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FqRepr, FqRepr)> = (0..SAMPLES) + .map(|_| { + let tmp1 = FqRepr::rand(&mut rng); + let mut tmp2 = tmp1; + // Ensure tmp2 is smaller than tmp1. + for _ in 0..10 { + tmp2.div2(); + } + (tmp1, tmp2) + }) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_noborrow(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_num_bits(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].num_bits(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_mul2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.mul2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_repr_div2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FqRepr> = (0..SAMPLES).map(|_| FqRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.div2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES) + .map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES) + .map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq, Fq)> = (0..SAMPLES) + .map(|_| (Fq::rand(&mut rng), Fq::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_square(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].inverse() + }); +} + +#[bench] +fn bench_fq_negate(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.negate(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES) + .map(|_| { + let mut tmp = Fq::rand(&mut rng); + tmp.square(); + tmp + }) + .collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].sqrt() + }); +} + +#[bench] +fn bench_fq_into_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq> = (0..SAMPLES).map(|_| Fq::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].into_repr() + }); +} + +#[bench] +fn bench_fq_from_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FqRepr> = (0..SAMPLES) + .map(|_| Fq::rand(&mut rng).into_repr()) + .collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + Fq::from_repr(v[count]) + }); +} diff --git a/benches/bn256/fq12.rs b/benches/bn256/fq12.rs new file mode 100644 index 0000000..42fca9d --- /dev/null +++ b/benches/bn256/fq12.rs @@ -0,0 +1,94 @@ +use rand::{Rand, SeedableRng, XorShiftRng}; + +use ff::Field; +use pairing::bn256::*; + +#[bench] +fn bench_fq12_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES) + .map(|_| (Fq12::rand(&mut rng), Fq12::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES) + .map(|_| (Fq12::rand(&mut rng), Fq12::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq12, Fq12)> = (0..SAMPLES) + .map(|_| (Fq12::rand(&mut rng), Fq12::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_squaring(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq12> = (0..SAMPLES).map(|_| Fq12::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq12_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq12> = (0..SAMPLES).map(|_| Fq12::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].inverse(); + count = (count + 1) % SAMPLES; + tmp + }); +} diff --git a/benches/bn256/fq2.rs b/benches/bn256/fq2.rs new file mode 100644 index 0000000..ee592ca --- /dev/null +++ b/benches/bn256/fq2.rs @@ -0,0 +1,110 @@ +use rand::{Rand, SeedableRng, XorShiftRng}; + +use ff::{Field, SqrtField}; +use pairing::bn256::*; + +#[bench] +fn bench_fq2_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES) + .map(|_| (Fq2::rand(&mut rng), Fq2::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES) + .map(|_| (Fq2::rand(&mut rng), Fq2::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fq2, Fq2)> = (0..SAMPLES) + .map(|_| (Fq2::rand(&mut rng), Fq2::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_squaring(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq2> = (0..SAMPLES).map(|_| Fq2::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq2> = (0..SAMPLES).map(|_| Fq2::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].inverse(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fq2_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq2> = (0..SAMPLES).map(|_| Fq2::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].sqrt(); + count = (count + 1) % SAMPLES; + tmp + }); +} diff --git a/benches/bn256/fr.rs b/benches/bn256/fr.rs new file mode 100644 index 0000000..62f0fe7 --- /dev/null +++ b/benches/bn256/fr.rs @@ -0,0 +1,268 @@ +use rand::{Rand, SeedableRng, XorShiftRng}; + +use ff::{Field, PrimeField, PrimeFieldRepr, SqrtField}; +use pairing::bn256::*; + +#[bench] +fn bench_fr_repr_add_nocarry(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES) + .map(|_| { + let mut tmp1 = FrRepr::rand(&mut rng); + let mut tmp2 = FrRepr::rand(&mut rng); + // Shave a few bits off to avoid overflow. + for _ in 0..3 { + tmp1.div2(); + tmp2.div2(); + } + (tmp1, tmp2) + }) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_nocarry(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_sub_noborrow(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(FrRepr, FrRepr)> = (0..SAMPLES) + .map(|_| { + let tmp1 = FrRepr::rand(&mut rng); + let mut tmp2 = tmp1; + // Ensure tmp2 is smaller than tmp1. + for _ in 0..10 { + tmp2.div2(); + } + (tmp1, tmp2) + }) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_noborrow(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_num_bits(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = v[count].num_bits(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_mul2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.mul2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_repr_div2(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FrRepr> = (0..SAMPLES).map(|_| FrRepr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.div2(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_add_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES) + .map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.add_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_sub_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES) + .map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.sub_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_mul_assign(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(Fr, Fr)> = (0..SAMPLES) + .map(|_| (Fr::rand(&mut rng), Fr::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count].0; + tmp.mul_assign(&v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_square(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.square(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_inverse(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].inverse() + }); +} + +#[bench] +fn bench_fr_negate(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let mut tmp = v[count]; + tmp.negate(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_fr_sqrt(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES) + .map(|_| { + let mut tmp = Fr::rand(&mut rng); + tmp.square(); + tmp + }) + .collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].sqrt() + }); +} + +#[bench] +fn bench_fr_into_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fr> = (0..SAMPLES).map(|_| Fr::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + v[count].into_repr() + }); +} + +#[bench] +fn bench_fr_from_repr(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<FrRepr> = (0..SAMPLES) + .map(|_| Fr::rand(&mut rng).into_repr()) + .collect(); + + let mut count = 0; + b.iter(|| { + count = (count + 1) % SAMPLES; + Fr::from_repr(v[count]) + }); +} diff --git a/benches/bn256/mod.rs b/benches/bn256/mod.rs new file mode 100644 index 0000000..3559cca --- /dev/null +++ b/benches/bn256/mod.rs @@ -0,0 +1,107 @@ +mod ec; +mod fq; +mod fq12; +mod fq2; +mod fr; + +use rand::{Rand, SeedableRng, XorShiftRng}; + +use pairing::bn256::*; +use pairing::{CurveAffine, Engine}; + +#[bench] +fn bench_pairing_g1_preparation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<G1> = (0..SAMPLES).map(|_| G1::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = G1Affine::from(v[count]).prepare(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_g2_preparation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<G2> = (0..SAMPLES).map(|_| G2::rand(&mut rng)).collect(); + + let mut count = 0; + b.iter(|| { + let tmp = G2Affine::from(v[count]).prepare(); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_miller_loop(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1Prepared, G2Prepared)> = (0..SAMPLES) + .map(|_| { + ( + G1Affine::from(G1::rand(&mut rng)).prepare(), + G2Affine::from(G2::rand(&mut rng)).prepare(), + ) + }) + .collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bn256::miller_loop(&[(&v[count].0, &v[count].1)]); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_final_exponentiation(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<Fq12> = (0..SAMPLES) + .map(|_| { + ( + G1Affine::from(G1::rand(&mut rng)).prepare(), + G2Affine::from(G2::rand(&mut rng)).prepare(), + ) + }) + .map(|(ref p, ref q)| Bn256::miller_loop(&[(p, q)])) + .collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bn256::final_exponentiation(&v[count]); + count = (count + 1) % SAMPLES; + tmp + }); +} + +#[bench] +fn bench_pairing_full(b: &mut ::test::Bencher) { + const SAMPLES: usize = 1000; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let v: Vec<(G1, G2)> = (0..SAMPLES) + .map(|_| (G1::rand(&mut rng), G2::rand(&mut rng))) + .collect(); + + let mut count = 0; + b.iter(|| { + let tmp = Bn256::pairing(v[count].0, v[count].1); + count = (count + 1) % SAMPLES; + tmp + }); +} diff --git a/benches/pairing_benches.rs b/benches/pairing_benches.rs index af32a8a..ddfd04d 100644 --- a/benches/pairing_benches.rs +++ b/benches/pairing_benches.rs @@ -6,3 +6,4 @@ extern crate rand; extern crate test; mod bls12_381; +mod bn256; diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 37fcbba..5c0545f 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -1262,6 +1262,7 @@ pub mod g1 { #[test] fn g1_curve_tests() { ::tests::curve::curve_tests::<G1>(); + ::tests::curve::random_transformation_tests_with_cofactor::<G1>(); } } @@ -2015,6 +2016,7 @@ pub mod g2 { #[test] fn g2_curve_tests() { ::tests::curve::curve_tests::<G2>(); + ::tests::curve::random_transformation_tests_with_cofactor::<G2>(); } } diff --git a/src/bn256/README.md b/src/bn256/README.md new file mode 100644 index 0000000..8d6b211 --- /dev/null +++ b/src/bn256/README.md @@ -0,0 +1,14 @@ +# BN256 + +This is an implementation of the BN256 pairing-friendly elliptic curve construction. + +## BN256 Parameterization + +Follows go-ethereum parametrization. + +## Notes + +- I couldn't find an easy wat of getting random G2 for BN256 curve (also have no idea why just scaling by cofactor works for BLS12), so don't use it. Make random sccalar and multiply by generator. +- For this reason tests had to be copied and modified for some cases. + + diff --git a/src/bn256/ec.rs b/src/bn256/ec.rs new file mode 100644 index 0000000..13342d5 --- /dev/null +++ b/src/bn256/ec.rs @@ -0,0 +1,1598 @@ +macro_rules! curve_impl { + ( + $name:expr, + $projective:ident, + $affine:ident, + $prepared:ident, + $basefield:ident, + $scalarfield:ident, + $uncompressed:ident, + $compressed:ident, + $pairing:ident + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + pub struct $affine { + pub(crate) x: $basefield, + pub(crate) y: $basefield, + pub(crate) infinity: bool + } + + impl ::std::fmt::Display for $affine + { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + if self.infinity { + write!(f, "{}(Infinity)", $name) + } else { + write!(f, "{}(x={}, y={})", $name, self.x, self.y) + } + } + } + + #[derive(Copy, Clone, Debug, Eq)] + pub struct $projective { + pub(crate) x: $basefield, + pub(crate) y: $basefield, + pub(crate) z: $basefield + } + + impl ::std::fmt::Display for $projective + { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self.into_affine()) + } + } + + impl PartialEq for $projective { + fn eq(&self, other: &$projective) -> bool { + if self.is_zero() { + return other.is_zero(); + } + + if other.is_zero() { + return false; + } + + // The points (X, Y, Z) and (X', Y', Z') + // are equal when (X * Z^2) = (X' * Z'^2) + // and (Y * Z^3) = (Y' * Z'^3). + + let mut z1 = self.z; + z1.square(); + let mut z2 = other.z; + z2.square(); + + let mut tmp1 = self.x; + tmp1.mul_assign(&z2); + + let mut tmp2 = other.x; + tmp2.mul_assign(&z1); + + if tmp1 != tmp2 { + return false; + } + + z1.mul_assign(&self.z); + z2.mul_assign(&other.z); + z2.mul_assign(&self.y); + z1.mul_assign(&other.y); + + if z1 != z2 { + return false; + } + + true + } + } + + impl $affine { + fn mul_bits<S: AsRef<[u64]>>(&self, bits: BitIterator<S>) -> $projective { + let mut res = $projective::zero(); + for i in bits { + res.double(); + if i { res.add_assign_mixed(self) } + } + res + } + + /// Attempts to construct an affine point given an x-coordinate. The + /// point is not guaranteed to be in the prime order subgroup. + /// + /// If and only if `greatest` is set will the lexicographically + /// largest y-coordinate be selected. + fn get_point_from_x(x: $basefield, greatest: bool) -> Option<$affine> { + // Compute x^3 + b + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&$affine::get_coeff_b()); + + x3b.sqrt().map(|y| { + let mut negy = y; + negy.negate(); + + $affine { + x: x, + y: if (y < negy) ^ greatest { + y + } else { + negy + }, + infinity: false + } + }) + } + + fn is_on_curve(&self) -> bool { + if self.is_zero() { + true + } else { + // Check that the point is on the curve + let mut y2 = self.y; + y2.square(); + + let mut x3b = self.x; + x3b.square(); + x3b.mul_assign(&self.x); + x3b.add_assign(&Self::get_coeff_b()); + + y2 == x3b + } + } + + } + + impl CurveAffine for $affine { + type Engine = Bn256; + type Scalar = $scalarfield; + type Base = $basefield; + type Prepared = $prepared; + type Projective = $projective; + type Uncompressed = $uncompressed; + type Compressed = $compressed; + type Pair = $pairing; + type PairingResult = Fq12; + + fn zero() -> Self { + $affine { + x: $basefield::zero(), + y: $basefield::one(), + infinity: true + } + } + + fn one() -> Self { + Self::get_generator() + } + + fn is_zero(&self) -> bool { + self.infinity + } + + fn mul<S: Into<<Self::Scalar as PrimeField>::Repr>>(&self, by: S) -> $projective { + let bits = BitIterator::new(by.into()); + self.mul_bits(bits) + } + + fn negate(&mut self) { + if !self.is_zero() { + self.y.negate(); + } + } + + fn prepare(&self) -> Self::Prepared { + $prepared::from_affine(*self) + } + + fn pairing_with(&self, other: &Self::Pair) -> Self::PairingResult { + self.perform_pairing(other) + } + + fn into_projective(&self) -> $projective { + (*self).into() + } + + } + + // impl Rand for $projective { + // fn rand<R: Rng>(rng: &mut R) -> Self { + // loop { + // let x = rng.gen(); + // let greatest = rng.gen(); + + // if let Some(p) = $affine::get_point_from_x(x, greatest) { + // if !p.is_zero() { + // // let mut q = p.into_projective(); + // // q.mul_assign($scalarfield::char()); && q.is_zero() + // if p.is_on_curve() { + // return p.into_projective(); + // } + // } + // } + // } + // } + // } + + impl CurveProjective for $projective { + type Engine = Bn256; + type Scalar = $scalarfield; + type Base = $basefield; + type Affine = $affine; + + // The point at infinity is always represented by + // Z = 0. + fn zero() -> Self { + $projective { + x: $basefield::zero(), + y: $basefield::one(), + z: $basefield::zero() + } + } + + fn one() -> Self { + $affine::one().into() + } + + // The point at infinity is always represented by + // Z = 0. + fn is_zero(&self) -> bool { + self.z.is_zero() + } + + fn is_normalized(&self) -> bool { + self.is_zero() || self.z == $basefield::one() + } + + fn batch_normalization(v: &mut [Self]) + { + // Montgomery’s Trick and Fast Implementation of Masked AES + // Genelle, Prouff and Quisquater + // Section 3.2 + + // First pass: compute [a, ab, abc, ...] + let mut prod = Vec::with_capacity(v.len()); + let mut tmp = $basefield::one(); + for g in v.iter_mut() + // Ignore normalized elements + .filter(|g| !g.is_normalized()) + { + tmp.mul_assign(&g.z); + prod.push(tmp); + } + + // Invert `tmp`. + tmp = tmp.inverse().unwrap(); // Guaranteed to be nonzero. + + // Second pass: iterate backwards to compute inverses + for (g, s) in v.iter_mut() + // Backwards + .rev() + // Ignore normalized elements + .filter(|g| !g.is_normalized()) + // Backwards, skip last element, fill in one for last term. + .zip(prod.into_iter().rev().skip(1).chain(Some($basefield::one()))) + { + // tmp := tmp * g.z; g.z := tmp * s = 1/z + let mut newtmp = tmp; + newtmp.mul_assign(&g.z); + g.z = tmp; + g.z.mul_assign(&s); + tmp = newtmp; + } + + // Perform affine transformations + for g in v.iter_mut() + .filter(|g| !g.is_normalized()) + { + let mut z = g.z; // 1/z + z.square(); // 1/z^2 + g.x.mul_assign(&z); // x/z^2 + z.mul_assign(&g.z); // 1/z^3 + g.y.mul_assign(&z); // y/z^3 + g.z = $basefield::one(); // z = 1 + } + } + + fn double(&mut self) { + if self.is_zero() { + return; + } + + // Other than the point at infinity, no points on E or E' + // can double to equal the point at infinity, as y=0 is + // never true for points on the curve. + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + + // A = X1^2 + let mut a = self.x; + a.square(); + + // B = Y1^2 + let mut b = self.y; + b.square(); + + // C = B^2 + let mut c = b; + c.square(); + + // D = 2*((X1+B)2-A-C) + let mut d = self.x; + d.add_assign(&b); + d.square(); + d.sub_assign(&a); + d.sub_assign(&c); + d.double(); + + // E = 3*A + let mut e = a; + e.double(); + e.add_assign(&a); + + // F = E^2 + let mut f = e; + f.square(); + + // Z3 = 2*Y1*Z1 + self.z.mul_assign(&self.y); + self.z.double(); + + // X3 = F-2*D + self.x = f; + self.x.sub_assign(&d); + self.x.sub_assign(&d); + + // Y3 = E*(D-X3)-8*C + self.y = d; + self.y.sub_assign(&self.x); + self.y.mul_assign(&e); + c.double(); + c.double(); + c.double(); + self.y.sub_assign(&c); + } + + fn add_assign(&mut self, other: &Self) { + if self.is_zero() { + *self = *other; + return; + } + + if other.is_zero() { + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl + + // Z1Z1 = Z1^2 + let mut z1z1 = self.z; + z1z1.square(); + + // Z2Z2 = Z2^2 + let mut z2z2 = other.z; + z2z2.square(); + + // U1 = X1*Z2Z2 + let mut u1 = self.x; + u1.mul_assign(&z2z2); + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2.mul_assign(&z1z1); + + // S1 = Y1*Z2*Z2Z2 + let mut s1 = self.y; + s1.mul_assign(&other.z); + s1.mul_assign(&z2z2); + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2.mul_assign(&self.z); + s2.mul_assign(&z1z1); + + if u1 == u2 && s1 == s2 { + // The two points are equal, so we double. + self.double(); + } else { + // If we're adding -a and a together, self.z becomes zero as H becomes zero. + + if u1 == u2 { + // The two points are equal, so we double. + (*self) = Self::zero(); + return; + } + + // H = U2-U1 + let mut h = u2; + h.sub_assign(&u1); + + // I = (2*H)^2 + let mut i = h; + i.double(); + i.square(); + + // J = H*I + let mut j = h; + j.mul_assign(&i); + + // r = 2*(S2-S1) + let mut r = s2; + r.sub_assign(&s1); + r.double(); + + // V = U1*I + let mut v = u1; + v.mul_assign(&i); + + // X3 = r^2 - J - 2*V + self.x = r; + self.x.square(); + self.x.sub_assign(&j); + self.x.sub_assign(&v); + self.x.sub_assign(&v); + + // Y3 = r*(V - X3) - 2*S1*J + self.y = v; + self.y.sub_assign(&self.x); + self.y.mul_assign(&r); + s1.mul_assign(&j); // S1 = S1 * J * 2 + s1.double(); + self.y.sub_assign(&s1); + + // Z3 = ((Z1+Z2)^2 - Z1Z1 - Z2Z2)*H + self.z.add_assign(&other.z); + self.z.square(); + self.z.sub_assign(&z1z1); + self.z.sub_assign(&z2z2); + self.z.mul_assign(&h); + } + } + + fn add_assign_mixed(&mut self, other: &Self::Affine) { + if other.is_zero() { + return; + } + + if self.is_zero() { + self.x = other.x; + self.y = other.y; + self.z = $basefield::one(); + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-madd-2007-bl + + // Z1Z1 = Z1^2 + let mut z1z1 = self.z; + z1z1.square(); + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2.mul_assign(&z1z1); + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2.mul_assign(&self.z); + s2.mul_assign(&z1z1); + + if self.x == u2 && self.y == s2 { + // The two points are equal, so we double. + self.double(); + } else { + // If we're adding -a and a together, self.z becomes zero as H becomes zero. + + // H = U2-X1 + let mut h = u2; + h.sub_assign(&self.x); + + // HH = H^2 + let mut hh = h; + hh.square(); + + // I = 4*HH + let mut i = hh; + i.double(); + i.double(); + + // J = H*I + let mut j = h; + j.mul_assign(&i); + + // r = 2*(S2-Y1) + let mut r = s2; + r.sub_assign(&self.y); + r.double(); + + // V = X1*I + let mut v = self.x; + v.mul_assign(&i); + + // X3 = r^2 - J - 2*V + self.x = r; + self.x.square(); + self.x.sub_assign(&j); + self.x.sub_assign(&v); + self.x.sub_assign(&v); + + // Y3 = r*(V-X3)-2*Y1*J + j.mul_assign(&self.y); // J = 2*Y1*J + j.double(); + self.y = v; + self.y.sub_assign(&self.x); + self.y.mul_assign(&r); + self.y.sub_assign(&j); + + // Z3 = (Z1+H)^2-Z1Z1-HH + self.z.add_assign(&h); + self.z.square(); + self.z.sub_assign(&z1z1); + self.z.sub_assign(&hh); + } + } + + fn negate(&mut self) { + if !self.is_zero() { + self.y.negate() + } + } + + fn mul_assign<S: Into<<Self::Scalar as PrimeField>::Repr>>(&mut self, other: S) { + let mut res = Self::zero(); + + let mut found_one = false; + + for i in BitIterator::new(other.into()) + { + if found_one { + res.double(); + } else { + found_one = i; + } + + if i { + res.add_assign(self); + } + } + + *self = res; + } + + fn into_affine(&self) -> $affine { + (*self).into() + } + + fn recommended_wnaf_for_scalar(scalar: <Self::Scalar as PrimeField>::Repr) -> usize { + Self::empirical_recommended_wnaf_for_scalar(scalar) + } + + fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + Self::empirical_recommended_wnaf_for_num_scalars(num_scalars) + } + } + + // The affine point X, Y is represented in the jacobian + // coordinates with Z = 1. + impl From<$affine> for $projective { + fn from(p: $affine) -> $projective { + if p.is_zero() { + $projective::zero() + } else { + $projective { + x: p.x, + y: p.y, + z: $basefield::one() + } + } + } + } + + // The projective point X, Y, Z is represented in the affine + // coordinates as X/Z^2, Y/Z^3. + impl From<$projective> for $affine { + fn from(p: $projective) -> $affine { + if p.is_zero() { + $affine::zero() + } else if p.z == $basefield::one() { + // If Z is one, the point is already normalized. + $affine { + x: p.x, + y: p.y, + infinity: false + } + } else { + // Z is nonzero, so it must have an inverse in a field. + let zinv = p.z.inverse().unwrap(); + let mut zinv_powered = zinv; + zinv_powered.square(); + + // X/Z^2 + let mut x = p.x; + x.mul_assign(&zinv_powered); + + // Y/Z^3 + let mut y = p.y; + zinv_powered.mul_assign(&zinv); + y.mul_assign(&zinv_powered); + + $affine { + x: x, + y: y, + infinity: false + } + } + } + } + } +} + +pub mod g1 { + use super::super::{Bn256, Fq, Fq12, FqRepr, Fr, FrRepr}; + use super::g2::G2Affine; + use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; + use rand::{Rand, Rng}; + use std::fmt; + use {CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; + + curve_impl!( + "G1", + G1, + G1Affine, + G1Prepared, + Fq, + Fr, + G1Uncompressed, + G1Compressed, + G2Affine + ); + + #[derive(Copy, Clone)] + pub struct G1Uncompressed([u8; 64]); + + impl Rand for G1 { + fn rand<R: Rng>(rng: &mut R) -> Self { + loop { + let x = rng.gen(); + let greatest = rng.gen(); + + if let Some(p) = G1Affine::get_point_from_x(x, greatest) { + if !p.is_zero() { + if p.is_on_curve() { + return p.into_projective(); + } + } + } + } + } + } + + impl Rand for G1Affine { + fn rand<R: Rng>(rng: &mut R) -> Self { + loop { + let x = rng.gen(); + let greatest = rng.gen(); + + if let Some(p) = G1Affine::get_point_from_x(x, greatest) { + if !p.is_zero() { + if p.is_on_curve() { + return p; + } + } + } + } + } + } + + impl AsRef<[u8]> for G1Uncompressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for G1Uncompressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl fmt::Debug for G1Uncompressed { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0[..].fmt(formatter) + } + } + + impl EncodedPoint for G1Uncompressed { + type Affine = G1Affine; + + fn empty() -> Self { + G1Uncompressed([0; 64]) + } + fn size() -> usize { + 64 + } + fn into_affine(&self) -> Result<G1Affine, GroupDecodingError> { + let affine = self.into_affine_unchecked()?; + + if !affine.is_on_curve() { + Err(GroupDecodingError::NotOnCurve) + } else { + Ok(affine) + } + } + fn into_affine_unchecked(&self) -> Result<G1Affine, GroupDecodingError> { + // Create a copy of this representation. + let mut copy = self.0; + + if copy[0] & (1 << 6) != 0 { + // This is the point at infinity, which means that if we mask away + // the first two bits, the entire representation should consist + // of zeroes. + copy[0] &= 0x3f; + + if copy.iter().all(|b| *b == 0) { + Ok(G1Affine::zero()) + } else { + Err(GroupDecodingError::UnexpectedInformation) + } + } else { + if copy[0] & (1 << 7) != 0 { + // The bit indicating the y-coordinate should be lexicographically + // largest is set, but this is an uncompressed element. + return Err(GroupDecodingError::UnexpectedInformation); + } + + // Unset the two most significant bits. + copy[0] &= 0x3f; + + let mut x = FqRepr([0; 4]); + let mut y = FqRepr([0; 4]); + + { + let mut reader = &copy[..]; + + x.read_be(&mut reader).unwrap(); + y.read_be(&mut reader).unwrap(); + } + + Ok(G1Affine { + x: Fq::from_repr(x).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate", e) + })?, + y: Fq::from_repr(y).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("y coordinate", e) + })?, + infinity: false, + }) + } + } + fn from_affine(affine: G1Affine) -> Self { + let mut res = Self::empty(); + + if affine.is_zero() { + // Set the second-most significant bit to indicate this point + // is at infinity. + res.0[0] |= 1 << 6; + } else { + let mut writer = &mut res.0[..]; + + affine.x.into_repr().write_be(&mut writer).unwrap(); + affine.y.into_repr().write_be(&mut writer).unwrap(); + } + + res + } + } + + #[derive(Copy, Clone)] + pub struct G1Compressed([u8; 32]); + + impl AsRef<[u8]> for G1Compressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for G1Compressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl fmt::Debug for G1Compressed { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0[..].fmt(formatter) + } + } + + impl EncodedPoint for G1Compressed { + type Affine = G1Affine; + + fn empty() -> Self { + G1Compressed([0; 32]) + } + fn size() -> usize { + 32 + } + fn into_affine(&self) -> Result<G1Affine, GroupDecodingError> { + let affine = self.into_affine_unchecked()?; + + // NB: Decompression guarantees that it is on the curve already. + + Ok(affine) + } + fn into_affine_unchecked(&self) -> Result<G1Affine, GroupDecodingError> { + // Create a copy of this representation. + let mut copy = self.0; + + if copy[0] & (1 << 6) != 0 { + // This is the point at infinity, which means that if we mask away + // the first two bits, the entire representation should consist + // of zeroes. + copy[0] &= 0x3f; + + if copy.iter().all(|b| *b == 0) { + Ok(G1Affine::zero()) + } else { + Err(GroupDecodingError::UnexpectedInformation) + } + } else { + // Determine if the intended y coordinate must be greater + // lexicographically. + let greatest = copy[0] & (1 << 7) != 0; + + // Unset the two most significant bits. + copy[0] &= 0x3f; + + let mut x = FqRepr([0; 4]); + + { + let mut reader = &copy[..]; + + x.read_be(&mut reader).unwrap(); + } + + // Interpret as Fq element. + let x = Fq::from_repr(x) + .map_err(|e| GroupDecodingError::CoordinateDecodingError("x coordinate", e))?; + + G1Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve) + } + } + fn from_affine(affine: G1Affine) -> Self { + let mut res = Self::empty(); + + if affine.is_zero() { + // Set the second-most significant bit to indicate this point + // is at infinity. + res.0[0] |= 1 << 6; + } else { + { + let mut writer = &mut res.0[..]; + + affine.x.into_repr().write_be(&mut writer).unwrap(); + } + + let mut negy = affine.y; + negy.negate(); + + // Set the third most significant bit if the correct y-coordinate + // is lexicographically largest. + if affine.y > negy { + res.0[0] |= 1 << 7; + } + } + + res + } + } + + impl G1Affine { + fn get_generator() -> Self { + G1Affine { + x: super::super::fq::G1_GENERATOR_X, + y: super::super::fq::G1_GENERATOR_Y, + infinity: false, + } + } + + fn get_coeff_b() -> Fq { + super::super::fq::B_COEFF + } + + fn perform_pairing(&self, other: &G2Affine) -> Fq12 { + super::super::Bn256::pairing(*self, *other) + } + } + + impl G1 { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize { + let num_bits = scalar.num_bits() as usize; + + if num_bits >= 130 { + 4 + } else if num_bits >= 34 { + 3 + } else { + 2 + } + } + + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + const RECOMMENDATIONS: [usize; 12] = + [1, 3, 7, 20, 43, 120, 273, 563, 1630, 3128, 7933, 62569]; + + let mut ret = 4; + for r in &RECOMMENDATIONS { + if num_scalars > *r { + ret += 1; + } else { + break; + } + } + + ret + } + } + + #[derive(Clone, Debug)] + pub struct G1Prepared(pub(crate) G1Affine); + + impl G1Prepared { + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + pub fn from_affine(p: G1Affine) -> Self { + G1Prepared(p) + } + } + + #[test] + fn g1_generator() { + use SqrtField; + + let mut x = Fq::zero(); + let mut i = 0; + loop { + // y^2 = x^3 + b + let mut rhs = x; + rhs.square(); + rhs.mul_assign(&x); + rhs.add_assign(&G1Affine::get_coeff_b()); + + if let Some(y) = rhs.sqrt() { + let yrepr = y.into_repr(); + let mut negy = y; + negy.negate(); + let negyrepr = negy.into_repr(); + + let p = G1Affine { + x: x, + y: if yrepr < negyrepr { y } else { negy }, + infinity: false, + }; + + let g1 = p.into_projective(); + if !g1.is_zero() { + assert_eq!(i, 1); + let g1 = G1Affine::from(g1); + + assert_eq!(g1, G1Affine::one()); + break; + } + } + + i += 1; + x.add_assign(&Fq::one()); + } + } + + #[test] + + fn test_base_point_addition_and_doubling() { + let mut a = G1::one(); + print!("{}\n\n", a); + + a.add_assign(&G1::one()); + + print!("{}\n\n", a); + } + + #[test] + fn g1_curve_tests() { + ::tests::curve::curve_tests::<G1>(); + ::tests::curve::random_transformation_tests::<G1>(); + } +} + +pub mod g2 { + use super::super::{Bn256, Fq, Fq12, Fq2, FqRepr, Fr, FrRepr}; + use super::g1::G1Affine; + use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; + use rand::{Rand, Rng}; + use std::fmt; + use {CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; + + curve_impl!( + "G2", + G2, + G2Affine, + G2Prepared, + Fq2, + Fr, + G2Uncompressed, + G2Compressed, + G1Affine + ); + + impl Rand for G2 { + fn rand<R: Rng>(rng: &mut R) -> Self { + let mut r = G2::one(); + let k = Fr::rand(rng); + r.mul_assign(k); + return r; + } + } + + impl Rand for G2Affine { + fn rand<R: Rng>(rng: &mut R) -> Self { + let mut r = G2::one(); + let k = Fr::rand(rng); + r.mul_assign(k); + return r.into_affine(); + } + } + + #[derive(Copy, Clone)] + pub struct G2Uncompressed([u8; 128]); + + impl AsRef<[u8]> for G2Uncompressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for G2Uncompressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl fmt::Debug for G2Uncompressed { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0[..].fmt(formatter) + } + } + + impl EncodedPoint for G2Uncompressed { + type Affine = G2Affine; + + fn empty() -> Self { + G2Uncompressed([0; 128]) + } + fn size() -> usize { + 128 + } + fn into_affine(&self) -> Result<G2Affine, GroupDecodingError> { + let affine = self.into_affine_unchecked()?; + + if !affine.is_on_curve() { + Err(GroupDecodingError::NotOnCurve) + } else { + Ok(affine) + } + } + fn into_affine_unchecked(&self) -> Result<G2Affine, GroupDecodingError> { + // Create a copy of this representation. + let mut copy = self.0; + + if copy[0] & (1 << 7) != 0 { + // Distinguisher bit is set, but this should be uncompressed! + return Err(GroupDecodingError::UnexpectedCompressionMode); + } + + if copy[0] & (1 << 6) != 0 { + // This is the point at infinity, which means that if we mask away + // the first two bits, the entire representation should consist + // of zeroes. + copy[0] &= 0x3f; + + if copy.iter().all(|b| *b == 0) { + Ok(G2Affine::zero()) + } else { + Err(GroupDecodingError::UnexpectedInformation) + } + } else { + + // Unset the two most significant bits. + copy[0] &= 0x3f; + + let mut x_c0 = FqRepr([0; 4]); + let mut x_c1 = FqRepr([0; 4]); + let mut y_c0 = FqRepr([0; 4]); + let mut y_c1 = FqRepr([0; 4]); + + { + let mut reader = &copy[..]; + + x_c1.read_be(&mut reader).unwrap(); + x_c0.read_be(&mut reader).unwrap(); + y_c1.read_be(&mut reader).unwrap(); + y_c0.read_be(&mut reader).unwrap(); + } + + Ok(G2Affine { + x: Fq2 { + c0: Fq::from_repr(x_c0).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate (c0)", e) + })?, + c1: Fq::from_repr(x_c1).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e) + })?, + }, + y: Fq2 { + c0: Fq::from_repr(y_c0).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("y coordinate (c0)", e) + })?, + c1: Fq::from_repr(y_c1).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("y coordinate (c1)", e) + })?, + }, + infinity: false, + }) + } + } + fn from_affine(affine: G2Affine) -> Self { + let mut res = Self::empty(); + + if affine.is_zero() { + // Set the second-most significant bit to indicate this point + // is at infinity. + res.0[0] |= 1 << 6; + } else { + let mut writer = &mut res.0[..]; + + affine.x.c1.into_repr().write_be(&mut writer).unwrap(); + affine.x.c0.into_repr().write_be(&mut writer).unwrap(); + affine.y.c1.into_repr().write_be(&mut writer).unwrap(); + affine.y.c0.into_repr().write_be(&mut writer).unwrap(); + } + + res + } + } + + #[derive(Copy, Clone)] + pub struct G2Compressed([u8; 64]); + + impl AsRef<[u8]> for G2Compressed { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl AsMut<[u8]> for G2Compressed { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + impl fmt::Debug for G2Compressed { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0[..].fmt(formatter) + } + } + + impl EncodedPoint for G2Compressed { + type Affine = G2Affine; + + fn empty() -> Self { + G2Compressed([0; 64]) + } + fn size() -> usize { + 64 + } + fn into_affine(&self) -> Result<G2Affine, GroupDecodingError> { + let affine = self.into_affine_unchecked()?; + + // NB: Decompression guarantees that it is on the curve already. + + Ok(affine) + } + fn into_affine_unchecked(&self) -> Result<G2Affine, GroupDecodingError> { + // Create a copy of this representation. + let mut copy = self.0; + + if copy[0] & (1 << 6) != 0 { + // This is the point at infinity, which means that if we mask away + // the first two bits, the entire representation should consist + // of zeroes. + copy[0] &= 0x3f; + + if copy.iter().all(|b| *b == 0) { + Ok(G2Affine::zero()) + } else { + Err(GroupDecodingError::UnexpectedInformation) + } + } else { + // Determine if the intended y coordinate must be greater + // lexicographically. + let greatest = copy[0] & (1 << 7) != 0; + + // Unset the two most significant bits. + copy[0] &= 0x3f; + + let mut x_c1 = FqRepr([0; 4]); + let mut x_c0 = FqRepr([0; 4]); + + { + let mut reader = &copy[..]; + + x_c1.read_be(&mut reader).unwrap(); + x_c0.read_be(&mut reader).unwrap(); + } + + // Interpret as Fq element. + let x = Fq2 { + c0: Fq::from_repr(x_c0).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate (c0)", e) + })?, + c1: Fq::from_repr(x_c1).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate (c1)", e) + })?, + }; + + G2Affine::get_point_from_x(x, greatest).ok_or(GroupDecodingError::NotOnCurve) + } + } + fn from_affine(affine: G2Affine) -> Self { + let mut res = Self::empty(); + + if affine.is_zero() { + // Set the second-most significant bit to indicate this point + // is at infinity. + res.0[0] |= 1 << 6; + } else { + { + let mut writer = &mut res.0[..]; + + affine.x.c1.into_repr().write_be(&mut writer).unwrap(); + affine.x.c0.into_repr().write_be(&mut writer).unwrap(); + } + + let mut negy = affine.y; + negy.negate(); + + // Set the third most significant bit if the correct y-coordinate + // is lexicographically largest. + if affine.y > negy { + res.0[0] |= 1 << 7; + } + } + + res + } + } + + impl G2Affine { + fn get_generator() -> Self { + G2Affine { + x: Fq2 { + c0: super::super::fq::G2_GENERATOR_X_C0, + c1: super::super::fq::G2_GENERATOR_X_C1, + }, + y: Fq2 { + c0: super::super::fq::G2_GENERATOR_Y_C0, + c1: super::super::fq::G2_GENERATOR_Y_C1, + }, + infinity: false, + } + } + + fn get_coeff_b() -> Fq2 { + super::super::fq::B_COEFF_FQ2 + // Fq2 { + // c0: super::super::fq::B_COEFF, + // c1: super::super::fq::B_COEFF, + // } + } + + fn perform_pairing(&self, other: &G1Affine) -> Fq12 { + super::super::Bn256::pairing(*other, *self) + } + } + + impl G2 { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize { + let num_bits = scalar.num_bits() as usize; + + if num_bits >= 103 { + 4 + } else if num_bits >= 37 { + 3 + } else { + 2 + } + } + + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + const RECOMMENDATIONS: [usize; 11] = + [1, 3, 8, 20, 47, 126, 260, 826, 1501, 4555, 84071]; + + let mut ret = 4; + for r in &RECOMMENDATIONS { + if num_scalars > *r { + ret += 1; + } else { + break; + } + } + + ret + } + } + + #[derive(Clone, Debug)] + pub struct G2Prepared { + pub(crate) coeffs: Vec<(Fq2, Fq2, Fq2)>, + pub(crate) infinity: bool, + } + + // This generator does not take a random element in Fp2 + // and tries to increment it to be on a curve, but + // generates a random scalar and multiplies predefined generator by it + + #[test] + fn g2_generator() { + use SqrtField; + + let mut x = Fq2::zero(); + let mut i = 0; + loop { + // y^2 = x^3 + b + let mut rhs = x; + rhs.square(); + rhs.mul_assign(&x); + rhs.add_assign(&G2Affine::get_coeff_b()); + + if let Some(y) = rhs.sqrt() { + let mut negy = y; + negy.negate(); + + let p = G2Affine { + x: x, + y: if y < negy { y } else { negy }, + infinity: false, + }; + + + let g2 = p.into_projective(); + if !g2.is_zero() { + assert_eq!(i, 0); + let g2 = G2Affine::from(g2); + + assert_eq!(g2, G2Affine::one()); + break; + } + } + + i += 1; + x.add_assign(&Fq2::one()); + } + } + + #[cfg(test)] + use rand::{SeedableRng, XorShiftRng}; + + #[test] + fn g2_generator_on_curve() { + use SqrtField; + + let gen = G2Affine::get_generator(); + let x = gen.x; + // y^2 = x^3 + 3/xi + let mut rhs = x; + rhs.square(); + rhs.mul_assign(&x); + rhs.add_assign(&G2Affine::get_coeff_b()); + + if let Some(y) = rhs.sqrt() { + let mut negy = y; + negy.negate(); + + let p = G2Affine { + x: x, + y: if y < negy { y } else { negy }, + infinity: false, + }; + + assert_eq!(p.y, gen.y); + assert_eq!(p, G2Affine::one()); + return; + } + panic!(); + } + + #[test] + fn g2_curve_tests() { + ::tests::curve::curve_tests::<G2>(); + ::tests::curve::random_transformation_tests::<G2>(); + } + + #[test] + + fn test_b_coeff() { + let b2 = G2Affine::get_coeff_b(); + print!("{}\n\n", b2); + } + + #[test] + + fn test_base_point_addition_and_doubling() { + let mut two = G2::one(); + two.add_assign(&G2::one()); + + let one = G2::one(); + + let mut three21 = two; + three21.add_assign(&one); + + let mut three12 = one; + three12.add_assign(&two); + + assert_eq!(three12, three21); + } + + #[test] + fn test_addition_and_doubling() { + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = G2::rand(&mut rng); + assert!(a.into_affine().is_on_curve()); + let b = G2::rand(&mut rng); + let c = G2::rand(&mut rng); + let a_affine = a.into_affine(); + let b_affine = b.into_affine(); + let c_affine = c.into_affine(); + + // a + a should equal the doubling + { + let mut aplusa = a; + aplusa.add_assign(&a); + + let mut aplusamixed = a; + aplusamixed.add_assign_mixed(&a.into_affine()); + + let mut adouble = a; + adouble.double(); + + assert_eq!(aplusa, adouble); + assert_eq!(aplusamixed, adouble); + } + + let mut ab = a; + ab.add_assign(&b); + + let mut ba = b; + ba.add_assign(&a); + + assert_eq!(ab, ba, "Addition should not depend on order"); + + let mut tmp = vec![G2::zero(); 6]; + + // (a + b) + c + tmp[0] = a; + tmp[0].add_assign(&b); + tmp[0].add_assign(&c); + + // a + (b + c) + tmp[1] = b; + tmp[1].add_assign(&c); + tmp[1].add_assign(&a); + + // (a + c) + b + tmp[2] = a; + tmp[2].add_assign(&c); + tmp[2].add_assign(&b); + + // Mixed addition + + // (a + b) + c + tmp[3] = a_affine.into_projective(); + tmp[3].add_assign_mixed(&b_affine); + tmp[3].add_assign_mixed(&c_affine); + + // a + (b + c) + tmp[4] = b_affine.into_projective(); + tmp[4].add_assign_mixed(&c_affine); + tmp[4].add_assign_mixed(&a_affine); + + // (a + c) + b + tmp[5] = a_affine.into_projective(); + tmp[5].add_assign_mixed(&c_affine); + tmp[5].add_assign_mixed(&b_affine); + + // Comparisons + for i in 0..6 { + for j in 0..6 { + assert_eq!(tmp[i], tmp[j]); + assert_eq!(tmp[i].into_affine(), tmp[j].into_affine()); + } + + assert!(tmp[i] != a); + assert!(tmp[i] != b); + assert!(tmp[i] != c); + + assert!(a != tmp[i]); + assert!(b != tmp[i]); + assert!(c != tmp[i]); + } + + } + } + + #[test] + fn random_negation_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // let r = G2::rand(&mut rng); + // assert!(r.into_affine().is_on_curve()); + + let mut r = G2::one(); + let k = Fr::rand(&mut rng); + r.mul_assign(k); + + let s = Fr::rand(&mut rng); + let mut sneg = s; + sneg.negate(); + + let mut t1 = r; + t1.mul_assign(s); + + let mut t2 = r; + t2.mul_assign(sneg); + + let mut t3 = t1; + t3.add_assign(&t2); + assert!(t3.is_zero()); + + let mut t4 = t1; + t4.add_assign_mixed(&t2.into_affine()); + assert!(t4.is_zero()); + + t1.negate(); + assert_eq!(t1, t2); + } + } + + #[test] + fn mul_by_order_tests() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // let r = G2::rand(&mut rng); + + let mut r = G2::one(); + let k = Fr::rand(&mut rng); + r.mul_assign(k); + + let order = Fr::char(); + + let mut q = G2::one(); + q.mul_assign(order); + assert!(q.is_zero()); + + r.mul_assign(order); + assert!(r.is_zero()); + + // let mut t = G2::rand(&mut rng); + // t.mul_assign(order); + // assert!(t.is_zero()); + } + } + +} + +pub use self::g1::*; +pub use self::g2::*; diff --git a/src/bn256/fq.rs b/src/bn256/fq.rs new file mode 100644 index 0000000..19e55d3 --- /dev/null +++ b/src/bn256/fq.rs @@ -0,0 +1,579 @@ +use super::fq2::Fq2; +use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; + +#[derive(PrimeField)] +#[PrimeFieldModulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583"] +#[PrimeFieldGenerator = "2"] +pub struct Fq(FqRepr); + +// B coefficient of BN256 curve, B = 3 +// In Montgommery form with R = 2^256 +pub const B_COEFF: Fq = Fq(FqRepr([ + 0x7a17caa950ad28d7, + 0x1f6ac17ae15521b9, + 0x334bea4e696bd284, + 0x2a1f6744ce179d8e, +])); + +pub const B_COEFF_FQ2: Fq2 = Fq2 { + c0: Fq(FqRepr([ + 0x3bf938e377b802a8, + 0x020b1b273633535d, + 0x26b7edf049755260, + 0x2514c6324384a86d, + ])), + c1: Fq(FqRepr([ + 0x38e7ecccd1dcff67, + 0x65f0b37d93ce0d3e, + 0xd749d0dd22ac00aa, + 0x0141b9ce4a688d4d, + ])), +}; + + +// The generators of G1/G2 + +// Generator of G1 +// x = 1 +// y = 2 +pub const G1_GENERATOR_X: Fq = Fq(FqRepr([ + 0xd35d438dc58f0d9d, + 0x0a78eb28f5c70b3d, + 0x666ea36f7879462c, + 0x0e0a77c19a07df2f, +])); +pub const G1_GENERATOR_Y: Fq = Fq(FqRepr([ + 0xa6ba871b8b1e1b3a, + 0x14f1d651eb8e167b, + 0xccdd46def0f28c58, + 0x1c14ef83340fbe5e, +])); + +// Generator of G2 +// +// x = 11559732032986387107991004021392285783925812861821192530917403151452391805634*u +// + 10857046999023057135944570762232829481370756359578518086990519993285655852781 +// +// y = 4082367875863433681332203403145435568316851327593401208105741076214120093531*u +// + 8495653923123431417604973247489272438418190587263600148770280649306958101930 + +pub const G2_GENERATOR_X_C0: Fq = Fq(FqRepr([ + 0x8e83b5d102bc2026, + 0xdceb1935497b0172, + 0xfbb8264797811adf, + 0x19573841af96503b, +])); +pub const G2_GENERATOR_X_C1: Fq = Fq(FqRepr([ + 0xafb4737da84c6140, + 0x6043dd5a5802d8c4, + 0x09e950fc52a02f86, + 0x14fef0833aea7b6b, +])); +pub const G2_GENERATOR_Y_C0: Fq = Fq(FqRepr([ + 0x619dfa9d886be9f6, + 0xfe7fd297f59e9b78, + 0xff9e1a62231b7dfe, + 0x28fd7eebae9e4206, +])); +pub const G2_GENERATOR_Y_C1: Fq = Fq(FqRepr([ + 0x64095b56c71856ee, + 0xdc57f922327d3cbb, + 0x55f935be33351076, + 0x0da4a0e693fd6482, +])); + + +// Coefficients for the Frobenius automorphism. +pub const FROBENIUS_COEFF_FQ2_C1: [Fq; 2] = [ + // Fq(-1)**(((q^0) - 1) / 2) + // it's 1 in Montgommery form + Fq(FqRepr([ + 0xd35d438dc58f0d9d, + 0x0a78eb28f5c70b3d, + 0x666ea36f7879462c, + 0x0e0a77c19a07df2f, + ])), + // Fq(-1)**(((q^1) - 1) / 2) + Fq(FqRepr([ + 0x68c3488912edefaa, + 0x8d087f6872aabf4f, + 0x51e1a24709081231, + 0x2259d6b14729c0fa, + ])), +]; + + // Fq2(u + 9)**(((q^1) - 1) / 2) +pub const XI_TO_Q_MINUS_1_OVER_2: Fq2 = Fq2 { + c0: Fq(FqRepr([ + 0xe4bbdd0c2936b629, + 0xbb30f162e133bacb, + 0x31a9d1b6f9645366, + 0x253570bea500f8dd, + ])), + c1: Fq(FqRepr([ + 0xa1d77ce45ffe77c7, + 0x07affd117826d1db, + 0x6d16bd27bb7edc6b, + 0x2c87200285defecc, + ])), +}; + +pub const FROBENIUS_COEFF_FQ6_C1: [Fq2; 6] = [ + // Fq2(u + 9)**(((q^0) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0xd35d438dc58f0d9d, + 0x0a78eb28f5c70b3d, + 0x666ea36f7879462c, + 0x0e0a77c19a07df2f, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0])), + }, + // Fq2(u + 9)**(((q^1) - 1) / 3) + // taken from go-ethereum and also re-calculated manually + Fq2 { + c0: Fq(FqRepr([ + 0xb5773b104563ab30, + 0x347f91c8a9aa6454, + 0x7a007127242e0991, + 0x1956bcd8118214ec, + ])), + c1: Fq(FqRepr([ + 0x6e849f1ea0aa4757, + 0xaa1c7b6d89f89141, + 0xb6e713cdfae0ca3a, + 0x26694fbb4e82ebc3, + ])), + }, + // Fq2(u + 9)**(((q^2) - 1) / 3) + // this one and other below are recalculated manually + Fq2 { + c0: Fq(FqRepr([ + 0x3350c88e13e80b9c, + 0x7dce557cdb5e56b9, + 0x6001b4b8b615564a, + 0x2682e617020217e0, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0])), + }, + // Fq2(u + 9)**(((q^3) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0xc9af22f716ad6bad, + 0xb311782a4aa662b2, + 0x19eeaf64e248c7f4, + 0x20273e77e3439f82, + ])), + c1: Fq(FqRepr([ + 0xacc02860f7ce93ac, + 0x3933d5817ba76b4c, + 0x69e6188b446c8467, + 0x0a46036d4417cc55, + ])), + }, + // Fq2(u + 9)**(((q^4) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0x71930c11d782e155, + 0xa6bb947cffbe3323, + 0xaa303344d4741444, + 0x2c3b3f0d26594943, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0,])), + }, + // Fq2(u + 9)**(((q^5) - 1) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0xf91aba2654e8e3b1, + 0x4771cb2fdc92ce12, + 0xdcb16ae0fc8bdf35, + 0x274aa195cd9d8be4, + ])), + c1: Fq(FqRepr([ + 0x5cfc50ae18811f8b, + 0x4bb28433cb43988c, + 0x4fd35f13c3b56219, + 0x301949bd2fc8883a, + ])), + }, +]; + +pub const FROBENIUS_COEFF_FQ6_C2: [Fq2; 6] = [ + // Fq2(u + 1)**(((2q^0) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0xd35d438dc58f0d9d, + 0x0a78eb28f5c70b3d, + 0x666ea36f7879462c, + 0x0e0a77c19a07df2f, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0])), + }, + // Fq2(u + 1)**(((2q^1) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0x7361d77f843abe92, + 0xa5bb2bd3273411fb, + 0x9c941f314b3e2399, + 0x15df9cddbb9fd3ec, + ])), + c1: Fq(FqRepr([ + 0x5dddfd154bd8c949, + 0x62cb29a5a4445b60, + 0x37bc870a0c7dd2b9, + 0x24830a9d3171f0fd, + ])), + }, + // Fq2(u + 1)**(((2q^2) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0x71930c11d782e155, + 0xa6bb947cffbe3323, + 0xaa303344d4741444, + 0x2c3b3f0d26594943, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0,])), + }, + // Fq2(u + 1)**(((2q^3) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0x448a93a57b6762df, + 0xbfd62df528fdeadf, + 0xd858f5d00e9bd47a, + 0x06b03d4d3476ec58, + ])), + c1: Fq(FqRepr([ + 0x2b19daf4bcc936d1, + 0xa1a54e7a56f4299f, + 0xb533eee05adeaef1, + 0x170c812b84dda0b2, + ])), + }, + // Fq2(u + 1)**(((2q^4) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0x3350c88e13e80b9c, + 0x7dce557cdb5e56b9, + 0x6001b4b8b615564a, + 0x2682e617020217e0, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0,])), + }, + // Fq2(u + 1)**(((2q^5) - 2) / 3) + Fq2 { + c0: Fq(FqRepr([ + 0x843420f1d8dadbd6, + 0x31f010c9183fcdb2, + 0x436330b527a76049, + 0x13d47447f11adfe4, + ])), + c1: Fq(FqRepr([ + 0xef494023a857fa74, + 0x2a925d02d5ab101a, + 0x83b015829ba62f10, + 0x2539111d0c13aea3, + ])), + }, +]; + +// non_residue^((modulus^i-1)/6) for i=0,...,11 +pub const FROBENIUS_COEFF_FQ12_C1: [Fq2; 12] = [ + // Fq2(u + 1)**(((q^0) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0xd35d438dc58f0d9d, + 0x0a78eb28f5c70b3d, + 0x666ea36f7879462c, + 0x0e0a77c19a07df2f, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0])), + }, + // Fq2(u + 1)**(((q^1) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0xaf9ba69633144907, + 0xca6b1d7387afb78a, + 0x11bded5ef08a2087, + 0x02f34d751a1f3a7c, + ])), + c1: Fq(FqRepr([ + 0xa222ae234c492d72, + 0xd00f02a4565de15b, + 0xdc2ff3a253dfc926, + 0x10a75716b3899551, + ])), + }, + // Fq2(u + 1)**(((q^2) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0xca8d800500fa1bf2, + 0xf0c5d61468b39769, + 0x0e201271ad0d4418, + 0x04290f65bad856e6, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0])), + }, + // Fq2(u + 1)**(((q^3) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0x365316184e46d97d, + 0x0af7129ed4c96d9f, + 0x659da72fca1009b5, + 0x08116d8983a20d23, + ])), + c1: Fq(FqRepr([ + 0xb1df4af7c39c1939, + 0x3d9f02878a73bf7f, + 0x9b2220928caf0ae0, + 0x26684515eff054a6, + ])), + }, + // Fq2(u + 1)**(((q^4) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0x3350c88e13e80b9c, + 0x7dce557cdb5e56b9, + 0x6001b4b8b615564a, + 0x2682e617020217e0, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0,])), + }, + // Fq2(u + 1)**(((q^5) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0x86b76f821b329076, + 0x408bf52b4d19b614, + 0x53dfb9d0d985e92d, + 0x051e20146982d2a7, + ])), + c1: Fq(FqRepr([ + 0x0fbc9cd47752ebc7, + 0x6d8fffe33415de24, + 0xbef22cf038cf41b9, + 0x15c0edff3c66bf54, + ])), + }, + // Fq2(u + 1)**(((q^6) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0x68c3488912edefaa, + 0x8d087f6872aabf4f, + 0x51e1a24709081231, + 0x2259d6b14729c0fa, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0,])), + }, + // Fq2(u + 1)**(((q^7) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0x8c84e580a568b440, + 0xcd164d1de0c21302, + 0xa692585790f737d5, + 0x2d7100fdc71265ad, + ])), + c1: Fq(FqRepr([ + 0x99fdddf38c33cfd5, + 0xc77267ed1213e931, + 0xdc2052142da18f36, + 0x1fbcf75c2da80ad7, + ])), + }, + // Fq2(u + 1)**(((q^8) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0x71930c11d782e155, + 0xa6bb947cffbe3323, + 0xaa303344d4741444, + 0x2c3b3f0d26594943, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0,])), + }, + // Fq2(u + 1)**(((q^9) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0x05cd75fe8a3623ca, + 0x8c8a57f293a85cee, + 0x52b29e86b7714ea8, + 0x2852e0e95d8f9306, + ])), + c1: Fq(FqRepr([ + 0x8a41411f14e0e40e, + 0x59e26809ddfe0b0d, + 0x1d2e2523f4d24d7d, + 0x09fc095cf1414b83, + ])), + }, + // Fq2(u + 1)**(((q^10) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0x08cfc388c494f1ab, + 0x19b315148d1373d4, + 0x584e90fdcb6c0213, + 0x09e1685bdf2f8849, + ])), + c1: Fq(FqRepr([0x0, 0x0, 0x0, 0x0,])), + }, + // Fq2(u + 1)**(((q^11) - 1) / 6) + Fq2 { + c0: Fq(FqRepr([ + 0xb5691c94bd4a6cd1, + 0x56f575661b581478, + 0x64708be5a7fb6f30, + 0x2b462e5e77aecd82, + ])), + c1: Fq(FqRepr([ + 0x2c63ef42612a1180, + 0x29f16aae345bec69, + 0xf95e18c648b216a4, + 0x1aa36073a4cae0d4, + ])), + }, +]; + +// -((2**256) mod q) mod q +pub const NEGATIVE_ONE: Fq = Fq(FqRepr([ + 0x974bc177a0000006, + 0xf13771b2da58a367, + 0x51e1a2470908122e, + 0x2259d6b14729c0fa, +])); + +#[cfg(test)] +use rand::{Rand, SeedableRng, XorShiftRng}; + +#[test] +fn test_fq_repr_from() { + assert_eq!(FqRepr::from(100), FqRepr([100, 0, 0, 0])); + assert_eq!(FqRepr::from(3), FqRepr([3, 0, 0, 0])); +} + +#[test] +fn test_fq_repr_is_odd() { + assert!(!FqRepr::from(0).is_odd()); + assert!(FqRepr::from(0).is_even()); + assert!(FqRepr::from(1).is_odd()); + assert!(!FqRepr::from(1).is_even()); + assert!(!FqRepr::from(324834872).is_odd()); + assert!(FqRepr::from(324834872).is_even()); + assert!(FqRepr::from(324834873).is_odd()); + assert!(!FqRepr::from(324834873).is_even()); +} + +#[test] +fn test_fq_repr_num_bits() { + let mut a = FqRepr::from(0); + assert_eq!(0, a.num_bits()); + a = FqRepr::from(1); + for i in 1..257 { + assert_eq!(i, a.num_bits()); + a.mul2(); + } + assert_eq!(0, a.num_bits()); +} + +#[test] +fn test_fq_is_valid() { + print!("modulus = {}\n", MODULUS); + print!("R = {}\n", R); + let mut a = Fq(MODULUS); + assert!(!a.is_valid()); + a.0.sub_noborrow(&FqRepr::from(1)); + assert!(a.is_valid()); + assert!(Fq(FqRepr::from(0)).is_valid()); + assert!( + Fq(FqRepr([ + 0xdf4671abd14dab3e, + 0xe2dc0c9f534fbd33, + 0x31ca6c880cc444a6, + 0x257a67e70ef33359 + ])).is_valid() + ); + assert!( + !Fq(FqRepr([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + ])).is_valid() + ); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = Fq::rand(&mut rng); + assert!(a.is_valid()); + } +} + +#[test] +fn test_fq_repr_display() { + assert_eq!( + format!("{}", Fq::into_repr(&Fq::one())), + "0x0000000000000000000000000000000000000000000000000000000000000001".to_string() + ); + assert_eq!( + format!("{}", FqRepr([0, 0, 0, 0])), + "0x0000000000000000000000000000000000000000000000000000000000000000".to_string() + ); +} + +#[test] +fn test_fq_num_bits() { + assert_eq!(Fq::NUM_BITS, 254); + assert_eq!(Fq::CAPACITY, 253); +} + +#[test] +fn test_fq_sqrt() { + use ff::SqrtField; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + assert_eq!(Fq::zero().sqrt().unwrap(), Fq::zero()); + + for _ in 0..1000 { + // Ensure sqrt(a^2) = a or -a + let a = Fq::rand(&mut rng); + let mut nega = a; + nega.negate(); + let mut b = a; + b.square(); + + let b = b.sqrt().unwrap(); + + assert!(a == b || nega == b); + } + + for _ in 0..1000 { + // Ensure sqrt(a)^2 = a for random a + let a = Fq::rand(&mut rng); + + if let Some(mut tmp) = a.sqrt() { + tmp.square(); + + assert_eq!(a, tmp); + } + } +} + +#[test] +fn test_fq_sqrt_2() { + use ff::SqrtField; + + let x = Fq::from_str("4").unwrap(); + print!("x = {}\n", x); + if let Some(y) = x.sqrt() { + print!("y = {}\n", y); + let mut y_other = y; + y_other.negate(); + print!("y' = {}\n", y_other); + } +} + +#[test] +fn fq_field_tests() { + ::tests::field::random_field_tests::<Fq>(); + ::tests::field::random_sqrt_tests::<Fq>(); + ::tests::field::random_frobenius_tests::<Fq, _>(Fq::char(), 13); + ::tests::field::from_str_tests::<Fq>(); +} \ No newline at end of file diff --git a/src/bn256/fq12.rs b/src/bn256/fq12.rs new file mode 100644 index 0000000..67fe6cb --- /dev/null +++ b/src/bn256/fq12.rs @@ -0,0 +1,221 @@ +use super::fq::FROBENIUS_COEFF_FQ12_C1; +use super::fq2::Fq2; +use super::fq6::Fq6; +use ff::Field; +use rand::{Rand, Rng}; + +/// An element of Fq12, represented by c0 + c1 * w. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Fq12 { + pub c0: Fq6, + pub c1: Fq6, +} + +impl ::std::fmt::Display for Fq12 { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fq12({} + {} * w)", self.c0, self.c1) + } +} + +impl Rand for Fq12 { + fn rand<R: Rng>(rng: &mut R) -> Self { + Fq12 { + c0: rng.gen(), + c1: rng.gen(), + } + } +} + +// BN256 and BLS12 implementations should be the same +// Defined over w^2 - v = 0 + +impl Fq12 { + pub fn conjugate(&mut self) { + self.c1.negate(); + } + + pub fn mul_by_014(&mut self, c0: &Fq2, c1: &Fq2, c4: &Fq2) { + let mut aa = self.c0; + aa.mul_by_01(c0, c1); + let mut bb = self.c1; + bb.mul_by_1(c4); + let mut o = *c1; + o.add_assign(c4); + self.c1.add_assign(&self.c0); + self.c1.mul_by_01(c0, &o); + self.c1.sub_assign(&aa); + self.c1.sub_assign(&bb); + self.c0 = bb; + self.c0.mul_by_nonresidue(); + self.c0.add_assign(&aa); + } + // TODO make it hand optimized + // // multiply by (c0, c1, c2) + (c3, c4, c5)*w where only c0, c3 and c4 are non-zero + pub fn mul_by_034(&mut self, c0: &Fq2, c3: &Fq2, c4: &Fq2) { + self.mul_assign(&Fq12 { + c0: Fq6 { + c0: *c0, + c1: Fq2::zero(), + c2: Fq2::zero(), + }, + c1: Fq6 { + c0: *c3, + c1: *c4, + c2: Fq2::zero(), + }, + }); + } +} + +impl Field for Fq12 { + fn zero() -> Self { + Fq12 { + c0: Fq6::zero(), + c1: Fq6::zero(), + } + } + + fn one() -> Self { + Fq12 { + c0: Fq6::one(), + c1: Fq6::zero(), + } + } + + fn is_zero(&self) -> bool { + self.c0.is_zero() && self.c1.is_zero() + } + + fn double(&mut self) { + self.c0.double(); + self.c1.double(); + } + + fn negate(&mut self) { + self.c0.negate(); + self.c1.negate(); + } + + fn add_assign(&mut self, other: &Self) { + self.c0.add_assign(&other.c0); + self.c1.add_assign(&other.c1); + } + + fn sub_assign(&mut self, other: &Self) { + self.c0.sub_assign(&other.c0); + self.c1.sub_assign(&other.c1); + } + + fn frobenius_map(&mut self, power: usize) { + self.c0.frobenius_map(power); + self.c1.frobenius_map(power); + + self.c1.c0.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + self.c1.c1.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + self.c1.c2.mul_assign(&FROBENIUS_COEFF_FQ12_C1[power % 12]); + } + + fn square(&mut self) { + let mut ab = self.c0; + ab.mul_assign(&self.c1); + let mut c0c1 = self.c0; + c0c1.add_assign(&self.c1); + let mut c0 = self.c1; + c0.mul_by_nonresidue(); + c0.add_assign(&self.c0); + c0.mul_assign(&c0c1); + c0.sub_assign(&ab); + self.c1 = ab; + self.c1.add_assign(&ab); + ab.mul_by_nonresidue(); + c0.sub_assign(&ab); + self.c0 = c0; + } + + fn mul_assign(&mut self, other: &Self) { + let mut aa = self.c0; + aa.mul_assign(&other.c0); + let mut bb = self.c1; + bb.mul_assign(&other.c1); + let mut o = other.c0; + o.add_assign(&other.c1); + self.c1.add_assign(&self.c0); + self.c1.mul_assign(&o); + self.c1.sub_assign(&aa); + self.c1.sub_assign(&bb); + self.c0 = bb; + self.c0.mul_by_nonresidue(); + self.c0.add_assign(&aa); + } + + fn inverse(&self) -> Option<Self> { + let mut c0s = self.c0; + c0s.square(); + let mut c1s = self.c1; + c1s.square(); + c1s.mul_by_nonresidue(); + c0s.sub_assign(&c1s); + + c0s.inverse().map(|t| { + let mut tmp = Fq12 { c0: t, c1: t }; + tmp.c0.mul_assign(&self.c0); + tmp.c1.mul_assign(&self.c1); + tmp.c1.negate(); + + tmp + }) + } +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng}; + +#[test] +fn test_fq12_mul_by_014() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let c0 = Fq2::rand(&mut rng); + let c1 = Fq2::rand(&mut rng); + let c5 = Fq2::rand(&mut rng); + let mut a = Fq12::rand(&mut rng); + let mut b = a; + + a.mul_by_014(&c0, &c1, &c5); + b.mul_assign(&Fq12 { + c0: Fq6 { + c0: c0, + c1: c1, + c2: Fq2::zero(), + }, + c1: Fq6 { + c0: Fq2::zero(), + c1: c5, + c2: Fq2::zero(), + }, + }); + + assert_eq!(a, b); + } +} + +#[test] +fn test_squaring() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut a = Fq12::rand(&mut rng); + let mut b = a; + b.mul_assign(&a); + a.square(); + assert_eq!(a, b); + } +} + +#[test] +fn fq12_field_tests() { + use ff::PrimeField; + + ::tests::field::random_field_tests::<Fq12>(); + ::tests::field::random_frobenius_tests::<Fq12, _>(super::fq::Fq::char(), 13); +} diff --git a/src/bn256/fq2.rs b/src/bn256/fq2.rs new file mode 100644 index 0000000..98939f3 --- /dev/null +++ b/src/bn256/fq2.rs @@ -0,0 +1,966 @@ +use super::fq::{FROBENIUS_COEFF_FQ2_C1, Fq, NEGATIVE_ONE}; +use ff::{Field, SqrtField}; +use rand::{Rand, Rng}; + +use std::cmp::Ordering; + +/// An element of Fq2, represented by c0 + c1 * u. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Fq2 { + pub c0: Fq, + pub c1: Fq, +} + +impl ::std::fmt::Display for Fq2 { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fq2({} + {} * u)", self.c0, self.c1) + } +} + +/// `Fq2` elements are ordered lexicographically. +impl Ord for Fq2 { + #[inline(always)] + fn cmp(&self, other: &Fq2) -> Ordering { + match self.c1.cmp(&other.c1) { + Ordering::Greater => Ordering::Greater, + Ordering::Less => Ordering::Less, + Ordering::Equal => self.c0.cmp(&other.c0), + } + } +} + +impl PartialOrd for Fq2 { + #[inline(always)] + fn partial_cmp(&self, other: &Fq2) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Fq2 { + // This is very confusing part, cause depends not of Fq2 itself, but form Fq6 construction + + /// Multiply this element by quadratic nonresidue 9 + u. + pub fn mul_by_nonresidue(&mut self) { + // (xi+y)(i+9) = (9x+y)i+(9y-x) + let t0 = self.c0; + let t1 = self.c1; + + // 8*x*i + 8*y + self.double(); + self.double(); + self.double(); + + // 9*y + self.c0.add_assign(&t0); + // (9*y - x) + self.c0.sub_assign(&t1); + + // (9*x)i + self.c1.add_assign(&t1); + // (9*x + y) + self.c1.add_assign(&t0); + } + + // Multiply this element by ξ where ξ=i+9 + pub fn mul_by_xi(&mut self) { + // (xi+y)(i+9) = (9x+y)i+(9y-x) + let t0 = self.c0; + let t1 = self.c1; + + // 8*x*i + 8*y + self.double(); + self.double(); + self.double(); + + // 9*y + self.c0.add_assign(&t0); + // (9*y - x) + self.c0.sub_assign(&t1); + + // (9*x)i + self.c1.add_assign(&t1); + // (9*x + y) + self.c1.add_assign(&t0); + } + + /// Norm of Fq2 as extension field in i over Fq + pub fn norm(&self) -> Fq { + let mut t0 = self.c0; + let mut t1 = self.c1; + t0.square(); + t1.square(); + t1.add_assign(&t0); + + t1 + } + + // conjucate by negating c1 + pub fn conjugate(&mut self) { + self.c1.negate(); + } +} + +impl Rand for Fq2 { + fn rand<R: Rng>(rng: &mut R) -> Self { + Fq2 { + c0: rng.gen(), + c1: rng.gen(), + } + } +} + +impl Field for Fq2 { + fn zero() -> Self { + Fq2 { + c0: Fq::zero(), + c1: Fq::zero(), + } + } + + fn one() -> Self { + Fq2 { + c0: Fq::one(), + c1: Fq::zero(), + } + } + + fn is_zero(&self) -> bool { + self.c0.is_zero() && self.c1.is_zero() + } + + fn square(&mut self) { + let mut ab = self.c0; + ab.mul_assign(&self.c1); + let mut c0c1 = self.c0; + c0c1.add_assign(&self.c1); + let mut c0 = self.c1; + c0.negate(); + c0.add_assign(&self.c0); + c0.mul_assign(&c0c1); + c0.sub_assign(&ab); + self.c1 = ab; + self.c1.add_assign(&ab); + c0.add_assign(&ab); + self.c0 = c0; + } + + fn double(&mut self) { + self.c0.double(); + self.c1.double(); + } + + fn negate(&mut self) { + self.c0.negate(); + self.c1.negate(); + } + + fn add_assign(&mut self, other: &Self) { + self.c0.add_assign(&other.c0); + self.c1.add_assign(&other.c1); + } + + fn sub_assign(&mut self, other: &Self) { + self.c0.sub_assign(&other.c0); + self.c1.sub_assign(&other.c1); + } + + fn mul_assign(&mut self, other: &Self) { + let mut aa = self.c0; + aa.mul_assign(&other.c0); + let mut bb = self.c1; + bb.mul_assign(&other.c1); + let mut o = other.c0; + o.add_assign(&other.c1); + self.c1.add_assign(&self.c0); + self.c1.mul_assign(&o); + self.c1.sub_assign(&aa); + self.c1.sub_assign(&bb); + self.c0 = aa; + self.c0.sub_assign(&bb); + } + + fn inverse(&self) -> Option<Self> { + let mut t1 = self.c1; + t1.square(); + let mut t0 = self.c0; + t0.square(); + t0.add_assign(&t1); + t0.inverse().map(|t| { + let mut tmp = Fq2 { + c0: self.c0, + c1: self.c1, + }; + tmp.c0.mul_assign(&t); + tmp.c1.mul_assign(&t); + tmp.c1.negate(); + + tmp + }) + } + + fn frobenius_map(&mut self, power: usize) { + self.c1.mul_assign(&FROBENIUS_COEFF_FQ2_C1[power % 2]); + } +} + +impl SqrtField for Fq2 { + fn legendre(&self) -> ::ff::LegendreSymbol { + self.norm().legendre() + } + + fn sqrt(&self) -> Option<Self> { + // Algorithm 9, https://eprint.iacr.org/2012/685.pdf + + if self.is_zero() { + Some(Self::zero()) + } else { + // a1 = self^((q - 3) / 4) + let mut a1 = self.pow([ + 0x4f082305b61f3f51, + 0x65e05aa45a1c72a3, + 0x6e14116da0605617, + 0x0c19139cb84c680a, + ]); + let mut alpha = a1; + alpha.square(); + alpha.mul_assign(self); + let mut a0 = alpha; + a0.frobenius_map(1); + a0.mul_assign(&alpha); + + let neg1 = Fq2 { + c0: NEGATIVE_ONE, + c1: Fq::zero(), + }; + + if a0 == neg1 { + None + } else { + a1.mul_assign(self); + + if alpha == neg1 { + a1.mul_assign(&Fq2 { + c0: Fq::zero(), + c1: Fq::one(), + }); + } else { + alpha.add_assign(&Fq2::one()); + // alpha = alpha^((q - 1) / 2) + alpha = alpha.pow([ + 0x9e10460b6c3e7ea3, + 0xcbc0b548b438e546, + 0xdc2822db40c0ac2e, + 0x183227397098d014, + ]); + a1.mul_assign(&alpha); + } + + Some(a1) + } + } + } +} + +#[test] +fn test_fq2_get_b() { + use ff::Field; + + let mut a = Fq2::one(); + a.mul_by_nonresidue(); + let mut b = a.inverse().unwrap(); + let c = b; + b.double(); + b.add_assign(&c); + + print!("B coeff in Fq2 = {}\n", b); +} + +#[test] +fn test_fq2_frobc1() { + use ff::Field; + + let mut a = Fq2::one(); + a.mul_by_nonresidue(); + + let res1 = a.pow([ + 0x69602eb24829a9c2, + 0xdd2b2385cd7b4384, + 0xe81ac1e7808072c9, + 0x10216f7ba065e00d, + ]); + print!("Frob1 = {}\n", res1); + + let res2 = a.pow([ + 0x691c1d8b62747890, + 0x8cab57b9adf8eb00, + 0x18c55d8979dcee49, + 0x56cd8a31d35b6b98, + 0xb7a4a8c966ece684, + 0xe5592c705cbd1cac, + 0x1dde2529566d9b5e, + 0x030c96e827699534, + ]); + print!("Frob2 = {}\n", res2); + + let res3 = a.pow([ + 0x3de6332b975d69b2, + 0x587b5b2bd890e101, + 0x16677d4bec77bbeb, + 0x3fdfdba3309dd645, + 0xdfd4137cd943954b, + 0xcfb035047f38c226, + 0x01b5daf7ac73104c, + 0x4cce8699d63e4f06, + 0x40c0b41264a4b9f4, + 0x7806da9ba1f6d7fb, + 0x110a40708107d53a, + 0x00938e25ae57c88f, + ]); + print!("Frob3 = {}\n", res3); + + let res4 = a.pow([ + 0xabb30419f6bee420, + 0x6ce183e5f2f8d3b9, + 0x9db42a441998ac99, + 0xf74b04aa96e3852f, + 0x64de4542a9807c06, + 0x41f83258fd90abd1, + 0x5ecb5383626aeca3, + 0xb60804ce8f24ca82, + 0xd4b3aadc1344e8bb, + 0x436b70833cb2615b, + 0x1a87eeb627861611, + 0x4e155ea3e5090666, + 0xacfcff9291a10112, + 0x1cba0005b295d5bc, + 0x319c8e7f94b31729, + 0x001be477ceef2455, + ]); + print!("Frob4 = {}\n", res4); + + let res5 = a.pow([ + 0x7501aa71de0e8ea2, + 0x97516fd7ca83b8fe, + 0x7da14ac0c03d4182, + 0xaf5d35dc7f80498d, + 0xb257f7f84fb899e0, + 0x372cb1bd547dbe69, + 0xb6696efbf52d5146, + 0x03b6707d4a42574c, + 0xeae6c62cf1670269, + 0xfe70626cbbb760e9, + 0xfa9d12d01fb42086, + 0xc85218d5a7af23b7, + 0x0a70a73464ed35fb, + 0x878713d44d9a2aca, + 0xc81d8fc5cdfe15ee, + 0xa3ebe919611e544d, + 0xfe46bd734126775c, + 0x06f8a7579371f67f, + 0xa94a371ceb68884c, + 0x000545c441ba73d6, + ]); + print!("Frob5 = {}\n", res5); + + let res6 = a.pow([ + 0xfc4dae0d07a152b0, + 0x3f383f79f9859a0a, + 0x261f0da312f72ab2, + 0x9cc6b2e6efb101d8, + 0xf45a236f76e806da, + 0x7158062cd79d6743, + 0x8adabccc870f23db, + 0x24428ff02b7988c1, + 0x8f55fa0a7ecfa21d, + 0xd5574a8dc73fdcc2, + 0xb86f06772524e5ca, + 0xb4b11653b762bd0f, + 0xb84ec7291c154c58, + 0x2a095f1259f99fb5, + 0x6ccb38fbc9f54a74, + 0x3a3f77faca5c2ea0, + 0x21a469bdd36b9656, + 0x0fa2e41314b53258, + 0xf8ca5207cb9f028e, + 0x489fbf415ec8104e, + 0x711aafe44a1ab611, + 0xfb508020969bab31, + 0xb8b71e4e258cf968, + 0x0000ff25aa9c2350, + ]); + print!("Frob6 = {}\n", res6); +} + +#[test] +fn test_fq2_frobc2() { + use ff::Field; + + let mut a = Fq2::one(); + a.mul_by_nonresidue(); + + let res1 = a.pow([ + 0xd2c05d6490535384, + 0xba56470b9af68708, + 0xd03583cf0100e593, + 0x2042def740cbc01b, + ]); + print!("Frob1 = {}\n", res1); + + let res2 = a.pow([ + 0xd2383b16c4e8f120, + 0x1956af735bf1d600, + 0x318abb12f3b9dc93, + 0xad9b1463a6b6d730, + 0x6f495192cdd9cd08, + 0xcab258e0b97a3959, + 0x3bbc4a52acdb36bd, + 0x06192dd04ed32a68, + ]); + print!("Frob2 = {}\n", res2); + + let res3 = a.pow([ + 0x7bcc66572ebad364, + 0xb0f6b657b121c202, + 0x2ccefa97d8ef77d6, + 0x7fbfb746613bac8a, + 0xbfa826f9b2872a96, + 0x9f606a08fe71844d, + 0x036bb5ef58e62099, + 0x999d0d33ac7c9e0c, + 0x81816824c94973e8, + 0xf00db53743edaff6, + 0x221480e1020faa74, + 0x01271c4b5caf911e, + ]); + print!("Frob3 = {}\n", res3); + + let res4 = a.pow([ + 0x57660833ed7dc840, + 0xd9c307cbe5f1a773, + 0x3b68548833315932, + 0xee9609552dc70a5f, + 0xc9bc8a855300f80d, + 0x83f064b1fb2157a2, + 0xbd96a706c4d5d946, + 0x6c10099d1e499504, + 0xa96755b82689d177, + 0x86d6e1067964c2b7, + 0x350fdd6c4f0c2c22, + 0x9c2abd47ca120ccc, + 0x59f9ff2523420224, + 0x3974000b652bab79, + 0x63391cff29662e52, + 0x0037c8ef9dde48aa, + ]); + print!("Frob4 = {}\n", res4); + + let res5 = a.pow([ + 0xea0354e3bc1d1d44, + 0x2ea2dfaf950771fc, + 0xfb429581807a8305, + 0x5eba6bb8ff00931a, + 0x64afeff09f7133c1, + 0x6e59637aa8fb7cd3, + 0x6cd2ddf7ea5aa28c, + 0x076ce0fa9484ae99, + 0xd5cd8c59e2ce04d2, + 0xfce0c4d9776ec1d3, + 0xf53a25a03f68410d, + 0x90a431ab4f5e476f, + 0x14e14e68c9da6bf7, + 0x0f0e27a89b345594, + 0x903b1f8b9bfc2bdd, + 0x47d7d232c23ca89b, + 0xfc8d7ae6824ceeb9, + 0x0df14eaf26e3ecff, + 0x52946e39d6d11098, + 0x000a8b888374e7ad, + ]); + print!("Frob5 = {}\n", res5); + + let res6 = a.pow([ + 0xf89b5c1a0f42a560, + 0x7e707ef3f30b3415, + 0x4c3e1b4625ee5564, + 0x398d65cddf6203b0, + 0xe8b446deedd00db5, + 0xe2b00c59af3ace87, + 0x15b579990e1e47b6, + 0x48851fe056f31183, + 0x1eabf414fd9f443a, + 0xaaae951b8e7fb985, + 0x70de0cee4a49cb95, + 0x69622ca76ec57a1f, + 0x709d8e52382a98b1, + 0x5412be24b3f33f6b, + 0xd99671f793ea94e8, + 0x747eeff594b85d40, + 0x4348d37ba6d72cac, + 0x1f45c826296a64b0, + 0xf194a40f973e051c, + 0x913f7e82bd90209d, + 0xe2355fc894356c22, + 0xf6a100412d375662, + 0x716e3c9c4b19f2d1, + 0x0001fe4b553846a1, + ]); + print!("Frob6 = {}\n", res6); +} + +#[test] +fn test_fq2_frob12() { + use ff::Field; + + let mut a = Fq2::one(); + a.mul_by_nonresidue(); + + let res1 = a.pow([ + 0x34b017592414d4e1, + 0xee9591c2e6bda1c2, + 0xf40d60f3c0403964, + 0x0810b7bdd032f006, + ]); + print!("Frob1 = {}\n", res1); + + let res2 = a.pow([ + 0x348e0ec5b13a3c48, + 0xc655abdcd6fc7580, + 0x0c62aec4bcee7724, + 0x2b66c518e9adb5cc, + 0x5bd25464b3767342, + 0x72ac96382e5e8e56, + 0x0eef1294ab36cdaf, + 0x01864b7413b4ca9a, + ]); + print!("Frob2 = {}\n", res2); + + let res3 = a.pow([ + 0x9ef31995cbaeb4d9, + 0xac3dad95ec487080, + 0x8b33bea5f63bddf5, + 0x9fefedd1984eeb22, + 0x6fea09be6ca1caa5, + 0x67d81a823f9c6113, + 0x00daed7bd6398826, + 0x2667434ceb1f2783, + 0xa0605a0932525cfa, + 0x3c036d4dd0fb6bfd, + 0x888520384083ea9d, + 0x0049c712d72be447, + ]); + print!("Frob3 = {}\n", res3); + + let res4 = a.pow([ + 0xd5d9820cfb5f7210, + 0xb670c1f2f97c69dc, + 0xceda15220ccc564c, + 0x7ba582554b71c297, + 0xb26f22a154c03e03, + 0xa0fc192c7ec855e8, + 0x2f65a9c1b1357651, + 0xdb04026747926541, + 0xea59d56e09a2745d, + 0xa1b5b8419e5930ad, + 0x0d43f75b13c30b08, + 0x270aaf51f2848333, + 0x567e7fc948d08089, + 0x8e5d0002d94aeade, + 0x98ce473fca598b94, + 0x000df23be777922a, + ]); + print!("Frob4 = {}\n", res4); + + let res5 = a.pow([ + 0x3a80d538ef074751, + 0x4ba8b7ebe541dc7f, + 0xbed0a560601ea0c1, + 0x57ae9aee3fc024c6, + 0xd92bfbfc27dc4cf0, + 0x1b9658deaa3edf34, + 0x5b34b77dfa96a8a3, + 0x81db383ea5212ba6, + 0xf573631678b38134, + 0x7f3831365ddbb074, + 0xfd4e89680fda1043, + 0xe4290c6ad3d791db, + 0x0538539a32769afd, + 0x43c389ea26cd1565, + 0xe40ec7e2e6ff0af7, + 0x51f5f48cb08f2a26, + 0xff235eb9a0933bae, + 0x037c53abc9b8fb3f, + 0x54a51b8e75b44426, + 0x0002a2e220dd39eb, + ]); + print!("Frob5 = {}\n", res5); + + let res6 = a.pow([ + 0x7e26d70683d0a958, + 0x1f9c1fbcfcc2cd05, + 0x130f86d1897b9559, + 0x4e63597377d880ec, + 0xfa2d11b7bb74036d, + 0xb8ac03166bceb3a1, + 0xc56d5e66438791ed, + 0x922147f815bcc460, + 0x47aafd053f67d10e, + 0x6aaba546e39fee61, + 0xdc37833b929272e5, + 0x5a588b29dbb15e87, + 0xdc2763948e0aa62c, + 0x1504af892cfccfda, + 0x36659c7de4faa53a, + 0x1d1fbbfd652e1750, + 0x10d234dee9b5cb2b, + 0x07d172098a5a992c, + 0x7c652903e5cf8147, + 0xa44fdfa0af640827, + 0xb88d57f2250d5b08, + 0x7da840104b4dd598, + 0x5c5b8f2712c67cb4, + 0x00007f92d54e11a8, + ]); + print!("Frob6 = {}\n", res6); + + let res7 = a.pow([ + 0x75e1bff130efc449, + 0xfb4f505fb284ee15, + 0x57b30efd96c492f5, + 0xfdcb862e4e948b59, + 0x3def467dae8887e2, + 0xa47e3f76b755ca8c, + 0xa63f6ea3debc563a, + 0x115d1111a4fc4be2, + 0xc3b2ece674d74549, + 0xb2099a8141cb8830, + 0x120dc0b8ac63867a, + 0xae0245267985fe96, + 0xed38ce9a40128f1d, + 0xeba67d5d8ffa4939, + 0xbff55f706b0de0f5, + 0xb4f3f86e6f982aed, + 0x062675f8a89bd61d, + 0xa1098fb006a9726c, + 0xe974fc0c7b0e5d9c, + 0x0f10af0bdc56fe9e, + 0x628ca855d5d4ac87, + 0x7bd59e7101d9d82d, + 0xed98625bf5dc71aa, + 0x7ab9b78fdc8558f4, + 0x489b4c8564d6f8d2, + 0xd055177a2fbfcd94, + 0x059f68dd1e0cb392, + 0x0000181d8471f268, + ]); + print!("Frob7 = {}\n", res7); + + let res8 = a.pow([ + 0x742e7ca156ec6a20, + 0x3fee59e5c3e8de2e, + 0xfdef69cd295152ef, + 0xe4ad8aece2ce3640, + 0x05308778897ea5eb, + 0x0a4fc046ae1c2e50, + 0xb16faf17473bba4a, + 0xd106751c900aadfa, + 0x115301a6c43ba345, + 0x19f012d49d8a716c, + 0x6b9d91b1c2a56cc5, + 0xb77230690204b675, + 0xf6d68e7229980805, + 0xf4263d3b11784a87, + 0x24bb64e5adeaa33d, + 0x684c4ff325fa1c4d, + 0x79a8c6430472e684, + 0x823af8186da5609c, + 0x2087966741a30941, + 0x1876205eaf407912, + 0xa614d3f14990435e, + 0xd405328435bcc8df, + 0x5afac38bad541421, + 0x0706fb9d17dec3d8, + 0xecc747832c3f5f69, + 0xe231b0ffd6651ed5, + 0x45fa8e7ff2a80f15, + 0xdce48166a2ee0170, + 0x305fc72544895a12, + 0x516ac4b20d800019, + 0x826e9ab28689a4d3, + 0x0000048efbc0eaac, + ]); + print!("Frob8 = {}\n", res8); + + let res9 = a.pow(&[ + 0x64984dce4c07e3c1, + 0x2e2096f441339496, + 0xd50c9bd49d279670, + 0xd52ead3ce3a93422, + 0x426dad5fc6a6779a, + 0x3f9dd6b6f19bc638, + 0x6be503d3981b0db5, + 0x0b222e7512412d2c, + 0x484bd275e77ff0bf, + 0xb357542fb851205b, + 0xd8c995246bf492ff, + 0xc6b92fc3bf2887bc, + 0xcd27cfd0d4499277, + 0x967aa0012f40dcf9, + 0x312baab0f5bc64e3, + 0xe465b3c98a822e05, + 0x3133d12c8828f7b8, + 0x357a20a6a8a244ca, + 0xd40b61719905e5b9, + 0xcc4f1d5e2aed7a75, + 0x7895032e16409563, + 0x536db2a17eb54630, + 0xdd66ae0d2d5ac57e, + 0xe150b5a7f229f541, + 0xd882dbabee789616, + 0x1f380eb8775416ca, + 0x73eca6c1c0abcd02, + 0x8bd4f78c2fe1861e, + 0xc53f421003b18ea2, + 0xcae3f7b5d0591ecb, + 0xbebe6ab21737113e, + 0x838f0df2a5f7f26d, + 0xbc2aa2593b06d88f, + 0x0cb02b95a74a8a0a, + 0x74bd9a7b50725838, + 0x000000dc98741fbf, + ][..]); + print!("Frob9 = {}\n", res9); + + let res10 = a.pow(&[ + 0xed4127472fd6bc68, + 0x72748872e11c4b47, + 0x9c84e64776edc3f8, + 0x008119b96d78b386, + 0xfb0fbff1c5556968, + 0x5009c51f998020be, + 0xd6e688613527a368, + 0xbe4f27942823152c, + 0xd0f09d15c45fe09e, + 0x7eb531158d2bbea5, + 0x51bbe8e71be2cfd1, + 0xbab37561b8c0c7c4, + 0xd9173b5ab551b267, + 0x05fafd9be4c78781, + 0x61883bc8a78540ee, + 0x7fe7aee3dcb694fb, + 0x0e4e85b12b4ac8a8, + 0x9a0aa13a9ab47a86, + 0xd5a3bd591ae12d4b, + 0x5865cbfabbe53b4d, + 0xf98188a9b0cd490f, + 0x3985ef4af715da43, + 0x573661cd006ced38, + 0x95853a6aaa77d5c1, + 0x165d538f0628b55e, + 0x583e75f890f32cac, + 0x5becf43a08a490b9, + 0x63ed4071c1a8087a, + 0x151d41c7701faa25, + 0x1c661c8e4900b051, + 0x581aa0f552590875, + 0x31bf39ff43375aca, + 0xe27c0f3d11310329, + 0x04071459ef3a42c2, + 0x59a2b029be2d6a1f, + 0x30ef71f271cdbf61, + 0xf3774b177f326e78, + 0x976d79b23e8501c5, + 0x9ed0e138633123c9, + 0x00000029b304ecc1, + ][..]); + print!("Frob10 = {}\n", res10); + + let res11 = a.pow(&[ + 0xf4e8c249a335ddb9, + 0x965085c9440aef70, + 0xc16d84a741174aef, + 0xbe1a366b81fe0680, + 0x1c65508409269d2f, + 0x185861e9cd07fb21, + 0x26b682d951220b7a, + 0x09f189f5a7b75876, + 0x0f7133ab3ecff7f0, + 0xbf7d1ada5df0b2fd, + 0x4b0df5207414a4b6, + 0xbf6a6941b58966d3, + 0x6a15cc7b6bb0483a, + 0xc338843b8a236597, + 0xc8d724986bc0856f, + 0x1dcb8b084e928e52, + 0x3645ba97c4af9161, + 0x7d257d1abed180d3, + 0x0a66e85068416bdb, + 0x8b745a2aeb2bd27e, + 0xe34f87ec4949ec06, + 0x6ba47fa06f902fd6, + 0x225cd33864121ed2, + 0xea5d91e41a3b068b, + 0x35d2fbc8b7a05f5c, + 0xe5b1e22f3dcbc837, + 0xa9f7bdbee44d8301, + 0xbb7a57512450e143, + 0x2e2ca4188fd4eb5b, + 0x9d512b5d1e158636, + 0xdd18753b03f38ee8, + 0xbbe44db3214b380e, + 0x4534f7b060cca3d2, + 0xcbb0309736f9df06, + 0xfcb01aba828f0678, + 0xe2e4d5dac5cc7917, + 0x6631e85c4224e136, + 0xb6c334bbd109d480, + 0x2608e9c50edc2cdf, + 0x959dba8288258d16, + 0x00d895fc73e207c8, + 0x6b5ce08dc4a7bf13, + 0xb02a4f252d6a301f, + 0x00000007e1e7a192, + ][..]); + print!("Frob11 = {}\n", res11); +} + +#[test] +fn test_calculate_frob_1() { + let mut a = Fq2::one(); + a.mul_by_nonresidue(); + + // Fq2(u + 9)**(((q^1) - 1) / 3) + + print!("(i + 9) = {}\n", a); +} + +#[test] +fn test_fq2_ordering() { + let mut a = Fq2 { + c0: Fq::zero(), + c1: Fq::zero(), + }; + + let mut b = a.clone(); + + assert!(a.cmp(&b) == Ordering::Equal); + b.c0.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Less); + a.c0.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Equal); + b.c1.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Less); + a.c0.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Less); + a.c1.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Greater); + b.c0.add_assign(&Fq::one()); + assert!(a.cmp(&b) == Ordering::Equal); +} + +#[test] +fn test_fq2_basics() { + assert_eq!( + Fq2 { + c0: Fq::zero(), + c1: Fq::zero(), + }, + Fq2::zero() + ); + assert_eq!( + Fq2 { + c0: Fq::one(), + c1: Fq::zero(), + }, + Fq2::one() + ); + assert!(Fq2::zero().is_zero()); + assert!(!Fq2::one().is_zero()); + assert!( + !Fq2 { + c0: Fq::zero(), + c1: Fq::one(), + }.is_zero() + ); +} + +#[test] +fn test_fq2_squaring() { + use super::fq::FqRepr; + use ff::PrimeField; + + let mut a = Fq2 { + c0: Fq::one(), + c1: Fq::one(), + }; // u + 1 + a.square(); + assert_eq!( + a, + Fq2 { + c0: Fq::zero(), + c1: Fq::from_repr(FqRepr::from(2)).unwrap(), + } + ); // 2u + + let mut a = Fq2 { + c0: Fq::zero(), + c1: Fq::one(), + }; // u + a.square(); + assert_eq!(a, { + let mut neg1 = Fq::one(); + neg1.negate(); + Fq2 { + c0: neg1, + c1: Fq::zero(), + } + }); // -1 + +} + +#[test] +fn test_fq2_legendre() { + use ff::LegendreSymbol::*; + + assert_eq!(Zero, Fq2::zero().legendre()); + // i^2 = -1 + let mut m1 = Fq2::one(); + m1.negate(); + assert_eq!(QuadraticResidue, m1.legendre()); + m1.mul_by_nonresidue(); + assert_eq!(QuadraticNonResidue, m1.legendre()); +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng}; + +#[test] +fn test_fq2_mul_nonresidue() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let mut nine = Fq::one(); + nine.double(); + nine.double(); + nine.double(); + nine.add_assign(&Fq::one()); + let nqr = Fq2 { + c0: nine, + c1: Fq::one(), + }; + + for _ in 0..1000 { + let mut a = Fq2::rand(&mut rng); + let mut b = a; + a.mul_by_nonresidue(); + b.mul_assign(&nqr); + + assert_eq!(a, b); + } +} + +#[test] +fn fq2_field_tests() { + use ff::PrimeField; + + ::tests::field::random_field_tests::<Fq2>(); + ::tests::field::random_sqrt_tests::<Fq2>(); + ::tests::field::random_frobenius_tests::<Fq2, _>(super::fq::Fq::char(), 13); +} diff --git a/src/bn256/fq6.rs b/src/bn256/fq6.rs new file mode 100644 index 0000000..412e4ab --- /dev/null +++ b/src/bn256/fq6.rs @@ -0,0 +1,400 @@ +use super::fq::{FROBENIUS_COEFF_FQ6_C1, FROBENIUS_COEFF_FQ6_C2}; +use super::fq2::Fq2; +use ff::Field; +use rand::{Rand, Rng}; + +/// An element of Fq6, represented by c0 + c1 * v + c2 * v^(2). +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Fq6 { + pub c0: Fq2, + pub c1: Fq2, + pub c2: Fq2, +} + +impl ::std::fmt::Display for Fq6 { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fq6({} + {} * v, {} * v^2)", self.c0, self.c1, self.c2) + } +} + +impl Rand for Fq6 { + fn rand<R: Rng>(rng: &mut R) -> Self { + Fq6 { + c0: rng.gen(), + c1: rng.gen(), + c2: rng.gen(), + } + } +} + +// Here it's getting tough, because extension tower diverges with BLS12 + +// BLS12 (v^3 - ξ) where ξ = u + 1 +// BN256 (v^3 - ξ) where ξ = u + 9 + +impl Fq6 { + + /// Multiply by cubic nonresidue v. + pub fn mul_by_nonresidue(&mut self) { + use std::mem::swap; + swap(&mut self.c0, &mut self.c1); + swap(&mut self.c0, &mut self.c2); + // c0, c1, c2 -> c2, c0, c1 + self.c0.mul_by_nonresidue(); + } + + /// Multiply by cubic nonresidue v. + pub fn mul_by_v(&mut self) { + use std::mem::swap; + swap(&mut self.c0, &mut self.c1); + swap(&mut self.c0, &mut self.c2); + + self.c0.mul_by_xi(); + } + + pub fn mul_by_1(&mut self, c1: &Fq2) { + let mut b_b = self.c1; + b_b.mul_assign(c1); + + let mut t1 = *c1; + { + let mut tmp = self.c1; + tmp.add_assign(&self.c2); + + t1.mul_assign(&tmp); + t1.sub_assign(&b_b); + t1.mul_by_nonresidue(); + } + + let mut t2 = *c1; + { + let mut tmp = self.c0; + tmp.add_assign(&self.c1); + + t2.mul_assign(&tmp); + t2.sub_assign(&b_b); + } + + self.c0 = t1; + self.c1 = t2; + self.c2 = b_b; + } + + pub fn mul_by_01(&mut self, c0: &Fq2, c1: &Fq2) { + let mut a_a = self.c0; + let mut b_b = self.c1; + a_a.mul_assign(c0); + b_b.mul_assign(c1); + + let mut t1 = *c1; + { + let mut tmp = self.c1; + tmp.add_assign(&self.c2); + + t1.mul_assign(&tmp); + t1.sub_assign(&b_b); + t1.mul_by_nonresidue(); + t1.add_assign(&a_a); + } + + let mut t3 = *c0; + { + let mut tmp = self.c0; + tmp.add_assign(&self.c2); + + t3.mul_assign(&tmp); + t3.sub_assign(&a_a); + t3.add_assign(&b_b); + } + + let mut t2 = *c0; + t2.add_assign(c1); + { + let mut tmp = self.c0; + tmp.add_assign(&self.c1); + + t2.mul_assign(&tmp); + t2.sub_assign(&a_a); + t2.sub_assign(&b_b); + } + + self.c0 = t1; + self.c1 = t2; + self.c2 = t3; + } +} + +impl Field for Fq6 { + fn zero() -> Self { + Fq6 { + c0: Fq2::zero(), + c1: Fq2::zero(), + c2: Fq2::zero(), + } + } + + fn one() -> Self { + Fq6 { + c0: Fq2::one(), + c1: Fq2::zero(), + c2: Fq2::zero(), + } + } + + fn is_zero(&self) -> bool { + self.c0.is_zero() && self.c1.is_zero() && self.c2.is_zero() + } + + fn double(&mut self) { + self.c0.double(); + self.c1.double(); + self.c2.double(); + } + + fn negate(&mut self) { + self.c0.negate(); + self.c1.negate(); + self.c2.negate(); + } + + fn add_assign(&mut self, other: &Self) { + self.c0.add_assign(&other.c0); + self.c1.add_assign(&other.c1); + self.c2.add_assign(&other.c2); + } + + fn sub_assign(&mut self, other: &Self) { + self.c0.sub_assign(&other.c0); + self.c1.sub_assign(&other.c1); + self.c2.sub_assign(&other.c2); + } + + fn frobenius_map(&mut self, power: usize) { + self.c0.frobenius_map(power); + self.c1.frobenius_map(power); + self.c2.frobenius_map(power); + + self.c1.mul_assign(&FROBENIUS_COEFF_FQ6_C1[power % 6]); + self.c2.mul_assign(&FROBENIUS_COEFF_FQ6_C2[power % 6]); + } + + fn square(&mut self) { + // s0 = a^2 + let mut s0 = self.c0; + s0.square(); + // s1 = 2ab + let mut ab = self.c0; + ab.mul_assign(&self.c1); + let mut s1 = ab; + s1.double(); + // s2 = (a - b + c)^2 + let mut s2 = self.c0; + s2.sub_assign(&self.c1); + s2.add_assign(&self.c2); + s2.square(); + // bc + let mut bc = self.c1; + bc.mul_assign(&self.c2); + // s3 = 2bc + let mut s3 = bc; + s3.double(); + // s4 = c^2 + let mut s4 = self.c2; + s4.square(); + + // new c0 = 2bc.mul_by_xi + a^2 + self.c0 = s3; + self.c0.mul_by_nonresidue(); + // self.c0.mul_by_xi(); + self.c0.add_assign(&s0); + + // new c1 = (c^2).mul_by_xi + 2ab + self.c1 = s4; + self.c1.mul_by_nonresidue(); + // self.c1.mul_by_xi(); + self.c1.add_assign(&s1); + + // new c2 = 2ab + (a - b + c)^2 + 2bc - a^2 - c^2 = b^2 + 2ac + self.c2 = s1; + self.c2.add_assign(&s2); + self.c2.add_assign(&s3); + self.c2.sub_assign(&s0); + self.c2.sub_assign(&s4); + } + + fn mul_assign(&mut self, other: &Self) { + let mut a_a = self.c0; + let mut b_b = self.c1; + let mut c_c = self.c2; + a_a.mul_assign(&other.c0); + b_b.mul_assign(&other.c1); + c_c.mul_assign(&other.c2); + + let mut t1 = other.c1; + t1.add_assign(&other.c2); + { + let mut tmp = self.c1; + tmp.add_assign(&self.c2); + + t1.mul_assign(&tmp); + t1.sub_assign(&b_b); + t1.sub_assign(&c_c); + t1.mul_by_nonresidue(); + t1.add_assign(&a_a); + } + + let mut t3 = other.c0; + t3.add_assign(&other.c2); + { + let mut tmp = self.c0; + tmp.add_assign(&self.c2); + + t3.mul_assign(&tmp); + t3.sub_assign(&a_a); + t3.add_assign(&b_b); + t3.sub_assign(&c_c); + } + + let mut t2 = other.c0; + t2.add_assign(&other.c1); + { + let mut tmp = self.c0; + tmp.add_assign(&self.c1); + + t2.mul_assign(&tmp); + t2.sub_assign(&a_a); + t2.sub_assign(&b_b); + c_c.mul_by_nonresidue(); + t2.add_assign(&c_c); + } + + self.c0 = t1; + self.c1 = t2; + self.c2 = t3; + } + + fn inverse(&self) -> Option<Self> { + let mut c0 = self.c2; + c0.mul_by_nonresidue(); + c0.mul_assign(&self.c1); + c0.negate(); + { + let mut c0s = self.c0; + c0s.square(); + c0.add_assign(&c0s); + } + let mut c1 = self.c2; + c1.square(); + c1.mul_by_nonresidue(); + { + let mut c01 = self.c0; + c01.mul_assign(&self.c1); + c1.sub_assign(&c01); + } + let mut c2 = self.c1; + c2.square(); + { + let mut c02 = self.c0; + c02.mul_assign(&self.c2); + c2.sub_assign(&c02); + } + + let mut tmp1 = self.c2; + tmp1.mul_assign(&c1); + let mut tmp2 = self.c1; + tmp2.mul_assign(&c2); + tmp1.add_assign(&tmp2); + tmp1.mul_by_nonresidue(); + tmp2 = self.c0; + tmp2.mul_assign(&c0); + tmp1.add_assign(&tmp2); + + match tmp1.inverse() { + Some(t) => { + let mut tmp = Fq6 { + c0: t, + c1: t, + c2: t, + }; + tmp.c0.mul_assign(&c0); + tmp.c1.mul_assign(&c1); + tmp.c2.mul_assign(&c2); + + Some(tmp) + } + None => None, + } + } +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng}; + +#[test] +fn test_fq6_mul_nonresidue() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let nqr = Fq6 { + c0: Fq2::zero(), + c1: Fq2::one(), + c2: Fq2::zero(), + }; + + for _ in 0..1000 { + let mut a = Fq6::rand(&mut rng); + let mut b = a; + a.mul_by_nonresidue(); + b.mul_assign(&nqr); + + assert_eq!(a, b); + } +} + +#[test] +fn test_fq6_mul_by_1() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let c1 = Fq2::rand(&mut rng); + let mut a = Fq6::rand(&mut rng); + let mut b = a; + + a.mul_by_1(&c1); + b.mul_assign(&Fq6 { + c0: Fq2::zero(), + c1: c1, + c2: Fq2::zero(), + }); + + assert_eq!(a, b); + } +} + +#[test] +fn test_fq6_mul_by_01() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let c0 = Fq2::rand(&mut rng); + let c1 = Fq2::rand(&mut rng); + let mut a = Fq6::rand(&mut rng); + let mut b = a; + + a.mul_by_01(&c0, &c1); + b.mul_assign(&Fq6 { + c0: c0, + c1: c1, + c2: Fq2::zero(), + }); + + assert_eq!(a, b); + } +} + +#[test] +fn fq6_field_tests() { + use ff::PrimeField; + + ::tests::field::random_field_tests::<Fq6>(); + ::tests::field::random_frobenius_tests::<Fq6, _>(super::fq::Fq::char(), 13); +} diff --git a/src/bn256/fr.rs b/src/bn256/fr.rs new file mode 100644 index 0000000..e713ac6 --- /dev/null +++ b/src/bn256/fr.rs @@ -0,0 +1,11 @@ +use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; + +#[derive(PrimeField)] +#[PrimeFieldModulus = "21888242871839275222246405745257275088548364400416034343698204186575808495617"] +#[PrimeFieldGenerator = "7"] +pub struct Fr(FrRepr); + +#[test] +fn test_roots_of_unity() { + assert_eq!(Fr::S, 28); +} \ No newline at end of file diff --git a/src/bn256/mod.rs b/src/bn256/mod.rs new file mode 100644 index 0000000..b5688ab --- /dev/null +++ b/src/bn256/mod.rs @@ -0,0 +1,599 @@ +mod ec; +mod fq; +mod fq12; +mod fq2; +mod fq6; +mod fr; + +// #[cfg(test)] +// mod tests; + +pub use self::ec::{ + G1, G1Affine, G1Compressed, G1Prepared, G1Uncompressed, + G2, G2Affine, G2Compressed, G2Prepared, G2Uncompressed, +}; +pub use self::fq::{Fq, FqRepr, FROBENIUS_COEFF_FQ6_C1, XI_TO_Q_MINUS_1_OVER_2}; +pub use self::fq12::Fq12; +pub use self::fq2::Fq2; +pub use self::fq6::Fq6; +pub use self::fr::{Fr, FrRepr}; + +use super::{CurveAffine, Engine}; + +use ff::{Field, ScalarEngine}; + +#[derive(Clone, Debug)] +pub struct Bn256; + +// U value that originates this particular curve +pub const BN_U: u64 = 4965661367192848881; + +// // 6U+2 for in NAF form +pub const SIX_U_PLUS_2_NAF : [i8; 65] = [0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1]; + + +impl ScalarEngine for Bn256 { + type Fr = Fr; +} + +impl Engine for Bn256 { + type G1 = G1; + type G1Affine = G1Affine; + type G2 = G2; + type G2Affine = G2Affine; + type Fq = Fq; + type Fqe = Fq2; + type Fqk = Fq12; + + fn miller_loop<'a, I>(i: I) -> Self::Fqk + where + I: IntoIterator< + Item = &'a ( + &'a <Self::G1Affine as CurveAffine>::Prepared, + &'a <Self::G2Affine as CurveAffine>::Prepared, + ), + >, + { + let mut pairs = vec![]; + for &(p, q) in i { + if !p.is_zero() && !q.is_zero() { + pairs.push((p, q.coeffs.iter())); + } + } + + // Final steps of the line function on prepared coefficients + fn ell(f: &mut Fq12, coeffs: &(Fq2, Fq2, Fq2), p: &G1Affine) { + let mut c0 = coeffs.0; + let mut c1 = coeffs.1; + + c0.c0.mul_assign(&p.y); + c0.c1.mul_assign(&p.y); + + c1.c0.mul_assign(&p.x); + c1.c1.mul_assign(&p.x); + + // Sparse multiplication in Fq12 + + f.mul_by_034(&c0, &c1, &coeffs.2); + } + + let mut f = Fq12::one(); + + for i in (1..SIX_U_PLUS_2_NAF.len()).rev() { + if i != SIX_U_PLUS_2_NAF.len() - 1 { + f.square(); + } + for &mut (p, ref mut coeffs) in &mut pairs { + ell(&mut f, coeffs.next().unwrap(), &p.0); + } + let x = SIX_U_PLUS_2_NAF[i-1]; + match x { + 1 => { + for &mut (p, ref mut coeffs) in &mut pairs { + ell(&mut f, coeffs.next().unwrap(), &p.0); + } + } + -1 => { + for &mut (p, ref mut coeffs) in &mut pairs { + ell(&mut f, coeffs.next().unwrap(), &p.0); + } + } + _ => { + continue + } + } + } + + // two additional steps: for q1 and minus q2 + + for &mut (p, ref mut coeffs) in &mut pairs { + ell(&mut f, coeffs.next().unwrap(), &p.0); + } + + for &mut (p, ref mut coeffs) in &mut pairs { + ell(&mut f, coeffs.next().unwrap(), &p.0); + } + + for &mut (_p, ref mut coeffs) in &mut pairs { + assert_eq!(coeffs.next(), None); + } + + f + } + + fn final_exponentiation(r: &Fq12) -> Option<Fq12> { + let mut f1 = *r; + f1.conjugate(); + + match r.inverse() { + Some(mut f2) => { + let mut r = f1; + r.mul_assign(&f2); + f2 = r; + r.frobenius_map(2); + r.mul_assign(&f2); + + fn exp_by_x(f: &mut Fq12, x: u64) { + *f = f.pow(&[x]); + } + + let x = BN_U; + + let mut fp = r; + fp.frobenius_map(1); + + let mut fp2 = r; + fp2.frobenius_map(2); + let mut fp3 = fp2; + fp3.frobenius_map(1); + + let mut fu = r; + exp_by_x(&mut fu, x); + + let mut fu2 = fu; + exp_by_x(&mut fu2, x); + + let mut fu3 = fu2; + exp_by_x(&mut fu3, x); + + let mut y3 = fu; + y3.frobenius_map(1); + + let mut fu2p = fu2; + fu2p.frobenius_map(1); + + let mut fu3p = fu3; + fu3p.frobenius_map(1); + + let mut y2 = fu2; + y2.frobenius_map(2); + + let mut y0 = fp; + y0.mul_assign(&fp2); + y0.mul_assign(&fp3); + + let mut y1 = r; + y1.conjugate(); + + let mut y5 = fu2; + y5.conjugate(); + + y3.conjugate(); + + let mut y4 = fu; + y4.mul_assign(&fu2p); + y4.conjugate(); + + let mut y6 = fu3; + y6.mul_assign(&fu3p); + y6.conjugate(); + + + y6.square(); + y6.mul_assign(&y4); + y6.mul_assign(&y5); + + let mut t1 = y3; + t1.mul_assign(&y5); + t1.mul_assign(&y6); + + y6.mul_assign(&y2); + + t1.square(); + t1.mul_assign(&y6); + t1.square(); + + let mut t0 = t1; + t0.mul_assign(&y1); + + t1.mul_assign(&y0); + + t0.square(); + t0.mul_assign(&t1); + + Some(t0) + } + None => None, + } + } + +} + +impl G2Prepared { + pub fn is_zero(&self) -> bool { + self.infinity + } + + pub fn from_affine(q: G2Affine) -> Self { + if q.is_zero() { + return G2Prepared { + coeffs: vec![], + infinity: true, + }; + } + + fn doubling_step(r: &mut G2) -> (Fq2, Fq2, Fq2) { + // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf + let mut tmp0 = r.x; + tmp0.square(); + + let mut tmp1 = r.y; + tmp1.square(); + + let mut tmp2 = tmp1; + tmp2.square(); + + let mut tmp3 = tmp1; + tmp3.add_assign(&r.x); + tmp3.square(); + tmp3.sub_assign(&tmp0); + tmp3.sub_assign(&tmp2); + tmp3.double(); + + let mut tmp4 = tmp0; + tmp4.double(); + tmp4.add_assign(&tmp0); + + let mut tmp6 = r.x; + tmp6.add_assign(&tmp4); + + let mut tmp5 = tmp4; + tmp5.square(); + + let mut zsquared = r.z; + zsquared.square(); + + r.x = tmp5; + r.x.sub_assign(&tmp3); + r.x.sub_assign(&tmp3); + + r.z.add_assign(&r.y); + r.z.square(); + r.z.sub_assign(&tmp1); + r.z.sub_assign(&zsquared); + + r.y = tmp3; + r.y.sub_assign(&r.x); + r.y.mul_assign(&tmp4); + + tmp2.double(); + tmp2.double(); + tmp2.double(); + + r.y.sub_assign(&tmp2); + + // up to here everything was by algorith, line 11 + // use R instead of new T + + // tmp3 is the first part of line 12 + tmp3 = tmp4; + tmp3.mul_assign(&zsquared); + tmp3.double(); + tmp3.negate(); + + // tmp6 is from line 14 + tmp6.square(); + tmp6.sub_assign(&tmp0); + tmp6.sub_assign(&tmp5); + + tmp1.double(); + tmp1.double(); + + tmp6.sub_assign(&tmp1); + + // tmp0 is the first part of line 16 + tmp0 = r.z; + tmp0.mul_assign(&zsquared); + tmp0.double(); + + (tmp0, tmp3, tmp6) + } + + fn addition_step(r: &mut G2, q: &G2Affine) -> (Fq2, Fq2, Fq2) { + // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf + let mut zsquared = r.z; + zsquared.square(); + + let mut ysquared = q.y; + ysquared.square(); + + // t0 corresponds to line 1 + let mut t0 = zsquared; + t0.mul_assign(&q.x); + + // t1 corresponds to lines 2 and 3 + let mut t1 = q.y; + t1.add_assign(&r.z); + t1.square(); + t1.sub_assign(&ysquared); + t1.sub_assign(&zsquared); + t1.mul_assign(&zsquared); + + // t2 corresponds to line 4 + let mut t2 = t0; + t2.sub_assign(&r.x); + + // t3 corresponds to line 5 + let mut t3 = t2; + t3.square(); + + // t4 corresponds to line 6 + let mut t4 = t3; + t4.double(); + t4.double(); + + // t5 corresponds to line 7 + let mut t5 = t4; + t5.mul_assign(&t2); + + // t6 corresponds to line 8 + let mut t6 = t1; + t6.sub_assign(&r.y); + t6.sub_assign(&r.y); + + // t9 corresponds to line 9 + let mut t9 = t6; + t9.mul_assign(&q.x); + + // corresponds to line 10 + let mut t7 = t4; + t7.mul_assign(&r.x); + + // corresponds to line 11, but assigns to r.x instead of T.x + r.x = t6; + r.x.square(); + r.x.sub_assign(&t5); + r.x.sub_assign(&t7); + r.x.sub_assign(&t7); + + // corresponds to line 12, but assigns to r.z instead of T.z + r.z.add_assign(&t2); + r.z.square(); + r.z.sub_assign(&zsquared); + r.z.sub_assign(&t3); + + // corresponds to line 13 + let mut t10 = q.y; + t10.add_assign(&r.z); + + // corresponds to line 14 + let mut t8 = t7; + t8.sub_assign(&r.x); + t8.mul_assign(&t6); + + // corresponds to line 15 + t0 = r.y; + t0.mul_assign(&t5); + t0.double(); + + // corresponds to line 12, but assigns to r.y instead of T.y + r.y = t8; + r.y.sub_assign(&t0); + + // corresponds to line 17 + t10.square(); + t10.sub_assign(&ysquared); + + let mut ztsquared = r.z; + ztsquared.square(); + + t10.sub_assign(&ztsquared); + + // corresponds to line 18 + t9.double(); + t9.sub_assign(&t10); + + // t10 = 2*Zt from Algo 27, line 19 + t10 = r.z; + t10.double(); + + // t1 = first multiplicator of line 21 + t6.negate(); + + t1 = t6; + t1.double(); + + // t9 corresponds to t9 from Algo 27 + (t10, t1, t9) + } + + let mut coeffs = vec![]; + let mut r: G2 = q.into(); + + let mut negq = q; + negq.negate(); + + for i in (1..SIX_U_PLUS_2_NAF.len()).rev() { + coeffs.push(doubling_step(& mut r)); + let x = SIX_U_PLUS_2_NAF[i-1]; + match x { + 1 => { + coeffs.push(addition_step(&mut r, &q)); + } + -1 => { + coeffs.push(addition_step(&mut r, &negq)); + } + _ => continue, + } + } + + let mut q1 = q; + + q1.x.c1.negate(); + q1.x.mul_assign(&FROBENIUS_COEFF_FQ6_C1[1]); + + q1.y.c1.negate(); + q1.y.mul_assign(&XI_TO_Q_MINUS_1_OVER_2); + + coeffs.push(addition_step(&mut r, &q1)); + + let mut minusq2 = q; + minusq2.x.mul_assign(&FROBENIUS_COEFF_FQ6_C1[2]); + + coeffs.push(addition_step(&mut r, &minusq2)); + + G2Prepared { + coeffs, + infinity: false, + } + } +} + + +#[cfg(test)] +use rand::{Rand, SeedableRng, XorShiftRng}; + +#[test] +fn test_pairing() { + use {CurveProjective}; + let mut g1 = G1::one(); + + let mut g2 = G2::one(); + g2.double(); + + let pair12 = Bn256::pairing(g1, g2); + + g1 = G1::one(); + g1.double(); + + g2 = G2::one(); + + let pair21 = Bn256::pairing(g1, g2); + + assert_eq!(pair12, pair21); + + // print!("GT = {}\n", pair12); + + g1 = G1::one(); + g1.double(); + g1.double(); + + let pair41 = Bn256::pairing(g1, g2); + + g1 = G1::one(); + g1.double(); + + g2.double(); + + let pair22 = Bn256::pairing(g1, g2); + + assert_eq!(pair41, pair22); + + g1 = G1::one(); + g1.double(); + g1.add_assign(&G1::one()); + + g2 = G2::one(); + g2.double(); + + let pair32 = Bn256::pairing(g1, g2); + + g2 = G2::one(); + g2.double(); + g2.add_assign(&G2::one()); + + g1 = G1::one(); + g1.double(); + + let pair23 = Bn256::pairing(g1, g2); + + assert_eq!(pair23, pair32); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = Fr::rand(&mut rng); + let b = Fr::rand(&mut rng); + + let mut g1 = G1::one(); + g1.mul_assign(a); + + let mut g2 = G2::one(); + g1.mul_assign(b); + + let pair_ab = Bn256::pairing(g1, g2); + + g1 = G1::one(); + g1.mul_assign(b); + + g2 = G2::one(); + g1.mul_assign(a); + + let pair_ba = Bn256::pairing(g1, g2); + + assert_eq!(pair_ab, pair_ba); + + } + +} + +#[test] +fn random_bilinearity_tests() { + use {CurveProjective}; + use ff::PrimeField; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut a = G1::one(); + let ka = Fr::rand(&mut rng); + a.mul_assign(ka); + let mut b = G2::one(); + let kb = Fr::rand(&mut rng); + b.mul_assign(kb); + + let c = Fr::rand(&mut rng); + let d = Fr::rand(&mut rng); + + let mut ac = a; + ac.mul_assign(c); + + let mut ad = a; + ad.mul_assign(d); + + let mut bc = b; + bc.mul_assign(c); + + let mut bd = b; + bd.mul_assign(d); + + let acbd = Bn256::pairing(ac, bd); + let adbc = Bn256::pairing(ad, bc); + + let mut cd = c; + cd.mul_assign(&d); + + let abcd = Bn256::pairing(a, b).pow(cd.into_repr()); + + assert_eq!(acbd, adbc); + assert_eq!(acbd, abcd); + } +} + +#[test] +fn bn256_engine_tests() { + ::tests::engine::engine_tests::<Bn256>(); +} diff --git a/src/lib.rs b/src/lib.rs index bbced76..4d2051f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,17 +2,16 @@ // common mistakes or strange code patterns. If the `cargo-clippy` feature // is provided, all compiler warnings are prohibited. #![cfg_attr(feature = "cargo-clippy", deny(warnings))] -#![cfg_attr(feature = "cargo-clippy", allow(inline_always))] -#![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] -#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))] -#![cfg_attr(feature = "cargo-clippy", allow(many_single_char_names))] -#![cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] -#![cfg_attr(feature = "cargo-clippy", allow(write_literal))] +// #![cfg_attr(feature = "cargo-clippy", allow(inline_always))] +// #![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] +// #![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))] +// #![cfg_attr(feature = "cargo-clippy", allow(many_single_char_names))] +// #![cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] +// #![cfg_attr(feature = "cargo-clippy", allow(write_literal))] // Force public structures to implement Debug #![deny(missing_debug_implementations)] extern crate byteorder; -#[macro_use] extern crate ff; extern crate rand; @@ -20,6 +19,7 @@ extern crate rand; pub mod tests; pub mod bls12_381; +pub mod bn256; mod wnaf; pub use self::wnaf::Wnaf; diff --git a/src/tests/curve.rs b/src/tests/curve.rs index bb0406c..a398a73 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -61,7 +61,6 @@ pub fn curve_tests<G: CurveProjective>() { random_multiplication_tests::<G>(); random_doubling_tests::<G>(); random_negation_tests::<G>(); - random_transformation_tests::<G>(); random_wnaf_tests::<G>(); random_encoding_tests::<G::Affine>(); } @@ -345,7 +344,46 @@ fn random_addition_tests<G: CurveProjective>() { } } -fn random_transformation_tests<G: CurveProjective>() { +pub fn random_transformation_tests<G: CurveProjective>() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let g = G::rand(&mut rng); + let g_affine = g.into_affine(); + let g_projective = g_affine.into_projective(); + assert_eq!(g, g_projective); + } + + // Batch normalization + for _ in 0..10 { + let mut v = (0..1000).map(|_| G::rand(&mut rng)).collect::<Vec<_>>(); + + use rand::distributions::{IndependentSample, Range}; + let between = Range::new(0, 1000); + // Sprinkle in some normalized points + for _ in 0..5 { + v[between.ind_sample(&mut rng)] = G::zero(); + } + for _ in 0..5 { + let s = between.ind_sample(&mut rng); + v[s] = v[s].into_affine().into_projective(); + } + + let expected_v = v + .iter() + .map(|v| v.into_affine().into_projective()) + .collect::<Vec<_>>(); + G::batch_normalization(&mut v); + + for i in &v { + assert!(i.is_normalized()); + } + + assert_eq!(v, expected_v); + } +} + +pub fn random_transformation_tests_with_cofactor<G: CurveProjective>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); for _ in 0..1000 { @@ -360,6 +398,7 @@ fn random_transformation_tests<G: CurveProjective>() { let mut v = (0..1000).map(|_| G::rand(&mut rng)).collect::<Vec<_>>(); for i in &v { + assert!(!i.is_normalized()); } From 3279e322eb9239e7f6e98f0abb9421e4e7f37c25 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Wed, 9 Jan 2019 20:34:17 +0200 Subject: [PATCH 125/140] update to a new version of ff crate with serde support --- Cargo.toml | 12 +++++++++--- src/bn256/fr.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 +++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d3e885..fb62b8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,12 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.15.0" +version = "0.15.1" authors = [ "Sean Bowe <ewillbefull@gmail.com>", "Jack Grigg <jack@z.cash>", - "Alex Vlasov <alex.m.vlasov@gmail.com>" + "Alex Vlasov <alex.m.vlasov@gmail.com>", + "Alex Gluchowski <alex@gluchowski.net>" ] license = "MIT/Apache-2.0" @@ -18,7 +19,12 @@ repository = "https://github.com/matterinc/pairing" [dependencies] rand = "0.4" byteorder = "1" -ff = { version = "0.4", features = ["derive"] } +ff = { git = 'https://github.com/matterinc/ff', features = ["derive"] } +#ff = { path = "../ff", features = ["derive"] } +serde = "1.0.80" +serde_derive = "1.0.80" +serde_json = "1.0.33" +hex = "0.3.2" [features] unstable-features = ["expose-arith"] diff --git a/src/bn256/fr.rs b/src/bn256/fr.rs index e713ac6..97715d8 100644 --- a/src/bn256/fr.rs +++ b/src/bn256/fr.rs @@ -5,7 +5,49 @@ use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; #[PrimeFieldGenerator = "7"] pub struct Fr(FrRepr); +#[test] +fn test_to_hex() { + assert_eq!(Fr::one().to_hex(), "0000000000000000000000000000000000000000000000000000000000000001"); +} + +#[test] +fn test_fr_from_hex() { + let fr = Fr::from_hex("0000000000000000000000000000000000000000000000000000000000000001").unwrap(); + assert_eq!(fr, Fr::one()); + + let fr = Fr::from_hex("0x0000000000000000000000000000000000000000000000000000000000000001").unwrap(); + assert_eq!(fr, Fr::one()); + + let fr = Fr::from_hex("0x01").unwrap(); + assert_eq!(fr, Fr::one()); + + let fr = Fr::from_hex("0x00").unwrap(); + assert_eq!(fr, Fr::zero()); + + let fr = Fr::from_hex("00").unwrap(); + assert_eq!(fr, Fr::zero()); +} + +#[test] +fn test_fr_serialize() { + assert_eq!( + serde_json::to_string(&Fr::one()).unwrap(), + r#""0x0000000000000000000000000000000000000000000000000000000000000001""#); +} + +#[test] +fn test_fr_deserialize() { + let json = r#""0x0000000000000000000000000000000000000000000000000000000000000001""#; + let fr: Fr = serde_json::from_str(json).unwrap(); + assert_eq!(fr, Fr::one()); +} + #[test] fn test_roots_of_unity() { assert_eq!(Fr::S, 28); +} + +#[test] +fn test_default() { + assert_eq!(Fr::default(), Fr::zero()); } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4d2051f..96f3fa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,11 @@ extern crate byteorder; extern crate ff; extern crate rand; +extern crate hex; +extern crate serde; +#[macro_use] +extern crate serde_derive; + #[cfg(test)] pub mod tests; From 59a1d70c919c7fd37cf58061b3b8f656cef5ce93 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Wed, 23 Jan 2019 21:00:28 +0300 Subject: [PATCH 126/140] remove remnants --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fb62b8b..1510a15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ repository = "https://github.com/matterinc/pairing" rand = "0.4" byteorder = "1" ff = { git = 'https://github.com/matterinc/ff', features = ["derive"] } -#ff = { path = "../ff", features = ["derive"] } serde = "1.0.80" serde_derive = "1.0.80" serde_json = "1.0.33" From 84b57df3259c2f41d97744792cc89269e261d44e Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Tue, 5 Feb 2019 15:49:31 +0300 Subject: [PATCH 127/140] generate G2 with unknown discrete logs --- Cargo.toml | 2 +- src/bn256/ec.rs | 61 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1510a15..80e69ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.15.1" +version = "0.15.2" authors = [ "Sean Bowe <ewillbefull@gmail.com>", "Jack Grigg <jack@z.cash>", diff --git a/src/bn256/ec.rs b/src/bn256/ec.rs index 13342d5..f4a3a63 100644 --- a/src/bn256/ec.rs +++ b/src/bn256/ec.rs @@ -885,6 +885,10 @@ pub mod g1 { } impl G1Affine { + // fn scale_by_cofactor(&self) -> G1 { + // self.into_projective() + // } + fn get_generator() -> Self { G1Affine { x: super::super::fq::G1_GENERATOR_X, @@ -1023,20 +1027,45 @@ pub mod g2 { G1Affine ); + // impl Rand for G2 { + // fn rand<R: Rng>(rng: &mut R) -> Self { + + // let mut r = G2::one(); + // let k = Fr::rand(rng); + // r.mul_assign(k); + // return r; + // } + // } + + // impl Rand for G2Affine { + // fn rand<R: Rng>(rng: &mut R) -> Self { + // let mut r = G2::one(); + // let k = Fr::rand(rng); + // r.mul_assign(k); + // return r.into_affine(); + // } + // } + impl Rand for G2 { fn rand<R: Rng>(rng: &mut R) -> Self { - let mut r = G2::one(); - let k = Fr::rand(rng); - r.mul_assign(k); - return r; + loop { + let x = rng.gen(); + let greatest = rng.gen(); + + if let Some(p) = G2Affine::get_point_from_x(x, greatest) { + if !p.is_zero() { + if p.is_on_curve() { + return p.scale_by_cofactor(); + } + } + } + } } } impl Rand for G2Affine { fn rand<R: Rng>(rng: &mut R) -> Self { - let mut r = G2::one(); - let k = Fr::rand(rng); - r.mul_assign(k); + let r = G2::rand(rng); return r.into_affine(); } } @@ -1273,6 +1302,18 @@ pub mod g2 { } impl G2Affine { + fn scale_by_cofactor(&self) -> G2 { + // G2 cofactor = 2p - n = 2q - r + // 0x30644e72e131a029b85045b68181585e06ceecda572a2489345f2299c0f9fa8d + let cofactor = BitIterator::new([ + 0x345f2299c0f9fa8d, + 0x06ceecda572a2489, + 0xb85045b68181585e, + 0x30644e72e131a029, + ]); + self.mul_bits(cofactor) + } + fn get_generator() -> Self { G2Affine { x: Fq2 { @@ -1586,9 +1627,9 @@ pub mod g2 { r.mul_assign(order); assert!(r.is_zero()); - // let mut t = G2::rand(&mut rng); - // t.mul_assign(order); - // assert!(t.is_zero()); + let mut t = G2::rand(&mut rng); + t.mul_assign(order); + assert!(t.is_zero()); } } From 443e40de5fae5f61bc8b5518fbacede73d04d6f8 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Mon, 4 Mar 2019 19:35:10 +0300 Subject: [PATCH 128/140] prefer to re-expose ff in pairing to avoid dependency hell --- Cargo.toml | 8 ++++---- src/lib.rs | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80e69ee..822411a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.15.2" +version = "0.16.0" authors = [ "Sean Bowe <ewillbefull@gmail.com>", "Jack Grigg <jack@z.cash>", @@ -13,13 +13,13 @@ license = "MIT/Apache-2.0" description = "Pairing-friendly elliptic curve library" documentation = "https://docs.rs/pairing/" -homepage = "https://github.com/matterinc/pairing" -repository = "https://github.com/matterinc/pairing" +homepage = "https://github.com/matter-labs/pairing" +repository = "https://github.com/matter-labs/pairing" [dependencies] rand = "0.4" byteorder = "1" -ff = { git = 'https://github.com/matterinc/ff', features = ["derive"] } +ff = { git = 'https://github.com/matterinc/ff', features = ["derive"], tag = "0.5"} serde = "1.0.80" serde_derive = "1.0.80" serde_json = "1.0.33" diff --git a/src/lib.rs b/src/lib.rs index 96f3fa0..985e85b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,8 @@ extern crate serde_derive; #[cfg(test)] pub mod tests; +pub use ff::*; + pub mod bls12_381; pub mod bn256; From 0c5a295c8a6058dafd39acc1525675cae4abd155 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Mon, 4 Mar 2019 19:49:14 +0300 Subject: [PATCH 129/140] reexport properly --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 985e85b..0fd4611 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,6 @@ #![deny(missing_debug_implementations)] extern crate byteorder; -extern crate ff; extern crate rand; extern crate hex; @@ -23,6 +22,7 @@ extern crate serde_derive; #[cfg(test)] pub mod tests; +pub extern crate ff; pub use ff::*; pub mod bls12_381; From 67f1b080c29463188515f8e11da583114ebefb7e Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Mon, 4 Mar 2019 19:49:52 +0300 Subject: [PATCH 130/140] update version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 822411a..66a9d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.16.0" +version = "0.16.1" authors = [ "Sean Bowe <ewillbefull@gmail.com>", "Jack Grigg <jack@z.cash>", From c2af46cac3e6ebc8e1e1f37bb993e5e6c7f689d1 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Tue, 5 Mar 2019 09:47:43 +0100 Subject: [PATCH 131/140] fix reexports finally --- Cargo.toml | 2 +- src/lib.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66a9d64..fcd21c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing" # Remember to change version string in README.md. -version = "0.16.1" +version = "0.16.2" authors = [ "Sean Bowe <ewillbefull@gmail.com>", "Jack Grigg <jack@z.cash>", diff --git a/src/lib.rs b/src/lib.rs index 0fd4611..348f240 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,11 @@ extern crate serde_derive; #[cfg(test)] pub mod tests; -pub extern crate ff; -pub use ff::*; +extern crate ff as imported_ff; + +pub mod ff { + pub use imported_ff::*; +} pub mod bls12_381; pub mod bn256; From d2c8b93fe96073344d45a6fe6d3bc6998712cb6c Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Sun, 31 Mar 2019 09:00:01 +0300 Subject: [PATCH 132/140] prepare for publishing --- Cargo.toml | 7 ++++--- README.md | 2 ++ src/lib.rs | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fcd21c4..e7b64b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "pairing" +name = "pairing_ce" # Remember to change version string in README.md. -version = "0.16.2" +version = "0.17.0" authors = [ "Sean Bowe <ewillbefull@gmail.com>", "Jack Grigg <jack@z.cash>", @@ -19,7 +19,8 @@ repository = "https://github.com/matter-labs/pairing" [dependencies] rand = "0.4" byteorder = "1" -ff = { git = 'https://github.com/matterinc/ff', features = ["derive"], tag = "0.5"} +ff_ce = {version = "0.6", features = ["derive"] } +#ff = { git = 'https://github.com/matterinc/ff', features = ["derive"], tag = "0.5"} serde = "1.0.80" serde_derive = "1.0.80" serde_json = "1.0.33" diff --git a/README.md b/README.md index 3031b61..c4347c7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # pairing "community edition" +Now published as `pairing_ce` to `crates.io` for users convenience. + Originally developed by ZCash, with extensions from us to make it a little more pleasant. This is a Rust crate for using pairing-friendly elliptic curves. Currently, only the [BLS12-381](https://z.cash/blog/new-snark-curve.html) and BN256 curves are implemented. diff --git a/src/lib.rs b/src/lib.rs index 348f240..f704b21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ extern crate serde_derive; #[cfg(test)] pub mod tests; -extern crate ff as imported_ff; +extern crate ff_ce as imported_ff; pub mod ff { pub use imported_ff::*; From 74f0a18f312a8bd3b919cfcee193b2bb8bc91936 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Thu, 30 May 2019 16:13:01 +0300 Subject: [PATCH 133/140] cleaner NAF form --- src/bn256/mod.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/bn256/mod.rs b/src/bn256/mod.rs index b5688ab..10f1a91 100644 --- a/src/bn256/mod.rs +++ b/src/bn256/mod.rs @@ -29,10 +29,16 @@ pub struct Bn256; pub const BN_U: u64 = 4965661367192848881; // // 6U+2 for in NAF form -pub const SIX_U_PLUS_2_NAF : [i8; 65] = [0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, - 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, - 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, - 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1]; +pub const SIX_U_PLUS_2_NAF : [i8; 65] = [ + 0, 0, 0, 1, 0, 1, 0, -1, + 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, + 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, + 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, + 1, 0, -1, 0, 0, 1, 0, 1, + 1]; impl ScalarEngine for Bn256 { @@ -76,7 +82,6 @@ impl Engine for Bn256 { c1.c1.mul_assign(&p.x); // Sparse multiplication in Fq12 - f.mul_by_034(&c0, &c1, &coeffs.2); } From 9228d20862476f3784ba31c71631c899bb7a77a9 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Thu, 30 May 2019 18:14:18 +0300 Subject: [PATCH 134/140] prepare for gpu integration --- Cargo.toml | 6 ++++-- src/bls12_381/ec.rs | 16 ++++++++++++++-- src/bn256/ec.rs | 18 ++++++++++++++---- src/lib.rs | 20 ++++++++++++++------ 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e7b64b3..22dbc56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,14 @@ description = "Pairing-friendly elliptic curve library" documentation = "https://docs.rs/pairing/" homepage = "https://github.com/matter-labs/pairing" repository = "https://github.com/matter-labs/pairing" +edition = "2018" [dependencies] rand = "0.4" byteorder = "1" -ff_ce = {version = "0.6", features = ["derive"] } -#ff = { git = 'https://github.com/matterinc/ff', features = ["derive"], tag = "0.5"} +#ff_ce = {version = "0.6", features = ["derive"] } +#ff_ce = { git = 'https://github.com/matter-labs/ff', features = ["derive"], branch = "gpu"} +ff_ce = { path = '../ff', features = ["derive", "derive_serde"]} serde = "1.0.80" serde_derive = "1.0.80" serde_json = "1.0.33" diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 5c0545f..d4e1131 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -626,7 +626,7 @@ pub mod g1 { use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; use rand::{Rand, Rng}; use std::fmt; - use {CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; + use crate::{RawEncodable, CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; curve_impl!( "G1", @@ -750,6 +750,18 @@ pub mod g1 { } } + impl RawEncodable for G1Affine { + fn into_raw_uncompressed_le(&self) -> Self::Uncompressed { + let mut res = Self::Uncompressed::empty(); + let mut writer = &mut res.0[..]; + + self.x.into_raw_repr().write_le(&mut writer).unwrap(); + self.y.into_raw_repr().write_le(&mut writer).unwrap(); + + res + } + } + #[derive(Copy, Clone)] pub struct G1Compressed([u8; 48]); @@ -1272,7 +1284,7 @@ pub mod g2 { use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; use rand::{Rand, Rng}; use std::fmt; - use {CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; + use crate::{CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; curve_impl!( "G2", diff --git a/src/bn256/ec.rs b/src/bn256/ec.rs index f4a3a63..ab58e29 100644 --- a/src/bn256/ec.rs +++ b/src/bn256/ec.rs @@ -190,9 +190,7 @@ macro_rules! curve_impl { fn into_projective(&self) -> $projective { (*self).into() } - } - // impl Rand for $projective { // fn rand<R: Rng>(rng: &mut R) -> Self { // loop { @@ -630,7 +628,7 @@ pub mod g1 { use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; use rand::{Rand, Rng}; use std::fmt; - use {CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; + use crate::{RawEncodable, CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; curve_impl!( "G1", @@ -644,6 +642,18 @@ pub mod g1 { G2Affine ); + impl RawEncodable for G1Affine { + fn into_raw_uncompressed_le(&self) -> Self::Uncompressed { + let mut res = Self::Uncompressed::empty(); + let mut writer = &mut res.0[..]; + + self.x.into_raw_repr().write_le(&mut writer).unwrap(); + self.y.into_raw_repr().write_le(&mut writer).unwrap(); + + res + } + } + #[derive(Copy, Clone)] pub struct G1Uncompressed([u8; 64]); @@ -1013,7 +1023,7 @@ pub mod g2 { use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr, SqrtField}; use rand::{Rand, Rng}; use std::fmt; - use {CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; + use crate::{CurveAffine, CurveProjective, EncodedPoint, Engine, GroupDecodingError}; curve_impl!( "G2", diff --git a/src/lib.rs b/src/lib.rs index f704b21..f6f1df6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,11 +22,13 @@ extern crate serde_derive; #[cfg(test)] pub mod tests; -extern crate ff_ce as imported_ff; +pub extern crate ff_ce as ff; -pub mod ff { - pub use imported_ff::*; -} +pub use ff::*; + +// pub mod ff { +// pub use ff::*; +// } pub mod bls12_381; pub mod bn256; @@ -60,7 +62,7 @@ pub trait Engine: ScalarEngine { Pair = Self::G2Affine, PairingResult = Self::Fqk, > - + From<Self::G1>; + + From<Self::G1> + RawEncodable; /// The projective representation of an element in G2. type G2: CurveProjective< @@ -102,7 +104,7 @@ pub trait Engine: ScalarEngine { >; /// Perform final exponentiation of the result of a miller loop. - fn final_exponentiation(&Self::Fqk) -> Option<Self::Fqk>; + fn final_exponentiation(r: &Self::Fqk) -> Option<Self::Fqk>; /// Performs a complete pairing operation `(p, q)`. fn pairing<G1, G2>(p: G1, q: G2) -> Self::Fqk @@ -240,6 +242,12 @@ pub trait CurveAffine: } } +pub trait RawEncodable: CurveAffine { + /// Converts this element into its uncompressed encoding, so long as it's not + /// the point at infinity. Leaves coordinates in Montgommery form + fn into_raw_uncompressed_le(&self) -> Self::Uncompressed; +} + /// An encoded elliptic curve point, which should essentially wrap a `[u8; N]`. pub trait EncodedPoint: Sized + Send + Sync + AsRef<[u8]> + AsMut<[u8]> + Clone + Copy + 'static From 47948ef8a59605eb39e6ab0e1a79715f6df87085 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Sat, 1 Jun 2019 14:08:44 +0300 Subject: [PATCH 135/140] implement decoding from raw representation --- src/bls12_381/ec.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/bn256/ec.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 ++++++ 3 files changed, 86 insertions(+) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index d4e1131..7eabb53 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -760,6 +760,45 @@ pub mod g1 { res } + + fn from_raw_uncompressed_le_unchecked( + encoded: &Self::Uncompressed, + _infinity: bool + ) -> Result<Self, GroupDecodingError> { + let copy = encoded.0; + if copy.iter().all(|b| *b == 0) { + return Ok(Self::zero()); + } + + let mut x = FqRepr([0; 6]); + let mut y = FqRepr([0; 6]); + + { + let mut reader = &copy[..]; + x.read_be(&mut reader).unwrap(); + y.read_be(&mut reader).unwrap(); + } + + Ok(G1Affine { + x: Fq::from_raw_repr(x).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate", e) + })?, + y: Fq::from_raw_repr(y).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("y coordinate", e) + })?, + infinity: false, + }) + } + + fn from_raw_uncompressed_le(encoded: &Self::Uncompressed, _infinity: bool) -> Result<Self, GroupDecodingError> { + let affine = Self::from_raw_uncompressed_le_unchecked(&encoded, _infinity)?; + + if !affine.is_on_curve() { + Err(GroupDecodingError::NotOnCurve) + } else { + Ok(affine) + } + } } #[derive(Copy, Clone)] diff --git a/src/bn256/ec.rs b/src/bn256/ec.rs index ab58e29..e60f757 100644 --- a/src/bn256/ec.rs +++ b/src/bn256/ec.rs @@ -652,6 +652,47 @@ pub mod g1 { res } + + /// Creates a point from raw encoded coordinates without checking on curve + fn from_raw_uncompressed_le_unchecked( + encoded: &Self::Uncompressed, + _infinity: bool + ) -> Result<Self, GroupDecodingError> { + let copy = encoded.0; + + if copy.iter().all(|b| *b == 0) { + return Ok(Self::zero()); + } + + let mut x = FqRepr([0; 4]); + let mut y = FqRepr([0; 4]); + + { + let mut reader = &copy[..]; + x.read_be(&mut reader).unwrap(); + y.read_be(&mut reader).unwrap(); + } + + Ok(G1Affine { + x: Fq::from_raw_repr(x).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("x coordinate", e) + })?, + y: Fq::from_raw_repr(y).map_err(|e| { + GroupDecodingError::CoordinateDecodingError("y coordinate", e) + })?, + infinity: false, + }) + } + + fn from_raw_uncompressed_le(encoded: &Self::Uncompressed, _infinity: bool) -> Result<Self, GroupDecodingError> { + let affine = Self::from_raw_uncompressed_le_unchecked(&encoded, _infinity)?; + + if !affine.is_on_curve() { + Err(GroupDecodingError::NotOnCurve) + } else { + Ok(affine) + } + } } #[derive(Copy, Clone)] diff --git a/src/lib.rs b/src/lib.rs index f6f1df6..8d7053f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,6 +246,12 @@ pub trait RawEncodable: CurveAffine { /// Converts this element into its uncompressed encoding, so long as it's not /// the point at infinity. Leaves coordinates in Montgommery form fn into_raw_uncompressed_le(&self) -> Self::Uncompressed; + + /// Creates a point from raw encoded coordinates without checking on curve + fn from_raw_uncompressed_le_unchecked(encoded: &Self::Uncompressed, infinity: bool) -> Result<Self, GroupDecodingError>; + + /// Creates a point from raw encoded coordinates + fn from_raw_uncompressed_le(encoded: &Self::Uncompressed, infinity: bool) -> Result<Self, GroupDecodingError>; } /// An encoded elliptic curve point, which should essentially wrap a `[u8; N]`. From b75da3d8dca5e3068fdaca872fa4f5e8e8531086 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Sun, 2 Jun 2019 16:55:34 +0300 Subject: [PATCH 136/140] fix BE to LE --- src/bls12_381/ec.rs | 4 ++-- src/bn256/ec.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 7eabb53..3d505de 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -775,8 +775,8 @@ pub mod g1 { { let mut reader = &copy[..]; - x.read_be(&mut reader).unwrap(); - y.read_be(&mut reader).unwrap(); + x.read_le(&mut reader).unwrap(); + y.read_le(&mut reader).unwrap(); } Ok(G1Affine { diff --git a/src/bn256/ec.rs b/src/bn256/ec.rs index e60f757..02fcbc0 100644 --- a/src/bn256/ec.rs +++ b/src/bn256/ec.rs @@ -669,8 +669,8 @@ pub mod g1 { { let mut reader = &copy[..]; - x.read_be(&mut reader).unwrap(); - y.read_be(&mut reader).unwrap(); + x.read_le(&mut reader).unwrap(); + y.read_le(&mut reader).unwrap(); } Ok(G1Affine { From 76c2d3451ff26812a0a9a178bb9f1f956d481b11 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Sun, 2 Jun 2019 21:26:26 +0300 Subject: [PATCH 137/140] fix warning --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8d7053f..a22b5b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,6 @@ extern crate rand; extern crate hex; extern crate serde; -#[macro_use] extern crate serde_derive; #[cfg(test)] From 1dae6b69dbde527af0762dd579c7c9a560ea0a12 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Fri, 12 Jul 2019 21:59:06 +0300 Subject: [PATCH 138/140] start migrating to edition 2018 --- Cargo.toml | 6 +--- src/bls12_381/ec.rs | 8 +++-- src/bn256/ec.rs | 71 +++++++++++++++++++++++++++++++-------------- src/lib.rs | 10 +------ 4 files changed, 56 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22dbc56..485800f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,7 @@ rand = "0.4" byteorder = "1" #ff_ce = {version = "0.6", features = ["derive"] } #ff_ce = { git = 'https://github.com/matter-labs/ff', features = ["derive"], branch = "gpu"} -ff_ce = { path = '../ff', features = ["derive", "derive_serde"]} -serde = "1.0.80" -serde_derive = "1.0.80" -serde_json = "1.0.33" -hex = "0.3.2" +ff = { path = '../ff', package = "ff_ce", features = ["derive"]} [features] unstable-features = ["expose-arith"] diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 3d505de..54c02c2 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -753,10 +753,12 @@ pub mod g1 { impl RawEncodable for G1Affine { fn into_raw_uncompressed_le(&self) -> Self::Uncompressed { let mut res = Self::Uncompressed::empty(); - let mut writer = &mut res.0[..]; + { + let mut writer = &mut res.0[..]; - self.x.into_raw_repr().write_le(&mut writer).unwrap(); - self.y.into_raw_repr().write_le(&mut writer).unwrap(); + self.x.into_raw_repr().write_le(&mut writer).unwrap(); + self.y.into_raw_repr().write_le(&mut writer).unwrap(); + } res } diff --git a/src/bn256/ec.rs b/src/bn256/ec.rs index 02fcbc0..cb3ab13 100644 --- a/src/bn256/ec.rs +++ b/src/bn256/ec.rs @@ -645,10 +645,12 @@ pub mod g1 { impl RawEncodable for G1Affine { fn into_raw_uncompressed_le(&self) -> Self::Uncompressed { let mut res = Self::Uncompressed::empty(); - let mut writer = &mut res.0[..]; + { + let mut writer = &mut res.0[..]; - self.x.into_raw_repr().write_le(&mut writer).unwrap(); - self.y.into_raw_repr().write_le(&mut writer).unwrap(); + self.x.into_raw_repr().write_le(&mut writer).unwrap(); + self.y.into_raw_repr().write_le(&mut writer).unwrap(); + } res } @@ -1078,25 +1080,6 @@ pub mod g2 { G1Affine ); - // impl Rand for G2 { - // fn rand<R: Rng>(rng: &mut R) -> Self { - - // let mut r = G2::one(); - // let k = Fr::rand(rng); - // r.mul_assign(k); - // return r; - // } - // } - - // impl Rand for G2Affine { - // fn rand<R: Rng>(rng: &mut R) -> Self { - // let mut r = G2::one(); - // let k = Fr::rand(rng); - // r.mul_assign(k); - // return r.into_affine(); - // } - // } - impl Rand for G2 { fn rand<R: Rng>(rng: &mut R) -> Self { loop { @@ -1471,6 +1454,50 @@ pub mod g2 { } } + #[test] + fn test_generate_g2_in_subgroup() { + use SqrtField; + + let mut x = Fq2::zero(); + loop { + // y^2 = x^3 + b + let mut rhs = x; + rhs.square(); + rhs.mul_assign(&x); + rhs.add_assign(&G2Affine::get_coeff_b()); + + if let Some(y) = rhs.sqrt() { + let mut negy = y; + negy.negate(); + + let p = G2Affine { + x: x, + y: if y < negy { y } else { negy }, + infinity: false, + }; + + let g2 = p.into_projective(); + let mut minus_one = Fr::one(); + minus_one.negate(); + + let mut expected_zero = p.mul(minus_one); + expected_zero.add_assign(&g2); + + if !expected_zero.is_zero() { + let p = expected_zero.into_affine(); + let scaled_by_cofactor = p.scale_by_cofactor(); + if scaled_by_cofactor.is_zero() { + let g2 = G2Affine::from(expected_zero); + println!("Invalid subgroup point = {}", g2); + return; + } + } + } + + x.add_assign(&Fq2::one()); + } + } + #[cfg(test)] use rand::{SeedableRng, XorShiftRng}; diff --git a/src/lib.rs b/src/lib.rs index a22b5b7..8c1a255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,21 +14,13 @@ extern crate byteorder; extern crate rand; -extern crate hex; -extern crate serde; -extern crate serde_derive; - #[cfg(test)] pub mod tests; -pub extern crate ff_ce as ff; +pub extern crate ff; pub use ff::*; -// pub mod ff { -// pub use ff::*; -// } - pub mod bls12_381; pub mod bn256; From 2567aab84d5167879e01199e3100944e7ab0ea3e Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Fri, 12 Jul 2019 22:05:30 +0300 Subject: [PATCH 139/140] fix for edition 2018 --- benches/pairing_benches.rs | 2 +- src/bls12_381/ec.rs | 8 ++++---- src/bls12_381/fq.rs | 12 ++++++------ src/bls12_381/fq12.rs | 4 ++-- src/bls12_381/fq2.rs | 6 +++--- src/bls12_381/fq6.rs | 4 ++-- src/bls12_381/fr.rs | 12 ++++++------ src/bls12_381/mod.rs | 2 +- src/bls12_381/tests/mod.rs | 2 +- src/bn256/ec.rs | 8 ++++---- src/bn256/fq.rs | 10 +++++----- src/bn256/fq12.rs | 4 ++-- src/bn256/fq2.rs | 6 +++--- src/bn256/fq6.rs | 4 ++-- src/bn256/fr.rs | 16 +--------------- src/bn256/mod.rs | 6 +++--- src/tests/curve.rs | 4 ++-- src/tests/engine.rs | 2 +- 18 files changed, 49 insertions(+), 63 deletions(-) diff --git a/benches/pairing_benches.rs b/benches/pairing_benches.rs index ddfd04d..757865a 100644 --- a/benches/pairing_benches.rs +++ b/benches/pairing_benches.rs @@ -1,7 +1,7 @@ #![feature(test)] extern crate ff; -extern crate pairing; +extern crate pairing_ce; extern crate rand; extern crate test; diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 54c02c2..d4be8cf 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -1314,8 +1314,8 @@ pub mod g1 { #[test] fn g1_curve_tests() { - ::tests::curve::curve_tests::<G1>(); - ::tests::curve::random_transformation_tests_with_cofactor::<G1>(); + crate::tests::curve::curve_tests::<G1>(); + crate::tests::curve::random_transformation_tests_with_cofactor::<G1>(); } } @@ -2068,8 +2068,8 @@ pub mod g2 { #[test] fn g2_curve_tests() { - ::tests::curve::curve_tests::<G2>(); - ::tests::curve::random_transformation_tests_with_cofactor::<G2>(); + crate::tests::curve::curve_tests::<G2>(); + crate::tests::curve::random_transformation_tests_with_cofactor::<G2>(); } } diff --git a/src/bls12_381/fq.rs b/src/bls12_381/fq.rs index fd0d416..601b503 100644 --- a/src/bls12_381/fq.rs +++ b/src/bls12_381/fq.rs @@ -1,5 +1,5 @@ use super::fq2::Fq2; -use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; +use ff::{Field, PrimeField, PrimeFieldRepr}; // B coefficient of BLS12-381 curve, 4. pub const B_COEFF: Fq = Fq(FqRepr([ @@ -2186,10 +2186,10 @@ fn test_fq_root_of_unity() { #[test] fn fq_field_tests() { - ::tests::field::random_field_tests::<Fq>(); - ::tests::field::random_sqrt_tests::<Fq>(); - ::tests::field::random_frobenius_tests::<Fq, _>(Fq::char(), 13); - ::tests::field::from_str_tests::<Fq>(); + crate::tests::field::random_field_tests::<Fq>(); + crate::tests::field::random_sqrt_tests::<Fq>(); + crate::tests::field::random_frobenius_tests::<Fq, _>(Fq::char(), 13); + crate::tests::field::from_str_tests::<Fq>(); } #[test] @@ -2205,7 +2205,7 @@ fn test_fq_ordering() { #[test] fn fq_repr_tests() { - ::tests::repr::random_repr_tests::<FqRepr>(); + crate::tests::repr::random_repr_tests::<FqRepr>(); } #[test] diff --git a/src/bls12_381/fq12.rs b/src/bls12_381/fq12.rs index b24fcaa..b8744ca 100644 --- a/src/bls12_381/fq12.rs +++ b/src/bls12_381/fq12.rs @@ -184,6 +184,6 @@ fn test_fq12_mul_by_014() { fn fq12_field_tests() { use ff::PrimeField; - ::tests::field::random_field_tests::<Fq12>(); - ::tests::field::random_frobenius_tests::<Fq12, _>(super::fq::Fq::char(), 13); + crate::tests::field::random_field_tests::<Fq12>(); + crate::tests::field::random_frobenius_tests::<Fq12, _>(super::fq::Fq::char(), 13); } diff --git a/src/bls12_381/fq2.rs b/src/bls12_381/fq2.rs index 363439a..9737adf 100644 --- a/src/bls12_381/fq2.rs +++ b/src/bls12_381/fq2.rs @@ -904,7 +904,7 @@ fn test_fq2_mul_nonresidue() { fn fq2_field_tests() { use ff::PrimeField; - ::tests::field::random_field_tests::<Fq2>(); - ::tests::field::random_sqrt_tests::<Fq2>(); - ::tests::field::random_frobenius_tests::<Fq2, _>(super::fq::Fq::char(), 13); + crate::tests::field::random_field_tests::<Fq2>(); + crate::tests::field::random_sqrt_tests::<Fq2>(); + crate::tests::field::random_frobenius_tests::<Fq2, _>(super::fq::Fq::char(), 13); } diff --git a/src/bls12_381/fq6.rs b/src/bls12_381/fq6.rs index 36c6e28..19b8316 100644 --- a/src/bls12_381/fq6.rs +++ b/src/bls12_381/fq6.rs @@ -369,6 +369,6 @@ fn test_fq6_mul_by_01() { fn fq6_field_tests() { use ff::PrimeField; - ::tests::field::random_field_tests::<Fq6>(); - ::tests::field::random_frobenius_tests::<Fq6, _>(super::fq::Fq::char(), 13); + crate::tests::field::random_field_tests::<Fq6>(); + crate::tests::field::random_frobenius_tests::<Fq6, _>(super::fq::Fq::char(), 13); } diff --git a/src/bls12_381/fr.rs b/src/bls12_381/fr.rs index 5e57631..bcecbaa 100644 --- a/src/bls12_381/fr.rs +++ b/src/bls12_381/fr.rs @@ -1,4 +1,4 @@ -use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; +use ff::{Field, PrimeField, PrimeFieldRepr}; #[derive(PrimeField)] #[PrimeFieldModulus = "52435875175126190479447740508185965837690552500527637822603658699938581184513"] @@ -974,13 +974,13 @@ fn test_fr_root_of_unity() { #[test] fn fr_field_tests() { - ::tests::field::random_field_tests::<Fr>(); - ::tests::field::random_sqrt_tests::<Fr>(); - ::tests::field::random_frobenius_tests::<Fr, _>(Fr::char(), 13); - ::tests::field::from_str_tests::<Fr>(); + crate::tests::field::random_field_tests::<Fr>(); + crate::tests::field::random_sqrt_tests::<Fr>(); + crate::tests::field::random_frobenius_tests::<Fr, _>(Fr::char(), 13); + crate::tests::field::from_str_tests::<Fr>(); } #[test] fn fr_repr_tests() { - ::tests::repr::random_repr_tests::<FrRepr>(); + crate::tests::repr::random_repr_tests::<FrRepr>(); } diff --git a/src/bls12_381/mod.rs b/src/bls12_381/mod.rs index 106591e..005a772 100644 --- a/src/bls12_381/mod.rs +++ b/src/bls12_381/mod.rs @@ -365,5 +365,5 @@ impl G2Prepared { #[test] fn bls12_engine_tests() { - ::tests::engine::engine_tests::<Bls12>(); + crate::tests::engine::engine_tests::<Bls12>(); } diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs index bf6c595..4f2e5b1 100644 --- a/src/bls12_381/tests/mod.rs +++ b/src/bls12_381/tests/mod.rs @@ -1,5 +1,5 @@ use super::*; -use *; +use crate::*; #[test] fn test_pairing_result_against_relic() { diff --git a/src/bn256/ec.rs b/src/bn256/ec.rs index cb3ab13..6413d6b 100644 --- a/src/bn256/ec.rs +++ b/src/bn256/ec.rs @@ -1055,8 +1055,8 @@ pub mod g1 { #[test] fn g1_curve_tests() { - ::tests::curve::curve_tests::<G1>(); - ::tests::curve::random_transformation_tests::<G1>(); + crate::tests::curve::curve_tests::<G1>(); + crate::tests::curve::random_transformation_tests::<G1>(); } } @@ -1532,8 +1532,8 @@ pub mod g2 { #[test] fn g2_curve_tests() { - ::tests::curve::curve_tests::<G2>(); - ::tests::curve::random_transformation_tests::<G2>(); + crate::tests::curve::curve_tests::<G2>(); + crate::tests::curve::random_transformation_tests::<G2>(); } #[test] diff --git a/src/bn256/fq.rs b/src/bn256/fq.rs index 19e55d3..9d40d3d 100644 --- a/src/bn256/fq.rs +++ b/src/bn256/fq.rs @@ -1,5 +1,5 @@ use super::fq2::Fq2; -use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; +use ff::{Field, PrimeField, PrimeFieldRepr}; #[derive(PrimeField)] #[PrimeFieldModulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583"] @@ -572,8 +572,8 @@ fn test_fq_sqrt_2() { #[test] fn fq_field_tests() { - ::tests::field::random_field_tests::<Fq>(); - ::tests::field::random_sqrt_tests::<Fq>(); - ::tests::field::random_frobenius_tests::<Fq, _>(Fq::char(), 13); - ::tests::field::from_str_tests::<Fq>(); + crate::tests::field::random_field_tests::<Fq>(); + crate::tests::field::random_sqrt_tests::<Fq>(); + crate::tests::field::random_frobenius_tests::<Fq, _>(Fq::char(), 13); + crate::tests::field::from_str_tests::<Fq>(); } \ No newline at end of file diff --git a/src/bn256/fq12.rs b/src/bn256/fq12.rs index 67fe6cb..79a68e7 100644 --- a/src/bn256/fq12.rs +++ b/src/bn256/fq12.rs @@ -216,6 +216,6 @@ fn test_squaring() { fn fq12_field_tests() { use ff::PrimeField; - ::tests::field::random_field_tests::<Fq12>(); - ::tests::field::random_frobenius_tests::<Fq12, _>(super::fq::Fq::char(), 13); + crate::tests::field::random_field_tests::<Fq12>(); + crate::tests::field::random_frobenius_tests::<Fq12, _>(super::fq::Fq::char(), 13); } diff --git a/src/bn256/fq2.rs b/src/bn256/fq2.rs index 98939f3..4a1f9b9 100644 --- a/src/bn256/fq2.rs +++ b/src/bn256/fq2.rs @@ -960,7 +960,7 @@ fn test_fq2_mul_nonresidue() { fn fq2_field_tests() { use ff::PrimeField; - ::tests::field::random_field_tests::<Fq2>(); - ::tests::field::random_sqrt_tests::<Fq2>(); - ::tests::field::random_frobenius_tests::<Fq2, _>(super::fq::Fq::char(), 13); + crate::tests::field::random_field_tests::<Fq2>(); + crate::tests::field::random_sqrt_tests::<Fq2>(); + crate::tests::field::random_frobenius_tests::<Fq2, _>(super::fq::Fq::char(), 13); } diff --git a/src/bn256/fq6.rs b/src/bn256/fq6.rs index 412e4ab..d9e481e 100644 --- a/src/bn256/fq6.rs +++ b/src/bn256/fq6.rs @@ -395,6 +395,6 @@ fn test_fq6_mul_by_01() { fn fq6_field_tests() { use ff::PrimeField; - ::tests::field::random_field_tests::<Fq6>(); - ::tests::field::random_frobenius_tests::<Fq6, _>(super::fq::Fq::char(), 13); + crate::tests::field::random_field_tests::<Fq6>(); + crate::tests::field::random_frobenius_tests::<Fq6, _>(super::fq::Fq::char(), 13); } diff --git a/src/bn256/fr.rs b/src/bn256/fr.rs index 97715d8..7f4d103 100644 --- a/src/bn256/fr.rs +++ b/src/bn256/fr.rs @@ -1,4 +1,4 @@ -use ff::{Field, PrimeField, PrimeFieldDecodingError, PrimeFieldRepr}; +use ff::{Field, PrimeField, PrimeFieldRepr}; #[derive(PrimeField)] #[PrimeFieldModulus = "21888242871839275222246405745257275088548364400416034343698204186575808495617"] @@ -28,20 +28,6 @@ fn test_fr_from_hex() { assert_eq!(fr, Fr::zero()); } -#[test] -fn test_fr_serialize() { - assert_eq!( - serde_json::to_string(&Fr::one()).unwrap(), - r#""0x0000000000000000000000000000000000000000000000000000000000000001""#); -} - -#[test] -fn test_fr_deserialize() { - let json = r#""0x0000000000000000000000000000000000000000000000000000000000000001""#; - let fr: Fr = serde_json::from_str(json).unwrap(); - assert_eq!(fr, Fr::one()); -} - #[test] fn test_roots_of_unity() { assert_eq!(Fr::S, 28); diff --git a/src/bn256/mod.rs b/src/bn256/mod.rs index 10f1a91..54087e1 100644 --- a/src/bn256/mod.rs +++ b/src/bn256/mod.rs @@ -473,7 +473,7 @@ use rand::{Rand, SeedableRng, XorShiftRng}; #[test] fn test_pairing() { - use {CurveProjective}; + use crate::{CurveProjective}; let mut g1 = G1::one(); let mut g2 = G2::one(); @@ -557,7 +557,7 @@ fn test_pairing() { #[test] fn random_bilinearity_tests() { - use {CurveProjective}; + use crate::{CurveProjective}; use ff::PrimeField; let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -600,5 +600,5 @@ fn random_bilinearity_tests() { #[test] fn bn256_engine_tests() { - ::tests::engine::engine_tests::<Bn256>(); + crate::tests::engine::engine_tests::<Bn256>(); } diff --git a/src/tests/curve.rs b/src/tests/curve.rs index a398a73..4663a42 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -1,7 +1,7 @@ use ff::Field; use rand::{Rand, Rng, SeedableRng, XorShiftRng}; -use {CurveAffine, CurveProjective, EncodedPoint}; +use crate::{CurveAffine, CurveProjective, EncodedPoint}; pub fn curve_tests<G: CurveProjective>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -67,7 +67,7 @@ pub fn curve_tests<G: CurveProjective>() { fn random_wnaf_tests<G: CurveProjective>() { use ff::PrimeField; - use wnaf::*; + use crate::wnaf::*; let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/tests/engine.rs b/src/tests/engine.rs index 52ff4e0..5fc37c4 100644 --- a/src/tests/engine.rs +++ b/src/tests/engine.rs @@ -1,6 +1,6 @@ use rand::{Rand, SeedableRng, XorShiftRng}; -use {CurveAffine, CurveProjective, Engine, Field, PrimeField}; +use crate::{CurveAffine, CurveProjective, Engine, Field, PrimeField}; pub fn engine_tests<E: Engine>() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); From bfb809c1cddd18e0cd9ac4a2c026c3a3ac24b158 Mon Sep 17 00:00:00 2001 From: Alex Vlasov <alex.m.vlasov@gmail.com> Date: Sat, 13 Jul 2019 21:58:06 +0300 Subject: [PATCH 140/140] prepare to publish --- Cargo.toml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 485800f..8eaac3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "pairing_ce" # Remember to change version string in README.md. -version = "0.17.0" +version = "0.18.0" authors = [ "Sean Bowe <ewillbefull@gmail.com>", "Jack Grigg <jack@z.cash>", @@ -20,11 +20,9 @@ edition = "2018" [dependencies] rand = "0.4" byteorder = "1" -#ff_ce = {version = "0.6", features = ["derive"] } -#ff_ce = { git = 'https://github.com/matter-labs/ff', features = ["derive"], branch = "gpu"} -ff = { path = '../ff', package = "ff_ce", features = ["derive"]} +ff = {package = "ff_ce", version = "0.7", features = ["derive"]} +#ff = { path = '../ff', package = "ff_ce", features = ["derive"]} [features] -unstable-features = ["expose-arith"] expose-arith = [] default = []