diff --git a/Cargo.toml b/Cargo.toml index 6c66de3..738965a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "crates/bellpepper-emulated", "crates/bellpepper-ed25519", + "crates/bellpepper-sha512", ] [workspace.package] diff --git a/crates/bellpepper-sha512/Cargo.toml b/crates/bellpepper-sha512/Cargo.toml new file mode 100644 index 0000000..fedac8b --- /dev/null +++ b/crates/bellpepper-sha512/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bellpepper-sha512" +version = "0.2.0" +edition = "2021" +authors = ["Varun Thakore "] +description = "R1CS circuit for SHA-512 hash function" +license = "MIT OR Apache-2.0" +homepage.workspace = true +repository.workspace = true + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bellpepper-core = { workspace = true } +bellpepper = { workspace = true } +ff = { workspace = true } + + +[dev-dependencies] +blstrs = "0.7.0" +rand = "0.8" +hex-literal = "0.4.1" +rand_core = "0.6.4" +rand_xorshift = "0.3.0" +sha2 = "0.10.6" diff --git a/crates/bellpepper-sha512/LICENSE-APACHE b/crates/bellpepper-sha512/LICENSE-APACHE new file mode 100644 index 0000000..b037b4e --- /dev/null +++ b/crates/bellpepper-sha512/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 Varun Thakore + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/crates/bellpepper-sha512/LICENSE-MIT b/crates/bellpepper-sha512/LICENSE-MIT new file mode 100644 index 0000000..2396422 --- /dev/null +++ b/crates/bellpepper-sha512/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Varun Thakore + +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-sha512/README.md b/crates/bellpepper-sha512/README.md new file mode 100644 index 0000000..60f90c7 --- /dev/null +++ b/crates/bellpepper-sha512/README.md @@ -0,0 +1,24 @@ +# bellpepper-SHA512 + +This library contains circuit for SHA-512 hash function and circuit representation of u64, created using [bellpepper](https://github.com/lurk-lab/bellpepper). + +## References +1. [Bellman implementation of SHA256](https://github.com/zkcrypto/bellman/blob/main/src/gadgets/sha256.rs) +2. [SHA-512 Hashing Algorithm](https://www.brainkart.com/article/Secure-Hash-Algorithm-(SHA)_8450/) + +## 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-sha512/src/lib.rs b/crates/bellpepper-sha512/src/lib.rs new file mode 100644 index 0000000..b2a1460 --- /dev/null +++ b/crates/bellpepper-sha512/src/lib.rs @@ -0,0 +1,2 @@ +pub mod sha512; +pub mod uint64; diff --git a/crates/bellpepper-sha512/src/sha512.rs b/crates/bellpepper-sha512/src/sha512.rs new file mode 100644 index 0000000..f2eb56b --- /dev/null +++ b/crates/bellpepper-sha512/src/sha512.rs @@ -0,0 +1,506 @@ +//! Circuits for the [SHA-512] hash function and its internal compression +//! function. +//! +//! [SHA-512]: https://tools.ietf.org/html/rfc6234 + +// #![allow(clippy::many_single_char_names)] + +use super::uint64::UInt64; +use bellpepper::gadgets::multieq::MultiEq; +use bellpepper_core::{boolean::Boolean, ConstraintSystem, SynthesisError}; +use ff::PrimeField; + +// Constants copied from https://github.com/RustCrypto/hashes/blob/master/sha2/src/consts.rs +#[allow(clippy::unreadable_literal)] +const ROUND_CONSTANTS: [u64; 80] = [ + 0x428a2f98d728ae22, + 0x7137449123ef65cd, + 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, + 0x59f111f1b605d019, + 0x923f82a4af194f9b, + 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, + 0x12835b0145706fbe, + 0x243185be4ee4b28c, + 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, + 0x80deb1fe3b1696b1, + 0x9bdc06a725c71235, + 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, + 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, + 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, + 0x5cb0a9dcbd41fbd4, + 0x76f988da831153b5, + 0x983e5152ee66dfab, + 0xa831c66d2db43210, + 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, + 0xd5a79147930aa725, + 0x06ca6351e003826f, + 0x142929670a0e6e70, + 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, + 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, + 0x650a73548baf63de, + 0x766a0abb3c77b2a8, + 0x81c2c92e47edaee6, + 0x92722c851482353b, + 0xa2bfe8a14cf10364, + 0xa81a664bbc423001, + 0xc24b8b70d0f89791, + 0xc76c51a30654be30, + 0xd192e819d6ef5218, + 0xd69906245565a910, + 0xf40e35855771202a, + 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, + 0x1e376c085141ab53, + 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, + 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, + 0x78a5636f43172f60, + 0x84c87814a1f0ab72, + 0x8cc702081a6439ec, + 0x90befffa23631e28, + 0xa4506cebde82bde9, + 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, + 0xca273eceea26619c, + 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, + 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, + 0x1b710b35131c471b, + 0x28db77f523047d84, + 0x32caab7b40c72493, + 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, + 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, + 0x6c44198c4a475817, +]; + +#[allow(clippy::unreadable_literal)] +const IV: [u64; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +pub fn sha512_block_no_padding( + mut cs: CS, + input: &[Boolean], +) -> Result, SynthesisError> +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + assert_eq!(input.len(), 1024); + + Ok( + sha512_compression_function(&mut cs, input, &get_sha512_iv())? + .into_iter() + .flat_map(|e| e.into_bits_be()) + .collect(), + ) +} + +pub fn sha512(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 u128; + // 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 + 128 is a multiple of 1024 + while (padded.len() + 128) % 1024 != 0 { + padded.push(Boolean::constant(false)); + } + // append L as a 128-bit big-endian integer, making the total post-processed length a multiple of 1024 bits + for b in (0..128).rev().map(|i| (plen >> i) & 1 == 1) { + padded.push(Boolean::constant(b)); + } + assert!(padded.len() % 1024 == 0); + + let mut cur = get_sha512_iv(); + for (i, block) in padded.chunks(1024).enumerate() { + cur = sha512_compression_function(cs.namespace(|| format!("block {}", i)), block, &cur)?; + } + + Ok(cur.into_iter().flat_map(|e| e.into_bits_be()).collect()) +} + +fn get_sha512_iv() -> Vec { + IV.iter().map(|&v| UInt64::constant(v)).collect() +} + +pub fn sha512_compression_function( + cs: CS, + input: &[Boolean], + current_hash_value: &[UInt64], +) -> Result, SynthesisError> +where + Scalar: PrimeField, + CS: ConstraintSystem, +{ + assert_eq!(input.len(), 1024); + assert_eq!(current_hash_value.len(), 8); + + let mut w = input + .chunks(64) + .map(UInt64::from_bits_be) + .collect::>(); + + // We can save some constraints by combining some of + // the constraints in different u64 additions + let mut cs = MultiEq::new(cs); + + for i in 16..80 { + let cs = &mut cs.namespace(|| format!("w extension {}", i)); + + // s0 := (w[i-15] rightrotate 1) xor (w[i-15] rightrotate 8) xor (w[i-15] rightshift 7) + let mut s0 = w[i - 15].rotr(1); + s0 = s0.xor(cs.namespace(|| "first xor for s0"), &w[i - 15].rotr(8))?; + s0 = s0.xor(cs.namespace(|| "second xor for s0"), &w[i - 15].shr(7))?; + + // s1 := (w[i-2] rightrotate 19) xor (w[i-2] rightrotate 61) xor (w[i-2] rightshift 6) + let mut s1 = w[i - 2].rotr(19); + s1 = s1.xor(cs.namespace(|| "first xor for s1"), &w[i - 2].rotr(61))?; + s1 = s1.xor(cs.namespace(|| "second xor for s1"), &w[i - 2].shr(6))?; + + let tmp = UInt64::addmany( + cs.namespace(|| "computation of w[i]"), + &[w[i - 16].clone(), s0, w[i - 7].clone(), s1], + )?; + + // w[i] := w[i-16] + s0 + w[i-7] + s1 + w.push(tmp); + } + + assert_eq!(w.len(), 80); + + enum Maybe { + Deferred(Vec), + Concrete(UInt64), + } + + impl Maybe { + fn compute(self, cs: M, others: &[UInt64]) -> Result + where + Scalar: PrimeField, + CS: ConstraintSystem, + M: ConstraintSystem>, + { + Ok(match self { + Maybe::Concrete(ref v) => return Ok(v.clone()), + Maybe::Deferred(mut v) => { + v.extend(others.iter().cloned()); + UInt64::addmany(cs, &v)? + } + }) + } + } + + let mut a = Maybe::Concrete(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 = Maybe::Concrete(current_hash_value[4].clone()); + let mut f = current_hash_value[5].clone(); + let mut g = current_hash_value[6].clone(); + let mut h = current_hash_value[7].clone(); + + for i in 0..80 { + let cs = &mut cs.namespace(|| format!("compression round {}", i)); + + // S1 := (e rightrotate 14) xor (e rightrotate 18) xor (e rightrotate 41) + let new_e = e.compute(cs.namespace(|| "deferred e computation"), &[])?; + let mut s1 = new_e.rotr(14); + s1 = s1.xor(cs.namespace(|| "first xor for s1"), &new_e.rotr(18))?; + s1 = s1.xor(cs.namespace(|| "second xor for s1"), &new_e.rotr(41))?; + + // ch := (e and f) xor ((not e) and g) + let ch = UInt64::sha512_ch(cs.namespace(|| "ch"), &new_e, &f, &g)?; + + // temp1 := h + S1 + ch + k[i] + w[i] + let temp1 = vec![ + h.clone(), + s1, + ch, + UInt64::constant(ROUND_CONSTANTS[i]), + w[i].clone(), + ]; + + // S0 := (a rightrotate 28) xor (a rightrotate 34) xor (a rightrotate 39) + let new_a = a.compute(cs.namespace(|| "deferred a computation"), &[])?; + let mut s0 = new_a.rotr(28); + s0 = s0.xor(cs.namespace(|| "first xor for s0"), &new_a.rotr(34))?; + s0 = s0.xor(cs.namespace(|| "second xor for s0"), &new_a.rotr(39))?; + + // maj := (a and b) xor (a and c) xor (b and c) + let maj = UInt64::sha512_maj(cs.namespace(|| "maj"), &new_a, &b, &c)?; + + // temp2 := S0 + maj + let temp2 = vec![s0, maj]; + + /* + h := g + g := f + f := e + e := d + temp1 + d := c + c := b + b := a + a := temp1 + temp2 + */ + + h = g; + g = f; + f = new_e; + e = Maybe::Deferred(temp1.iter().cloned().chain(Some(d)).collect::>()); + d = c; + c = b; + b = new_a; + a = Maybe::Deferred( + temp1 + .iter() + .cloned() + .chain(temp2.iter().cloned()) + .collect::>(), + ); + } + + /* + Add the compressed chunk to the current hash value: + h0 := h0 + a + h1 := h1 + b + h2 := h2 + c + h3 := h3 + d + h4 := h4 + e + h5 := h5 + f + h6 := h6 + g + h7 := h7 + h + */ + + let h0 = a.compute( + cs.namespace(|| "deferred h0 computation"), + &[current_hash_value[0].clone()], + )?; + + let h1 = UInt64::addmany( + cs.namespace(|| "new h1"), + &[current_hash_value[1].clone(), b], + )?; + + let h2 = UInt64::addmany( + cs.namespace(|| "new h2"), + &[current_hash_value[2].clone(), c], + )?; + + let h3 = UInt64::addmany( + cs.namespace(|| "new h3"), + &[current_hash_value[3].clone(), d], + )?; + + let h4 = e.compute( + cs.namespace(|| "deferred h4 computation"), + &[current_hash_value[4].clone()], + )?; + + let h5 = UInt64::addmany( + cs.namespace(|| "new h5"), + &[current_hash_value[5].clone(), f], + )?; + + let h6 = UInt64::addmany( + cs.namespace(|| "new h6"), + &[current_hash_value[6].clone(), g], + )?; + + let h7 = UInt64::addmany( + cs.namespace(|| "new h7"), + &[current_hash_value[7].clone(), h], + )?; + + Ok(vec![h0, h1, h2, h3, h4, h5, h6, h7]) +} + +#[cfg(test)] +mod test { + use super::*; + use bellpepper_core::{boolean::AllocatedBit, test_cs::TestConstraintSystem}; + use blstrs::Scalar as Fr; + use rand::Rng; + use rand_core::{RngCore, SeedableRng}; + use rand_xorshift::XorShiftRng; + use sha2::{Digest, Sha512}; + + #[test] + #[allow(clippy::needless_collect)] + fn test_hash() { + let length: u8 = rand::thread_rng().gen(); + let mut h = Sha512::new(); + let data: Vec = (0..length).map(|_| rand::thread_rng().gen()).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() { + input_bits.push( + AllocatedBit::alloc( + cs.namespace(|| format!("input bit {} {}", byte_i, bit_i)), + Some((input_byte >> bit_i) & 1u8 == 1u8), + ) + .unwrap() + .into(), + ); + } + } + + let r = sha512(&mut cs, &input_bits).unwrap(); + + assert!(cs.is_satisfied()); + + let s = hash_result + .iter() + .flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1u8 == 1u8)); + + for (i, j) in r.into_iter().zip(s) { + assert_eq!(i.get_value(), Some(j)); + } + } + + #[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_sha512_iv(); + + let mut cs = TestConstraintSystem::::new(); + let input_bits: Vec<_> = (0..1024) + .map(|i| { + Boolean::from( + AllocatedBit::alloc( + cs.namespace(|| format!("input bit {}", i)), + Some(rng.next_u64() % 2 != 0), + ) + .unwrap(), + ) + }) + .collect(); + + sha512_compression_function(cs.namespace(|| "sha512"), &input_bits, &iv).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(67123, cs.num_constraints()); + } + + #[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..1024) + .map(|i| { + Boolean::from( + AllocatedBit::alloc( + cs.namespace(|| format!("input bit {}", i)), + Some(rng.next_u64() % 2 != 0), + ) + .unwrap(), + ) + }) + .collect(); + + sha512(cs.namespace(|| "sha512"), &input_bits).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(114129, cs.num_constraints()); + } + + #[test] + fn test_against_vectors() { + 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..64).chain((64..512).filter(|a| a % 8 == 0)) { + let mut h = Sha512::new(); + let data: Vec = (0..input_len).map(|_| rng.next_u64() 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 = sha512(&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/bellpepper-sha512/src/uint64.rs b/crates/bellpepper-sha512/src/uint64.rs new file mode 100644 index 0000000..18c4f11 --- /dev/null +++ b/crates/bellpepper-sha512/src/uint64.rs @@ -0,0 +1,783 @@ +//! Circuit representation of a [`u64`], with helpers for the [`sha512`] +//! gadgets. + +use bellpepper::gadgets::multieq::MultiEq; +use bellpepper_core::{ + boolean::{AllocatedBit, Boolean}, + ConstraintSystem, LinearCombination, SynthesisError, +}; +use ff::PrimeField; + +/// Represents an interpretation of 64 `Boolean` objects as an +/// unsigned integer. +#[derive(Clone)] +pub struct UInt64 { + // Least significant bit first + bits: Vec, + value: Option, +} + +impl UInt64 { + /// Construct a constant `UInt64` from a `u64` + pub fn constant(value: u64) -> Self { + let mut bits = Vec::with_capacity(64); + + let mut tmp = value; + for _ in 0..64 { + if tmp & 1 == 1 { + bits.push(Boolean::constant(true)) + } else { + bits.push(Boolean::constant(false)) + } + + tmp >>= 1; + } + + UInt64 { + bits, + value: Some(value), + } + } + + /// Allocate a `UInt64` in the constraint system + pub fn alloc(mut cs: CS, value: Option) -> Result + where + Scalar: PrimeField, + CS: ConstraintSystem, + { + let values = match value { + Some(mut val) => { + let mut v = Vec::with_capacity(64); + + for _ in 0..64 { + v.push(Some(val & 1 == 1)); + val >>= 1; + } + + v + } + None => vec![None; 32], + }; + + let bits = values + .into_iter() + .enumerate() + .map(|(i, v)| { + Ok(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("allocated bit {}", i)), + v, + )?)) + }) + .collect::, SynthesisError>>()?; + + Ok(UInt64 { bits, value }) + } + + pub fn into_bits_be(self) -> Vec { + let mut ret = self.bits; + ret.reverse(); + ret + } + + pub fn from_bits_be(bits: &[Boolean]) -> Self { + assert_eq!(bits.len(), 64); + + let mut value = Some(0u64); + for b in bits { + if let Some(v) = value.as_mut() { + *v <<= 1; + } + + match b.get_value() { + Some(true) => { + if let Some(v) = value.as_mut() { + *v |= 1; + } + } + Some(false) => {} + None => { + value = None; + } + } + } + + UInt64 { + value, + bits: bits.iter().rev().cloned().collect(), + } + } + + /// Turns this `UInt64` into its little-endian byte order representation. + pub fn into_bits(self) -> Vec { + self.bits + } + + /// Converts a little-endian byte order representation of bits into a + /// `UInt64`. + pub fn from_bits(bits: &[Boolean]) -> Self { + assert_eq!(bits.len(), 64); + + let new_bits = bits.to_vec(); + + let mut value = Some(0u64); + for b in new_bits.iter().rev() { + if let Some(v) = value.as_mut() { + *v <<= 1; + } + + match *b { + Boolean::Constant(b) => { + if b { + if let Some(v) = value.as_mut() { + *v |= 1; + } + } + } + Boolean::Is(ref b) => match b.get_value() { + Some(true) => { + if let Some(v) = value.as_mut() { + *v |= 1; + } + } + Some(false) => {} + None => value = None, + }, + Boolean::Not(ref b) => match b.get_value() { + Some(false) => { + if let Some(v) = value.as_mut() { + *v |= 1; + } + } + Some(true) => {} + None => value = None, + }, + } + } + + UInt64 { + value, + bits: new_bits, + } + } + + pub fn rotr(&self, by: usize) -> Self { + let by = by % 64; + + let new_bits = self + .bits + .iter() + .skip(by) + .chain(self.bits.iter()) + .take(64) + .cloned() + .collect(); + + UInt64 { + bits: new_bits, + value: self.value.map(|v| v.rotate_right(by as u32)), + } + } + + pub fn shr(&self, by: usize) -> Self { + let by = by % 64; + + let fill = Boolean::constant(false); + + let new_bits = self + .bits + .iter() // The bits are least significant first + .skip(by) // Skip the bits that will be lost during the shift + .chain(Some(&fill).into_iter().cycle()) // Rest will be zeros + .take(64) // Only 64 bits needed! + .cloned() + .collect(); + + UInt64 { + bits: new_bits, + value: self.value.map(|v| v >> by as u32), + } + } + + fn triop( + mut cs: CS, + a: &Self, + b: &Self, + c: &Self, + tri_fn: F, + circuit_fn: U, + ) -> Result + where + Scalar: PrimeField, + CS: ConstraintSystem, + F: Fn(u64, u64, u64) -> u64, + U: Fn(&mut CS, usize, &Boolean, &Boolean, &Boolean) -> Result, + { + let new_value = match (a.value, b.value, c.value) { + (Some(a), Some(b), Some(c)) => Some(tri_fn(a, b, c)), + _ => None, + }; + + let bits = a + .bits + .iter() + .zip(b.bits.iter()) + .zip(c.bits.iter()) + .enumerate() + .map(|(i, ((a, b), c))| circuit_fn(&mut cs, i, a, b, c)) + .collect::>()?; + + Ok(UInt64 { + bits, + value: new_value, + }) + } + + /// Compute the `maj` value (a and b) xor (a and c) xor (b and c) + /// during SHA512. + pub fn sha512_maj( + cs: CS, + a: &Self, + b: &Self, + c: &Self, + ) -> Result + where + Scalar: PrimeField, + CS: ConstraintSystem, + { + Self::triop( + cs, + a, + b, + c, + |a, b, c| (a & b) ^ (a & c) ^ (b & c), + // Boolean::sha256_maj operates on 3 Booleans and outputs the majority Boolean. + // It will remain same for sha512_maj since it does not take into account UInt32 or UInt64. + |cs, i, a, b, c| Boolean::sha256_maj(cs.namespace(|| format!("maj {}", i)), a, b, c), + ) + } + + /// Compute the `ch` value `(a and b) xor ((not a) and c)` + /// during SHA512. + pub fn sha512_ch( + cs: CS, + a: &Self, + b: &Self, + c: &Self, + ) -> Result + where + Scalar: PrimeField, + CS: ConstraintSystem, + { + Self::triop( + cs, + a, + b, + c, + |a, b, c| (a & b) ^ ((!a) & c), + // Boolean::sha256_ch operates on 3 Booleans and outputs the choice Boolean. + // It will remain same for sha512_ch since it does not take into account UInt32 or UInt64. + |cs, i, a, b, c| Boolean::sha256_ch(cs.namespace(|| format!("ch {}", i)), a, b, c), + ) + } + + /// XOR this `UInt64` with another `UInt64` + pub fn xor(&self, mut cs: CS, other: &Self) -> Result + where + Scalar: PrimeField, + CS: ConstraintSystem, + { + let new_value = match (self.value, other.value) { + (Some(a), Some(b)) => Some(a ^ b), + _ => None, + }; + + let bits = self + .bits + .iter() + .zip(other.bits.iter()) + .enumerate() + .map(|(i, (a, b))| Boolean::xor(cs.namespace(|| format!("xor of bit {}", i)), a, b)) + .collect::>()?; + + Ok(UInt64 { + bits, + value: new_value, + }) + } + + /// Perform modular addition of several `UInt64` objects. + #[allow(clippy::unnecessary_unwrap)] + pub fn addmany(mut cs: M, operands: &[Self]) -> Result + where + Scalar: PrimeField, + CS: ConstraintSystem, + M: ConstraintSystem>, + { + // Make some arbitrary bounds for ourselves to avoid overflows + // in the scalar field + assert!(Scalar::NUM_BITS >= 64); + assert!(operands.len() >= 2); // Weird trivial cases that should never happen + assert!(operands.len() <= 10); + + // Compute the maximum value of the sum so we allocate enough bits for + // the result + let mut max_value = (operands.len() as u128) * (u128::from(u64::max_value())); + + // Keep track of the resulting value + let mut result_value = Some(0u128); + + // This is a linear combination that we will enforce to equal the + // output + let mut lc = LinearCombination::zero(); + + let mut all_constants = true; + + // Iterate over the operands + for op in operands { + // Accumulate the value + match op.value { + Some(val) => { + if let Some(v) = result_value.as_mut() { + *v += u128::from(val); + } + } + None => { + // If any of our operands have unknown value, we won't + // know the value of the result + result_value = None; + } + } + + // Iterate over each bit of the operand and add the operand to + // the linear combination + let mut coeff = Scalar::ONE; + for bit in &op.bits { + lc = lc + &bit.lc(CS::one(), coeff); + + all_constants &= bit.is_constant(); + + coeff = coeff.double(); + } + } + + // The value of the actual result is modulo 2^64 + let modular_value = result_value.map(|v| v as u64); + + if all_constants && modular_value.is_some() { + // We can just return a constant, rather than + // unpacking the result into allocated bits. + + return Ok(UInt64::constant(modular_value.unwrap())); + } + + // Storage area for the resulting bits + let mut result_bits = vec![]; + + // Linear combination representing the output, + // for comparison with the sum of the operands + let mut result_lc = LinearCombination::zero(); + + // Allocate each bit of the result + let mut coeff = Scalar::ONE; + let mut i = 0; + while max_value != 0 { + // Allocate the bit + let b = AllocatedBit::alloc( + cs.namespace(|| format!("result bit {}", i)), + result_value.map(|v| (v >> i) & 1 == 1), + )?; + + // Add this bit to the result combination + result_lc = result_lc + (coeff, b.get_variable()); + + result_bits.push(b.into()); + + max_value >>= 1; + i += 1; + coeff = coeff.double(); + } + + // Enforce equality between the sum and result + cs.get_root().enforce_equal(i, &lc, &result_lc); + + // Discard carry bits that we don't care about + result_bits.truncate(64); + + Ok(UInt64 { + bits: result_bits, + value: modular_value, + }) + } +} + +#[cfg(test)] +mod test { + use super::UInt64; + use bellpepper::gadgets::multieq::MultiEq; + use bellpepper_core::{boolean::Boolean, test_cs::TestConstraintSystem, ConstraintSystem}; + + use blstrs::Scalar as Fr; + use ff::Field; + use rand_core::{RngCore, SeedableRng}; + use rand_xorshift::XorShiftRng; + + #[test] + fn test_uint64_from_bits_be() { + 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 v = (0..64) + .map(|_| Boolean::constant(rng.next_u64() % 2 != 0)) + .collect::>(); + + let b = UInt64::from_bits_be(&v); + + for (i, bit) in b.bits.iter().enumerate() { + match *bit { + Boolean::Constant(bit) => { + assert!(bit == ((b.value.unwrap() >> i) & 1 == 1)); + } + _ => unreachable!(), + } + } + + let expected_to_be_same = b.into_bits_be(); + + for x in v.iter().zip(expected_to_be_same.iter()) { + match x { + (&Boolean::Constant(true), &Boolean::Constant(true)) => {} + (&Boolean::Constant(false), &Boolean::Constant(false)) => {} + _ => unreachable!(), + } + } + } + } + + #[test] + fn test_uint64_from_bits() { + 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 v = (0..64) + .map(|_| Boolean::constant(rng.next_u32() % 2 != 0)) + .collect::>(); + + let b = UInt64::from_bits(&v); + + for (i, bit) in b.bits.iter().enumerate() { + match *bit { + Boolean::Constant(bit) => { + assert!(bit == ((b.value.unwrap() >> i) & 1 == 1)); + } + _ => unreachable!(), + } + } + + let expected_to_be_same = b.into_bits(); + + for x in v.iter().zip(expected_to_be_same.iter()) { + match x { + (&Boolean::Constant(true), &Boolean::Constant(true)) => {} + (&Boolean::Constant(false), &Boolean::Constant(false)) => {} + _ => unreachable!(), + } + } + } + } + + #[test] + fn test_uint64_xor() { + 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_u64(); + let b = rng.next_u64(); + let c = rng.next_u64(); + + let mut expected = a ^ b ^ c; + + let a_bit = UInt64::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt64::constant(b); + let c_bit = UInt64::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap(); + + let r = a_bit.xor(cs.namespace(|| "first xor"), &b_bit).unwrap(); + let r = r.xor(cs.namespace(|| "second xor"), &c_bit).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + 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_uint64_addmany_constants() { + 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_u64(); + let b = rng.next_u64(); + let c = rng.next_u64(); + + let a_bit = UInt64::constant(a); + let b_bit = UInt64::constant(b); + let c_bit = UInt64::constant(c); + + let mut expected = a.wrapping_add(b).wrapping_add(c); + + let r = { + let mut cs = MultiEq::new(&mut cs); + let r = + UInt64::addmany(cs.namespace(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap(); + r + }; + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + match *b { + Boolean::Is(_) => panic!(), + Boolean::Not(_) => panic!(), + Boolean::Constant(b) => { + assert!(b == (expected & 1 == 1)); + } + } + + expected >>= 1; + } + } + } + + #[test] + #[allow(clippy::many_single_char_names)] + fn test_uint64_addmany() { + 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_u64(); + let b = rng.next_u64(); + let c = rng.next_u64(); + let d = rng.next_u64(); + + let mut expected = (a ^ b).wrapping_add(c).wrapping_add(d); + + let a_bit = UInt64::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt64::constant(b); + let c_bit = UInt64::constant(c); + let d_bit = UInt64::alloc(cs.namespace(|| "d_bit"), Some(d)).unwrap(); + + let r = a_bit.xor(cs.namespace(|| "xor"), &b_bit).unwrap(); + let r = { + let mut cs = MultiEq::new(&mut cs); + UInt64::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap() + }; + + assert!(cs.is_satisfied()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + 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(_) => unreachable!(), + } + + expected >>= 1; + } + + // Flip a bit and see if the addition constraint still works + if cs.get("addition/result bit 0/boolean").is_zero().into() { + cs.set("addition/result bit 0/boolean", Field::ONE); + } else { + cs.set("addition/result bit 0/boolean", Field::ZERO); + } + + assert!(!cs.is_satisfied()); + } + } + + #[test] + fn test_uint64_rotr() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, + 0xbc, 0xe5, + ]); + + let mut num = rng.next_u64(); + + let a = UInt64::constant(num); + + for i in 0..64 { + let b = a.rotr(i); + assert_eq!(a.bits.len(), b.bits.len()); + + assert!(b.value.unwrap() == num); + + let mut tmp = num; + for b in &b.bits { + match *b { + Boolean::Constant(b) => { + assert_eq!(b, tmp & 1 == 1); + } + _ => unreachable!(), + } + + tmp >>= 1; + } + + num = num.rotate_right(1); + } + } + + #[test] + fn test_uint64_shr() { + 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..50 { + for i in 0..120 { + let num = rng.next_u64(); + let a = UInt64::constant(num).shr(i); + let b = UInt64::constant(num.wrapping_shr(i as u32)); + + assert_eq!(a.value.unwrap(), b.value.unwrap()); + + assert_eq!(a.bits.len(), b.bits.len()); + for (a, b) in a.bits.iter().zip(b.bits.iter()) { + assert_eq!(a.get_value().unwrap(), b.get_value().unwrap()); + } + } + } + } + + #[test] + fn test_uint64_sha512_maj() { + 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_u64(); + let b = rng.next_u64(); + let c = rng.next_u64(); + + let mut expected = (a & b) ^ (a & c) ^ (b & c); + + let a_bit = UInt64::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt64::constant(b); + let c_bit = UInt64::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap(); + + let r = UInt64::sha512_maj(&mut cs, &a_bit, &b_bit, &c_bit).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + 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_uint64_sha512_ch() { + 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_u64(); + let b = rng.next_u64(); + let c = rng.next_u64(); + + let mut expected = (a & b) ^ ((!a) & c); + + let a_bit = UInt64::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt64::constant(b); + let c_bit = UInt64::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap(); + + let r = UInt64::sha512_ch(&mut cs, &a_bit, &b_bit, &c_bit).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + 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; + } + } + } +}