diff --git a/group/.gitignore b/group/.gitignore new file mode 100644 index 000000000..693699042 --- /dev/null +++ b/group/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/group/COPYRIGHT b/group/COPYRIGHT new file mode 100644 index 000000000..849e32747 --- /dev/null +++ b/group/COPYRIGHT @@ -0,0 +1,14 @@ +Copyrights in the "group" library are retained by their contributors. No +copyright assignment is required to contribute to the "group" library. + +The "group" library is licensed under either of + + * Apache License, Version 2.0, (see ./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license (see ./LICENSE-MIT or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/group/Cargo.toml b/group/Cargo.toml new file mode 100644 index 000000000..92c28705e --- /dev/null +++ b/group/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "group" +version = "0.1.0" +authors = [ + "Sean Bowe ", + "Jack Grigg ", +] +license = "MIT/Apache-2.0" + +description = "Elliptic curve group traits and utilities" +documentation = "https://docs.rs/group/" +homepage = "https://github.com/ebfull/group" +repository = "https://github.com/ebfull/group" + +[dependencies] +ff = "0.4" +rand = "0.4" diff --git a/group/LICENSE-APACHE b/group/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/group/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/group/LICENSE-MIT b/group/LICENSE-MIT new file mode 100644 index 000000000..31aa79387 --- /dev/null +++ b/group/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/group/README.md b/group/README.md new file mode 100644 index 000000000..7fadc0b66 --- /dev/null +++ b/group/README.md @@ -0,0 +1,17 @@ +# group [![Crates.io](https://img.shields.io/crates/v/group.svg)](https://crates.io/crates/group) # + +## 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/group/src/lib.rs b/group/src/lib.rs new file mode 100644 index 000000000..fc924c374 --- /dev/null +++ b/group/src/lib.rs @@ -0,0 +1,196 @@ +extern crate ff; +extern crate rand; + +use ff::{PrimeField, PrimeFieldDecodingError, ScalarEngine, SqrtField}; +use std::error::Error; +use std::fmt; + +pub mod tests; + +mod wnaf; +pub use self::wnaf::Wnaf; + +/// 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: ScalarEngine; + type Scalar: PrimeField + SqrtField; + 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 into_affine(&self) -> Self::Affine; + + /// Recommends a wNAF window table size given a scalar. Always returns a number + /// between 2 and 22, inclusive. + fn recommended_wnaf_for_scalar(scalar: ::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. + fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize; +} + +/// 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: ScalarEngine; + type Scalar: PrimeField + SqrtField; + type Base: SqrtField; + type Projective: CurveProjective; + type Uncompressed: EncodedPoint; + type Compressed: EncodedPoint; + + /// 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; + + /// Negates this element. + fn negate(&mut self); + + /// Performs scalar multiplication of this element with mixed addition. + fn mul::Repr>>(&self, other: S) -> Self::Projective; + + /// Converts this element into its affine representation. + fn into_projective(&self) -> Self::Projective; + + /// Converts this element into its compressed encoding, so long as it's not + /// the point at infinity. + fn into_compressed(&self) -> Self::Compressed { + ::from_affine(*self) + } + + /// Converts this element into its uncompressed encoding, so long as it's not + /// the point at infinity. + fn into_uncompressed(&self) -> Self::Uncompressed { + ::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]> + Clone + Copy + '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 encoding represents a valid element. + fn into_affine(&self) -> Result; + + /// Converts an `EncodedPoint` into a `CurveAffine` element, + /// 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; + + /// Creates an `EncodedPoint` from an affine point, as long as the + /// point is not the point at infinity. + fn from_affine(affine: Self::Affine) -> Self; +} + +/// 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. + 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 element 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()), + } + } +} diff --git a/group/src/tests/mod.rs b/group/src/tests/mod.rs new file mode 100644 index 000000000..b4c47dbdc --- /dev/null +++ b/group/src/tests/mod.rs @@ -0,0 +1,421 @@ +use rand::{Rand, Rng, SeedableRng, XorShiftRng}; + +use {CurveAffine, CurveProjective, EncodedPoint}; + +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.into_affine()); + + assert_eq!(z, z2); + assert_eq!(z, r); + } + + // Transformations + { + let a = G::rand(&mut rng); + 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); + } + + random_addition_tests::(); + random_multiplication_tests::(); + random_doubling_tests::(); + random_negation_tests::(); + random_transformation_tests::(); + random_wnaf_tests::(); + random_encoding_tests::(); +} + +fn random_wnaf_tests() { + use ff::PrimeField; + + use wnaf::*; + + 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 only_compiles_if_send(_: &S) {} + + 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); + + 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); + } + } +} + +fn random_negation_tests() { + use ff::Field; + + 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.into_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.into_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.into_affine(); + let b_affine = b.into_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.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!(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.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]); + } + } +} + +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.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::>(); + + 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].into_affine().into_projective(); + } + + let expected_v = v.iter() + .map(|v| v.into_affine().into_projective()) + .collect::>(); + G::batch_normalization(&mut v); + + for i in &v { + assert!(i.is_normalized()); + } + + assert_eq!(v, expected_v); + } +} + +fn random_encoding_tests() { + 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(); + let de_uncompressed = uncompressed.into_affine().unwrap(); + assert_eq!(de_uncompressed, r); + + let compressed = r.into_compressed(); + let de_compressed = compressed.into_affine().unwrap(); + assert_eq!(de_compressed, r); + + r.negate(); + + let compressed = r.into_compressed(); + let de_compressed = compressed.into_affine().unwrap(); + assert_eq!(de_compressed, r); + } +} diff --git a/group/src/wnaf.rs b/group/src/wnaf.rs new file mode 100644 index 000000000..381cd106b --- /dev/null +++ b/group/src/wnaf.rs @@ -0,0 +1,181 @@ +use ff::{PrimeField, PrimeFieldRepr}; + +use super::CurveProjective; + +/// Replaces the contents of `table` with a w-NAF window table for the given window size. +pub(crate) fn wnaf_table(table: &mut Vec, mut base: G, window: usize) { + 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 w-NAF representation of a scalar. +pub(crate) fn wnaf_form(wnaf: &mut Vec, 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; + + 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 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. +pub(crate) 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 +} + +/// A "w-ary non-adjacent form" exponentiation context. +#[derive(Debug)] +pub struct Wnaf { + base: B, + scalar: S, + window_size: W, +} + +impl Wnaf<(), Vec, Vec> { + /// 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(&mut self, base: G, num_scalars: usize) -> Wnaf> { + // 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, + } + } + + /// Given a scalar, compute its wNAF representation and return a `Wnaf` object that can perform + /// exponentiations with `.base(..)`. + pub fn scalar( + &mut self, + scalar: <::Scalar as PrimeField>::Repr, + ) -> Wnaf, &[i64]> { + // Compute the appropriate window size for the scalar. + let window_size = G::recommended_wnaf_for_scalar(scalar); + + // 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, + } + } +} + +impl<'a, G: CurveProjective> Wnaf> { + /// 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> { + Wnaf { + base: self.base, + scalar: vec![], + window_size: self.window_size, + } + } +} + +impl<'a, G: CurveProjective> Wnaf, &'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, &'a [i64]> { + Wnaf { + base: vec![], + scalar: self.scalar, + window_size: self.window_size, + } + } +} + +impl> Wnaf { + /// Performs exponentiation given a base. + pub fn base(&mut self, base: G) -> G + where + B: AsMut>, + { + wnaf_table(self.base.as_mut(), base, self.window_size); + wnaf_exp(self.base.as_mut(), self.scalar.as_ref()) + } +} + +impl>> Wnaf { + /// Performs exponentiation given a scalar. + pub fn scalar( + &mut self, + scalar: <::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()) + } +}