diff --git a/Cargo.toml b/Cargo.toml index 6c66de3..a87d35f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "crates/sha1", "crates/bellpepper-emulated", "crates/bellpepper-ed25519", ] @@ -13,3 +14,4 @@ repository = "https://github.com/lurk-lab/bellpepper-gadgets" bellpepper-core = { version="0.2.0", default-features = false } bellpepper = { version="0.2.0", default-features = false } ff = "0.13.0" +pasta_curves = { version = "0.5", features = ["repr-c", "serde"] } diff --git a/crates/sha1/Cargo.toml b/crates/sha1/Cargo.toml new file mode 100644 index 0000000..1d7ee88 --- /dev/null +++ b/crates/sha1/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bellpepper-sha1" +version = "0.1.0" +edition = "2021" +authors = ["Saravanan Vijayakumaran "] +license = "MIT OR Apache-2.0" + +[dependencies] +bellpepper-core = { workspace = true } +bellpepper = { workspace = true } +ff = { workspace = true } + +[dev-dependencies] +pasta_curves = { workspace = true } +hex-literal = "0.4.1" +rand_core = "0.6.4" +rand_xorshift = "0.3.0" +sha1 = "0.10.5" diff --git a/crates/sha1/LICENSE-APACHE b/crates/sha1/LICENSE-APACHE new file mode 100644 index 0000000..09bb92f --- /dev/null +++ b/crates/sha1/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/sha1/LICENSE-MIT b/crates/sha1/LICENSE-MIT new file mode 100644 index 0000000..d57dc48 --- /dev/null +++ b/crates/sha1/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/sha1/README.md b/crates/sha1/README.md new file mode 100644 index 0000000..39164b7 --- /dev/null +++ b/crates/sha1/README.md @@ -0,0 +1,19 @@ +# bellpepper-sha1 +A [bellpepper](https://github.com/lurk-lab/bellpepper) gadget for the SHA-1 hash function + +## 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/sha1/src/lib.rs b/crates/sha1/src/lib.rs new file mode 100644 index 0000000..ea528c7 --- /dev/null +++ b/crates/sha1/src/lib.rs @@ -0,0 +1,2 @@ +pub mod sha1; +pub mod util; diff --git a/crates/sha1/src/sha1.rs b/crates/sha1/src/sha1.rs new file mode 100644 index 0000000..400a16f --- /dev/null +++ b/crates/sha1/src/sha1.rs @@ -0,0 +1,406 @@ +//! Circuits for the [SHA-1] hash function and its internal compression +//! function. +//! +//! [SHA-1]: https://datatracker.ietf.org/doc/html/rfc3174 + +use bellpepper::gadgets::{multieq::MultiEq, uint32::UInt32}; +use bellpepper_core::{boolean::Boolean, ConstraintSystem, SynthesisError}; +use ff::PrimeField; + +use crate::util::{and_uint32, or_uint32}; + +const ROUND_CONSTANTS: [u32; 4] = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6]; + +const IV: [u32; 5] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]; + +pub fn sha1_block_no_padding( + mut cs: CS, + input: &[Boolean], +) -> Result, SynthesisError> +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + assert_eq!(input.len(), 512); + + Ok(sha1_compression_function(&mut cs, input, &get_sha1_iv())? + .into_iter() + .flat_map(|e| e.into_bits_be()) + .collect()) +} + +pub fn sha1(mut cs: CS, input: &[Boolean]) -> Result, SynthesisError> +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + assert!(input.len() % 8 == 0); + + let mut padded = input.to_vec(); + let plen = padded.len() as u64; + // append a single '1' bit + padded.push(Boolean::constant(true)); + // append K '0' bits, where K is the minimum number >= 0 such that L + 1 + K + 64 is a multiple of 512 + while (padded.len() + 64) % 512 != 0 { + padded.push(Boolean::constant(false)); + } + // append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bits + for b in (0..64).rev().map(|i| (plen >> i) & 1 == 1) { + padded.push(Boolean::constant(b)); + } + assert!(padded.len() % 512 == 0); + + let mut cur = get_sha1_iv(); + for (i, block) in padded.chunks(512).enumerate() { + cur = sha1_compression_function(cs.namespace(|| format!("block {}", i)), block, &cur)?; + } + + Ok(cur.into_iter().flat_map(|e| e.into_bits_be()).collect()) +} + +fn get_sha1_iv() -> Vec { + IV.iter().map(|&v| UInt32::constant(v)).collect() +} + +pub fn sha1_compression_function( + cs: CS, + input: &[Boolean], + current_hash_value: &[UInt32], +) -> Result, SynthesisError> +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + assert_eq!(input.len(), 512); + assert_eq!(current_hash_value.len(), 5); + + let mut w = input + .chunks(32) + .map(UInt32::from_bits_be) + .collect::>(); + + // We can save some constraints by combining some of + // the constraints in different u32 additions + let mut cs = MultiEq::new(cs); + + for i in 16..80 { + let cs = &mut cs.namespace(|| format!("w extension {}", i)); + + // w[i] := (w[i-3] xor w[i-8] xor w[i-14] xor w[i-16]) leftrotate 1 + let mut wi = w[i - 3].xor(cs.namespace(|| format!("first xor for w[{i}]")), &w[i - 8])?; + wi = wi.xor( + cs.namespace(|| format!("second xor for w[{i}]")), + &w[i - 14], + )?; + wi = wi.xor(cs.namespace(|| format!("third xor for w[{i}]")), &w[i - 16])?; + + // rotr(31) is equivalent to leftrotate 1 + wi = wi.rotr(31); + w.push(wi); + } + + assert_eq!(w.len(), 80); + + let mut a = current_hash_value[0].clone(); + let mut b = current_hash_value[1].clone(); + let mut c = current_hash_value[2].clone(); + let mut d = current_hash_value[3].clone(); + let mut e = current_hash_value[4].clone(); + + for i in 0..80 { + let cs = &mut cs.namespace(|| format!("compression round {}", i)); + + let f = if i < 20 { + // f = (b and c) or ((not b) and d) + UInt32::sha256_ch(cs.namespace(|| "ch"), &b, &c, &d)? + } else if !(40..60).contains(&i) { + // b xor c xor d + b.xor(cs.namespace(|| "b xor c"), &c)? + .xor(cs.namespace(|| "b xor c xor d"), &d)? + } else { + let a1 = and_uint32(cs.namespace(|| "1st and"), &b, &c)?; + let a2 = and_uint32(cs.namespace(|| "2nd and"), &b, &d)?; + let a3 = and_uint32(cs.namespace(|| "3rd and"), &c, &d)?; + + let tmp = or_uint32(cs.namespace(|| "1st or"), &a1, &a2)?; + or_uint32(cs.namespace(|| "2nd or"), &tmp, &a3)? + }; + + // temp = (a leftrotate 5) + f + e + k + w[i] + let temp = UInt32::addmany( + cs.namespace(|| "temp"), + &[ + a.rotr(27), + f, + e, + UInt32::constant(ROUND_CONSTANTS[i / 20]), + w[i].clone(), + ], + )?; + + /* + e = d + d = c + c = b leftrotate 30 + b = a + a = temp + */ + + e = d; + d = c; + c = b.rotr(2); + b = a; + a = temp; + } + + /* + Add the compressed chunk to the current hash value: + h0 := h0 + a + h1 := h1 + b + h2 := h2 + c + h3 := h3 + d + h4 := h4 + e + */ + + let h0 = UInt32::addmany( + cs.namespace(|| "new h0"), + &[current_hash_value[0].clone(), a], + )?; + + let h1 = UInt32::addmany( + cs.namespace(|| "new h1"), + &[current_hash_value[1].clone(), b], + )?; + + let h2 = UInt32::addmany( + cs.namespace(|| "new h2"), + &[current_hash_value[2].clone(), c], + )?; + + let h3 = UInt32::addmany( + cs.namespace(|| "new h3"), + &[current_hash_value[3].clone(), d], + )?; + + let h4 = UInt32::addmany( + cs.namespace(|| "new h4"), + &[current_hash_value[4].clone(), e], + )?; + + Ok(vec![h0, h1, h2, h3, h4]) +} + +#[cfg(test)] +mod test { + use super::*; + use bellpepper::gadgets::multipack::bytes_to_bits; + use bellpepper_core::{boolean::AllocatedBit, test_cs::TestConstraintSystem}; + use hex_literal::hex; + use pasta_curves::Fp; + use rand_core::{RngCore, SeedableRng}; + use rand_xorshift::XorShiftRng; + + #[test] + fn test_blank_hash() { + let iv = get_sha1_iv(); + + let mut cs = TestConstraintSystem::::new(); + let mut input_bits: Vec<_> = (0..512).map(|_| Boolean::Constant(false)).collect(); + input_bits[0] = Boolean::Constant(true); + let out = sha1_compression_function(&mut cs, &input_bits, &iv).unwrap(); + let out_bits = out.into_iter().flat_map(|e| e.into_bits_be()); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 0); + + let expected = hex!("da39a3ee5e6b4b0d3255bfef95601890afd80709"); + + let mut out = out_bits; + for b in expected.iter() { + for i in (0..8).rev() { + let c = out.next().unwrap().get_value().unwrap(); + + assert_eq!(c, (b >> i) & 1u8 == 1u8); + } + } + } + + #[test] + fn test_hash_abc() { + let iv = get_sha1_iv(); + + let msg = "abc".as_bytes(); + let msg_bits = bytes_to_bits(msg); + + let mut cs = TestConstraintSystem::::new(); + let mut input_bits = msg_bits + .into_iter() + .map(Boolean::Constant) + .collect::>(); + input_bits.push(Boolean::Constant(true)); + input_bits.append(&mut vec![Boolean::Constant(false); 479]); + // The number 24 as a boolean vector + let mut len = vec![false, false, false, true, true, false, false, false] + .into_iter() + .map(Boolean::Constant) + .collect::>(); + input_bits.append(&mut len); + + let out = sha1_compression_function(&mut cs, &input_bits, &iv).unwrap(); + let out_bits = out.into_iter().flat_map(|e| e.into_bits_be()); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 0); + + let expected = hex!("a9993e364706816aba3e25717850c26c9cd0d89d"); + + let mut out = out_bits; + for b in expected.iter() { + for i in (0..8).rev() { + let c = out.next().unwrap().get_value().unwrap(); + + assert_eq!(c, (b >> i) & 1u8 == 1u8); + } + } + } + + #[test] + fn test_hash_abcde_string() { + let msg = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq".as_bytes(); + let msg_bits = bytes_to_bits(msg); + + let mut cs = TestConstraintSystem::::new(); + let input_bits: Vec = msg_bits + .into_iter() + .enumerate() + .map(|(i, b)| { + Boolean::from( + AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(b)) + .unwrap(), + ) + }) + .collect(); + + let out_bits = sha1(cs.namespace(|| "sha1"), &input_bits).unwrap(); + assert!(cs.is_satisfied()); + + let expected = hex!("84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + let mut out = out_bits.iter(); + for b in expected.iter() { + for i in (0..8).rev() { + let c = out.next().unwrap().get_value().unwrap(); + + assert_eq!(c, (b >> i) & 1u8 == 1u8); + } + } + } + + #[test] + fn test_full_block() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + let iv = get_sha1_iv(); + + let mut cs = TestConstraintSystem::::new(); + let input_bits: Vec<_> = (0..512) + .map(|i| { + Boolean::from( + AllocatedBit::alloc( + cs.namespace(|| format!("input bit {}", i)), + Some(rng.next_u32() % 2 != 0), + ) + .unwrap(), + ) + }) + .collect(); + + sha1_compression_function(cs.namespace(|| "sha1"), &input_bits, &iv).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints() - 512, 16706); + } + + #[test] + fn test_full_hash() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + let mut cs = TestConstraintSystem::::new(); + let input_bits: Vec<_> = (0..512) + .map(|i| { + Boolean::from( + AllocatedBit::alloc( + cs.namespace(|| format!("input bit {}", i)), + Some(rng.next_u32() % 2 != 0), + ) + .unwrap(), + ) + }) + .collect(); + + sha1(cs.namespace(|| "sha1"), &input_bits).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints() - 512, 27364); + } + + #[test] + fn test_against_vectors() { + use sha1::{Digest, Sha1}; + + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) { + let mut h = Sha1::new(); + let data: Vec = (0..input_len).map(|_| rng.next_u32() as u8).collect(); + h.update(&data); + let hash_result = h.finalize(); + + let mut cs = TestConstraintSystem::::new(); + let mut input_bits = vec![]; + + for (byte_i, input_byte) in data.into_iter().enumerate() { + for bit_i in (0..8).rev() { + let cs = cs.namespace(|| format!("input bit {} {}", byte_i, bit_i)); + + input_bits.push( + AllocatedBit::alloc(cs, Some((input_byte >> bit_i) & 1u8 == 1u8)) + .unwrap() + .into(), + ); + } + } + + let r = sha1(&mut cs, &input_bits).unwrap(); + + assert!(cs.is_satisfied()); + + let mut s = hash_result + .iter() + .flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1u8 == 1u8)); + + for b in r { + match b { + Boolean::Is(b) => { + assert!(s.next().unwrap() == b.get_value().unwrap()); + } + Boolean::Not(b) => { + assert!(s.next().unwrap() != b.get_value().unwrap()); + } + Boolean::Constant(b) => { + assert!(input_len == 0); + assert!(s.next().unwrap() == b); + } + } + } + } + } +} diff --git a/crates/sha1/src/util.rs b/crates/sha1/src/util.rs new file mode 100644 index 0000000..c8c6eec --- /dev/null +++ b/crates/sha1/src/util.rs @@ -0,0 +1,491 @@ +use bellpepper::gadgets::uint32::UInt32; +use bellpepper_core::{ + boolean::{AllocatedBit, Boolean}, + ConstraintSystem, SynthesisError, +}; +use ff::PrimeField; + +/// Perform OR over two boolean operands +pub fn or_boolean<'a, Scalar, CS>( + cs: CS, + a: &'a Boolean, + b: &'a Boolean, +) -> Result +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + match (a, b) { + // true OR x is always true + (&Boolean::Constant(true), _) | (_, &Boolean::Constant(true)) => { + Ok(Boolean::Constant(true)) + } + // false OR x is always x + (&Boolean::Constant(false), x) | (x, &Boolean::Constant(false)) => Ok(x.clone()), + // a OR (NOT b) + (&Boolean::Is(ref is), &Boolean::Not(ref not)) + | (&Boolean::Not(ref not), &Boolean::Is(ref is)) => { + Ok(Boolean::Is(or_not_allocated_bits(cs, is, not)?)) + } + // (NOT a) OR (NOT b) = a NOR b + (Boolean::Not(a), Boolean::Not(b)) => Ok(Boolean::Is(nand_allocated_bits(cs, a, b)?)), + // a OR b + (Boolean::Is(a), Boolean::Is(b)) => Ok(Boolean::Is(or_allocated_bits(cs, a, b)?)), + } +} + +/// AND two `UInt32` variables +pub fn and_uint32(mut cs: CS, a: &UInt32, b: &UInt32) -> Result +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + let a_bits = a.clone().into_bits(); + let b_bits = b.clone().into_bits(); + let and_bits = a_bits + .iter() + .zip(b_bits.iter()) + .enumerate() + .map(|(i, (x, y))| Boolean::and(cs.namespace(|| format!("and {i}")), x, y).unwrap()) + .collect::>(); + + Ok(UInt32::from_bits(&and_bits)) +} + +/// OR two `UInt32` variables +pub fn or_uint32(mut cs: CS, a: &UInt32, b: &UInt32) -> Result +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + let a_bits = a.clone().into_bits(); + let b_bits = b.clone().into_bits(); + let and_bits = a_bits + .iter() + .zip(b_bits.iter()) + .enumerate() + .map(|(i, (x, y))| or_boolean(cs.namespace(|| format!("or {i}")), x, y).unwrap()) + .collect::>(); + + Ok(UInt32::from_bits(&and_bits)) +} + +/// Calculates `a OR (NOT b)`. +pub fn or_not_allocated_bits( + mut cs: CS, + a: &AllocatedBit, + b: &AllocatedBit, +) -> Result +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + let result_value = a + .get_value() + .zip(b.get_value()) + .map(|(a_val, b_val)| a_val | !b_val); + + let result_bit = AllocatedBit::alloc(cs.namespace(|| "or not result"), result_value)?; + + // Constrain (1-a) * (b) = (1-c), ensuring c is 0 iff + // a is false and b is true, and otherwise c is 1. + cs.enforce( + || "or not constraint", + |lc| lc + CS::one() - a.get_variable(), + |lc| lc + b.get_variable(), + |lc| lc + CS::one() - result_bit.get_variable(), + ); + + Ok(result_bit) +} + +/// Calculates `NOT(a AND b) = (NOT a) OR (NOT b)`. +pub fn nand_allocated_bits( + mut cs: CS, + a: &AllocatedBit, + b: &AllocatedBit, +) -> Result +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + let result_value = a + .get_value() + .zip(b.get_value()) + .map(|(a_val, b_val)| !(a_val & b_val)); + + let result_bit = AllocatedBit::alloc(cs.namespace(|| "nand result"), result_value)?; + + // Constrain (a) * (b) = (1-c), ensuring c is 0 iff + // a and b are both true, and otherwise c is 1. + cs.enforce( + || "nand constraint", + |lc| lc + a.get_variable(), + |lc| lc + b.get_variable(), + |lc| lc + CS::one() - result_bit.get_variable(), + ); + + Ok(result_bit) +} + +/// Calculates `NOT(a AND b) = (NOT a) OR (NOT b)`. +pub fn or_allocated_bits( + mut cs: CS, + a: &AllocatedBit, + b: &AllocatedBit, +) -> Result +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + let result_value = a + .get_value() + .zip(b.get_value()) + .map(|(a_val, b_val)| a_val | b_val); + + let result_bit = AllocatedBit::alloc(cs.namespace(|| "or result"), result_value)?; + + // Constrain (1 - a) * (1 - b) = (1-c), ensuring c is 0 iff + // a and b are both false, and otherwise c is 1. + cs.enforce( + || "or constraint", + |lc| lc + CS::one() - a.get_variable(), + |lc| lc + CS::one() - b.get_variable(), + |lc| lc + CS::one() - result_bit.get_variable(), + ); + + Ok(result_bit) +} + +#[cfg(test)] +mod test { + + use bellpepper_core::test_cs::TestConstraintSystem; + use ff::Field; + use pasta_curves::Fp; + use rand_core::{RngCore, SeedableRng}; + use rand_xorshift::XorShiftRng; + + use super::*; + + #[test] + fn test_uint32_and() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a = rng.next_u32(); + let b = rng.next_u32(); + let c = rng.next_u32(); + + let mut expected = a & b & c; + + let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt32::constant(b); + let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap(); + + let r = and_uint32(cs.namespace(|| "first and"), &a_bit, &b_bit).unwrap(); + let r = and_uint32(cs.namespace(|| "second and"), &r, &c_bit).unwrap(); + + assert!(cs.is_satisfied()); + + for b in r.into_bits() { + match b { + Boolean::Is(ref b) => { + assert_eq!(b.get_value().unwrap(), expected & 1 == 1); + } + Boolean::Not(ref b) => { + assert_ne!(b.get_value().unwrap(), expected & 1 == 1); + } + Boolean::Constant(b) => { + assert_eq!(b, expected & 1 == 1); + } + } + + expected >>= 1; + } + } + } + + #[test] + fn test_uint32_or() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a = rng.next_u32(); + let b = rng.next_u32(); + let c = rng.next_u32(); + + let mut expected = a | b | c; + + let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt32::constant(b); + let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap(); + + let r = or_uint32(cs.namespace(|| "first or"), &a_bit, &b_bit).unwrap(); + let r = or_uint32(cs.namespace(|| "second or"), &r, &c_bit).unwrap(); + + assert!(cs.is_satisfied()); + + for b in r.into_bits() { + match b { + Boolean::Is(ref b) => { + assert_eq!(b.get_value().unwrap(), expected & 1 == 1); + } + Boolean::Not(ref b) => { + assert_ne!(b.get_value().unwrap(), expected & 1 == 1); + } + Boolean::Constant(b) => { + assert_eq!(b, expected & 1 == 1); + } + } + + expected >>= 1; + } + } + } + + #[derive(Copy, Clone, Debug)] + enum OperandType { + True, + False, + AllocatedTrue, + AllocatedFalse, + NegatedAllocatedTrue, + NegatedAllocatedFalse, + } + + #[test] + fn test_boolean_or() { + let variants = [ + OperandType::True, + OperandType::False, + OperandType::AllocatedTrue, + OperandType::AllocatedFalse, + OperandType::NegatedAllocatedTrue, + OperandType::NegatedAllocatedFalse, + ]; + + for first_operand in variants.iter().cloned() { + for second_operand in variants.iter().cloned() { + let mut cs = TestConstraintSystem::::new(); + + let a; + let b; + + { + let mut dyn_construct = |operand, name| { + let cs = cs.namespace(|| name); + + match operand { + OperandType::True => Boolean::constant(true), + OperandType::False => Boolean::constant(false), + OperandType::AllocatedTrue => { + Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()) + } + OperandType::AllocatedFalse => { + Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()) + } + OperandType::NegatedAllocatedTrue => { + Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()).not() + } + OperandType::NegatedAllocatedFalse => { + Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()).not() + } + } + }; + + a = dyn_construct(first_operand, "a"); + b = dyn_construct(second_operand, "b"); + } + + let c = or_boolean(&mut cs, &a, &b).unwrap(); + + assert!(cs.is_satisfied()); + + match (first_operand, second_operand, c.clone()) { + (OperandType::True, OperandType::True, Boolean::Constant(true)) => {} + (OperandType::True, OperandType::False, Boolean::Constant(true)) => {} + (OperandType::True, OperandType::AllocatedTrue, Boolean::Constant(true)) => {} + (OperandType::True, OperandType::AllocatedFalse, Boolean::Constant(true)) => {} + ( + OperandType::True, + OperandType::NegatedAllocatedTrue, + Boolean::Constant(true), + ) => {} + ( + OperandType::True, + OperandType::NegatedAllocatedFalse, + Boolean::Constant(true), + ) => {} + + (OperandType::False, OperandType::True, Boolean::Constant(true)) => {} + (OperandType::False, OperandType::False, Boolean::Constant(false)) => {} + (OperandType::False, OperandType::AllocatedTrue, Boolean::Is(_)) => {} + (OperandType::False, OperandType::AllocatedFalse, Boolean::Is(_)) => {} + (OperandType::False, OperandType::NegatedAllocatedTrue, Boolean::Not(_)) => {} + (OperandType::False, OperandType::NegatedAllocatedFalse, Boolean::Not(_)) => {} + + (OperandType::AllocatedTrue, OperandType::True, Boolean::Constant(true)) => {} + (OperandType::AllocatedTrue, OperandType::False, Boolean::Is(_)) => {} + ( + OperandType::AllocatedTrue, + OperandType::AllocatedTrue, + Boolean::Is(ref v), + ) => { + // assert!(cs.get("or result") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + ( + OperandType::AllocatedTrue, + OperandType::AllocatedFalse, + Boolean::Is(ref v), + ) => { + // assert!(cs.get("or result") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + ( + OperandType::AllocatedTrue, + OperandType::NegatedAllocatedTrue, + Boolean::Is(ref v), + ) => { + // assert!(cs.get("or not result") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + ( + OperandType::AllocatedTrue, + OperandType::NegatedAllocatedFalse, + Boolean::Is(ref v), + ) => { + // assert!(cs.get("or not result") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + + (OperandType::AllocatedFalse, OperandType::True, Boolean::Constant(true)) => {} + (OperandType::AllocatedFalse, OperandType::False, Boolean::Is(_)) => {} + ( + OperandType::AllocatedFalse, + OperandType::AllocatedTrue, + Boolean::Is(ref v), + ) => { + // assert!(cs.get("or result") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + ( + OperandType::AllocatedFalse, + OperandType::AllocatedFalse, + Boolean::Is(ref v), + ) => { + assert!(cs.get("or result/boolean") == Field::ZERO); + assert_eq!(v.get_value(), Some(false)); + } + ( + OperandType::AllocatedFalse, + OperandType::NegatedAllocatedTrue, + Boolean::Is(ref v), + ) => { + assert!(cs.get("or not result/boolean") == Field::ZERO); + assert_eq!(v.get_value(), Some(false)); + } + ( + OperandType::AllocatedFalse, + OperandType::NegatedAllocatedFalse, + Boolean::Is(ref v), + ) => { + assert!(cs.get("or not result/boolean") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + + ( + OperandType::NegatedAllocatedTrue, + OperandType::True, + Boolean::Constant(true), + ) => {} + (OperandType::NegatedAllocatedTrue, OperandType::False, Boolean::Not(_)) => {} + ( + OperandType::NegatedAllocatedTrue, + OperandType::AllocatedTrue, + Boolean::Is(ref v), + ) => { + assert!(cs.get("or not result/boolean") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + ( + OperandType::NegatedAllocatedTrue, + OperandType::AllocatedFalse, + Boolean::Is(ref v), + ) => { + assert!(cs.get("or not result/boolean") == Field::ZERO); + assert_eq!(v.get_value(), Some(false)); + } + ( + OperandType::NegatedAllocatedTrue, + OperandType::NegatedAllocatedTrue, + Boolean::Is(ref v), + ) => { + assert!(cs.get("nand result/boolean") == Field::ZERO); + assert_eq!(v.get_value(), Some(false)); + } + ( + OperandType::NegatedAllocatedTrue, + OperandType::NegatedAllocatedFalse, + Boolean::Is(ref v), + ) => { + assert!(cs.get("nand result/boolean") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + + ( + OperandType::NegatedAllocatedFalse, + OperandType::True, + Boolean::Constant(true), + ) => {} + (OperandType::NegatedAllocatedFalse, OperandType::False, Boolean::Not(_)) => {} + ( + OperandType::NegatedAllocatedFalse, + OperandType::AllocatedTrue, + Boolean::Is(ref v), + ) => { + assert!(cs.get("or not result/boolean") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + ( + OperandType::NegatedAllocatedFalse, + OperandType::AllocatedFalse, + Boolean::Is(ref v), + ) => { + assert!(cs.get("or not result/boolean") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + ( + OperandType::NegatedAllocatedFalse, + OperandType::NegatedAllocatedTrue, + Boolean::Is(ref v), + ) => { + assert!(cs.get("nand result/boolean") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + ( + OperandType::NegatedAllocatedFalse, + OperandType::NegatedAllocatedFalse, + Boolean::Is(ref v), + ) => { + assert!(cs.get("nand result/boolean") == Field::ONE); + assert_eq!(v.get_value(), Some(true)); + } + + _ => panic!("this should never be encountered"), + } + } + } + } +}