From 1c7cb175872a1b65ab0bcc7e8d4751aa0095bb8b Mon Sep 17 00:00:00 2001 From: Saravanan Vijayakumaran Date: Wed, 27 Sep 2023 03:39:48 +0530 Subject: [PATCH] Emulated and ed25519 crates (#3) * Removing starter file * Adding emulated and ed25519 crates * cargo clippy * cargo xclippy * Replace printing of num_constraints with assert_eq --- .gitignore | 2 + Cargo.toml | 19 +- crates/bellpepper-ed25519/Cargo.toml | 24 + crates/bellpepper-ed25519/LICENSE-APACHE | 201 +++ crates/bellpepper-ed25519/LICENSE-MIT | 21 + crates/bellpepper-ed25519/README.md | 21 + crates/bellpepper-ed25519/src/circuit.rs | 641 +++++++++ crates/bellpepper-ed25519/src/curve.rs | 256 ++++ crates/bellpepper-ed25519/src/field.rs | 372 +++++ crates/bellpepper-ed25519/src/lib.rs | 3 + crates/bellpepper-emulated/Cargo.toml | 21 + crates/bellpepper-emulated/LICENSE-APACHE | 201 +++ crates/bellpepper-emulated/LICENSE-MIT | 21 + crates/bellpepper-emulated/README.md | 20 + .../bellpepper-emulated/src/field_element.rs | 1235 +++++++++++++++++ crates/bellpepper-emulated/src/field_hints.rs | 136 ++ crates/bellpepper-emulated/src/field_ops.rs | 867 ++++++++++++ crates/bellpepper-emulated/src/lib.rs | 4 + crates/bellpepper-emulated/src/util.rs | 209 +++ src/lib.rs | 14 - 20 files changed, 4268 insertions(+), 20 deletions(-) create mode 100644 .gitignore create mode 100644 crates/bellpepper-ed25519/Cargo.toml create mode 100644 crates/bellpepper-ed25519/LICENSE-APACHE create mode 100644 crates/bellpepper-ed25519/LICENSE-MIT create mode 100644 crates/bellpepper-ed25519/README.md create mode 100644 crates/bellpepper-ed25519/src/circuit.rs create mode 100644 crates/bellpepper-ed25519/src/curve.rs create mode 100644 crates/bellpepper-ed25519/src/field.rs create mode 100644 crates/bellpepper-ed25519/src/lib.rs create mode 100644 crates/bellpepper-emulated/Cargo.toml create mode 100644 crates/bellpepper-emulated/LICENSE-APACHE create mode 100644 crates/bellpepper-emulated/LICENSE-MIT create mode 100644 crates/bellpepper-emulated/README.md create mode 100644 crates/bellpepper-emulated/src/field_element.rs create mode 100644 crates/bellpepper-emulated/src/field_hints.rs create mode 100644 crates/bellpepper-emulated/src/field_ops.rs create mode 100644 crates/bellpepper-emulated/src/lib.rs create mode 100644 crates/bellpepper-emulated/src/util.rs delete mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2f9e58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index dd1a511..6c66de3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,15 @@ -[package] -name = "bellpepper-gadgets" -version = "0.1.0" -edition = "2021" +[workspace] +resolver = "2" +members = [ + "crates/bellpepper-emulated", + "crates/bellpepper-ed25519", +] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace.package] +homepage = "https://github.com/lurk-lab/bellpepper-gadgets" +repository = "https://github.com/lurk-lab/bellpepper-gadgets" -[dependencies] +[workspace.dependencies] +bellpepper-core = { version="0.2.0", default-features = false } +bellpepper = { version="0.2.0", default-features = false } +ff = "0.13.0" diff --git a/crates/bellpepper-ed25519/Cargo.toml b/crates/bellpepper-ed25519/Cargo.toml new file mode 100644 index 0000000..ae50231 --- /dev/null +++ b/crates/bellpepper-ed25519/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bellpepper-ed25519" +version = "0.2.0" +edition = "2021" +authors = ["Saravanan Vijayakumaran "] +license = "MIT OR Apache-2.0" +description = "Ed25519 curve operations using the bellperson-emulated library" +documentation = "https://docs.rs/bellpepper-ed25519" +homepage.workspace = true +repository.workspace = true + + +[dependencies] +bellpepper-core = { workspace = true } +bellpepper = { workspace = true } +ff = { workspace = true } +bellpepper-emulated = { version = "0.2.0", path = "../bellpepper-emulated" } +num-bigint = { version = "0.4.3", features = ["rand"] } +num-integer = "0.1.45" +num-traits = "0.2.15" +rand = "0.8.5" + +[dev-dependencies] +pasta_curves = "0.5.1" diff --git a/crates/bellpepper-ed25519/LICENSE-APACHE b/crates/bellpepper-ed25519/LICENSE-APACHE new file mode 100644 index 0000000..09bb92f --- /dev/null +++ b/crates/bellpepper-ed25519/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 2023 Saravanan Vijayakumaran + + 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. \ No newline at end of file diff --git a/crates/bellpepper-ed25519/LICENSE-MIT b/crates/bellpepper-ed25519/LICENSE-MIT new file mode 100644 index 0000000..d57dc48 --- /dev/null +++ b/crates/bellpepper-ed25519/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Saravanan Vijayakumaran + +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. \ No newline at end of file diff --git a/crates/bellpepper-ed25519/README.md b/crates/bellpepper-ed25519/README.md new file mode 100644 index 0000000..cf30b30 --- /dev/null +++ b/crates/bellpepper-ed25519/README.md @@ -0,0 +1,21 @@ +# bellpepper-ed25519 + +Ed25519 curve operations using the `bellpepper-emulated` library + + +## 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/crates/bellpepper-ed25519/src/circuit.rs b/crates/bellpepper-ed25519/src/circuit.rs new file mode 100644 index 0000000..befd9d5 --- /dev/null +++ b/crates/bellpepper-ed25519/src/circuit.rs @@ -0,0 +1,641 @@ +use bellpepper_core::boolean::Boolean; +use bellpepper_core::{ConstraintSystem, SynthesisError}; +use bellpepper_emulated::field_element::{ + EmulatedFieldElement, EmulatedFieldParams, PseudoMersennePrime, +}; +use ff::{PrimeField, PrimeFieldBits}; +use num_bigint::BigInt; + +use crate::{ + curve::{AffinePoint, Ed25519Curve}, + field::Fe25519, +}; + +const DEFAULT_SCALAR_MULT_WINDOW_SIZE: i32 = 4; + +struct Ed25519FpParams; + +impl EmulatedFieldParams for Ed25519FpParams { + fn num_limbs() -> usize { + 5 + } + + fn bits_per_limb() -> usize { + 51 + } + + fn modulus() -> BigInt { + BigInt::parse_bytes( + b"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", + 16, + ) + .unwrap() + } + + fn is_modulus_pseudo_mersenne() -> bool { + true + } + + fn pseudo_mersenne_params() -> Option { + Some(PseudoMersennePrime { + e: 255, + c: BigInt::from(19), + }) + } +} + +type Ed25519Fp = EmulatedFieldElement; + +impl From<&Fe25519> for Ed25519Fp +where + F: PrimeField + PrimeFieldBits, +{ + fn from(value: &Fe25519) -> Self { + Ed25519Fp::::from(&value.0) + } +} + +#[derive(Clone)] +pub struct AllocatedAffinePoint { + x: Ed25519Fp, + y: Ed25519Fp, + value: AffinePoint, +} + +impl AllocatedAffinePoint { + pub fn get_point(&self) -> AffinePoint { + self.value.clone() + } + + pub fn alloc_affine_point(cs: &mut CS, value: &AffinePoint) -> Result + where + CS: ConstraintSystem, + { + let x_limb_values = Ed25519Fp::::from(&value.x); + let y_limb_values = Ed25519Fp::::from(&value.y); + + let x = + x_limb_values.allocate_field_element_unchecked(&mut cs.namespace(|| "allocate x"))?; + let y = + y_limb_values.allocate_field_element_unchecked(&mut cs.namespace(|| "allocate y"))?; + + Ok(Self { + x, + y, + value: value.clone(), + }) + } + + pub fn alloc_identity_point(cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + let identity_value = AffinePoint::default(); + let identity = Self::alloc_affine_point( + &mut cs.namespace(|| "alloc identity point"), + &identity_value, + )?; + + identity.x.assert_equality_to_constant( + &mut cs.namespace(|| "check x equals 0"), + &Ed25519Fp::zero(), + )?; + identity.y.assert_equality_to_constant( + &mut cs.namespace(|| "check y equals 1"), + &Ed25519Fp::one(), + )?; + Ok(identity) + } + + fn verify_point_addition( + cs: &mut CS, + p: &Self, + q: &Self, + r: &Self, + ) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + let x1 = &p.x; + let y1 = &p.y; + let x2 = &q.x; + let y2 = &q.y; + let x3 = &r.x; + let y3 = &r.y; + + let one = Ed25519Fp::::from(&Fe25519::one()); + let d = Ed25519Fp::::from(&Ed25519Curve::d()); + + let x1x2 = x1.mul(&mut cs.namespace(|| "x1*x2"), x2)?; + let y1y2 = y1.mul(&mut cs.namespace(|| "y1*y2"), y2)?; + let x1y2 = x1.mul(&mut cs.namespace(|| "x1*y2"), y2)?; + let x2y1 = x2.mul(&mut cs.namespace(|| "x2*y1"), y1)?; + + let x1x2y1y2 = x1x2.mul(&mut cs.namespace(|| "x1*x2*y1*y2"), &y1y2)?; + let dx1x2y1y2 = d.mul(&mut cs.namespace(|| "d*x1*x2*y1*y2"), &x1x2y1y2)?; + + let dx1x2y1y2_plus_1 = one.add(&mut cs.namespace(|| "1 + d*x1*x2*y1*y2"), &dx1x2y1y2)?; + let neg_dx1x2y1y2_plus_1 = + one.sub(&mut cs.namespace(|| "1 - d*x1*x2*y1*y2"), &dx1x2y1y2)?; + + let x3_times_denominator = x3.mul( + &mut cs.namespace(|| "x3*(1 + d*x1*x2*y1*y2)"), + &dx1x2y1y2_plus_1, + )?; + + let x1y2_plus_x2y1 = x1y2.add(&mut cs.namespace(|| "x1*y2 + x1*y2"), &x2y1)?; + Ed25519Fp::::assert_is_equal( + &mut cs.namespace(|| "x3*(1 + d*x1*x2*y1*y2) == x1*y2 + x2*y1"), + &x1y2_plus_x2y1, + &x3_times_denominator, + )?; + + let y3_times_denominator = y3.mul( + &mut cs.namespace(|| "y3*(1 - d*x1*x2*y1*y2)"), + &neg_dx1x2y1y2_plus_1, + )?; + + let x1x2_plus_y1y2 = x1x2.add(&mut cs.namespace(|| "Reduce x1*x2 + y1*y2"), &y1y2)?; + Ed25519Fp::::assert_is_equal( + &mut cs.namespace(|| "y3*(1 - d*x1*x2*y1*y2) == x1*x2 + y1*y2"), + &y3_times_denominator, + &x1x2_plus_y1y2, + )?; + + Ok(()) + } + + pub fn ed25519_point_addition( + cs: &mut CS, + p: &Self, + q: &Self, + ) -> Result + where + CS: ConstraintSystem, + { + let sum_value = &p.value + &q.value; + let sum = Self::alloc_affine_point(&mut cs.namespace(|| "allocate sum"), &sum_value)?; + + sum.x.check_field_membership( + &mut cs.namespace(|| "check x coordinate of sum is in base field"), + )?; + sum.y.check_field_membership( + &mut cs.namespace(|| "check y coordinate of sum is in base field"), + )?; + + Self::verify_point_addition(&mut cs.namespace(|| "verify point addition"), p, q, &sum)?; + + Ok(sum) + } + + fn verify_point_doubling( + cs: &mut CS, + p: &Self, + doubled_p: &Self, + ) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + if !p.value.is_on_curve() { + eprintln!("Input to this method must be a curve point"); + return Err(SynthesisError::Unsatisfiable); + } + + let x = &p.x; + let y = &p.y; + + let x2 = x.mul(&mut cs.namespace(|| "x*x"), x)?; + let y2 = y.mul(&mut cs.namespace(|| "y*y"), y)?; + let xy = x.mul(&mut cs.namespace(|| "x*y"), y)?; + + // Numerator of doubled_p x-coordinate + let expected_x_numerator = xy.mul_const(&mut cs.namespace(|| "2*x*y"), &BigInt::from(2))?; + let minus_x2 = x2.neg(&mut cs.namespace(|| "-x*x"))?; + // Since curve equation is -x^2 + y^2 = 1 + dx^2y^2, we can calculate the RHS using the LHS + let doubled_p_x_denominator = + minus_x2.add(&mut cs.namespace(|| "-x*x+ y*y a.k.a 1 + d*x*x*y*y"), &y2)?; + let doubled_p_x_numerator = doubled_p.x.mul( + &mut cs.namespace(|| "2P.x times (1+d*x*x*y*y)"), + &doubled_p_x_denominator, + )?; + Ed25519Fp::::assert_is_equal( + &mut cs.namespace(|| "2P.x times (1+d*x*x*y*y) == 2*x*y"), + &doubled_p_x_numerator, + &expected_x_numerator, + )?; + + // Numerator of doubled_p y-coordinate + let expected_y_numerator = x2.add(&mut cs.namespace(|| "x*x + y*y"), &y2)?; + let two = Ed25519Fp::::from(&Fe25519::from(2u64)); + let doubled_p_y_denominator = two.sub( + &mut cs.namespace(|| " 2 - (1 + d*x*x*y*y) = 1 - d*x*x*y*y"), + &doubled_p_x_denominator, + )?; + let doubled_p_y_numerator = doubled_p.y.mul( + &mut cs.namespace(|| "2P.y times (1-d*x*x*y*y)"), + &doubled_p_y_denominator, + )?; + Ed25519Fp::::assert_is_equal( + &mut cs.namespace(|| "2P.y times (1-d*x*x*y*y) == x*x + y*y"), + &doubled_p_y_numerator, + &expected_y_numerator, + )?; + + Ok(()) + } + + pub fn ed25519_point_doubling(cs: &mut CS, p: &Self) -> Result + where + CS: ConstraintSystem, + { + let double_value = p.value.double(); + let double_p = + Self::alloc_affine_point(&mut cs.namespace(|| "allocate 2P"), &double_value)?; + + double_p.x.check_field_membership( + &mut cs.namespace(|| "check x coordinate of double point is in base field"), + )?; + double_p.y.check_field_membership( + &mut cs.namespace(|| "check y coordinate of double point is in base field"), + )?; + + Self::verify_point_doubling(&mut cs.namespace(|| "verify point doubling"), p, &double_p)?; + + Ok(double_p) + } + + /// If `condition` is true, return `in1`. Otherwise, return `in0`. + /// `selector_bits` are little-endian order + fn conditionally_select( + cs: &mut CS, + inputs: &[Self], + selector_bits: &[Boolean], + ) -> Result + where + CS: ConstraintSystem, + { + if inputs.len() != (1usize << selector_bits.len()) { + eprintln!( + "Number of inputs {} must be equal to 2^(number of selector bits) = 2^{}", + inputs.len(), + selector_bits.len(), + ); + return Err(SynthesisError::Unsatisfiable); + } + let inputs_x = inputs.iter().map(|i| i.x.clone()).collect::>(); + let inputs_y = inputs.iter().map(|i| i.y.clone()).collect::>(); + + let x = EmulatedFieldElement::mux_tree( + &mut cs.namespace(|| "allocate value of output x coordinate"), + selector_bits.iter().rev(), // mux_tree requires MSB first + &inputs_x, + )?; + let y = EmulatedFieldElement::mux_tree( + &mut cs.namespace(|| "allocate value of output y coordinate"), + selector_bits.iter().rev(), // mux_tree requires MSB first + &inputs_y, + )?; + + let mut res_index = 0usize; + for (i, b) in selector_bits.iter().enumerate() { + if b.get_value().unwrap() { + res_index += 1 << i; + } + } + let value = inputs[res_index].value.clone(); + + Ok(Self { x, y, value }) + } + + pub fn ed25519_scalar_multiplication_windowed( + &self, + cs: &mut CS, + scalar: Vec, + window_size: i32, + ) -> Result + where + CS: ConstraintSystem, + { + if window_size <= 0 { + eprintln!("Window size must be positive"); + return Err(SynthesisError::Unsatisfiable); + }; + if scalar.len() < window_size as usize { + eprintln!("Scalar bit vector cannot be shorter than window size"); + return Err(SynthesisError::Unsatisfiable); + }; + if scalar.len() >= 254usize { + // the largest curve25519 scalar fits in 253 bits + eprintln!("Scalar bit vector has more than 253 bits"); + return Err(SynthesisError::Unsatisfiable); + } + + // No range checks on limbs required as it is checked to be equal to (0,1) + let identity_point = + Self::alloc_identity_point(&mut cs.namespace(|| "allocate identity point"))?; + + // Remember to avoid field membership checks before calling this function + self.x.check_field_membership( + &mut cs.namespace(|| "check x coordinate of base point is in base field"), + )?; + self.y.check_field_membership( + &mut cs.namespace(|| "check y coordinate of base point is in base field"), + )?; + + let mut lookup_table: Vec = vec![]; + lookup_table.push(identity_point); + lookup_table.push(self.clone()); + + for i in 2..(1usize << window_size) { + if i % 2 == 0 { + lookup_table.push(Self::ed25519_point_doubling( + &mut cs.namespace(|| format!("allocate {i} times the base")), + &lookup_table[i / 2], + )?); + } else { + lookup_table.push(Self::ed25519_point_addition( + &mut cs.namespace(|| format!("allocate {i} times the base")), + &lookup_table[i - 1], + self, + )?); + }; + } + + let n: i32 = (scalar.len() - 1) as i32; + + let mut window_bits: Vec = vec![]; + for i in 0..window_size { + window_bits.push(scalar[(n - window_size + 1 + i) as usize].clone()) + } + + let mut output = Self::conditionally_select( + &mut cs.namespace(|| "allocate initial value of output"), + &lookup_table, + &window_bits, + )?; + + let mut i: i32 = n - window_size; + while i >= window_size - 1 { + for j in 0..window_size { + output = Self::ed25519_point_doubling( + &mut cs.namespace(|| format!("doubling number {} in iteration {i}", j + 1)), + &output, + )?; + } + + window_bits.clear(); + for j in 0..window_size { + window_bits.push(scalar[(i - window_size + 1 + j) as usize].clone()) + } + + let tmp = Self::conditionally_select( + &mut cs.namespace(|| format!("allocate tmp value in iteration {i}")), + &lookup_table, + &window_bits, + )?; + + output = Self::ed25519_point_addition( + &mut cs.namespace(|| format!("allocate sum of output and tmp in iteration {i}")), + &output, + &tmp, + )?; + + i -= window_size; + } + + let num_remaining_bits = scalar.len() % (window_size as usize); + + if num_remaining_bits != 0 { + for j in 0..num_remaining_bits { + output = Self::ed25519_point_doubling( + &mut cs.namespace(|| format!("final output doubling number {}", j + 1)), + &output, + )?; + } + let tmp = Self::ed25519_point_addition( + &mut cs.namespace(|| format!("sum of {}*output and base", 1 << num_remaining_bits)), + &output, + self, + )?; + + let mut final_lookup_table = vec![]; + final_lookup_table.push(output.clone()); + final_lookup_table.push(tmp); + + #[allow(clippy::needless_range_loop)] + for j in 2..(1usize << num_remaining_bits) { + let tmp = Self::ed25519_point_addition( + &mut cs.namespace(|| { + format!("sum of {}*output and {j}*base", 1 << num_remaining_bits) + }), + &output, + &lookup_table[j], + )?; + final_lookup_table.push(tmp); + } + + window_bits.clear(); + window_bits = scalar[..num_remaining_bits].to_vec(); + + output = Self::conditionally_select( + &mut cs.namespace(|| "final doubling of output"), + &final_lookup_table, + &window_bits, + )?; + } + + Ok(output) + } + + pub fn ed25519_scalar_multiplication( + &self, + cs: &mut CS, + scalar: Vec, + ) -> Result + where + CS: ConstraintSystem, + { + Self::ed25519_scalar_multiplication_windowed( + self, + &mut cs.namespace(|| { + format!("scalar multiplication with window {DEFAULT_SCALAR_MULT_WINDOW_SIZE}") + }), + scalar, + DEFAULT_SCALAR_MULT_WINDOW_SIZE, + ) + } +} + +#[cfg(test)] +mod tests { + use crate::curve::Ed25519Curve; + + use super::*; + use bellpepper_core::test_cs::TestConstraintSystem; + use num_bigint::{BigUint, RandBigInt}; + use num_integer::Integer; + use num_traits::Zero; + use pasta_curves::Fp; + + #[test] + fn alloc_affine_point_addition() { + let b = Ed25519Curve::basepoint(); + let mut rng = rand::thread_rng(); + let scalar = rng.gen_biguint_range(&BigUint::zero(), &Ed25519Curve::order()); + let p = Ed25519Curve::scalar_multiplication(&b, &scalar); + let scalar = rng.gen_biguint_range(&BigUint::zero(), &Ed25519Curve::order()); + let q = Ed25519Curve::scalar_multiplication(&b, &scalar); + let sum_expected_value = &p + &q; + + let mut cs = TestConstraintSystem::::new(); + + let p_alloc = + AllocatedAffinePoint::alloc_affine_point(&mut cs.namespace(|| "alloc point p"), &p); + assert!(p_alloc.is_ok()); + let p_al = p_alloc.unwrap(); + + let q_alloc = + AllocatedAffinePoint::alloc_affine_point(&mut cs.namespace(|| "alloc point q"), &q); + assert!(q_alloc.is_ok()); + let q_al = q_alloc.unwrap(); + + let sum_alloc = AllocatedAffinePoint::ed25519_point_addition( + &mut cs.namespace(|| "adding p and q"), + &p_al, + &q_al, + ); + assert!(sum_alloc.is_ok()); + let sum_al = sum_alloc.unwrap(); + + assert_eq!(sum_expected_value, sum_al.value); + + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 3689); + assert_eq!(cs.num_inputs(), 1); + } + + #[test] + fn alloc_affine_point_doubling() { + let b = Ed25519Curve::basepoint(); + let mut rng = rand::thread_rng(); + let scalar = rng.gen_biguint_range(&BigUint::zero(), &Ed25519Curve::order()); + let p = Ed25519Curve::scalar_multiplication(&b, &scalar); + let double_expected_value = p.double(); + + let mut cs = TestConstraintSystem::::new(); + + let p_alloc = + AllocatedAffinePoint::alloc_affine_point(&mut cs.namespace(|| "alloc point p"), &p); + assert!(p_alloc.is_ok()); + let p_al = p_alloc.unwrap(); + + let double_alloc = + AllocatedAffinePoint::ed25519_point_doubling(&mut cs.namespace(|| "doubling p"), &p_al); + assert!(double_alloc.is_ok()); + let double_al = double_alloc.unwrap(); + + assert_eq!(double_expected_value, double_al.value); + + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 1749); + assert_eq!(cs.num_inputs(), 1); + } + + #[test] + fn alloc_affine_scalar_multiplication_default() { + let b = Ed25519Curve::basepoint(); + let mut rng = rand::thread_rng(); + + let mut scalar = rng.gen_biguint(256u64); + scalar >>= 3; // scalar now has 253 significant bits + let p = Ed25519Curve::scalar_multiplication(&b, &scalar); + + let mut scalar_vec: Vec = vec![]; + for _i in 0..253 { + if scalar.is_odd() { + scalar_vec.push(Boolean::constant(true)) + } else { + scalar_vec.push(Boolean::constant(false)) + }; + scalar >>= 1; + } + + let mut cs = TestConstraintSystem::::new(); + + let b_alloc = AllocatedAffinePoint::alloc_affine_point( + &mut cs.namespace(|| "allocate base point"), + &b, + ); + assert!(b_alloc.is_ok()); + let b_al = b_alloc.unwrap(); + + let p_alloc = b_al.ed25519_scalar_multiplication( + &mut cs.namespace(|| "scalar multiplication"), + scalar_vec, + ); + assert!(p_alloc.is_ok()); + let p_al = p_alloc.unwrap(); + + assert_eq!(p, p_al.value); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 716_086); + assert_eq!(cs.num_inputs(), 1); + } + + #[test] + fn alloc_affine_scalar_multiplication_window_range() { + assert_eq!(scalar_multiplication_helper(1), 1_373_558); + assert_eq!(scalar_multiplication_helper(2), 913_693); + assert_eq!(scalar_multiplication_helper(3), 769_982); + assert_eq!(scalar_multiplication_helper(4), 716_086); + assert_eq!(scalar_multiplication_helper(5), 738_128); + } + + fn scalar_multiplication_helper(window_size: i32) -> usize { + let b = Ed25519Curve::basepoint(); + let mut rng = rand::thread_rng(); + + let mut scalar = rng.gen_biguint(256u64); + scalar >>= 3; // scalar now has 253 significant bits + let p = Ed25519Curve::scalar_multiplication(&b, &scalar); + + let mut scalar_vec: Vec = vec![]; + for _i in 0..253 { + if scalar.is_odd() { + scalar_vec.push(Boolean::constant(true)) + } else { + scalar_vec.push(Boolean::constant(false)) + }; + scalar >>= 1; + } + + let mut cs = TestConstraintSystem::::new(); + + let b_alloc = AllocatedAffinePoint::alloc_affine_point( + &mut cs.namespace(|| "allocate base point"), + &b, + ); + assert!(b_alloc.is_ok()); + let b_al = b_alloc.unwrap(); + + let p_alloc = b_al.ed25519_scalar_multiplication_windowed( + &mut cs.namespace(|| "scalar multiplication"), + scalar_vec, + window_size, + ); + assert!(p_alloc.is_ok()); + let p_al = p_alloc.unwrap(); + + assert_eq!(p, p_al.value); + + assert!(cs.is_satisfied()); + cs.num_constraints() + } +} diff --git a/crates/bellpepper-ed25519/src/curve.rs b/crates/bellpepper-ed25519/src/curve.rs new file mode 100644 index 0000000..e284774 --- /dev/null +++ b/crates/bellpepper-ed25519/src/curve.rs @@ -0,0 +1,256 @@ +use std::ops::{Add, Neg, Sub}; + +use num_bigint::{BigInt, BigUint}; +use num_integer::Integer; + +use crate::field::Fe25519; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AffinePoint { + pub x: Fe25519, + pub y: Fe25519, +} + +impl Add for AffinePoint { + type Output = AffinePoint; + + fn add(self, rhs: AffinePoint) -> Self::Output { + Ed25519Curve::add_points(&self, &rhs) + } +} + +impl Add<&AffinePoint> for AffinePoint { + type Output = AffinePoint; + + fn add(self, rhs: &AffinePoint) -> Self::Output { + Ed25519Curve::add_points(&self, rhs) + } +} + +impl Add<&AffinePoint> for &AffinePoint { + type Output = AffinePoint; + + fn add(self, rhs: &AffinePoint) -> Self::Output { + Ed25519Curve::add_points(self, rhs) + } +} + +impl Sub for AffinePoint { + type Output = AffinePoint; + + fn sub(self, rhs: AffinePoint) -> Self::Output { + let rhs_neg = -rhs; + Ed25519Curve::add_points(&self, &rhs_neg) + } +} + +impl Sub<&AffinePoint> for AffinePoint { + type Output = AffinePoint; + + fn sub(self, rhs: &AffinePoint) -> Self::Output { + let rhs_neg = -rhs; + Ed25519Curve::add_points(&self, &rhs_neg) + } +} + +impl Sub<&AffinePoint> for &AffinePoint { + type Output = AffinePoint; + + fn sub(self, rhs: &AffinePoint) -> Self::Output { + let rhs_neg = -rhs; + Ed25519Curve::add_points(self, &rhs_neg) + } +} + +impl Neg for AffinePoint { + type Output = Self; + + fn neg(self) -> Self::Output { + AffinePoint { + x: self.x.neg(), + y: self.y, + } + } +} + +impl Neg for &AffinePoint { + type Output = AffinePoint; + + fn neg(self) -> Self::Output { + AffinePoint { + x: self.x.clone().neg(), + y: self.y.clone(), + } + } +} + +impl AffinePoint { + pub fn is_on_curve(&self) -> bool { + Ed25519Curve::is_on_curve(self) + } + + pub fn is_zero(&self) -> bool { + self.x == Fe25519::zero() && self.y == Fe25519::one() + } + + pub fn double(&self) -> Self { + Ed25519Curve::add_points(self, self) + } +} + +impl Default for AffinePoint { + fn default() -> Self { + Self { + x: Fe25519::zero(), + y: Fe25519::one(), + } + } +} + +pub struct Ed25519Curve; + +impl Ed25519Curve { + pub fn d() -> Fe25519 { + Fe25519( + BigInt::parse_bytes( + b"52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3", + 16, + ) + .unwrap(), + ) + } + + pub fn order() -> BigUint { + BigUint::parse_bytes( + b"1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", + 16, + ) + .unwrap() + } + + pub fn recover_even_x_from_y(y: &Fe25519) -> Fe25519 { + let y_sq = &y.square(); + let x_sq = (y_sq - &Fe25519::one()) * (Self::d() * y_sq + Fe25519::one()).invert().unwrap(); + + let x = x_sq.sqrt(); + assert!(x.is_some()); // y must correspond to a curve point + let x = x.unwrap(); + if x.is_even() { + x + } else { + -x + } + } + + pub fn basepoint() -> AffinePoint { + let y = Fe25519::from(4u64) * Fe25519::from(5u64).invert().unwrap(); + let x = Self::recover_even_x_from_y(&y); + AffinePoint { x, y } + } + + pub fn is_on_curve(point: &AffinePoint) -> bool { + let x = &point.x; + let y = &point.y; + let x_sq = x.square(); + let y_sq = y.square(); + let tmp = -&x_sq + &y_sq - Fe25519::one() - Self::d() * x_sq * y_sq; + tmp == Fe25519::zero() + } + + pub fn add_points(p: &AffinePoint, q: &AffinePoint) -> AffinePoint { + let x1 = &p.x; + let y1 = &p.y; + let x2 = &q.x; + let y2 = &q.y; + let dx1x2y1y2 = Self::d() * x1 * x2 * y1 * y2; + AffinePoint { + x: (x1 * y2 + x2 * y1) * (Fe25519::one() + &dx1x2y1y2).invert().unwrap(), + y: (x1 * x2 + y1 * y2) * (Fe25519::one() - &dx1x2y1y2).invert().unwrap(), + } + } + + pub fn scalar_multiplication(point: &AffinePoint, scalar: &BigUint) -> AffinePoint { + assert!(scalar.bits() <= 256u64); + let mut scaled_scalar = scalar.clone(); + // let mut scaled_scalar = scalar.rem(Self::order()); + let num_scalar_bits = scaled_scalar.bits() as usize; + let mut output = AffinePoint::default(); + let mut step_point: AffinePoint = point.clone(); + for _i in 0..num_scalar_bits { + if scaled_scalar.is_odd() { + output = output + &step_point; + } + step_point = step_point.double(); + scaled_scalar >>= 1; + } + output + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn random_point() -> AffinePoint { + let mut rng = rand::thread_rng(); + let mut point = AffinePoint::default(); + loop { + let y = Fe25519::random(&mut rng); + let y_sq = &y.square(); + let x_sq = (y_sq - &Fe25519::one()) + * (Ed25519Curve::d() * y_sq + Fe25519::one()) + .invert() + .unwrap(); + + let x = x_sq.sqrt(); + if let Some(x) = x { + point.x = x; + point.y = y; + break; + } + } + point + } + + #[test] + fn point_negation() { + let p = random_point(); + assert!(Ed25519Curve::is_on_curve(&p)); + let neg_p = -&p; + let sum = p + &neg_p; + assert!(sum.is_zero()); + } + + #[test] + fn point_addition_difference() { + let p = &random_point(); + assert!(Ed25519Curve::is_on_curve(p)); + let p2 = &p.double(); + let p3 = &(p + p + p); + assert_eq!(p2 + p, *p3); + assert_eq!(p3 - p, *p2); + } + + #[test] + fn point_scalar_multiplication() { + let b = &Ed25519Curve::basepoint(); + assert!(Ed25519Curve::is_on_curve(b)); + let scalar = BigUint::from(6u8); + let p = Ed25519Curve::scalar_multiplication(b, &scalar); + assert_eq!(p, b + b + b + b + b + b); + } + + #[test] + fn point_order() { + let b = Ed25519Curve::basepoint(); + assert!(Ed25519Curve::is_on_curve(&b)); + let scalar: BigUint = Ed25519Curve::order(); + let p = Ed25519Curve::scalar_multiplication(&b, &scalar); + assert!(p.is_zero()); + + let p = random_point(); + let scalar = Ed25519Curve::order() << 3; // Accounting for the co-factor + let p = Ed25519Curve::scalar_multiplication(&p, &scalar); + assert!(p.is_zero()); + } +} diff --git a/crates/bellpepper-ed25519/src/field.rs b/crates/bellpepper-ed25519/src/field.rs new file mode 100644 index 0000000..4c3b48d --- /dev/null +++ b/crates/bellpepper-ed25519/src/field.rs @@ -0,0 +1,372 @@ +use std::{ + borrow::Borrow, + iter::{Product, Sum}, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign}, +}; + +use num_bigint::{BigInt, RandBigInt, Sign}; +use num_integer::Integer; +use num_traits::{One, Zero}; +use rand::RngCore; + +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct Fe25519(pub(crate) BigInt); + +impl From for Fe25519 { + fn from(value: u64) -> Self { + Self(BigInt::from(value)) + } +} + +impl From for Fe25519 { + fn from(value: BigInt) -> Self { + Self(value) + } +} + +impl Add for Fe25519 { + type Output = Fe25519; + + fn add(self, rhs: Self) -> Self::Output { + Self((&self.0 + &rhs.0).rem(Self::modulus())) + } +} + +impl AddAssign for Fe25519 { + fn add_assign(&mut self, rhs: Self) { + self.0 = (&self.0 + &rhs.0).rem(Self::modulus()) + } +} + +impl<'a> Add<&'a Fe25519> for Fe25519 { + type Output = Fe25519; + + fn add(self, rhs: &'a Fe25519) -> Self::Output { + Self((&self.0 + &rhs.0).rem(Self::modulus())) + } +} + +impl<'a> Add<&'a Fe25519> for &Fe25519 { + type Output = Fe25519; + + fn add(self, rhs: &'a Fe25519) -> Self::Output { + Fe25519((self.0.clone() + rhs.0.clone()).rem(Fe25519::modulus())) + } +} + +impl<'a> AddAssign<&'a Fe25519> for Fe25519 { + fn add_assign(&mut self, rhs: &'a Fe25519) { + self.0 = (&self.0 + &rhs.0).rem(Self::modulus()) + } +} + +impl Sub for Fe25519 { + type Output = Fe25519; + + fn sub(self, rhs: Self) -> Self::Output { + Self((&self.0 + Self::modulus() - &rhs.0).rem(Self::modulus())) + } +} + +impl<'a> Sub<&'a Fe25519> for Fe25519 { + type Output = Fe25519; + + fn sub(self, rhs: &'a Fe25519) -> Self::Output { + Self((&self.0 + Self::modulus() - &rhs.0).rem(Self::modulus())) + } +} + +impl SubAssign for Fe25519 { + fn sub_assign(&mut self, rhs: Self) { + self.0 = (&self.0 + Self::modulus() - &rhs.0).rem(Self::modulus()) + } +} + +impl<'a> Sub<&'a Fe25519> for &Fe25519 { + type Output = Fe25519; + + fn sub(self, rhs: &'a Fe25519) -> Self::Output { + Fe25519((self.0.clone() + Fe25519::modulus() - rhs.0.clone()).rem(Fe25519::modulus())) + } +} +impl<'a> SubAssign<&'a Fe25519> for Fe25519 { + fn sub_assign(&mut self, rhs: &'a Fe25519) { + self.0 = (&self.0 + Self::modulus() - &rhs.0).rem(Self::modulus()) + } +} + +impl Mul for Fe25519 { + type Output = Fe25519; + + fn mul(self, rhs: Self) -> Self::Output { + Self((&self.0 * &rhs.0).rem(Self::modulus())) + } +} + +impl<'a> Mul<&'a Fe25519> for Fe25519 { + type Output = Fe25519; + + fn mul(self, rhs: &'a Fe25519) -> Self::Output { + Self((&self.0 * &rhs.0).rem(Self::modulus())) + } +} + +impl<'a> Mul<&'a Fe25519> for &Fe25519 { + type Output = Fe25519; + + fn mul(self, rhs: &'a Fe25519) -> Self::Output { + Fe25519((&self.0 * &rhs.0).rem(Fe25519::modulus())) + } +} + +impl<'a> MulAssign<&'a Fe25519> for Fe25519 { + fn mul_assign(&mut self, rhs: &'a Fe25519) { + self.0 = (&self.0 * &rhs.0).rem(Self::modulus()) + } +} + +impl Neg for Fe25519 { + type Output = Self; + fn neg(self) -> Self::Output { + let p = Fe25519::modulus(); + Self(p - self.0) + } +} + +impl<'a> Neg for &'a Fe25519 { + type Output = Fe25519; + fn neg(self) -> Self::Output { + let p = Fe25519::modulus(); + Fe25519(p - &self.0) + } +} + +impl Sum for Fe25519 +where + T: Borrow, +{ + fn sum>(iter: I) -> Self { + iter.fold(Fe25519::zero(), |acc, item| acc + item.borrow()) + } +} + +impl Product for Fe25519 +where + T: Borrow, +{ + fn product>(iter: I) -> Self { + iter.fold(Fe25519::one(), |acc, item| acc * item.borrow()) + } +} + +impl Fe25519 { + pub fn zero() -> Self { + Self(BigInt::zero()) + } + + pub fn one() -> Self { + Self(BigInt::one()) + } + + pub fn modulus() -> BigInt { + BigInt::parse_bytes( + b"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", + 16, + ) + .unwrap() + } + + pub fn sqrt_minus_one() -> Self { + Self( + BigInt::parse_bytes( + b"2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0", + 16, + ) + .unwrap(), + ) + } + + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + + pub fn random(mut rng: impl RngCore) -> Self { + Self(rng.gen_bigint_range(&BigInt::zero(), &Self::modulus())) + } + + pub fn square(&self) -> Self { + let sq = &self.0 * &self.0; + Self(sq.rem(Self::modulus())) + } + + pub fn pow(&self, exponent: &BigInt) -> Self { + let pw = self.0.modpow(exponent, &Self::modulus()); + Self(pw) + } + + pub fn double(&self) -> Self { + let dbl: BigInt = &self.0 << 1; + Self(dbl.rem(Self::modulus())) + } + + pub fn invert(&self) -> Option { + if self.is_zero() { + None + } else { + let p = Self::modulus(); + let p_minus_2 = &p - BigInt::from(2); + let inv = self.0.modpow(&p_minus_2, &p); + Some(Self(inv)) + } + } + + // https://www.rfc-editor.org/rfc/rfc8032#section-5.1.3 + pub fn sqrt(&self) -> Option { + let exponent = (Self::modulus() + 3) / 8; + let beta = self.pow(&exponent); // candidate square root + + let beta_sq = beta.square(); + let is_sq_root = (&beta_sq - self).is_zero() | (&beta_sq + self).is_zero(); + + let neg_not_required = (&beta_sq - self).is_zero(); + let sq_root = if neg_not_required { + beta + } else { + beta * Self::sqrt_minus_one() + }; + + if is_sq_root { + Some(sq_root) + } else { + None + } + } + + pub fn is_even(&self) -> bool { + self.0.is_even() + } + + pub fn to_bytes_le(&self) -> [u8; 32] { + let (_sign, bytes) = self.0.to_bytes_le(); + bytes + .into_iter() + .chain(vec![0u8; 31]) + .take(32) + .collect::>() + .try_into() + .unwrap() + } + + pub fn from_bytes_le(bytes: &[u8]) -> Self { + Self(BigInt::from_bytes_le(Sign::Plus, bytes)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sanity_checks() { + let two = Fe25519::from(2u64); + let four = two.clone().mul(&two); + let expected_four = Fe25519::from(4u64); + + assert_eq!(four, expected_four); + assert_eq!(two.pow(&BigInt::from(2)), four); + assert_eq!(two.square(), four); + + let two_inv = two.invert().unwrap(); + assert_eq!(two_inv * two, Fe25519::one()); + + let sqrt_m1 = Fe25519::sqrt_minus_one(); + assert_eq!(sqrt_m1.square(), -Fe25519::one()); + } + + #[test] + fn check_add_sub() { + let mut rng = rand::thread_rng(); + let mut x = Fe25519::random(&mut rng); + let y = Fe25519::random(&mut rng); + let neg_y = -&y; + assert_eq!(&y + &neg_y, Fe25519::zero()); + assert_eq!(&x + &neg_y, &x - &y); + + let old_x = x.clone(); + x -= &y; + assert_eq!(&x + &y, old_x); + x += &y; + assert_eq!(x, old_x); + + let a = [ + x.clone(), + Fe25519::from(1u64), + Fe25519::from(2u64), + y.clone(), + ]; + assert_eq!(Fe25519::sum(a.iter()), &x + &y + Fe25519::from(3u64)); + + let y_ref = &y; + assert_eq!(&x + &y, &x + y_ref); + x += y_ref; + assert_eq!(&x - &y, &x - y_ref); + x -= y_ref; + assert_eq!(&x + &y, &x + y_ref); + } + + #[test] + fn check_mul() { + let mut rng = rand::thread_rng(); + let mut x = Fe25519::random(&mut rng); + let y = Fe25519::random(&mut rng); + assert_eq!(x.invert().unwrap() * &x, Fe25519::one()); + + let old_x = x.clone(); + x *= &y; + assert_eq!(&x * &y.invert().unwrap(), old_x); + + let a = [ + x.clone(), + Fe25519::from(2u64), + Fe25519::from(3u64), + y.clone(), + ]; + assert_eq!(Fe25519::product(a.iter()), &x * &y * Fe25519::from(6u64)); + + let y_ref = &y; + assert_eq!(&x * &y, &x * y_ref); + x *= y_ref; + assert_eq!(&x * &y.invert().unwrap(), x * y_ref.invert().unwrap()); + } + + #[test] + fn check_square_double() { + let mut rng = rand::thread_rng(); + let x = Fe25519::random(&mut rng); + assert_eq!(x.square(), &x * &x); + assert_eq!(x.double(), &x + &x); + let two = Fe25519::from(2u64); + assert_eq!(x.double(), x * two); + } + + #[test] + fn check_square_root() { + let two = Fe25519::from(2u64); + assert!(two.sqrt().is_none()); + + let four = two.square(); + let sq_root_opt = Fe25519::sqrt(&four); + assert!(sq_root_opt.is_some()); + let sq_root = sq_root_opt.unwrap(); + assert!(sq_root == two || sq_root == -two); + + let mut rng = rand::thread_rng(); + let x = Fe25519::random(&mut rng); + let x_sq = x.square(); + let sq_root_opt = Fe25519::sqrt(&x_sq); + assert!(sq_root_opt.is_some()); + let sq_root = sq_root_opt.unwrap(); + assert!(sq_root == x || sq_root == -x); + assert_eq!(sq_root.square(), x_sq); + } +} diff --git a/crates/bellpepper-ed25519/src/lib.rs b/crates/bellpepper-ed25519/src/lib.rs new file mode 100644 index 0000000..6a764fd --- /dev/null +++ b/crates/bellpepper-ed25519/src/lib.rs @@ -0,0 +1,3 @@ +pub mod circuit; +pub mod curve; +pub mod field; diff --git a/crates/bellpepper-emulated/Cargo.toml b/crates/bellpepper-emulated/Cargo.toml new file mode 100644 index 0000000..dce2990 --- /dev/null +++ b/crates/bellpepper-emulated/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bellpepper-emulated" +version = "0.2.0" +edition = "2021" +authors = ["Saravanan Vijayakumaran "] +license = "MIT OR Apache-2.0" +description = "Nonnative arithmetic library using bellpepper inspired by the emulated package in Gnark" +documentation = "https://docs.rs/bellpepper-emulated" +homepage.workspace = true +repository.workspace = true + +[dependencies] +bellpepper-core = { workspace = true } +bellpepper = { workspace = true } +ff = { workspace = true } +num-bigint = { version = "0.4.3", features = ["rand"] } +num-traits = "0.2.15" + +[dev-dependencies] +pasta_curves = "0.5.1" +rand = "0.8.5" diff --git a/crates/bellpepper-emulated/LICENSE-APACHE b/crates/bellpepper-emulated/LICENSE-APACHE new file mode 100644 index 0000000..09bb92f --- /dev/null +++ b/crates/bellpepper-emulated/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 2023 Saravanan Vijayakumaran + + 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. \ No newline at end of file diff --git a/crates/bellpepper-emulated/LICENSE-MIT b/crates/bellpepper-emulated/LICENSE-MIT new file mode 100644 index 0000000..8d71f45 --- /dev/null +++ b/crates/bellpepper-emulated/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Saravanan Vijayakumaran + +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/crates/bellpepper-emulated/README.md b/crates/bellpepper-emulated/README.md new file mode 100644 index 0000000..49cd32a --- /dev/null +++ b/crates/bellpepper-emulated/README.md @@ -0,0 +1,20 @@ +# bellpepper-emulated + +Nonnative arithmetic library using [bellpepper](https://github.com/lurk-lab/bellpepper) inspired by the [emulated](https://github.com/Consensys/gnark/tree/master/std/math/emulated) package in [Gnark](https://github.com/Consensys/gnark) + +## 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. \ No newline at end of file diff --git a/crates/bellpepper-emulated/src/field_element.rs b/crates/bellpepper-emulated/src/field_element.rs new file mode 100644 index 0000000..7d459db --- /dev/null +++ b/crates/bellpepper-emulated/src/field_element.rs @@ -0,0 +1,1235 @@ +use std::vec; +use std::{marker::PhantomData, ops::Rem}; + +use bellpepper_core::num::AllocatedNum; +use bellpepper_core::{ + boolean::{AllocatedBit, Boolean}, + num::Num, +}; +use bellpepper_core::{ConstraintSystem, LinearCombination, SynthesisError}; +use ff::{PrimeField, PrimeFieldBits}; +use num_bigint::{BigInt, BigUint}; +use num_traits::{One, Signed, Zero}; + +use crate::util::*; + +pub enum EmulatedLimbs { + Allocated(Vec>), + Constant(Vec), +} + +impl From> for EmulatedLimbs +where + F: PrimeField + PrimeFieldBits, +{ + fn from(value: Vec) -> Self { + EmulatedLimbs::Constant(value) + } +} + +impl AsRef> for EmulatedLimbs +where + F: PrimeField + PrimeFieldBits, +{ + fn as_ref(&self) -> &EmulatedLimbs { + self + } +} + +impl Clone for EmulatedLimbs { + fn clone(&self) -> Self { + match self { + Self::Allocated(a) => Self::Allocated(a.clone()), + Self::Constant(c) => Self::Constant(c.clone()), + } + } +} + +impl EmulatedLimbs +where + F: PrimeField + PrimeFieldBits, +{ + pub(crate) fn allocate_limbs(cs: &mut CS, limb_values: &[F]) -> Result + where + CS: ConstraintSystem, + { + let mut num_vec: Vec> = vec![]; + + for (i, v) in limb_values.iter().enumerate() { + let allocated_limb = + AllocatedNum::alloc(cs.namespace(|| format!("allocating limb {i}")), || Ok(*v))?; + num_vec.push(Num::::from(allocated_limb)); + } + + Ok(EmulatedLimbs::Allocated(num_vec)) + } +} + +/// Parameters of a prime of the form `2^e-c` +pub struct PseudoMersennePrime { + pub e: u32, + pub c: BigInt, +} + +/// Emulated field is assumed to be prime. So inverses always +/// exist for non-zero field elements +pub trait EmulatedFieldParams { + fn num_limbs() -> usize; + fn bits_per_limb() -> usize; + fn modulus() -> BigInt; + + fn is_modulus_pseudo_mersenne() -> bool { + false + } + + fn pseudo_mersenne_params() -> Option { + None + } +} + +#[allow(clippy::len_without_is_empty)] +pub struct EmulatedFieldElement { + pub(crate) limbs: EmulatedLimbs, + pub(crate) overflow: usize, + pub(crate) internal: bool, + pub(crate) marker: PhantomData

, +} + +impl Clone for EmulatedFieldElement +where + F: PrimeField + PrimeFieldBits, + P: EmulatedFieldParams, +{ + fn clone(&self) -> Self { + Self { + limbs: self.limbs.clone(), + overflow: self.overflow, + internal: self.internal, + marker: self.marker, + } + } +} + +impl From<&BigInt> for EmulatedFieldElement +where + F: PrimeField + PrimeFieldBits, + P: EmulatedFieldParams, +{ + /// Converts a [BigInt] into an [EmulatedFieldElement] + /// + /// Note that any [BigInt] larger than the field modulus is + /// first reduced. A [BigInt] equal to the modulus itself is not + /// reduced. + fn from(value: &BigInt) -> Self { + let mut v = value.clone(); + assert!(!v.is_negative()); + + if v > P::modulus() { + v = v.rem(P::modulus()); + } + + assert!(v.bits() <= (P::num_limbs() * P::bits_per_limb()) as u64); + let mut v_bits: Vec = vec![false; P::num_limbs() * P::bits_per_limb()]; + + let v_bytes = v.to_biguint().map(|w| w.to_bytes_le()).unwrap(); + for (i, b) in v_bytes.into_iter().enumerate() { + for j in 0..8usize { + if b & (1u8 << j) != 0 { + v_bits[i * 8 + j] = true; + } + } + } + + let mut limbs = vec![F::ZERO; P::num_limbs()]; + for i in 0..P::num_limbs() { + let mut coeff = F::ONE; + for j in 0..P::bits_per_limb() { + if v_bits[i * P::bits_per_limb() + j] { + limbs[i] += coeff + } + coeff = coeff.double(); + } + } + + Self { + limbs: EmulatedLimbs::Constant(limbs), + overflow: 0, + internal: true, + marker: PhantomData, + } + } +} + +impl From<&EmulatedFieldElement> for BigInt +where + F: PrimeField + PrimeFieldBits, + P: EmulatedFieldParams, +{ + fn from(value: &EmulatedFieldElement) -> Self { + let mut res: BigUint = Zero::zero(); + let one: &BigUint = &One::one(); + let mut base: BigUint = one.clone(); + let limbs = match value.limbs.clone() { + EmulatedLimbs::Allocated(x) => x + .into_iter() + .map(|a| a.get_value().unwrap_or_default()) + .collect(), + EmulatedLimbs::Constant(x) => x, + }; + for limb in limbs { + res += base.clone() * BigUint::from_bytes_le(limb.to_repr().as_ref()); + base *= one << P::bits_per_limb(); + } + BigInt::from(res) + } +} + +impl EmulatedFieldElement +where + F: PrimeField + PrimeFieldBits, + P: EmulatedFieldParams, +{ + pub fn zero() -> EmulatedFieldElement { + EmulatedFieldElement::::from(&BigInt::zero()) + } + + pub fn one() -> EmulatedFieldElement { + EmulatedFieldElement::::from(&BigInt::one()) + } + + pub fn modulus() -> EmulatedFieldElement { + EmulatedFieldElement::::from(&P::modulus()) + } + + pub fn max_overflow() -> usize { + F::CAPACITY as usize - P::bits_per_limb() + } + + pub fn new_internal_element(limbs: EmulatedLimbs, overflow: usize) -> Self { + Self { + limbs, + overflow, + internal: true, + marker: PhantomData, + } + } + + pub fn len(&self) -> usize { + match &self.limbs { + EmulatedLimbs::Allocated(allocated_limbs) => allocated_limbs.len(), + EmulatedLimbs::Constant(constant_limbs) => constant_limbs.len(), + } + } + + pub fn is_constant(&self) -> bool { + matches!(self.limbs, EmulatedLimbs::Constant(_)) + } + + pub fn allocate_limbs(&self, cs: &mut CS) -> Result, SynthesisError> + where + CS: ConstraintSystem, + { + if let EmulatedLimbs::Constant(limb_values) = &self.limbs { + EmulatedLimbs::::allocate_limbs( + &mut cs.namespace(|| "allocate variables from constant limbs"), + limb_values, + ) + } else { + eprintln!("input must have constant limb values"); + Err(SynthesisError::Unsatisfiable) + } + } + + /// Allocates an emulated field element from constant limbs **without** + /// in-circuit checks for field membership. If you want to enforce membership + /// in the field, you can call `check_field_membership` on the output of this + /// method. + /// + /// This method is suitable for allocating field elements from public inputs + /// that are known to be in the field. + pub fn allocate_field_element_unchecked(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + if self.is_constant() { + // Below statement does not perform a in-circuit check as the input is a constant + self.check_field_membership( + &mut cs.namespace(|| "check field membership of constant input"), + )?; + + let allocated_limbs = self + .allocate_limbs(&mut cs.namespace(|| "allocate variables from constant limbs"))?; + + let allocated_field_element = Self::new_internal_element(allocated_limbs, 0); + Ok(allocated_field_element) + } else { + eprintln!("input must have constant limb values"); + Err(SynthesisError::Unsatisfiable) + } + } + + /// Enforces limb bit widths in a [EmulatedFieldElement] + /// + /// All the limbs are constrained to have width that is at most equal to the width + /// specified by [EmulatedFieldParams]. + /// If `modulus_width` is `true`, the most significant limb will be constrained to have + /// width less than or equal to the most significant limb of the modulus. + /// For constant elements, the number of limbs is required to be equal to P::num_limbs(). + /// For allocated elements, the number of limbs is required to be equal to P::num_limbs() + /// only if `modulus_width` is true. In the calculation of quotients, the limbs may not + /// be equal to P::num_limbs() + fn enforce_width(&self, cs: &mut CS, modulus_width: bool) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + if let EmulatedLimbs::Constant(limb_values) = &self.limbs { + if limb_values.len() != P::num_limbs() { + eprintln!("Constant limb count does not match required count"); + return Err(SynthesisError::Unsatisfiable); + } + + for (i, limb) in limb_values.iter().enumerate() { + let mut required_bit_width = P::bits_per_limb(); + if modulus_width && i == P::num_limbs() - 1 { + required_bit_width = + (P::modulus().bits() as usize - 1) % P::bits_per_limb() + 1; + } + range_check_constant(*limb, required_bit_width)?; + } + } + if let EmulatedLimbs::Allocated(allocated_limbs) = &self.limbs { + if modulus_width && allocated_limbs.len() != P::num_limbs() { + eprintln!("Allocated limb count does not match required count"); + return Err(SynthesisError::Unsatisfiable); + } + + for (i, limb) in allocated_limbs.iter().enumerate() { + let mut required_bit_width = P::bits_per_limb(); + if modulus_width && i == P::num_limbs() - 1 { + required_bit_width = + (P::modulus().bits() as usize - 1) % P::bits_per_limb() + 1; + } + + range_check_num( + &mut cs.namespace(|| format!("range check limb {i}")), + limb, + required_bit_width, + )?; + } + } + Ok(()) + } + + /// Enforces limb bit widths in a [EmulatedFieldElement] if it is not an + /// internal element or a constant + /// + /// The number of limbs is required to be equal to P::num_limbs(), and + /// the most significant limb will be constrained to have + /// width less than or equal to the most significant limb of the modulus. + pub(crate) fn enforce_width_conditional(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + if self.internal { + return Ok(false); + } + if self.is_constant() { + return Ok(false); + } + self.enforce_width(&mut cs.namespace(|| "enforce width"), true)?; + Ok(true) + } + + /// Constructs a [EmulatedFieldElement] from limbs of type [EmulatedLimbs]. + /// The method name is inherited from gnark. + /// + /// All the limbs are constrained to have width that is at most equal to the width + /// specified by [EmulatedFieldParams]. + /// If `strict` is `true`, the most significant limb will be constrained to have + /// width less than or equal to the most significant limb of the modulus. + pub(crate) fn pack_limbs( + cs: &mut CS, + limbs: EmulatedLimbs, + strict: bool, + ) -> Result + where + CS: ConstraintSystem, + { + let elem = Self::new_internal_element(limbs, 0); + elem.enforce_width(&mut cs.namespace(|| "pack limbs"), strict)?; + Ok(elem) + } + + pub fn compact_limbs( + &self, + group_size: usize, + new_bits_per_limb: usize, + ) -> Result, SynthesisError> { + if P::bits_per_limb() == new_bits_per_limb { + return Ok(self.limbs.clone()); + } + if self.is_constant() { + eprintln!("compact_limbs not implemented for constants"); + return Err(SynthesisError::Unsatisfiable); + } + + if let EmulatedLimbs::::Allocated(allocated_limbs) = &self.limbs { + let mut coeffs = vec![]; + for i in 0..group_size { + coeffs.push(bigint_to_scalar( + &(BigInt::one() << (P::bits_per_limb() * i)), + )); + } + + let new_num_limbs = (P::num_limbs() + group_size - 1) / group_size; + let mut res = vec![Num::::zero(); new_num_limbs]; + + for i in 0..new_num_limbs { + for j in 0..group_size { + if i * group_size + j < allocated_limbs.len() { + res[i] = allocated_limbs[i * group_size + j] + .clone() + .scale(coeffs[j]) + .add(&res[i]); + } + } + } + return Ok(EmulatedLimbs::Allocated(res)); + } + // Should not reach this line + Err(SynthesisError::Unsatisfiable) + } + + pub fn check_field_membership(&self, cs: &mut CS) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + if self.is_constant() { + if BigInt::from(self) < P::modulus() { + return Ok(()); + } else { + return Err(SynthesisError::Unsatisfiable); + } + } + + if self.len() != P::num_limbs() { + eprintln!("Field membership check only implemented for limb count equal to default"); + return Err(SynthesisError::Unsatisfiable); + } + + match &self.limbs { + EmulatedLimbs::Allocated(allocated_limbs) => { + // Number of modulus bits in most significant limb + let num_mod_bits_in_msl = + (P::modulus().bits() as usize - 1) % P::bits_per_limb() + 1; + + for (i, limb) in allocated_limbs.iter().enumerate() { + let num_bits = if i == P::num_limbs() - 1 { + num_mod_bits_in_msl + } else { + P::bits_per_limb() + }; + + range_check_num( + &mut cs.namespace(|| format!("range check limb {i}")), + limb, + num_bits, + )?; + } + + if P::is_modulus_pseudo_mersenne() { + let pseudo_mersenne_params = P::pseudo_mersenne_params().unwrap(); + // Maximum value of most significant limb + let max_msl_value = (BigInt::one() << num_mod_bits_in_msl) - BigInt::one(); + // Maximum value of least significant limbs + let max_lsl_value = (BigInt::one() << P::bits_per_limb()) - BigInt::one(); + + let equality_bits: Vec = (1..P::num_limbs()) + .map(|i| { + let max_limb_value = if i == P::num_limbs() - 1 { + bigint_to_scalar(&max_msl_value) + } else { + bigint_to_scalar(&max_lsl_value) + }; + + let bit = alloc_num_equals_constant( + cs.namespace(|| format!("limb {i} equals max value")), + &allocated_limbs[i], + max_limb_value, + ); + bit.unwrap() + }) + .collect(); + + let mut kary_and = equality_bits[0].clone(); + #[allow(clippy::needless_range_loop)] + for i in 1..P::num_limbs() - 1 { + kary_and = AllocatedBit::and( + cs.namespace(|| format!("and of bits {} and {}", i - 1, i)), + &kary_and, + &equality_bits[i], + )? + } + + let c = bigint_to_scalar(&pseudo_mersenne_params.c); + + // Least significant limb increased by c if all the most significant limbs are maxxed out + // If kary_and is true, then lsl_num = allocated_limbs[0] + c. Otherwise, lsl_num = allocated_limbs[0]. + // The latter is already within P::bits_per_limb(). If the former only has P::bits_per_limb(), + // then allocated_limbs[0] is at most 2^(P::bits_per_limb())-1-c + let lsl_num = allocated_limbs[0].clone().add_bool_with_coeff( + CS::one(), + &Boolean::Is(kary_and), + c, + ); + range_check_num( + &mut cs.namespace(|| { + "range check limb least significant limb + possibly c".to_string() + }), + &lsl_num, + P::bits_per_limb(), + )?; + } else { + panic!( + "Check field membership implemented only for pseudo-Mersenne prime moduli" + ); + } + } + EmulatedLimbs::Constant(_) => { + panic!("constant case is already handled; this code should be unreachable") + } + } + + Ok(()) + } + + // If condition is true, return a1. Otherwise return a0. + // Based on Nova/src/gadgets/utils.rs:conditionally_select + pub fn conditionally_select( + cs: &mut CS, + a0: &Self, + a1: &Self, + condition: &Boolean, + ) -> Result + where + CS: ConstraintSystem, + { + if a1.len() != a0.len() { + eprintln!( + "Current implementation of conditionally_select only allows same number of limbs" + ); + return Err(SynthesisError::Unsatisfiable); + } + let res_overflow = a1.overflow.max(a0.overflow); + + let res_values = if condition.get_value().unwrap() { + match &a1.limbs { + EmulatedLimbs::Allocated(a1_var) => a1_var + .iter() + .map(|x| x.get_value().unwrap_or_default()) + .collect::>(), + EmulatedLimbs::Constant(a1_const) => a1_const.clone(), + } + } else { + match &a0.limbs { + EmulatedLimbs::Allocated(a0_var) => a0_var + .iter() + .map(|x| x.get_value().unwrap_or_default()) + .collect::>(), + EmulatedLimbs::Constant(a0_const) => a0_const.clone(), + } + }; + + let res_alloc_limbs = EmulatedLimbs::allocate_limbs( + &mut cs.namespace(|| "allocate result limbs"), + &res_values, + )?; + + match &res_alloc_limbs { + EmulatedLimbs::Allocated(res_limbs) => { + for i in 0..res_values.len() { + let a1_lc = match &a1.limbs { + EmulatedLimbs::Allocated(a1_var) => a1_var[i].lc(F::ONE), + EmulatedLimbs::Constant(a1_const) => { + LinearCombination::::from_coeff(CS::one(), a1_const[i]) + } + }; + let a0_lc = match &a0.limbs { + EmulatedLimbs::Allocated(a0_var) => a0_var[i].lc(F::ONE), + EmulatedLimbs::Constant(a0_const) => { + LinearCombination::::from_coeff(CS::one(), a0_const[i]) + } + }; + + cs.enforce( + || format!("conditional select constraint on limb {i}"), + |lc| lc + &a1_lc - &a0_lc, + |_| condition.lc(CS::one(), F::ONE), + |lc| lc + &res_limbs[i].lc(F::ONE) - &a0_lc, + ); + } + } + EmulatedLimbs::Constant(_) => panic!("Unreachable match arm"), + } + let res = Self::new_internal_element(res_alloc_limbs, res_overflow); + Ok(res) + } + + // Based on bellperson-nonnative/src/util/gadget.rs:mux_tree + // `select_bits` are given in big-endian order and `inputs` have + // the zero index input first, i.e. [a0, a1, a2, ...] + pub fn mux_tree<'a, CS>( + cs: &mut CS, + mut select_bits: impl Iterator + Clone, + inputs: &[Self], + ) -> Result + where + CS: ConstraintSystem, + { + if let Some(bit) = select_bits.next() { + if inputs.len() & 1 != 0 { + return Err(SynthesisError::Unsatisfiable); + } + let left_half = &inputs[..(inputs.len() / 2)]; + let right_half = &inputs[(inputs.len() / 2)..]; + let left = + Self::mux_tree(&mut cs.namespace(|| "left"), select_bits.clone(), left_half)?; + let right = Self::mux_tree(&mut cs.namespace(|| "right"), select_bits, right_half)?; + Self::conditionally_select(&mut cs.namespace(|| "join"), &left, &right, bit) + } else { + if inputs.len() != 1 { + return Err(SynthesisError::Unsatisfiable); + } + Ok(inputs[0].clone()) + } + } +} + +#[cfg(test)] +mod tests { + use bellpepper_core::test_cs::TestConstraintSystem; + use num_bigint::RandBigInt; + + use super::*; + use pasta_curves::Fp; + + struct Ed25519Fp; + + impl EmulatedFieldParams for Ed25519Fp { + fn num_limbs() -> usize { + 5 + } + + fn bits_per_limb() -> usize { + 51 + } + + fn modulus() -> BigInt { + BigInt::parse_bytes( + b"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", + 16, + ) + .unwrap() + } + + fn is_modulus_pseudo_mersenne() -> bool { + true + } + + fn pseudo_mersenne_params() -> Option { + Some(PseudoMersennePrime { + e: 255, + c: BigInt::from(19), + }) + } + } + + #[test] + fn test_constant_equality() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = rand::thread_rng(); + let a_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + + let a_const = EmulatedFieldElement::::from(&a_int); + + let a = a_const.allocate_field_element_unchecked(&mut cs.namespace(|| "a")); + assert!(a.is_ok()); + let a = a.unwrap(); + + let res = a.assert_equality_to_constant(&mut cs.namespace(|| "check equality"), &a_const); + assert!(res.is_ok()); + + if !cs.is_satisfied() { + println!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 5); + } + + #[test] + fn test_add() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = rand::thread_rng(); + let a_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let b_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let sum_int = (&a_int + &b_int).rem(&Ed25519Fp::modulus()); + + let a_const = EmulatedFieldElement::::from(&a_int); + let b_const = EmulatedFieldElement::::from(&b_int); + let sum_const = EmulatedFieldElement::::from(&sum_int); + + let a = a_const.allocate_field_element_unchecked(&mut cs.namespace(|| "a")); + let b = b_const.allocate_field_element_unchecked(&mut cs.namespace(|| "b")); + let sum = sum_const.allocate_field_element_unchecked(&mut cs.namespace(|| "sum")); + assert!(a.is_ok()); + assert!(b.is_ok()); + assert!(sum.is_ok()); + let a = a.unwrap(); + let b = b.unwrap(); + let sum = sum.unwrap(); + + let sum_calc = a.add(&mut cs.namespace(|| "a + b"), &b); + assert!(sum_calc.is_ok()); + let sum_calc = sum_calc.unwrap(); + + let res = EmulatedFieldElement::::assert_is_equal( + &mut cs.namespace(|| "check equality"), + &sum_calc, + &sum, + ); + assert!(res.is_ok()); + + if !cs.is_satisfied() { + println!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 157); + } + + #[test] + fn test_sub() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = rand::thread_rng(); + let tmp1 = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let tmp2 = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let a_int = (&tmp1).max(&tmp2); + let b_int = (&tmp1).min(&tmp2); + let diff_int = (a_int - b_int).rem(&Ed25519Fp::modulus()); + + let a_const = EmulatedFieldElement::::from(a_int); + let b_const = EmulatedFieldElement::::from(b_int); + let diff_const = EmulatedFieldElement::::from(&diff_int); + + let a = a_const.allocate_field_element_unchecked(&mut cs.namespace(|| "a")); + let b = b_const.allocate_field_element_unchecked(&mut cs.namespace(|| "b")); + let diff = diff_const.allocate_field_element_unchecked(&mut cs.namespace(|| "diff")); + assert!(a.is_ok()); + assert!(b.is_ok()); + assert!(diff.is_ok()); + let a = a.unwrap(); + let b = b.unwrap(); + let diff = diff.unwrap(); + + let diff_calc = a.sub(&mut cs.namespace(|| "a - b"), &b); + assert!(diff_calc.is_ok()); + let diff_calc = diff_calc.unwrap(); + + let res = EmulatedFieldElement::::assert_is_equal( + &mut cs.namespace(|| "check equality"), + &diff_calc, + &diff, + ); + assert!(res.is_ok()); + + if !cs.is_satisfied() { + println!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 157); + } + + #[test] + fn test_mul() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = rand::thread_rng(); + let a_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let b_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let prod_int = (&a_int * &b_int).rem(&Ed25519Fp::modulus()); + + let a_const = EmulatedFieldElement::::from(&a_int); + let b_const = EmulatedFieldElement::::from(&b_int); + let prod_const = EmulatedFieldElement::::from(&prod_int); + + let a = a_const.allocate_field_element_unchecked(&mut cs.namespace(|| "a")); + let b = b_const.allocate_field_element_unchecked(&mut cs.namespace(|| "b")); + let prod = prod_const.allocate_field_element_unchecked(&mut cs.namespace(|| "prod")); + assert!(a.is_ok()); + assert!(b.is_ok()); + assert!(prod.is_ok()); + let a = a.unwrap(); + let b = b.unwrap(); + let prod = prod.unwrap(); + + let prod_calc = a.mul(&mut cs.namespace(|| "a * b"), &b); + assert!(prod_calc.is_ok()); + let prod_calc = prod_calc.unwrap(); + + let res = EmulatedFieldElement::::assert_is_equal( + &mut cs.namespace(|| "check equality"), + &prod_calc, + &prod, + ); + assert!(res.is_ok()); + + if !cs.is_satisfied() { + println!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 235); + } + + #[test] + fn test_divide() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = rand::thread_rng(); + let a_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let b_int = rng.gen_bigint_range(&BigInt::one(), &Ed25519Fp::modulus()); + let p = Ed25519Fp::modulus(); + let p_minus_2 = &p - BigInt::from(2); + // b^(p-1) = 1 mod p for non-zero b. So b^(-1) = b^(p-2) + let b_inv_int = b_int.modpow(&p_minus_2, &p); + let ratio_int = (&a_int * b_inv_int).rem(&p); + + let a_const = EmulatedFieldElement::::from(&a_int); + let b_const = EmulatedFieldElement::::from(&b_int); + let ratio_const = EmulatedFieldElement::::from(&ratio_int); + + let a = a_const.allocate_field_element_unchecked(&mut cs.namespace(|| "a")); + let b = b_const.allocate_field_element_unchecked(&mut cs.namespace(|| "b")); + let ratio = ratio_const.allocate_field_element_unchecked(&mut cs.namespace(|| "ratio")); + assert!(a.is_ok()); + assert!(b.is_ok()); + assert!(ratio.is_ok()); + let a = a.unwrap(); + let b = b.unwrap(); + let ratio = ratio.unwrap(); + + let ratio_calc = a.divide(&mut cs.namespace(|| "a divided by b"), &b); + assert!(ratio_calc.is_ok()); + let ratio_calc = ratio_calc.unwrap(); + + let res = EmulatedFieldElement::::assert_is_equal( + &mut cs.namespace(|| "check equality"), + &ratio_calc, + &ratio, + ); + assert!(res.is_ok()); + + let b_mul_ratio = b.mul(&mut cs.namespace(|| "b * (a div b)"), &ratio); + assert!(b_mul_ratio.is_ok()); + let b_mul_ratio = b_mul_ratio.unwrap(); + + let res = EmulatedFieldElement::::assert_is_equal( + &mut cs.namespace(|| "check equality of a and b * (a div b)"), + &b_mul_ratio, + &a, + ); + assert!(res.is_ok()); + + if !cs.is_satisfied() { + println!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 882); + } + + #[test] + fn test_inverse() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = rand::thread_rng(); + let b_int = rng.gen_bigint_range(&BigInt::one(), &Ed25519Fp::modulus()); + let p = Ed25519Fp::modulus(); + let p_minus_2 = &p - BigInt::from(2); + // b^(p-1) = 1 mod p for non-zero b. So b^(-1) = b^(p-2) + let b_inv_int = b_int.modpow(&p_minus_2, &p); + + let b_const = EmulatedFieldElement::::from(&b_int); + let b_inv_const = EmulatedFieldElement::::from(&b_inv_int); + + let b = b_const.allocate_field_element_unchecked(&mut cs.namespace(|| "b")); + let b_inv = b_inv_const.allocate_field_element_unchecked(&mut cs.namespace(|| "b_inv")); + assert!(b.is_ok()); + assert!(b_inv.is_ok()); + let b = b.unwrap(); + let b_inv = b_inv.unwrap(); + + let b_inv_calc = b.inverse(&mut cs.namespace(|| "b inverse")); + assert!(b_inv_calc.is_ok()); + let b_inv_calc = b_inv_calc.unwrap(); + + let res = EmulatedFieldElement::::assert_is_equal( + &mut cs.namespace(|| "check equality"), + &b_inv_calc, + &b_inv, + ); + assert!(res.is_ok()); + + let b_mul_b_inv = b.mul(&mut cs.namespace(|| "b * b_inv"), &b_inv); + assert!(b_mul_b_inv.is_ok()); + let b_mul_b_inv = b_mul_b_inv.unwrap(); + + let res = EmulatedFieldElement::::assert_is_equal( + &mut cs.namespace(|| "check equality one and b * b_inv"), + &b_mul_b_inv, + &EmulatedFieldElement::::one(), + ); + assert!(res.is_ok()); + + if !cs.is_satisfied() { + println!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 882); + } + + #[test] + fn test_field_membership() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = rand::thread_rng(); + + let a_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let a_const = EmulatedFieldElement::::from(&a_int); + let a = a_const.allocate_field_element_unchecked(&mut cs.namespace(|| "a")); + // Num constraints before field membership check = 0 + assert_eq!(cs.num_constraints(), 0); + assert!(a.is_ok()); + let a = a.unwrap(); + + let res = + a.check_field_membership(&mut cs.namespace(|| "check field membership of random a")); + assert!(res.is_ok()); + + assert!(cs.is_satisfied()); + // Num constraints after field membership check = 321 + assert_eq!(cs.num_constraints(), 321); + + let b_int = &Ed25519Fp::modulus() - BigInt::one(); + let b_const = EmulatedFieldElement::::from(&b_int); + let b = b_const.allocate_field_element_unchecked(&mut cs.namespace(|| "q-1")); + assert!(b.is_ok()); + let b = b.unwrap(); + + let res = b.check_field_membership(&mut cs.namespace(|| "check field membership of q-1")); + assert!(res.is_ok()); + + assert!(cs.is_satisfied()); + + let one = EmulatedFieldElement::::one(); + let q = b.add(&mut cs.namespace(|| "add 1 to q-1"), &one); + assert!(q.is_ok()); + let q = q.unwrap(); + + let res = q.check_field_membership(&mut cs.namespace(|| "check field non-membership of q")); + assert!(res.is_ok()); + + assert!(!cs.is_satisfied()); + } + + #[test] + fn test_conditionally_select() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = rand::thread_rng(); + let a0_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + let a1_int = rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus()); + + let a0_const = EmulatedFieldElement::::from(&a0_int); + let a1_const = EmulatedFieldElement::::from(&a1_int); + + let one = TestConstraintSystem::::one(); + let conditions = vec![false, true]; + for c in conditions.clone() { + let condition = Boolean::constant(c); + + let res = EmulatedFieldElement::::conditionally_select( + &mut cs.namespace(|| { + format!("conditionally select constant a0 or a1 for condition = {c}") + }), + &a0_const, + &a1_const, + &condition, + ); + assert!(res.is_ok()); + if !c { + assert_eq!(cs.num_constraints(), 5); + } + let res = res.unwrap(); + + let res_expected_limbs = match (&a0_const.limbs, &a1_const.limbs) { + ( + EmulatedLimbs::Constant(a0_const_limbs), + EmulatedLimbs::Constant(a1_const_limbs), + ) => { + if c { + a1_const_limbs + } else { + a0_const_limbs + } + } + _ => panic!("Both sets of limbs must be constant"), + }; + + if let EmulatedLimbs::Allocated(res_limbs) = res.limbs { + for i in 0..res_limbs.len() { + cs.enforce( + || format!("c constant limb {i} equality for condition = {c}"), + |lc| lc + &res_limbs[i].lc(Fp::one()), + |lc| lc + one, + |lc| lc + (res_expected_limbs[i], one), + ); + } + } else { + // Execution should not reach this line + eprintln!("res should have allocated limbs"); + unreachable!(); + } + + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + } + let num_constraints_here = cs.num_constraints(); + + let a0 = a0_const.allocate_field_element_unchecked(&mut cs.namespace(|| "a")); + let a1 = a1_const.allocate_field_element_unchecked(&mut cs.namespace(|| "b")); + assert!(a0.is_ok()); + assert!(a1.is_ok()); + let a0 = a0.unwrap(); + let a1 = a1.unwrap(); + + for c in conditions { + let condition = Boolean::constant(c); + + let res = EmulatedFieldElement::::conditionally_select( + &mut cs.namespace(|| { + format!("conditionally select variable a or b for condition = {c}") + }), + &a0, + &a1, + &condition, + ); + assert!(res.is_ok()); + if !c { + assert_eq!(cs.num_constraints() - num_constraints_here, 5); + } + let res = res.unwrap(); + + let res_expected_limbs = match (&a0.limbs, &a1.limbs) { + (EmulatedLimbs::Allocated(a0_limbs), EmulatedLimbs::Allocated(a1_limbs)) => { + if c { + a1_limbs + } else { + a0_limbs + } + } + _ => panic!("Both sets of limbs must be allocated"), + }; + + if let EmulatedLimbs::Allocated(res_limbs) = res.limbs { + for i in 0..res_limbs.len() { + cs.enforce( + || format!("c variable limb {i} equality for condition = {c}"), + |lc| lc + &res_limbs[i].lc(Fp::one()), + |lc| lc + one, + |lc| lc + &res_expected_limbs[i].lc(Fp::one()), + ); + } + } else { + // Execution should not reach this line + eprintln!("res should have allocated limbs"); + unreachable!(); + } + + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + } + } + + #[test] + fn test_mux_tree() { + test_mux_tree_helper(1, 5); + test_mux_tree_helper(2, 15); + test_mux_tree_helper(3, 35); + test_mux_tree_helper(4, 75); + } + + fn test_mux_tree_helper(num_selector_bits: usize, expected_num_constraints: usize) { + let mut cs = TestConstraintSystem::::new(); + let num_inputs = 1usize << num_selector_bits; + let mut rng = rand::thread_rng(); + let mut a_ints = vec![]; + (0..num_inputs).for_each(|_| { + a_ints.push(rng.gen_bigint_range(&BigInt::zero(), &Ed25519Fp::modulus())); + }); + + let a_consts = a_ints + .iter() + .map(EmulatedFieldElement::::from) + .collect::>(); + let one = TestConstraintSystem::::one(); + + let mut conditions: Vec> = vec![]; + for i in 0..num_inputs { + let mut bool_vec = vec![]; + for j in 0..num_selector_bits { + let bit = (i >> j) & 1 == 1; + bool_vec.push(bit); + } + conditions.push(bool_vec); // little-endian + } + + for i in 0..num_inputs { + let condition_bools = &conditions[i]; + let condition_booleans = condition_bools + .iter() + .rev() // mux_tree takes slice with MSB first + .map(|b| Boolean::constant(*b)) + .collect::>(); + + let res = EmulatedFieldElement::::mux_tree( + &mut cs.namespace(|| { + format!( + "select one of constants a0 to a{} for conditions = {:?}", + num_inputs - 1, + condition_bools + ) + }), + condition_booleans.iter(), + &a_consts, + ); + assert!(res.is_ok()); + if condition_bools.iter().all(|&x| !x) { + // Number of constraints for a window size and constant inputs + assert_eq!(cs.num_constraints(), expected_num_constraints); + } + let res = res.unwrap(); + + let a_const_limbs_vec = a_consts + .clone() + .into_iter() + .map(|a_const| match &a_const.limbs { + EmulatedLimbs::Constant(a_const_limbs) => a_const_limbs.clone(), + EmulatedLimbs::Allocated(_) => panic!("Unreachable match arm"), + }) + .collect::>(); + + let res_expected_limbs = &a_const_limbs_vec[i]; + + if let EmulatedLimbs::Allocated(res_limbs) = res.limbs { + for i in 0..res_limbs.len() { + cs.enforce( + || { + format!( + "c constant limb {i} equality for condition = {:?}", + condition_bools + ) + }, + |lc| lc + &res_limbs[i].lc(Fp::one()), + |lc| lc + one, + |lc| lc + (res_expected_limbs[i], one), + ); + } + } else { + // Execution should not reach this line + eprintln!("res should have allocated limbs"); + unreachable!(); + } + + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + } + + let num_constraints_here = cs.num_constraints(); + + let a_vars = a_consts + .iter() + .enumerate() + .map(|(i, a_const)| { + let a = a_const + .allocate_field_element_unchecked(&mut cs.namespace(|| format!("a[{i}]"))); + assert!(a.is_ok()); + a.unwrap() + }) + .collect::>(); + + for i in 0..num_inputs { + let condition_bools = &conditions[i]; + let condition_booleans = condition_bools + .iter() + .rev() // mux_tree takes slice with MSB first + .map(|b| Boolean::constant(*b)) + .collect::>(); + + let res = EmulatedFieldElement::::mux_tree( + &mut cs.namespace(|| { + format!( + "select one of variables a0 to a{} for conditions = {:?}", + num_inputs - 1, + condition_bools + ) + }), + condition_booleans.iter(), + &a_vars, + ); + assert!(res.is_ok()); + if condition_bools.iter().all(|&x| !x) { + // Number of constraints for a window size and variable inputs + assert_eq!( + cs.num_constraints() - num_constraints_here, + expected_num_constraints + ); + } + let res = res.unwrap(); + + let a_var_limbs_vec = a_vars + .clone() + .into_iter() + .map(|a_var| match &a_var.limbs { + EmulatedLimbs::Allocated(a_var_limbs) => a_var_limbs.clone(), + EmulatedLimbs::Constant(_) => panic!("Unreachable match arm"), + }) + .collect::>(); + + let res_expected_limbs = &a_var_limbs_vec[i]; + + if let EmulatedLimbs::Allocated(res_limbs) = res.limbs { + for i in 0..res_limbs.len() { + cs.enforce( + || { + format!( + "c variable limb {i} equality for condition = {:?}", + condition_bools + ) + }, + |lc| lc + &res_limbs[i].lc(Fp::one()), + |lc| lc + one, + |lc| lc + &res_expected_limbs[i].lc(Fp::one()), + ); + } + } else { + // Execution should not reach this line + eprintln!("res should have allocated limbs"); + unreachable!(); + } + + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()); + } + assert!(cs.is_satisfied()); + } + } +} diff --git a/crates/bellpepper-emulated/src/field_hints.rs b/crates/bellpepper-emulated/src/field_hints.rs new file mode 100644 index 0000000..a94c170 --- /dev/null +++ b/crates/bellpepper-emulated/src/field_hints.rs @@ -0,0 +1,136 @@ +use std::ops::{Div, Rem}; + +use bellpepper_core::{ConstraintSystem, SynthesisError}; +use ff::{PrimeField, PrimeFieldBits}; +use num_bigint::BigInt; +use num_traits::Zero; + +use crate::field_element::EmulatedLimbs; +use crate::util::{bigint_to_scalar, decompose}; +use crate::{field_element::EmulatedFieldElement, field_element::EmulatedFieldParams}; + +impl EmulatedFieldElement +where + F: PrimeField + PrimeFieldBits, + P: EmulatedFieldParams, +{ + /// Computes the remainder modulo the field modulus + pub(crate) fn compute_rem(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + let a_int: BigInt = self.into(); + let p = P::modulus(); + let r_int = a_int.rem(p); + let r_value = Self::from(&r_int); + + let res_limbs = + r_value.allocate_limbs(&mut cs.namespace(|| "allocate from remainder value"))?; + + let res = Self::pack_limbs( + &mut cs.namespace(|| "enforce bitwidths on remainder"), + res_limbs, + true, + )?; + Ok(res) + } + + /// Computes the quotient + pub(crate) fn compute_quotient(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + // TODO: Check the need for the "+ 1" + let num_res_limbs = (self.len()*P::bits_per_limb() + self.overflow + 1 + - (P::modulus().bits() as usize) // Deduct the modulus bit size + + P::bits_per_limb() - 1) / // This term is to round up to next integer + P::bits_per_limb(); + + let a_int: BigInt = self.into(); + let p = P::modulus(); + let k_int = a_int.div(p); + let k_int_limbs = decompose(&k_int, P::bits_per_limb(), num_res_limbs)?; + + let res_limb_values: Vec = k_int_limbs + .into_iter() + .map(|i| bigint_to_scalar(&i)) + .collect::>(); + + let res_limbs = EmulatedLimbs::::allocate_limbs( + &mut cs.namespace(|| "allocate from quotient value"), + &res_limb_values, + )?; + + let res = Self::pack_limbs( + &mut cs.namespace(|| "enforce bitwidths on quotient"), + res_limbs, + false, + )?; + Ok(res) + } + + /// Computes the multiplicative inverse + pub(crate) fn compute_inverse(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + let mut a_int: BigInt = self.into(); + let p = P::modulus(); + a_int = a_int.rem(&p); + if a_int.is_zero() { + eprintln!("Inverse of zero element cannot be calculated"); + return Err(SynthesisError::DivisionByZero); + } + let p_minus_2 = &p - BigInt::from(2); + // a^(p-1) = 1 mod p for non-zero a. So a^(-1) = a^(p-2) + let a_inv_int = a_int.modpow(&p_minus_2, &p); + let a_inv_value = Self::from(&a_inv_int); + + let a_inv_limbs = + a_inv_value.allocate_limbs(&mut cs.namespace(|| "allocate from inverse value"))?; + + let a_inv = Self::pack_limbs( + &mut cs.namespace(|| "enforce bitwidths on inverse"), + a_inv_limbs, + true, + )?; + + Ok(a_inv) + } + + /// Computes the ratio modulo the field modulus + pub(crate) fn compute_ratio( + &self, + cs: &mut CS, + other: &Self, + ) -> Result + where + CS: ConstraintSystem, + { + let numer_int: BigInt = self.into(); + let mut denom_int: BigInt = other.into(); + let p = P::modulus(); + denom_int = denom_int.rem(&p); + if denom_int.is_zero() { + eprintln!("Inverse of zero element cannot be calculated"); + return Err(SynthesisError::DivisionByZero); + } + let p_minus_2 = &p - BigInt::from(2); + // a^(p-1) = 1 mod p for non-zero a. So a^(-1) = a^(p-2) + let denom_inv_int = denom_int.modpow(&p_minus_2, &p); + let ratio_int = (numer_int * denom_inv_int).rem(&p); + + let ratio_value = Self::from(&ratio_int); + + let ratio_limbs = + ratio_value.allocate_limbs(&mut cs.namespace(|| "allocate from ratio value"))?; + + let ratio = Self::pack_limbs( + &mut cs.namespace(|| "enforce bitwidths on ratio"), + ratio_limbs, + true, + )?; + + Ok(ratio) + } +} diff --git a/crates/bellpepper-emulated/src/field_ops.rs b/crates/bellpepper-emulated/src/field_ops.rs new file mode 100644 index 0000000..37add51 --- /dev/null +++ b/crates/bellpepper-emulated/src/field_ops.rs @@ -0,0 +1,867 @@ +use std::{ + fmt::Debug, + marker::PhantomData, + ops::{Rem, Shl}, +}; + +use bellpepper_core::boolean::{AllocatedBit, Boolean}; +use bellpepper_core::num::{AllocatedNum, Num}; +use bellpepper_core::{ConstraintSystem, LinearCombination, SynthesisError}; +use ff::{PrimeField, PrimeFieldBits}; +use num_bigint::BigInt; +use num_traits::One; + +use crate::field_element::{EmulatedFieldElement, EmulatedFieldParams, EmulatedLimbs}; +use crate::util::{bigint_to_scalar, decompose, recompose}; + +#[derive(Debug, Clone)] +pub enum Optype { + Add, + Sub, + Mul, +} + +#[derive(Clone)] +pub struct OverflowError { + op: Optype, + next_overflow: usize, + reduce_right: bool, +} + +impl Debug for OverflowError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OverflowError") + .field("op", &self.op) + .field("next_overflow", &self.next_overflow) + .field("reduce_right", &self.reduce_right) + .finish() + } +} + +impl EmulatedFieldElement +where + F: PrimeField + PrimeFieldBits, + P: EmulatedFieldParams, +{ + fn compact( + a: &Self, + b: &Self, + ) -> Result<(EmulatedLimbs, EmulatedLimbs, usize), SynthesisError> { + let max_overflow = a.overflow.max(b.overflow); + // Substract one bit to account for overflow due to grouping in compact_limbs + let max_num_bits = F::CAPACITY as usize - 1 - max_overflow; + let group_size = max_num_bits / P::bits_per_limb(); + + if group_size == 0 { + // No space for compacting + return Ok((a.limbs.clone(), b.limbs.clone(), P::bits_per_limb())); + } + + let new_bits_per_limb = P::bits_per_limb() * group_size; + let a_compact = a.compact_limbs(group_size, new_bits_per_limb)?; + let b_compact = b.compact_limbs(group_size, new_bits_per_limb)?; + + Ok((a_compact, b_compact, new_bits_per_limb)) + } + + /// Asserts that two allocated limbs vectors represent the same integer value. + /// This is a costly operation as it performs bit decomposition of the limbs. + fn assert_limbs_equality_slow( + cs: &mut CS, + a: &EmulatedLimbs, + b: &EmulatedLimbs, + num_bits_per_limb: usize, + num_carry_bits: usize, + ) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + if let (EmulatedLimbs::Allocated(a_l), EmulatedLimbs::Allocated(b_l)) = (a, b) { + let num_limbs = a_l.len().max(b_l.len()); + let max_value = + bigint_to_scalar::(&BigInt::one().shl(num_bits_per_limb + num_carry_bits)); + let max_value_shift = bigint_to_scalar::(&BigInt::one().shl(num_carry_bits)); + + let mut carry = Num::::zero(); + for i in 0..num_limbs { + let mut diff_num = carry.add(&Num::::zero().add_bool_with_coeff( + CS::one(), + &Boolean::Constant(true), + max_value, + )); + if i < a_l.len() { + diff_num = diff_num.add(&a_l[i]); + } + if i < b_l.len() { + let mut neg_bl = b_l[i].clone(); + neg_bl = neg_bl.scale(-F::ONE); + diff_num = diff_num.add(&neg_bl); + } + if i > 0 { + diff_num = diff_num.add_bool_with_coeff( + CS::one(), + &Boolean::Constant(true), + -max_value_shift, + ); + } + + carry = Self::right_shift( + &mut cs.namespace(|| format!("right shift to get carry {i}")), + &diff_num, + num_bits_per_limb, + num_bits_per_limb + num_carry_bits + 1, + )?; + } + } else { + eprintln!("Both inputs must be allocated limbs, not constants"); + return Err(SynthesisError::Unsatisfiable); + } + Ok(()) + } + + fn right_shift( + cs: &mut CS, + v: &Num, + start_digit: usize, + end_digit: usize, + ) -> Result, SynthesisError> + where + CS: ConstraintSystem, + { + let v_value = v.get_value().unwrap(); + let mut v_bits = v_value + .to_le_bits() + .into_iter() + .skip(start_digit) + .collect::>(); + v_bits.truncate(end_digit - start_digit); + + let mut v_booleans: Vec = vec![]; + for (i, b) in v_bits.into_iter().enumerate() { + let alloc_bit = + AllocatedBit::alloc(cs.namespace(|| format!("allocate bit {i}")), Some(b))?; + v_booleans.push(Boolean::from(alloc_bit)); + } + + let mut sum_higher_order_bits = Num::::zero(); + let mut sum_shifted_bits = Num::::zero(); + let mut coeff = bigint_to_scalar::(&(BigInt::one() << start_digit)); + let mut coeff_shifted = F::ONE; + + for b in v_booleans { + sum_higher_order_bits = sum_higher_order_bits.add_bool_with_coeff(CS::one(), &b, coeff); + sum_shifted_bits = sum_shifted_bits.add_bool_with_coeff(CS::one(), &b, coeff_shifted); + coeff_shifted = coeff_shifted.double(); + coeff = coeff.double(); + } + + cs.enforce( + || "enforce equality between input value and weighted sum of higher order bits", + |lc| lc, + |lc| lc, + |lc| lc + &v.lc(F::ONE) - &sum_higher_order_bits.lc(F::ONE), + ); + + Ok(sum_shifted_bits) + } + + /// Asserts that the limbs represent the same integer value. + /// For constant inputs, it ensures that the values are equal modulo the field order. + /// For allocated inputs, it does not ensure that the values are equal modulo the field order. + fn assert_limbs_equality(cs: &mut CS, a: &Self, b: &Self) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + a.enforce_width_conditional(&mut cs.namespace(|| "ensure bitwidths in a"))?; + b.enforce_width_conditional(&mut cs.namespace(|| "ensure bitwidths in b"))?; + + if a.is_constant() && b.is_constant() { + let a_i = BigInt::from(a); + let b_i = BigInt::from(b); + let a_r = a_i.rem(P::modulus()); + let b_r = b_i.rem(P::modulus()); + if a_r != b_r { + eprintln!("Constant values are not equal"); + return Err(SynthesisError::Unsatisfiable); + } + } + let (a_c, b_c, bits_per_limb) = Self::compact(a, b)?; + + if a.overflow > b.overflow { + Self::assert_limbs_equality_slow( + &mut cs.namespace(|| "check limbs equality"), + &a_c, + &b_c, + bits_per_limb, + a.overflow, + )?; + } else { + Self::assert_limbs_equality_slow( + &mut cs.namespace(|| "check limbs equality"), + &b_c, + &a_c, + bits_per_limb, + b.overflow, + )?; + } + + Ok(()) + } + + /// Asserts that the limbs represent the same integer value modulo the modulus. + pub fn assert_is_equal(cs: &mut CS, a: &Self, b: &Self) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + if a.is_constant() && b.is_constant() { + let a_i = BigInt::from(a); + let b_i = BigInt::from(b); + let a_r = a_i.rem(P::modulus()); + let b_r = b_i.rem(P::modulus()); + if a_r != b_r { + eprintln!("Constant values are not equal"); + return Err(SynthesisError::Unsatisfiable); + } + } + + let diff = a.sub(&mut cs.namespace(|| "a-b"), b)?; + let k = diff.compute_quotient(&mut cs.namespace(|| "quotient when divided by modulus"))?; + + let kp = Self::reduce_and_apply_op( + &mut cs.namespace(|| "computing product of quotient and modulus"), + Optype::Mul, + &k, + &Self::modulus(), + )?; + + Self::assert_limbs_equality(cs, &diff, &kp)?; + + Ok(()) + } + + /// Asserts that the limbs of an allocated `EmulatedFieldElement` limbs equal the + /// limbs of a specific constant `EmulatedFieldElement`. + /// + /// This methods uses fewer constraints (equal to limb count) than the general + /// `assert_is_equal`. It is useful for checking equality with constants like + /// 0 or 1 (which constitute the coordinates of the identity in ed25519). + pub fn assert_equality_to_constant( + &self, + cs: &mut CS, + constant: &Self, + ) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + if self.is_constant() || !constant.is_constant() { + eprintln!( + "Method should be called on a non-constant field element with a constant argument" + ); + return Err(SynthesisError::Unsatisfiable); + } + + match (&self.limbs, &constant.limbs) { + (EmulatedLimbs::Allocated(var_limbs), EmulatedLimbs::Constant(const_limbs)) => { + if var_limbs.len() != const_limbs.len() { + eprintln!( + "Limb counts do not match: {} != {}", + var_limbs.len(), + const_limbs.len() + ); + return Err(SynthesisError::Unsatisfiable); + } + + for i in 0..var_limbs.len() { + cs.enforce( + || format!("checking equality of limb {i}"), + |lc| lc, + |lc| lc, + |lc| lc + &var_limbs[i].lc(F::ONE) - (const_limbs[i], CS::one()), + ); + } + } + _ => panic!("Unreachable code reached"), + } + + Ok(()) + } + + pub fn reduce(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + if self.overflow + 2 > Self::max_overflow() { + panic!( + "Not enough bits in native field to accomodate a subtraction operation which is performed during reduce: {} > {}", + self.overflow + 2, + Self::max_overflow(), + ); + } + + self.enforce_width_conditional(&mut cs.namespace(|| "ensure bitwidths in input"))?; + if self.overflow == 0 { + return Ok(self.clone()); + } + + if self.is_constant() { + eprintln!("Trying to reduce a constant with overflow flag set; should not happen"); + return Err(SynthesisError::Unsatisfiable); + } + + let r = self.compute_rem(&mut cs.namespace(|| "remainder modulo field modulus"))?; + Self::assert_is_equal(&mut cs.namespace(|| "check equality"), &r, self)?; + Ok(r) + } + + fn add_precondition(a: &Self, b: &Self) -> Result { + let reduce_right = a.overflow < b.overflow; + let next_overflow = a.overflow.max(b.overflow) + 1; + + if next_overflow > Self::max_overflow() { + Err(OverflowError { + op: Optype::Add, + next_overflow, + reduce_right, + }) + } else { + Ok(next_overflow) + } + } + + fn add_op(a: &Self, b: &Self, next_overflow: usize) -> Result + where + CS: ConstraintSystem, + { + if a.is_constant() && b.is_constant() { + let a_int = BigInt::from(a); + let b_int = BigInt::from(b); + let res_int = (a_int + b_int).rem(P::modulus()); + return Ok(Self::from(&res_int)); + } + + let num_res_limbs = a.len().max(b.len()); + let mut res: Vec> = vec![Num::::zero(); num_res_limbs]; + + match (a.limbs.clone(), b.limbs.clone()) { + (EmulatedLimbs::Constant(const_limbs), EmulatedLimbs::Allocated(var_limbs)) + | (EmulatedLimbs::Allocated(var_limbs), EmulatedLimbs::Constant(const_limbs)) => { + for i in 0..num_res_limbs { + if i < var_limbs.len() { + res[i] = var_limbs[i].clone(); + } + if i < const_limbs.len() { + res[i] = res[i].clone().add_bool_with_coeff( + CS::one(), + &Boolean::Constant(true), + const_limbs[i], + ); + } + } + } + (EmulatedLimbs::Allocated(a_var), EmulatedLimbs::Allocated(b_var)) => { + for i in 0..num_res_limbs { + if i < a_var.len() { + res[i] = a_var[i].clone(); + } + if i < b_var.len() { + res[i] = res[i].clone().add(&b_var[i]); + } + } + } + (EmulatedLimbs::Constant(_), EmulatedLimbs::Constant(_)) => { + panic!("Constant limb case has already been handled") + } + } + + Ok(Self::new_internal_element( + EmulatedLimbs::Allocated(res), + next_overflow, + )) + } + + pub fn add(&self, cs: &mut CS, other: &Self) -> Result + where + CS: ConstraintSystem, + { + Self::reduce_and_apply_op( + &mut cs.namespace(|| "compute a + b"), + Optype::Add, + self, + other, + ) + } + + fn sub_precondition(a: &Self, b: &Self) -> Result { + let reduce_right = a.overflow < b.overflow; + let next_overflow = a.overflow.max(b.overflow + 2); + + if next_overflow > Self::max_overflow() { + Err(OverflowError { + op: Optype::Sub, + next_overflow, + reduce_right, + }) + } else { + Ok(next_overflow) + } + } + + /// Returns a k*P::modulus() for some k as a [EmulatedFieldElement] + /// + /// Underflow may occur when computing a - b. Let d = [d[0], d[1], ...] be the padding. + /// If d is a multiple of P::modulus() that is greater than b, then + /// (a[0]+d[0]-b[0], a[1]+d[1]-b[1],...) will not underflow + fn sub_padding(overflow: usize, limb_count: usize) -> Result, SynthesisError> { + let tmp = BigInt::one() << (overflow + P::bits_per_limb()); + let upper_bound_limbs = vec![tmp; limb_count]; + + let p = P::modulus(); + let mut padding_int_delta = recompose(&upper_bound_limbs, P::bits_per_limb())?; + padding_int_delta = padding_int_delta.rem(&p); + padding_int_delta = p - padding_int_delta; + + let padding_delta = decompose(&padding_int_delta, P::bits_per_limb(), limb_count)?; + + let padding_limbs = upper_bound_limbs + .into_iter() + .zip(padding_delta) + .map(|(a, b)| bigint_to_scalar(&(a + b))) + .collect::>(); + + Ok(padding_limbs) + } + + fn sub_op(a: &Self, b: &Self, next_overflow: usize) -> Result + where + CS: ConstraintSystem, + { + if a.is_constant() && b.is_constant() { + let a_int = BigInt::from(a); + let b_int = BigInt::from(b); + let res_int = (a_int + b_int).rem(P::modulus()); + return Ok(Self::from(&res_int)); + } + + let num_res_limbs = a.len().max(b.len()); + let mut res: Vec> = vec![]; + let pad_limbs = Self::sub_padding(b.overflow, num_res_limbs)?; + for limb in pad_limbs.into_iter() { + res.push(Num::::zero().add_bool_with_coeff( + CS::one(), + &Boolean::Constant(true), + limb, + )); + } + + match (a.limbs.clone(), b.limbs.clone()) { + (EmulatedLimbs::Allocated(a_var), EmulatedLimbs::Constant(b_const)) => { + for i in 0..num_res_limbs { + if i < a_var.len() { + res[i] = res[i].clone().add(&a_var[i]); + } + if i < b_const.len() { + res[i] = res[i].clone().add_bool_with_coeff( + CS::one(), + &Boolean::Constant(true), + -b_const[i], + ); + } + } + } + (EmulatedLimbs::Constant(a_const), EmulatedLimbs::Allocated(b_var)) => { + for i in 0..num_res_limbs { + if i < a_const.len() { + res[i] = res[i].clone().add_bool_with_coeff( + CS::one(), + &Boolean::Constant(true), + a_const[i], + ); + } + if i < b_var.len() { + let mut neg_bl = b_var[i].clone(); + neg_bl = neg_bl.scale(-F::ONE); + res[i] = res[i].clone().add(&neg_bl); + } + } + } + (EmulatedLimbs::Allocated(a_var), EmulatedLimbs::Allocated(b_var)) => { + for i in 0..num_res_limbs { + if i < a_var.len() { + res[i] = res[i].clone().add(&a_var[i]); + } + if i < b_var.len() { + let mut neg_bl = b_var[i].clone(); + neg_bl = neg_bl.scale(-F::ONE); + res[i] = res[i].clone().add(&neg_bl); + } + } + } + (EmulatedLimbs::Constant(_), EmulatedLimbs::Constant(_)) => { + panic!("Constant limb case has already been handled") + } + } + + Ok(Self::new_internal_element( + EmulatedLimbs::Allocated(res), + next_overflow, + )) + } + + pub fn sub(&self, cs: &mut CS, other: &Self) -> Result + where + CS: ConstraintSystem, + { + Self::reduce_and_apply_op( + &mut cs.namespace(|| "compute a - b"), + Optype::Sub, + self, + other, + ) + } + + pub fn neg(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + let zero = Self::zero(); + zero.sub(&mut cs.namespace(|| "negate"), self) + } + + fn mul_precondition(a: &Self, b: &Self) -> Result { + if 2 * P::bits_per_limb() > F::CAPACITY as usize { + panic!( + "Not enough bits in native field to accomodate a product of limbs: {} < {}", + F::CAPACITY, + 2 * P::bits_per_limb(), + ); + } + let reduce_right = a.overflow < b.overflow; + let max_carry_bits = (a.len().min(b.len()) as f32).log2().ceil() as usize; + let next_overflow = P::bits_per_limb() + a.overflow + b.overflow + max_carry_bits; + + if next_overflow > Self::max_overflow() { + Err(OverflowError { + op: Optype::Mul, + next_overflow, + reduce_right, + }) + } else { + Ok(next_overflow) + } + } + + fn mul_op( + cs: &mut CS, + a: &Self, + b: &Self, + next_overflow: usize, + ) -> Result + where + CS: ConstraintSystem, + { + if a.is_constant() && b.is_constant() { + let a_int = BigInt::from(a); + let b_int = BigInt::from(b); + let res_int = (a_int * b_int).rem(P::modulus()); + return Ok(Self::from(&res_int)); + } + + let num_prod_limbs = a.len() + b.len() - 1; + let mut prod: Vec> = vec![Num::::zero(); num_prod_limbs]; + let mut prod_values: Vec = vec![F::ZERO; num_prod_limbs]; + + match (a.limbs.clone(), b.limbs.clone()) { + (EmulatedLimbs::Constant(const_limbs), EmulatedLimbs::Allocated(var_limbs)) + | (EmulatedLimbs::Allocated(var_limbs), EmulatedLimbs::Constant(const_limbs)) => { + for i in 0..var_limbs.len() { + for j in 0..const_limbs.len() { + prod[i + j] = prod[i + j] + .clone() + .add(&var_limbs[i].clone().scale(const_limbs[j])); + } + } + } + (EmulatedLimbs::Allocated(a_var), EmulatedLimbs::Allocated(b_var)) => { + let a_var_limb_values: Vec = a_var + .iter() + .map(|v| v.get_value().unwrap_or_default()) + .collect(); + let b_var_limb_values: Vec = b_var + .iter() + .map(|v| v.get_value().unwrap_or_default()) + .collect(); + for i in 0..a.len() { + for j in 0..b.len() { + prod_values[i + j] += a_var_limb_values[i] * b_var_limb_values[j]; + } + } + + let prod_allocated_nums: Vec> = (0..num_prod_limbs) + .map(|i| { + AllocatedNum::alloc(cs.namespace(|| format!("product limb {i}")), || { + Ok(prod_values[i]) + }) + }) + .collect::, _>>()?; + + prod = prod_allocated_nums.into_iter().map(Num::from).collect(); + + let mut c = F::ZERO; + for _ in 0..num_prod_limbs { + c += F::ONE; + cs.enforce( + || format!("pointwise product @ {c:?}"), + |lc| { + let mut coeff = F::ONE; + let a_lcs: Vec> = + a_var.iter().map(|x| x.lc(F::ONE)).collect(); + + a_lcs.iter().fold(lc, |acc, elem| { + let r = acc + (coeff, elem); + coeff *= c; + r + }) + }, + |lc| { + let mut coeff = F::ONE; + let b_lcs: Vec> = + b_var.iter().map(|x| x.lc(F::ONE)).collect(); + + b_lcs.iter().fold(lc, |acc, elem| { + let r = acc + (coeff, elem); + coeff *= c; + r + }) + }, + |lc| { + let mut coeff = F::ONE; + let prod_lcs: Vec> = + prod.iter().map(|x| x.lc(F::ONE)).collect(); + + prod_lcs.iter().fold(lc, |acc, elem| { + let r = acc + (coeff, elem); + coeff *= c; + r + }) + }, + ) + } + } + (EmulatedLimbs::Constant(_), EmulatedLimbs::Constant(_)) => { + panic!("Constant limb case has already been handled") + } + } + + Ok(Self::new_internal_element( + EmulatedLimbs::Allocated(prod), + next_overflow, + )) + } + + pub fn mul(&self, cs: &mut CS, other: &Self) -> Result + where + CS: ConstraintSystem, + { + let mut prod = Self::reduce_and_apply_op( + &mut cs.namespace(|| "compute a * b"), + Optype::Mul, + self, + other, + )?; + prod.fold_limbs(&mut cs.namespace(|| "fold limbs of product"))?; + Ok(prod) + } + + pub fn mul_const(&self, cs: &mut CS, constant: &BigInt) -> Result + where + CS: ConstraintSystem, + { + if constant.bits() as usize > Self::max_overflow() { + eprintln!( + "constant and limb product will overflow native limb capacity even after reduction" + ); + return Err(SynthesisError::Unsatisfiable); + } + let mut next_overflow: usize = constant.bits() as usize + self.overflow; + + let elem = if next_overflow > Self::max_overflow() { + next_overflow = constant.bits() as usize; + self.reduce( + &mut cs.namespace(|| "reduce element to accommodate mul with const".to_string()), + )? + } else { + self.clone() + }; + + let mut prod: Vec> = vec![]; + let constant_scalar = bigint_to_scalar(constant); + + match elem.limbs { + EmulatedLimbs::Allocated(allocated_limbs) => { + for limb in &allocated_limbs { + prod.push(limb.clone().scale(constant_scalar)); + } + } + EmulatedLimbs::Constant(_) => { + panic!("mul_const not implemented for element with constant limbs") + } + } + + Ok(Self::new_internal_element( + EmulatedLimbs::Allocated(prod), + next_overflow, + )) + } + + pub fn inverse(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + let a_inv = self.compute_inverse(&mut cs.namespace(|| "multiplicative inverse"))?; + let prod = self.mul(&mut cs.namespace(|| "product of a and a_inv"), &a_inv)?; + Self::assert_is_equal( + &mut cs.namespace(|| "product equals one"), + &prod, + &Self::one(), + )?; + + Ok(a_inv) + } + + pub fn divide(&self, cs: &mut CS, denom: &Self) -> Result + where + CS: ConstraintSystem, + { + let ratio = self.compute_ratio(&mut cs.namespace(|| "ratio"), denom)?; + let prod = ratio.mul( + &mut cs.namespace(|| "product of ratio and denominator"), + denom, + )?; + Self::assert_is_equal( + &mut cs.namespace(|| "product equals numerator"), + &prod, + self, + )?; + + Ok(ratio) + } + + pub fn fold_limbs(&mut self, cs: &mut CS) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + // No folding algorithm for non-pseudo Mersenne primes; this method becomes a no-op + if !P::is_modulus_pseudo_mersenne() { + return Ok(()); + } + + if self.is_constant() { + eprintln!("fold_limbs not implemented for constants"); + return Err(SynthesisError::Unsatisfiable); + } + + // No extra limbs to fold + if self.len() <= P::num_limbs() { + return Ok(()); + } + + let num_chunks = (self.len() + P::num_limbs() - 1) / P::num_limbs(); + let mut chunks: Vec> = vec![]; + + match &self.limbs { + EmulatedLimbs::Allocated(var) => { + for i in 0..num_chunks { + let mut part_lcs = vec![]; + for j in 0..P::num_limbs() { + if i * P::num_limbs() + j < self.len() { + part_lcs.push(var[i * P::num_limbs() + j].clone()); + } + } + + let chunk = Self { + limbs: EmulatedLimbs::Allocated(part_lcs), + overflow: self.overflow, + internal: self.internal, + marker: PhantomData, + }; + chunks.push(chunk); + } + } + EmulatedLimbs::Constant(_) => panic!( + "Constant input already handled with a return. Execution should not reach here" + ), + } + + let pseudo_mersenne_params = P::pseudo_mersenne_params().unwrap(); + if P::num_limbs() * P::bits_per_limb() < pseudo_mersenne_params.e as usize { + panic!("The number of bits available is too small to accommodate the non-native field elements"); + } + + let mut acc = chunks[0].clone(); + + for (i, chunk) in chunks.iter().enumerate().skip(1) { + let bitwidth = (i * P::num_limbs() * P::bits_per_limb()) as u32; + let q = bitwidth / pseudo_mersenne_params.e; + let r = bitwidth % pseudo_mersenne_params.e; + let mut scale = pseudo_mersenne_params.c.pow(q); + scale *= BigInt::one() << r; + let scaled_chunk = chunk.mul_const( + &mut cs.namespace(|| format!("multiplying chunk {i} with {scale}")), + &scale, + )?; + acc = acc.add( + &mut cs.namespace(|| format!("adding chunk {i}-1 and chunk {i}")), + &scaled_chunk, + )?; + } + + *self = acc; + + Ok(()) + } + + fn reduce_and_apply_op( + cs: &mut CS, + op_type: Optype, + a: &Self, + b: &Self, + ) -> Result + where + CS: ConstraintSystem, + { + a.enforce_width_conditional(&mut cs.namespace(|| "ensure bitwidths in a"))?; + b.enforce_width_conditional(&mut cs.namespace(|| "ensure bitwidths in b"))?; + + let precondition = match op_type { + Optype::Add => Self::add_precondition, + Optype::Sub => Self::sub_precondition, + Optype::Mul => Self::mul_precondition, + }; + + let mut a_r: Self = a.clone(); + let mut b_r: Self = b.clone(); + let mut loop_iteration = 0u32; // Used to prevent namespace collisions in below loop + let next_overflow: usize = loop { + let res = precondition(&a_r, &b_r); + if let Ok(res_next_overflow) = res { + break res_next_overflow; + } else { + let err = res.err().unwrap(); + if err.reduce_right { + b_r = b_r.reduce(&mut cs.namespace(|| format!("reduce b {loop_iteration}")))?; + } else { + a_r = a_r.reduce(&mut cs.namespace(|| format!("reduce a {loop_iteration}")))?; + } + } + loop_iteration += 1; + }; + + let res = match op_type { + Optype::Add => Self::add_op::(&a_r, &b_r, next_overflow), + Optype::Sub => Self::sub_op::(&a_r, &b_r, next_overflow), + Optype::Mul => Self::mul_op(&mut cs.namespace(|| "mul_op"), &a_r, &b_r, next_overflow), + }; + + res + } +} diff --git a/crates/bellpepper-emulated/src/lib.rs b/crates/bellpepper-emulated/src/lib.rs new file mode 100644 index 0000000..82b1a41 --- /dev/null +++ b/crates/bellpepper-emulated/src/lib.rs @@ -0,0 +1,4 @@ +pub mod field_element; +pub mod field_hints; +pub mod field_ops; +pub mod util; diff --git a/crates/bellpepper-emulated/src/util.rs b/crates/bellpepper-emulated/src/util.rs new file mode 100644 index 0000000..68325e9 --- /dev/null +++ b/crates/bellpepper-emulated/src/util.rs @@ -0,0 +1,209 @@ +use std::ops::Rem; + +use bellpepper_core::boolean::AllocatedBit; +use bellpepper_core::num::{AllocatedNum, Num}; +use bellpepper_core::{ConstraintSystem, LinearCombination, SynthesisError, Variable}; +use ff::{PrimeField, PrimeFieldBits}; +use num_bigint::BigInt; +use num_traits::{One, Signed, Zero}; + +/// Range check a Num +pub fn range_check_num( + cs: &mut CS, + num: &Num, + num_bits: usize, +) -> Result<(), SynthesisError> +where + F: PrimeField + PrimeFieldBits, + CS: ConstraintSystem, +{ + range_check_lc( + cs, + &num.lc(F::ONE), + num.get_value().unwrap_or_default(), + num_bits, + ) +} + +/// Range check an expression represented by a LinearCombination +/// +/// From `fits_in_bits` of `bellperson-nonnative` +pub fn range_check_lc( + cs: &mut CS, + lc_input: &LinearCombination, + lc_value: F, + num_bits: usize, +) -> Result<(), SynthesisError> +where + F: PrimeField + PrimeFieldBits, + CS: ConstraintSystem, +{ + let value_bits = lc_value.to_le_bits(); + + // Allocate all but the first bit. + let bits: Vec = (1..num_bits) + .map(|i| { + cs.alloc( + || format!("bit {i}"), + || { + let r = if value_bits[i] { F::ONE } else { F::ZERO }; + Ok(r) + }, + ) + }) + .collect::>()?; + + for (i, v) in bits.iter().enumerate() { + cs.enforce( + || format!("bit {i} is bit"), + |lc| lc + *v, + |lc| lc + CS::one() - *v, + |lc| lc, + ) + } + + // Last bit + cs.enforce( + || "last bit of variable is a bit".to_string(), + |mut lc| { + let mut f = F::ONE; + lc = lc + lc_input; + for v in bits.iter() { + f = f.double(); + lc = lc - (f, *v); + } + lc + }, + |mut lc| { + lc = lc + CS::one(); + let mut f = F::ONE; + lc = lc - lc_input; + for v in bits.iter() { + f = f.double(); + lc = lc + (f, *v); + } + lc + }, + |lc| lc, + ); + + Ok(()) +} + +/// Range check a constant field element +pub fn range_check_constant(value: F, num_bits: usize) -> Result<(), SynthesisError> +where + F: PrimeField + PrimeFieldBits, +{ + let value_bits = value.to_le_bits(); + let mut res = F::ZERO; + let mut coeff = F::ONE; + for i in 0..num_bits { + if value_bits[i] { + res += coeff; + } + coeff = coeff.double(); + } + if res != value { + eprintln!("value does not fit in the required number of bits"); + return Err(SynthesisError::Unsatisfiable); + } + + Ok(()) +} + +/// Check that a Num equals a constant and return a bit +/// +/// Based on `alloc_num_equals` in `Nova/src/gadgets/utils.rs` +pub fn alloc_num_equals_constant>( + mut cs: CS, + a: &Num, + b: F, +) -> Result { + // Allocate and constrain `r`: result boolean bit. + // It equals `true` if `a` equals `b`, `false` otherwise + let a_value = a.get_value().unwrap_or_default(); + let r = AllocatedBit::alloc(cs.namespace(|| "r"), Some(a_value == b))?; + + // Allocate t s.t. t=1 if a == b else 1/(a - b) + let t_value = if a_value == b { + F::ONE + } else { + (a_value - b).invert().unwrap() + }; + let t = AllocatedNum::alloc(cs.namespace(|| "t"), || Ok(t_value))?; + + cs.enforce( + || "t*(a - b) = 1 - r", + |lc| lc + t.get_variable(), + |lc| lc + &a.lc(F::ONE) - &LinearCombination::from_coeff(CS::one(), b), + |lc| lc + CS::one() - r.get_variable(), + ); + + cs.enforce( + || "r*(a - b) = 0", + |lc| lc + r.get_variable(), + |lc| lc + &a.lc(F::ONE) - &LinearCombination::from_coeff(CS::one(), b), + |lc| lc, + ); + + Ok(r) +} + +/// Convert a non-negative BigInt into a field element +pub fn bigint_to_scalar(value: &BigInt) -> F +where + F: PrimeField + PrimeFieldBits, +{ + assert!(value.bits() as u32 <= F::CAPACITY); + assert!(!value.is_negative()); + + let mut base = F::from(u64::MAX); + base += F::ONE; // 2^64 in the field + let mut coeff = F::ONE; + let mut res = F::ZERO; + + let (_sign, digits) = value.to_u64_digits(); + for d in digits.into_iter() { + let d_f = F::from(d); + res += d_f * coeff; + coeff *= base; + } + res +} + +/// Construct a [BigInt] from a vector of [BigInt] limbs with base equal to 2^num_bits_per_limb +pub fn recompose(limbs: &Vec, num_bits_per_limb: usize) -> Result { + if limbs.is_empty() { + eprintln!("Empty input"); + return Err(SynthesisError::Unsatisfiable); + } + + let mut res = BigInt::zero(); + for i in 0..limbs.len() { + res <<= num_bits_per_limb; + res += &limbs[limbs.len() - i - 1]; + } + Ok(res) +} + +/// Decompose a [BigInt] into a vector of [BigInt] limbs each occupying `num_bits_per_limb` bits. +pub fn decompose( + input: &BigInt, + num_bits_per_limb: usize, + num_limbs: usize, +) -> Result, SynthesisError> { + if input.bits() as usize > num_limbs * num_bits_per_limb { + eprintln!("Not enough limbs to represent input {:?}", input); + return Err(SynthesisError::Unsatisfiable); + } + + let mut res = vec![BigInt::zero(); num_limbs]; + let base = BigInt::one() << num_bits_per_limb; + let mut tmp = input.clone(); + for r in res.iter_mut() { + *r = tmp.clone().rem(&base); + tmp >>= num_bits_per_limb; + } + Ok(res) +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 7d12d9a..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -}