diff --git a/sapling-crypto/.gitignore b/sapling-crypto/.gitignore new file mode 100644 index 0000000..6aa1064 --- /dev/null +++ b/sapling-crypto/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/sapling-crypto/COPYRIGHT b/sapling-crypto/COPYRIGHT new file mode 100644 index 0000000..f2c6a3b --- /dev/null +++ b/sapling-crypto/COPYRIGHT @@ -0,0 +1,14 @@ +Copyrights in the "sapling-crypto" library are retained by their contributors. No +copyright assignment is required to contribute to the "sapling-crypto" library. + +The "sapling-crypto" library is licensed under either of + + * Apache License, Version 2.0, (see ./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license (see ./LICENSE-MIT or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/sapling-crypto/Cargo.toml b/sapling-crypto/Cargo.toml new file mode 100644 index 0000000..79a5ed7 --- /dev/null +++ b/sapling-crypto/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = ["Sean Bowe "] +description = "Cryptographic library for Zcash Sapling" +documentation = "https://github.com/zcash-hackworks/sapling" +homepage = "https://github.com/zcash-hackworks/sapling" +license = "MIT/Apache-2.0" +name = "sapling-crypto" +repository = "https://github.com/zcash-hackworks/sapling" +version = "0.0.1" + +[dependencies.pairing] +version = "0.14.2" +features = ["expose-arith"] + +[dependencies] +rand = "0.4" +digest = "0.7" +bellman = "0.1" +byteorder = "1" + +[dependencies.blake2-rfc] +git = "https://github.com/gtank/blake2-rfc" +rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9" + +[dev-dependencies] +hex-literal = "0.1" +rust-crypto = "0.2" + +[features] +default = ["u128-support"] +u128-support = ["pairing/u128-support"] diff --git a/sapling-crypto/LICENSE-APACHE b/sapling-crypto/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/sapling-crypto/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/sapling-crypto/LICENSE-MIT b/sapling-crypto/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/sapling-crypto/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/sapling-crypto/README.md b/sapling-crypto/README.md new file mode 100644 index 0000000..f5d3bce --- /dev/null +++ b/sapling-crypto/README.md @@ -0,0 +1,23 @@ +# sapling-crypto + +This repository contains a (work-in-progress) implementation of Zcash's "Sapling" cryptography. + +## Security Warnings + +This library is currently under development and has not been reviewed. + +## 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/sapling-crypto/benches/pedersen_hash.rs b/sapling-crypto/benches/pedersen_hash.rs new file mode 100644 index 0000000..c5968de --- /dev/null +++ b/sapling-crypto/benches/pedersen_hash.rs @@ -0,0 +1,23 @@ +#![feature(test)] + +extern crate rand; +extern crate test; +extern crate pairing; +extern crate sapling_crypto; + +use rand::{Rand, thread_rng}; +use pairing::bls12_381::Bls12; +use sapling_crypto::jubjub::JubjubBls12; +use sapling_crypto::pedersen_hash::{pedersen_hash, Personalization}; + +#[bench] +fn bench_pedersen_hash(b: &mut test::Bencher) { + let params = JubjubBls12::new(); + let rng = &mut thread_rng(); + let bits = (0..510).map(|_| bool::rand(rng)).collect::>(); + let personalization = Personalization::MerkleTree(31); + + b.iter(|| { + pedersen_hash::(personalization, bits.clone(), ¶ms) + }); +} diff --git a/sapling-crypto/examples/bench.rs b/sapling-crypto/examples/bench.rs new file mode 100644 index 0000000..4b7a707 --- /dev/null +++ b/sapling-crypto/examples/bench.rs @@ -0,0 +1,102 @@ +extern crate sapling_crypto; +extern crate bellman; +extern crate rand; +extern crate pairing; + +use std::time::{Duration, Instant}; +use sapling_crypto::jubjub::{ + JubjubBls12, + edwards, + fs, +}; +use sapling_crypto::circuit::sapling::{ + Spend +}; +use sapling_crypto::primitives::{ + Diversifier, + ProofGenerationKey, + ValueCommitment +}; +use bellman::groth16::*; +use rand::{XorShiftRng, SeedableRng, Rng}; +use pairing::bls12_381::{Bls12, Fr}; + +const TREE_DEPTH: usize = 32; + +fn main() { + let jubjub_params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + println!("Creating sample parameters..."); + let groth_params = generate_random_parameters::( + Spend { + params: jubjub_params, + value_commitment: None, + proof_generation_key: None, + payment_address: None, + commitment_randomness: None, + ar: None, + auth_path: vec![None; TREE_DEPTH], + anchor: None + }, + rng + ).unwrap(); + + const SAMPLES: u32 = 50; + + let mut total_time = Duration::new(0, 0); + for _ in 0..SAMPLES { + let value_commitment = ValueCommitment { + value: 1, + randomness: rng.gen() + }; + + let nsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, jubjub_params).mul_by_cofactor(jubjub_params); + + let proof_generation_key = ProofGenerationKey { + ak: ak.clone(), + nsk: nsk.clone() + }; + + let viewing_key = proof_generation_key.into_viewing_key(jubjub_params); + + let payment_address; + + loop { + let diversifier = Diversifier(rng.gen()); + + if let Some(p) = viewing_key.into_payment_address( + diversifier, + jubjub_params + ) + { + payment_address = p; + break; + } + } + + let commitment_randomness: fs::Fs = rng.gen(); + let auth_path = vec![Some((rng.gen(), rng.gen())); TREE_DEPTH]; + let ar: fs::Fs = rng.gen(); + let anchor: Fr = rng.gen(); + + let start = Instant::now(); + let _ = create_random_proof(Spend { + params: jubjub_params, + value_commitment: Some(value_commitment), + proof_generation_key: Some(proof_generation_key), + payment_address: Some(payment_address), + commitment_randomness: Some(commitment_randomness), + ar: Some(ar), + auth_path: auth_path, + anchor: Some(anchor) + }, &groth_params, rng).unwrap(); + total_time += start.elapsed(); + } + let avg = total_time / SAMPLES; + let avg = avg.subsec_nanos() as f64 / 1_000_000_000f64 + + (avg.as_secs() as f64); + + println!("Average proving time (in seconds): {}", avg); +} diff --git a/sapling-crypto/src/circuit/blake2s.rs b/sapling-crypto/src/circuit/blake2s.rs new file mode 100644 index 0000000..93af806 --- /dev/null +++ b/sapling-crypto/src/circuit/blake2s.rs @@ -0,0 +1,438 @@ +use pairing::{ + Engine, +}; + +use bellman::{ + SynthesisError, + ConstraintSystem +}; + +use super::boolean::{ + Boolean +}; + +use super::uint32::{ + UInt32 +}; + +use super::multieq::MultiEq; + +/* +2.1. Parameters + The following table summarizes various parameters and their ranges: + | BLAKE2b | BLAKE2s | + --------------+------------------+------------------+ + Bits in word | w = 64 | w = 32 | + Rounds in F | r = 12 | r = 10 | + Block bytes | bb = 128 | bb = 64 | + Hash bytes | 1 <= nn <= 64 | 1 <= nn <= 32 | + Key bytes | 0 <= kk <= 64 | 0 <= kk <= 32 | + Input bytes | 0 <= ll < 2**128 | 0 <= ll < 2**64 | + --------------+------------------+------------------+ + G Rotation | (R1, R2, R3, R4) | (R1, R2, R3, R4) | + constants = | (32, 24, 16, 63) | (16, 12, 8, 7) | + --------------+------------------+------------------+ +*/ + +const R1: usize = 16; +const R2: usize = 12; +const R3: usize = 8; +const R4: usize = 7; + +/* + Round | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | + ----------+-------------------------------------------------+ + SIGMA[0] | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | + SIGMA[1] | 14 10 4 8 9 15 13 6 1 12 0 2 11 7 5 3 | + SIGMA[2] | 11 8 12 0 5 2 15 13 10 14 3 6 7 1 9 4 | + SIGMA[3] | 7 9 3 1 13 12 11 14 2 6 5 10 4 0 15 8 | + SIGMA[4] | 9 0 5 7 2 4 10 15 14 1 11 12 6 8 3 13 | + SIGMA[5] | 2 12 6 10 0 11 8 3 4 13 7 5 15 14 1 9 | + SIGMA[6] | 12 5 1 15 14 13 4 10 0 7 6 3 9 2 8 11 | + SIGMA[7] | 13 11 7 14 12 1 3 9 5 0 15 4 8 6 2 10 | + SIGMA[8] | 6 15 14 9 11 3 0 8 12 2 13 7 1 4 10 5 | + SIGMA[9] | 10 2 8 4 7 6 1 5 15 11 9 14 3 12 13 0 | + ----------+-------------------------------------------------+ +*/ + +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0] +]; + +/* +3.1. Mixing Function G + The G primitive function mixes two input words, "x" and "y", into + four words indexed by "a", "b", "c", and "d" in the working vector + v[0..15]. The full modified vector is returned. The rotation + constants (R1, R2, R3, R4) are given in Section 2.1. + FUNCTION G( v[0..15], a, b, c, d, x, y ) + | + | v[a] := (v[a] + v[b] + x) mod 2**w + | v[d] := (v[d] ^ v[a]) >>> R1 + | v[c] := (v[c] + v[d]) mod 2**w + | v[b] := (v[b] ^ v[c]) >>> R2 + | v[a] := (v[a] + v[b] + y) mod 2**w + | v[d] := (v[d] ^ v[a]) >>> R3 + | v[c] := (v[c] + v[d]) mod 2**w + | v[b] := (v[b] ^ v[c]) >>> R4 + | + | RETURN v[0..15] + | + END FUNCTION. +*/ + +fn mixing_g, M>( + mut cs: M, + v: &mut [UInt32], + a: usize, + b: usize, + c: usize, + d: usize, + x: &UInt32, + y: &UInt32 +) -> Result<(), SynthesisError> + where M: ConstraintSystem> +{ + v[a] = UInt32::addmany(cs.namespace(|| "mixing step 1"), &[v[a].clone(), v[b].clone(), x.clone()])?; + v[d] = v[d].xor(cs.namespace(|| "mixing step 2"), &v[a])?.rotr(R1); + v[c] = UInt32::addmany(cs.namespace(|| "mixing step 3"), &[v[c].clone(), v[d].clone()])?; + v[b] = v[b].xor(cs.namespace(|| "mixing step 4"), &v[c])?.rotr(R2); + v[a] = UInt32::addmany(cs.namespace(|| "mixing step 5"), &[v[a].clone(), v[b].clone(), y.clone()])?; + v[d] = v[d].xor(cs.namespace(|| "mixing step 6"), &v[a])?.rotr(R3); + v[c] = UInt32::addmany(cs.namespace(|| "mixing step 7"), &[v[c].clone(), v[d].clone()])?; + v[b] = v[b].xor(cs.namespace(|| "mixing step 8"), &v[c])?.rotr(R4); + + Ok(()) +} + +/* +3.2. Compression Function F + Compression function F takes as an argument the state vector "h", + message block vector "m" (last block is padded with zeros to full + block size, if required), 2w-bit offset counter "t", and final block + indicator flag "f". Local vector v[0..15] is used in processing. F + returns a new state vector. The number of rounds, "r", is 12 for + BLAKE2b and 10 for BLAKE2s. Rounds are numbered from 0 to r - 1. + FUNCTION F( h[0..7], m[0..15], t, f ) + | + | // Initialize local work vector v[0..15] + | v[0..7] := h[0..7] // First half from state. + | v[8..15] := IV[0..7] // Second half from IV. + | + | v[12] := v[12] ^ (t mod 2**w) // Low word of the offset. + | v[13] := v[13] ^ (t >> w) // High word. + | + | IF f = TRUE THEN // last block flag? + | | v[14] := v[14] ^ 0xFF..FF // Invert all bits. + | END IF. + | + | // Cryptographic mixing + | FOR i = 0 TO r - 1 DO // Ten or twelve rounds. + | | + | | // Message word selection permutation for this round. + | | s[0..15] := SIGMA[i mod 10][0..15] + | | + | | v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] ) + | | v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] ) + | | v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] ) + | | v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] ) + | | + | | v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] ) + | | v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] ) + | | v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] ) + | | v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] ) + | | + | END FOR + | + | FOR i = 0 TO 7 DO // XOR the two halves. + | | h[i] := h[i] ^ v[i] ^ v[i + 8] + | END FOR. + | + | RETURN h[0..7] // New state. + | + END FUNCTION. +*/ + + +fn blake2s_compression>( + mut cs: CS, + h: &mut [UInt32], + m: &[UInt32], + t: u64, + f: bool +) -> Result<(), SynthesisError> +{ + assert_eq!(h.len(), 8); + assert_eq!(m.len(), 16); + + /* + static const uint32_t blake2s_iv[8] = + { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 + }; + */ + + let mut v = Vec::with_capacity(16); + v.extend_from_slice(h); + v.push(UInt32::constant(0x6A09E667)); + v.push(UInt32::constant(0xBB67AE85)); + v.push(UInt32::constant(0x3C6EF372)); + v.push(UInt32::constant(0xA54FF53A)); + v.push(UInt32::constant(0x510E527F)); + v.push(UInt32::constant(0x9B05688C)); + v.push(UInt32::constant(0x1F83D9AB)); + v.push(UInt32::constant(0x5BE0CD19)); + + assert_eq!(v.len(), 16); + + v[12] = v[12].xor(cs.namespace(|| "first xor"), &UInt32::constant(t as u32))?; + v[13] = v[13].xor(cs.namespace(|| "second xor"), &UInt32::constant((t >> 32) as u32))?; + + if f { + v[14] = v[14].xor(cs.namespace(|| "third xor"), &UInt32::constant(u32::max_value()))?; + } + + { + let mut cs = MultiEq::new(&mut cs); + + for i in 0..10 { + let mut cs = cs.namespace(|| format!("round {}", i)); + + let s = SIGMA[i % 10]; + + mixing_g(cs.namespace(|| "mixing invocation 1"), &mut v, 0, 4, 8, 12, &m[s[ 0]], &m[s[ 1]])?; + mixing_g(cs.namespace(|| "mixing invocation 2"), &mut v, 1, 5, 9, 13, &m[s[ 2]], &m[s[ 3]])?; + mixing_g(cs.namespace(|| "mixing invocation 3"), &mut v, 2, 6, 10, 14, &m[s[ 4]], &m[s[ 5]])?; + mixing_g(cs.namespace(|| "mixing invocation 4"), &mut v, 3, 7, 11, 15, &m[s[ 6]], &m[s[ 7]])?; + + mixing_g(cs.namespace(|| "mixing invocation 5"), &mut v, 0, 5, 10, 15, &m[s[ 8]], &m[s[ 9]])?; + mixing_g(cs.namespace(|| "mixing invocation 6"), &mut v, 1, 6, 11, 12, &m[s[10]], &m[s[11]])?; + mixing_g(cs.namespace(|| "mixing invocation 7"), &mut v, 2, 7, 8, 13, &m[s[12]], &m[s[13]])?; + mixing_g(cs.namespace(|| "mixing invocation 8"), &mut v, 3, 4, 9, 14, &m[s[14]], &m[s[15]])?; + } + } + + for i in 0..8 { + let mut cs = cs.namespace(|| format!("h[{i}] ^ v[{i}] ^ v[{i} + 8]", i=i)); + + h[i] = h[i].xor(cs.namespace(|| "first xor"), &v[i])?; + h[i] = h[i].xor(cs.namespace(|| "second xor"), &v[i + 8])?; + } + + Ok(()) +} + +/* + FUNCTION BLAKE2( d[0..dd-1], ll, kk, nn ) + | + | h[0..7] := IV[0..7] // Initialization Vector. + | + | // Parameter block p[0] + | h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn + | + | // Process padded key and data blocks + | IF dd > 1 THEN + | | FOR i = 0 TO dd - 2 DO + | | | h := F( h, d[i], (i + 1) * bb, FALSE ) + | | END FOR. + | END IF. + | + | // Final block. + | IF kk = 0 THEN + | | h := F( h, d[dd - 1], ll, TRUE ) + | ELSE + | | h := F( h, d[dd - 1], ll + bb, TRUE ) + | END IF. + | + | RETURN first "nn" bytes from little-endian word array h[]. + | + END FUNCTION. +*/ + +pub fn blake2s>( + mut cs: CS, + input: &[Boolean], + personalization: &[u8] +) -> Result, SynthesisError> +{ + use byteorder::{ByteOrder, LittleEndian}; + + assert_eq!(personalization.len(), 8); + assert!(input.len() % 8 == 0); + + let mut h = Vec::with_capacity(8); + h.push(UInt32::constant(0x6A09E667 ^ 0x01010000 ^ 32)); + h.push(UInt32::constant(0xBB67AE85)); + h.push(UInt32::constant(0x3C6EF372)); + h.push(UInt32::constant(0xA54FF53A)); + h.push(UInt32::constant(0x510E527F)); + h.push(UInt32::constant(0x9B05688C)); + + // Personalization is stored here + h.push(UInt32::constant(0x1F83D9AB ^ LittleEndian::read_u32(&personalization[0..4]))); + h.push(UInt32::constant(0x5BE0CD19 ^ LittleEndian::read_u32(&personalization[4..8]))); + + let mut blocks: Vec> = vec![]; + + for block in input.chunks(512) { + let mut this_block = Vec::with_capacity(16); + for word in block.chunks(32) { + let mut tmp = word.to_vec(); + while tmp.len() < 32 { + tmp.push(Boolean::constant(false)); + } + this_block.push(UInt32::from_bits(&tmp)); + } + while this_block.len() < 16 { + this_block.push(UInt32::constant(0)); + } + blocks.push(this_block); + } + + if blocks.len() == 0 { + blocks.push((0..16).map(|_| UInt32::constant(0)).collect()); + } + + for (i, block) in blocks[0..blocks.len() - 1].iter().enumerate() { + let cs = cs.namespace(|| format!("block {}", i)); + + blake2s_compression(cs, &mut h, block, ((i as u64) + 1) * 64, false)?; + } + + { + let cs = cs.namespace(|| "final block"); + + blake2s_compression(cs, &mut h, &blocks[blocks.len() - 1], (input.len() / 8) as u64, true)?; + } + + Ok(h.iter().flat_map(|b| b.into_bits()).collect()) +} + +#[cfg(test)] +mod test { + use rand::{XorShiftRng, SeedableRng, Rng}; + use pairing::bls12_381::{Bls12}; + use ::circuit::boolean::{Boolean, AllocatedBit}; + use ::circuit::test::TestConstraintSystem; + use super::blake2s; + use bellman::{ConstraintSystem}; + use blake2_rfc::blake2s::Blake2s; + + #[test] + fn test_blank_hash() { + let mut cs = TestConstraintSystem::::new(); + let input_bits = vec![]; + let out = blake2s(&mut cs, &input_bits, b"12345678").unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 0); + + // >>> import blake2s from hashlib + // >>> h = blake2s(digest_size=32, person=b'12345678') + // >>> h.hexdigest() + let expected = hex!("c59f682376d137f3f255e671e207d1f2374ebe504e9314208a52d9f88d69e8c8"); + + let mut out = out.into_iter(); + for b in expected.into_iter() { + for i in 0..8 { + let c = out.next().unwrap().get_value().unwrap(); + + assert_eq!(c, (b >> i) & 1u8 == 1u8); + } + } + } + + #[test] + fn test_blake2s_constraints() { + let mut cs = TestConstraintSystem::::new(); + let input_bits: Vec<_> = (0..512).map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into()).collect(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 21518); + } + + #[test] + fn test_blake2s_precomp_constraints() { + // Test that 512 fixed leading bits (constants) + // doesn't result in more constraints. + + let mut cs = TestConstraintSystem::::new(); + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let input_bits: Vec<_> = (0..512) + .map(|_| Boolean::constant(rng.gen())) + .chain((0..512) + .map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into())) + .collect(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 21518); + } + + #[test] + fn test_blake2s_constant_constraints() { + let mut cs = TestConstraintSystem::::new(); + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let input_bits: Vec<_> = (0..512).map(|_| Boolean::constant(rng.gen())).collect(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); + assert_eq!(cs.num_constraints(), 0); + } + + #[test] + fn test_blake2s() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) + { + let mut h = Blake2s::with_params(32, &[], &[], b"12345678"); + + let data: Vec = (0..input_len).map(|_| 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 { + 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 = blake2s(&mut cs, &input_bits, b"12345678").unwrap(); + + assert!(cs.is_satisfied()); + + let mut s = hash_result.as_ref().iter() + .flat_map(|&byte| (0..8).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/sapling-crypto/src/circuit/boolean.rs b/sapling-crypto/src/circuit/boolean.rs new file mode 100644 index 0000000..08f407e --- /dev/null +++ b/sapling-crypto/src/circuit/boolean.rs @@ -0,0 +1,1582 @@ +use pairing::{ + Engine, + Field, + PrimeField, + BitIterator +}; + +use bellman::{ + ConstraintSystem, + SynthesisError, + LinearCombination, + Variable +}; + +use super::{ + Assignment +}; + +/// Represents a variable in the constraint system which is guaranteed +/// to be either zero or one. +#[derive(Clone)] +pub struct AllocatedBit { + variable: Variable, + value: Option +} + +impl AllocatedBit { + pub fn get_value(&self) -> Option { + self.value + } + + pub fn get_variable(&self) -> Variable { + self.variable + } + + /// Allocate a variable in the constraint system which can only be a + /// boolean value. Further, constrain that the boolean is false + /// unless the condition is false. + pub fn alloc_conditionally( + mut cs: CS, + value: Option, + must_be_false: &AllocatedBit + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let var = cs.alloc(|| "boolean", || { + if *value.get()? { + Ok(E::Fr::one()) + } else { + Ok(E::Fr::zero()) + } + })?; + + // Constrain: (1 - must_be_false - a) * a = 0 + // if must_be_false is true, the equation + // reduces to -a * a = 0, which implies a = 0. + // if must_be_false is false, the equation + // reduces to (1 - a) * a = 0, which is a + // traditional boolean constraint. + cs.enforce( + || "boolean constraint", + |lc| lc + CS::one() - must_be_false.variable - var, + |lc| lc + var, + |lc| lc + ); + + Ok(AllocatedBit { + variable: var, + value: value + }) + } + + /// Allocate a variable in the constraint system which can only be a + /// boolean value. + pub fn alloc( + mut cs: CS, + value: Option, + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let var = cs.alloc(|| "boolean", || { + if *value.get()? { + Ok(E::Fr::one()) + } else { + Ok(E::Fr::zero()) + } + })?; + + // Constrain: (1 - a) * a = 0 + // This constrains a to be either 0 or 1. + cs.enforce( + || "boolean constraint", + |lc| lc + CS::one() - var, + |lc| lc + var, + |lc| lc + ); + + Ok(AllocatedBit { + variable: var, + value: value + }) + } + + /// Performs an XOR operation over the two operands, returning + /// an `AllocatedBit`. + pub fn xor( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let mut result_value = None; + + let result_var = cs.alloc(|| "xor result", || { + if *a.value.get()? ^ *b.value.get()? { + result_value = Some(true); + + Ok(E::Fr::one()) + } else { + result_value = Some(false); + + Ok(E::Fr::zero()) + } + })?; + + // Constrain (a + a) * (b) = (a + b - c) + // Given that a and b are boolean constrained, if they + // are equal, the only solution for c is 0, and if they + // are different, the only solution for c is 1. + // + // ¬(a ∧ b) ∧ ¬(¬a ∧ ¬b) = c + // (1 - (a * b)) * (1 - ((1 - a) * (1 - b))) = c + // (1 - ab) * (1 - (1 - a - b + ab)) = c + // (1 - ab) * (a + b - ab) = c + // a + b - ab - (a^2)b - (b^2)a + (a^2)(b^2) = c + // a + b - ab - ab - ab + ab = c + // a + b - 2ab = c + // -2a * b = c - a - b + // 2a * b = a + b - c + // (a + a) * b = a + b - c + cs.enforce( + || "xor constraint", + |lc| lc + a.variable + a.variable, + |lc| lc + b.variable, + |lc| lc + a.variable + b.variable - result_var + ); + + Ok(AllocatedBit { + variable: result_var, + value: result_value + }) + } + + /// Performs an AND operation over the two operands, returning + /// an `AllocatedBit`. + pub fn and( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let mut result_value = None; + + let result_var = cs.alloc(|| "and result", || { + if *a.value.get()? & *b.value.get()? { + result_value = Some(true); + + Ok(E::Fr::one()) + } else { + result_value = Some(false); + + Ok(E::Fr::zero()) + } + })?; + + // Constrain (a) * (b) = (c), ensuring c is 1 iff + // a AND b are both 1. + cs.enforce( + || "and constraint", + |lc| lc + a.variable, + |lc| lc + b.variable, + |lc| lc + result_var + ); + + Ok(AllocatedBit { + variable: result_var, + value: result_value + }) + } + + /// Calculates `a AND (NOT b)`. + pub fn and_not( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let mut result_value = None; + + let result_var = cs.alloc(|| "and not result", || { + if *a.value.get()? & !*b.value.get()? { + result_value = Some(true); + + Ok(E::Fr::one()) + } else { + result_value = Some(false); + + Ok(E::Fr::zero()) + } + })?; + + // Constrain (a) * (1 - b) = (c), ensuring c is 1 iff + // a is true and b is false, and otherwise c is 0. + cs.enforce( + || "and not constraint", + |lc| lc + a.variable, + |lc| lc + CS::one() - b.variable, + |lc| lc + result_var + ); + + Ok(AllocatedBit { + variable: result_var, + value: result_value + }) + } + + /// Calculates `(NOT a) AND (NOT b)`. + pub fn nor( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let mut result_value = None; + + let result_var = cs.alloc(|| "nor result", || { + if !*a.value.get()? & !*b.value.get()? { + result_value = Some(true); + + Ok(E::Fr::one()) + } else { + result_value = Some(false); + + Ok(E::Fr::zero()) + } + })?; + + // Constrain (1 - a) * (1 - b) = (c), ensuring c is 1 iff + // a and b are both false, and otherwise c is 0. + cs.enforce( + || "nor constraint", + |lc| lc + CS::one() - a.variable, + |lc| lc + CS::one() - b.variable, + |lc| lc + result_var + ); + + Ok(AllocatedBit { + variable: result_var, + value: result_value + }) + } +} + +pub fn u64_into_boolean_vec_le>( + mut cs: CS, + value: Option +) -> Result, SynthesisError> +{ + let values = match value { + Some(ref value) => { + let mut tmp = Vec::with_capacity(64); + + for i in 0..64 { + tmp.push(Some(*value >> i & 1 == 1)); + } + + tmp + }, + None => { + vec![None; 64] + } + }; + + let bits = values.into_iter().enumerate().map(|(i, b)| { + Ok(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + b + )?)) + }).collect::, SynthesisError>>()?; + + Ok(bits) +} + +pub fn field_into_boolean_vec_le, F: PrimeField>( + cs: CS, + value: Option +) -> Result, SynthesisError> +{ + let v = field_into_allocated_bits_le::(cs, value)?; + + Ok(v.into_iter().map(|e| Boolean::from(e)).collect()) +} + +pub fn field_into_allocated_bits_le, F: PrimeField>( + mut cs: CS, + value: Option +) -> Result, SynthesisError> +{ + // Deconstruct in big-endian bit order + let values = match value { + Some(ref value) => { + let mut field_char = BitIterator::new(F::char()); + + let mut tmp = Vec::with_capacity(F::NUM_BITS as usize); + + let mut found_one = false; + for b in BitIterator::new(value.into_repr()) { + // Skip leading bits + found_one |= field_char.next().unwrap(); + if !found_one { + continue; + } + + tmp.push(Some(b)); + } + + assert_eq!(tmp.len(), F::NUM_BITS as usize); + + tmp + }, + None => { + vec![None; F::NUM_BITS as usize] + } + }; + + // Allocate in little-endian order + let bits = values.into_iter().rev().enumerate().map(|(i, b)| { + AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + b + ) + }).collect::, SynthesisError>>()?; + + Ok(bits) +} + +/// This is a boolean value which may be either a constant or +/// an interpretation of an `AllocatedBit`. +#[derive(Clone)] +pub enum Boolean { + /// Existential view of the boolean variable + Is(AllocatedBit), + /// Negated view of the boolean variable + Not(AllocatedBit), + /// Constant (not an allocated variable) + Constant(bool) +} + +impl Boolean { + pub fn is_constant(&self) -> bool { + match *self { + Boolean::Constant(_) => true, + _ => false + } + } + + pub fn enforce_equal( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result<(), SynthesisError> + where E: Engine, + CS: ConstraintSystem + { + match (a, b) { + (&Boolean::Constant(a), &Boolean::Constant(b)) => { + if a == b { + Ok(()) + } else { + Err(SynthesisError::Unsatisfiable) + } + }, + (&Boolean::Constant(true), a) | (a, &Boolean::Constant(true)) => { + cs.enforce( + || "enforce equal to one", + |lc| lc, + |lc| lc, + |lc| lc + CS::one() - &a.lc(CS::one(), E::Fr::one()) + ); + + Ok(()) + }, + (&Boolean::Constant(false), a) | (a, &Boolean::Constant(false)) => { + cs.enforce( + || "enforce equal to zero", + |lc| lc, + |lc| lc, + |_| a.lc(CS::one(), E::Fr::one()) + ); + + Ok(()) + }, + (a, b) => { + cs.enforce( + || "enforce equal", + |lc| lc, + |lc| lc, + |_| a.lc(CS::one(), E::Fr::one()) - &b.lc(CS::one(), E::Fr::one()) + ); + + Ok(()) + } + } + } + + pub fn get_value(&self) -> Option { + match self { + &Boolean::Constant(c) => Some(c), + &Boolean::Is(ref v) => v.get_value(), + &Boolean::Not(ref v) => v.get_value().map(|b| !b) + } + } + + pub fn lc( + &self, + one: Variable, + coeff: E::Fr + ) -> LinearCombination + { + match self { + &Boolean::Constant(c) => { + if c { + LinearCombination::::zero() + (coeff, one) + } else { + LinearCombination::::zero() + } + }, + &Boolean::Is(ref v) => { + LinearCombination::::zero() + (coeff, v.get_variable()) + }, + &Boolean::Not(ref v) => { + LinearCombination::::zero() + (coeff, one) - (coeff, v.get_variable()) + } + } + } + + /// Construct a boolean from a known constant + pub fn constant(b: bool) -> Self { + Boolean::Constant(b) + } + + /// Return a negated interpretation of this boolean. + pub fn not(&self) -> Self { + match self { + &Boolean::Constant(c) => Boolean::Constant(!c), + &Boolean::Is(ref v) => Boolean::Not(v.clone()), + &Boolean::Not(ref v) => Boolean::Is(v.clone()) + } + } + + /// Perform XOR over two boolean operands + pub fn xor<'a, E, CS>( + cs: CS, + a: &'a Self, + b: &'a Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + match (a, b) { + (&Boolean::Constant(false), x) | (x, &Boolean::Constant(false)) => Ok(x.clone()), + (&Boolean::Constant(true), x) | (x, &Boolean::Constant(true)) => Ok(x.not()), + // a XOR (NOT b) = NOT(a XOR b) + (is @ &Boolean::Is(_), not @ &Boolean::Not(_)) | (not @ &Boolean::Not(_), is @ &Boolean::Is(_)) => { + Ok(Boolean::xor( + cs, + is, + ¬.not() + )?.not()) + }, + // a XOR b = (NOT a) XOR (NOT b) + (&Boolean::Is(ref a), &Boolean::Is(ref b)) | (&Boolean::Not(ref a), &Boolean::Not(ref b)) => { + Ok(Boolean::Is(AllocatedBit::xor(cs, a, b)?)) + } + } + } + + /// Perform AND over two boolean operands + pub fn and<'a, E, CS>( + cs: CS, + a: &'a Self, + b: &'a Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + match (a, b) { + // false AND x is always false + (&Boolean::Constant(false), _) | (_, &Boolean::Constant(false)) => Ok(Boolean::Constant(false)), + // true AND x is always x + (&Boolean::Constant(true), x) | (x, &Boolean::Constant(true)) => Ok(x.clone()), + // a AND (NOT b) + (&Boolean::Is(ref is), &Boolean::Not(ref not)) | (&Boolean::Not(ref not), &Boolean::Is(ref is)) => { + Ok(Boolean::Is(AllocatedBit::and_not(cs, is, not)?)) + }, + // (NOT a) AND (NOT b) = a NOR b + (&Boolean::Not(ref a), &Boolean::Not(ref b)) => { + Ok(Boolean::Is(AllocatedBit::nor(cs, a, b)?)) + }, + // a AND b + (&Boolean::Is(ref a), &Boolean::Is(ref b)) => { + Ok(Boolean::Is(AllocatedBit::and(cs, a, b)?)) + } + } + } + + /// Computes (a and b) xor ((not a) and c) + pub fn sha256_ch<'a, E, CS>( + mut cs: CS, + a: &'a Self, + b: &'a Self, + c: &'a Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let ch_value = match (a.get_value(), b.get_value(), c.get_value()) { + (Some(a), Some(b), Some(c)) => { + // (a and b) xor ((not a) and c) + Some((a & b) ^ ((!a) & c)) + }, + _ => None + }; + + match (a, b, c) { + (&Boolean::Constant(_), + &Boolean::Constant(_), + &Boolean::Constant(_)) => { + // They're all constants, so we can just compute the value. + + return Ok(Boolean::Constant(ch_value.expect("they're all constants"))); + }, + (&Boolean::Constant(false), _, c) => { + // If a is false + // (a and b) xor ((not a) and c) + // equals + // (false) xor (c) + // equals + // c + return Ok(c.clone()); + }, + (a, &Boolean::Constant(false), c) => { + // If b is false + // (a and b) xor ((not a) and c) + // equals + // ((not a) and c) + return Boolean::and( + cs, + &a.not(), + &c + ); + }, + (a, b, &Boolean::Constant(false)) => { + // If c is false + // (a and b) xor ((not a) and c) + // equals + // (a and b) + return Boolean::and( + cs, + &a, + &b + ); + }, + (a, b, &Boolean::Constant(true)) => { + // If c is true + // (a and b) xor ((not a) and c) + // equals + // (a and b) xor (not a) + // equals + // not (a and (not b)) + return Ok(Boolean::and( + cs, + &a, + &b.not() + )?.not()); + }, + (a, &Boolean::Constant(true), c) => { + // If b is true + // (a and b) xor ((not a) and c) + // equals + // a xor ((not a) and c) + // equals + // not ((not a) and (not c)) + return Ok(Boolean::and( + cs, + &a.not(), + &c.not() + )?.not()); + }, + (&Boolean::Constant(true), _, _) => { + // If a is true + // (a and b) xor ((not a) and c) + // equals + // b xor ((not a) and c) + // So we just continue! + }, + (&Boolean::Is(_), &Boolean::Is(_), &Boolean::Is(_)) | + (&Boolean::Is(_), &Boolean::Is(_), &Boolean::Not(_)) | + (&Boolean::Is(_), &Boolean::Not(_), &Boolean::Is(_)) | + (&Boolean::Is(_), &Boolean::Not(_), &Boolean::Not(_)) | + (&Boolean::Not(_), &Boolean::Is(_), &Boolean::Is(_)) | + (&Boolean::Not(_), &Boolean::Is(_), &Boolean::Not(_)) | + (&Boolean::Not(_), &Boolean::Not(_), &Boolean::Is(_)) | + (&Boolean::Not(_), &Boolean::Not(_), &Boolean::Not(_)) + => {} + } + + let ch = cs.alloc(|| "ch", || { + ch_value.get().map(|v| { + if *v { + E::Fr::one() + } else { + E::Fr::zero() + } + }) + })?; + + // a(b - c) = ch - c + cs.enforce( + || "ch computation", + |_| b.lc(CS::one(), E::Fr::one()) + - &c.lc(CS::one(), E::Fr::one()), + |_| a.lc(CS::one(), E::Fr::one()), + |lc| lc + ch - &c.lc(CS::one(), E::Fr::one()) + ); + + Ok(AllocatedBit { + value: ch_value, + variable: ch + }.into()) + } + + /// Computes (a and b) xor (a and c) xor (b and c) + pub fn sha256_maj<'a, E, CS>( + mut cs: CS, + a: &'a Self, + b: &'a Self, + c: &'a Self, + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let maj_value = match (a.get_value(), b.get_value(), c.get_value()) { + (Some(a), Some(b), Some(c)) => { + // (a and b) xor (a and c) xor (b and c) + Some((a & b) ^ (a & c) ^ (b & c)) + }, + _ => None + }; + + match (a, b, c) { + (&Boolean::Constant(_), + &Boolean::Constant(_), + &Boolean::Constant(_)) => { + // They're all constants, so we can just compute the value. + + return Ok(Boolean::Constant(maj_value.expect("they're all constants"))); + }, + (&Boolean::Constant(false), b, c) => { + // If a is false, + // (a and b) xor (a and c) xor (b and c) + // equals + // (b and c) + return Boolean::and( + cs, + b, + c + ); + }, + (a, &Boolean::Constant(false), c) => { + // If b is false, + // (a and b) xor (a and c) xor (b and c) + // equals + // (a and c) + return Boolean::and( + cs, + a, + c + ); + }, + (a, b, &Boolean::Constant(false)) => { + // If c is false, + // (a and b) xor (a and c) xor (b and c) + // equals + // (a and b) + return Boolean::and( + cs, + a, + b + ); + }, + (a, b, &Boolean::Constant(true)) => { + // If c is true, + // (a and b) xor (a and c) xor (b and c) + // equals + // (a and b) xor (a) xor (b) + // equals + // not ((not a) and (not b)) + return Ok(Boolean::and( + cs, + &a.not(), + &b.not() + )?.not()); + }, + (a, &Boolean::Constant(true), c) => { + // If b is true, + // (a and b) xor (a and c) xor (b and c) + // equals + // (a) xor (a and c) xor (c) + return Ok(Boolean::and( + cs, + &a.not(), + &c.not() + )?.not()); + }, + (&Boolean::Constant(true), b, c) => { + // If a is true, + // (a and b) xor (a and c) xor (b and c) + // equals + // (b) xor (c) xor (b and c) + return Ok(Boolean::and( + cs, + &b.not(), + &c.not() + )?.not()); + }, + (&Boolean::Is(_), &Boolean::Is(_), &Boolean::Is(_)) | + (&Boolean::Is(_), &Boolean::Is(_), &Boolean::Not(_)) | + (&Boolean::Is(_), &Boolean::Not(_), &Boolean::Is(_)) | + (&Boolean::Is(_), &Boolean::Not(_), &Boolean::Not(_)) | + (&Boolean::Not(_), &Boolean::Is(_), &Boolean::Is(_)) | + (&Boolean::Not(_), &Boolean::Is(_), &Boolean::Not(_)) | + (&Boolean::Not(_), &Boolean::Not(_), &Boolean::Is(_)) | + (&Boolean::Not(_), &Boolean::Not(_), &Boolean::Not(_)) + => {} + } + + let maj = cs.alloc(|| "maj", || { + maj_value.get().map(|v| { + if *v { + E::Fr::one() + } else { + E::Fr::zero() + } + }) + })?; + + // ¬(¬a ∧ ¬b) ∧ ¬(¬a ∧ ¬c) ∧ ¬(¬b ∧ ¬c) + // (1 - ((1 - a) * (1 - b))) * (1 - ((1 - a) * (1 - c))) * (1 - ((1 - b) * (1 - c))) + // (a + b - ab) * (a + c - ac) * (b + c - bc) + // -2abc + ab + ac + bc + // a (-2bc + b + c) + bc + // + // (b) * (c) = (bc) + // (2bc - b - c) * (a) = bc - maj + + let bc = Self::and( + cs.namespace(|| "b and c"), + b, + c + )?; + + cs.enforce( + || "maj computation", + |_| bc.lc(CS::one(), E::Fr::one()) + + &bc.lc(CS::one(), E::Fr::one()) + - &b.lc(CS::one(), E::Fr::one()) + - &c.lc(CS::one(), E::Fr::one()), + |_| a.lc(CS::one(), E::Fr::one()), + |_| bc.lc(CS::one(), E::Fr::one()) - maj + ); + + Ok(AllocatedBit { + value: maj_value, + variable: maj + }.into()) + } +} + +impl From for Boolean { + fn from(b: AllocatedBit) -> Boolean { + Boolean::Is(b) + } +} + +#[cfg(test)] +mod test { + use bellman::{ConstraintSystem}; + use pairing::bls12_381::{Bls12, Fr}; + use pairing::{Field, PrimeField}; + use ::circuit::test::*; + use super::{ + AllocatedBit, + Boolean, + field_into_allocated_bits_le, + u64_into_boolean_vec_le + }; + + #[test] + fn test_allocated_bit() { + let mut cs = TestConstraintSystem::::new(); + + AllocatedBit::alloc(&mut cs, Some(true)).unwrap(); + assert!(cs.get("boolean") == Fr::one()); + assert!(cs.is_satisfied()); + cs.set("boolean", Fr::zero()); + assert!(cs.is_satisfied()); + cs.set("boolean", Fr::from_str("2").unwrap()); + assert!(!cs.is_satisfied()); + assert!(cs.which_is_unsatisfied() == Some("boolean constraint")); + } + + #[test] + fn test_xor() { + for a_val in [false, true].iter() { + for b_val in [false, true].iter() { + let mut cs = TestConstraintSystem::::new(); + let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap(); + let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap(); + let c = AllocatedBit::xor(&mut cs, &a, &b).unwrap(); + assert_eq!(c.value.unwrap(), *a_val ^ *b_val); + + assert!(cs.is_satisfied()); + assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() }); + assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() }); + assert!(cs.get("xor result") == if *a_val ^ *b_val { Field::one() } else { Field::zero() }); + + // Invert the result and check if the constraint system is still satisfied + cs.set("xor result", if *a_val ^ *b_val { Field::zero() } else { Field::one() }); + assert!(!cs.is_satisfied()); + } + } + } + + #[test] + fn test_and() { + for a_val in [false, true].iter() { + for b_val in [false, true].iter() { + let mut cs = TestConstraintSystem::::new(); + let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap(); + let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap(); + let c = AllocatedBit::and(&mut cs, &a, &b).unwrap(); + assert_eq!(c.value.unwrap(), *a_val & *b_val); + + assert!(cs.is_satisfied()); + assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() }); + assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() }); + assert!(cs.get("and result") == if *a_val & *b_val { Field::one() } else { Field::zero() }); + + // Invert the result and check if the constraint system is still satisfied + cs.set("and result", if *a_val & *b_val { Field::zero() } else { Field::one() }); + assert!(!cs.is_satisfied()); + } + } + } + + #[test] + fn test_and_not() { + for a_val in [false, true].iter() { + for b_val in [false, true].iter() { + let mut cs = TestConstraintSystem::::new(); + let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap(); + let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap(); + let c = AllocatedBit::and_not(&mut cs, &a, &b).unwrap(); + assert_eq!(c.value.unwrap(), *a_val & !*b_val); + + assert!(cs.is_satisfied()); + assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() }); + assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() }); + assert!(cs.get("and not result") == if *a_val & !*b_val { Field::one() } else { Field::zero() }); + + // Invert the result and check if the constraint system is still satisfied + cs.set("and not result", if *a_val & !*b_val { Field::zero() } else { Field::one() }); + assert!(!cs.is_satisfied()); + } + } + } + + #[test] + fn test_nor() { + for a_val in [false, true].iter() { + for b_val in [false, true].iter() { + let mut cs = TestConstraintSystem::::new(); + let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap(); + let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap(); + let c = AllocatedBit::nor(&mut cs, &a, &b).unwrap(); + assert_eq!(c.value.unwrap(), !*a_val & !*b_val); + + assert!(cs.is_satisfied()); + assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() }); + assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() }); + assert!(cs.get("nor result") == if !*a_val & !*b_val { Field::one() } else { Field::zero() }); + + // Invert the result and check if the constraint system is still satisfied + cs.set("nor result", if !*a_val & !*b_val { Field::zero() } else { Field::one() }); + assert!(!cs.is_satisfied()); + } + } + } + + #[test] + fn test_enforce_equal() { + for a_bool in [false, true].iter().cloned() { + for b_bool in [false, true].iter().cloned() { + for a_neg in [false, true].iter().cloned() { + for b_neg in [false, true].iter().cloned() { + { + let mut cs = TestConstraintSystem::::new(); + + let mut a = Boolean::from(AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_bool)).unwrap()); + let mut b = Boolean::from(AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_bool)).unwrap()); + + if a_neg { + a = a.not(); + } + if b_neg { + b = b.not(); + } + + Boolean::enforce_equal(&mut cs, &a, &b).unwrap(); + + assert_eq!( + cs.is_satisfied(), + (a_bool ^ a_neg) == (b_bool ^ b_neg) + ); + } + { + let mut cs = TestConstraintSystem::::new(); + + let mut a = Boolean::Constant(a_bool); + let mut b = Boolean::from(AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_bool)).unwrap()); + + if a_neg { + a = a.not(); + } + if b_neg { + b = b.not(); + } + + Boolean::enforce_equal(&mut cs, &a, &b).unwrap(); + + assert_eq!( + cs.is_satisfied(), + (a_bool ^ a_neg) == (b_bool ^ b_neg) + ); + } + { + let mut cs = TestConstraintSystem::::new(); + + let mut a = Boolean::from(AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_bool)).unwrap()); + let mut b = Boolean::Constant(b_bool); + + if a_neg { + a = a.not(); + } + if b_neg { + b = b.not(); + } + + Boolean::enforce_equal(&mut cs, &a, &b).unwrap(); + + assert_eq!( + cs.is_satisfied(), + (a_bool ^ a_neg) == (b_bool ^ b_neg) + ); + } + { + let mut cs = TestConstraintSystem::::new(); + + let mut a = Boolean::Constant(a_bool); + let mut b = Boolean::Constant(b_bool); + + if a_neg { + a = a.not(); + } + if b_neg { + b = b.not(); + } + + let result = Boolean::enforce_equal(&mut cs, &a, &b); + + if (a_bool ^ a_neg) == (b_bool ^ b_neg) { + assert!(result.is_ok()); + assert!(cs.is_satisfied()); + } else { + assert!(result.is_err()); + } + } + } + } + } + } + } + + #[test] + fn test_boolean_negation() { + let mut cs = TestConstraintSystem::::new(); + + let mut b = Boolean::from(AllocatedBit::alloc(&mut cs, Some(true)).unwrap()); + + match b { + Boolean::Is(_) => {}, + _ => panic!("unexpected value") + } + + b = b.not(); + + match b { + Boolean::Not(_) => {}, + _ => panic!("unexpected value") + } + + b = b.not(); + + match b { + Boolean::Is(_) => {}, + _ => panic!("unexpected value") + } + + b = Boolean::constant(true); + + match b { + Boolean::Constant(true) => {}, + _ => panic!("unexpected value") + } + + b = b.not(); + + match b { + Boolean::Constant(false) => {}, + _ => panic!("unexpected value") + } + + b = b.not(); + + match b { + Boolean::Constant(true) => {}, + _ => panic!("unexpected value") + } + } + + #[derive(Copy, Clone, Debug)] + enum OperandType { + True, + False, + AllocatedTrue, + AllocatedFalse, + NegatedAllocatedTrue, + NegatedAllocatedFalse + } + + impl OperandType { + fn is_constant(&self) -> bool { + match *self { + OperandType::True => true, + OperandType::False => true, + OperandType::AllocatedTrue => false, + OperandType::AllocatedFalse => false, + OperandType::NegatedAllocatedTrue => false, + OperandType::NegatedAllocatedFalse => false + } + } + + fn val(&self) -> bool { + match *self { + OperandType::True => true, + OperandType::False => false, + OperandType::AllocatedTrue => true, + OperandType::AllocatedFalse => false, + OperandType::NegatedAllocatedTrue => false, + OperandType::NegatedAllocatedFalse => true + } + } + } + + + #[test] + fn test_boolean_xor() { + 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 = Boolean::xor(&mut cs, &a, &b).unwrap(); + + assert!(cs.is_satisfied()); + + match (first_operand, second_operand, c) { + (OperandType::True, OperandType::True, Boolean::Constant(false)) => {}, + (OperandType::True, OperandType::False, Boolean::Constant(true)) => {}, + (OperandType::True, OperandType::AllocatedTrue, Boolean::Not(_)) => {}, + (OperandType::True, OperandType::AllocatedFalse, Boolean::Not(_)) => {}, + (OperandType::True, OperandType::NegatedAllocatedTrue, Boolean::Is(_)) => {}, + (OperandType::True, OperandType::NegatedAllocatedFalse, Boolean::Is(_)) => {}, + + (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::Not(_)) => {}, + (OperandType::AllocatedTrue, OperandType::False, Boolean::Is(_)) => {}, + (OperandType::AllocatedTrue, OperandType::AllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("xor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::AllocatedTrue, OperandType::AllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("xor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + (OperandType::AllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Not(ref v)) => { + assert!(cs.get("xor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::AllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Not(ref v)) => { + assert!(cs.get("xor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + + (OperandType::AllocatedFalse, OperandType::True, Boolean::Not(_)) => {}, + (OperandType::AllocatedFalse, OperandType::False, Boolean::Is(_)) => {}, + (OperandType::AllocatedFalse, OperandType::AllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("xor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + (OperandType::AllocatedFalse, OperandType::AllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("xor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::AllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Not(ref v)) => { + assert!(cs.get("xor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + (OperandType::AllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Not(ref v)) => { + assert!(cs.get("xor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + + (OperandType::NegatedAllocatedTrue, OperandType::True, Boolean::Is(_)) => {}, + (OperandType::NegatedAllocatedTrue, OperandType::False, Boolean::Not(_)) => {}, + (OperandType::NegatedAllocatedTrue, OperandType::AllocatedTrue, Boolean::Not(ref v)) => { + assert!(cs.get("xor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::NegatedAllocatedTrue, OperandType::AllocatedFalse, Boolean::Not(ref v)) => { + assert!(cs.get("xor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + (OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("xor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("xor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + + (OperandType::NegatedAllocatedFalse, OperandType::True, Boolean::Is(_)) => {}, + (OperandType::NegatedAllocatedFalse, OperandType::False, Boolean::Not(_)) => {}, + (OperandType::NegatedAllocatedFalse, OperandType::AllocatedTrue, Boolean::Not(ref v)) => { + assert!(cs.get("xor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + (OperandType::NegatedAllocatedFalse, OperandType::AllocatedFalse, Boolean::Not(ref v)) => { + assert!(cs.get("xor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("xor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + (OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("xor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + + _ => panic!("this should never be encountered") + } + } + } + } + + #[test] + fn test_boolean_and() { + 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 = Boolean::and(&mut cs, &a, &b).unwrap(); + + assert!(cs.is_satisfied()); + + match (first_operand, second_operand, c) { + (OperandType::True, OperandType::True, Boolean::Constant(true)) => {}, + (OperandType::True, OperandType::False, Boolean::Constant(false)) => {}, + (OperandType::True, OperandType::AllocatedTrue, Boolean::Is(_)) => {}, + (OperandType::True, OperandType::AllocatedFalse, Boolean::Is(_)) => {}, + (OperandType::True, OperandType::NegatedAllocatedTrue, Boolean::Not(_)) => {}, + (OperandType::True, OperandType::NegatedAllocatedFalse, Boolean::Not(_)) => {}, + + (OperandType::False, OperandType::True, Boolean::Constant(false)) => {}, + (OperandType::False, OperandType::False, Boolean::Constant(false)) => {}, + (OperandType::False, OperandType::AllocatedTrue, Boolean::Constant(false)) => {}, + (OperandType::False, OperandType::AllocatedFalse, Boolean::Constant(false)) => {}, + (OperandType::False, OperandType::NegatedAllocatedTrue, Boolean::Constant(false)) => {}, + (OperandType::False, OperandType::NegatedAllocatedFalse, Boolean::Constant(false)) => {}, + + (OperandType::AllocatedTrue, OperandType::True, Boolean::Is(_)) => {}, + (OperandType::AllocatedTrue, OperandType::False, Boolean::Constant(false)) => {}, + (OperandType::AllocatedTrue, OperandType::AllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("and result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + (OperandType::AllocatedTrue, OperandType::AllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("and result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::AllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("and not result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::AllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("and not result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + + (OperandType::AllocatedFalse, OperandType::True, Boolean::Is(_)) => {}, + (OperandType::AllocatedFalse, OperandType::False, Boolean::Constant(false)) => {}, + (OperandType::AllocatedFalse, OperandType::AllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("and result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::AllocatedFalse, OperandType::AllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("and result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::AllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("and not result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::AllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("and not result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + + (OperandType::NegatedAllocatedTrue, OperandType::True, Boolean::Not(_)) => {}, + (OperandType::NegatedAllocatedTrue, OperandType::False, Boolean::Constant(false)) => {}, + (OperandType::NegatedAllocatedTrue, OperandType::AllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("and not result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::NegatedAllocatedTrue, OperandType::AllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("and not result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("nor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("nor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + + (OperandType::NegatedAllocatedFalse, OperandType::True, Boolean::Not(_)) => {}, + (OperandType::NegatedAllocatedFalse, OperandType::False, Boolean::Constant(false)) => {}, + (OperandType::NegatedAllocatedFalse, OperandType::AllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("and not result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + (OperandType::NegatedAllocatedFalse, OperandType::AllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("and not result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => { + assert!(cs.get("nor result") == Field::zero()); + assert_eq!(v.value, Some(false)); + }, + (OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => { + assert!(cs.get("nor result") == Field::one()); + assert_eq!(v.value, Some(true)); + }, + + _ => { + panic!("unexpected behavior at {:?} AND {:?}", first_operand, second_operand); + } + } + } + } + } + + #[test] + fn test_u64_into_boolean_vec_le() { + let mut cs = TestConstraintSystem::::new(); + + let bits = u64_into_boolean_vec_le(&mut cs, Some(17234652694787248421)).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(bits.len(), 64); + + assert_eq!(bits[63 - 0].get_value().unwrap(), true); + assert_eq!(bits[63 - 1].get_value().unwrap(), true); + assert_eq!(bits[63 - 2].get_value().unwrap(), true); + assert_eq!(bits[63 - 3].get_value().unwrap(), false); + assert_eq!(bits[63 - 4].get_value().unwrap(), true); + assert_eq!(bits[63 - 5].get_value().unwrap(), true); + assert_eq!(bits[63 - 20].get_value().unwrap(), true); + assert_eq!(bits[63 - 21].get_value().unwrap(), false); + assert_eq!(bits[63 - 22].get_value().unwrap(), false); + } + + #[test] + fn test_field_into_allocated_bits_le() { + let mut cs = TestConstraintSystem::::new(); + + let r = Fr::from_str("9147677615426976802526883532204139322118074541891858454835346926874644257775").unwrap(); + + let bits = field_into_allocated_bits_le(&mut cs, Some(r)).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(bits.len(), 255); + + assert_eq!(bits[254 - 0].value.unwrap(), false); + assert_eq!(bits[254 - 1].value.unwrap(), false); + assert_eq!(bits[254 - 2].value.unwrap(), true); + assert_eq!(bits[254 - 3].value.unwrap(), false); + assert_eq!(bits[254 - 4].value.unwrap(), true); + assert_eq!(bits[254 - 5].value.unwrap(), false); + assert_eq!(bits[254 - 20].value.unwrap(), true); + assert_eq!(bits[254 - 23].value.unwrap(), true); + } + + #[test] + fn test_boolean_sha256_ch() { + 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() { + for third_operand in variants.iter().cloned() { + let mut cs = TestConstraintSystem::::new(); + + let a; + let b; + let c; + + // ch = (a and b) xor ((not a) and c) + let expected = (first_operand.val() & second_operand.val()) ^ + ((!first_operand.val()) & third_operand.val()); + + { + 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"); + c = dyn_construct(third_operand, "c"); + } + + let maj = Boolean::sha256_ch(&mut cs, &a, &b, &c).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(maj.get_value().unwrap(), expected); + + if first_operand.is_constant() || + second_operand.is_constant() || + third_operand.is_constant() + { + if first_operand.is_constant() && + second_operand.is_constant() && + third_operand.is_constant() + { + assert_eq!(cs.num_constraints(), 0); + } + } + else + { + assert_eq!(cs.get("ch"), { + if expected { + Fr::one() + } else { + Fr::zero() + } + }); + cs.set("ch", { + if expected { + Fr::zero() + } else { + Fr::one() + } + }); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "ch computation"); + } + } + } + } + } + + #[test] + fn test_boolean_sha256_maj() { + 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() { + for third_operand in variants.iter().cloned() { + let mut cs = TestConstraintSystem::::new(); + + let a; + let b; + let c; + + // maj = (a and b) xor (a and c) xor (b and c) + let expected = (first_operand.val() & second_operand.val()) ^ + (first_operand.val() & third_operand.val()) ^ + (second_operand.val() & third_operand.val()); + + { + 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"); + c = dyn_construct(third_operand, "c"); + } + + let maj = Boolean::sha256_maj(&mut cs, &a, &b, &c).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(maj.get_value().unwrap(), expected); + + if first_operand.is_constant() || + second_operand.is_constant() || + third_operand.is_constant() + { + if first_operand.is_constant() && + second_operand.is_constant() && + third_operand.is_constant() + { + assert_eq!(cs.num_constraints(), 0); + } + } + else + { + assert_eq!(cs.get("maj"), { + if expected { + Fr::one() + } else { + Fr::zero() + } + }); + cs.set("maj", { + if expected { + Fr::zero() + } else { + Fr::one() + } + }); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "maj computation"); + } + } + } + } + } +} diff --git a/sapling-crypto/src/circuit/ecc.rs b/sapling-crypto/src/circuit/ecc.rs new file mode 100644 index 0000000..71f1caa --- /dev/null +++ b/sapling-crypto/src/circuit/ecc.rs @@ -0,0 +1,1213 @@ +use pairing::{ + Engine, + Field +}; + +use bellman::{ + SynthesisError, + ConstraintSystem +}; + +use super::{ + Assignment +}; + +use super::num::{ + AllocatedNum, + Num +}; + +use ::jubjub::{ + edwards, + JubjubEngine, + JubjubParams, + FixedGenerators +}; + +use super::lookup::{ + lookup3_xy +}; + +use super::boolean::Boolean; + +#[derive(Clone)] +pub struct EdwardsPoint { + x: AllocatedNum, + y: AllocatedNum +} + +/// Perform a fixed-base scalar multiplication with +/// `by` being in little-endian bit order. +pub fn fixed_base_multiplication( + mut cs: CS, + base: FixedGenerators, + by: &[Boolean], + params: &E::Params +) -> Result, SynthesisError> + where CS: ConstraintSystem, + E: JubjubEngine +{ + // Represents the result of the multiplication + let mut result = None; + + for (i, (chunk, window)) in by.chunks(3) + .zip(params.circuit_generators(base).iter()) + .enumerate() + { + let chunk_a = chunk.get(0).map(|e| e.clone()).unwrap_or(Boolean::constant(false)); + let chunk_b = chunk.get(1).map(|e| e.clone()).unwrap_or(Boolean::constant(false)); + let chunk_c = chunk.get(2).map(|e| e.clone()).unwrap_or(Boolean::constant(false)); + + let (x, y) = lookup3_xy( + cs.namespace(|| format!("window table lookup {}", i)), + &[chunk_a, chunk_b, chunk_c], + window + )?; + + let p = EdwardsPoint { + x: x, + y: y + }; + + if result.is_none() { + result = Some(p); + } else { + result = Some(result.unwrap().add( + cs.namespace(|| format!("addition {}", i)), + &p, + params + )?); + } + } + + Ok(result.get()?.clone()) +} + +impl EdwardsPoint { + pub fn get_x(&self) -> &AllocatedNum { + &self.x + } + + pub fn get_y(&self) -> &AllocatedNum { + &self.y + } + + pub fn assert_not_small_order( + &self, + mut cs: CS, + params: &E::Params + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + let tmp = self.double( + cs.namespace(|| "first doubling"), + params + )?; + let tmp = tmp.double( + cs.namespace(|| "second doubling"), + params + )?; + let tmp = tmp.double( + cs.namespace(|| "third doubling"), + params + )?; + + // (0, -1) is a small order point, but won't ever appear here + // because cofactor is 2^3, and we performed three doublings. + // (0, 1) is the neutral element, so checking if x is nonzero + // is sufficient to prevent small order points here. + tmp.x.assert_nonzero(cs.namespace(|| "check x != 0"))?; + + Ok(()) + } + + pub fn inputize( + &self, + mut cs: CS + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + self.x.inputize(cs.namespace(|| "x"))?; + self.y.inputize(cs.namespace(|| "y"))?; + + Ok(()) + } + + /// This converts the point into a representation. + pub fn repr( + &self, + mut cs: CS + ) -> Result, SynthesisError> + where CS: ConstraintSystem + { + let mut tmp = vec![]; + + let x = self.x.into_bits_le_strict( + cs.namespace(|| "unpack x") + )?; + + let y = self.y.into_bits_le_strict( + cs.namespace(|| "unpack y") + )?; + + tmp.extend(y); + tmp.push(x[0].clone()); + + Ok(tmp) + } + + /// This 'witnesses' a point inside the constraint system. + /// It guarantees the point is on the curve. + pub fn witness( + mut cs: CS, + p: Option>, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + let p = p.map(|p| p.into_xy()); + + // Allocate x + let x = AllocatedNum::alloc( + cs.namespace(|| "x"), + || { + Ok(p.get()?.0) + } + )?; + + // Allocate y + let y = AllocatedNum::alloc( + cs.namespace(|| "y"), + || { + Ok(p.get()?.1) + } + )?; + + Self::interpret( + cs.namespace(|| "point interpretation"), + &x, + &y, + params + ) + } + + /// Returns `self` if condition is true, and the neutral + /// element (0, 1) otherwise. + pub fn conditionally_select( + &self, + mut cs: CS, + condition: &Boolean + ) -> Result + where CS: ConstraintSystem + { + // Compute x' = self.x if condition, and 0 otherwise + let x_prime = AllocatedNum::alloc(cs.namespace(|| "x'"), || { + if *condition.get_value().get()? { + Ok(*self.x.get_value().get()?) + } else { + Ok(E::Fr::zero()) + } + })?; + + // condition * x = x' + // if condition is 0, x' must be 0 + // if condition is 1, x' must be x + let one = CS::one(); + cs.enforce( + || "x' computation", + |lc| lc + self.x.get_variable(), + |_| condition.lc(one, E::Fr::one()), + |lc| lc + x_prime.get_variable() + ); + + // Compute y' = self.y if condition, and 1 otherwise + let y_prime = AllocatedNum::alloc(cs.namespace(|| "y'"), || { + if *condition.get_value().get()? { + Ok(*self.y.get_value().get()?) + } else { + Ok(E::Fr::one()) + } + })?; + + // condition * y = y' - (1 - condition) + // if condition is 0, y' must be 1 + // if condition is 1, y' must be y + cs.enforce( + || "y' computation", + |lc| lc + self.y.get_variable(), + |_| condition.lc(one, E::Fr::one()), + |lc| lc + y_prime.get_variable() + - &condition.not().lc(one, E::Fr::one()) + ); + + Ok(EdwardsPoint { + x: x_prime, + y: y_prime + }) + } + + /// Performs a scalar multiplication of this twisted Edwards + /// point by a scalar represented as a sequence of booleans + /// in little-endian bit order. + pub fn mul( + &self, + mut cs: CS, + by: &[Boolean], + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Represents the current "magnitude" of the base + // that we're operating over. Starts at self, + // then 2*self, then 4*self, ... + let mut curbase = None; + + // Represents the result of the multiplication + let mut result = None; + + for (i, bit) in by.iter().enumerate() { + if curbase.is_none() { + curbase = Some(self.clone()); + } else { + // Double the previous value + curbase = Some( + curbase.unwrap() + .double(cs.namespace(|| format!("doubling {}", i)), params)? + ); + } + + // Represents the select base. If the bit for this magnitude + // is true, this will return `curbase`. Otherwise it will + // return the neutral element, which will have no effect on + // the result. + let thisbase = curbase.as_ref() + .unwrap() + .conditionally_select( + cs.namespace(|| format!("selection {}", i)), + bit + )?; + + if result.is_none() { + result = Some(thisbase); + } else { + result = Some(result.unwrap().add( + cs.namespace(|| format!("addition {}", i)), + &thisbase, + params + )?); + } + } + + Ok(result.get()?.clone()) + } + + pub fn interpret( + mut cs: CS, + x: &AllocatedNum, + y: &AllocatedNum, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // -x^2 + y^2 = 1 + dx^2y^2 + + let x2 = x.square(cs.namespace(|| "x^2"))?; + let y2 = y.square(cs.namespace(|| "y^2"))?; + let x2y2 = x2.mul(cs.namespace(|| "x^2 y^2"), &y2)?; + + let one = CS::one(); + cs.enforce( + || "on curve check", + |lc| lc - x2.get_variable() + + y2.get_variable(), + |lc| lc + one, + |lc| lc + one + + (*params.edwards_d(), x2y2.get_variable()) + ); + + Ok(EdwardsPoint { + x: x.clone(), + y: y.clone() + }) + } + + pub fn double( + &self, + mut cs: CS, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Compute T = (x1 + y1) * (x1 + y1) + let t = AllocatedNum::alloc(cs.namespace(|| "T"), || { + let mut t0 = *self.x.get_value().get()?; + t0.add_assign(self.y.get_value().get()?); + + let mut t1 = *self.x.get_value().get()?; + t1.add_assign(self.y.get_value().get()?); + + t0.mul_assign(&t1); + + Ok(t0) + })?; + + cs.enforce( + || "T computation", + |lc| lc + self.x.get_variable() + + self.y.get_variable(), + |lc| lc + self.x.get_variable() + + self.y.get_variable(), + |lc| lc + t.get_variable() + ); + + // Compute A = x1 * y1 + let a = self.x.mul(cs.namespace(|| "A computation"), &self.y)?; + + // Compute C = d*A*A + let c = AllocatedNum::alloc(cs.namespace(|| "C"), || { + let mut t0 = *a.get_value().get()?; + t0.square(); + t0.mul_assign(params.edwards_d()); + + Ok(t0) + })?; + + cs.enforce( + || "C computation", + |lc| lc + (*params.edwards_d(), a.get_variable()), + |lc| lc + a.get_variable(), + |lc| lc + c.get_variable() + ); + + // Compute x3 = (2.A) / (1 + C) + let x3 = AllocatedNum::alloc(cs.namespace(|| "x3"), || { + let mut t0 = *a.get_value().get()?; + t0.double(); + + let mut t1 = E::Fr::one(); + t1.add_assign(c.get_value().get()?); + + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + Err(SynthesisError::DivisionByZero) + } + } + })?; + + let one = CS::one(); + cs.enforce( + || "x3 computation", + |lc| lc + one + c.get_variable(), + |lc| lc + x3.get_variable(), + |lc| lc + a.get_variable() + + a.get_variable() + ); + + // Compute y3 = (U - 2.A) / (1 - C) + let y3 = AllocatedNum::alloc(cs.namespace(|| "y3"), || { + let mut t0 = *a.get_value().get()?; + t0.double(); + t0.negate(); + t0.add_assign(t.get_value().get()?); + + let mut t1 = E::Fr::one(); + t1.sub_assign(c.get_value().get()?); + + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + Err(SynthesisError::DivisionByZero) + } + } + })?; + + cs.enforce( + || "y3 computation", + |lc| lc + one - c.get_variable(), + |lc| lc + y3.get_variable(), + |lc| lc + t.get_variable() + - a.get_variable() + - a.get_variable() + ); + + Ok(EdwardsPoint { + x: x3, + y: y3 + }) + } + + /// Perform addition between any two points + pub fn add( + &self, + mut cs: CS, + other: &Self, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Compute U = (x1 + y1) * (x2 + y2) + let u = AllocatedNum::alloc(cs.namespace(|| "U"), || { + let mut t0 = *self.x.get_value().get()?; + t0.add_assign(self.y.get_value().get()?); + + let mut t1 = *other.x.get_value().get()?; + t1.add_assign(other.y.get_value().get()?); + + t0.mul_assign(&t1); + + Ok(t0) + })?; + + cs.enforce( + || "U computation", + |lc| lc + self.x.get_variable() + + self.y.get_variable(), + |lc| lc + other.x.get_variable() + + other.y.get_variable(), + |lc| lc + u.get_variable() + ); + + // Compute A = y2 * x1 + let a = other.y.mul(cs.namespace(|| "A computation"), &self.x)?; + + // Compute B = x2 * y1 + let b = other.x.mul(cs.namespace(|| "B computation"), &self.y)?; + + // Compute C = d*A*B + let c = AllocatedNum::alloc(cs.namespace(|| "C"), || { + let mut t0 = *a.get_value().get()?; + t0.mul_assign(b.get_value().get()?); + t0.mul_assign(params.edwards_d()); + + Ok(t0) + })?; + + cs.enforce( + || "C computation", + |lc| lc + (*params.edwards_d(), a.get_variable()), + |lc| lc + b.get_variable(), + |lc| lc + c.get_variable() + ); + + // Compute x3 = (A + B) / (1 + C) + let x3 = AllocatedNum::alloc(cs.namespace(|| "x3"), || { + let mut t0 = *a.get_value().get()?; + t0.add_assign(b.get_value().get()?); + + let mut t1 = E::Fr::one(); + t1.add_assign(c.get_value().get()?); + + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + Err(SynthesisError::DivisionByZero) + } + } + })?; + + let one = CS::one(); + cs.enforce( + || "x3 computation", + |lc| lc + one + c.get_variable(), + |lc| lc + x3.get_variable(), + |lc| lc + a.get_variable() + + b.get_variable() + ); + + // Compute y3 = (U - A - B) / (1 - C) + let y3 = AllocatedNum::alloc(cs.namespace(|| "y3"), || { + let mut t0 = *u.get_value().get()?; + t0.sub_assign(a.get_value().get()?); + t0.sub_assign(b.get_value().get()?); + + let mut t1 = E::Fr::one(); + t1.sub_assign(c.get_value().get()?); + + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + Err(SynthesisError::DivisionByZero) + } + } + })?; + + cs.enforce( + || "y3 computation", + |lc| lc + one - c.get_variable(), + |lc| lc + y3.get_variable(), + |lc| lc + u.get_variable() + - a.get_variable() + - b.get_variable() + ); + + Ok(EdwardsPoint { + x: x3, + y: y3 + }) + } +} + +pub struct MontgomeryPoint { + x: Num, + y: Num +} + +impl MontgomeryPoint { + /// Converts an element in the prime order subgroup into + /// a point in the birationally equivalent twisted + /// Edwards curve. + pub fn into_edwards( + &self, + mut cs: CS, + params: &E::Params + ) -> Result, SynthesisError> + where CS: ConstraintSystem + { + // Compute u = (scale*x) / y + let u = AllocatedNum::alloc(cs.namespace(|| "u"), || { + let mut t0 = *self.x.get_value().get()?; + t0.mul_assign(params.scale()); + + match self.y.get_value().get()?.inverse() { + Some(invy) => { + t0.mul_assign(&invy); + + Ok(t0) + }, + None => { + Err(SynthesisError::DivisionByZero) + } + } + })?; + + cs.enforce( + || "u computation", + |lc| lc + &self.y.lc(E::Fr::one()), + |lc| lc + u.get_variable(), + |lc| lc + &self.x.lc(*params.scale()) + ); + + // Compute v = (x - 1) / (x + 1) + let v = AllocatedNum::alloc(cs.namespace(|| "v"), || { + let mut t0 = *self.x.get_value().get()?; + let mut t1 = t0; + t0.sub_assign(&E::Fr::one()); + t1.add_assign(&E::Fr::one()); + + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + Err(SynthesisError::DivisionByZero) + } + } + })?; + + let one = CS::one(); + cs.enforce( + || "v computation", + |lc| lc + &self.x.lc(E::Fr::one()) + + one, + |lc| lc + v.get_variable(), + |lc| lc + &self.x.lc(E::Fr::one()) + - one, + ); + + Ok(EdwardsPoint { + x: u, + y: v + }) + } + + /// Interprets an (x, y) pair as a point + /// in Montgomery, does not check that it's + /// on the curve. Useful for constants and + /// window table lookups. + pub fn interpret_unchecked( + x: Num, + y: Num + ) -> Self + { + MontgomeryPoint { + x: x, + y: y + } + } + + /// Performs an affine point addition, not defined for + /// coincident points. + pub fn add( + &self, + mut cs: CS, + other: &Self, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Compute lambda = (y' - y) / (x' - x) + let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || { + let mut n = *other.y.get_value().get()?; + n.sub_assign(self.y.get_value().get()?); + + let mut d = *other.x.get_value().get()?; + d.sub_assign(self.x.get_value().get()?); + + match d.inverse() { + Some(d) => { + n.mul_assign(&d); + Ok(n) + }, + None => { + Err(SynthesisError::DivisionByZero) + } + } + })?; + + cs.enforce( + || "evaluate lambda", + |lc| lc + &other.x.lc(E::Fr::one()) + - &self.x.lc(E::Fr::one()), + + |lc| lc + lambda.get_variable(), + + |lc| lc + &other.y.lc(E::Fr::one()) + - &self.y.lc(E::Fr::one()) + ); + + // Compute x'' = lambda^2 - A - x - x' + let xprime = AllocatedNum::alloc(cs.namespace(|| "xprime"), || { + let mut t0 = *lambda.get_value().get()?; + t0.square(); + t0.sub_assign(params.montgomery_a()); + t0.sub_assign(self.x.get_value().get()?); + t0.sub_assign(other.x.get_value().get()?); + + Ok(t0) + })?; + + // (lambda) * (lambda) = (A + x + x' + x'') + let one = CS::one(); + cs.enforce( + || "evaluate xprime", + |lc| lc + lambda.get_variable(), + |lc| lc + lambda.get_variable(), + |lc| lc + (*params.montgomery_a(), one) + + &self.x.lc(E::Fr::one()) + + &other.x.lc(E::Fr::one()) + + xprime.get_variable() + ); + + // Compute y' = -(y + lambda(x' - x)) + let yprime = AllocatedNum::alloc(cs.namespace(|| "yprime"), || { + let mut t0 = *xprime.get_value().get()?; + t0.sub_assign(self.x.get_value().get()?); + t0.mul_assign(lambda.get_value().get()?); + t0.add_assign(self.y.get_value().get()?); + t0.negate(); + + Ok(t0) + })?; + + // y' + y = lambda(x - x') + cs.enforce( + || "evaluate yprime", + |lc| lc + &self.x.lc(E::Fr::one()) + - xprime.get_variable(), + + |lc| lc + lambda.get_variable(), + + |lc| lc + yprime.get_variable() + + &self.y.lc(E::Fr::one()) + ); + + Ok(MontgomeryPoint { + x: xprime.into(), + y: yprime.into() + }) + } +} + +#[cfg(test)] +mod test { + use bellman::{ConstraintSystem}; + use rand::{XorShiftRng, SeedableRng, Rand, Rng}; + use pairing::bls12_381::{Bls12, Fr}; + use pairing::{BitIterator, Field, PrimeField}; + use ::circuit::test::*; + use ::jubjub::{ + montgomery, + edwards, + JubjubBls12, + JubjubParams, + FixedGenerators + }; + use ::jubjub::fs::Fs; + use super::{ + MontgomeryPoint, + EdwardsPoint, + AllocatedNum, + fixed_base_multiplication + }; + use super::super::boolean::{ + Boolean, + AllocatedBit + }; + + #[test] + fn test_into_edwards() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let p = montgomery::Point::::rand(rng, params); + let (u, v) = edwards::Point::from_montgomery(&p, params).into_xy(); + let (x, y) = p.into_xy().unwrap(); + + let numx = AllocatedNum::alloc(cs.namespace(|| "mont x"), || { + Ok(x) + }).unwrap(); + let numy = AllocatedNum::alloc(cs.namespace(|| "mont y"), || { + Ok(y) + }).unwrap(); + + let p = MontgomeryPoint::interpret_unchecked(numx.into(), numy.into()); + + let q = p.into_edwards(&mut cs, params).unwrap(); + + assert!(cs.is_satisfied()); + assert!(q.x.get_value().unwrap() == u); + assert!(q.y.get_value().unwrap() == v); + + cs.set("u/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "u computation"); + cs.set("u/num", u); + assert!(cs.is_satisfied()); + + cs.set("v/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "v computation"); + cs.set("v/num", v); + assert!(cs.is_satisfied()); + } + } + + #[test] + fn test_interpret() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p = edwards::Point::::rand(rng, ¶ms); + + let mut cs = TestConstraintSystem::::new(); + let q = EdwardsPoint::witness( + &mut cs, + Some(p.clone()), + ¶ms + ).unwrap(); + + let p = p.into_xy(); + + assert!(cs.is_satisfied()); + assert_eq!(q.x.get_value().unwrap(), p.0); + assert_eq!(q.y.get_value().unwrap(), p.1); + } + + for _ in 0..100 { + let p = edwards::Point::::rand(rng, ¶ms); + let (x, y) = p.into_xy(); + + let mut cs = TestConstraintSystem::::new(); + let numx = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(x) + }).unwrap(); + let numy = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(y) + }).unwrap(); + + let p = EdwardsPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(p.x.get_value().unwrap(), x); + assert_eq!(p.y.get_value().unwrap(), y); + } + + // Random (x, y) are unlikely to be on the curve. + for _ in 0..100 { + let x = rng.gen(); + let y = rng.gen(); + + let mut cs = TestConstraintSystem::::new(); + let numx = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(x) + }).unwrap(); + let numy = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(y) + }).unwrap(); + + EdwardsPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); + + assert_eq!(cs.which_is_unsatisfied().unwrap(), "on curve check"); + } + } + + #[test] + fn test_edwards_fixed_base_multiplication() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let p = params.generator(FixedGenerators::NoteCommitmentRandomness); + let s = Fs::rand(rng); + let q = p.mul(s, params); + let (x1, y1) = q.into_xy(); + + let mut s_bits = BitIterator::new(s.into_repr()).collect::>(); + s_bits.reverse(); + s_bits.truncate(Fs::NUM_BITS as usize); + + let s_bits = s_bits.into_iter() + .enumerate() + .map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("scalar bit {}", i)), Some(b)).unwrap()) + .map(|v| Boolean::from(v)) + .collect::>(); + + let q = fixed_base_multiplication( + cs.namespace(|| "multiplication"), + FixedGenerators::NoteCommitmentRandomness, + &s_bits, + params + ).unwrap(); + + assert_eq!(q.x.get_value().unwrap(), x1); + assert_eq!(q.y.get_value().unwrap(), y1); + } + } + + #[test] + fn test_edwards_multiplication() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let p = edwards::Point::::rand(rng, params); + let s = Fs::rand(rng); + let q = p.mul(s, params); + + let (x0, y0) = p.into_xy(); + let (x1, y1) = q.into_xy(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let p = EdwardsPoint { + x: num_x0, + y: num_y0 + }; + + let mut s_bits = BitIterator::new(s.into_repr()).collect::>(); + s_bits.reverse(); + s_bits.truncate(Fs::NUM_BITS as usize); + + let s_bits = s_bits.into_iter() + .enumerate() + .map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("scalar bit {}", i)), Some(b)).unwrap()) + .map(|v| Boolean::from(v)) + .collect::>(); + + let q = p.mul( + cs.namespace(|| "scalar mul"), + &s_bits, + params + ).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!( + q.x.get_value().unwrap(), + x1 + ); + + assert_eq!( + q.y.get_value().unwrap(), + y1 + ); + } + } + + #[test] + fn test_conditionally_select() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let p = edwards::Point::::rand(rng, params); + + let (x0, y0) = p.into_xy(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let p = EdwardsPoint { + x: num_x0, + y: num_y0 + }; + + let mut should_we_select = rng.gen(); + + // Conditionally allocate + let mut b = if rng.gen() { + Boolean::from(AllocatedBit::alloc( + cs.namespace(|| "condition"), + Some(should_we_select) + ).unwrap()) + } else { + Boolean::constant(should_we_select) + }; + + // Conditionally negate + if rng.gen() { + b = b.not(); + should_we_select = !should_we_select; + } + + let q = p.conditionally_select(cs.namespace(|| "select"), &b).unwrap(); + + assert!(cs.is_satisfied()); + + if should_we_select { + assert_eq!(q.x.get_value().unwrap(), x0); + assert_eq!(q.y.get_value().unwrap(), y0); + + cs.set("select/y'/num", Fr::one()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/y' computation"); + cs.set("select/x'/num", Fr::zero()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/x' computation"); + } else { + assert_eq!(q.x.get_value().unwrap(), Fr::zero()); + assert_eq!(q.y.get_value().unwrap(), Fr::one()); + + cs.set("select/y'/num", x0); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/y' computation"); + cs.set("select/x'/num", y0); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/x' computation"); + } + } + } + + #[test] + fn test_edwards_addition() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p1 = edwards::Point::::rand(rng, params); + let p2 = edwards::Point::::rand(rng, params); + + let p3 = p1.add(&p2, params); + + let (x0, y0) = p1.into_xy(); + let (x1, y1) = p2.into_xy(); + let (x2, y2) = p3.into_xy(); + + let mut cs = TestConstraintSystem::::new(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let num_x1 = AllocatedNum::alloc(cs.namespace(|| "x1"), || { + Ok(x1) + }).unwrap(); + let num_y1 = AllocatedNum::alloc(cs.namespace(|| "y1"), || { + Ok(y1) + }).unwrap(); + + let p1 = EdwardsPoint { + x: num_x0, + y: num_y0 + }; + + let p2 = EdwardsPoint { + x: num_x1, + y: num_y1 + }; + + let p3 = p1.add(cs.namespace(|| "addition"), &p2, params).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p3.x.get_value().unwrap() == x2); + assert!(p3.y.get_value().unwrap() == y2); + + let u = cs.get("addition/U/num"); + cs.set("addition/U/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/U computation")); + cs.set("addition/U/num", u); + assert!(cs.is_satisfied()); + + let x3 = cs.get("addition/x3/num"); + cs.set("addition/x3/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/x3 computation")); + cs.set("addition/x3/num", x3); + assert!(cs.is_satisfied()); + + let y3 = cs.get("addition/y3/num"); + cs.set("addition/y3/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/y3 computation")); + cs.set("addition/y3/num", y3); + assert!(cs.is_satisfied()); + } + } + + #[test] + fn test_edwards_doubling() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p1 = edwards::Point::::rand(rng, params); + let p2 = p1.double(params); + + let (x0, y0) = p1.into_xy(); + let (x1, y1) = p2.into_xy(); + + let mut cs = TestConstraintSystem::::new(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let p1 = EdwardsPoint { + x: num_x0, + y: num_y0 + }; + + let p2 = p1.double(cs.namespace(|| "doubling"), params).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p2.x.get_value().unwrap() == x1); + assert!(p2.y.get_value().unwrap() == y1); + } + } + + #[test] + fn test_montgomery_addition() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p1 = loop { + let x: Fr = rng.gen(); + let s: bool = rng.gen(); + + if let Some(p) = montgomery::Point::::get_for_x(x, s, params) { + break p; + } + }; + + let p2 = loop { + let x: Fr = rng.gen(); + let s: bool = rng.gen(); + + if let Some(p) = montgomery::Point::::get_for_x(x, s, params) { + break p; + } + }; + + let p3 = p1.add(&p2, params); + + let (x0, y0) = p1.into_xy().unwrap(); + let (x1, y1) = p2.into_xy().unwrap(); + let (x2, y2) = p3.into_xy().unwrap(); + + let mut cs = TestConstraintSystem::::new(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let num_x1 = AllocatedNum::alloc(cs.namespace(|| "x1"), || { + Ok(x1) + }).unwrap(); + let num_y1 = AllocatedNum::alloc(cs.namespace(|| "y1"), || { + Ok(y1) + }).unwrap(); + + let p1 = MontgomeryPoint { + x: num_x0.into(), + y: num_y0.into() + }; + + let p2 = MontgomeryPoint { + x: num_x1.into(), + y: num_y1.into() + }; + + let p3 = p1.add(cs.namespace(|| "addition"), &p2, params).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p3.x.get_value().unwrap() == x2); + assert!(p3.y.get_value().unwrap() == y2); + + cs.set("addition/yprime/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate yprime")); + cs.set("addition/yprime/num", y2); + assert!(cs.is_satisfied()); + + cs.set("addition/xprime/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate xprime")); + cs.set("addition/xprime/num", x2); + assert!(cs.is_satisfied()); + + cs.set("addition/lambda/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate lambda")); + } + } +} diff --git a/sapling-crypto/src/circuit/lookup.rs b/sapling-crypto/src/circuit/lookup.rs new file mode 100644 index 0000000..1ffc7f7 --- /dev/null +++ b/sapling-crypto/src/circuit/lookup.rs @@ -0,0 +1,307 @@ +use pairing::{Engine, Field}; +use super::*; +use super::num::{ + AllocatedNum, + Num +}; +use super::boolean::Boolean; +use bellman::{ + ConstraintSystem +}; + +// Synthesize the constants for each base pattern. +fn synth<'a, E: Engine, I>( + window_size: usize, + constants: I, + assignment: &mut [E::Fr] +) + where I: IntoIterator +{ + assert_eq!(assignment.len(), 1 << window_size); + + for (i, constant) in constants.into_iter().enumerate() { + let mut cur = assignment[i]; + cur.negate(); + cur.add_assign(constant); + assignment[i] = cur; + for (j, eval) in assignment.iter_mut().enumerate().skip(i + 1) { + if j & i == i { + eval.add_assign(&cur); + } + } + } +} + +/// Performs a 3-bit window table lookup. `bits` is in +/// little-endian order. +pub fn lookup3_xy( + mut cs: CS, + bits: &[Boolean], + coords: &[(E::Fr, E::Fr)] +) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> + where CS: ConstraintSystem +{ + assert_eq!(bits.len(), 3); + assert_eq!(coords.len(), 8); + + // Calculate the index into `coords` + let i = + match (bits[0].get_value(), bits[1].get_value(), bits[2].get_value()) { + (Some(a_value), Some(b_value), Some(c_value)) => { + let mut tmp = 0; + if a_value { + tmp += 1; + } + if b_value { + tmp += 2; + } + if c_value { + tmp += 4; + } + Some(tmp) + }, + _ => None + }; + + // Allocate the x-coordinate resulting from the lookup + let res_x = AllocatedNum::alloc( + cs.namespace(|| "x"), + || { + Ok(coords[*i.get()?].0) + } + )?; + + // Allocate the y-coordinate resulting from the lookup + let res_y = AllocatedNum::alloc( + cs.namespace(|| "y"), + || { + Ok(coords[*i.get()?].1) + } + )?; + + // Compute the coefficients for the lookup constraints + let mut x_coeffs = [E::Fr::zero(); 8]; + let mut y_coeffs = [E::Fr::zero(); 8]; + synth::(3, coords.iter().map(|c| &c.0), &mut x_coeffs); + synth::(3, coords.iter().map(|c| &c.1), &mut y_coeffs); + + let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[1], &bits[2])?; + + let one = CS::one(); + + cs.enforce( + || "x-coordinate lookup", + |lc| lc + (x_coeffs[0b001], one) + + &bits[1].lc::(one, x_coeffs[0b011]) + + &bits[2].lc::(one, x_coeffs[0b101]) + + &precomp.lc::(one, x_coeffs[0b111]), + |lc| lc + &bits[0].lc::(one, E::Fr::one()), + |lc| lc + res_x.get_variable() + - (x_coeffs[0b000], one) + - &bits[1].lc::(one, x_coeffs[0b010]) + - &bits[2].lc::(one, x_coeffs[0b100]) + - &precomp.lc::(one, x_coeffs[0b110]), + ); + + cs.enforce( + || "y-coordinate lookup", + |lc| lc + (y_coeffs[0b001], one) + + &bits[1].lc::(one, y_coeffs[0b011]) + + &bits[2].lc::(one, y_coeffs[0b101]) + + &precomp.lc::(one, y_coeffs[0b111]), + |lc| lc + &bits[0].lc::(one, E::Fr::one()), + |lc| lc + res_y.get_variable() + - (y_coeffs[0b000], one) + - &bits[1].lc::(one, y_coeffs[0b010]) + - &bits[2].lc::(one, y_coeffs[0b100]) + - &precomp.lc::(one, y_coeffs[0b110]), + ); + + Ok((res_x, res_y)) +} + +/// Performs a 3-bit window table lookup, where +/// one of the bits is a sign bit. +pub fn lookup3_xy_with_conditional_negation( + mut cs: CS, + bits: &[Boolean], + coords: &[(E::Fr, E::Fr)] +) -> Result<(Num, Num), SynthesisError> + where CS: ConstraintSystem +{ + assert_eq!(bits.len(), 3); + assert_eq!(coords.len(), 4); + + // Calculate the index into `coords` + let i = + match (bits[0].get_value(), bits[1].get_value()) { + (Some(a_value), Some(b_value)) => { + let mut tmp = 0; + if a_value { + tmp += 1; + } + if b_value { + tmp += 2; + } + Some(tmp) + }, + _ => None + }; + + // Allocate the y-coordinate resulting from the lookup + // and conditional negation + let y = AllocatedNum::alloc( + cs.namespace(|| "y"), + || { + let mut tmp = coords[*i.get()?].1; + if *bits[2].get_value().get()? { + tmp.negate(); + } + Ok(tmp) + } + )?; + + let one = CS::one(); + + // Compute the coefficients for the lookup constraints + let mut x_coeffs = [E::Fr::zero(); 4]; + let mut y_coeffs = [E::Fr::zero(); 4]; + synth::(2, coords.iter().map(|c| &c.0), &mut x_coeffs); + synth::(2, coords.iter().map(|c| &c.1), &mut y_coeffs); + + let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[0], &bits[1])?; + + let x = Num::zero() + .add_bool_with_coeff(one, &Boolean::constant(true), x_coeffs[0b00]) + .add_bool_with_coeff(one, &bits[0], x_coeffs[0b01]) + .add_bool_with_coeff(one, &bits[1], x_coeffs[0b10]) + .add_bool_with_coeff(one, &precomp, x_coeffs[0b11]); + + let y_lc = precomp.lc::(one, y_coeffs[0b11]) + + &bits[1].lc::(one, y_coeffs[0b10]) + + &bits[0].lc::(one, y_coeffs[0b01]) + + (y_coeffs[0b00], one); + + cs.enforce( + || "y-coordinate lookup", + |lc| lc + &y_lc + &y_lc, + |lc| lc + &bits[2].lc::(one, E::Fr::one()), + |lc| lc + &y_lc - y.get_variable() + ); + + Ok((x, y.into())) +} + +#[cfg(test)] +mod test { + use rand::{SeedableRng, Rand, Rng, XorShiftRng}; + use super::*; + use ::circuit::test::*; + use ::circuit::boolean::{Boolean, AllocatedBit}; + use pairing::bls12_381::{Bls12, Fr}; + + #[test] + fn test_lookup3_xy() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0656]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let a_val = rng.gen(); + let a = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_val)).unwrap() + ); + + let b_val = rng.gen(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_val)).unwrap() + ); + + let c_val = rng.gen(); + let c = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "c"), Some(c_val)).unwrap() + ); + + let bits = vec![a, b, c]; + + let points: Vec<(Fr, Fr)> = (0..8).map(|_| (rng.gen(), rng.gen())).collect(); + + let res = lookup3_xy(&mut cs, &bits, &points).unwrap(); + + assert!(cs.is_satisfied()); + + let mut index = 0; + if a_val { index += 1 } + if b_val { index += 2 } + if c_val { index += 4 } + + assert_eq!(res.0.get_value().unwrap(), points[index].0); + assert_eq!(res.1.get_value().unwrap(), points[index].1); + } + } + + #[test] + fn test_lookup3_xy_with_conditional_negation() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let a_val = rng.gen(); + let a = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_val)).unwrap() + ); + + let b_val = rng.gen(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_val)).unwrap() + ); + + let c_val = rng.gen(); + let c = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "c"), Some(c_val)).unwrap() + ); + + let bits = vec![a, b, c]; + + let points: Vec<(Fr, Fr)> = (0..4).map(|_| (rng.gen(), rng.gen())).collect(); + + let res = lookup3_xy_with_conditional_negation(&mut cs, &bits, &points).unwrap(); + + assert!(cs.is_satisfied()); + + let mut index = 0; + if a_val { index += 1 } + if b_val { index += 2 } + + assert_eq!(res.0.get_value().unwrap(), points[index].0); + let mut tmp = points[index].1; + if c_val { tmp.negate() } + assert_eq!(res.1.get_value().unwrap(), tmp); + } + } + + #[test] + fn test_synth() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let window_size = 4; + + let mut assignment = vec![Fr::zero(); 1 << window_size]; + let constants: Vec<_> = (0..(1 << window_size)).map(|_| Fr::rand(&mut rng)).collect(); + + synth::(window_size, &constants, &mut assignment); + + for b in 0..(1 << window_size) { + let mut acc = Fr::zero(); + + for j in 0..(1 << window_size) { + if j & b == j { + acc.add_assign(&assignment[j]); + } + } + + assert_eq!(acc, constants[b]); + } + } +} diff --git a/sapling-crypto/src/circuit/mod.rs b/sapling-crypto/src/circuit/mod.rs new file mode 100644 index 0000000..fe0fe50 --- /dev/null +++ b/sapling-crypto/src/circuit/mod.rs @@ -0,0 +1,39 @@ +#[cfg(test)] +pub mod test; + +pub mod boolean; +pub mod multieq; +pub mod uint32; +pub mod blake2s; +pub mod num; +pub mod lookup; +pub mod ecc; +pub mod pedersen_hash; +pub mod multipack; +pub mod sha256; + +pub mod sapling; +pub mod sprout; + +use bellman::{ + SynthesisError +}; + +// TODO: This should probably be removed and we +// should use existing helper methods on `Option` +// for mapping with an error. +/// This basically is just an extension to `Option` +/// which allows for a convenient mapping to an +/// error on `None`. +trait Assignment { + fn get(&self) -> Result<&T, SynthesisError>; +} + +impl Assignment for Option { + fn get(&self) -> Result<&T, SynthesisError> { + match *self { + Some(ref v) => Ok(v), + None => Err(SynthesisError::AssignmentMissing) + } + } +} diff --git a/sapling-crypto/src/circuit/multieq.rs b/sapling-crypto/src/circuit/multieq.rs new file mode 100644 index 0000000..0f9c755 --- /dev/null +++ b/sapling-crypto/src/circuit/multieq.rs @@ -0,0 +1,137 @@ +use pairing::{ + Engine, + Field, + PrimeField +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination, + Variable +}; + +pub struct MultiEq>{ + cs: CS, + ops: usize, + bits_used: usize, + lhs: LinearCombination, + rhs: LinearCombination, +} + +impl> MultiEq { + pub fn new(cs: CS) -> Self { + MultiEq { + cs: cs, + ops: 0, + bits_used: 0, + lhs: LinearCombination::zero(), + rhs: LinearCombination::zero() + } + } + + fn accumulate(&mut self) + { + let ops = self.ops; + let lhs = self.lhs.clone(); + let rhs = self.rhs.clone(); + self.cs.enforce( + || format!("multieq {}", ops), + |_| lhs, + |lc| lc + CS::one(), + |_| rhs + ); + self.lhs = LinearCombination::zero(); + self.rhs = LinearCombination::zero(); + self.bits_used = 0; + self.ops += 1; + } + + pub fn enforce_equal( + &mut self, + num_bits: usize, + lhs: &LinearCombination, + rhs: &LinearCombination + ) + { + // Check if we will exceed the capacity + if (E::Fr::CAPACITY as usize) <= (self.bits_used + num_bits) { + self.accumulate(); + } + + assert!((E::Fr::CAPACITY as usize) > (self.bits_used + num_bits)); + + let coeff = E::Fr::from_str("2").unwrap().pow(&[self.bits_used as u64]); + self.lhs = self.lhs.clone() + (coeff, lhs); + self.rhs = self.rhs.clone() + (coeff, rhs); + self.bits_used += num_bits; + } +} + +impl> Drop for MultiEq { + fn drop(&mut self) { + if self.bits_used > 0 { + self.accumulate(); + } + } +} + +impl> ConstraintSystem for MultiEq +{ + type Root = Self; + + fn one() -> Variable { + CS::one() + } + + fn alloc( + &mut self, + annotation: A, + f: F + ) -> Result + where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + { + self.cs.alloc(annotation, f) + } + + fn alloc_input( + &mut self, + annotation: A, + f: F + ) -> Result + where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + { + self.cs.alloc_input(annotation, f) + } + + fn enforce( + &mut self, + annotation: A, + a: LA, + b: LB, + c: LC + ) + where A: FnOnce() -> AR, AR: Into, + LA: FnOnce(LinearCombination) -> LinearCombination, + LB: FnOnce(LinearCombination) -> LinearCombination, + LC: FnOnce(LinearCombination) -> LinearCombination + { + self.cs.enforce(annotation, a, b, c) + } + + fn push_namespace(&mut self, name_fn: N) + where NR: Into, N: FnOnce() -> NR + { + self.cs.get_root().push_namespace(name_fn) + } + + fn pop_namespace(&mut self) + { + self.cs.get_root().pop_namespace() + } + + fn get_root(&mut self) -> &mut Self::Root + { + self + } +} diff --git a/sapling-crypto/src/circuit/multipack.rs b/sapling-crypto/src/circuit/multipack.rs new file mode 100644 index 0000000..54d4138 --- /dev/null +++ b/sapling-crypto/src/circuit/multipack.rs @@ -0,0 +1,113 @@ +use pairing::{Engine, Field, PrimeField}; +use bellman::{ConstraintSystem, SynthesisError}; +use super::boolean::{Boolean}; +use super::num::Num; +use super::Assignment; + +/// Takes a sequence of booleans and exposes them as compact +/// public inputs +pub fn pack_into_inputs( + mut cs: CS, + bits: &[Boolean] +) -> Result<(), SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + for (i, bits) in bits.chunks(E::Fr::CAPACITY as usize).enumerate() + { + let mut num = Num::::zero(); + let mut coeff = E::Fr::one(); + for bit in bits { + num = num.add_bool_with_coeff(CS::one(), bit, coeff); + + coeff.double(); + } + + let input = cs.alloc_input(|| format!("input {}", i), || { + Ok(*num.get_value().get()?) + })?; + + // num * 1 = input + cs.enforce( + || format!("packing constraint {}", i), + |_| num.lc(E::Fr::one()), + |lc| lc + CS::one(), + |lc| lc + input + ); + } + + Ok(()) +} + +pub fn bytes_to_bits(bytes: &[u8]) -> Vec +{ + bytes.iter() + .flat_map(|&v| (0..8).rev().map(move |i| (v >> i) & 1 == 1)) + .collect() +} + +pub fn bytes_to_bits_le(bytes: &[u8]) -> Vec +{ + bytes.iter() + .flat_map(|&v| (0..8).map(move |i| (v >> i) & 1 == 1)) + .collect() +} + +pub fn compute_multipacking( + bits: &[bool] +) -> Vec +{ + let mut result = vec![]; + + for bits in bits.chunks(E::Fr::CAPACITY as usize) + { + let mut cur = E::Fr::zero(); + let mut coeff = E::Fr::one(); + + for bit in bits { + if *bit { + cur.add_assign(&coeff); + } + + coeff.double(); + } + + result.push(cur); + } + + result +} + +#[test] +fn test_multipacking() { + use rand::{SeedableRng, Rng, XorShiftRng}; + use bellman::{ConstraintSystem}; + use pairing::bls12_381::{Bls12}; + use ::circuit::test::*; + use super::boolean::{AllocatedBit, Boolean}; + + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for num_bits in 0..1500 { + let mut cs = TestConstraintSystem::::new(); + + let bits: Vec = (0..num_bits).map(|_| rng.gen()).collect(); + + let circuit_bits = bits.iter().enumerate() + .map(|(i, &b)| { + Boolean::from( + AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + Some(b) + ).unwrap() + ) + }) + .collect::>(); + + let expected_inputs = compute_multipacking::(&bits); + + pack_into_inputs(cs.namespace(|| "pack"), &circuit_bits).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.verify(&expected_inputs)); + } +} diff --git a/sapling-crypto/src/circuit/num.rs b/sapling-crypto/src/circuit/num.rs new file mode 100644 index 0000000..53a2f6c --- /dev/null +++ b/sapling-crypto/src/circuit/num.rs @@ -0,0 +1,622 @@ +use pairing::{ + Engine, + Field, + PrimeField, + PrimeFieldRepr, + BitIterator +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination, + Variable +}; + +use super::{ + Assignment +}; + +use super::boolean::{ + self, + Boolean, + AllocatedBit +}; + +pub struct AllocatedNum { + value: Option, + variable: Variable +} + +impl Clone for AllocatedNum { + fn clone(&self) -> Self { + AllocatedNum { + value: self.value, + variable: self.variable + } + } +} + +impl AllocatedNum { + pub fn alloc( + mut cs: CS, + value: F, + ) -> Result + where CS: ConstraintSystem, + F: FnOnce() -> Result + { + let mut new_value = None; + let var = cs.alloc(|| "num", || { + let tmp = value()?; + + new_value = Some(tmp); + + Ok(tmp) + })?; + + Ok(AllocatedNum { + value: new_value, + variable: var + }) + } + + pub fn inputize( + &self, + mut cs: CS + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + let input = cs.alloc_input( + || "input variable", + || { + Ok(*self.value.get()?) + } + )?; + + cs.enforce( + || "enforce input is correct", + |lc| lc + input, + |lc| lc + CS::one(), + |lc| lc + self.variable + ); + + Ok(()) + } + + /// Deconstructs this allocated number into its + /// boolean representation in little-endian bit + /// order, requiring that the representation + /// strictly exists "in the field" (i.e., a + /// congruency is not allowed.) + pub fn into_bits_le_strict( + &self, + mut cs: CS + ) -> Result, SynthesisError> + where CS: ConstraintSystem + { + pub fn kary_and( + mut cs: CS, + v: &[AllocatedBit] + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + assert!(v.len() > 0); + + // Let's keep this simple for now and just AND them all + // manually + let mut cur = None; + + for (i, v) in v.iter().enumerate() { + if cur.is_none() { + cur = Some(v.clone()); + } else { + cur = Some(AllocatedBit::and( + cs.namespace(|| format!("and {}", i)), + cur.as_ref().unwrap(), + v + )?); + } + } + + Ok(cur.expect("v.len() > 0")) + } + + // We want to ensure that the bit representation of a is + // less than or equal to r - 1. + let mut a = self.value.map(|e| BitIterator::new(e.into_repr())); + let mut b = E::Fr::char(); + b.sub_noborrow(&1.into()); + + let mut result = vec![]; + + // Runs of ones in r + let mut last_run = None; + let mut current_run = vec![]; + + let mut found_one = false; + let mut i = 0; + for b in BitIterator::new(b) { + let a_bit = a.as_mut().map(|e| e.next().unwrap()); + + // Skip over unset bits at the beginning + found_one |= b; + if !found_one { + // a_bit should also be false + a_bit.map(|e| assert!(!e)); + continue; + } + + if b { + // This is part of a run of ones. Let's just + // allocate the boolean with the expected value. + let a_bit = AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + a_bit + )?; + // ... and add it to the current run of ones. + current_run.push(a_bit.clone()); + result.push(a_bit); + } else { + if current_run.len() > 0 { + // This is the start of a run of zeros, but we need + // to k-ary AND against `last_run` first. + + if last_run.is_some() { + current_run.push(last_run.clone().unwrap()); + } + last_run = Some(kary_and( + cs.namespace(|| format!("run ending at {}", i)), + ¤t_run + )?); + current_run.truncate(0); + } + + // If `last_run` is true, `a` must be false, or it would + // not be in the field. + // + // If `last_run` is false, `a` can be true or false. + + let a_bit = AllocatedBit::alloc_conditionally( + cs.namespace(|| format!("bit {}", i)), + a_bit, + &last_run.as_ref().expect("char always starts with a one") + )?; + result.push(a_bit); + } + + i += 1; + } + + // char is prime, so we'll always end on + // a run of zeros. + assert_eq!(current_run.len(), 0); + + // Now, we have `result` in big-endian order. + // However, now we have to unpack self! + + let mut lc = LinearCombination::zero(); + let mut coeff = E::Fr::one(); + + for bit in result.iter().rev() { + lc = lc + (coeff, bit.get_variable()); + + coeff.double(); + } + + lc = lc - self.variable; + + cs.enforce( + || "unpacking constraint", + |lc| lc, + |lc| lc, + |_| lc + ); + + // Convert into booleans, and reverse for little-endian bit order + Ok(result.into_iter().map(|b| Boolean::from(b)).rev().collect()) + } + + /// Convert the allocated number into its little-endian representation. + /// Note that this does not strongly enforce that the commitment is + /// "in the field." + pub fn into_bits_le( + &self, + mut cs: CS + ) -> Result, SynthesisError> + where CS: ConstraintSystem + { + let bits = boolean::field_into_allocated_bits_le( + &mut cs, + self.value + )?; + + let mut lc = LinearCombination::zero(); + let mut coeff = E::Fr::one(); + + for bit in bits.iter() { + lc = lc + (coeff, bit.get_variable()); + + coeff.double(); + } + + lc = lc - self.variable; + + cs.enforce( + || "unpacking constraint", + |lc| lc, + |lc| lc, + |_| lc + ); + + Ok(bits.into_iter().map(|b| Boolean::from(b)).collect()) + } + + pub fn mul( + &self, + mut cs: CS, + other: &Self + ) -> Result + where CS: ConstraintSystem + { + let mut value = None; + + let var = cs.alloc(|| "product num", || { + let mut tmp = *self.value.get()?; + tmp.mul_assign(other.value.get()?); + + value = Some(tmp); + + Ok(tmp) + })?; + + // Constrain: a * b = ab + cs.enforce( + || "multiplication constraint", + |lc| lc + self.variable, + |lc| lc + other.variable, + |lc| lc + var + ); + + Ok(AllocatedNum { + value: value, + variable: var + }) + } + + pub fn square( + &self, + mut cs: CS + ) -> Result + where CS: ConstraintSystem + { + let mut value = None; + + let var = cs.alloc(|| "squared num", || { + let mut tmp = *self.value.get()?; + tmp.square(); + + value = Some(tmp); + + Ok(tmp) + })?; + + // Constrain: a * a = aa + cs.enforce( + || "squaring constraint", + |lc| lc + self.variable, + |lc| lc + self.variable, + |lc| lc + var + ); + + Ok(AllocatedNum { + value: value, + variable: var + }) + } + + pub fn assert_nonzero( + &self, + mut cs: CS + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + let inv = cs.alloc(|| "ephemeral inverse", || { + let tmp = *self.value.get()?; + + if tmp.is_zero() { + Err(SynthesisError::DivisionByZero) + } else { + Ok(tmp.inverse().unwrap()) + } + })?; + + // Constrain a * inv = 1, which is only valid + // iff a has a multiplicative inverse, untrue + // for zero. + cs.enforce( + || "nonzero assertion constraint", + |lc| lc + self.variable, + |lc| lc + inv, + |lc| lc + CS::one() + ); + + Ok(()) + } + + /// Takes two allocated numbers (a, b) and returns + /// (b, a) if the condition is true, and (a, b) + /// otherwise. + pub fn conditionally_reverse( + mut cs: CS, + a: &Self, + b: &Self, + condition: &Boolean + ) -> Result<(Self, Self), SynthesisError> + where CS: ConstraintSystem + { + let c = Self::alloc( + cs.namespace(|| "conditional reversal result 1"), + || { + if *condition.get_value().get()? { + Ok(*b.value.get()?) + } else { + Ok(*a.value.get()?) + } + } + )?; + + cs.enforce( + || "first conditional reversal", + |lc| lc + a.variable - b.variable, + |_| condition.lc(CS::one(), E::Fr::one()), + |lc| lc + a.variable - c.variable + ); + + let d = Self::alloc( + cs.namespace(|| "conditional reversal result 2"), + || { + if *condition.get_value().get()? { + Ok(*a.value.get()?) + } else { + Ok(*b.value.get()?) + } + } + )?; + + cs.enforce( + || "second conditional reversal", + |lc| lc + b.variable - a.variable, + |_| condition.lc(CS::one(), E::Fr::one()), + |lc| lc + b.variable - d.variable + ); + + Ok((c, d)) + } + + pub fn get_value(&self) -> Option { + self.value + } + + pub fn get_variable(&self) -> Variable { + self.variable + } +} + +pub struct Num { + value: Option, + lc: LinearCombination +} + +impl From> for Num { + fn from(num: AllocatedNum) -> Num { + Num { + value: num.value, + lc: LinearCombination::::zero() + num.variable + } + } +} + +impl Num { + pub fn zero() -> Self { + Num { + value: Some(E::Fr::zero()), + lc: LinearCombination::zero() + } + } + + pub fn get_value(&self) -> Option { + self.value + } + + pub fn lc(&self, coeff: E::Fr) -> LinearCombination { + LinearCombination::zero() + (coeff, &self.lc) + } + + pub fn add_bool_with_coeff( + self, + one: Variable, + bit: &Boolean, + coeff: E::Fr + ) -> Self + { + let newval = match (self.value, bit.get_value()) { + (Some(mut curval), Some(bval)) => { + if bval { + curval.add_assign(&coeff); + } + + Some(curval) + }, + _ => None + }; + + Num { + value: newval, + lc: self.lc + &bit.lc(one, coeff) + } + } +} + +#[cfg(test)] +mod test { + use rand::{SeedableRng, Rand, Rng, XorShiftRng}; + use bellman::{ConstraintSystem}; + use pairing::bls12_381::{Bls12, Fr}; + use pairing::{Field, PrimeField, BitIterator}; + use ::circuit::test::*; + use super::{AllocatedNum, Boolean}; + + #[test] + fn test_allocated_num() { + let mut cs = TestConstraintSystem::::new(); + + AllocatedNum::alloc(&mut cs, || Ok(Fr::one())).unwrap(); + + assert!(cs.get("num") == Fr::one()); + } + + #[test] + fn test_num_squaring() { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap(); + let n2 = n.square(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("squared num") == Fr::from_str("9").unwrap()); + assert!(n2.value.unwrap() == Fr::from_str("9").unwrap()); + cs.set("squared num", Fr::from_str("10").unwrap()); + assert!(!cs.is_satisfied()); + } + + #[test] + fn test_num_multiplication() { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::from_str("12").unwrap())).unwrap(); + let n2 = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::from_str("10").unwrap())).unwrap(); + let n3 = n.mul(&mut cs, &n2).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("product num") == Fr::from_str("120").unwrap()); + assert!(n3.value.unwrap() == Fr::from_str("120").unwrap()); + cs.set("product num", Fr::from_str("121").unwrap()); + assert!(!cs.is_satisfied()); + } + + #[test] + fn test_num_conditional_reversal() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + { + let mut cs = TestConstraintSystem::::new(); + + let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(rng.gen())).unwrap(); + let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(rng.gen())).unwrap(); + let condition = Boolean::constant(false); + let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(a.value.unwrap(), c.value.unwrap()); + assert_eq!(b.value.unwrap(), d.value.unwrap()); + } + + { + let mut cs = TestConstraintSystem::::new(); + + let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(rng.gen())).unwrap(); + let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(rng.gen())).unwrap(); + let condition = Boolean::constant(true); + let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(a.value.unwrap(), d.value.unwrap()); + assert_eq!(b.value.unwrap(), c.value.unwrap()); + } + } + + #[test] + fn test_num_nonzero() { + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap(); + n.assert_nonzero(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + cs.set("ephemeral inverse", Fr::from_str("3").unwrap()); + assert!(cs.which_is_unsatisfied() == Some("nonzero assertion constraint")); + } + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::zero())).unwrap(); + assert!(n.assert_nonzero(&mut cs).is_err()); + } + } + + #[test] + fn test_into_bits_strict() { + let mut negone = Fr::one(); + negone.negate(); + + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(negone)).unwrap(); + n.into_bits_le_strict(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + + // make the bit representation the characteristic + cs.set("bit 254/boolean", Fr::one()); + + // this makes the conditional boolean constraint fail + assert_eq!(cs.which_is_unsatisfied().unwrap(), "bit 254/boolean constraint"); + } + + #[test] + fn test_into_bits() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for i in 0..200 { + let r = Fr::rand(&mut rng); + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap(); + + let bits = if i % 2 == 0 { + n.into_bits_le(&mut cs).unwrap() + } else { + n.into_bits_le_strict(&mut cs).unwrap() + }; + + assert!(cs.is_satisfied()); + + for (b, a) in BitIterator::new(r.into_repr()).skip(1).zip(bits.iter().rev()) { + if let &Boolean::Is(ref a) = a { + assert_eq!(b, a.get_value().unwrap()); + } else { + unreachable!() + } + } + + cs.set("num", Fr::rand(&mut rng)); + assert!(!cs.is_satisfied()); + cs.set("num", r); + assert!(cs.is_satisfied()); + + for i in 0..Fr::NUM_BITS { + let name = format!("bit {}/boolean", i); + let cur = cs.get(&name); + let mut tmp = Fr::one(); + tmp.sub_assign(&cur); + cs.set(&name, tmp); + assert!(!cs.is_satisfied()); + cs.set(&name, cur); + assert!(cs.is_satisfied()); + } + } + } +} diff --git a/sapling-crypto/src/circuit/pedersen_hash.rs b/sapling-crypto/src/circuit/pedersen_hash.rs new file mode 100644 index 0000000..eb1745f --- /dev/null +++ b/sapling-crypto/src/circuit/pedersen_hash.rs @@ -0,0 +1,194 @@ +use super::*; +use super::ecc::{ + MontgomeryPoint, + EdwardsPoint +}; +use super::boolean::Boolean; +use ::jubjub::*; +use bellman::{ + ConstraintSystem +}; +use super::lookup::*; +pub use pedersen_hash::Personalization; + +impl Personalization { + fn get_constant_bools(&self) -> Vec { + self.get_bits() + .into_iter() + .map(|e| Boolean::constant(e)) + .collect() + } +} + +pub fn pedersen_hash( + mut cs: CS, + personalization: Personalization, + bits: &[Boolean], + params: &E::Params +) -> Result, SynthesisError> + where CS: ConstraintSystem +{ + let personalization = personalization.get_constant_bools(); + assert_eq!(personalization.len(), 6); + + let mut edwards_result = None; + let mut bits = personalization.iter().chain(bits.iter()); + let mut segment_generators = params.pedersen_circuit_generators().iter(); + let boolean_false = Boolean::constant(false); + + let mut segment_i = 0; + loop { + let mut segment_result = None; + let mut segment_windows = &segment_generators.next() + .expect("enough segments")[..]; + + let mut window_i = 0; + while let Some(a) = bits.next() { + let b = bits.next().unwrap_or(&boolean_false); + let c = bits.next().unwrap_or(&boolean_false); + + let tmp = lookup3_xy_with_conditional_negation( + cs.namespace(|| format!("segment {}, window {}", segment_i, window_i)), + &[a.clone(), b.clone(), c.clone()], + &segment_windows[0] + )?; + + let tmp = MontgomeryPoint::interpret_unchecked(tmp.0, tmp.1); + + match segment_result { + None => { + segment_result = Some(tmp); + }, + Some(ref mut segment_result) => { + *segment_result = tmp.add( + cs.namespace(|| format!("addition of segment {}, window {}", segment_i, window_i)), + segment_result, + params + )?; + } + } + + segment_windows = &segment_windows[1..]; + + if segment_windows.len() == 0 { + break; + } + + window_i += 1; + } + + match segment_result { + Some(segment_result) => { + // Convert this segment into twisted Edwards form. + let segment_result = segment_result.into_edwards( + cs.namespace(|| format!("conversion of segment {} into edwards", segment_i)), + params + )?; + + match edwards_result { + Some(ref mut edwards_result) => { + *edwards_result = segment_result.add( + cs.namespace(|| format!("addition of segment {} to accumulator", segment_i)), + edwards_result, + params + )?; + }, + None => { + edwards_result = Some(segment_result); + } + } + }, + None => { + // We didn't process any new bits. + break; + } + } + + segment_i += 1; + } + + Ok(edwards_result.unwrap()) +} + +#[cfg(test)] +mod test { + use rand::{SeedableRng, Rng, XorShiftRng}; + use super::*; + use ::circuit::test::*; + use ::circuit::boolean::{Boolean, AllocatedBit}; + use pairing::bls12_381::{Bls12, Fr}; + use pairing::PrimeField; + + #[test] + fn test_pedersen_hash_constraints() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubBls12::new(); + let mut cs = TestConstraintSystem::::new(); + + let input: Vec = (0..(Fr::NUM_BITS * 2)).map(|_| rng.gen()).collect(); + + let input_bools: Vec = input.iter().enumerate().map(|(i, b)| { + Boolean::from( + AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap() + ) + }).collect(); + + pedersen_hash( + cs.namespace(|| "pedersen hash"), + Personalization::NoteCommitment, + &input_bools, + params + ).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 1377); + } + + #[test] + fn test_pedersen_hash() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubBls12::new(); + + for length in 0..751 { + for _ in 0..5 { + let mut input: Vec = (0..length).map(|_| rng.gen()).collect(); + + let mut cs = TestConstraintSystem::::new(); + + let input_bools: Vec = input.iter().enumerate().map(|(i, b)| { + Boolean::from( + AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap() + ) + }).collect(); + + let res = pedersen_hash( + cs.namespace(|| "pedersen hash"), + Personalization::MerkleTree(1), + &input_bools, + params + ).unwrap(); + + assert!(cs.is_satisfied()); + + let expected = ::pedersen_hash::pedersen_hash::( + Personalization::MerkleTree(1), + input.clone().into_iter(), + params + ).into_xy(); + + assert_eq!(res.get_x().get_value().unwrap(), expected.0); + assert_eq!(res.get_y().get_value().unwrap(), expected.1); + + // Test against the output of a different personalization + let unexpected = ::pedersen_hash::pedersen_hash::( + Personalization::MerkleTree(0), + input.into_iter(), + params + ).into_xy(); + + assert!(res.get_x().get_value().unwrap() != unexpected.0); + assert!(res.get_y().get_value().unwrap() != unexpected.1); + } + } + } +} diff --git a/sapling-crypto/src/circuit/sapling/mod.rs b/sapling-crypto/src/circuit/sapling/mod.rs new file mode 100644 index 0000000..31bab6b --- /dev/null +++ b/sapling-crypto/src/circuit/sapling/mod.rs @@ -0,0 +1,815 @@ +use pairing::{ + PrimeField, + PrimeFieldRepr, + Field, +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + Circuit +}; + +use jubjub::{ + JubjubEngine, + FixedGenerators +}; + +use constants; + +use primitives::{ + ValueCommitment, + ProofGenerationKey, + PaymentAddress +}; + +use super::Assignment; +use super::boolean; +use super::ecc; +use super::pedersen_hash; +use super::blake2s; +use super::num; +use super::multipack; + +/// This is an instance of the `Spend` circuit. +pub struct Spend<'a, E: JubjubEngine> { + pub params: &'a E::Params, + + /// Pedersen commitment to the value being spent + pub value_commitment: Option>, + + /// Key required to construct proofs for spending notes + /// for a particular spending key + pub proof_generation_key: Option>, + + /// The payment address associated with the note + pub payment_address: Option>, + + /// The randomness of the note commitment + pub commitment_randomness: Option, + + /// Re-randomization of the public key + pub ar: Option, + + /// The authentication path of the commitment in the tree + pub auth_path: Vec>, + + /// The anchor; the root of the tree. If the note being + /// spent is zero-value, this can be anything. + pub anchor: Option +} + +/// This is an output circuit instance. +pub struct Output<'a, E: JubjubEngine> { + pub params: &'a E::Params, + + /// Pedersen commitment to the value being spent + pub value_commitment: Option>, + + /// The payment address of the recipient + pub payment_address: Option>, + + /// The randomness used to hide the note commitment data + pub commitment_randomness: Option, + + /// The ephemeral secret key for DH with recipient + pub esk: Option +} + +/// Exposes a Pedersen commitment to the value as an +/// input to the circuit +fn expose_value_commitment( + mut cs: CS, + value_commitment: Option>, + params: &E::Params +) -> Result, SynthesisError> + where E: JubjubEngine, + CS: ConstraintSystem +{ + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_boolean_vec_le( + cs.namespace(|| "value"), + value_commitment.as_ref().map(|c| c.value) + )?; + + // Compute the note value in the exponent + let value = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + params + )?; + + // Booleanize the randomness. This does not ensure + // the bit representation is "in the field" because + // it doesn't matter for security. + let rcv = boolean::field_into_boolean_vec_le( + cs.namespace(|| "rcv"), + value_commitment.as_ref().map(|c| c.randomness) + )?; + + // Compute the randomness in the exponent + let rcv = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of rcv"), + FixedGenerators::ValueCommitmentRandomness, + &rcv, + params + )?; + + // Compute the Pedersen commitment to the value + let cv = value.add( + cs.namespace(|| "computation of cv"), + &rcv, + params + )?; + + // Expose the commitment as an input to the circuit + cv.inputize(cs.namespace(|| "commitment point"))?; + + Ok(value_bits) +} + +impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + // Prover witnesses ak (ensures that it's on the curve) + let ak = ecc::EdwardsPoint::witness( + cs.namespace(|| "ak"), + self.proof_generation_key.as_ref().map(|k| k.ak.clone()), + self.params + )?; + + // There are no sensible attacks on small order points + // of ak (that we're aware of!) but it's a cheap check, + // so we do it. + ak.assert_not_small_order( + cs.namespace(|| "ak not small order"), + self.params + )?; + + // Rerandomize ak and expose it as an input to the circuit + { + let ar = boolean::field_into_boolean_vec_le( + cs.namespace(|| "ar"), + self.ar + )?; + + // Compute the randomness in the exponent + let ar = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for the signing key"), + FixedGenerators::SpendingKeyGenerator, + &ar, + self.params + )?; + + let rk = ak.add( + cs.namespace(|| "computation of rk"), + &ar, + self.params + )?; + + rk.inputize(cs.namespace(|| "rk"))?; + } + + // Compute nk = [nsk] ProofGenerationKey + let nk; + { + // Witness nsk as bits + let nsk = boolean::field_into_boolean_vec_le( + cs.namespace(|| "nsk"), + self.proof_generation_key.as_ref().map(|k| k.nsk.clone()) + )?; + + // NB: We don't ensure that the bit representation of nsk + // is "in the field" (Fs) because it's not used except to + // demonstrate the prover knows it. If they know a + // congruency then that's equivalent. + + // Compute nk = [nsk] ProvingPublicKey + nk = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of nk"), + FixedGenerators::ProofGenerationKey, + &nsk, + self.params + )?; + } + + // This is the "viewing key" preimage for CRH^ivk + let mut ivk_preimage = vec![]; + + // Place ak in the preimage for CRH^ivk + ivk_preimage.extend( + ak.repr(cs.namespace(|| "representation of ak"))? + ); + + // This is the nullifier preimage for PRF^nf + let mut nf_preimage = vec![]; + + // Extend ivk and nf preimages with the representation of + // nk. + { + let repr_nk = nk.repr( + cs.namespace(|| "representation of nk") + )?; + + ivk_preimage.extend(repr_nk.iter().cloned()); + nf_preimage.extend(repr_nk); + } + + assert_eq!(ivk_preimage.len(), 512); + assert_eq!(nf_preimage.len(), 256); + + // Compute the incoming viewing key ivk + let mut ivk = blake2s::blake2s( + cs.namespace(|| "computation of ivk"), + &ivk_preimage, + constants::CRH_IVK_PERSONALIZATION + )?; + + // drop_5 to ensure it's in the field + ivk.truncate(E::Fs::CAPACITY as usize); + + // Witness g_d, checking that it's on the curve. + let g_d = { + // This binding is to avoid a weird edge case in Rust's + // ownership/borrowing rules. self is partially moved + // above, but the closure for and_then will have to + // move self (or a reference to self) to reference + // self.params, so we have to copy self.params here. + let params = self.params; + + ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.payment_address.as_ref().and_then(|a| a.g_d(params)), + self.params + )? + }; + + // Check that g_d is not small order. Technically, this check + // is already done in the Output circuit, and this proof ensures + // g_d is bound to a product of that check, but for defense in + // depth let's check it anyway. It's cheap. + g_d.assert_not_small_order( + cs.namespace(|| "g_d not small order"), + self.params + )?; + + // Compute pk_d = g_d^ivk + let pk_d = g_d.mul( + cs.namespace(|| "compute pk_d"), + &ivk, + self.params + )?; + + // Compute note contents: + // value (in big endian) followed by g_d and pk_d + let mut note_contents = vec![]; + + // Handle the value; we'll need it later for the + // dummy input check. + let mut value_num = num::Num::zero(); + { + // Get the value in little-endian bit order + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params + )?; + + // Compute the note's value as a linear combination + // of the bits. + let mut coeff = E::Fr::one(); + for bit in &value_bits { + value_num = value_num.add_bool_with_coeff( + CS::one(), + bit, + coeff + ); + coeff.double(); + } + + // Place the value in the note + note_contents.extend(value_bits); + } + + // Place g_d in the note + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); + + // Place pk_d in the note + note_contents.extend( + pk_d.repr(cs.namespace(|| "representation of pk_d"))? + ); + + assert_eq!( + note_contents.len(), + 64 + // value + 256 + // g_d + 256 // p_d + ); + + // Compute the hash of the note contents + let mut cm = pedersen_hash::pedersen_hash( + cs.namespace(|| "note content hash"), + pedersen_hash::Personalization::NoteCommitment, + ¬e_contents, + self.params + )?; + + { + // Booleanize the randomness for the note commitment + let rcm = boolean::field_into_boolean_vec_le( + cs.namespace(|| "rcm"), + self.commitment_randomness + )?; + + // Compute the note commitment randomness in the exponent + let rcm = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &rcm, + self.params + )?; + + // Randomize the note commitment. Pedersen hashes are not + // themselves hiding commitments. + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &rcm, + self.params + )?; + } + + // This will store (least significant bit first) + // the position of the note in the tree, for use + // in nullifier computation. + let mut position_bits = vec![]; + + // This is an injective encoding, as cur is a + // point in the prime order subgroup. + let mut cur = cm.get_x().clone(); + + // Ascend the merkle tree authentication path + for (i, e) in self.auth_path.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); + + // Determines if the current subtree is the "right" leaf at this + // depth of the tree. + let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "position bit"), + e.map(|e| e.1) + )?); + + // Push this boolean for nullifier computation later + position_bits.push(cur_is_right.clone()); + + // Witness the authentication path element adjacent + // at this depth. + let path_element = num::AllocatedNum::alloc( + cs.namespace(|| "path element"), + || { + Ok(e.get()?.0) + } + )?; + + // Swap the two if the current subtree is on the right + let (xl, xr) = num::AllocatedNum::conditionally_reverse( + cs.namespace(|| "conditional reversal of preimage"), + &cur, + &path_element, + &cur_is_right + )?; + + // We don't need to be strict, because the function is + // collision-resistant. If the prover witnesses a congruency, + // they will be unable to find an authentication path in the + // tree with high probability. + let mut preimage = vec![]; + preimage.extend(xl.into_bits_le(cs.namespace(|| "xl into bits"))?); + preimage.extend(xr.into_bits_le(cs.namespace(|| "xr into bits"))?); + + // Compute the new subtree value + cur = pedersen_hash::pedersen_hash( + cs.namespace(|| "computation of pedersen hash"), + pedersen_hash::Personalization::MerkleTree(i), + &preimage, + self.params + )?.get_x().clone(); // Injective encoding + } + + { + let real_anchor_value = self.anchor; + + // Allocate the "real" anchor that will be exposed. + let rt = num::AllocatedNum::alloc( + cs.namespace(|| "conditional anchor"), + || { + Ok(*real_anchor_value.get()?) + } + )?; + + // (cur - rt) * value = 0 + // if value is zero, cur and rt can be different + // if value is nonzero, they must be equal + cs.enforce( + || "conditionally enforce correct root", + |lc| lc + cur.get_variable() - rt.get_variable(), + |lc| lc + &value_num.lc(E::Fr::one()), + |lc| lc + ); + + // Expose the anchor + rt.inputize(cs.namespace(|| "anchor"))?; + } + + // Compute the cm + g^position for preventing + // faerie gold attacks + let mut rho = cm; + { + // Compute the position in the exponent + let position = ecc::fixed_base_multiplication( + cs.namespace(|| "g^position"), + FixedGenerators::NullifierPosition, + &position_bits, + self.params + )?; + + // Add the position to the commitment + rho = rho.add( + cs.namespace(|| "faerie gold prevention"), + &position, + self.params + )?; + } + + // Let's compute nf = BLAKE2s(nk || rho) + nf_preimage.extend( + rho.repr(cs.namespace(|| "representation of rho"))? + ); + + assert_eq!(nf_preimage.len(), 512); + + // Compute nf + let nf = blake2s::blake2s( + cs.namespace(|| "nf computation"), + &nf_preimage, + constants::PRF_NF_PERSONALIZATION + )?; + + multipack::pack_into_inputs(cs.namespace(|| "pack nullifier"), &nf) + } +} + +impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + // Let's start to construct our note, which contains + // value (big endian) + let mut note_contents = vec![]; + + // Expose the value commitment and place the value + // in the note. + note_contents.extend(expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params + )?); + + // Let's deal with g_d + { + let params = self.params; + + // Prover witnesses g_d, ensuring it's on the + // curve. + let g_d = ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.payment_address.as_ref().and_then(|a| a.g_d(params)), + self.params + )?; + + // g_d is ensured to be large order. The relationship + // between g_d and pk_d ultimately binds ivk to the + // note. If this were a small order point, it would + // not do this correctly, and the prover could + // double-spend by finding random ivk's that satisfy + // the relationship. + // + // Further, if it were small order, epk would be + // small order too! + g_d.assert_not_small_order( + cs.namespace(|| "g_d not small order"), + self.params + )?; + + // Extend our note contents with the representation of + // g_d. + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); + + // Booleanize our ephemeral secret key + let esk = boolean::field_into_boolean_vec_le( + cs.namespace(|| "esk"), + self.esk + )?; + + // Create the ephemeral public key from g_d. + let epk = g_d.mul( + cs.namespace(|| "epk computation"), + &esk, + self.params + )?; + + // Expose epk publicly. + epk.inputize(cs.namespace(|| "epk"))?; + } + + // Now let's deal with pk_d. We don't do any checks and + // essentially allow the prover to witness any 256 bits + // they would like. + { + // Just grab pk_d from the witness + let pk_d = self.payment_address.as_ref().map(|e| e.pk_d.into_xy()); + + // Witness the y-coordinate, encoded as little + // endian bits (to match the representation) + let y_contents = boolean::field_into_boolean_vec_le( + cs.namespace(|| "pk_d bits of y"), + pk_d.map(|e| e.1) + )?; + + // Witness the sign bit + let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "pk_d bit of x"), + pk_d.map(|e| e.0.into_repr().is_odd()) + )?); + + // Extend the note with pk_d representation + note_contents.extend(y_contents); + note_contents.push(sign_bit); + } + + assert_eq!( + note_contents.len(), + 64 + // value + 256 + // g_d + 256 // pk_d + ); + + // Compute the hash of the note contents + let mut cm = pedersen_hash::pedersen_hash( + cs.namespace(|| "note content hash"), + pedersen_hash::Personalization::NoteCommitment, + ¬e_contents, + self.params + )?; + + { + // Booleanize the randomness + let rcm = boolean::field_into_boolean_vec_le( + cs.namespace(|| "rcm"), + self.commitment_randomness + )?; + + // Compute the note commitment randomness in the exponent + let rcm = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &rcm, + self.params + )?; + + // Randomize our note commitment + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &rcm, + self.params + )?; + } + + // Only the x-coordinate of the output is revealed, + // since we know it is prime order, and we know that + // the x-coordinate is an injective encoding for + // prime-order elements. + cm.get_x().inputize(cs.namespace(|| "commitment"))?; + + Ok(()) + } +} + +#[test] +fn test_input_circuit_with_bls12_381() { + use pairing::{Field, BitIterator}; + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs, edwards}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let tree_depth = 32; + + for _ in 0..10 { + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + + let nsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + nsk: nsk.clone() + }; + + let viewing_key = proof_generation_key.into_viewing_key(params); + + let payment_address; + + loop { + let diversifier = ::primitives::Diversifier(rng.gen()); + + if let Some(p) = viewing_key.into_payment_address( + diversifier, + params + ) + { + payment_address = p; + break; + } + } + + let g_d = payment_address.diversifier.g_d(params).unwrap(); + let commitment_randomness: fs::Fs = rng.gen(); + let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; + let ar: fs::Fs = rng.gen(); + + { + let rk = viewing_key.rk(ar, params).into_xy(); + let expected_value_cm = value_commitment.cm(params).into_xy(); + let note = ::primitives::Note { + value: value_commitment.value, + g_d: g_d.clone(), + pk_d: payment_address.pk_d.clone(), + r: commitment_randomness.clone() + }; + + let mut position = 0u64; + let cm: Fr = note.cm(params); + let mut cur = cm.clone(); + + for (i, val) in auth_path.clone().into_iter().enumerate() + { + let (uncle, b) = val.unwrap(); + + let mut lhs = cur; + let mut rhs = uncle; + + if b { + ::std::mem::swap(&mut lhs, &mut rhs); + } + + let mut lhs: Vec = BitIterator::new(lhs.into_repr()).collect(); + let mut rhs: Vec = BitIterator::new(rhs.into_repr()).collect(); + + lhs.reverse(); + rhs.reverse(); + + cur = ::pedersen_hash::pedersen_hash::( + ::pedersen_hash::Personalization::MerkleTree(i), + lhs.into_iter() + .take(Fr::NUM_BITS as usize) + .chain(rhs.into_iter().take(Fr::NUM_BITS as usize)), + params + ).into_xy().0; + + if b { + position |= 1 << i; + } + } + + let expected_nf = note.nf(&viewing_key, position, params); + let expected_nf = multipack::bytes_to_bits_le(&expected_nf); + let expected_nf = multipack::compute_multipacking::(&expected_nf); + assert_eq!(expected_nf.len(), 2); + + let mut cs = TestConstraintSystem::::new(); + + let instance = Spend { + params: params, + value_commitment: Some(value_commitment.clone()), + proof_generation_key: Some(proof_generation_key.clone()), + payment_address: Some(payment_address.clone()), + commitment_randomness: Some(commitment_randomness), + ar: Some(ar), + auth_path: auth_path.clone(), + anchor: Some(cur) + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 98777); + assert_eq!(cs.hash(), "d37c738e83df5d9b0bb6495ac96abf21bcb2697477e2c15c2c7916ff7a3b6a89"); + + assert_eq!(cs.get("randomization of note commitment/x3/num"), cm); + + assert_eq!(cs.num_inputs(), 8); + assert_eq!(cs.get_input(0, "ONE"), Fr::one()); + assert_eq!(cs.get_input(1, "rk/x/input variable"), rk.0); + assert_eq!(cs.get_input(2, "rk/y/input variable"), rk.1); + assert_eq!(cs.get_input(3, "value commitment/commitment point/x/input variable"), expected_value_cm.0); + assert_eq!(cs.get_input(4, "value commitment/commitment point/y/input variable"), expected_value_cm.1); + assert_eq!(cs.get_input(5, "anchor/input variable"), cur); + assert_eq!(cs.get_input(6, "pack nullifier/input 0"), expected_nf[0]); + assert_eq!(cs.get_input(7, "pack nullifier/input 1"), expected_nf[1]); + } + } +} + +#[test] +fn test_output_circuit_with_bls12_381() { + use pairing::{Field}; + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs, edwards}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + + let nsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + nsk: nsk.clone() + }; + + let viewing_key = proof_generation_key.into_viewing_key(params); + + let payment_address; + + loop { + let diversifier = ::primitives::Diversifier(rng.gen()); + + if let Some(p) = viewing_key.into_payment_address( + diversifier, + params + ) + { + payment_address = p; + break; + } + } + + let commitment_randomness: fs::Fs = rng.gen(); + let esk: fs::Fs = rng.gen(); + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Output { + params: params, + value_commitment: Some(value_commitment.clone()), + payment_address: Some(payment_address.clone()), + commitment_randomness: Some(commitment_randomness), + esk: Some(esk.clone()) + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 7827); + assert_eq!(cs.hash(), "c26d5cdfe6ccd65c03390902c02e11393ea6bb96aae32a7f2ecb12eb9103faee"); + + let expected_cm = payment_address.create_note( + value_commitment.value, + commitment_randomness, + params + ).expect("should be valid").cm(params); + + let expected_value_cm = value_commitment.cm(params).into_xy(); + + let expected_epk = payment_address.g_d(params).expect("should be valid").mul(esk, params); + let expected_epk_xy = expected_epk.into_xy(); + + assert_eq!(cs.num_inputs(), 6); + assert_eq!(cs.get_input(0, "ONE"), Fr::one()); + assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm.0); + assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm.1); + assert_eq!(cs.get_input(3, "epk/x/input variable"), expected_epk_xy.0); + assert_eq!(cs.get_input(4, "epk/y/input variable"), expected_epk_xy.1); + assert_eq!(cs.get_input(5, "commitment/input variable"), expected_cm); + } + } +} diff --git a/sapling-crypto/src/circuit/sha256.rs b/sapling-crypto/src/circuit/sha256.rs new file mode 100644 index 0000000..7b55fc8 --- /dev/null +++ b/sapling-crypto/src/circuit/sha256.rs @@ -0,0 +1,417 @@ +use super::uint32::UInt32; +use super::multieq::MultiEq; +use super::boolean::Boolean; +use bellman::{ConstraintSystem, SynthesisError}; +use pairing::Engine; + +const ROUND_CONSTANTS: [u32; 64] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +]; + +const IV: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +]; + +pub fn sha256_block_no_padding( + mut cs: CS, + input: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert_eq!(input.len(), 512); + + Ok(sha256_compression_function( + &mut cs, + &input, + &get_sha256_iv() + )? + .into_iter() + .flat_map(|e| e.into_bits_be()) + .collect()) +} + +pub fn sha256( + mut cs: CS, + input: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, 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_sha256_iv(); + for (i, block) in padded.chunks(512).enumerate() { + cur = sha256_compression_function( + cs.namespace(|| format!("block {}", i)), + block, + &cur + )?; + } + + Ok(cur.into_iter() + .flat_map(|e| e.into_bits_be()) + .collect()) +} + +fn get_sha256_iv() -> Vec { + IV.iter().map(|&v| UInt32::constant(v)).collect() +} + +fn sha256_compression_function( + cs: CS, + input: &[Boolean], + current_hash_value: &[UInt32] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert_eq!(input.len(), 512); + assert_eq!(current_hash_value.len(), 8); + + let mut w = input.chunks(32) + .map(|e| UInt32::from_bits_be(e)) + .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..64 { + let cs = &mut cs.namespace(|| format!("w extension {}", i)); + + // s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3) + let mut s0 = w[i-15].rotr(7); + s0 = s0.xor( + cs.namespace(|| "first xor for s0"), + &w[i-15].rotr(18) + )?; + s0 = s0.xor( + cs.namespace(|| "second xor for s0"), + &w[i-15].shr(3) + )?; + + // s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor (w[i-2] rightshift 10) + let mut s1 = w[i-2].rotr(17); + s1 = s1.xor( + cs.namespace(|| "first xor for s1"), + &w[i-2].rotr(19) + )?; + s1 = s1.xor( + cs.namespace(|| "second xor for s1"), + &w[i-2].shr(10) + )?; + + let tmp = UInt32::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(), 64); + + enum Maybe { + Deferred(Vec), + Concrete(UInt32) + } + + impl Maybe { + fn compute( + self, + cs: M, + others: &[UInt32] + ) -> Result + where E: Engine, + CS: ConstraintSystem, + M: ConstraintSystem> + { + Ok(match self { + Maybe::Concrete(ref v) => { + return Ok(v.clone()) + }, + Maybe::Deferred(mut v) => { + v.extend(others.into_iter().cloned()); + UInt32::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..64 { + let cs = &mut cs.namespace(|| format!("compression round {}", i)); + + // S1 := (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25) + let new_e = e.compute(cs.namespace(|| "deferred e computation"), &[])?; + let mut s1 = new_e.rotr(6); + s1 = s1.xor( + cs.namespace(|| "first xor for s1"), + &new_e.rotr(11) + )?; + s1 = s1.xor( + cs.namespace(|| "second xor for s1"), + &new_e.rotr(25) + )?; + + // ch := (e and f) xor ((not e) and g) + let ch = UInt32::sha256_ch( + cs.namespace(|| "ch"), + &new_e, + &f, + &g + )?; + + // temp1 := h + S1 + ch + k[i] + w[i] + let temp1 = vec![ + h.clone(), + s1, + ch, + UInt32::constant(ROUND_CONSTANTS[i]), + w[i].clone() + ]; + + // S0 := (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22) + let new_a = a.compute(cs.namespace(|| "deferred a computation"), &[])?; + let mut s0 = new_a.rotr(2); + s0 = s0.xor( + cs.namespace(|| "first xor for s0"), + &new_a.rotr(13) + )?; + s0 = s0.xor( + cs.namespace(|| "second xor for s0"), + &new_a.rotr(22) + )?; + + // maj := (a and b) xor (a and c) xor (b and c) + let maj = UInt32::sha256_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 = 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 = e.compute( + cs.namespace(|| "deferred h4 computation"), + &[current_hash_value[4].clone()] + )?; + + let h5 = UInt32::addmany( + cs.namespace(|| "new h5"), + &[current_hash_value[5].clone(), f] + )?; + + let h6 = UInt32::addmany( + cs.namespace(|| "new h6"), + &[current_hash_value[6].clone(), g] + )?; + + let h7 = UInt32::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 circuit::boolean::AllocatedBit; + use pairing::bls12_381::Bls12; + use circuit::test::TestConstraintSystem; + use rand::{XorShiftRng, SeedableRng, Rng}; + + #[test] + fn test_blank_hash() { + let iv = get_sha256_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 = sha256_compression_function( + &mut cs, + &input_bits, + &iv + ).unwrap(); + let out_bits: Vec<_> = out.into_iter().flat_map(|e| e.into_bits_be()).collect(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 0); + + let expected = hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + + let mut out = out_bits.into_iter(); + for b in expected.into_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([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let iv = get_sha256_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.gen()) + ).unwrap() + ) + }).collect(); + + sha256_compression_function( + cs.namespace(|| "sha256"), + &input_bits, + &iv + ).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints() - 512, 25840); + } + + #[test] + fn test_against_vectors() { + use crypto::sha2::Sha256; + use crypto::digest::Digest; + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) + { + let mut h = Sha256::new(); + let data: Vec = (0..input_len).map(|_| rng.gen()).collect(); + h.input(&data); + let mut hash_result = [0u8; 32]; + h.result(&mut hash_result[..]); + + 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 = sha256(&mut cs, &input_bits).unwrap(); + + assert!(cs.is_satisfied()); + + let mut s = hash_result.as_ref().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/sapling-crypto/src/circuit/sprout/commitment.rs b/sapling-crypto/src/circuit/sprout/commitment.rs new file mode 100644 index 0000000..a32f05c --- /dev/null +++ b/sapling-crypto/src/circuit/sprout/commitment.rs @@ -0,0 +1,42 @@ +use pairing::{Engine}; +use bellman::{ConstraintSystem, SynthesisError}; +use circuit::sha256::{ + sha256 +}; +use circuit::boolean::{ + Boolean +}; + +pub fn note_comm( + cs: CS, + a_pk: &[Boolean], + value: &[Boolean], + rho: &[Boolean], + r: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert_eq!(a_pk.len(), 256); + assert_eq!(value.len(), 64); + assert_eq!(rho.len(), 256); + assert_eq!(r.len(), 256); + + let mut image = vec![]; + image.push(Boolean::constant(true)); + image.push(Boolean::constant(false)); + image.push(Boolean::constant(true)); + image.push(Boolean::constant(true)); + image.push(Boolean::constant(false)); + image.push(Boolean::constant(false)); + image.push(Boolean::constant(false)); + image.push(Boolean::constant(false)); + image.extend(a_pk.iter().cloned()); + image.extend(value.iter().cloned()); + image.extend(rho.iter().cloned()); + image.extend(r.iter().cloned()); + + sha256( + cs, + &image + ) +} diff --git a/sapling-crypto/src/circuit/sprout/input.rs b/sapling-crypto/src/circuit/sprout/input.rs new file mode 100644 index 0000000..ce69bc0 --- /dev/null +++ b/sapling-crypto/src/circuit/sprout/input.rs @@ -0,0 +1,226 @@ +use pairing::{Engine}; +use bellman::{ConstraintSystem, SynthesisError}; +use circuit::sha256::{ + sha256_block_no_padding +}; +use circuit::boolean::{ + AllocatedBit, + Boolean +}; + +use super::*; +use super::prfs::*; +use super::commitment::note_comm; + +pub struct InputNote { + pub nf: Vec, + pub mac: Vec, +} + +impl InputNote { + pub fn compute( + mut cs: CS, + a_sk: Option, + rho: Option, + r: Option, + value: &NoteValue, + h_sig: &[Boolean], + nonce: bool, + auth_path: [Option<([u8; 32], bool)>; TREE_DEPTH], + rt: &[Boolean] + ) -> Result + where E: Engine, CS: ConstraintSystem + { + let a_sk = witness_u252( + cs.namespace(|| "a_sk"), + a_sk.as_ref().map(|a_sk| &a_sk.0[..]) + )?; + + let rho = witness_u256( + cs.namespace(|| "rho"), + rho.as_ref().map(|rho| &rho.0[..]) + )?; + + let r = witness_u256( + cs.namespace(|| "r"), + r.as_ref().map(|r| &r.0[..]) + )?; + + let a_pk = prf_a_pk( + cs.namespace(|| "a_pk computation"), + &a_sk + )?; + + let nf = prf_nf( + cs.namespace(|| "nf computation"), + &a_sk, + &rho + )?; + + let mac = prf_pk( + cs.namespace(|| "mac computation"), + &a_sk, + h_sig, + nonce + )?; + + let cm = note_comm( + cs.namespace(|| "cm computation"), + &a_pk, + &value.bits_le(), + &rho, + &r + )?; + + // Witness into the merkle tree + let mut cur = cm.clone(); + + for (i, layer) in auth_path.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("layer {}", i)); + + let cur_is_right = AllocatedBit::alloc( + cs.namespace(|| "cur is right"), + layer.as_ref().map(|&(_, p)| p) + )?; + + let lhs = cur; + let rhs = witness_u256( + cs.namespace(|| "sibling"), + layer.as_ref().map(|&(ref sibling, _)| &sibling[..]) + )?; + + // Conditionally swap if cur is right + let preimage = conditionally_swap_u256( + cs.namespace(|| "conditional swap"), + &lhs[..], + &rhs[..], + &cur_is_right + )?; + + cur = sha256_block_no_padding( + cs.namespace(|| "hash of this layer"), + &preimage + )?; + } + + // enforce must be true if the value is nonzero + let enforce = AllocatedBit::alloc( + cs.namespace(|| "enforce"), + value.get_value().map(|n| n != 0) + )?; + + // value * (1 - enforce) = 0 + // If `value` is zero, `enforce` _can_ be zero. + // If `value` is nonzero, `enforce` _must_ be one. + cs.enforce( + || "enforce validity", + |_| value.lc(), + |lc| lc + CS::one() - enforce.get_variable(), + |lc| lc + ); + + assert_eq!(cur.len(), rt.len()); + + // Check that the anchor (exposed as a public input) + // is equal to the merkle tree root that we calculated + // for this note + for (i, (cur, rt)) in cur.into_iter().zip(rt.iter()).enumerate() { + // (cur - rt) * enforce = 0 + // if enforce is zero, cur and rt can be different + // if enforce is one, they must be equal + cs.enforce( + || format!("conditionally enforce correct root for bit {}", i), + |_| cur.lc(CS::one(), E::Fr::one()) - &rt.lc(CS::one(), E::Fr::one()), + |lc| lc + enforce.get_variable(), + |lc| lc + ); + } + + Ok(InputNote { + mac: mac, + nf: nf + }) + } +} + +/// Swaps two 256-bit blobs conditionally, returning the +/// 512-bit concatenation. +pub fn conditionally_swap_u256( + mut cs: CS, + lhs: &[Boolean], + rhs: &[Boolean], + condition: &AllocatedBit +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem, +{ + assert_eq!(lhs.len(), 256); + assert_eq!(rhs.len(), 256); + + let mut new_lhs = vec![]; + let mut new_rhs = vec![]; + + for (i, (lhs, rhs)) in lhs.iter().zip(rhs.iter()).enumerate() { + let cs = &mut cs.namespace(|| format!("bit {}", i)); + + let x = Boolean::from(AllocatedBit::alloc( + cs.namespace(|| "x"), + condition.get_value().and_then(|v| { + if v { + rhs.get_value() + } else { + lhs.get_value() + } + }) + )?); + + // x = (1-condition)lhs + (condition)rhs + // x = lhs - lhs(condition) + rhs(condition) + // x - lhs = condition (rhs - lhs) + // if condition is zero, we don't swap, so + // x - lhs = 0 + // x = lhs + // if condition is one, we do swap, so + // x - lhs = rhs - lhs + // x = rhs + cs.enforce( + || "conditional swap for x", + |lc| lc + &rhs.lc(CS::one(), E::Fr::one()) + - &lhs.lc(CS::one(), E::Fr::one()), + |lc| lc + condition.get_variable(), + |lc| lc + &x.lc(CS::one(), E::Fr::one()) + - &lhs.lc(CS::one(), E::Fr::one()) + ); + + let y = Boolean::from(AllocatedBit::alloc( + cs.namespace(|| "y"), + condition.get_value().and_then(|v| { + if v { + lhs.get_value() + } else { + rhs.get_value() + } + }) + )?); + + // y = (1-condition)rhs + (condition)lhs + // y - rhs = condition (lhs - rhs) + cs.enforce( + || "conditional swap for y", + |lc| lc + &lhs.lc(CS::one(), E::Fr::one()) + - &rhs.lc(CS::one(), E::Fr::one()), + |lc| lc + condition.get_variable(), + |lc| lc + &y.lc(CS::one(), E::Fr::one()) + - &rhs.lc(CS::one(), E::Fr::one()) + ); + + new_lhs.push(x); + new_rhs.push(y); + } + + let mut f = new_lhs; + f.extend(new_rhs); + + assert_eq!(f.len(), 512); + + Ok(f) +} diff --git a/sapling-crypto/src/circuit/sprout/mod.rs b/sapling-crypto/src/circuit/sprout/mod.rs new file mode 100644 index 0000000..586de8c --- /dev/null +++ b/sapling-crypto/src/circuit/sprout/mod.rs @@ -0,0 +1,488 @@ +use pairing::{Engine, Field}; +use bellman::{ConstraintSystem, SynthesisError, Circuit, LinearCombination}; +use circuit::boolean::{ + AllocatedBit, + Boolean +}; +use circuit::multipack::pack_into_inputs; + +mod prfs; +mod commitment; +mod input; +mod output; + +use self::input::*; +use self::output::*; + +pub const TREE_DEPTH: usize = 29; + +pub struct SpendingKey(pub [u8; 32]); +pub struct PayingKey(pub [u8; 32]); +pub struct UniqueRandomness(pub [u8; 32]); +pub struct CommitmentRandomness(pub [u8; 32]); + +pub struct JoinSplit { + pub vpub_old: Option, + pub vpub_new: Option, + pub h_sig: Option<[u8; 32]>, + pub phi: Option<[u8; 32]>, + pub inputs: Vec, + pub outputs: Vec, + pub rt: Option<[u8; 32]>, +} + +pub struct JSInput { + pub value: Option, + pub a_sk: Option, + pub rho: Option, + pub r: Option, + pub auth_path: [Option<([u8; 32], bool)>; TREE_DEPTH] +} + +pub struct JSOutput { + pub value: Option, + pub a_pk: Option, + pub r: Option +} + +impl Circuit for JoinSplit { + fn synthesize>( + self, + cs: &mut CS + ) -> Result<(), SynthesisError> + { + assert_eq!(self.inputs.len(), 2); + assert_eq!(self.outputs.len(), 2); + + // vpub_old is the value entering the + // JoinSplit from the "outside" value + // pool + let vpub_old = NoteValue::new( + cs.namespace(|| "vpub_old"), + self.vpub_old + )?; + + // vpub_new is the value leaving the + // JoinSplit into the "outside" value + // pool + let vpub_new = NoteValue::new( + cs.namespace(|| "vpub_new"), + self.vpub_new + )?; + + // The left hand side of the balance equation + // vpub_old + inputs[0].value + inputs[1].value + let mut lhs = vpub_old.lc(); + + // The right hand side of the balance equation + // vpub_old + inputs[0].value + inputs[1].value + let mut rhs = vpub_new.lc(); + + // Witness rt (merkle tree root) + let rt = witness_u256( + cs.namespace(|| "rt"), + self.rt.as_ref().map(|v| &v[..]) + ).unwrap(); + + // Witness h_sig + let h_sig = witness_u256( + cs.namespace(|| "h_sig"), + self.h_sig.as_ref().map(|v| &v[..]) + ).unwrap(); + + // Witness phi + let phi = witness_u252( + cs.namespace(|| "phi"), + self.phi.as_ref().map(|v| &v[..]) + ).unwrap(); + + let mut input_notes = vec![]; + let mut lhs_total = self.vpub_old; + + // Iterate over the JoinSplit inputs + for (i, input) in self.inputs.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("input {}", i)); + + // Accumulate the value of the left hand side + if let Some(value) = input.value { + lhs_total = lhs_total.map(|v| v.wrapping_add(value)); + } + + // Allocate the value of the note + let value = NoteValue::new( + cs.namespace(|| "value"), + input.value + )?; + + // Compute the nonce (for PRF inputs) which is false + // for the first input, and true for the second input. + let nonce = match i { + 0 => false, + 1 => true, + _ => unreachable!() + }; + + // Perform input note computations + input_notes.push(InputNote::compute( + cs.namespace(|| "note"), + input.a_sk, + input.rho, + input.r, + &value, + &h_sig, + nonce, + input.auth_path, + &rt + )?); + + // Add the note value to the left hand side of + // the balance equation + lhs = lhs + &value.lc(); + } + + // Rebind lhs so that it isn't mutable anymore + let lhs = lhs; + + // See zcash/zcash/issues/854 + { + // Expected sum of the left hand side of the balance + // equation, expressed as a 64-bit unsigned integer + let lhs_total = NoteValue::new( + cs.namespace(|| "total value of left hand side"), + lhs_total + )?; + + // Enforce that the left hand side can be expressed as a 64-bit + // integer + cs.enforce( + || "left hand side can be expressed as a 64-bit unsigned integer", + |_| lhs.clone(), + |lc| lc + CS::one(), + |_| lhs_total.lc() + ); + } + + let mut output_notes = vec![]; + + // Iterate over the JoinSplit outputs + for (i, output) in self.outputs.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("output {}", i)); + + let value = NoteValue::new( + cs.namespace(|| "value"), + output.value + )?; + + // Compute the nonce (for PRF inputs) which is false + // for the first output, and true for the second output. + let nonce = match i { + 0 => false, + 1 => true, + _ => unreachable!() + }; + + // Perform output note computations + output_notes.push(OutputNote::compute( + cs.namespace(|| "note"), + output.a_pk, + &value, + output.r, + &phi, + &h_sig, + nonce + )?); + + // Add the note value to the right hand side of + // the balance equation + rhs = rhs + &value.lc(); + } + + // Enforce that balance is equal + cs.enforce( + || "balance equation", + |_| lhs.clone(), + |lc| lc + CS::one(), + |_| rhs + ); + + let mut public_inputs = vec![]; + public_inputs.extend(rt); + public_inputs.extend(h_sig); + + for note in input_notes { + public_inputs.extend(note.nf); + public_inputs.extend(note.mac); + } + + for note in output_notes { + public_inputs.extend(note.cm); + } + + public_inputs.extend(vpub_old.bits_le()); + public_inputs.extend(vpub_new.bits_le()); + + pack_into_inputs(cs.namespace(|| "input packing"), &public_inputs) + } +} + +pub struct NoteValue { + value: Option, + // Least significant digit first + bits: Vec +} + +impl NoteValue { + fn new( + mut cs: CS, + value: Option + ) -> Result + where E: Engine, CS: ConstraintSystem, + { + let mut values; + match value { + Some(mut val) => { + values = vec![]; + for _ in 0..64 { + values.push(Some(val & 1 == 1)); + val >>= 1; + } + }, + None => { + values = vec![None; 64]; + } + } + + let mut bits = vec![]; + for (i, value) in values.into_iter().enumerate() { + bits.push( + AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + value + )? + ); + } + + Ok(NoteValue { + value: value, + bits: bits + }) + } + + /// Encodes the bits of the value into little-endian + /// byte order. + fn bits_le(&self) -> Vec { + self.bits.chunks(8) + .flat_map(|v| v.iter().rev()) + .cloned() + .map(|e| Boolean::from(e)) + .collect() + } + + /// Computes this value as a linear combination of + /// its bits. + fn lc(&self) -> LinearCombination { + let mut tmp = LinearCombination::zero(); + + let mut coeff = E::Fr::one(); + for b in &self.bits { + tmp = tmp + (coeff, b.get_variable()); + coeff.double(); + } + + tmp + } + + fn get_value(&self) -> Option { + self.value + } +} + +/// Witnesses some bytes in the constraint system, +/// skipping the first `skip_bits`. +fn witness_bits( + mut cs: CS, + value: Option<&[u8]>, + num_bits: usize, + skip_bits: usize +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem, +{ + let bit_values = if let Some(value) = value { + let mut tmp = vec![]; + for b in value.iter() + .flat_map(|&m| (0..8).rev().map(move |i| m >> i & 1 == 1)) + .skip(skip_bits) + { + tmp.push(Some(b)); + } + tmp + } else { + vec![None; num_bits] + }; + assert_eq!(bit_values.len(), num_bits); + + let mut bits = vec![]; + + for (i, value) in bit_values.into_iter().enumerate() { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + value + )?)); + } + + Ok(bits) +} + +fn witness_u256( + cs: CS, + value: Option<&[u8]>, +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem, +{ + witness_bits(cs, value, 256, 0) +} + +fn witness_u252( + cs: CS, + value: Option<&[u8]>, +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem, +{ + witness_bits(cs, value, 252, 4) +} + +#[test] +fn test_sprout_constraints() { + use pairing::bls12_381::{Bls12}; + use ::circuit::test::*; + + use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; + + let test_vector = include_bytes!("test_vectors.dat"); + let mut test_vector = &test_vector[..]; + + fn get_u256(mut reader: R) -> [u8; 32] { + let mut result = [0u8; 32]; + + for i in 0..32 { + result[i] = reader.read_u8().unwrap(); + } + + result + } + + while test_vector.len() != 0 { + let mut cs = TestConstraintSystem::::new(); + + let phi = Some(get_u256(&mut test_vector)); + let rt = Some(get_u256(&mut test_vector)); + let h_sig = Some(get_u256(&mut test_vector)); + + let mut inputs = vec![]; + for _ in 0..2 { + test_vector.read_u8().unwrap(); + + let mut auth_path = [None; TREE_DEPTH]; + for i in (0..TREE_DEPTH).rev() { + test_vector.read_u8().unwrap(); + + let sibling = get_u256(&mut test_vector); + + auth_path[i] = Some((sibling, false)); + } + let mut position = test_vector.read_u64::().unwrap(); + for i in 0..TREE_DEPTH { + auth_path[i].as_mut().map(|p| { + p.1 = (position & 1) == 1 + }); + + position >>= 1; + } + + // a_pk + let _ = Some(SpendingKey(get_u256(&mut test_vector))); + let value = Some(test_vector.read_u64::().unwrap()); + let rho = Some(UniqueRandomness(get_u256(&mut test_vector))); + let r = Some(CommitmentRandomness(get_u256(&mut test_vector))); + let a_sk = Some(SpendingKey(get_u256(&mut test_vector))); + + inputs.push( + JSInput { + value: value, + a_sk: a_sk, + rho: rho, + r: r, + auth_path: auth_path + } + ); + } + + let mut outputs = vec![]; + + for _ in 0..2 { + let a_pk = Some(PayingKey(get_u256(&mut test_vector))); + let value = Some(test_vector.read_u64::().unwrap()); + get_u256(&mut test_vector); + let r = Some(CommitmentRandomness(get_u256(&mut test_vector))); + + outputs.push( + JSOutput { + value: value, + a_pk: a_pk, + r: r + } + ); + } + + let vpub_old = Some(test_vector.read_u64::().unwrap()); + let vpub_new = Some(test_vector.read_u64::().unwrap()); + + let nf1 = get_u256(&mut test_vector); + let nf2 = get_u256(&mut test_vector); + + let cm1 = get_u256(&mut test_vector); + let cm2 = get_u256(&mut test_vector); + + let mac1 = get_u256(&mut test_vector); + let mac2 = get_u256(&mut test_vector); + + let js = JoinSplit { + vpub_old: vpub_old, + vpub_new: vpub_new, + h_sig: h_sig, + phi: phi, + inputs: inputs, + outputs: outputs, + rt: rt + }; + + js.synthesize(&mut cs).unwrap(); + + if let Some(s) = cs.which_is_unsatisfied() { + panic!("{:?}", s); + } + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 1989085); + assert_eq!(cs.num_inputs(), 10); + assert_eq!(cs.hash(), "1a228d3c6377130d1778c7885811dc8b8864049cb5af8aff7e6cd46c5bc4b84c"); + + let mut expected_inputs = vec![]; + expected_inputs.extend(rt.unwrap().to_vec()); + expected_inputs.extend(h_sig.unwrap().to_vec()); + expected_inputs.extend(nf1.to_vec()); + expected_inputs.extend(mac1.to_vec()); + expected_inputs.extend(nf2.to_vec()); + expected_inputs.extend(mac2.to_vec()); + expected_inputs.extend(cm1.to_vec()); + expected_inputs.extend(cm2.to_vec()); + expected_inputs.write_u64::(vpub_old.unwrap()).unwrap(); + expected_inputs.write_u64::(vpub_new.unwrap()).unwrap(); + + use circuit::multipack; + + let expected_inputs = multipack::bytes_to_bits(&expected_inputs); + let expected_inputs = multipack::compute_multipacking::(&expected_inputs); + + assert!(cs.verify(&expected_inputs)); + } +} diff --git a/sapling-crypto/src/circuit/sprout/output.rs b/sapling-crypto/src/circuit/sprout/output.rs new file mode 100644 index 0000000..9cdbf52 --- /dev/null +++ b/sapling-crypto/src/circuit/sprout/output.rs @@ -0,0 +1,54 @@ +use pairing::{Engine}; +use bellman::{ConstraintSystem, SynthesisError}; +use circuit::boolean::{Boolean}; + +use super::*; +use super::prfs::*; +use super::commitment::note_comm; + +pub struct OutputNote { + pub cm: Vec +} + +impl OutputNote { + pub fn compute<'a, E, CS>( + mut cs: CS, + a_pk: Option, + value: &NoteValue, + r: Option, + phi: &[Boolean], + h_sig: &[Boolean], + nonce: bool + ) -> Result + where E: Engine, CS: ConstraintSystem, + { + let rho = prf_rho( + cs.namespace(|| "rho"), + phi, + h_sig, + nonce + )?; + + let a_pk = witness_u256( + cs.namespace(|| "a_pk"), + a_pk.as_ref().map(|a_pk| &a_pk.0[..]) + )?; + + let r = witness_u256( + cs.namespace(|| "r"), + r.as_ref().map(|r| &r.0[..]) + )?; + + let cm = note_comm( + cs.namespace(|| "cm computation"), + &a_pk, + &value.bits_le(), + &rho, + &r + )?; + + Ok(OutputNote { + cm: cm + }) + } +} diff --git a/sapling-crypto/src/circuit/sprout/prfs.rs b/sapling-crypto/src/circuit/sprout/prfs.rs new file mode 100644 index 0000000..fff8648 --- /dev/null +++ b/sapling-crypto/src/circuit/sprout/prfs.rs @@ -0,0 +1,79 @@ +use pairing::{Engine}; +use bellman::{ConstraintSystem, SynthesisError}; +use circuit::sha256::{ + sha256_block_no_padding +}; +use circuit::boolean::{ + Boolean +}; + +fn prf( + cs: CS, + a: bool, + b: bool, + c: bool, + d: bool, + x: &[Boolean], + y: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + assert_eq!(x.len(), 252); + assert_eq!(y.len(), 256); + + let mut image = vec![]; + image.push(Boolean::constant(a)); + image.push(Boolean::constant(b)); + image.push(Boolean::constant(c)); + image.push(Boolean::constant(d)); + image.extend(x.iter().cloned()); + image.extend(y.iter().cloned()); + + assert_eq!(image.len(), 512); + + sha256_block_no_padding( + cs, + &image + ) +} + +pub fn prf_a_pk( + cs: CS, + a_sk: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + prf(cs, true, true, false, false, a_sk, &(0..256).map(|_| Boolean::constant(false)).collect::>()) +} + +pub fn prf_nf( + cs: CS, + a_sk: &[Boolean], + rho: &[Boolean] +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + prf(cs, true, true, true, false, a_sk, rho) +} + +pub fn prf_pk( + cs: CS, + a_sk: &[Boolean], + h_sig: &[Boolean], + nonce: bool +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + prf(cs, false, nonce, false, false, a_sk, h_sig) +} + +pub fn prf_rho( + cs: CS, + phi: &[Boolean], + h_sig: &[Boolean], + nonce: bool +) -> Result, SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + prf(cs, false, nonce, true, false, phi, h_sig) +} diff --git a/sapling-crypto/src/circuit/sprout/test_vectors.dat b/sapling-crypto/src/circuit/sprout/test_vectors.dat new file mode 100644 index 0000000..1316955 Binary files /dev/null and b/sapling-crypto/src/circuit/sprout/test_vectors.dat differ diff --git a/sapling-crypto/src/circuit/test/mod.rs b/sapling-crypto/src/circuit/test/mod.rs new file mode 100644 index 0000000..12fe0ca --- /dev/null +++ b/sapling-crypto/src/circuit/test/mod.rs @@ -0,0 +1,492 @@ +use pairing::{ + Engine, + Field, + PrimeField, + PrimeFieldRepr +}; + +use bellman::{ + LinearCombination, + SynthesisError, + ConstraintSystem, + Variable, + Index +}; + +use std::collections::HashMap; +use std::fmt::Write; + +use byteorder::{BigEndian, ByteOrder}; +use std::cmp::Ordering; +use std::collections::BTreeMap; + +use blake2_rfc::blake2s::Blake2s; + +#[derive(Debug)] +enum NamedObject { + Constraint(usize), + Var(Variable), + Namespace +} + +/// Constraint system for testing purposes. +pub struct TestConstraintSystem { + named_objects: HashMap, + current_namespace: Vec, + constraints: Vec<( + LinearCombination, + LinearCombination, + LinearCombination, + String + )>, + inputs: Vec<(E::Fr, String)>, + aux: Vec<(E::Fr, String)> +} + +#[derive(Clone, Copy)] +struct OrderedVariable(Variable); + +impl Eq for OrderedVariable {} +impl PartialEq for OrderedVariable { + fn eq(&self, other: &OrderedVariable) -> bool { + match (self.0.get_unchecked(), other.0.get_unchecked()) { + (Index::Input(ref a), Index::Input(ref b)) => a == b, + (Index::Aux(ref a), Index::Aux(ref b)) => a == b, + _ => false + } + } +} +impl PartialOrd for OrderedVariable { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for OrderedVariable { + fn cmp(&self, other: &Self) -> Ordering { + match (self.0.get_unchecked(), other.0.get_unchecked()) { + (Index::Input(ref a), Index::Input(ref b)) => a.cmp(b), + (Index::Aux(ref a), Index::Aux(ref b)) => a.cmp(b), + (Index::Input(_), Index::Aux(_)) => Ordering::Less, + (Index::Aux(_), Index::Input(_)) => Ordering::Greater + } + } +} + +fn proc_lc( + terms: &[(Variable, E::Fr)], +) -> BTreeMap +{ + let mut map = BTreeMap::new(); + for &(var, coeff) in terms { + map.entry(OrderedVariable(var)) + .or_insert(E::Fr::zero()) + .add_assign(&coeff); + } + + // Remove terms that have a zero coefficient to normalize + let mut to_remove = vec![]; + for (var, coeff) in map.iter() { + if coeff.is_zero() { + to_remove.push(var.clone()) + } + } + + for var in to_remove { + map.remove(&var); + } + + map +} + +fn hash_lc( + terms: &[(Variable, E::Fr)], + h: &mut Blake2s +) +{ + let map = proc_lc::(terms); + + let mut buf = [0u8; 9 + 32]; + BigEndian::write_u64(&mut buf[0..8], map.len() as u64); + h.update(&buf[0..8]); + + for (var, coeff) in map { + match var.0.get_unchecked() { + Index::Input(i) => { + buf[0] = b'I'; + BigEndian::write_u64(&mut buf[1..9], i as u64); + }, + Index::Aux(i) => { + buf[0] = b'A'; + BigEndian::write_u64(&mut buf[1..9], i as u64); + } + } + + coeff.into_repr().write_be(&mut buf[9..]).unwrap(); + + h.update(&buf); + } +} + +fn eval_lc( + terms: &[(Variable, E::Fr)], + inputs: &[(E::Fr, String)], + aux: &[(E::Fr, String)] +) -> E::Fr +{ + let mut acc = E::Fr::zero(); + + for &(var, ref coeff) in terms { + let mut tmp = match var.get_unchecked() { + Index::Input(index) => inputs[index].0, + Index::Aux(index) => aux[index].0 + }; + + tmp.mul_assign(&coeff); + acc.add_assign(&tmp); + } + + acc +} + +impl TestConstraintSystem { + pub fn new() -> TestConstraintSystem { + let mut map = HashMap::new(); + map.insert("ONE".into(), NamedObject::Var(TestConstraintSystem::::one())); + + TestConstraintSystem { + named_objects: map, + current_namespace: vec![], + constraints: vec![], + inputs: vec![(E::Fr::one(), "ONE".into())], + aux: vec![] + } + } + + pub fn pretty_print(&self) -> String { + let mut s = String::new(); + + let negone = { + let mut tmp = E::Fr::one(); + tmp.negate(); + tmp + }; + + let powers_of_two = (0..E::Fr::NUM_BITS).map(|i| { + E::Fr::from_str("2").unwrap().pow(&[i as u64]) + }).collect::>(); + + let pp = |s: &mut String, lc: &LinearCombination| { + write!(s, "(").unwrap(); + let mut is_first = true; + for (var, coeff) in proc_lc::(lc.as_ref()) { + if coeff == negone { + write!(s, " - ").unwrap(); + } else if !is_first { + write!(s, " + ").unwrap(); + } + is_first = false; + + if coeff != E::Fr::one() && coeff != negone { + for (i, x) in powers_of_two.iter().enumerate() { + if x == &coeff { + write!(s, "2^{} . ", i).unwrap(); + break; + } + } + + write!(s, "{} . ", coeff).unwrap(); + } + + match var.0.get_unchecked() { + Index::Input(i) => { + write!(s, "`{}`", &self.inputs[i].1).unwrap(); + }, + Index::Aux(i) => { + write!(s, "`{}`", &self.aux[i].1).unwrap(); + } + } + } + if is_first { + // Nothing was visited, print 0. + write!(s, "0").unwrap(); + } + write!(s, ")").unwrap(); + }; + + for &(ref a, ref b, ref c, ref name) in &self.constraints { + write!(&mut s, "\n").unwrap(); + + write!(&mut s, "{}: ", name).unwrap(); + pp(&mut s, a); + write!(&mut s, " * ").unwrap(); + pp(&mut s, b); + write!(&mut s, " = ").unwrap(); + pp(&mut s, c); + } + + write!(&mut s, "\n").unwrap(); + + s + } + + pub fn hash(&self) -> String { + let mut h = Blake2s::new(32); + { + let mut buf = [0u8; 24]; + + BigEndian::write_u64(&mut buf[0..8], self.inputs.len() as u64); + BigEndian::write_u64(&mut buf[8..16], self.aux.len() as u64); + BigEndian::write_u64(&mut buf[16..24], self.constraints.len() as u64); + h.update(&buf); + } + + for constraint in &self.constraints { + hash_lc::(constraint.0.as_ref(), &mut h); + hash_lc::(constraint.1.as_ref(), &mut h); + hash_lc::(constraint.2.as_ref(), &mut h); + } + + let mut s = String::new(); + for b in h.finalize().as_ref() { + s += &format!("{:02x}", b); + } + + s + } + + pub fn which_is_unsatisfied(&self) -> Option<&str> { + for &(ref a, ref b, ref c, ref path) in &self.constraints { + let mut a = eval_lc::(a.as_ref(), &self.inputs, &self.aux); + let b = eval_lc::(b.as_ref(), &self.inputs, &self.aux); + let c = eval_lc::(c.as_ref(), &self.inputs, &self.aux); + + a.mul_assign(&b); + + if a != c { + return Some(&*path) + } + } + + None + } + + pub fn is_satisfied(&self) -> bool + { + self.which_is_unsatisfied().is_none() + } + + pub fn num_constraints(&self) -> usize + { + self.constraints.len() + } + + pub fn set(&mut self, path: &str, to: E::Fr) + { + match self.named_objects.get(path) { + Some(&NamedObject::Var(ref v)) => { + match v.get_unchecked() { + Index::Input(index) => self.inputs[index].0 = to, + Index::Aux(index) => self.aux[index].0 = to + } + } + Some(e) => panic!("tried to set path `{}` to value, but `{:?}` already exists there.", path, e), + _ => panic!("no variable exists at path: {}", path) + } + } + + pub fn verify(&self, expected: &[E::Fr]) -> bool + { + assert_eq!(expected.len() + 1, self.inputs.len()); + + for (a, b) in self.inputs.iter().skip(1).zip(expected.iter()) + { + if &a.0 != b { + return false + } + } + + return true; + } + + pub fn num_inputs(&self) -> usize { + self.inputs.len() + } + + pub fn get_input(&mut self, index: usize, path: &str) -> E::Fr + { + let (assignment, name) = self.inputs[index].clone(); + + assert_eq!(path, name); + + assignment + } + + pub fn get(&mut self, path: &str) -> E::Fr + { + match self.named_objects.get(path) { + Some(&NamedObject::Var(ref v)) => { + match v.get_unchecked() { + Index::Input(index) => self.inputs[index].0, + Index::Aux(index) => self.aux[index].0 + } + } + Some(e) => panic!("tried to get value of path `{}`, but `{:?}` exists there (not a variable)", path, e), + _ => panic!("no variable exists at path: {}", path) + } + } + + fn set_named_obj(&mut self, path: String, to: NamedObject) { + if self.named_objects.contains_key(&path) { + panic!("tried to create object at existing path: {}", path); + } + + self.named_objects.insert(path, to); + } +} + +fn compute_path(ns: &[String], this: String) -> String { + if this.chars().any(|a| a == '/') { + panic!("'/' is not allowed in names"); + } + + let mut name = String::new(); + + let mut needs_separation = false; + for ns in ns.iter().chain(Some(&this).into_iter()) + { + if needs_separation { + name += "/"; + } + + name += ns; + needs_separation = true; + } + + name +} + +impl ConstraintSystem for TestConstraintSystem { + type Root = Self; + + fn alloc( + &mut self, + annotation: A, + f: F + ) -> Result + where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + { + let index = self.aux.len(); + let path = compute_path(&self.current_namespace, annotation().into()); + self.aux.push((f()?, path.clone())); + let var = Variable::new_unchecked(Index::Aux(index)); + self.set_named_obj(path, NamedObject::Var(var)); + + Ok(var) + } + + fn alloc_input( + &mut self, + annotation: A, + f: F + ) -> Result + where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + { + let index = self.inputs.len(); + let path = compute_path(&self.current_namespace, annotation().into()); + self.inputs.push((f()?, path.clone())); + let var = Variable::new_unchecked(Index::Input(index)); + self.set_named_obj(path, NamedObject::Var(var)); + + Ok(var) + } + + fn enforce( + &mut self, + annotation: A, + a: LA, + b: LB, + c: LC + ) + where A: FnOnce() -> AR, AR: Into, + LA: FnOnce(LinearCombination) -> LinearCombination, + LB: FnOnce(LinearCombination) -> LinearCombination, + LC: FnOnce(LinearCombination) -> LinearCombination + { + let path = compute_path(&self.current_namespace, annotation().into()); + let index = self.constraints.len(); + self.set_named_obj(path.clone(), NamedObject::Constraint(index)); + + let a = a(LinearCombination::zero()); + let b = b(LinearCombination::zero()); + let c = c(LinearCombination::zero()); + + self.constraints.push((a, b, c, path)); + } + + fn push_namespace(&mut self, name_fn: N) + where NR: Into, N: FnOnce() -> NR + { + let name = name_fn().into(); + let path = compute_path(&self.current_namespace, name.clone()); + self.set_named_obj(path.clone(), NamedObject::Namespace); + self.current_namespace.push(name); + } + + fn pop_namespace(&mut self) + { + assert!(self.current_namespace.pop().is_some()); + } + + fn get_root(&mut self) -> &mut Self::Root + { + self + } +} + +#[test] +fn test_cs() { + use pairing::bls12_381::{Bls12, Fr}; + use pairing::PrimeField; + + let mut cs = TestConstraintSystem::::new(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 0); + let a = cs.namespace(|| "a").alloc(|| "var", || Ok(Fr::from_str("10").unwrap())).unwrap(); + let b = cs.namespace(|| "b").alloc(|| "var", || Ok(Fr::from_str("4").unwrap())).unwrap(); + let c = cs.alloc(|| "product", || Ok(Fr::from_str("40").unwrap())).unwrap(); + + cs.enforce( + || "mult", + |lc| lc + a, + |lc| lc + b, + |lc| lc + c + ); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 1); + + cs.set("a/var", Fr::from_str("4").unwrap()); + + let one = TestConstraintSystem::::one(); + cs.enforce( + || "eq", + |lc| lc + a, + |lc| lc + one, + |lc| lc + b + ); + + assert!(!cs.is_satisfied()); + assert!(cs.which_is_unsatisfied() == Some("mult")); + + assert!(cs.get("product") == Fr::from_str("40").unwrap()); + + cs.set("product", Fr::from_str("16").unwrap()); + assert!(cs.is_satisfied()); + + { + let mut cs = cs.namespace(|| "test1"); + let mut cs = cs.namespace(|| "test2"); + cs.alloc(|| "hehe", || Ok(Fr::one())).unwrap(); + } + + assert!(cs.get("test1/test2/hehe") == Fr::one()); +} diff --git a/sapling-crypto/src/circuit/uint32.rs b/sapling-crypto/src/circuit/uint32.rs new file mode 100644 index 0000000..fb0bfa9 --- /dev/null +++ b/sapling-crypto/src/circuit/uint32.rs @@ -0,0 +1,755 @@ +use pairing::{ + Engine, + Field, + PrimeField +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination +}; + +use super::boolean::{ + Boolean, + AllocatedBit +}; + +use super::multieq::MultiEq; + +/// Represents an interpretation of 32 `Boolean` objects as an +/// unsigned integer. +#[derive(Clone)] +pub struct UInt32 { + // Least significant bit first + bits: Vec, + value: Option +} + +impl UInt32 { + /// Construct a constant `UInt32` from a `u32` + pub fn constant(value: u32) -> Self + { + let mut bits = Vec::with_capacity(32); + + let mut tmp = value; + for _ in 0..32 { + if tmp & 1 == 1 { + bits.push(Boolean::constant(true)) + } else { + bits.push(Boolean::constant(false)) + } + + tmp >>= 1; + } + + UInt32 { + bits: bits, + value: Some(value) + } + } + + /// Allocate a `UInt32` in the constraint system + pub fn alloc( + mut cs: CS, + value: Option + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let values = match value { + Some(mut val) => { + let mut v = Vec::with_capacity(32); + + for _ in 0..32 { + 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(UInt32 { + bits: bits, + value: value + }) + } + + pub fn into_bits_be(&self) -> Vec { + self.bits.iter().rev().cloned().collect() + } + + pub fn from_bits_be(bits: &[Boolean]) -> Self { + assert_eq!(bits.len(), 32); + + let mut value = Some(0u32); + for b in bits { + value.as_mut().map(|v| *v <<= 1); + + match b.get_value() { + Some(true) => { value.as_mut().map(|v| *v |= 1); }, + Some(false) => {}, + None => { value = None; } + } + } + + UInt32 { + value: value, + bits: bits.iter().rev().cloned().collect() + } + } + + + /// Turns this `UInt32` into its little-endian byte order representation. + pub fn into_bits(&self) -> Vec { + self.bits.clone() + } + + /// Converts a little-endian byte order representation of bits into a + /// `UInt32`. + pub fn from_bits(bits: &[Boolean]) -> Self + { + assert_eq!(bits.len(), 32); + + let new_bits = bits.to_vec(); + + let mut value = Some(0u32); + for b in new_bits.iter().rev() { + value.as_mut().map(|v| *v <<= 1); + + match b { + &Boolean::Constant(b) => { + if b { + value.as_mut().map(|v| *v |= 1); + } + }, + &Boolean::Is(ref b) => { + match b.get_value() { + Some(true) => { value.as_mut().map(|v| *v |= 1); }, + Some(false) => {}, + None => { value = None } + } + }, + &Boolean::Not(ref b) => { + match b.get_value() { + Some(false) => { value.as_mut().map(|v| *v |= 1); }, + Some(true) => {}, + None => { value = None } + } + } + } + } + + UInt32 { + value: value, + bits: new_bits + } + } + + pub fn rotr(&self, by: usize) -> Self { + let by = by % 32; + + let new_bits = self.bits.iter() + .skip(by) + .chain(self.bits.iter()) + .take(32) + .cloned() + .collect(); + + UInt32 { + bits: new_bits, + value: self.value.map(|v| v.rotate_right(by as u32)) + } + } + + pub fn shr(&self, by: usize) -> Self { + let by = by % 32; + + 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(32) // Only 32 bits needed! + .cloned() + .collect(); + + UInt32 { + 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 E: Engine, + CS: ConstraintSystem, + F: Fn(u32, u32, u32) -> u32, + 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(UInt32 { + bits: bits, + value: new_value + }) + } + + /// Compute the `maj` value (a and b) xor (a and c) xor (b and c) + /// during SHA256. + pub fn sha256_maj( + cs: CS, + a: &Self, + b: &Self, + c: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + Self::triop(cs, a, b, c, |a, b, c| (a & b) ^ (a & c) ^ (b & c), + |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 SHA256. + pub fn sha256_ch( + cs: CS, + a: &Self, + b: &Self, + c: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + Self::triop(cs, a, b, c, |a, b, c| (a & b) ^ ((!a) & c), + |cs, i, a, b, c| { + Boolean::sha256_ch( + cs.namespace(|| format!("ch {}", i)), + a, + b, + c + ) + } + ) + } + + /// XOR this `UInt32` with another `UInt32` + pub fn xor( + &self, + mut cs: CS, + other: &Self + ) -> Result + where E: Engine, + 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(UInt32 { + bits: bits, + value: new_value + }) + } + + /// Perform modular addition of several `UInt32` objects. + pub fn addmany( + mut cs: M, + operands: &[Self] + ) -> Result + where E: Engine, + CS: ConstraintSystem, + M: ConstraintSystem> + { + // Make some arbitrary bounds for ourselves to avoid overflows + // in the scalar field + assert!(E::Fr::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 u64) * (u32::max_value() as u64); + + // Keep track of the resulting value + let mut result_value = Some(0u64); + + // 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) => { + result_value.as_mut().map(|v| *v += val as u64); + }, + 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 = E::Fr::one(); + for bit in &op.bits { + lc = lc + &bit.lc(CS::one(), coeff); + + all_constants &= bit.is_constant(); + + coeff.double(); + } + } + + // The value of the actual result is modulo 2^32 + let modular_value = result_value.map(|v| v as u32); + + if all_constants && modular_value.is_some() { + // We can just return a constant, rather than + // unpacking the result into allocated bits. + + return Ok(UInt32::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 = E::Fr::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.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(32); + + Ok(UInt32 { + bits: result_bits, + value: modular_value + }) + } +} + +#[cfg(test)] +mod test { + use rand::{XorShiftRng, SeedableRng, Rng}; + use ::circuit::boolean::{Boolean}; + use super::{UInt32}; + use pairing::bls12_381::{Bls12}; + use pairing::{Field}; + use ::circuit::test::*; + use bellman::{ConstraintSystem}; + use circuit::multieq::MultiEq; + + #[test] + fn test_uint32_from_bits_be() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); + + for _ in 0..1000 { + let mut v = (0..32).map(|_| Boolean::constant(rng.gen())).collect::>(); + + let b = UInt32::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_uint32_from_bits() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); + + for _ in 0..1000 { + let mut v = (0..32).map(|_| Boolean::constant(rng.gen())).collect::>(); + + let b = UInt32::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_uint32_xor() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a: u32 = rng.gen(); + let b: u32 = rng.gen(); + let c: u32 = rng.gen(); + + 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 = 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!(b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Not(ref b) => { + assert!(!b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Constant(b) => { + assert!(b == (expected & 1 == 1)); + } + } + + expected >>= 1; + } + } + } + + #[test] + fn test_uint32_addmany_constants() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a: u32 = rng.gen(); + let b: u32 = rng.gen(); + let c: u32 = rng.gen(); + + let a_bit = UInt32::constant(a); + let b_bit = UInt32::constant(b); + let c_bit = UInt32::constant(c); + + let mut expected = a.wrapping_add(b).wrapping_add(c); + + let r = { + let mut cs = MultiEq::new(&mut cs); + let r = UInt32::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] + fn test_uint32_addmany() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a: u32 = rng.gen(); + let b: u32 = rng.gen(); + let c: u32 = rng.gen(); + let d: u32 = rng.gen(); + + let mut expected = (a ^ b).wrapping_add(c).wrapping_add(d); + + let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap(); + let b_bit = UInt32::constant(b); + let c_bit = UInt32::constant(c); + let d_bit = UInt32::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); + let r = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap(); + r + }; + + assert!(cs.is_satisfied()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + match b { + &Boolean::Is(ref b) => { + assert!(b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Not(ref b) => { + assert!(!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() { + 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_uint32_rotr() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut num = rng.gen(); + + let a = UInt32::constant(num); + + for i in 0..32 { + 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_uint32_shr() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..50 { + for i in 0..60 { + let num = rng.gen(); + let a = UInt32::constant(num).shr(i); + let b = UInt32::constant(num >> i); + + assert_eq!(a.value.unwrap(), num >> i); + + 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_uint32_sha256_maj() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a: u32 = rng.gen(); + let b: u32 = rng.gen(); + let c: u32 = rng.gen(); + + let mut expected = (a & b) ^ (a & c) ^ (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 = UInt32::sha256_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!(b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Not(ref b) => { + assert!(!b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Constant(b) => { + assert!(b == (expected & 1 == 1)); + } + } + + expected >>= 1; + } + } + } + + #[test] + fn test_uint32_sha256_ch() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let a: u32 = rng.gen(); + let b: u32 = rng.gen(); + let c: u32 = rng.gen(); + + let mut expected = (a & b) ^ ((!a) & 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 = UInt32::sha256_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!(b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Not(ref b) => { + assert!(!b.get_value().unwrap() == (expected & 1 == 1)); + }, + &Boolean::Constant(b) => { + assert!(b == (expected & 1 == 1)); + } + } + + expected >>= 1; + } + } + } +} diff --git a/sapling-crypto/src/constants.rs b/sapling-crypto/src/constants.rs new file mode 100644 index 0000000..dfdd36f --- /dev/null +++ b/sapling-crypto/src/constants.rs @@ -0,0 +1,40 @@ +/// First 64 bytes of the BLAKE2s input during group hash. +/// This is chosen to be some random string that we couldn't have anticipated when we designed +/// the algorithm, for rigidity purposes. +/// We deliberately use an ASCII hex string of 32 bytes here. +pub const GH_FIRST_BLOCK: &'static [u8; 64] + = b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0"; + +// BLAKE2s invocation personalizations +/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | nk) +pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] + = b"Zcashivk"; + +/// BLAKE2s Personalization for PRF^nf = BLAKE2s(nk | rho) +pub const PRF_NF_PERSONALIZATION: &'static [u8; 8] + = b"Zcash_nf"; + +// Group hash personalizations +/// BLAKE2s Personalization for Pedersen hash generators. +pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] + = b"Zcash_PH"; + +/// BLAKE2s Personalization for the group hash for key diversification +pub const KEY_DIVERSIFICATION_PERSONALIZATION: &'static [u8; 8] + = b"Zcash_gd"; + +/// BLAKE2s Personalization for the spending key base point +pub const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8] + = b"Zcash_G_"; + +/// BLAKE2s Personalization for the proof generation key base point +pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] + = b"Zcash_H_"; + +/// BLAKE2s Personalization for the value commitment generator for the value +pub const VALUE_COMMITMENT_GENERATOR_PERSONALIZATION: &'static [u8; 8] + = b"Zcash_cv"; + +/// BLAKE2s Personalization for the nullifier position generator (for computing rho) +pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8] + = b"Zcash_J_"; diff --git a/sapling-crypto/src/group_hash.rs b/sapling-crypto/src/group_hash.rs new file mode 100644 index 0000000..25e65f9 --- /dev/null +++ b/sapling-crypto/src/group_hash.rs @@ -0,0 +1,46 @@ +use jubjub::{ + JubjubEngine, + PrimeOrder, + edwards +}; + +use pairing::{ + PrimeField +}; + +use blake2_rfc::blake2s::Blake2s; +use constants; + +/// Produces a random point in the Jubjub curve. +/// The point is guaranteed to be prime order +/// and not the identity. +pub fn group_hash( + tag: &[u8], + personalization: &[u8], + params: &E::Params +) -> Option> +{ + assert_eq!(personalization.len(), 8); + + // Check to see that scalar field is 255 bits + assert!(E::Fr::NUM_BITS == 255); + + let mut h = Blake2s::with_params(32, &[], &[], personalization); + h.update(constants::GH_FIRST_BLOCK); + h.update(tag); + let h = h.finalize().as_ref().to_vec(); + assert!(h.len() == 32); + + match edwards::Point::::read(&h[..], params) { + Ok(p) => { + let p = p.mul_by_cofactor(params); + + if p != edwards::Point::zero() { + Some(p) + } else { + None + } + }, + Err(_) => None + } +} diff --git a/sapling-crypto/src/jubjub/edwards.rs b/sapling-crypto/src/jubjub/edwards.rs new file mode 100644 index 0000000..e91455c --- /dev/null +++ b/sapling-crypto/src/jubjub/edwards.rs @@ -0,0 +1,523 @@ +use pairing::{ + Field, + SqrtField, + PrimeField, + PrimeFieldRepr, + BitIterator +}; + +use super::{ + JubjubEngine, + JubjubParams, + Unknown, + PrimeOrder, + montgomery +}; + +use rand::{ + Rng +}; + +use std::marker::PhantomData; + +use std::io::{ + self, + Write, + Read +}; + +// Represents the affine point (X/Z, Y/Z) via the extended +// twisted Edwards coordinates. +// +// See "Twisted Edwards Curves Revisited" +// Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson +pub struct Point { + x: E::Fr, + y: E::Fr, + t: E::Fr, + z: E::Fr, + _marker: PhantomData +} + +fn convert_subgroup(from: &Point) -> Point +{ + Point { + x: from.x, + y: from.y, + t: from.t, + z: from.z, + _marker: PhantomData + } +} + +impl From> for Point +{ + fn from(p: Point) -> Point + { + convert_subgroup(&p) + } +} + +impl Clone for Point +{ + fn clone(&self) -> Self { + convert_subgroup(self) + } +} + +impl PartialEq for Point { + fn eq(&self, other: &Point) -> bool { + // p1 = (x1/z1, y1/z1) + // p2 = (x2/z2, y2/z2) + // Deciding that these two points are equal is a matter of + // determining that x1/z1 = x2/z2, or equivalently that + // x1*z2 = x2*z1, and similarly for y. + + let mut x1 = self.x; + x1.mul_assign(&other.z); + + let mut y1 = self.y; + y1.mul_assign(&other.z); + + let mut x2 = other.x; + x2.mul_assign(&self.z); + + let mut y2 = other.y; + y2.mul_assign(&self.z); + + x1 == x2 && y1 == y2 + } +} + +impl Point { + pub fn read( + reader: R, + params: &E::Params + ) -> io::Result + { + let mut y_repr = ::Repr::default(); + y_repr.read_le(reader)?; + + let x_sign = (y_repr.as_ref()[3] >> 63) == 1; + y_repr.as_mut()[3] &= 0x7fffffffffffffff; + + match E::Fr::from_repr(y_repr) { + Ok(y) => { + match Self::get_for_y(y, x_sign, params) { + Some(p) => Ok(p), + None => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "not on curve")) + } + } + }, + Err(_) => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "y is not in field")) + } + } + } + + pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> Option + { + // Given a y on the curve, x^2 = (y^2 - 1) / (dy^2 + 1) + // This is defined for all valid y-coordinates, + // as dy^2 + 1 = 0 has no solution in Fr. + + // tmp1 = y^2 + let mut tmp1 = y; + tmp1.square(); + + // tmp2 = (y^2 * d) + 1 + let mut tmp2 = tmp1; + tmp2.mul_assign(params.edwards_d()); + tmp2.add_assign(&E::Fr::one()); + + // tmp1 = y^2 - 1 + tmp1.sub_assign(&E::Fr::one()); + + match tmp2.inverse() { + Some(tmp2) => { + // tmp1 = (y^2 - 1) / (dy^2 + 1) + tmp1.mul_assign(&tmp2); + + match tmp1.sqrt() { + Some(mut x) => { + if x.into_repr().is_odd() != sign { + x.negate(); + } + + let mut t = x; + t.mul_assign(&y); + + Some(Point { + x: x, + y: y, + t: t, + z: E::Fr::one(), + _marker: PhantomData + }) + }, + None => None + } + }, + None => None + } + } + + /// This guarantees the point is in the prime order subgroup + #[must_use] + pub fn mul_by_cofactor(&self, params: &E::Params) -> Point + { + let tmp = self.double(params) + .double(params) + .double(params); + + convert_subgroup(&tmp) + } + + pub fn rand(rng: &mut R, params: &E::Params) -> Self + { + loop { + let y: E::Fr = rng.gen(); + + if let Some(p) = Self::get_for_y(y, rng.gen(), params) { + return p; + } + } + } +} + +impl Point { + pub fn write( + &self, + writer: W + ) -> io::Result<()> + { + let (x, y) = self.into_xy(); + + assert_eq!(E::Fr::NUM_BITS, 255); + + let x_repr = x.into_repr(); + let mut y_repr = y.into_repr(); + if x_repr.is_odd() { + y_repr.as_mut()[3] |= 0x8000000000000000u64; + } + + y_repr.write_le(writer) + } + + /// Convert from a Montgomery point + pub fn from_montgomery( + m: &montgomery::Point, + params: &E::Params + ) -> Self + { + match m.into_xy() { + None => { + // Map the point at infinity to the neutral element. + Point::zero() + }, + Some((x, y)) => { + // The map from a Montgomery curve is defined as: + // (x, y) -> (u, v) where + // u = x / y + // v = (x - 1) / (x + 1) + // + // This map is not defined for y = 0 and x = -1. + // + // y = 0 is a valid point only for x = 0: + // y^2 = x^3 + A.x^2 + x + // 0 = x^3 + A.x^2 + x + // 0 = x(x^2 + A.x + 1) + // We have: x = 0 OR x^2 + A.x + 1 = 0 + // x^2 + A.x + 1 = 0 + // (2.x + A)^2 = A^2 - 4 (Complete the square.) + // The left hand side is a square, and so if A^2 - 4 + // is nonsquare, there is no solution. Indeed, A^2 - 4 + // is nonsquare. + // + // (0, 0) is a point of order 2, and so we map it to + // (0, -1) in the twisted Edwards curve, which is the + // only point of order 2 that is not the neutral element. + if y.is_zero() { + // This must be the point (0, 0) as above. + let mut neg1 = E::Fr::one(); + neg1.negate(); + + Point { + x: E::Fr::zero(), + y: neg1, + t: E::Fr::zero(), + z: E::Fr::one(), + _marker: PhantomData + } + } else { + // Otherwise, as stated above, the mapping is still + // not defined at x = -1. However, x = -1 is not + // on the curve when A - 2 is nonsquare: + // y^2 = x^3 + A.x^2 + x + // y^2 = (-1) + A + (-1) + // y^2 = A - 2 + // Indeed, A - 2 is nonsquare. + // + // We need to map into (projective) extended twisted + // Edwards coordinates (X, Y, T, Z) which represents + // the point (X/Z, Y/Z) with Z nonzero and T = XY/Z. + // + // Thus, we compute... + // + // u = x(x + 1) + // v = y(x - 1) + // t = x(x - 1) + // z = y(x + 1) (Cannot be nonzero, as above.) + // + // ... which represents the point ( x / y , (x - 1) / (x + 1) ) + // as required by the mapping and preserves the property of + // the auxiliary coordinate t. + // + // We need to scale the coordinate, so u and t will have + // an extra factor s. + + // u = xs + let mut u = x; + u.mul_assign(params.scale()); + + // v = x - 1 + let mut v = x; + v.sub_assign(&E::Fr::one()); + + // t = xs(x - 1) + let mut t = u; + t.mul_assign(&v); + + // z = (x + 1) + let mut z = x; + z.add_assign(&E::Fr::one()); + + // u = xs(x + 1) + u.mul_assign(&z); + + // z = y(x + 1) + z.mul_assign(&y); + + // v = y(x - 1) + v.mul_assign(&y); + + Point { + x: u, + y: v, + t: t, + z: z, + _marker: PhantomData + } + } + } + } + } + + /// Attempts to cast this as a prime order element, failing if it's + /// not in the prime order subgroup. + pub fn as_prime_order(&self, params: &E::Params) -> Option> { + if self.mul(E::Fs::char(), params) == Point::zero() { + Some(convert_subgroup(self)) + } else { + None + } + } + + pub fn zero() -> Self { + Point { + x: E::Fr::zero(), + y: E::Fr::one(), + t: E::Fr::zero(), + z: E::Fr::one(), + _marker: PhantomData + } + } + + pub fn into_xy(&self) -> (E::Fr, E::Fr) + { + let zinv = self.z.inverse().unwrap(); + + let mut x = self.x; + x.mul_assign(&zinv); + + let mut y = self.y; + y.mul_assign(&zinv); + + (x, y) + } + + #[must_use] + pub fn negate(&self) -> Self { + let mut p = self.clone(); + + p.x.negate(); + p.t.negate(); + + p + } + + #[must_use] + pub fn double(&self, _: &E::Params) -> Self { + // See "Twisted Edwards Curves Revisited" + // Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson + // Section 3.3 + // http://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd + + // A = X1^2 + let mut a = self.x; + a.square(); + + // B = Y1^2 + let mut b = self.y; + b.square(); + + // C = 2*Z1^2 + let mut c = self.z; + c.square(); + c.double(); + + // D = a*A + // = -A + let mut d = a; + d.negate(); + + // E = (X1+Y1)^2 - A - B + let mut e = self.x; + e.add_assign(&self.y); + e.square(); + e.add_assign(&d); // -A = D + e.sub_assign(&b); + + // G = D+B + let mut g = d; + g.add_assign(&b); + + // F = G-C + let mut f = g; + f.sub_assign(&c); + + // H = D-B + let mut h = d; + h.sub_assign(&b); + + // X3 = E*F + let mut x3 = e; + x3.mul_assign(&f); + + // Y3 = G*H + let mut y3 = g; + y3.mul_assign(&h); + + // T3 = E*H + let mut t3 = e; + t3.mul_assign(&h); + + // Z3 = F*G + let mut z3 = f; + z3.mul_assign(&g); + + Point { + x: x3, + y: y3, + t: t3, + z: z3, + _marker: PhantomData + } + } + + #[must_use] + pub fn add(&self, other: &Self, params: &E::Params) -> Self + { + // See "Twisted Edwards Curves Revisited" + // Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson + // 3.1 Unified Addition in E^e + + // A = x1 * x2 + let mut a = self.x; + a.mul_assign(&other.x); + + // B = y1 * y2 + let mut b = self.y; + b.mul_assign(&other.y); + + // C = d * t1 * t2 + let mut c = params.edwards_d().clone(); + c.mul_assign(&self.t); + c.mul_assign(&other.t); + + // D = z1 * z2 + let mut d = self.z; + d.mul_assign(&other.z); + + // H = B - aA + // = B + A + let mut h = b; + h.add_assign(&a); + + // E = (x1 + y1) * (x2 + y2) - A - B + // = (x1 + y1) * (x2 + y2) - H + let mut e = self.x; + e.add_assign(&self.y); + { + let mut tmp = other.x; + tmp.add_assign(&other.y); + e.mul_assign(&tmp); + } + e.sub_assign(&h); + + // F = D - C + let mut f = d; + f.sub_assign(&c); + + // G = D + C + let mut g = d; + g.add_assign(&c); + + // x3 = E * F + let mut x3 = e; + x3.mul_assign(&f); + + // y3 = G * H + let mut y3 = g; + y3.mul_assign(&h); + + // t3 = E * H + let mut t3 = e; + t3.mul_assign(&h); + + // z3 = F * G + let mut z3 = f; + z3.mul_assign(&g); + + Point { + x: x3, + y: y3, + t: t3, + z: z3, + _marker: PhantomData + } + } + + #[must_use] + pub fn mul::Repr>>( + &self, + scalar: S, + params: &E::Params + ) -> Self + { + // Standard double-and-add scalar multiplication + + let mut res = Self::zero(); + + for b in BitIterator::new(scalar.into()) { + res = res.double(params); + + if b { + res = res.add(self, params); + } + } + + res + } +} diff --git a/sapling-crypto/src/jubjub/fs.rs b/sapling-crypto/src/jubjub/fs.rs new file mode 100644 index 0000000..eb10e65 --- /dev/null +++ b/sapling-crypto/src/jubjub/fs.rs @@ -0,0 +1,1232 @@ +use byteorder::{ByteOrder, LittleEndian}; +use pairing::{BitIterator, Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError, LegendreSymbol}; +use pairing::LegendreSymbol::*; +use pairing::{adc, sbb, mac_with_carry}; + +use super::ToUniform; + +// s = 6554484396890773809930967563523245729705921265872317281365359162392183254199 +const MODULUS: FsRepr = FsRepr([0xd0970e5ed6f72cb7, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9]); + +// The number of bits needed to represent the modulus. +const MODULUS_BITS: u32 = 252; + +// The number of bits that must be shaved from the beginning of +// the representation when randomly sampling. +const REPR_SHAVE_BITS: u32 = 4; + +// R = 2**256 % s +const R: FsRepr = FsRepr([0x25f80bb3b99607d9, 0xf315d62f66b6e750, 0x932514eeeb8814f4, 0x9a6fc6f479155c6]); + +// R2 = R^2 % s +const R2: FsRepr = FsRepr([0x67719aa495e57731, 0x51b0cef09ce3fc26, 0x69dab7fac026e9a5, 0x4f6547b8d127688]); + +// INV = -(s^{-1} mod 2^64) mod s +const INV: u64 = 0x1ba3a358ef788ef9; + +// GENERATOR = 6 (multiplicative generator of r-1 order, that is also quadratic nonresidue) +const GENERATOR: FsRepr = FsRepr([0x720b1b19d49ea8f1, 0xbf4aa36101f13a58, 0x5fa8cc968193ccbb, 0xe70cbdc7dccf3ac]); + +// 2^S * t = MODULUS - 1 with t odd +const S: u32 = 1; + +// 2^S root of unity computed by GENERATOR^t +const ROOT_OF_UNITY: FsRepr = FsRepr([0xaa9f02ab1d6124de, 0xb3524a6466112932, 0x7342261215ac260b, 0x4d6b87b1da259e2]); + +// -((2**256) mod s) mod s +const NEGATIVE_ONE: Fs = Fs(FsRepr([0xaa9f02ab1d6124de, 0xb3524a6466112932, 0x7342261215ac260b, 0x4d6b87b1da259e2])); + +/// This is the underlying representation of an element of `Fs`. +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +pub struct FsRepr(pub [u64; 4]); + +impl ::rand::Rand for FsRepr { + #[inline(always)] + fn rand(rng: &mut R) -> Self { + FsRepr(rng.gen()) + } +} + +impl ::std::fmt::Display for FsRepr +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + try!(write!(f, "0x")); + for i in self.0.iter().rev() { + try!(write!(f, "{:016x}", *i)); + } + + Ok(()) + } +} + +impl AsRef<[u64]> for FsRepr { + #[inline(always)] + fn as_ref(&self) -> &[u64] { + &self.0 + } +} + +impl AsMut<[u64]> for FsRepr { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u64] { + &mut self.0 + } +} + +impl From for FsRepr { + #[inline(always)] + fn from(val: u64) -> FsRepr { + let mut repr = Self::default(); + repr.0[0] = val; + repr + } +} + +impl Ord for FsRepr { + #[inline(always)] + fn cmp(&self, other: &FsRepr) -> ::std::cmp::Ordering { + for (a, b) in self.0.iter().rev().zip(other.0.iter().rev()) { + if a < b { + return ::std::cmp::Ordering::Less + } else if a > b { + return ::std::cmp::Ordering::Greater + } + } + + ::std::cmp::Ordering::Equal + } +} + +impl PartialOrd for FsRepr { + #[inline(always)] + fn partial_cmp(&self, other: &FsRepr) -> Option<::std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl PrimeFieldRepr for FsRepr { + #[inline(always)] + fn is_odd(&self) -> bool { + self.0[0] & 1 == 1 + } + + #[inline(always)] + fn is_even(&self) -> bool { + !self.is_odd() + } + + #[inline(always)] + fn is_zero(&self) -> bool { + self.0.iter().all(|&e| e == 0) + } + + #[inline(always)] + fn shr(&mut self, mut n: u32) { + if n >= 64 * 4 { + *self = Self::from(0); + return; + } + + while n >= 64 { + let mut t = 0; + for i in self.0.iter_mut().rev() { + ::std::mem::swap(&mut t, i); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + for i in self.0.iter_mut().rev() { + let t2 = *i << (64 - n); + *i >>= n; + *i |= t; + t = t2; + } + } + } + + #[inline(always)] + fn div2(&mut self) { + let mut t = 0; + for i in self.0.iter_mut().rev() { + let t2 = *i << 63; + *i >>= 1; + *i |= t; + t = t2; + } + } + + #[inline(always)] + fn mul2(&mut self) { + let mut last = 0; + for i in &mut self.0 { + let tmp = *i >> 63; + *i <<= 1; + *i |= last; + last = tmp; + } + } + + #[inline(always)] + fn shl(&mut self, mut n: u32) { + if n >= 64 * 4 { + *self = Self::from(0); + return; + } + + while n >= 64 { + let mut t = 0; + for i in &mut self.0 { + ::std::mem::swap(&mut t, i); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + for i in &mut self.0 { + let t2 = *i >> (64 - n); + *i <<= n; + *i |= t; + t = t2; + } + } + } + + #[inline(always)] + fn num_bits(&self) -> u32 { + let mut ret = (4 as u32) * 64; + for i in self.0.iter().rev() { + let leading = i.leading_zeros(); + ret -= leading; + if leading != 64 { + break; + } + } + + ret + } + + #[inline(always)] + fn add_nocarry(&mut self, other: &FsRepr) { + let mut carry = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = adc(*a, *b, &mut carry); + } + } + + #[inline(always)] + fn sub_noborrow(&mut self, other: &FsRepr) { + let mut borrow = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = sbb(*a, *b, &mut borrow); + } + } +} + +/// This is an element of the scalar field of the Jubjub curve. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Fs(FsRepr); + +impl ::std::fmt::Display for Fs +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "Fs({})", self.into_repr()) + } +} + +impl ::rand::Rand for Fs { + fn rand(rng: &mut R) -> Self { + loop { + let mut tmp = Fs(FsRepr::rand(rng)); + + // Mask away the unused bits at the beginning. + tmp.0.as_mut()[3] &= 0xffffffffffffffff >> REPR_SHAVE_BITS; + + if tmp.is_valid() { + return tmp + } + } + } +} + +impl From for FsRepr { + fn from(e: Fs) -> FsRepr { + e.into_repr() + } +} + +impl PrimeField for Fs { + type Repr = FsRepr; + + fn from_repr(r: FsRepr) -> Result { + let mut r = Fs(r); + if r.is_valid() { + r.mul_assign(&Fs(R2)); + + Ok(r) + } else { + Err(PrimeFieldDecodingError::NotInField(format!("{}", r.0))) + } + } + + fn into_repr(&self) -> FsRepr { + let mut r = *self; + r.mont_reduce((self.0).0[0], (self.0).0[1], + (self.0).0[2], (self.0).0[3], + 0, 0, 0, 0); + r.0 + } + + fn char() -> FsRepr { + MODULUS + } + + const NUM_BITS: u32 = MODULUS_BITS; + + const CAPACITY: u32 = Self::NUM_BITS - 1; + + fn multiplicative_generator() -> Self { + Fs(GENERATOR) + } + + const S: u32 = S; + + fn root_of_unity() -> Self { + Fs(ROOT_OF_UNITY) + } +} + +impl Field for Fs { + #[inline] + fn zero() -> Self { + Fs(FsRepr::from(0)) + } + + #[inline] + fn one() -> Self { + Fs(R) + } + + #[inline] + fn is_zero(&self) -> bool { + self.0.is_zero() + } + + #[inline] + fn add_assign(&mut self, other: &Fs) { + // This cannot exceed the backing capacity. + self.0.add_nocarry(&other.0); + + // However, it may need to be reduced. + self.reduce(); + } + + #[inline] + fn double(&mut self) { + // This cannot exceed the backing capacity. + self.0.mul2(); + + // However, it may need to be reduced. + self.reduce(); + } + + #[inline] + fn sub_assign(&mut self, other: &Fs) { + // If `other` is larger than `self`, we'll need to add the modulus to self first. + if other.0 > self.0 { + self.0.add_nocarry(&MODULUS); + } + + self.0.sub_noborrow(&other.0); + } + + #[inline] + fn negate(&mut self) { + if !self.is_zero() { + let mut tmp = MODULUS; + tmp.sub_noborrow(&self.0); + self.0 = tmp; + } + } + + fn inverse(&self) -> Option { + if self.is_zero() { + None + } else { + // Guajardo Kumar Paar Pelzl + // Efficient Software-Implementation of Finite Fields with Applications to Cryptography + // Algorithm 16 (BEA for Inversion in Fp) + + let one = FsRepr::from(1); + + let mut u = self.0; + let mut v = MODULUS; + let mut b = Fs(R2); // Avoids unnecessary reduction step. + let mut c = Self::zero(); + + while u != one && v != one { + while u.is_even() { + u.div2(); + + if b.0.is_even() { + b.0.div2(); + } else { + b.0.add_nocarry(&MODULUS); + b.0.div2(); + } + } + + while v.is_even() { + v.div2(); + + if c.0.is_even() { + c.0.div2(); + } else { + c.0.add_nocarry(&MODULUS); + c.0.div2(); + } + } + + if v < u { + u.sub_noborrow(&v); + b.sub_assign(&c); + } else { + v.sub_noborrow(&u); + c.sub_assign(&b); + } + } + + if u == one { + Some(b) + } else { + Some(c) + } + } + } + + #[inline(always)] + fn frobenius_map(&mut self, _: usize) { + // This has no effect in a prime field. + } + + #[inline] + fn mul_assign(&mut self, other: &Fs) + { + let mut carry = 0; + let r0 = mac_with_carry(0, (self.0).0[0], (other.0).0[0], &mut carry); + let r1 = mac_with_carry(0, (self.0).0[0], (other.0).0[1], &mut carry); + let r2 = mac_with_carry(0, (self.0).0[0], (other.0).0[2], &mut carry); + let r3 = mac_with_carry(0, (self.0).0[0], (other.0).0[3], &mut carry); + let r4 = carry; + let mut carry = 0; + let r1 = mac_with_carry(r1, (self.0).0[1], (other.0).0[0], &mut carry); + let r2 = mac_with_carry(r2, (self.0).0[1], (other.0).0[1], &mut carry); + let r3 = mac_with_carry(r3, (self.0).0[1], (other.0).0[2], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[1], (other.0).0[3], &mut carry); + let r5 = carry; + let mut carry = 0; + let r2 = mac_with_carry(r2, (self.0).0[2], (other.0).0[0], &mut carry); + let r3 = mac_with_carry(r3, (self.0).0[2], (other.0).0[1], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[2], (other.0).0[2], &mut carry); + let r5 = mac_with_carry(r5, (self.0).0[2], (other.0).0[3], &mut carry); + let r6 = carry; + let mut carry = 0; + let r3 = mac_with_carry(r3, (self.0).0[3], (other.0).0[0], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[3], (other.0).0[1], &mut carry); + let r5 = mac_with_carry(r5, (self.0).0[3], (other.0).0[2], &mut carry); + let r6 = mac_with_carry(r6, (self.0).0[3], (other.0).0[3], &mut carry); + let r7 = carry; + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + } + + #[inline] + fn square(&mut self) + { + let mut carry = 0; + let r1 = mac_with_carry(0, (self.0).0[0], (self.0).0[1], &mut carry); + let r2 = mac_with_carry(0, (self.0).0[0], (self.0).0[2], &mut carry); + let r3 = mac_with_carry(0, (self.0).0[0], (self.0).0[3], &mut carry); + let r4 = carry; + let mut carry = 0; + let r3 = mac_with_carry(r3, (self.0).0[1], (self.0).0[2], &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[1], (self.0).0[3], &mut carry); + let r5 = carry; + let mut carry = 0; + let r5 = mac_with_carry(r5, (self.0).0[2], (self.0).0[3], &mut carry); + let r6 = carry; + + let r7 = r6 >> 63; + let r6 = (r6 << 1) | (r5 >> 63); + let r5 = (r5 << 1) | (r4 >> 63); + let r4 = (r4 << 1) | (r3 >> 63); + let r3 = (r3 << 1) | (r2 >> 63); + let r2 = (r2 << 1) | (r1 >> 63); + let r1 = r1 << 1; + + let mut carry = 0; + let r0 = mac_with_carry(0, (self.0).0[0], (self.0).0[0], &mut carry); + let r1 = adc(r1, 0, &mut carry); + let r2 = mac_with_carry(r2, (self.0).0[1], (self.0).0[1], &mut carry); + let r3 = adc(r3, 0, &mut carry); + let r4 = mac_with_carry(r4, (self.0).0[2], (self.0).0[2], &mut carry); + let r5 = adc(r5, 0, &mut carry); + let r6 = mac_with_carry(r6, (self.0).0[3], (self.0).0[3], &mut carry); + let r7 = adc(r7, 0, &mut carry); + self.mont_reduce(r0, r1, r2, r3, r4, r5, r6, r7); + } +} + +impl Fs { + /// Determines if the element is really in the field. This is only used + /// internally. + #[inline(always)] + fn is_valid(&self) -> bool { + self.0 < MODULUS + } + + /// Subtracts the modulus from this element if this element is not in the + /// field. Only used internally. + #[inline(always)] + fn reduce(&mut self) { + if !self.is_valid() { + self.0.sub_noborrow(&MODULUS); + } + } + + #[inline(always)] + fn mont_reduce( + &mut self, + r0: u64, + mut r1: u64, + mut r2: u64, + mut r3: u64, + mut r4: u64, + mut r5: u64, + mut r6: u64, + mut r7: u64 + ) + { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = r0.wrapping_mul(INV); + let mut carry = 0; + mac_with_carry(r0, k, MODULUS.0[0], &mut carry); + r1 = mac_with_carry(r1, k, MODULUS.0[1], &mut carry); + r2 = mac_with_carry(r2, k, MODULUS.0[2], &mut carry); + r3 = mac_with_carry(r3, k, MODULUS.0[3], &mut carry); + r4 = adc(r4, 0, &mut carry); + let carry2 = carry; + let k = r1.wrapping_mul(INV); + let mut carry = 0; + mac_with_carry(r1, k, MODULUS.0[0], &mut carry); + r2 = mac_with_carry(r2, k, MODULUS.0[1], &mut carry); + r3 = mac_with_carry(r3, k, MODULUS.0[2], &mut carry); + r4 = mac_with_carry(r4, k, MODULUS.0[3], &mut carry); + r5 = adc(r5, carry2, &mut carry); + let carry2 = carry; + let k = r2.wrapping_mul(INV); + let mut carry = 0; + mac_with_carry(r2, k, MODULUS.0[0], &mut carry); + r3 = mac_with_carry(r3, k, MODULUS.0[1], &mut carry); + r4 = mac_with_carry(r4, k, MODULUS.0[2], &mut carry); + r5 = mac_with_carry(r5, k, MODULUS.0[3], &mut carry); + r6 = adc(r6, carry2, &mut carry); + let carry2 = carry; + let k = r3.wrapping_mul(INV); + let mut carry = 0; + mac_with_carry(r3, k, MODULUS.0[0], &mut carry); + r4 = mac_with_carry(r4, k, MODULUS.0[1], &mut carry); + r5 = mac_with_carry(r5, k, MODULUS.0[2], &mut carry); + r6 = mac_with_carry(r6, k, MODULUS.0[3], &mut carry); + r7 = adc(r7, carry2, &mut carry); + (self.0).0[0] = r4; + (self.0).0[1] = r5; + (self.0).0[2] = r6; + (self.0).0[3] = r7; + self.reduce(); + } + + fn mul_bits>(&self, bits: BitIterator) -> Self { + let mut res = Self::zero(); + for bit in bits { + res.double(); + + if bit { + res.add_assign(self) + } + } + res + } +} + +impl ToUniform for Fs { + /// Convert a little endian byte string into a uniform + /// field element. The number is reduced mod s. The caller + /// is responsible for ensuring the input is 64 bytes of + /// Random Oracle output. + fn to_uniform(digest: &[u8]) -> Self { + assert_eq!(digest.len(), 64); + let mut repr: [u64; 8] = [0; 8]; + LittleEndian::read_u64_into(digest, &mut repr); + Self::one().mul_bits(BitIterator::new(repr)) + } +} + +impl SqrtField for Fs { + + fn legendre(&self) -> LegendreSymbol { + // s = self^((s - 1) // 2) + let s = self.pow([0x684b872f6b7b965b, 0x53341049e6640841, 0x83339d80809a1d80, 0x73eda753299d7d4]); + if s == Self::zero() { Zero } + else if s == Self::one() { QuadraticResidue } + else { QuadraticNonResidue } + } + + fn sqrt(&self) -> Option { + // Shank's algorithm for s mod 4 = 3 + // https://eprint.iacr.org/2012/685.pdf (page 9, algorithm 2) + + // a1 = self^((s - 3) // 4) + let mut a1 = self.pow([0xb425c397b5bdcb2d, 0x299a0824f3320420, 0x4199cec0404d0ec0, 0x39f6d3a994cebea]); + let mut a0 = a1; + a0.square(); + a0.mul_assign(self); + + if a0 == NEGATIVE_ONE + { + None + } + else + { + a1.mul_assign(self); + Some(a1) + } + } +} + + +#[test] +fn test_neg_one() { + let mut o = Fs::one(); + o.negate(); + + assert_eq!(NEGATIVE_ONE, o); +} + +#[cfg(test)] +use rand::{SeedableRng, XorShiftRng, Rand}; + +#[test] +fn test_fs_repr_ordering() { + fn assert_equality(a: FsRepr, b: FsRepr) { + assert_eq!(a, b); + assert!(a.cmp(&b) == ::std::cmp::Ordering::Equal); + } + + fn assert_lt(a: FsRepr, b: FsRepr) { + assert!(a < b); + assert!(b > a); + } + + assert_equality(FsRepr([9999, 9999, 9999, 9999]), FsRepr([9999, 9999, 9999, 9999])); + assert_equality(FsRepr([9999, 9998, 9999, 9999]), FsRepr([9999, 9998, 9999, 9999])); + assert_equality(FsRepr([9999, 9999, 9999, 9997]), FsRepr([9999, 9999, 9999, 9997])); + assert_lt(FsRepr([9999, 9997, 9999, 9998]), FsRepr([9999, 9997, 9999, 9999])); + assert_lt(FsRepr([9999, 9997, 9998, 9999]), FsRepr([9999, 9997, 9999, 9999])); + assert_lt(FsRepr([9, 9999, 9999, 9997]), FsRepr([9999, 9999, 9999, 9997])); +} + +#[test] +fn test_fs_repr_from() { + assert_eq!(FsRepr::from(100), FsRepr([100, 0, 0, 0])); +} + +#[test] +fn test_fs_repr_is_odd() { + assert!(!FsRepr::from(0).is_odd()); + assert!(FsRepr::from(0).is_even()); + assert!(FsRepr::from(1).is_odd()); + assert!(!FsRepr::from(1).is_even()); + assert!(!FsRepr::from(324834872).is_odd()); + assert!(FsRepr::from(324834872).is_even()); + assert!(FsRepr::from(324834873).is_odd()); + assert!(!FsRepr::from(324834873).is_even()); +} + +#[test] +fn test_fs_repr_is_zero() { + assert!(FsRepr::from(0).is_zero()); + assert!(!FsRepr::from(1).is_zero()); + assert!(!FsRepr([0, 0, 1, 0]).is_zero()); +} + +#[test] +fn test_fs_repr_div2() { + let mut a = FsRepr([0xbd2920b19c972321, 0x174ed0466a3be37e, 0xd468d5e3b551f0b5, 0xcb67c072733beefc]); + a.div2(); + assert_eq!(a, FsRepr([0x5e949058ce4b9190, 0x8ba76823351df1bf, 0x6a346af1daa8f85a, 0x65b3e039399df77e])); + for _ in 0..10 { + a.div2(); + } + assert_eq!(a, FsRepr([0x6fd7a524163392e4, 0x16a2e9da08cd477c, 0xdf9a8d1abc76aa3e, 0x196cf80e4e677d])); + for _ in 0..200 { + a.div2(); + } + assert_eq!(a, FsRepr([0x196cf80e4e67, 0x0, 0x0, 0x0])); + for _ in 0..40 { + a.div2(); + } + assert_eq!(a, FsRepr([0x19, 0x0, 0x0, 0x0])); + for _ in 0..4 { + a.div2(); + } + assert_eq!(a, FsRepr([0x1, 0x0, 0x0, 0x0])); + a.div2(); + assert!(a.is_zero()); +} + +#[test] +fn test_fs_repr_shr() { + let mut a = FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]); + a.shr(0); + assert_eq!( + a, + FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]) + ); + a.shr(1); + assert_eq!( + a, + FsRepr([0xd99fdd762415141f, 0xccbef069d44659ef, 0xcd7b16954d072a92, 0x1b001d5846f386d0]) + ); + a.shr(50); + assert_eq!( + a, + FsRepr([0xbc1a7511967bf667, 0xc5a55341caa4b32f, 0x75611bce1b4335e, 0x6c0]) + ); + a.shr(130); + assert_eq!( + a, + FsRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0]) + ); + a.shr(64); + assert_eq!( + a, + FsRepr([0x1b0, 0x0, 0x0, 0x0]) + ); +} + +#[test] +fn test_fs_repr_mul2() { + let mut a = FsRepr::from(23712937547); + a.mul2(); + assert_eq!(a, FsRepr([0xb0acd6c96, 0x0, 0x0, 0x0])); + for _ in 0..60 { + a.mul2(); + } + assert_eq!(a, FsRepr([0x6000000000000000, 0xb0acd6c9, 0x0, 0x0])); + for _ in 0..128 { + a.mul2(); + } + assert_eq!(a, FsRepr([0x0, 0x0, 0x6000000000000000, 0xb0acd6c9])); + for _ in 0..60 { + a.mul2(); + } + assert_eq!(a, FsRepr([0x0, 0x0, 0x0, 0x9600000000000000])); + for _ in 0..7 { + a.mul2(); + } + assert!(a.is_zero()); +} + +#[test] +fn test_fs_repr_num_bits() { + let mut a = FsRepr::from(0); + assert_eq!(0, a.num_bits()); + a = FsRepr::from(1); + for i in 1..257 { + assert_eq!(i, a.num_bits()); + a.mul2(); + } + assert_eq!(0, a.num_bits()); +} + +#[test] +fn test_fs_repr_sub_noborrow() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut t = FsRepr([0x8e62a7e85264e2c3, 0xb23d34c1941d3ca, 0x5976930b7502dd15, 0x600f3fb517bf5495]); + t.sub_noborrow(&FsRepr([0xd64f669809cbc6a4, 0xfa76cb9d90cf7637, 0xfefb0df9038d43b3, 0x298a30c744b31acf])); + assert!(t == FsRepr([0xb813415048991c1f, 0x10ad07ae88725d92, 0x5a7b851271759961, 0x36850eedd30c39c5])); + + for _ in 0..1000 { + let mut a = FsRepr::rand(&mut rng); + a.0[3] >>= 30; + let mut b = a; + for _ in 0..10 { + b.mul2(); + } + let mut c = b; + for _ in 0..10 { + c.mul2(); + } + + assert!(a < b); + assert!(b < c); + + let mut csub_ba = c; + csub_ba.sub_noborrow(&b); + csub_ba.sub_noborrow(&a); + + let mut csub_ab = c; + csub_ab.sub_noborrow(&a); + csub_ab.sub_noborrow(&b); + + assert_eq!(csub_ab, csub_ba); + } +} + +#[test] +fn test_fs_legendre() { + assert_eq!(QuadraticResidue, Fs::one().legendre()); + assert_eq!(Zero, Fs::zero().legendre()); + + let e = FsRepr([0x8385eec23df1f88e, 0x9a01fb412b2dba16, 0x4c928edcdd6c22f, 0x9f2df7ef69ecef9]); + assert_eq!(QuadraticResidue, Fs::from_repr(e).unwrap().legendre()); + let e = FsRepr([0xe8ed9f299da78568, 0x35efdebc88b2209, 0xc82125cb1f916dbe, 0x6813d2b38c39bd0]); + assert_eq!(QuadraticNonResidue, Fs::from_repr(e).unwrap().legendre()); +} + +#[test] +fn test_fr_repr_add_nocarry() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut t = FsRepr([0xd64f669809cbc6a4, 0xfa76cb9d90cf7637, 0xfefb0df9038d43b3, 0x298a30c744b31acf]); + t.add_nocarry(&FsRepr([0x8e62a7e85264e2c3, 0xb23d34c1941d3ca, 0x5976930b7502dd15, 0x600f3fb517bf5495])); + assert_eq!(t, FsRepr([0x64b20e805c30a967, 0x59a9ee9aa114a02, 0x5871a104789020c9, 0x8999707c5c726f65])); + + // Test for the associativity of addition. + for _ in 0..1000 { + let mut a = FsRepr::rand(&mut rng); + let mut b = FsRepr::rand(&mut rng); + let mut c = FsRepr::rand(&mut rng); + + // Unset the first few bits, so that overflow won't occur. + a.0[3] >>= 3; + b.0[3] >>= 3; + c.0[3] >>= 3; + + let mut abc = a; + abc.add_nocarry(&b); + abc.add_nocarry(&c); + + let mut acb = a; + acb.add_nocarry(&c); + acb.add_nocarry(&b); + + let mut bac = b; + bac.add_nocarry(&a); + bac.add_nocarry(&c); + + let mut bca = b; + bca.add_nocarry(&c); + bca.add_nocarry(&a); + + let mut cab = c; + cab.add_nocarry(&a); + cab.add_nocarry(&b); + + let mut cba = c; + cba.add_nocarry(&b); + cba.add_nocarry(&a); + + assert_eq!(abc, acb); + assert_eq!(abc, bac); + assert_eq!(abc, bca); + assert_eq!(abc, cab); + assert_eq!(abc, cba); + } +} + +#[test] +fn test_fs_is_valid() { + let mut a = Fs(MODULUS); + assert!(!a.is_valid()); + a.0.sub_noborrow(&FsRepr::from(1)); + assert!(a.is_valid()); + assert!(Fs(FsRepr::from(0)).is_valid()); + assert!(Fs(FsRepr([0xd0970e5ed6f72cb6, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9])).is_valid()); + assert!(!Fs(FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])).is_valid()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let a = Fs::rand(&mut rng); + assert!(a.is_valid()); + } +} + +#[test] +fn test_fs_add_assign() { + { + // Random number + let mut tmp = Fs::from_str("4577408157467272683998459759522778614363623736323078995109579213719612604198").unwrap(); + assert!(tmp.is_valid()); + // Test that adding zero has no effect. + tmp.add_assign(&Fs(FsRepr::from(0))); + assert_eq!(tmp, Fs(FsRepr([0x8e6bfff4722d6e67, 0x5643da5c892044f9, 0x9465f4b281921a69, 0x25f752d3edd7162]))); + // Add one and test for the result. + tmp.add_assign(&Fs(FsRepr::from(1))); + assert_eq!(tmp, Fs(FsRepr([0x8e6bfff4722d6e68, 0x5643da5c892044f9, 0x9465f4b281921a69, 0x25f752d3edd7162]))); + // Add another random number that exercises the reduction. + tmp.add_assign(&Fs(FsRepr([0xb634d07bc42d4a70, 0xf724f0c008411f5f, 0x456d4053d865af34, 0x24ce814e8c63027]))); + assert_eq!(tmp, Fs(FsRepr([0x44a0d070365ab8d8, 0x4d68cb1c91616459, 0xd9d3350659f7c99e, 0x4ac5d4227a3a189]))); + // Add one to (s - 1) and test for the result. + tmp = Fs(FsRepr([0xd0970e5ed6f72cb6, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9])); + tmp.add_assign(&Fs(FsRepr::from(1))); + assert!(tmp.0.is_zero()); + // Add a random number to another one such that the result is s - 1 + tmp = Fs(FsRepr([0xa11fda5950ce3636, 0x922e0dbccfe0ca0e, 0xacebb6e215b82d4a, 0x97ffb8cdc3aee93])); + tmp.add_assign(&Fs(FsRepr([0x2f7734058628f680, 0x143a12d6fce74674, 0x597b841eeb7c0db6, 0x4fdb95d88f8c115]))); + assert_eq!(tmp, Fs(FsRepr([0xd0970e5ed6f72cb6, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9]))); + // Add one to the result and test for it. + tmp.add_assign(&Fs(FsRepr::from(1))); + assert!(tmp.0.is_zero()); + } + + // Test associativity + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Generate a, b, c and ensure (a + b) + c == a + (b + c). + let a = Fs::rand(&mut rng); + let b = Fs::rand(&mut rng); + let c = Fs::rand(&mut rng); + + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.add_assign(&c); + + let mut tmp2 = b; + tmp2.add_assign(&c); + tmp2.add_assign(&a); + + assert!(tmp1.is_valid()); + assert!(tmp2.is_valid()); + assert_eq!(tmp1, tmp2); + } +} + +#[test] +fn test_fs_sub_assign() { + { + // Test arbitrary subtraction that tests reduction. + let mut tmp = Fs(FsRepr([0xb384d9f6877afd99, 0x4442513958e1a1c1, 0x352c4b8a95eccc3f, 0x2db62dee4b0f2])); + tmp.sub_assign(&Fs(FsRepr([0xec5bd2d13ed6b05a, 0x2adc0ab3a39b5fa, 0x82d3360a493e637e, 0x53ccff4a64d6679]))); + assert_eq!(tmp, Fs(FsRepr([0x97c015841f9b79f6, 0xe7fcb121eb6ffc49, 0xb8c050814de2a3c1, 0x943c0589dcafa21]))); + + // Test the opposite subtraction which doesn't test reduction. + tmp = Fs(FsRepr([0xec5bd2d13ed6b05a, 0x2adc0ab3a39b5fa, 0x82d3360a493e637e, 0x53ccff4a64d6679])); + tmp.sub_assign(&Fs(FsRepr([0xb384d9f6877afd99, 0x4442513958e1a1c1, 0x352c4b8a95eccc3f, 0x2db62dee4b0f2]))); + assert_eq!(tmp, Fs(FsRepr([0x38d6f8dab75bb2c1, 0xbe6b6f71e1581439, 0x4da6ea7fb351973e, 0x539f491c768b587]))); + + // Test for sensible results with zero + tmp = Fs(FsRepr::from(0)); + tmp.sub_assign(&Fs(FsRepr::from(0))); + assert!(tmp.is_zero()); + + tmp = Fs(FsRepr([0x361e16aef5cce835, 0x55bbde2536e274c1, 0x4dc77a63fd15ee75, 0x1e14bb37c14f230])); + tmp.sub_assign(&Fs(FsRepr::from(0))); + assert_eq!(tmp, Fs(FsRepr([0x361e16aef5cce835, 0x55bbde2536e274c1, 0x4dc77a63fd15ee75, 0x1e14bb37c14f230]))); + } + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure that (a - b) + (b - a) = 0. + let a = Fs::rand(&mut rng); + let b = Fs::rand(&mut rng); + + let mut tmp1 = a; + tmp1.sub_assign(&b); + + let mut tmp2 = b; + tmp2.sub_assign(&a); + + tmp1.add_assign(&tmp2); + assert!(tmp1.is_zero()); + } +} + +#[test] +fn test_fs_mul_assign() { + let mut tmp = Fs(FsRepr([0xb433b01287f71744, 0x4eafb86728c4d108, 0xfdd52c14b9dfbe65, 0x2ff1f3434821118])); + tmp.mul_assign(&Fs(FsRepr([0xdae00fc63c9fa90f, 0x5a5ed89b96ce21ce, 0x913cd26101bd6f58, 0x3f0822831697fe9]))); + assert!(tmp == Fs(FsRepr([0xb68ecb61d54d2992, 0x5ff95874defce6a6, 0x3590eb053894657d, 0x53823a118515933]))); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000000 { + // Ensure that (a * b) * c = a * (b * c) + let a = Fs::rand(&mut rng); + let b = Fs::rand(&mut rng); + let c = Fs::rand(&mut rng); + + let mut tmp1 = a; + tmp1.mul_assign(&b); + tmp1.mul_assign(&c); + + let mut tmp2 = b; + tmp2.mul_assign(&c); + tmp2.mul_assign(&a); + + assert_eq!(tmp1, tmp2); + } + + for _ in 0..1000000 { + // Ensure that r * (a + b + c) = r*a + r*b + r*c + + let r = Fs::rand(&mut rng); + let mut a = Fs::rand(&mut rng); + let mut b = Fs::rand(&mut rng); + let mut c = Fs::rand(&mut rng); + + let mut tmp1 = a; + tmp1.add_assign(&b); + tmp1.add_assign(&c); + tmp1.mul_assign(&r); + + a.mul_assign(&r); + b.mul_assign(&r); + c.mul_assign(&r); + + a.add_assign(&b); + a.add_assign(&c); + + assert_eq!(tmp1, a); + } +} + +#[test] +fn test_fr_squaring() { + let mut a = Fs(FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xe7db4ea6533afa8])); + assert!(a.is_valid()); + a.square(); + assert_eq!(a, Fs::from_repr(FsRepr([0x12c7f55cbc52fbaa, 0xdedc98a0b5e6ce9e, 0xad2892726a5396a, 0x9fe82af8fee77b3])).unwrap()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000000 { + // Ensure that (a * a) = a^2 + let a = Fs::rand(&mut rng); + + let mut tmp = a; + tmp.square(); + + let mut tmp2 = a; + tmp2.mul_assign(&a); + + assert_eq!(tmp, tmp2); + } +} + +#[test] +fn test_fs_inverse() { + assert!(Fs::zero().inverse().is_none()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let one = Fs::one(); + + for _ in 0..1000 { + // Ensure that a * a^-1 = 1 + let mut a = Fs::rand(&mut rng); + let ainv = a.inverse().unwrap(); + a.mul_assign(&ainv); + assert_eq!(a, one); + } +} + +#[test] +fn test_fs_double() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure doubling a is equivalent to adding a to itself. + let mut a = Fs::rand(&mut rng); + let mut b = a; + b.add_assign(&a); + a.double(); + assert_eq!(a, b); + } +} + +#[test] +fn test_fs_negate() { + { + let mut a = Fs::zero(); + a.negate(); + + assert!(a.is_zero()); + } + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Ensure (a - (-a)) = 0. + let mut a = Fs::rand(&mut rng); + let mut b = a; + b.negate(); + a.add_assign(&b); + + assert!(a.is_zero()); + } +} + +#[test] +fn test_fs_pow() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for i in 0..1000 { + // Exponentiate by various small numbers and ensure it consists with repeated + // multiplication. + let a = Fs::rand(&mut rng); + let target = a.pow(&[i]); + let mut c = Fs::one(); + for _ in 0..i { + c.mul_assign(&a); + } + assert_eq!(c, target); + } + + for _ in 0..1000 { + // Exponentiating by the modulus should have no effect in a prime field. + let a = Fs::rand(&mut rng); + + assert_eq!(a, a.pow(Fs::char())); + } +} + +#[test] +fn test_fs_sqrt() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + assert_eq!(Fs::zero().sqrt().unwrap(), Fs::zero()); + + for _ in 0..1000 { + // Ensure sqrt(a^2) = a or -a + let a = Fs::rand(&mut rng); + let mut nega = a; + nega.negate(); + let mut b = a; + b.square(); + + let b = b.sqrt().unwrap(); + + assert!(a == b || nega == b); + } + + for _ in 0..1000 { + // Ensure sqrt(a)^2 = a for random a + let a = Fs::rand(&mut rng); + + if let Some(mut tmp) = a.sqrt() { + tmp.square(); + + assert_eq!(a, tmp); + } + } +} + +#[test] +fn test_fs_from_into_repr() { + // r + 1 should not be in the field + assert!(Fs::from_repr(FsRepr([0xd0970e5ed6f72cb8, 0xa6682093ccc81082, 0x6673b0101343b00, 0xe7db4ea6533afa9])).is_err()); + + // r should not be in the field + assert!(Fs::from_repr(Fs::char()).is_err()); + + // Multiply some arbitrary representations to see if the result is as expected. + let a = FsRepr([0x5f2d0c05d0337b71, 0xa1df2b0f8a20479, 0xad73785e71bb863, 0x504a00480c9acec]); + let mut a_fs = Fs::from_repr(a).unwrap(); + let b = FsRepr([0x66356ff51e477562, 0x60a92ab55cf7603, 0x8e4273c7364dd192, 0x36df8844a344dc5]); + let b_fs = Fs::from_repr(b).unwrap(); + let c = FsRepr([0x7eef61708f4f2868, 0x747a7e6cf52946fb, 0x83dd75d7c9120017, 0x762f5177f0f3df7]); + a_fs.mul_assign(&b_fs); + assert_eq!(a_fs.into_repr(), c); + + // Zero should be in the field. + assert!(Fs::from_repr(FsRepr::from(0)).unwrap().is_zero()); + + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + // Try to turn Fs elements into representations and back again, and compare. + let a = Fs::rand(&mut rng); + let a_repr = a.into_repr(); + let b_repr = FsRepr::from(a); + assert_eq!(a_repr, b_repr); + let a_again = Fs::from_repr(a_repr).unwrap(); + + assert_eq!(a, a_again); + } +} + +#[test] +fn test_fs_repr_display() { + assert_eq!( + format!("{}", FsRepr([0xa296db59787359df, 0x8d3e33077430d318, 0xd1abf5c606102eb7, 0xcbc33ee28108f0])), + "0x00cbc33ee28108f0d1abf5c606102eb78d3e33077430d318a296db59787359df".to_string() + ); + assert_eq!( + format!("{}", FsRepr([0x14cb03535054a620, 0x312aa2bf2d1dff52, 0x970fe98746ab9361, 0xc1e18acf82711e6])), + "0x0c1e18acf82711e6970fe98746ab9361312aa2bf2d1dff5214cb03535054a620".to_string() + ); + assert_eq!( + format!("{}", FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff])), + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string() + ); + assert_eq!( + format!("{}", FsRepr([0, 0, 0, 0])), + "0x0000000000000000000000000000000000000000000000000000000000000000".to_string() + ); +} + +#[test] +fn test_fs_display() { + assert_eq!( + format!("{}", Fs::from_repr(FsRepr([0x5528efb9998a01a3, 0x5bd2add5cb357089, 0xc061fa6adb491f98, 0x70db9d143db03d9])).unwrap()), + "Fs(0x070db9d143db03d9c061fa6adb491f985bd2add5cb3570895528efb9998a01a3)".to_string() + ); + assert_eq!( + format!("{}", Fs::from_repr(FsRepr([0xd674745e2717999e, 0xbeb1f52d3e96f338, 0x9c7ae147549482b9, 0x999706024530d22])).unwrap()), + "Fs(0x0999706024530d229c7ae147549482b9beb1f52d3e96f338d674745e2717999e)".to_string() + ); +} + +#[test] +fn test_fs_num_bits() { + assert_eq!(Fs::NUM_BITS, 252); + assert_eq!(Fs::CAPACITY, 251); +} + +#[test] +fn test_fs_root_of_unity() { + assert_eq!(Fs::S, 1); + assert_eq!(Fs::multiplicative_generator(), Fs::from_repr(FsRepr::from(6)).unwrap()); + assert_eq!( + Fs::multiplicative_generator().pow([0x684b872f6b7b965b, 0x53341049e6640841, 0x83339d80809a1d80, 0x73eda753299d7d4]), + Fs::root_of_unity() + ); + assert_eq!( + Fs::root_of_unity().pow([1 << Fs::S]), + Fs::one() + ); + assert!(Fs::multiplicative_generator().sqrt().is_none()); +} diff --git a/sapling-crypto/src/jubjub/mod.rs b/sapling-crypto/src/jubjub/mod.rs new file mode 100644 index 0000000..51a000a --- /dev/null +++ b/sapling-crypto/src/jubjub/mod.rs @@ -0,0 +1,435 @@ +//! Jubjub is a twisted Edwards curve defined over the BLS12-381 scalar +//! field, Fr. It takes the form `-x^2 + y^2 = 1 + dx^2y^2` with +//! `d = -(10240/10241)`. It is birationally equivalent to a Montgomery +//! curve of the form `y^2 = x^3 + Ax^2 + x` with `A = 40962`. This +//! value `A` is the smallest integer choice such that: +//! +//! * `(A - 2) / 4` is a small integer (`10240`). +//! * `A^2 - 4` is quadratic nonresidue. +//! * The group order of the curve and its quadratic twist has a large +//! prime factor. +//! +//! Jubjub has `s = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7` +//! as the prime subgroup order, with cofactor 8. (The twist has +//! cofactor 4.) +//! +//! It is a complete twisted Edwards curve, so the equivalence with +//! the Montgomery curve forms a group isomorphism, allowing points +//! to be freely converted between the two forms. + +use pairing::{ + Engine, + Field, + PrimeField, + SqrtField +}; + +use group_hash::group_hash; + +use constants; + +use pairing::bls12_381::{ + Bls12, + Fr +}; + +/// This is an implementation of the twisted Edwards Jubjub curve. +pub mod edwards; + +/// This is an implementation of the birationally equivalent +/// Montgomery curve. +pub mod montgomery; + +/// This is an implementation of the scalar field for Jubjub. +pub mod fs; + +#[cfg(test)] +pub mod tests; + +/// Point of unknown order. +pub enum Unknown { } + +/// Point of prime order. +pub enum PrimeOrder { } + +/// Fixed generators of the Jubjub curve of unknown +/// exponent. +#[derive(Copy, Clone)] +pub enum FixedGenerators { + /// The prover will demonstrate knowledge of discrete log + /// with respect to this base when they are constructing + /// a proof, in order to authorize proof construction. + ProofGenerationKey = 0, + + /// The note commitment is randomized over this generator. + NoteCommitmentRandomness = 1, + + /// The node commitment is randomized again by the position + /// in order to supply the nullifier computation with a + /// unique input w.r.t. the note being spent, to prevent + /// Faerie gold attacks. + NullifierPosition = 2, + + /// The value commitment is used to check balance between + /// inputs and outputs. The value is placed over this + /// generator. + ValueCommitmentValue = 3, + /// The value commitment is randomized over this generator, + /// for privacy. + ValueCommitmentRandomness = 4, + + /// The spender proves discrete log with respect to this + /// base at spend time. + SpendingKeyGenerator = 5, + + Max = 6 +} + +pub trait ToUniform { + fn to_uniform(digest: &[u8]) -> Self; +} + +/// This is an extension to the pairing Engine trait which +/// offers a scalar field for the embedded curve (Jubjub) +/// and some pre-computed parameters. +pub trait JubjubEngine: Engine { + /// The scalar field of the Jubjub curve + type Fs: PrimeField + SqrtField + ToUniform; + /// The parameters of Jubjub and the Sapling protocol + type Params: JubjubParams; +} + +/// The pre-computed parameters for Jubjub, including curve +/// constants and various limits and window tables. +pub trait JubjubParams: Sized { + /// The `d` constant of the twisted Edwards curve. + fn edwards_d(&self) -> &E::Fr; + /// The `A` constant of the birationally equivalent Montgomery curve. + fn montgomery_a(&self) -> &E::Fr; + /// The `A` constant, doubled. + fn montgomery_2a(&self) -> &E::Fr; + /// The scaling factor used for conversion from the Montgomery form. + fn scale(&self) -> &E::Fr; + /// Returns the generators (for each segment) used in all Pedersen commitments. + fn pedersen_hash_generators(&self) -> &[edwards::Point]; + /// Returns the exp table for Pedersen hashes. + fn pedersen_hash_exp_table(&self) -> &[Vec>>]; + /// Returns the maximum number of chunks per segment of the Pedersen hash. + fn pedersen_hash_chunks_per_generator(&self) -> usize; + /// Returns the pre-computed window tables [-4, 3, 2, 1, 1, 2, 3, 4] of different + /// magnitudes of the Pedersen hash segment generators. + fn pedersen_circuit_generators(&self) -> &[Vec>]; + + /// Returns the number of chunks needed to represent a full scalar during fixed-base + /// exponentiation. + fn fixed_base_chunks_per_generator(&self) -> usize; + /// Returns a fixed generator. + fn generator(&self, base: FixedGenerators) -> &edwards::Point; + /// Returns a window table [0, 1, ..., 8] for different magnitudes of some + /// fixed generator. + fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>]; + /// Returns the window size for exponentiation of Pedersen hash generators + /// outside the circuit + fn pedersen_hash_exp_window_size() -> u32; +} + +impl JubjubEngine for Bls12 { + type Fs = self::fs::Fs; + type Params = JubjubBls12; +} + +pub struct JubjubBls12 { + edwards_d: Fr, + montgomery_a: Fr, + montgomery_2a: Fr, + scale: Fr, + + pedersen_hash_generators: Vec>, + pedersen_hash_exp: Vec>>>, + pedersen_circuit_generators: Vec>>, + + fixed_base_generators: Vec>, + fixed_base_circuit_generators: Vec>>, +} + +impl JubjubParams for JubjubBls12 { + fn edwards_d(&self) -> &Fr { &self.edwards_d } + fn montgomery_a(&self) -> &Fr { &self.montgomery_a } + fn montgomery_2a(&self) -> &Fr { &self.montgomery_2a } + fn scale(&self) -> &Fr { &self.scale } + fn pedersen_hash_generators(&self) -> &[edwards::Point] { + &self.pedersen_hash_generators + } + fn pedersen_hash_exp_table(&self) -> &[Vec>>] { + &self.pedersen_hash_exp + } + fn pedersen_hash_chunks_per_generator(&self) -> usize { + 63 + } + fn fixed_base_chunks_per_generator(&self) -> usize { + 84 + } + fn pedersen_circuit_generators(&self) -> &[Vec>] { + &self.pedersen_circuit_generators + } + fn generator(&self, base: FixedGenerators) -> &edwards::Point + { + &self.fixed_base_generators[base as usize] + } + fn circuit_generators(&self, base: FixedGenerators) -> &[Vec<(Fr, Fr)>] + { + &self.fixed_base_circuit_generators[base as usize][..] + } + fn pedersen_hash_exp_window_size() -> u32 { + 8 + } +} + +impl JubjubBls12 { + pub fn new() -> Self { + let montgomery_a = Fr::from_str("40962").unwrap(); + let mut montgomery_2a = montgomery_a; + montgomery_2a.double(); + + let mut tmp_params = JubjubBls12 { + // d = -(10240/10241) + edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(), + // A = 40962 + montgomery_a: montgomery_a, + // 2A = 2.A + montgomery_2a: montgomery_2a, + // scaling factor = sqrt(4 / (a - d)) + scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(), + + // We'll initialize these below + pedersen_hash_generators: vec![], + pedersen_hash_exp: vec![], + pedersen_circuit_generators: vec![], + fixed_base_generators: vec![], + fixed_base_circuit_generators: vec![], + }; + + fn find_group_hash( + m: &[u8], + personalization: &[u8; 8], + params: &E::Params + ) -> edwards::Point + { + let mut tag = m.to_vec(); + let i = tag.len(); + tag.push(0u8); + + loop { + let gh = group_hash( + &tag, + personalization, + params + ); + + // We don't want to overflow and start reusing generators + assert!(tag[i] != u8::max_value()); + tag[i] += 1; + + if let Some(gh) = gh { + break gh; + } + } + } + + // Create the bases for the Pedersen hashes + { + let mut pedersen_hash_generators = vec![]; + + for m in 0..5 { + use byteorder::{WriteBytesExt, LittleEndian}; + + let mut segment_number = [0u8; 4]; + (&mut segment_number[0..4]).write_u32::(m).unwrap(); + + pedersen_hash_generators.push( + find_group_hash( + &segment_number, + constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, + &tmp_params + ) + ); + } + + // Check for duplicates, far worse than spec inconsistencies! + for (i, p1) in pedersen_hash_generators.iter().enumerate() { + if p1 == &edwards::Point::zero() { + panic!("Neutral element!"); + } + + for p2 in pedersen_hash_generators.iter().skip(i+1) { + if p1 == p2 { + panic!("Duplicate generator!"); + } + } + } + + tmp_params.pedersen_hash_generators = pedersen_hash_generators; + } + + // Create the exp table for the Pedersen hash generators + { + let mut pedersen_hash_exp = vec![]; + + for g in &tmp_params.pedersen_hash_generators { + let mut g = g.clone(); + + let window = JubjubBls12::pedersen_hash_exp_window_size(); + + let mut tables = vec![]; + + let mut num_bits = 0; + while num_bits <= fs::Fs::NUM_BITS { + let mut table = Vec::with_capacity(1 << window); + + let mut base = edwards::Point::zero(); + + for _ in 0..(1 << window) { + table.push(base.clone()); + base = base.add(&g, &tmp_params); + } + + tables.push(table); + num_bits += window; + + for _ in 0..window { + g = g.double(&tmp_params); + } + } + + pedersen_hash_exp.push(tables); + } + + tmp_params.pedersen_hash_exp = pedersen_hash_exp; + } + + // Create the bases for other parts of the protocol + { + let mut fixed_base_generators = vec![edwards::Point::zero(); FixedGenerators::Max as usize]; + + fixed_base_generators[FixedGenerators::ProofGenerationKey as usize] = + find_group_hash(&[], constants::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION, &tmp_params); + + fixed_base_generators[FixedGenerators::NoteCommitmentRandomness as usize] = + find_group_hash(b"r", constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp_params); + + fixed_base_generators[FixedGenerators::NullifierPosition as usize] = + find_group_hash(&[], constants::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION, &tmp_params); + + fixed_base_generators[FixedGenerators::ValueCommitmentValue as usize] = + find_group_hash(b"v", constants::VALUE_COMMITMENT_GENERATOR_PERSONALIZATION, &tmp_params); + + fixed_base_generators[FixedGenerators::ValueCommitmentRandomness as usize] = + find_group_hash(b"r", constants::VALUE_COMMITMENT_GENERATOR_PERSONALIZATION, &tmp_params); + + fixed_base_generators[FixedGenerators::SpendingKeyGenerator as usize] = + find_group_hash(&[], constants::SPENDING_KEY_GENERATOR_PERSONALIZATION, &tmp_params); + + // Check for duplicates, far worse than spec inconsistencies! + for (i, p1) in fixed_base_generators.iter().enumerate() { + if p1 == &edwards::Point::zero() { + panic!("Neutral element!"); + } + + for p2 in fixed_base_generators.iter().skip(i+1) { + if p1 == p2 { + panic!("Duplicate generator!"); + } + } + } + + tmp_params.fixed_base_generators = fixed_base_generators; + } + + // Create the 2-bit window table lookups for each 4-bit + // "chunk" in each segment of the Pedersen hash + { + let mut pedersen_circuit_generators = vec![]; + + // Process each segment + for mut gen in tmp_params.pedersen_hash_generators.iter().cloned() { + let mut gen = montgomery::Point::from_edwards(&gen, &tmp_params); + let mut windows = vec![]; + for _ in 0..tmp_params.pedersen_hash_chunks_per_generator() { + // Create (x, y) coeffs for this chunk + let mut coeffs = vec![]; + let mut g = gen.clone(); + + // coeffs = g, g*2, g*3, g*4 + for _ in 0..4 { + coeffs.push(g.into_xy().expect("cannot produce O")); + g = g.add(&gen, &tmp_params); + } + windows.push(coeffs); + + // Our chunks are separated by 2 bits to prevent overlap. + for _ in 0..4 { + gen = gen.double(&tmp_params); + } + } + pedersen_circuit_generators.push(windows); + } + + tmp_params.pedersen_circuit_generators = pedersen_circuit_generators; + } + + // Create the 3-bit window table lookups for fixed-base + // exp of each base in the protocol. + { + let mut fixed_base_circuit_generators = vec![]; + + for mut gen in tmp_params.fixed_base_generators.iter().cloned() { + let mut windows = vec![]; + for _ in 0..tmp_params.fixed_base_chunks_per_generator() { + let mut coeffs = vec![(Fr::zero(), Fr::one())]; + let mut g = gen.clone(); + for _ in 0..7 { + coeffs.push(g.into_xy()); + g = g.add(&gen, &tmp_params); + } + windows.push(coeffs); + + // gen = gen * 8 + gen = g; + } + fixed_base_circuit_generators.push(windows); + } + + tmp_params.fixed_base_circuit_generators = fixed_base_circuit_generators; + } + + tmp_params + } +} + +#[test] +fn test_jubjub_bls12() { + let params = JubjubBls12::new(); + + tests::test_suite::(¶ms); + + let test_repr = hex!("9d12b88b08dcbef8a11ee0712d94cb236ee2f4ca17317075bfafc82ce3139d31"); + let p = edwards::Point::::read(&test_repr[..], ¶ms).unwrap(); + let q = edwards::Point::::get_for_y( + Fr::from_str("22440861827555040311190986994816762244378363690614952020532787748720529117853").unwrap(), + false, + ¶ms + ).unwrap(); + + assert!(p == q); + + // Same thing, but sign bit set + let test_repr = hex!("9d12b88b08dcbef8a11ee0712d94cb236ee2f4ca17317075bfafc82ce3139db1"); + let p = edwards::Point::::read(&test_repr[..], ¶ms).unwrap(); + let q = edwards::Point::::get_for_y( + Fr::from_str("22440861827555040311190986994816762244378363690614952020532787748720529117853").unwrap(), + true, + ¶ms + ).unwrap(); + + assert!(p == q); +} diff --git a/sapling-crypto/src/jubjub/montgomery.rs b/sapling-crypto/src/jubjub/montgomery.rs new file mode 100644 index 0000000..18d0fcb --- /dev/null +++ b/sapling-crypto/src/jubjub/montgomery.rs @@ -0,0 +1,358 @@ +use pairing::{ + Field, + SqrtField, + PrimeField, + PrimeFieldRepr, + BitIterator +}; + +use super::{ + JubjubEngine, + JubjubParams, + Unknown, + PrimeOrder, + edwards +}; + +use rand::{ + Rng +}; + +use std::marker::PhantomData; + +// Represents the affine point (X, Y) +pub struct Point { + x: E::Fr, + y: E::Fr, + infinity: bool, + _marker: PhantomData +} + +fn convert_subgroup(from: &Point) -> Point +{ + Point { + x: from.x, + y: from.y, + infinity: from.infinity, + _marker: PhantomData + } +} + +impl From> for Point +{ + fn from(p: Point) -> Point + { + convert_subgroup(&p) + } +} + +impl Clone for Point +{ + fn clone(&self) -> Self { + convert_subgroup(self) + } +} + +impl PartialEq for Point { + fn eq(&self, other: &Point) -> bool { + match (self.infinity, other.infinity) { + (true, true) => true, + (true, false) | (false, true) => false, + (false, false) => { + self.x == other.x && self.y == other.y + } + } + } +} + +impl Point { + pub fn get_for_x(x: E::Fr, sign: bool, params: &E::Params) -> Option + { + // Given an x on the curve, y = sqrt(x^3 + A*x^2 + x) + + let mut x2 = x; + x2.square(); + + let mut rhs = x2; + rhs.mul_assign(params.montgomery_a()); + rhs.add_assign(&x); + x2.mul_assign(&x); + rhs.add_assign(&x2); + + match rhs.sqrt() { + Some(mut y) => { + if y.into_repr().is_odd() != sign { + y.negate(); + } + + return Some(Point { + x: x, + y: y, + infinity: false, + _marker: PhantomData + }) + }, + None => None + } + } + + /// This guarantees the point is in the prime order subgroup + #[must_use] + pub fn mul_by_cofactor(&self, params: &E::Params) -> Point + { + let tmp = self.double(params) + .double(params) + .double(params); + + convert_subgroup(&tmp) + } + + pub fn rand(rng: &mut R, params: &E::Params) -> Self + { + loop { + let x: E::Fr = rng.gen(); + + match Self::get_for_x(x, rng.gen(), params) { + Some(p) => { + return p + }, + None => {} + } + } + } +} + +impl Point { + /// Convert from an Edwards point + pub fn from_edwards( + e: &edwards::Point, + params: &E::Params + ) -> Self + { + let (x, y) = e.into_xy(); + + if y == E::Fr::one() { + // The only solution for y = 1 is x = 0. (0, 1) is + // the neutral element, so we map this to the point + // at infinity. + + Point::zero() + } else { + // The map from a twisted Edwards curve is defined as + // (x, y) -> (u, v) where + // u = (1 + y) / (1 - y) + // v = u / x + // + // This mapping is not defined for y = 1 and for x = 0. + // + // We have that y != 1 above. If x = 0, the only + // solutions for y are 1 (contradiction) or -1. + if x.is_zero() { + // (0, -1) is the point of order two which is not + // the neutral element, so we map it to (0, 0) which is + // the only affine point of order 2. + + Point { + x: E::Fr::zero(), + y: E::Fr::zero(), + infinity: false, + _marker: PhantomData + } + } else { + // The mapping is defined as above. + // + // (x, y) -> (u, v) where + // u = (1 + y) / (1 - y) + // v = u / x + + let mut u = E::Fr::one(); + u.add_assign(&y); + { + let mut tmp = E::Fr::one(); + tmp.sub_assign(&y); + u.mul_assign(&tmp.inverse().unwrap()) + } + + let mut v = u; + v.mul_assign(&x.inverse().unwrap()); + + // Scale it into the correct curve constants + v.mul_assign(params.scale()); + + Point { + x: u, + y: v, + infinity: false, + _marker: PhantomData + } + } + } + } + + /// Attempts to cast this as a prime order element, failing if it's + /// not in the prime order subgroup. + pub fn as_prime_order(&self, params: &E::Params) -> Option> { + if self.mul(E::Fs::char(), params) == Point::zero() { + Some(convert_subgroup(self)) + } else { + None + } + } + + pub fn zero() -> Self { + Point { + x: E::Fr::zero(), + y: E::Fr::zero(), + infinity: true, + _marker: PhantomData + } + } + + pub fn into_xy(&self) -> Option<(E::Fr, E::Fr)> + { + if self.infinity { + None + } else { + Some((self.x, self.y)) + } + } + + #[must_use] + pub fn negate(&self) -> Self { + let mut p = self.clone(); + + p.y.negate(); + + p + } + + #[must_use] + pub fn double(&self, params: &E::Params) -> Self { + if self.infinity { + return Point::zero(); + } + + // (0, 0) is the point of order 2. Doubling + // produces the point at infinity. + if self.y == E::Fr::zero() { + return Point::zero(); + } + + // This is a standard affine point doubling formula + // See 4.3.2 The group law for Weierstrass curves + // Montgomery curves and the Montgomery Ladder + // Daniel J. Bernstein and Tanja Lange + + let mut delta = E::Fr::one(); + { + let mut tmp = params.montgomery_a().clone(); + tmp.mul_assign(&self.x); + tmp.double(); + delta.add_assign(&tmp); + } + { + let mut tmp = self.x; + tmp.square(); + delta.add_assign(&tmp); + tmp.double(); + delta.add_assign(&tmp); + } + { + let mut tmp = self.y; + tmp.double(); + delta.mul_assign(&tmp.inverse().expect("y is nonzero so this must be nonzero")); + } + + let mut x3 = delta; + x3.square(); + x3.sub_assign(params.montgomery_a()); + x3.sub_assign(&self.x); + x3.sub_assign(&self.x); + + let mut y3 = x3; + y3.sub_assign(&self.x); + y3.mul_assign(&delta); + y3.add_assign(&self.y); + y3.negate(); + + Point { + x: x3, + y: y3, + infinity: false, + _marker: PhantomData + } + } + + #[must_use] + pub fn add(&self, other: &Self, params: &E::Params) -> Self + { + // This is a standard affine point addition formula + // See 4.3.2 The group law for Weierstrass curves + // Montgomery curves and the Montgomery Ladder + // Daniel J. Bernstein and Tanja Lange + + match (self.infinity, other.infinity) { + (true, true) => Point::zero(), + (true, false) => other.clone(), + (false, true) => self.clone(), + (false, false) => { + if self.x == other.x { + if self.y == other.y { + self.double(params) + } else { + Point::zero() + } + } else { + let mut delta = other.y; + delta.sub_assign(&self.y); + { + let mut tmp = other.x; + tmp.sub_assign(&self.x); + delta.mul_assign(&tmp.inverse().expect("self.x != other.x, so this must be nonzero")); + } + + let mut x3 = delta; + x3.square(); + x3.sub_assign(params.montgomery_a()); + x3.sub_assign(&self.x); + x3.sub_assign(&other.x); + + let mut y3 = x3; + y3.sub_assign(&self.x); + y3.mul_assign(&delta); + y3.add_assign(&self.y); + y3.negate(); + + Point { + x: x3, + y: y3, + infinity: false, + _marker: PhantomData + } + } + } + } + } + + #[must_use] + pub fn mul::Repr>>( + &self, + scalar: S, + params: &E::Params + ) -> Self + { + // Standard double-and-add scalar multiplication + + let mut res = Self::zero(); + + for b in BitIterator::new(scalar.into()) { + res = res.double(params); + + if b { + res = res.add(self, params); + } + } + + res + } +} diff --git a/sapling-crypto/src/jubjub/tests.rs b/sapling-crypto/src/jubjub/tests.rs new file mode 100644 index 0000000..421a8f7 --- /dev/null +++ b/sapling-crypto/src/jubjub/tests.rs @@ -0,0 +1,416 @@ +use super::{ + JubjubEngine, + JubjubParams, + PrimeOrder, + montgomery, + edwards +}; + +use pairing::{ + Field, + PrimeField, + PrimeFieldRepr, + SqrtField, + LegendreSymbol +}; + +use rand::{XorShiftRng, SeedableRng, Rand}; + +pub fn test_suite(params: &E::Params) { + test_back_and_forth::(params); + test_jubjub_params::(params); + test_rand::(params); + test_get_for::(params); + test_identities::(params); + test_addition_associativity::(params); + test_order::(params); + test_mul_associativity::(params); + test_loworder::(params); + test_read_write::(params); +} + +fn is_on_mont_curve>( + x: E::Fr, + y: E::Fr, + params: &P +) -> bool +{ + let mut lhs = y; + lhs.square(); + + let mut x2 = x; + x2.square(); + + let mut x3 = x2; + x3.mul_assign(&x); + + let mut rhs = x2; + rhs.mul_assign(params.montgomery_a()); + rhs.add_assign(&x); + rhs.add_assign(&x3); + + lhs == rhs +} + +fn is_on_twisted_edwards_curve>( + x: E::Fr, + y: E::Fr, + params: &P +) -> bool +{ + let mut x2 = x; + x2.square(); + + let mut y2 = y; + y2.square(); + + // -x^2 + y^2 + let mut lhs = y2; + lhs.sub_assign(&x2); + + // 1 + d x^2 y^2 + let mut rhs = y2; + rhs.mul_assign(&x2); + rhs.mul_assign(params.edwards_d()); + rhs.add_assign(&E::Fr::one()); + + lhs == rhs +} + +fn test_loworder(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let inf = montgomery::Point::zero(); + + // try to find a point of order 8 + let p = loop { + let r = montgomery::Point::::rand(rng, params).mul(E::Fs::char(), params); + + let r2 = r.double(params); + let r4 = r2.double(params); + let r8 = r4.double(params); + + if r2 != inf && r4 != inf && r8 == inf { + break r; + } + }; + + let mut loworder_points = vec![]; + { + let mut tmp = p.clone(); + + for _ in 0..8 { + assert!(!loworder_points.contains(&tmp)); + loworder_points.push(tmp.clone()); + tmp = tmp.add(&p, params); + } + } + assert!(loworder_points[7] == inf); +} + +fn test_mul_associativity(params: &E::Params) { + use self::edwards::Point; + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + // Pick a random point and multiply it by the cofactor + let base = Point::::rand(rng, params).mul_by_cofactor(params); + + let mut a = E::Fs::rand(rng); + let b = E::Fs::rand(rng); + let c = E::Fs::rand(rng); + + let res1 = base.mul(a, params).mul(b, params).mul(c, params); + let res2 = base.mul(b, params).mul(c, params).mul(a, params); + let res3 = base.mul(c, params).mul(a, params).mul(b, params); + a.mul_assign(&b); + a.mul_assign(&c); + let res4 = base.mul(a, params); + + assert!(res1 == res2); + assert!(res2 == res3); + assert!(res3 == res4); + + let (x, y) = res1.into_xy(); + assert!(is_on_twisted_edwards_curve(x, y, params)); + + let (x, y) = res2.into_xy(); + assert!(is_on_twisted_edwards_curve(x, y, params)); + + let (x, y) = res3.into_xy(); + assert!(is_on_twisted_edwards_curve(x, y, params)); + } +} + +fn test_order(params: &E::Params) { + use self::edwards::Point; + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + // The neutral element is in the prime order subgroup. + assert!(Point::::zero().as_prime_order(params).is_some()); + + for _ in 0..50 { + // Pick a random point and multiply it by the cofactor + let base = Point::::rand(rng, params).mul_by_cofactor(params); + + // Any point multiplied by the cofactor will be in the prime + // order subgroup + assert!(base.as_prime_order(params).is_some()); + } + + // It's very likely that at least one out of 50 random points on the curve + // is not in the prime order subgroup. + let mut at_least_one_not_in_prime_order_subgroup = false; + for _ in 0..50 { + // Pick a random point. + let base = Point::::rand(rng, params); + + at_least_one_not_in_prime_order_subgroup |= base.as_prime_order(params).is_none(); + } + assert!(at_least_one_not_in_prime_order_subgroup); +} + +fn test_addition_associativity(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + use self::montgomery::Point; + + let a = Point::::rand(rng, params); + let b = Point::::rand(rng, params); + let c = Point::::rand(rng, params); + + assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms)); + } + + for _ in 0..1000 { + use self::edwards::Point; + + let a = Point::::rand(rng, params); + let b = Point::::rand(rng, params); + let c = Point::::rand(rng, params); + + assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms)); + } +} + +fn test_identities(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + { + use self::edwards::Point; + + let z = Point::::zero(); + assert!(z.double(¶ms) == z); + assert!(z.negate() == z); + + for _ in 0..100 { + let r = Point::::rand(rng, params); + + assert!(r.add(&Point::zero(), ¶ms) == r); + assert!(r.add(&r.negate(), ¶ms) == Point::zero()); + } + } + + { + use self::montgomery::Point; + + let z = Point::::zero(); + assert!(z.double(¶ms) == z); + assert!(z.negate() == z); + + for _ in 0..100 { + let r = Point::::rand(rng, params); + + assert!(r.add(&Point::zero(), ¶ms) == r); + assert!(r.add(&r.negate(), ¶ms) == Point::zero()); + } + } +} + +fn test_get_for(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let y = E::Fr::rand(rng); + let sign = bool::rand(rng); + + if let Some(mut p) = edwards::Point::::get_for_y(y, sign, params) { + assert!(p.into_xy().0.into_repr().is_odd() == sign); + p = p.negate(); + assert!( + edwards::Point::::get_for_y(y, !sign, params).unwrap() + == + p + ); + } + } +} + +fn test_read_write(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let e = edwards::Point::::rand(rng, params); + + let mut v = vec![]; + e.write(&mut v).unwrap(); + + let e2 = edwards::Point::read(&v[..], params).unwrap(); + + assert!(e == e2); + } +} + +fn test_rand(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let p = montgomery::Point::::rand(rng, params); + let e = edwards::Point::::rand(rng, params); + + { + let (x, y) = p.into_xy().unwrap(); + assert!(is_on_mont_curve(x, y, params)); + } + + { + let (x, y) = e.into_xy(); + assert!(is_on_twisted_edwards_curve(x, y, params)); + } + } +} + +fn test_back_and_forth(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let s = E::Fs::rand(rng); + let edwards_p1 = edwards::Point::::rand(rng, params); + let mont_p1 = montgomery::Point::from_edwards(&edwards_p1, params); + let mont_p2 = montgomery::Point::::rand(rng, params); + let edwards_p2 = edwards::Point::from_montgomery(&mont_p2, params); + + let mont = mont_p1.add(&mont_p2, params).mul(s, params); + let edwards = edwards_p1.add(&edwards_p2, params).mul(s, params); + + assert!( + montgomery::Point::from_edwards(&edwards, params) == mont + ); + + assert!( + edwards::Point::from_montgomery(&mont, params) == edwards + ); + } +} + +fn test_jubjub_params(params: &E::Params) { + // a = -1 + let mut a = E::Fr::one(); + a.negate(); + + { + // Check that 2A is consistent with A + let mut tmp = *params.montgomery_a(); + tmp.double(); + + assert_eq!(&tmp, params.montgomery_2a()); + } + + { + // The twisted Edwards addition law is complete when d is nonsquare + // and a is square. + + assert!(params.edwards_d().legendre() == LegendreSymbol::QuadraticNonResidue); + assert!(a.legendre() == LegendreSymbol::QuadraticResidue); + } + + { + // Other convenient sanity checks regarding d + + // tmp = d + let mut tmp = *params.edwards_d(); + + // 1 / d is nonsquare + assert!(tmp.inverse().unwrap().legendre() == LegendreSymbol::QuadraticNonResidue); + + // tmp = -d + tmp.negate(); + + // -d is nonsquare + assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + + // 1 / -d is nonsquare + assert!(tmp.inverse().unwrap().legendre() == LegendreSymbol::QuadraticNonResidue); + } + + { + // Check that A^2 - 4 is nonsquare: + let mut tmp = params.montgomery_a().clone(); + tmp.square(); + tmp.sub_assign(&E::Fr::from_str("4").unwrap()); + assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + } + + { + // Check that A - 2 is nonsquare: + let mut tmp = params.montgomery_a().clone(); + tmp.sub_assign(&E::Fr::from_str("2").unwrap()); + assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + } + + { + // Check the validity of the scaling factor + let mut tmp = a; + tmp.sub_assign(¶ms.edwards_d()); + tmp = tmp.inverse().unwrap(); + tmp.mul_assign(&E::Fr::from_str("4").unwrap()); + tmp = tmp.sqrt().unwrap(); + assert_eq!(&tmp, params.scale()); + } + + { + // Check that the number of windows per generator + // in the Pedersen hash does not allow for collisions + + let mut cur = E::Fs::one().into_repr(); + + let mut max = E::Fs::char(); + { + max.sub_noborrow(&E::Fs::one().into_repr()); + max.div2(); + } + + let mut pacc = E::Fs::zero().into_repr(); + let mut nacc = E::Fs::char(); + + for _ in 0..params.pedersen_hash_chunks_per_generator() + { + // tmp = cur * 4 + let mut tmp = cur; + tmp.mul2(); + tmp.mul2(); + + pacc.add_nocarry(&tmp); + nacc.sub_noborrow(&tmp); + + assert!(pacc < max); + assert!(pacc < nacc); + + // cur = cur * 16 + for _ in 0..4 { + cur.mul2(); + } + } + } + + { + // Check that the number of windows for fixed-base + // scalar multiplication is sufficient for all scalars. + + assert!(params.fixed_base_chunks_per_generator() * 3 >= E::Fs::NUM_BITS as usize); + + // ... and that it's *just* efficient enough. + + assert!((params.fixed_base_chunks_per_generator() - 1) * 3 < E::Fs::NUM_BITS as usize); + } +} diff --git a/sapling-crypto/src/lib.rs b/sapling-crypto/src/lib.rs new file mode 100644 index 0000000..27d306c --- /dev/null +++ b/sapling-crypto/src/lib.rs @@ -0,0 +1,22 @@ +extern crate pairing; +extern crate bellman; +extern crate blake2_rfc; +extern crate digest; +extern crate rand; +extern crate byteorder; + +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + +#[cfg(test)] +extern crate crypto; + +pub mod jubjub; +pub mod group_hash; +pub mod circuit; +pub mod pedersen_hash; +pub mod primitives; +pub mod constants; +pub mod redjubjub; +pub mod util; diff --git a/sapling-crypto/src/pedersen_hash.rs b/sapling-crypto/src/pedersen_hash.rs new file mode 100644 index 0000000..0590a5c --- /dev/null +++ b/sapling-crypto/src/pedersen_hash.rs @@ -0,0 +1,103 @@ +use jubjub::*; +use pairing::*; + +#[derive(Copy, Clone)] +pub enum Personalization { + NoteCommitment, + MerkleTree(usize) +} + +impl Personalization { + pub fn get_bits(&self) -> Vec { + match *self { + Personalization::NoteCommitment => + vec![true, true, true, true, true, true], + Personalization::MerkleTree(num) => { + assert!(num < 63); + + (0..6).map(|i| (num >> i) & 1 == 1).collect() + } + } + } +} + +pub fn pedersen_hash( + personalization: Personalization, + bits: I, + params: &E::Params +) -> edwards::Point + where I: IntoIterator, + E: JubjubEngine +{ + let mut bits = personalization.get_bits().into_iter().chain(bits.into_iter()); + + let mut result = edwards::Point::zero(); + let mut generators = params.pedersen_hash_exp_table().iter(); + + loop { + let mut acc = E::Fs::zero(); + let mut cur = E::Fs::one(); + let mut chunks_remaining = params.pedersen_hash_chunks_per_generator(); + let mut encountered_bits = false; + + // Grab three bits from the input + while let Some(a) = bits.next() { + encountered_bits = true; + + let b = bits.next().unwrap_or(false); + let c = bits.next().unwrap_or(false); + + // Start computing this portion of the scalar + let mut tmp = cur; + if a { + tmp.add_assign(&cur); + } + cur.double(); // 2^1 * cur + if b { + tmp.add_assign(&cur); + } + + // conditionally negate + if c { + tmp.negate(); + } + + acc.add_assign(&tmp); + + chunks_remaining -= 1; + + if chunks_remaining == 0 { + break; + } else { + cur.double(); // 2^2 * cur + cur.double(); // 2^3 * cur + cur.double(); // 2^4 * cur + } + } + + if !encountered_bits { + break; + } + + let mut table: &[Vec>] = &generators.next().expect("we don't have enough generators"); + let window = JubjubBls12::pedersen_hash_exp_window_size(); + let window_mask = (1 << window) - 1; + + let mut acc = acc.into_repr(); + + let mut tmp = edwards::Point::zero(); + + while !acc.is_zero() { + let i = (acc.as_ref()[0] & window_mask) as usize; + + tmp = tmp.add(&table[0][i], params); + + acc.shr(window); + table = &table[1..]; + } + + result = result.add(&tmp, params); + } + + result +} diff --git a/sapling-crypto/src/primitives/mod.rs b/sapling-crypto/src/primitives/mod.rs new file mode 100644 index 0000000..26dafab --- /dev/null +++ b/sapling-crypto/src/primitives/mod.rs @@ -0,0 +1,258 @@ +use pairing::{ + Field, + PrimeField, + PrimeFieldRepr +}; + +use constants; + +use group_hash::group_hash; + +use pedersen_hash::{ + pedersen_hash, + Personalization +}; + +use byteorder::{ + LittleEndian, + WriteBytesExt +}; + +use jubjub::{ + JubjubEngine, + JubjubParams, + edwards, + PrimeOrder, + FixedGenerators +}; + +use blake2_rfc::blake2s::Blake2s; + +#[derive(Clone)] +pub struct ValueCommitment { + pub value: u64, + pub randomness: E::Fs +} + +impl ValueCommitment { + pub fn cm( + &self, + params: &E::Params + ) -> edwards::Point + { + params.generator(FixedGenerators::ValueCommitmentValue) + .mul(self.value, params) + .add( + ¶ms.generator(FixedGenerators::ValueCommitmentRandomness) + .mul(self.randomness, params), + params + ) + } +} + +#[derive(Clone)] +pub struct ProofGenerationKey { + pub ak: edwards::Point, + pub nsk: E::Fs +} + +impl ProofGenerationKey { + pub fn into_viewing_key(&self, params: &E::Params) -> ViewingKey { + ViewingKey { + ak: self.ak.clone(), + nk: params.generator(FixedGenerators::ProofGenerationKey) + .mul(self.nsk, params) + } + } +} + +pub struct ViewingKey { + pub ak: edwards::Point, + pub nk: edwards::Point +} + +impl ViewingKey { + pub fn rk( + &self, + ar: E::Fs, + params: &E::Params + ) -> edwards::Point { + self.ak.add( + ¶ms.generator(FixedGenerators::SpendingKeyGenerator) + .mul(ar, params), + params + ) + } + + pub fn ivk(&self) -> E::Fs { + let mut preimage = [0; 64]; + + self.ak.write(&mut preimage[0..32]).unwrap(); + self.nk.write(&mut preimage[32..64]).unwrap(); + + let mut h = Blake2s::with_params(32, &[], &[], constants::CRH_IVK_PERSONALIZATION); + h.update(&preimage); + let mut h = h.finalize().as_ref().to_vec(); + + // Drop the most significant five bits, so it can be interpreted as a scalar. + h[31] &= 0b0000_0111; + + let mut e = ::Repr::default(); + e.read_le(&h[..]).unwrap(); + + E::Fs::from_repr(e).expect("should be a valid scalar") + } + + pub fn into_payment_address( + &self, + diversifier: Diversifier, + params: &E::Params + ) -> Option> + { + diversifier.g_d(params).map(|g_d| { + let pk_d = g_d.mul(self.ivk(), params); + + PaymentAddress { + pk_d: pk_d, + diversifier: diversifier + } + }) + } +} + +#[derive(Copy, Clone)] +pub struct Diversifier(pub [u8; 11]); + +impl Diversifier { + pub fn g_d( + &self, + params: &E::Params + ) -> Option> + { + group_hash::(&self.0, constants::KEY_DIVERSIFICATION_PERSONALIZATION, params) + } +} + +#[derive(Clone)] +pub struct PaymentAddress { + pub pk_d: edwards::Point, + pub diversifier: Diversifier +} + +impl PaymentAddress { + pub fn g_d( + &self, + params: &E::Params + ) -> Option> + { + self.diversifier.g_d(params) + } + + pub fn create_note( + &self, + value: u64, + randomness: E::Fs, + params: &E::Params + ) -> Option> + { + self.g_d(params).map(|g_d| { + Note { + value: value, + r: randomness, + g_d: g_d, + pk_d: self.pk_d.clone() + } + }) + } +} + +pub struct Note { + /// The value of the note + pub value: u64, + /// The diversified base of the address, GH(d) + pub g_d: edwards::Point, + /// The public key of the address, g_d^ivk + pub pk_d: edwards::Point, + /// The commitment randomness + pub r: E::Fs +} + +impl Note { + pub fn uncommitted() -> E::Fr { + // The smallest u-coordinate that is not on the curve + // is one. + // TODO: This should be relocated to JubjubEngine as + // it's specific to the curve we're using, not all + // twisted edwards curves. + E::Fr::one() + } + + /// Computes the note commitment, returning the full point. + fn cm_full_point(&self, params: &E::Params) -> edwards::Point + { + // Calculate the note contents, as bytes + let mut note_contents = vec![]; + + // Writing the value in little endian + (&mut note_contents).write_u64::(self.value).unwrap(); + + // Write g_d + self.g_d.write(&mut note_contents).unwrap(); + + // Write pk_d + self.pk_d.write(&mut note_contents).unwrap(); + + assert_eq!(note_contents.len(), 32 + 32 + 8); + + // Compute the Pedersen hash of the note contents + let hash_of_contents = pedersen_hash( + Personalization::NoteCommitment, + note_contents.into_iter() + .flat_map(|byte| { + (0..8).map(move |i| ((byte >> i) & 1) == 1) + }), + params + ); + + // Compute final commitment + params.generator(FixedGenerators::NoteCommitmentRandomness) + .mul(self.r, params) + .add(&hash_of_contents, params) + } + + /// Computes the nullifier given the viewing key and + /// note position + pub fn nf( + &self, + viewing_key: &ViewingKey, + position: u64, + params: &E::Params + ) -> Vec + { + // Compute rho = cm + position.G + let rho = self + .cm_full_point(params) + .add( + ¶ms.generator(FixedGenerators::NullifierPosition) + .mul(position, params), + params + ); + + // Compute nf = BLAKE2s(nk | rho) + let mut nf_preimage = [0u8; 64]; + viewing_key.nk.write(&mut nf_preimage[0..32]).unwrap(); + rho.write(&mut nf_preimage[32..64]).unwrap(); + let mut h = Blake2s::with_params(32, &[], &[], constants::PRF_NF_PERSONALIZATION); + h.update(&nf_preimage); + + h.finalize().as_ref().to_vec() + } + + /// Computes the note commitment + pub fn cm(&self, params: &E::Params) -> E::Fr + { + // The commitment is in the prime order subgroup, so mapping the + // commitment to the x-coordinate is an injective encoding. + self.cm_full_point(params).into_xy().0 + } +} diff --git a/sapling-crypto/src/redjubjub.rs b/sapling-crypto/src/redjubjub.rs new file mode 100644 index 0000000..dfae28c --- /dev/null +++ b/sapling-crypto/src/redjubjub.rs @@ -0,0 +1,343 @@ +//! Implementation of RedJubjub, a specialization of RedDSA to the Jubjub curve. +//! See section 5.4.6 of the Sapling protocol specification. + +use pairing::{Field, PrimeField, PrimeFieldRepr}; +use rand::{Rng, Rand}; +use std::io::{self, Read, Write}; + +use jubjub::{FixedGenerators, JubjubEngine, JubjubParams, Unknown, edwards::Point}; +use util::{hash_to_scalar}; + +fn read_scalar(reader: R) -> io::Result { + let mut s_repr = ::Repr::default(); + s_repr.read_le(reader)?; + + match E::Fs::from_repr(s_repr) { + Ok(s) => Ok(s), + Err(_) => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "scalar is not in field", + )), + } +} + +fn write_scalar(s: &E::Fs, writer: W) -> io::Result<()> { + s.into_repr().write_le(writer) +} + +fn h_star(a: &[u8], b: &[u8]) -> E::Fs { + hash_to_scalar::(b"Zcash_RedJubjubH", a, b) +} + +#[derive(Copy, Clone)] +pub struct Signature { + rbar: [u8; 32], + sbar: [u8; 32], +} + +pub struct PrivateKey(pub E::Fs); + +pub struct PublicKey(pub Point); + +impl Signature { + pub fn read(mut reader: R) -> io::Result { + let mut rbar = [0u8; 32]; + let mut sbar = [0u8; 32]; + reader.read_exact(&mut rbar)?; + reader.read_exact(&mut sbar)?; + Ok(Signature { rbar, sbar }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.rbar)?; + writer.write_all(&self.sbar) + } +} + +impl PrivateKey { + pub fn randomize(&self, alpha: E::Fs) -> Self { + let mut tmp = self.0; + tmp.add_assign(&alpha); + PrivateKey(tmp) + } + + pub fn read(reader: R) -> io::Result { + let pk = read_scalar::(reader)?; + Ok(PrivateKey(pk)) + } + + pub fn write(&self, writer: W) -> io::Result<()> { + write_scalar::(&self.0, writer) + } + + pub fn sign( + &self, + msg: &[u8], + rng: &mut R, + p_g: FixedGenerators, + params: &E::Params, + ) -> Signature { + // T = (l_H + 128) bits of randomness + // For H*, l_H = 512 bits + let mut t = [0u8; 80]; + rng.fill_bytes(&mut t[..]); + + // r = H*(T || M) + let r = h_star::(&t[..], msg); + + // R = r . P_G + let r_g = params.generator(p_g).mul(r, params); + let mut rbar = [0u8; 32]; + r_g.write(&mut rbar[..]) + .expect("Jubjub points should serialize to 32 bytes"); + + // S = r + H*(Rbar || M) . sk + let mut s = h_star::(&rbar[..], msg); + s.mul_assign(&self.0); + s.add_assign(&r); + let mut sbar = [0u8; 32]; + write_scalar::(&s, &mut sbar[..]) + .expect("Jubjub scalars should serialize to 32 bytes"); + + Signature { rbar, sbar } + } +} + +impl PublicKey { + pub fn from_private(privkey: &PrivateKey, p_g: FixedGenerators, params: &E::Params) -> Self { + let res = params.generator(p_g).mul(privkey.0, params).into(); + PublicKey(res) + } + + pub fn randomize(&self, alpha: E::Fs, p_g: FixedGenerators, params: &E::Params) -> Self { + let res: Point = params.generator(p_g).mul(alpha, params).into(); + let res = res.add(&self.0, params); + PublicKey(res) + } + + pub fn read(reader: R, params: &E::Params) -> io::Result { + let p = Point::read(reader, params)?; + Ok(PublicKey(p)) + } + + pub fn write(&self, writer: W) -> io::Result<()> { + self.0.write(writer) + } + + pub fn verify( + &self, + msg: &[u8], + sig: &Signature, + p_g: FixedGenerators, + params: &E::Params, + ) -> bool { + // c = H*(Rbar || M) + let c = h_star::(&sig.rbar[..], msg); + + // Signature checks: + // R != invalid + let r = match Point::read(&sig.rbar[..], params) { + Ok(r) => r, + Err(_) => return false, + }; + // S < order(G) + // (E::Fs guarantees its representation is in the field) + let s = match read_scalar::(&sig.sbar[..]) { + Ok(s) => s, + Err(_) => return false, + }; + // 0 = h_G(-S . P_G + R + c . vk) + self.0.mul(c, params).add(&r, params).add( + ¶ms.generator(p_g).mul(s, params).negate().into(), + params + ).mul_by_cofactor(params).eq(&Point::zero()) + } +} + +pub struct BatchEntry<'a, E: JubjubEngine> { + vk: PublicKey, + msg: &'a [u8], + sig: Signature, +} + +// TODO: #82: This is a naive implementation currently, +// and doesn't use multiexp. +pub fn batch_verify<'a, E: JubjubEngine, R: Rng>( + rng: &mut R, + batch: &[BatchEntry<'a, E>], + p_g: FixedGenerators, + params: &E::Params, +) -> bool +{ + let mut acc = Point::::zero(); + + for entry in batch { + let mut r = match Point::::read(&entry.sig.rbar[..], params) { + Ok(r) => r, + Err(_) => return false, + }; + let mut s = match read_scalar::(&entry.sig.sbar[..]) { + Ok(s) => s, + Err(_) => return false, + }; + + let mut c = h_star::(&entry.sig.rbar[..], entry.msg); + + let z = E::Fs::rand(rng); + + s.mul_assign(&z); + s.negate(); + + r = r.mul(z, params); + + c.mul_assign(&z); + + acc = acc.add(&r, params); + acc = acc.add(&entry.vk.0.mul(c, params), params); + acc = acc.add(¶ms.generator(p_g).mul(s, params).into(), params); + } + + acc = acc.mul_by_cofactor(params).into(); + + acc.eq(&Point::zero()) +} + +#[cfg(test)] +mod tests { + use pairing::bls12_381::Bls12; + use rand::thread_rng; + + use jubjub::{JubjubBls12, fs::Fs, edwards}; + + use super::*; + + #[test] + fn test_batch_verify() { + let rng = &mut thread_rng(); + let params = &JubjubBls12::new(); + let p_g = FixedGenerators::SpendingKeyGenerator; + + let sk1 = PrivateKey::(rng.gen()); + let vk1 = PublicKey::from_private(&sk1, p_g, params); + let msg1 = b"Foo bar"; + let sig1 = sk1.sign(msg1, rng, p_g, params); + assert!(vk1.verify(msg1, &sig1, p_g, params)); + + let sk2 = PrivateKey::(rng.gen()); + let vk2 = PublicKey::from_private(&sk2, p_g, params); + let msg2 = b"Foo bar"; + let sig2 = sk2.sign(msg2, rng, p_g, params); + assert!(vk2.verify(msg2, &sig2, p_g, params)); + + let mut batch = vec![ + BatchEntry { vk: vk1, msg: msg1, sig: sig1 }, + BatchEntry { vk: vk2, msg: msg2, sig: sig2 } + ]; + + assert!(batch_verify(rng, &batch, p_g, params)); + + batch[0].sig = sig2; + + assert!(!batch_verify(rng, &batch, p_g, params)); + } + + #[test] + fn cofactor_check() { + let rng = &mut thread_rng(); + let params = &JubjubBls12::new(); + let zero = edwards::Point::zero(); + let p_g = FixedGenerators::SpendingKeyGenerator; + + // Get a point of order 8 + let p8 = loop { + let r = edwards::Point::::rand(rng, params).mul(Fs::char(), params); + + let r2 = r.double(params); + let r4 = r2.double(params); + let r8 = r4.double(params); + + if r2 != zero && r4 != zero && r8 == zero { + break r; + } + }; + + let sk = PrivateKey::(rng.gen()); + let vk = PublicKey::from_private(&sk, p_g, params); + + // TODO: This test will need to change when #77 is fixed + let msg = b"Foo bar"; + let sig = sk.sign(msg, rng, p_g, params); + assert!(vk.verify(msg, &sig, p_g, params)); + + let vktorsion = PublicKey(vk.0.add(&p8, params)); + assert!(vktorsion.verify(msg, &sig, p_g, params)); + } + + #[test] + fn round_trip_serialization() { + let rng = &mut thread_rng(); + let p_g = FixedGenerators::SpendingKeyGenerator; + let params = &JubjubBls12::new(); + + for _ in 0..1000 { + let sk = PrivateKey::(rng.gen()); + let vk = PublicKey::from_private(&sk, p_g, params); + let msg = b"Foo bar"; + let sig = sk.sign(msg, rng, p_g, params); + + let mut sk_bytes = [0u8; 32]; + let mut vk_bytes = [0u8; 32]; + let mut sig_bytes = [0u8; 64]; + sk.write(&mut sk_bytes[..]).unwrap(); + vk.write(&mut vk_bytes[..]).unwrap(); + sig.write(&mut sig_bytes[..]).unwrap(); + + let sk_2 = PrivateKey::::read(&sk_bytes[..]).unwrap(); + let vk_2 = PublicKey::from_private(&sk_2, p_g, params); + let mut vk_2_bytes = [0u8; 32]; + vk_2.write(&mut vk_2_bytes[..]).unwrap(); + assert!(vk_bytes == vk_2_bytes); + + let vk_2 = PublicKey::::read(&vk_bytes[..], params).unwrap(); + let sig_2 = Signature::read(&sig_bytes[..]).unwrap(); + assert!(vk.verify(msg, &sig_2, p_g, params)); + assert!(vk_2.verify(msg, &sig, p_g, params)); + assert!(vk_2.verify(msg, &sig_2, p_g, params)); + } + } + + #[test] + fn random_signatures() { + let rng = &mut thread_rng(); + let p_g = FixedGenerators::SpendingKeyGenerator; + let params = &JubjubBls12::new(); + + for _ in 0..1000 { + let sk = PrivateKey::(rng.gen()); + let vk = PublicKey::from_private(&sk, p_g, params); + + let msg1 = b"Foo bar"; + let msg2 = b"Spam eggs"; + + let sig1 = sk.sign(msg1, rng, p_g, params); + let sig2 = sk.sign(msg2, rng, p_g, params); + + assert!(vk.verify(msg1, &sig1, p_g, params)); + assert!(vk.verify(msg2, &sig2, p_g, params)); + assert!(!vk.verify(msg1, &sig2, p_g, params)); + assert!(!vk.verify(msg2, &sig1, p_g, params)); + + let alpha = rng.gen(); + let rsk = sk.randomize(alpha); + let rvk = vk.randomize(alpha, p_g, params); + + let sig1 = rsk.sign(msg1, rng, p_g, params); + let sig2 = rsk.sign(msg2, rng, p_g, params); + + assert!(rvk.verify(msg1, &sig1, p_g, params)); + assert!(rvk.verify(msg2, &sig2, p_g, params)); + assert!(!rvk.verify(msg1, &sig2, p_g, params)); + assert!(!rvk.verify(msg2, &sig1, p_g, params)); + } + } +} diff --git a/sapling-crypto/src/util.rs b/sapling-crypto/src/util.rs new file mode 100644 index 0000000..e67e660 --- /dev/null +++ b/sapling-crypto/src/util.rs @@ -0,0 +1,11 @@ +use blake2_rfc::blake2b::Blake2b; + +use jubjub::{JubjubEngine, ToUniform}; + +pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> E::Fs { + let mut hasher = Blake2b::with_params(64, &[], &[], persona); + hasher.update(a); + hasher.update(b); + let ret = hasher.finalize(); + E::Fs::to_uniform(ret.as_ref()) +}