From 35314c8771bd2f70756d8e405af091e1645a4409 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 13 Nov 2017 01:54:13 -0700 Subject: [PATCH 001/168] Initial commit --- .gitignore | 3 + COPYRIGHT | 14 ++++ Cargo.toml | 16 ++++ LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 23 ++++++ src/lib.rs | 3 + 6 files changed, 260 insertions(+) create mode 100644 .gitignore create mode 100644 COPYRIGHT create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6aa1064 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..0df5950 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,14 @@ +Copyrights in the "sapling" library are retained by their contributors. No +copyright assignment is required to contribute to the "sapling" library. + +The "sapling" 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/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..91643ba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +authors = ["Sean Bowe "] +description = "Cryptographic library for Zcash Sapling" +documentation = "https://github.com/zcash/sapling" +homepage = "https://github.com/zcash/sapling" +license = "MIT/Apache-2.0" +name = "sapling" +repository = "https://github.com/zcash/sapling" +version = "0.0.1" + +[dependencies] +bellman = "0.0.5" + +[dependencies.pairing] +version = "0.13" +features = ["u128-support"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/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/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/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/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e4c902b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +extern crate pairing; +extern crate bellman; + From 86619c733444a6cf4a2d16b1b019c44090cf5849 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 22 Nov 2017 21:57:00 -0700 Subject: [PATCH 002/168] Implementation of fundamental circuitry and primitive Jubjub curve arithmetic. --- Cargo.toml | 17 +- src/circuit/blake2s.rs | 374 ++++++++++++ src/circuit/boolean.rs | 476 +++++++++++++++ src/circuit/mod.rs | 21 + src/circuit/test/mod.rs | 253 ++++++++ src/circuit/uint32.rs | 451 ++++++++++++++ src/jubjub/edwards.rs | 547 +++++++++++++++++ src/jubjub/fs.rs | 1240 ++++++++++++++++++++++++++++++++++++++ src/jubjub/mod.rs | 100 +++ src/jubjub/montgomery.rs | 608 +++++++++++++++++++ src/lib.rs | 6 + 11 files changed, 4088 insertions(+), 5 deletions(-) create mode 100644 src/circuit/blake2s.rs create mode 100644 src/circuit/boolean.rs create mode 100644 src/circuit/mod.rs create mode 100644 src/circuit/test/mod.rs create mode 100644 src/circuit/uint32.rs create mode 100644 src/jubjub/edwards.rs create mode 100644 src/jubjub/fs.rs create mode 100644 src/jubjub/mod.rs create mode 100644 src/jubjub/montgomery.rs diff --git a/Cargo.toml b/Cargo.toml index 91643ba..6bc00b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,16 @@ name = "sapling" repository = "https://github.com/zcash/sapling" version = "0.0.1" -[dependencies] -bellman = "0.0.5" - [dependencies.pairing] -version = "0.13" -features = ["u128-support"] +version = "~0.13.2" +features = ["expose-arith"] + +[dependencies] +rand = "0.3" +blake2 = "0.7" +digest = "0.7" +bellman = "0.0.6" + +[features] +default = ["u128-support"] +u128-support = ["pairing/u128-support"] diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs new file mode 100644 index 0000000..d49a233 --- /dev/null +++ b/src/circuit/blake2s.rs @@ -0,0 +1,374 @@ +use pairing::{ + Engine, +}; + +use bellman::{ + SynthesisError, + ConstraintSystem +}; + +use super::boolean::{ + Boolean +}; + +use super::uint32::{ + UInt32 +}; + +/* +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>( + mut cs: CS, + v: &mut [UInt32], + a: usize, + b: usize, + c: usize, + d: usize, + x: &UInt32, + y: &UInt32 +) -> Result<(), SynthesisError> +{ + 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()))?; + } + + 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] +) -> Result>, SynthesisError> +{ + 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)); + h.push(UInt32::constant(0x1F83D9AB)); + h.push(UInt32::constant(0x5BE0CD19)); + + 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::{Blake2s}; + use digest::{FixedOutput, Input}; + + #[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).unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 21792); + } + + #[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::new_keyed(&[], 32); + + let data: Vec = (0..input_len).map(|_| rng.gen()).collect(); + + h.process(&data); + + let hash_result = h.fixed_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 = blake2s(&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()); + }, + _ => panic!() + } + } + } + } +} diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs new file mode 100644 index 0000000..4d3bc3a --- /dev/null +++ b/src/circuit/boolean.rs @@ -0,0 +1,476 @@ +use pairing::{ + Engine, + Field +}; + +use bellman::{ + ConstraintSystem, + SynthesisError, + LinearCombination +}; + +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: Var, + value: Option +} + +impl AllocatedBit { + pub fn get_value(&self) -> Option { + self.value + } + + pub fn get_variable(&self) -> Var { + self.variable + } + + /// 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. + let one = cs.one(); + cs.enforce( + || "boolean constraint", + LinearCombination::zero() + one - var, + LinearCombination::zero() + var, + LinearCombination::zero() + ); + + 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", + LinearCombination::zero() + a.variable + a.variable, + LinearCombination::zero() + b.variable, + LinearCombination::zero() + 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", + LinearCombination::zero() + a.variable, + LinearCombination::zero() + b.variable, + LinearCombination::zero() + result_var + ); + + Ok(AllocatedBit { + variable: result_var, + value: result_value + }) + } +} + +/// 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 { + /// 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)?)) + } + } + } +} + +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}; + + #[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(); + AllocatedBit::xor(&mut cs, &a, &b).unwrap(); + + 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(); + AllocatedBit::and(&mut cs, &a, &b).unwrap(); + + 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_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") + } + } + + #[test] + fn test_boolean_xor() { + #[derive(Copy, Clone)] + enum OperandType { + True, + False, + AllocatedTrue, + AllocatedFalse, + NegatedAllocatedTrue, + NegatedAllocatedFalse + } + + 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") + } + } + } + } +} diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs new file mode 100644 index 0000000..1f0cf94 --- /dev/null +++ b/src/circuit/mod.rs @@ -0,0 +1,21 @@ +#[cfg(test)] +pub mod test; + +pub mod boolean; +pub mod uint32; +pub mod blake2s; + +use bellman::SynthesisError; + +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/src/circuit/test/mod.rs b/src/circuit/test/mod.rs new file mode 100644 index 0000000..22575e9 --- /dev/null +++ b/src/circuit/test/mod.rs @@ -0,0 +1,253 @@ +use pairing::{ + Engine, + Field +}; + +use bellman::{ + LinearCombination, + SynthesisError, + ConstraintSystem +}; + +use std::collections::HashMap; + +#[derive(Debug, Copy, Clone)] +pub enum Variable { + Input(usize), + Aux(usize) +} + +#[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)> +} + +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 { + Variable::Input(index) => inputs[index].0, + Variable::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(Variable::Input(0))); + + TestConstraintSystem { + named_objects: map, + current_namespace: vec![], + constraints: vec![], + inputs: vec![(E::Fr::one(), "ONE".into())], + aux: vec![] + } + } + + 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(Variable::Input(index))) => self.inputs[index].0 = to, + Some(&NamedObject::Var(Variable::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 get(&mut self, path: &str) -> E::Fr + { + match self.named_objects.get(path) { + Some(&NamedObject::Var(Variable::Input(index))) => self.inputs[index].0, + Some(&NamedObject::Var(Variable::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 Variable = Variable; + type Root = Self; + + fn one(&self) -> Self::Variable { + Variable::Input(0) + } + + 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::Aux(index); + self.set_named_obj(path, NamedObject::Var(var)); + + Ok(var) + } + + fn enforce( + &mut self, + annotation: A, + a: LinearCombination, + b: LinearCombination, + c: LinearCombination + ) + where A: FnOnce() -> AR, AR: Into + { + let path = compute_path(&self.current_namespace, annotation().into()); + let index = self.constraints.len(); + self.set_named_obj(path.clone(), NamedObject::Constraint(index)); + + 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", + LinearCombination::zero() + a, + LinearCombination::zero() + b, + LinearCombination::zero() + c + ); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 1); + + cs.set("a/var", Fr::from_str("4").unwrap()); + + let one = cs.one(); + cs.enforce( + || "eq", + LinearCombination::zero() + a, + LinearCombination::zero() + one, + LinearCombination::zero() + 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/src/circuit/uint32.rs b/src/circuit/uint32.rs new file mode 100644 index 0000000..c8bb629 --- /dev/null +++ b/src/circuit/uint32.rs @@ -0,0 +1,451 @@ +use pairing::{ + Engine, + Field, + PrimeField +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination +}; + +use super::boolean::{ + Boolean, + AllocatedBit +}; + +/// 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 + }) + } + + /// Turns this `UInt32` into its little-endian byte order representation. + pub fn into_bits(&self) -> Vec> { + self.bits.chunks(8) + .flat_map(|v| v.iter().rev()) + .cloned() + .collect() + } + + /// 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.chunks(8) + .flat_map(|v| v.iter().rev()) + .cloned() + .collect::>(); + + 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)) + } + } + + /// 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 + }) + } + + // TODO: could optimize + /// Perform modular addition of several `UInt32` objects. + pub fn addmany( + mut cs: CS, + operands: &[Self] + ) -> Result + where E: Engine, + CS: 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 be "zero" + let mut lc = LinearCombination::zero(); + + // 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 { + match bit { + &Boolean::Is(ref bit) => { + // Add coeff * bit + lc = lc + (coeff, bit.get_variable()); + }, + &Boolean::Not(ref bit) => { + // Add coeff * (1 - bit) = coeff * ONE - coeff * bit + lc = lc + (coeff, cs.one()) - (coeff, bit.get_variable()); + }, + &Boolean::Constant(bit) => { + if bit { + lc = lc + (coeff, cs.one()); + } + } + } + + coeff.double(); + } + } + + // The value of the actual result is modulo 2^32 + let modular_value = result_value.map(|v| v as u32); + + // Storage area for the resulting bits + let mut result_bits = vec![]; + + // 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))?; + + // Subtract this bit from the linear combination to ensure the sums balance out + lc = lc - (coeff, b.get_variable()); + + result_bits.push(b.into()); + + max_value >>= 1; + i += 1; + coeff.double(); + } + + // Enforce that the linear combination equals zero + cs.enforce( + || "modular addition", + LinearCombination::zero(), + LinearCombination::zero(), + 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}; + + #[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() { + 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 = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + match b { + &Boolean::Is(ref b) => { + assert!(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; + } + + // 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!(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); + } + } +} diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs new file mode 100644 index 0000000..e803a9c --- /dev/null +++ b/src/jubjub/edwards.rs @@ -0,0 +1,547 @@ +use pairing::{ + Engine, + Field, + SqrtField, + PrimeField, + PrimeFieldRepr, + BitIterator +}; + +use super::{ + JubjubParams, + Unknown, + PrimeOrder, + Fs, + FsRepr, + montgomery +}; + +use rand::{ + Rng +}; + +use std::marker::PhantomData; + +// Represents the affine point (X/Z, Y/Z) via the extended +// twisted Edwards coordinates. +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 { + /// This guarantees the point is in the prime order subgroup + pub fn mul_by_cofactor(&self, params: &JubjubParams) -> Point + { + let tmp = self.double(params) + .double(params) + .double(params); + + convert_subgroup(&tmp) + } + + pub fn rand(rng: &mut R, params: &JubjubParams) -> Self + { + loop { + // given an x on the curve, y^2 = (1 + x^2) / (1 - dx^2) + let x: E::Fr = rng.gen(); + let mut x2 = x; + x2.square(); + + let mut num = E::Fr::one(); + num.add_assign(&x2); + + x2.mul_assign(¶ms.edwards_d); + + let mut den = E::Fr::one(); + den.sub_assign(&x2); + + match den.inverse() { + Some(invden) => { + num.mul_assign(&invden); + + match num.sqrt() { + Some(mut y) => { + if y.into_repr().is_odd() != rng.gen() { + y.negate(); + } + + let mut t = x; + t.mul_assign(&y); + + return Point { + x: x, + y: y, + t: t, + z: E::Fr::one(), + _marker: PhantomData + } + }, + None => {} + } + }, + None => {} + } + } + } +} + +impl Point { + /// Convert from a Montgomery point + pub fn from_montgomery( + m: &montgomery::Point, + params: &JubjubParams + ) -> 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. + + let mut u = x; + u.mul_assign(&y.inverse().expect("y is nonzero")); + + let mut v = x; + v.sub_assign(&E::Fr::one()); + { + let mut tmp = x; + tmp.add_assign(&E::Fr::one()); + v.mul_assign(&tmp.inverse().expect("A - 2 is nonsquare")); + } + + // The resulting x-coordinate needs to be scaled. + u.mul_assign(¶ms.scale); + + let mut t = u; + t.mul_assign(&v); + + Point { + x: u, + y: v, + t: t, + z: E::Fr::one(), + _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: &JubjubParams) -> Option> { + if self.mul(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) + } + + pub fn negate(&self) -> Self { + let mut p = self.clone(); + + p.x.negate(); + p.t.negate(); + + p + } + + pub fn double(&self, params: &JubjubParams) -> Self { + self.add(self, params) + } + + pub fn add(&self, other: &Self, params: &JubjubParams) -> Self + { + // 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; + 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 + } + } + + pub fn mul>(&self, scalar: S, params: &JubjubParams) -> Self + { + let mut res = Self::zero(); + + for b in BitIterator::new(scalar.into()) { + res = res.double(params); + + if b { + res = res.add(self, params); + } + } + + res + } +} + +#[cfg(test)] +mod test { + use rand::{XorShiftRng, SeedableRng, Rand}; + use super::{JubjubParams, Point, PrimeOrder, Fs}; + use pairing::bls12_381::{Bls12}; + use pairing::{Engine, Field}; + + fn is_on_curve( + x: E::Fr, + y: E::Fr, + params: &JubjubParams + ) -> 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(¶ms.edwards_d); + rhs.add_assign(&E::Fr::one()); + + lhs == rhs + } + + #[test] + fn test_rand() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = JubjubParams::new(); + + for _ in 0..100 { + let (x, y) = Point::rand(&mut rng, ¶ms).into_xy(); + + assert!(is_on_curve(x, y, ¶ms)); + } + } + + #[test] + fn test_identities() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = JubjubParams::new(); + + let z = Point::::zero(); + assert!(z.double(¶ms) == z); + assert!(z.negate() == z); + + for _ in 0..100 { + let r = Point::rand(&mut rng, ¶ms); + + assert!(r.add(&Point::zero(), ¶ms) == r); + assert!(r.add(&r.negate(), ¶ms) == Point::zero()); + } + } + + #[test] + fn test_associativity() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = JubjubParams::new(); + + for _ in 0..1000 { + let a = Point::rand(&mut rng, ¶ms); + let b = Point::rand(&mut rng, ¶ms); + let c = Point::rand(&mut rng, ¶ms); + + assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms)); + } + } + + #[test] + fn test_order() { + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + // 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); + } + + #[test] + fn test_mul_associativity() { + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + 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 = Fs::rand(rng); + let b = Fs::rand(rng); + let c = 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_curve(x, y, params)); + + let (x, y) = res2.into_xy(); + assert!(is_on_curve(x, y, params)); + + let (x, y) = res3.into_xy(); + assert!(is_on_curve(x, y, params)); + } + } + + #[test] + fn test_montgomery_conversion() { + use super::montgomery; + + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + for _ in 0..200 { + // compute base in montgomery + let base = montgomery::Point::rand(rng, params); + + // sample random exponent + let exp = Fs::rand(rng); + + // exponentiate in montgomery, convert to edwards + let ed_expected = Point::from_montgomery(&base.mul(exp, params), params); + + // convert to edwards and exponentiate + let ed_exponentiated = Point::from_montgomery(&base, params).mul(exp, params); + + let (x, y) = ed_expected.into_xy(); + assert!(is_on_curve(x, y, params)); + + assert!(ed_exponentiated == ed_expected); + } + } + + #[test] + fn test_back_and_forth() { + use super::montgomery; + + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + for _ in 0..200 { + // compute base in montgomery + let base = montgomery::Point::rand(rng, params); + + // convert to edwards + let base_ed = Point::from_montgomery(&base, params); + + { + let (x, y) = base_ed.into_xy(); + assert!(is_on_curve(x, y, params)); + } + + // convert back to montgomery + let base_mont = montgomery::Point::from_edwards(&base_ed, params); + + assert!(base == base_mont); + } + } +} diff --git a/src/jubjub/fs.rs b/src/jubjub/fs.rs new file mode 100644 index 0000000..888660e --- /dev/null +++ b/src/jubjub/fs.rs @@ -0,0 +1,1240 @@ +use pairing::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError, LegendreSymbol}; +use pairing::LegendreSymbol::*; +use pairing::{adc, sbb, mac_with_carry}; + +// 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 divn(&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 muln(&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) -> bool { + let mut carry = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = adc(*a, *b, &mut carry); + } + + carry != 0 + } + + #[inline(always)] + fn sub_noborrow(&mut self, other: &FsRepr) -> bool { + let mut borrow = 0; + + for (a, b) in self.0.iter_mut().zip(other.0.iter()) { + *a = sbb(*a, *b, &mut borrow); + } + + borrow != 0 + } +} + +/// 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(); + } +} + +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_divn() { + let mut a = FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]); + a.divn(0); + assert_eq!( + a, + FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]) + ); + a.divn(1); + assert_eq!( + a, + FsRepr([0xd99fdd762415141f, 0xccbef069d44659ef, 0xcd7b16954d072a92, 0x1b001d5846f386d0]) + ); + a.divn(50); + assert_eq!( + a, + FsRepr([0xbc1a7511967bf667, 0xc5a55341caa4b32f, 0x75611bce1b4335e, 0x6c0]) + ); + a.divn(130); + assert_eq!( + a, + FsRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0]) + ); + a.divn(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); + } + + // Subtracting r+1 from r should produce a borrow + let mut qplusone = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(qplusone.sub_noborrow(&FsRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))); + + // Subtracting x from x should produce no borrow + let mut x = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(!x.sub_noborrow(&FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))) +} + +#[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); + } + + // Adding 1 to (2^256 - 1) should produce a carry + let mut x = FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); + assert!(x.add_nocarry(&FsRepr::from(1))); + + // Adding 1 to r should not produce a carry + let mut x = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); + assert!(!x.add_nocarry(&FsRepr::from(1))); +} + +#[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()); +} + +// TODO +/* +#[test] +fn fr_field_tests() { + ::tests::field::random_field_tests::(); + ::tests::field::random_sqrt_tests::(); + ::tests::field::random_frobenius_tests::(Fr::char(), 13); + ::tests::field::from_str_tests::(); +} + +#[test] +fn fr_repr_tests() { + ::tests::repr::random_repr_tests::(); +} +*/ diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs new file mode 100644 index 0000000..2bffda8 --- /dev/null +++ b/src/jubjub/mod.rs @@ -0,0 +1,100 @@ +//! Jubjub is an elliptic curve defined over the BLS12-381 scalar field, Fr. +//! It is a Montgomery curve that takes the form `y^2 = x^3 + Ax^2 + x` where +//! `A = 40962`. This is the smallest integer choice of A such that: +//! +//! * `(A - 2) / 4` is a small integer (`10240`). +//! * `A^2 - 4` is quadratic residue. +//! * 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.) +//! +//! This curve is birationally equivalent to a twisted Edwards curve of the +//! form `-x^2 + y^2 = 1 + dx^2y^2` with `d = -(10240/10241)`. In fact, this equivalence +//! forms a group isomorphism, so points can be freely converted between the Montgomery +//! and twisted Edwards forms. + +use pairing::{ + Engine, + PrimeField +}; + +use pairing::bls12_381::{ + Bls12, + Fr +}; + +mod fs; + +pub use self::fs::{Fs, FsRepr}; + +pub mod edwards; +pub mod montgomery; + +/// These are the pre-computed parameters of the Jubjub +/// curve. +pub struct JubjubParams { + edwards_d: E::Fr, + montgomery_a: E::Fr, + + scale: E::Fr +} + +pub enum Unknown { } +pub enum PrimeOrder { } + +impl JubjubParams { + pub fn new() -> Self { + JubjubParams { + // d = -(10240/10241) + edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(), + // A = 40962 + montgomery_a: Fr::from_str("40962").unwrap(), + // scaling factor = sqrt(4 / (a - d)) + scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap() + } + } +} + +#[cfg(test)] +mod test { + use pairing::{Field, SqrtField, LegendreSymbol, PrimeField}; + use pairing::bls12_381::{Fr}; + use super::JubjubParams; + + #[test] + fn test_params() { + let params = JubjubParams::new(); + + // a = -1 + let mut a = Fr::one(); + a.negate(); + + { + // 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); + } + + // Check that A^2 - 4 is nonsquare: + let mut tmp = params.montgomery_a; + tmp.square(); + tmp.sub_assign(&Fr::from_str("4").unwrap()); + assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + + // Check that A - 2 is nonsquare: + let mut tmp = params.montgomery_a; + tmp.sub_assign(&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(&Fr::from_str("4").unwrap()); + tmp = tmp.sqrt().unwrap(); + assert_eq!(tmp, params.scale); + } +} diff --git a/src/jubjub/montgomery.rs b/src/jubjub/montgomery.rs new file mode 100644 index 0000000..559d69a --- /dev/null +++ b/src/jubjub/montgomery.rs @@ -0,0 +1,608 @@ +use pairing::{ + Engine, + Field, + SqrtField, + PrimeField, + PrimeFieldRepr, + BitIterator +}; + +use super::{ + JubjubParams, + Unknown, + PrimeOrder, + Fs, + FsRepr, + edwards +}; + +use rand::{ + Rng +}; + +use std::marker::PhantomData; + +// Represents the affine point (X/Z, Y/Z) via the extended +// twisted Edwards coordinates. +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 { + /// This guarantees the point is in the prime order subgroup + pub fn mul_by_cofactor(&self, params: &JubjubParams) -> Point + { + let tmp = self.double(params) + .double(params) + .double(params); + + convert_subgroup(&tmp) + } + + pub fn rand(rng: &mut R, params: &JubjubParams) -> Self + { + loop { + // given an x on the curve, y^2 = x^3 + A*x^2 + x + let x: E::Fr = rng.gen(); + + let mut x2 = x; + x2.square(); + + let mut rhs = x2; + rhs.mul_assign(¶ms.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() != rng.gen() { + y.negate(); + } + + return Point { + x: x, + y: y, + infinity: false, + _marker: PhantomData + } + }, + None => {} + } + } + } +} + +impl Point { + /// Convert from an Edwards point + pub fn from_edwards( + e: &edwards::Point, + params: &JubjubParams + ) -> 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(¶ms.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: &JubjubParams) -> Option> { + if self.mul(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)) + } + } + + pub fn negate(&self) -> Self { + let mut p = self.clone(); + + p.y.negate(); + + p + } + + pub fn double(&self, params: &JubjubParams) -> Self { + if self.infinity { + return Point::zero(); + } + + if self.y == E::Fr::zero() { + return Point::zero(); + } + + let mut delta = E::Fr::one(); + { + let mut tmp = params.montgomery_a; + 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(¶ms.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 + } + } + + pub fn add(&self, other: &Self, params: &JubjubParams) -> Self + { + 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(¶ms.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 + } + } + } + } + } + + pub fn mul>(&self, scalar: S, params: &JubjubParams) -> Self + { + let mut res = Self::zero(); + + for b in BitIterator::new(scalar.into()) { + res = res.double(params); + + if b { + res = res.add(self, params); + } + } + + res + } +} + +#[cfg(test)] +mod test { + use rand::{XorShiftRng, SeedableRng, Rand}; + use super::{JubjubParams, Point, PrimeOrder, Unknown, Fs}; + use pairing::bls12_381::{Bls12, Fr}; + use pairing::{Engine, Field, PrimeField}; + use std::marker::PhantomData; + + fn is_on_curve( + x: E::Fr, + y: E::Fr, + params: &JubjubParams + ) -> 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(¶ms.montgomery_a); + rhs.add_assign(&x); + rhs.add_assign(&x3); + + lhs == rhs + } + + #[test] + fn test_rand() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = JubjubParams::new(); + + for _ in 0..100 { + let (x, y) = Point::rand(&mut rng, ¶ms).into_xy().unwrap(); + + assert!(is_on_curve(x, y, ¶ms)); + } + } + + #[test] + fn test_identities() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = JubjubParams::new(); + + let z = Point::::zero(); + assert!(z.double(¶ms) == z); + assert!(z.negate() == z); + + for _ in 0..100 { + let r = Point::rand(&mut rng, ¶ms); + + assert!(r.add(&Point::zero(), ¶ms) == r); + assert!(r.add(&r.negate(), ¶ms) == Point::zero()); + } + } + + #[test] + fn test_associativity() { + let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = JubjubParams::new(); + + for _ in 0..1000 { + let a = Point::rand(&mut rng, ¶ms); + let b = Point::rand(&mut rng, ¶ms); + let c = Point::rand(&mut rng, ¶ms); + + assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms)); + } + } + + #[test] + fn test_order() { + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + // 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); + } + + #[test] + fn test_mul_associativity() { + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + 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 = Fs::rand(rng); + let b = Fs::rand(rng); + let c = 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().unwrap(); + assert!(is_on_curve(x, y, params)); + + let (x, y) = res2.into_xy().unwrap(); + assert!(is_on_curve(x, y, params)); + + let (x, y) = res3.into_xy().unwrap(); + assert!(is_on_curve(x, y, params)); + } + } + + #[test] + fn test_edwards_conversion() { + use super::edwards; + + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + for _ in 0..100 { + // compute base in edwards + let base = edwards::Point::rand(rng, params); + + // sample random exponent + let exp = Fs::rand(rng); + + // exponentiate in edwards + let mont_expected = Point::from_edwards(&base.mul(exp, params), params); + + // convert to montgomery and exponentiate + let mont_exp = Point::from_edwards(&base, params).mul(exp, params); + + assert!(mont_exp == mont_expected); + + let (x, y) = mont_expected.into_xy().unwrap(); + assert!(is_on_curve(x, y, params)); + } + } + + #[test] + fn test_back_and_forth() { + use super::edwards; + + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + for _ in 0..100 { + // compute base in edwards + let base = edwards::Point::rand(rng, params); + + // convert to montgomery + let base_mont = Point::from_edwards(&base, params); + + { + let (x, y) = base_mont.into_xy().unwrap(); + assert!(is_on_curve(x, y, params)); + } + + // convert back to edwards + let base_ed = edwards::Point::from_montgomery(&base_mont, params); + + assert!(base == base_ed); + } + } + + #[test] + fn test_awkward_points() { + use super::edwards; + + //let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubParams::new(); + + let mut awkward_points: Vec> = vec![]; + + { + let mut push_point = |x, y| { + let x = Fr::from_str(x).unwrap(); + let y = Fr::from_str(y).unwrap(); + + assert!(is_on_curve(x, y, params)); + + awkward_points.push(Point { + x: x, + y: y, + infinity: false, + _marker: PhantomData + }); + }; + + // p is a point of order 8 + + // push p + push_point( + "26700795483254565448379661158233243896148151268643422869645920428793919977699", + "38240351061652197568958466618399906060451208175623222883988435386266133962140" + ); + + // push 2p + push_point( + "1", + "40876724960280933289965479552128619538703197557433544801868355907127087029496" + ); + + // push 3p + push_point( + "48853380121562139410032601262067414539517111118072400994428343856767649516850", + "32041076745907035847439769934443325418710075447471957144325987857573529479623" + ); + + // push 4p + push_point( + "0", + "0" + ); + + // push 5p + push_point( + "48853380121562139410032601262067414539517111118072400994428343856767649516850", + "20394798429219154632007970573742640418980477053055680678277670842365051704890" + ); + + // push 6p + push_point( + "1", + "11559150214845257189482260956057346298987354943094093020735302792811494155017" + ); + + // push 7p + push_point( + "26700795483254565448379661158233243896148151268643422869645920428793919977699", + "14195524113473992910489273889786059777239344324904414938615223313672447222373" + ); + } + + // push 8p (point at infinity) + awkward_points.push(Point::zero()); + + for point in &awkward_points { + let ed = edwards::Point::from_montgomery(point, params); + let mut ed_tmp = ed.clone(); + let mut mont_tmp = point.clone(); + for _ in 0..8 { + let mont_again = Point::from_edwards(&ed_tmp, params); + assert!(mont_again == mont_tmp); + + let ed_again = edwards::Point::from_montgomery(&mont_tmp, params); + assert!(ed_again == ed_tmp); + + ed_tmp = ed_tmp.add(&ed, params); + mont_tmp = mont_tmp.add(point, params); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index e4c902b..552fe01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,9 @@ extern crate pairing; extern crate bellman; +extern crate blake2; +extern crate digest; +extern crate rand; + +pub mod jubjub; +pub mod circuit; From 96bcc84447db78647f0be58a4f7053949c8f786e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 6 Dec 2017 10:10:56 -0700 Subject: [PATCH 003/168] Change library name and add README. --- Cargo.toml | 8 ++++---- README.md | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 README.md diff --git a/Cargo.toml b/Cargo.toml index 6bc00b2..d4c111f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] authors = ["Sean Bowe "] description = "Cryptographic library for Zcash Sapling" -documentation = "https://github.com/zcash/sapling" -homepage = "https://github.com/zcash/sapling" +documentation = "https://github.com/zcash-hackworks/sapling" +homepage = "https://github.com/zcash-hackworks/sapling" license = "MIT/Apache-2.0" -name = "sapling" -repository = "https://github.com/zcash/sapling" +name = "sapling-crypto" +repository = "https://github.com/zcash-hackworks/sapling" version = "0.0.1" [dependencies.pairing] diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5d3bce --- /dev/null +++ b/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. From bcb4925c6d18dd403c2213a7f61fcd69f570148b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 6 Dec 2017 10:15:12 -0700 Subject: [PATCH 004/168] Rename "sapling" to "sapling-crypto" in COPYRIGHT. --- COPYRIGHT | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/COPYRIGHT b/COPYRIGHT index 0df5950..f2c6a3b 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,7 +1,7 @@ -Copyrights in the "sapling" library are retained by their contributors. No -copyright assignment is required to contribute to the "sapling" library. +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" library is licensed under either of +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) From 7c4879251193bccb5099a00dd15d0cf5780da7bc Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 6 Dec 2017 10:21:56 -0700 Subject: [PATCH 005/168] Some test cleanups. --- src/jubjub/mod.rs | 38 ++++++++++++++++++++++---------------- src/jubjub/montgomery.rs | 11 +++++------ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 2bffda8..61b9992 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -78,23 +78,29 @@ mod test { assert!(a.legendre() == LegendreSymbol::QuadraticResidue); } - // Check that A^2 - 4 is nonsquare: - let mut tmp = params.montgomery_a; - tmp.square(); - tmp.sub_assign(&Fr::from_str("4").unwrap()); - assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + { + // Check that A^2 - 4 is nonsquare: + let mut tmp = params.montgomery_a; + tmp.square(); + tmp.sub_assign(&Fr::from_str("4").unwrap()); + assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + } - // Check that A - 2 is nonsquare: - let mut tmp = params.montgomery_a; - tmp.sub_assign(&Fr::from_str("2").unwrap()); - assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + { + // Check that A - 2 is nonsquare: + let mut tmp = params.montgomery_a; + tmp.sub_assign(&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(&Fr::from_str("4").unwrap()); - tmp = tmp.sqrt().unwrap(); - assert_eq!(tmp, params.scale); + { + // Check the validity of the scaling factor + let mut tmp = a; + tmp.sub_assign(¶ms.edwards_d); + tmp = tmp.inverse().unwrap(); + tmp.mul_assign(&Fr::from_str("4").unwrap()); + tmp = tmp.sqrt().unwrap(); + assert_eq!(tmp, params.scale); + } } } diff --git a/src/jubjub/montgomery.rs b/src/jubjub/montgomery.rs index 559d69a..9d7c944 100644 --- a/src/jubjub/montgomery.rs +++ b/src/jubjub/montgomery.rs @@ -518,13 +518,12 @@ mod test { } #[test] - fn test_awkward_points() { + fn test_low_order_points() { use super::edwards; - //let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); let params = &JubjubParams::new(); - let mut awkward_points: Vec> = vec![]; + let mut low_order_points: Vec> = vec![]; { let mut push_point = |x, y| { @@ -533,7 +532,7 @@ mod test { assert!(is_on_curve(x, y, params)); - awkward_points.push(Point { + low_order_points.push(Point { x: x, y: y, infinity: false, @@ -587,9 +586,9 @@ mod test { } // push 8p (point at infinity) - awkward_points.push(Point::zero()); + low_order_points.push(Point::zero()); - for point in &awkward_points { + for point in &low_order_points { let ed = edwards::Point::from_montgomery(point, params); let mut ed_tmp = ed.clone(); let mut mont_tmp = point.clone(); From c0f5645ab44f004dd565fa4ade7d2d9947196ab8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 6 Dec 2017 17:22:35 -0700 Subject: [PATCH 006/168] Do not perform inversions when converting from Montgomery to projective extended twisted Edwards. --- src/jubjub/edwards.rs | 50 ++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index e803a9c..68d323b 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -193,29 +193,55 @@ impl Point { // 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 auxillary 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(&y.inverse().expect("y is nonzero")); - - let mut v = x; - v.sub_assign(&E::Fr::one()); - { - let mut tmp = x; - tmp.add_assign(&E::Fr::one()); - v.mul_assign(&tmp.inverse().expect("A - 2 is nonsquare")); - } - - // The resulting x-coordinate needs to be scaled. u.mul_assign(¶ms.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: E::Fr::one(), + z: z, _marker: PhantomData } } From e9d3923829affc219bda28b31f2845cce4ba9e38 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 11 Dec 2017 23:06:05 -0700 Subject: [PATCH 007/168] Refactor jubjub implementation to be abstract over field, parameters. --- src/jubjub/edwards.rs | 237 +++------------------------- src/jubjub/mod.rs | 97 +++++------- src/jubjub/montgomery.rs | 328 +++------------------------------------ src/jubjub/tests.rs | 299 +++++++++++++++++++++++++++++++++++ 4 files changed, 385 insertions(+), 576 deletions(-) create mode 100644 src/jubjub/tests.rs diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index 68d323b..bf9b3f4 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -1,5 +1,4 @@ use pairing::{ - Engine, Field, SqrtField, PrimeField, @@ -8,11 +7,10 @@ use pairing::{ }; use super::{ + JubjubEngine, JubjubParams, Unknown, PrimeOrder, - Fs, - FsRepr, montgomery }; @@ -24,7 +22,7 @@ use std::marker::PhantomData; // Represents the affine point (X/Z, Y/Z) via the extended // twisted Edwards coordinates. -pub struct Point { +pub struct Point { x: E::Fr, y: E::Fr, t: E::Fr, @@ -32,7 +30,7 @@ pub struct Point { _marker: PhantomData } -fn convert_subgroup(from: &Point) -> Point +fn convert_subgroup(from: &Point) -> Point { Point { x: from.x, @@ -43,7 +41,7 @@ fn convert_subgroup(from: &Point) -> Point } } -impl From> for Point +impl From> for Point { fn from(p: Point) -> Point { @@ -51,14 +49,14 @@ impl From> for Point } } -impl Clone for Point +impl Clone for Point { fn clone(&self) -> Self { convert_subgroup(self) } } -impl PartialEq for Point { +impl PartialEq for Point { fn eq(&self, other: &Point) -> bool { // p1 = (x1/z1, y1/z1) // p2 = (x2/z2, y2/z2) @@ -82,9 +80,9 @@ impl PartialEq for Point { } } -impl Point { +impl Point { /// This guarantees the point is in the prime order subgroup - pub fn mul_by_cofactor(&self, params: &JubjubParams) -> Point + pub fn mul_by_cofactor(&self, params: &E::Params) -> Point { let tmp = self.double(params) .double(params) @@ -93,7 +91,7 @@ impl Point { convert_subgroup(&tmp) } - pub fn rand(rng: &mut R, params: &JubjubParams) -> Self + pub fn rand(rng: &mut R, params: &E::Params) -> Self { loop { // given an x on the curve, y^2 = (1 + x^2) / (1 - dx^2) @@ -104,7 +102,7 @@ impl Point { let mut num = E::Fr::one(); num.add_assign(&x2); - x2.mul_assign(¶ms.edwards_d); + x2.mul_assign(params.edwards_d()); let mut den = E::Fr::one(); den.sub_assign(&x2); @@ -139,11 +137,11 @@ impl Point { } } -impl Point { +impl Point { /// Convert from a Montgomery point pub fn from_montgomery( m: &montgomery::Point, - params: &JubjubParams + params: &E::Params ) -> Self { match m.into_xy() { @@ -214,7 +212,7 @@ impl Point { // u = xs let mut u = x; - u.mul_assign(¶ms.scale); + u.mul_assign(params.scale()); // v = x - 1 let mut v = x; @@ -251,8 +249,8 @@ impl Point { /// 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: &JubjubParams) -> Option> { - if self.mul(Fs::char(), params) == Point::zero() { + 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 @@ -291,11 +289,11 @@ impl Point { p } - pub fn double(&self, params: &JubjubParams) -> Self { + pub fn double(&self, params: &E::Params) -> Self { self.add(self, params) } - pub fn add(&self, other: &Self, params: &JubjubParams) -> Self + pub fn add(&self, other: &Self, params: &E::Params) -> Self { // A = x1 * x2 let mut a = self.x; @@ -306,7 +304,7 @@ impl Point { b.mul_assign(&other.y); // C = d * t1 * t2 - let mut c = params.edwards_d; + let mut c = params.edwards_d().clone(); c.mul_assign(&self.t); c.mul_assign(&other.t); @@ -363,7 +361,11 @@ impl Point { } } - pub fn mul>(&self, scalar: S, params: &JubjubParams) -> Self + pub fn mul::Repr>>( + &self, + scalar: S, + params: &E::Params + ) -> Self { let mut res = Self::zero(); @@ -378,196 +380,3 @@ impl Point { res } } - -#[cfg(test)] -mod test { - use rand::{XorShiftRng, SeedableRng, Rand}; - use super::{JubjubParams, Point, PrimeOrder, Fs}; - use pairing::bls12_381::{Bls12}; - use pairing::{Engine, Field}; - - fn is_on_curve( - x: E::Fr, - y: E::Fr, - params: &JubjubParams - ) -> 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(¶ms.edwards_d); - rhs.add_assign(&E::Fr::one()); - - lhs == rhs - } - - #[test] - fn test_rand() { - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = JubjubParams::new(); - - for _ in 0..100 { - let (x, y) = Point::rand(&mut rng, ¶ms).into_xy(); - - assert!(is_on_curve(x, y, ¶ms)); - } - } - - #[test] - fn test_identities() { - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = JubjubParams::new(); - - let z = Point::::zero(); - assert!(z.double(¶ms) == z); - assert!(z.negate() == z); - - for _ in 0..100 { - let r = Point::rand(&mut rng, ¶ms); - - assert!(r.add(&Point::zero(), ¶ms) == r); - assert!(r.add(&r.negate(), ¶ms) == Point::zero()); - } - } - - #[test] - fn test_associativity() { - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = JubjubParams::new(); - - for _ in 0..1000 { - let a = Point::rand(&mut rng, ¶ms); - let b = Point::rand(&mut rng, ¶ms); - let c = Point::rand(&mut rng, ¶ms); - - assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms)); - } - } - - #[test] - fn test_order() { - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = &JubjubParams::new(); - - // 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); - } - - #[test] - fn test_mul_associativity() { - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = &JubjubParams::new(); - - 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 = Fs::rand(rng); - let b = Fs::rand(rng); - let c = 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_curve(x, y, params)); - - let (x, y) = res2.into_xy(); - assert!(is_on_curve(x, y, params)); - - let (x, y) = res3.into_xy(); - assert!(is_on_curve(x, y, params)); - } - } - - #[test] - fn test_montgomery_conversion() { - use super::montgomery; - - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = &JubjubParams::new(); - - for _ in 0..200 { - // compute base in montgomery - let base = montgomery::Point::rand(rng, params); - - // sample random exponent - let exp = Fs::rand(rng); - - // exponentiate in montgomery, convert to edwards - let ed_expected = Point::from_montgomery(&base.mul(exp, params), params); - - // convert to edwards and exponentiate - let ed_exponentiated = Point::from_montgomery(&base, params).mul(exp, params); - - let (x, y) = ed_expected.into_xy(); - assert!(is_on_curve(x, y, params)); - - assert!(ed_exponentiated == ed_expected); - } - } - - #[test] - fn test_back_and_forth() { - use super::montgomery; - - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = &JubjubParams::new(); - - for _ in 0..200 { - // compute base in montgomery - let base = montgomery::Point::rand(rng, params); - - // convert to edwards - let base_ed = Point::from_montgomery(&base, params); - - { - let (x, y) = base_ed.into_xy(); - assert!(is_on_curve(x, y, params)); - } - - // convert back to montgomery - let base_mont = montgomery::Point::from_edwards(&base_ed, params); - - assert!(base == base_mont); - } - } -} diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 61b9992..e510eb8 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -16,7 +16,8 @@ use pairing::{ Engine, - PrimeField + PrimeField, + SqrtField }; use pairing::bls12_381::{ @@ -24,28 +25,48 @@ use pairing::bls12_381::{ Fr }; -mod fs; - -pub use self::fs::{Fs, FsRepr}; - pub mod edwards; pub mod montgomery; -/// These are the pre-computed parameters of the Jubjub -/// curve. -pub struct JubjubParams { - edwards_d: E::Fr, - montgomery_a: E::Fr, +#[cfg(test)] +pub mod tests; - scale: E::Fr +pub trait JubjubEngine: Engine { + type Fs: PrimeField + SqrtField; + type Params: JubjubParams; +} + +pub trait JubjubParams: Sized { + fn edwards_d(&self) -> &E::Fr; + fn montgomery_a(&self) -> &E::Fr; + fn scale(&self) -> &E::Fr; } pub enum Unknown { } pub enum PrimeOrder { } -impl JubjubParams { +pub mod fs; + +impl JubjubEngine for Bls12 { + type Fs = self::fs::Fs; + type Params = JubjubBls12; +} + +pub struct JubjubBls12 { + edwards_d: Fr, + montgomery_a: Fr, + scale: Fr +} + +impl JubjubParams for JubjubBls12 { + fn edwards_d(&self) -> &Fr { &self.edwards_d } + fn montgomery_a(&self) -> &Fr { &self.montgomery_a } + fn scale(&self) -> &Fr { &self.scale } +} + +impl JubjubBls12 { pub fn new() -> Self { - JubjubParams { + JubjubBls12 { // d = -(10240/10241) edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(), // A = 40962 @@ -56,51 +77,9 @@ impl JubjubParams { } } -#[cfg(test)] -mod test { - use pairing::{Field, SqrtField, LegendreSymbol, PrimeField}; - use pairing::bls12_381::{Fr}; - use super::JubjubParams; +#[test] +fn test_jubjub_bls12() { + let params = JubjubBls12::new(); - #[test] - fn test_params() { - let params = JubjubParams::new(); - - // a = -1 - let mut a = Fr::one(); - a.negate(); - - { - // 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); - } - - { - // Check that A^2 - 4 is nonsquare: - let mut tmp = params.montgomery_a; - tmp.square(); - tmp.sub_assign(&Fr::from_str("4").unwrap()); - assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); - } - - { - // Check that A - 2 is nonsquare: - let mut tmp = params.montgomery_a; - tmp.sub_assign(&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(&Fr::from_str("4").unwrap()); - tmp = tmp.sqrt().unwrap(); - assert_eq!(tmp, params.scale); - } - } + tests::test_suite::(¶ms); } diff --git a/src/jubjub/montgomery.rs b/src/jubjub/montgomery.rs index 9d7c944..6af0e6d 100644 --- a/src/jubjub/montgomery.rs +++ b/src/jubjub/montgomery.rs @@ -1,5 +1,4 @@ use pairing::{ - Engine, Field, SqrtField, PrimeField, @@ -8,11 +7,10 @@ use pairing::{ }; use super::{ + JubjubEngine, JubjubParams, Unknown, PrimeOrder, - Fs, - FsRepr, edwards }; @@ -24,14 +22,14 @@ use std::marker::PhantomData; // Represents the affine point (X/Z, Y/Z) via the extended // twisted Edwards coordinates. -pub struct Point { +pub struct Point { x: E::Fr, y: E::Fr, infinity: bool, _marker: PhantomData } -fn convert_subgroup(from: &Point) -> Point +fn convert_subgroup(from: &Point) -> Point { Point { x: from.x, @@ -41,7 +39,7 @@ fn convert_subgroup(from: &Point) -> Point } } -impl From> for Point +impl From> for Point { fn from(p: Point) -> Point { @@ -49,14 +47,14 @@ impl From> for Point } } -impl Clone for Point +impl Clone for Point { fn clone(&self) -> Self { convert_subgroup(self) } } -impl PartialEq for Point { +impl PartialEq for Point { fn eq(&self, other: &Point) -> bool { match (self.infinity, other.infinity) { (true, true) => true, @@ -68,9 +66,9 @@ impl PartialEq for Point { } } -impl Point { +impl Point { /// This guarantees the point is in the prime order subgroup - pub fn mul_by_cofactor(&self, params: &JubjubParams) -> Point + pub fn mul_by_cofactor(&self, params: &E::Params) -> Point { let tmp = self.double(params) .double(params) @@ -79,7 +77,7 @@ impl Point { convert_subgroup(&tmp) } - pub fn rand(rng: &mut R, params: &JubjubParams) -> Self + pub fn rand(rng: &mut R, params: &E::Params) -> Self { loop { // given an x on the curve, y^2 = x^3 + A*x^2 + x @@ -89,7 +87,7 @@ impl Point { x2.square(); let mut rhs = x2; - rhs.mul_assign(¶ms.montgomery_a); + rhs.mul_assign(params.montgomery_a()); rhs.add_assign(&x); x2.mul_assign(&x); rhs.add_assign(&x2); @@ -113,11 +111,11 @@ impl Point { } } -impl Point { +impl Point { /// Convert from an Edwards point pub fn from_edwards( e: &edwards::Point, - params: &JubjubParams + params: &E::Params ) -> Self { let (x, y) = e.into_xy(); @@ -168,7 +166,7 @@ impl Point { v.mul_assign(&x.inverse().unwrap()); // Scale it into the correct curve constants - v.mul_assign(¶ms.scale); + v.mul_assign(params.scale()); Point { x: u, @@ -182,8 +180,8 @@ impl Point { /// 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: &JubjubParams) -> Option> { - if self.mul(Fs::char(), params) == Point::zero() { + 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 @@ -216,7 +214,7 @@ impl Point { p } - pub fn double(&self, params: &JubjubParams) -> Self { + pub fn double(&self, params: &E::Params) -> Self { if self.infinity { return Point::zero(); } @@ -227,7 +225,7 @@ impl Point { let mut delta = E::Fr::one(); { - let mut tmp = params.montgomery_a; + let mut tmp = params.montgomery_a().clone(); tmp.mul_assign(&self.x); tmp.double(); delta.add_assign(&tmp); @@ -247,7 +245,7 @@ impl Point { let mut x3 = delta; x3.square(); - x3.sub_assign(¶ms.montgomery_a); + x3.sub_assign(params.montgomery_a()); x3.sub_assign(&self.x); x3.sub_assign(&self.x); @@ -265,7 +263,7 @@ impl Point { } } - pub fn add(&self, other: &Self, params: &JubjubParams) -> Self + pub fn add(&self, other: &Self, params: &E::Params) -> Self { match (self.infinity, other.infinity) { (true, true) => Point::zero(), @@ -289,7 +287,7 @@ impl Point { let mut x3 = delta; x3.square(); - x3.sub_assign(¶ms.montgomery_a); + x3.sub_assign(params.montgomery_a()); x3.sub_assign(&self.x); x3.sub_assign(&other.x); @@ -310,7 +308,11 @@ impl Point { } } - pub fn mul>(&self, scalar: S, params: &JubjubParams) -> Self + pub fn mul::Repr>>( + &self, + scalar: S, + params: &E::Params + ) -> Self { let mut res = Self::zero(); @@ -325,283 +327,3 @@ impl Point { res } } - -#[cfg(test)] -mod test { - use rand::{XorShiftRng, SeedableRng, Rand}; - use super::{JubjubParams, Point, PrimeOrder, Unknown, Fs}; - use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Engine, Field, PrimeField}; - use std::marker::PhantomData; - - fn is_on_curve( - x: E::Fr, - y: E::Fr, - params: &JubjubParams - ) -> 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(¶ms.montgomery_a); - rhs.add_assign(&x); - rhs.add_assign(&x3); - - lhs == rhs - } - - #[test] - fn test_rand() { - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = JubjubParams::new(); - - for _ in 0..100 { - let (x, y) = Point::rand(&mut rng, ¶ms).into_xy().unwrap(); - - assert!(is_on_curve(x, y, ¶ms)); - } - } - - #[test] - fn test_identities() { - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = JubjubParams::new(); - - let z = Point::::zero(); - assert!(z.double(¶ms) == z); - assert!(z.negate() == z); - - for _ in 0..100 { - let r = Point::rand(&mut rng, ¶ms); - - assert!(r.add(&Point::zero(), ¶ms) == r); - assert!(r.add(&r.negate(), ¶ms) == Point::zero()); - } - } - - #[test] - fn test_associativity() { - let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = JubjubParams::new(); - - for _ in 0..1000 { - let a = Point::rand(&mut rng, ¶ms); - let b = Point::rand(&mut rng, ¶ms); - let c = Point::rand(&mut rng, ¶ms); - - assert!(a.add(&b, ¶ms).add(&c, ¶ms) == c.add(&a, ¶ms).add(&b, ¶ms)); - } - } - - #[test] - fn test_order() { - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = &JubjubParams::new(); - - // 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); - } - - #[test] - fn test_mul_associativity() { - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = &JubjubParams::new(); - - 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 = Fs::rand(rng); - let b = Fs::rand(rng); - let c = 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().unwrap(); - assert!(is_on_curve(x, y, params)); - - let (x, y) = res2.into_xy().unwrap(); - assert!(is_on_curve(x, y, params)); - - let (x, y) = res3.into_xy().unwrap(); - assert!(is_on_curve(x, y, params)); - } - } - - #[test] - fn test_edwards_conversion() { - use super::edwards; - - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = &JubjubParams::new(); - - for _ in 0..100 { - // compute base in edwards - let base = edwards::Point::rand(rng, params); - - // sample random exponent - let exp = Fs::rand(rng); - - // exponentiate in edwards - let mont_expected = Point::from_edwards(&base.mul(exp, params), params); - - // convert to montgomery and exponentiate - let mont_exp = Point::from_edwards(&base, params).mul(exp, params); - - assert!(mont_exp == mont_expected); - - let (x, y) = mont_expected.into_xy().unwrap(); - assert!(is_on_curve(x, y, params)); - } - } - - #[test] - fn test_back_and_forth() { - use super::edwards; - - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let params = &JubjubParams::new(); - - for _ in 0..100 { - // compute base in edwards - let base = edwards::Point::rand(rng, params); - - // convert to montgomery - let base_mont = Point::from_edwards(&base, params); - - { - let (x, y) = base_mont.into_xy().unwrap(); - assert!(is_on_curve(x, y, params)); - } - - // convert back to edwards - let base_ed = edwards::Point::from_montgomery(&base_mont, params); - - assert!(base == base_ed); - } - } - - #[test] - fn test_low_order_points() { - use super::edwards; - - let params = &JubjubParams::new(); - - let mut low_order_points: Vec> = vec![]; - - { - let mut push_point = |x, y| { - let x = Fr::from_str(x).unwrap(); - let y = Fr::from_str(y).unwrap(); - - assert!(is_on_curve(x, y, params)); - - low_order_points.push(Point { - x: x, - y: y, - infinity: false, - _marker: PhantomData - }); - }; - - // p is a point of order 8 - - // push p - push_point( - "26700795483254565448379661158233243896148151268643422869645920428793919977699", - "38240351061652197568958466618399906060451208175623222883988435386266133962140" - ); - - // push 2p - push_point( - "1", - "40876724960280933289965479552128619538703197557433544801868355907127087029496" - ); - - // push 3p - push_point( - "48853380121562139410032601262067414539517111118072400994428343856767649516850", - "32041076745907035847439769934443325418710075447471957144325987857573529479623" - ); - - // push 4p - push_point( - "0", - "0" - ); - - // push 5p - push_point( - "48853380121562139410032601262067414539517111118072400994428343856767649516850", - "20394798429219154632007970573742640418980477053055680678277670842365051704890" - ); - - // push 6p - push_point( - "1", - "11559150214845257189482260956057346298987354943094093020735302792811494155017" - ); - - // push 7p - push_point( - "26700795483254565448379661158233243896148151268643422869645920428793919977699", - "14195524113473992910489273889786059777239344324904414938615223313672447222373" - ); - } - - // push 8p (point at infinity) - low_order_points.push(Point::zero()); - - for point in &low_order_points { - let ed = edwards::Point::from_montgomery(point, params); - let mut ed_tmp = ed.clone(); - let mut mont_tmp = point.clone(); - for _ in 0..8 { - let mont_again = Point::from_edwards(&ed_tmp, params); - assert!(mont_again == mont_tmp); - - let ed_again = edwards::Point::from_montgomery(&mont_tmp, params); - assert!(ed_again == ed_tmp); - - ed_tmp = ed_tmp.add(&ed, params); - mont_tmp = mont_tmp.add(point, params); - } - } - } -} diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs new file mode 100644 index 0000000..d594383 --- /dev/null +++ b/src/jubjub/tests.rs @@ -0,0 +1,299 @@ +// TODO +use super::*; + +use pairing::{ + Field, + 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_identities::(params); + test_addition_associativity::(params); + test_order::(params); + test_mul_associativity::(params); + test_loworder::(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_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(); + + { + // 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); + } + + { + // 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()); + } +} From 3a6e8d448f25a7339dc425c9a0f4b2b95ca039e1 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 12 Dec 2017 10:31:13 -0700 Subject: [PATCH 008/168] Add `get_for_x` to Montgomery implementation. --- src/jubjub/montgomery.rs | 55 ++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/jubjub/montgomery.rs b/src/jubjub/montgomery.rs index 6af0e6d..e827111 100644 --- a/src/jubjub/montgomery.rs +++ b/src/jubjub/montgomery.rs @@ -67,6 +67,36 @@ impl PartialEq for Point { } impl Point { + pub fn get_for_x(x: E::Fr, sign: bool, params: &E::Params) -> Option + { + // given an x on the curve, y^2 = 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 pub fn mul_by_cofactor(&self, params: &E::Params) -> Point { @@ -80,30 +110,11 @@ impl Point { pub fn rand(rng: &mut R, params: &E::Params) -> Self { loop { - // given an x on the curve, y^2 = x^3 + A*x^2 + x let x: E::Fr = rng.gen(); - 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() != rng.gen() { - y.negate(); - } - - return Point { - x: x, - y: y, - infinity: false, - _marker: PhantomData - } + match Self::get_for_x(x, rng.gen(), params) { + Some(p) => { + return p }, None => {} } From 6b43a4ed108c5124ee14c8df081679d2af432f67 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 12 Dec 2017 14:42:13 -0700 Subject: [PATCH 009/168] Make UInt32::addmany produce constant results when fed constant inputs, to allow for blake2s block precomputation for group hash. --- src/circuit/blake2s.rs | 31 ++++++++++++++++++++++++- src/circuit/uint32.rs | 52 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index d49a233..9a0d283 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -325,6 +325,32 @@ mod test { assert_eq!(cs.num_constraints(), 21792); } + #[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).unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 21792); + } + + #[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).unwrap(); + assert_eq!(cs.num_constraints(), 0); + } + #[test] fn test_blake2s() { let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -366,7 +392,10 @@ mod test { Boolean::Not(b) => { assert!(s.next().unwrap() != b.get_value().unwrap()); }, - _ => panic!() + Boolean::Constant(b) => { + assert!(input_len == 0); + assert!(s.next().unwrap() == b); + } } } } diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index c8bb629..7954538 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -205,6 +205,8 @@ impl UInt32 { // This is a linear combination that we will enforce to be "zero" let mut lc = LinearCombination::zero(); + let mut all_constants = true; + // Iterate over the operands for op in operands { // Accumulate the value @@ -225,10 +227,14 @@ impl UInt32 { for bit in &op.bits { match bit { &Boolean::Is(ref bit) => { + all_constants = false; + // Add coeff * bit lc = lc + (coeff, bit.get_variable()); }, &Boolean::Not(ref bit) => { + all_constants = false; + // Add coeff * (1 - bit) = coeff * ONE - coeff * bit lc = lc + (coeff, cs.one()) - (coeff, bit.get_variable()); }, @@ -246,6 +252,13 @@ impl UInt32 { // 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![]; @@ -367,6 +380,41 @@ mod test { } } + #[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 = UInt32::addmany(cs.namespace(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap(); + + 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]); @@ -401,8 +449,8 @@ mod test { &Boolean::Not(ref b) => { assert!(!b.get_value().unwrap() == (expected & 1 == 1)); }, - &Boolean::Constant(b) => { - assert!(b == (expected & 1 == 1)); + &Boolean::Constant(_) => { + unreachable!() } } From 8c4433ee820d233a03c3669aecc0986542853030 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 14 Dec 2017 11:34:57 -0700 Subject: [PATCH 010/168] Add 2A precomputation to jubjub parameters. --- src/jubjub/mod.rs | 12 +++++++++++- src/jubjub/tests.rs | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index e510eb8..d281b96 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -16,6 +16,7 @@ use pairing::{ Engine, + Field, PrimeField, SqrtField }; @@ -39,6 +40,7 @@ pub trait JubjubEngine: Engine { pub trait JubjubParams: Sized { fn edwards_d(&self) -> &E::Fr; fn montgomery_a(&self) -> &E::Fr; + fn montgomery_2a(&self) -> &E::Fr; fn scale(&self) -> &E::Fr; } @@ -55,22 +57,30 @@ impl JubjubEngine for Bls12 { pub struct JubjubBls12 { edwards_d: Fr, montgomery_a: Fr, + montgomery_2a: Fr, scale: Fr } 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 } } impl JubjubBls12 { pub fn new() -> Self { + let montgomery_a = Fr::from_str("40962").unwrap(); + let mut montgomery_2a = montgomery_a; + montgomery_2a.double(); + JubjubBls12 { // d = -(10240/10241) edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(), // A = 40962 - montgomery_a: Fr::from_str("40962").unwrap(), + montgomery_a: montgomery_a, + // 2A = 2.A + montgomery_2a: montgomery_2a, // scaling factor = sqrt(4 / (a - d)) scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap() } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index d594383..c26bbe7 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -264,6 +264,14 @@ fn test_jubjub_params(params: &E::Params) { 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. From 46cbfb483103184d22e0c0eb0f4e2685924c45e4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 14 Dec 2017 15:41:37 -0700 Subject: [PATCH 011/168] Implementation of Montgomery point doubling in the circuit. --- src/circuit/mod.rs | 2 + src/circuit/mont.rs | 225 ++++++++++++++++++++++++++++++++++++++++++++ src/circuit/num.rs | 164 ++++++++++++++++++++++++++++++++ 3 files changed, 391 insertions(+) create mode 100644 src/circuit/mont.rs create mode 100644 src/circuit/num.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 1f0cf94..9849599 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -4,6 +4,8 @@ pub mod test; pub mod boolean; pub mod uint32; pub mod blake2s; +pub mod num; +pub mod mont; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs new file mode 100644 index 0000000..e5837a9 --- /dev/null +++ b/src/circuit/mont.rs @@ -0,0 +1,225 @@ +use pairing::{ + Engine, + Field, +// TODO +// PrimeField +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination +}; + +use super::{ + Assignment +}; + +use super::num::AllocatedNum; + +use ::jubjub::{ + JubjubEngine, + JubjubParams +}; + +pub struct MontgomeryPoint { + x: AllocatedNum, + y: AllocatedNum +} + +impl MontgomeryPoint { + /// Performs an affine point doubling, not defined for + /// the point of order two (0, 0). + pub fn double( + &self, + mut cs: CS, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Square x + let xx = self.x.square(&mut cs)?; + + // Compute lambda = (3.xx + 2.A.x + 1) / 2.y + let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || { + let mut t0 = *xx.get_value().get()?; + let mut t1 = t0; + t0.double(); // t0 = 2.xx + t0.add_assign(&t1); // t0 = 3.xx + t1 = *self.x.get_value().get()?; // t1 = x + t1.mul_assign(params.montgomery_2a()); // t1 = 2.A.x + t0.add_assign(&t1); + t0.add_assign(&E::Fr::one()); + t1 = *self.y.get_value().get()?; // t1 = y + t1.double(); // t1 = 2.y + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + // TODO: add a more descriptive error to bellman + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + // (2.y) * (lambda) = (3.xx + 2.A.x + 1) + let one = cs.one(); + cs.enforce( + || "evaluate lambda", + LinearCombination::::zero() + self.y.get_variable() + + self.y.get_variable(), + + LinearCombination::zero() + lambda.get_variable(), + + LinearCombination::::zero() + xx.get_variable() + + xx.get_variable() + + xx.get_variable() + + (*params.montgomery_2a(), self.x.get_variable()) + + one + ); + + // Compute x' = (lambda^2) - A - 2.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(self.x.get_value().get()?); + + Ok(t0) + })?; + + // (lambda) * (lambda) = (A + 2.x + x') + cs.enforce( + || "evaluate xprime", + LinearCombination::zero() + lambda.get_variable(), + LinearCombination::zero() + lambda.get_variable(), + LinearCombination::::zero() + (*params.montgomery_a(), one) + + self.x.get_variable() + + self.x.get_variable() + + 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", + LinearCombination::zero() + self.x.get_variable() + - xprime.get_variable(), + + LinearCombination::zero() + lambda.get_variable(), + + LinearCombination::::zero() + yprime.get_variable() + + self.y.get_variable() + ); + + Ok(MontgomeryPoint { + x: xprime, + y: yprime + }) + } +} + +#[cfg(test)] +mod test { + use bellman::{ConstraintSystem}; + use rand::{XorShiftRng, SeedableRng, Rng}; + use pairing::bls12_381::{Bls12, Fr}; + use pairing::{Field}; + use ::circuit::test::*; + use ::jubjub::{ + montgomery, + JubjubBls12 + }; + use super::{MontgomeryPoint, AllocatedNum}; + + #[test] + fn test_doubling_order_2() { + let params = &JubjubBls12::new(); + + let mut cs = TestConstraintSystem::::new(); + + let x = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(Fr::zero()) + }).unwrap(); + let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(Fr::zero()) + }).unwrap(); + + let p = MontgomeryPoint { + x: x, + y: y + }; + + assert!(p.double(&mut cs, params).is_err()); + } + + #[test] + fn test_doubling() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p = 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 = p.double(params); + + let (x0, y0) = p.into_xy().unwrap(); + let (x1, y1) = p2.into_xy().unwrap(); + + let mut cs = TestConstraintSystem::::new(); + + let x = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(x0) + }).unwrap(); + let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(y0) + }).unwrap(); + + let p = MontgomeryPoint { + x: x, + y: y + }; + + let p2 = p.double(cs.namespace(|| "doubling"), params).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p2.x.get_value().unwrap() == x1); + assert!(p2.y.get_value().unwrap() == y1); + + cs.set("doubling/yprime/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate yprime")); + cs.set("doubling/yprime/num", y1); + assert!(cs.is_satisfied()); + + cs.set("doubling/xprime/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate xprime")); + cs.set("doubling/xprime/num", x1); + assert!(cs.is_satisfied()); + + cs.set("doubling/lambda/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate lambda")); + } + } +} diff --git a/src/circuit/num.rs b/src/circuit/num.rs new file mode 100644 index 0000000..f072a8e --- /dev/null +++ b/src/circuit/num.rs @@ -0,0 +1,164 @@ +use pairing::{ + Engine, + Field +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination +}; + +use super::{ + Assignment +}; + +pub struct AllocatedNum { + value: Option, + variable: Var +} + +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 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", + LinearCombination::zero() + self.variable, + LinearCombination::zero() + self.variable, + LinearCombination::zero() + 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() { + // TODO: add a more descriptive error to bellman + Err(SynthesisError::AssignmentMissing) + } else { + Ok(tmp.inverse().unwrap()) + } + })?; + + // Constrain a * inv = 1, which is only valid + // iff a has a multiplicative inverse, untrue + // for zero. + let one = cs.one(); + cs.enforce( + || "nonzero assertion constraint", + LinearCombination::zero() + self.variable, + LinearCombination::zero() + inv, + LinearCombination::zero() + one + ); + + Ok(()) + } + + pub fn get_value(&self) -> Option { + self.value + } + + pub fn get_variable(&self) -> Var { + self.variable + } +} + +#[cfg(test)] +mod test { + use pairing::bls12_381::{Bls12, Fr}; + use pairing::{Field, PrimeField}; + use ::circuit::test::*; + use super::{AllocatedNum}; + + #[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_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()); + } + } +} From dfd82439def52e2a3281ce39d740c7a92b814209 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 15 Dec 2017 14:12:38 -0700 Subject: [PATCH 012/168] Implementation of k-ary AND. --- src/circuit/boolean.rs | 374 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 362 insertions(+), 12 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 4d3bc3a..779c9e1 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -153,6 +153,84 @@ impl AllocatedBit { 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. + let one = cs.one(); + cs.enforce( + || "and not constraint", + LinearCombination::zero() + a.variable, + LinearCombination::zero() + one - b.variable, + LinearCombination::zero() + 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. + let one = cs.one(); + cs.enforce( + || "nor constraint", + LinearCombination::zero() + one - a.variable, + LinearCombination::zero() + one - b.variable, + LinearCombination::zero() + result_var + ); + + Ok(AllocatedBit { + variable: result_var, + value: result_value + }) + } } /// This is a boolean value which may be either a constant or @@ -208,6 +286,58 @@ impl Boolean { } } } + + /// 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)?)) + } + } + } + + pub fn kary_and( + mut cs: CS, + bits: &[Self] + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + assert!(bits.len() > 0); + let mut bits = bits.iter(); + + // TODO: optimize + let mut cur: Self = bits.next().unwrap().clone(); + + let mut i = 0; + while let Some(next) = bits.next() { + cur = Boolean::and(cs.namespace(|| format!("AND {}", i)), &cur, next)?; + + i += 1; + } + + Ok(cur) + } } impl From> for Boolean { @@ -245,7 +375,8 @@ mod test { 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(); - AllocatedBit::xor(&mut cs, &a, &b).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() }); @@ -266,7 +397,8 @@ mod test { 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(); - AllocatedBit::and(&mut cs, &a, &b).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() }); @@ -280,6 +412,50 @@ mod test { } } + #[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_boolean_negation() { let mut cs = TestConstraintSystem::::new(); @@ -327,18 +503,18 @@ mod test { } } + #[derive(Copy, Clone, Debug)] + enum OperandType { + True, + False, + AllocatedTrue, + AllocatedFalse, + NegatedAllocatedTrue, + NegatedAllocatedFalse + } + #[test] fn test_boolean_xor() { - #[derive(Copy, Clone)] - enum OperandType { - True, - False, - AllocatedTrue, - AllocatedFalse, - NegatedAllocatedTrue, - NegatedAllocatedFalse - } - let variants = [ OperandType::True, OperandType::False, @@ -473,4 +649,178 @@ mod test { } } } + + #[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_kary_and() { + // test different numbers of operands + for i in 1..15 { + // with every possible assignment for them + for mut b in 0..(1 << i) { + let mut cs = TestConstraintSystem::::new(); + + let mut expected = true; + + let mut bits = vec![]; + for j in 0..i { + expected &= b & 1 == 1; + + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", j)), + Some(b & 1 == 1) + ).unwrap())); + b >>= 1; + } + + let r = Boolean::kary_and(&mut cs, &bits).unwrap(); + + assert!(cs.is_satisfied()); + + match r { + Boolean::Is(ref r) => { + assert_eq!(r.value.unwrap(), expected); + }, + _ => unreachable!() + } + } + } + } } From d04c5acdb8e34ef5f035709d6c0a0f1e367e3bd4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 16 Dec 2017 16:01:13 -0700 Subject: [PATCH 013/168] Implement `enforce_in_field` and `enforce_nand` for Boolean. --- src/circuit/boolean.rs | 242 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 240 insertions(+), 2 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 779c9e1..c1cfd2d 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -1,6 +1,9 @@ use pairing::{ Engine, - Field + Field, + PrimeField, + PrimeFieldRepr, + BitIterator }; use bellman::{ @@ -338,6 +341,121 @@ impl Boolean { Ok(cur) } + + /// Asserts that at least one operand is false. + pub fn enforce_nand( + mut cs: CS, + bits: &[Self] + ) -> Result<(), SynthesisError> + where E: Engine, + CS: ConstraintSystem + { + let res = Self::kary_and(&mut cs, bits)?; + + // TODO: optimize + match res { + Boolean::Constant(false) => { + Ok(()) + }, + Boolean::Constant(true) => { + // TODO: more descriptive error + Err(SynthesisError::AssignmentMissing) + }, + Boolean::Is(ref res) => { + cs.enforce( + || "enforce nand", + LinearCombination::zero(), + LinearCombination::zero(), + LinearCombination::zero() + res.get_variable() + ); + + Ok(()) + }, + Boolean::Not(ref res) => { + let one = cs.one(); + cs.enforce( + || "enforce nand", + LinearCombination::zero(), + LinearCombination::zero(), + LinearCombination::zero() + one - res.get_variable() + ); + + Ok(()) + }, + } + } + + /// Asserts that this bit representation is "in + /// the field" when interpreted in big endian. + pub fn enforce_in_field( + mut cs: CS, + bits: &[Self] + ) -> Result<(), SynthesisError> + where E: Engine, + CS: ConstraintSystem + { + assert_eq!(bits.len(), F::NUM_BITS as usize); + + let mut a = bits.iter(); + + // b = char() - 1 + let mut b = F::char(); + b.sub_noborrow(&1.into()); + + // Runs of ones in r + let mut last_run = Boolean::::constant(true); + let mut current_run = vec![]; + + let mut found_one = false; + let mut run_i = 0; + let mut nand_i = 0; + for b in BitIterator::new(b) { + // Skip over unset bits at the beginning + found_one |= b; + if !found_one { + continue; + } + + let a = a.next().unwrap(); + + if b { + // This is part of a run of ones. + current_run.push(a.clone()); + } 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. + + current_run.push(last_run.clone()); + last_run = Self::kary_and( + cs.namespace(|| format!("run {}", run_i)), + ¤t_run + )?; + run_i += 1; + 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. + // + // Ergo, at least one of `last_run` and `a` must be false. + Self::enforce_nand( + cs.namespace(|| format!("nand {}", nand_i)), + &[last_run.clone(), a.clone()] + )?; + nand_i += 1; + } + } + + // We should always end in a "run" of zeros, because + // the characteristic is an odd prime. So, this should + // be empty. + assert_eq!(current_run.len(), 0); + + Ok(()) + } } impl From> for Boolean { @@ -348,9 +466,10 @@ impl From> for Boolean { #[cfg(test)] mod test { + use rand::{SeedableRng, Rand, XorShiftRng}; use bellman::{ConstraintSystem}; use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Field, PrimeField}; + use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator}; use ::circuit::test::*; use super::{AllocatedBit, Boolean}; @@ -789,6 +908,125 @@ mod test { } } + #[test] + fn test_enforce_in_field() { + { + let mut cs = TestConstraintSystem::::new(); + + let mut bits = vec![]; + for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + Some(b) + ).unwrap())); + } + + Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); + + assert!(!cs.is_satisfied()); + } + + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let r = Fr::rand(&mut rng); + let mut cs = TestConstraintSystem::::new(); + + let mut bits = vec![]; + for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + Some(b) + ).unwrap())); + } + + Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); + + assert!(cs.is_satisfied()); + } + + for _ in 0..1000 { + // Sample a random element not in the field + let r = loop { + let mut a = Fr::rand(&mut rng).into_repr(); + let b = Fr::rand(&mut rng).into_repr(); + + a.add_nocarry(&b); + // we're shaving off the high bit later + a.as_mut()[3] &= 0x7fffffffffffffff; + if Fr::from_repr(a).is_err() { + break a; + } + }; + + let mut cs = TestConstraintSystem::::new(); + + let mut bits = vec![]; + for (i, b) in BitIterator::new(r).skip(1).enumerate() { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + Some(b) + ).unwrap())); + } + + Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); + + assert!(!cs.is_satisfied()); + } + } + + #[test] + fn test_enforce_nand() { + { + let mut cs = TestConstraintSystem::::new(); + + Boolean::enforce_nand(&mut cs, &[Boolean::constant(false)]).is_ok(); + Boolean::enforce_nand(&mut cs, &[Boolean::constant(true)]).is_err(); + } + + for i in 1..5 { + // with every possible assignment for them + for mut b in 0..(1 << i) { + // with every possible negation + for mut n in 0..(1 << i) { + let mut cs = TestConstraintSystem::::new(); + + let mut expected = true; + + let mut bits = vec![]; + for j in 0..i { + expected &= b & 1 == 1; + + if n & 1 == 1 { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", j)), + Some(b & 1 == 1) + ).unwrap())); + } else { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", j)), + Some(b & 1 == 0) + ).unwrap()).not()); + } + + b >>= 1; + n >>= 1; + } + + let expected = !expected; + + Boolean::enforce_nand(&mut cs, &bits).unwrap(); + + if expected { + assert!(cs.is_satisfied()); + } else { + assert!(!cs.is_satisfied()); + } + } + } + } + } + #[test] fn test_kary_and() { // test different numbers of operands From 6c6a1935510adaf8e35f1509001321b45f94c99d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 16 Dec 2017 17:03:40 -0700 Subject: [PATCH 014/168] Implementation of `from_bits_strict` for `Num`. --- src/circuit/num.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 3 deletions(-) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index f072a8e..27858b4 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -1,6 +1,7 @@ use pairing::{ Engine, - Field + Field, + PrimeField }; use bellman::{ @@ -13,6 +14,10 @@ use super::{ Assignment }; +use super::boolean::{ + Boolean +}; + pub struct AllocatedNum { value: Option, variable: Var @@ -41,6 +46,75 @@ impl AllocatedNum { }) } + pub fn from_bits_strict( + mut cs: CS, + bits: &[Boolean] + ) -> Result + where CS: ConstraintSystem + { + assert_eq!(bits.len(), E::Fr::NUM_BITS as usize); + + Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, bits)?; + + let one = cs.one(); + let mut lc = LinearCombination::::zero(); + let mut coeff = E::Fr::one(); + let mut value = Some(E::Fr::zero()); + for bit in bits.iter().rev() { + match bit { + &Boolean::Constant(false) => {}, + &Boolean::Constant(true) => { + value.as_mut().map(|value| value.add_assign(&coeff)); + + lc = lc + (coeff, one); + }, + &Boolean::Is(ref bit) => { + match bit.get_value() { + Some(bit) => { + if bit { + value.as_mut().map(|value| value.add_assign(&coeff)); + } + }, + None => { + value = None; + } + } + + lc = lc + (coeff, bit.get_variable()); + }, + &Boolean::Not(ref bit) => { + match bit.get_value() { + Some(bit) => { + if !bit { + value.as_mut().map(|value| value.add_assign(&coeff)); + } + }, + None => { + value = None; + } + } + + lc = lc + (coeff, one) - (coeff, bit.get_variable()); + } + } + + coeff.double(); + } + + let num = Self::alloc(&mut cs, || value.get().map(|v| *v))?; + + lc = lc - num.get_variable(); + + cs.enforce( + || "packing constraint", + LinearCombination::zero(), + LinearCombination::zero(), + lc + ); + + Ok(num) + } + pub fn square( &self, mut cs: CS @@ -114,10 +188,13 @@ impl AllocatedNum { #[cfg(test)] mod test { + use rand::{SeedableRng, Rand, Rng, XorShiftRng}; + use bellman::{ConstraintSystem}; use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Field, PrimeField}; + use pairing::{Field, PrimeField, BitIterator}; use ::circuit::test::*; - use super::{AllocatedNum}; + use super::{AllocatedNum, Boolean}; + use super::super::boolean::AllocatedBit; #[test] fn test_allocated_num() { @@ -161,4 +238,55 @@ mod test { assert!(n.assert_nonzero(&mut cs).is_err()); } } + + #[test] + fn test_from_bits_strict() { + { + let mut cs = TestConstraintSystem::::new(); + + let mut bits = vec![]; + for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + Some(b) + ).unwrap())); + } + + let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap(); + assert!(num.value.unwrap().is_zero()); + assert!(!cs.is_satisfied()); + } + + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let r = Fr::rand(&mut rng); + let mut cs = TestConstraintSystem::::new(); + + let mut bits = vec![]; + for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { + let parity: bool = rng.gen(); + + if parity { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + Some(b) + ).unwrap())); + } else { + bits.push(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + Some(!b) + ).unwrap()).not()); + } + } + + let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(num.value.unwrap(), r); + assert_eq!(cs.get("num"), r); + + cs.set("num", Fr::rand(&mut rng)); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "packing constraint"); + } + } } From 068fbbc2be8e071e4eda3a1cc3438af13caec8e4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 17 Dec 2017 09:12:04 -0700 Subject: [PATCH 015/168] Implementation of `into_bits` for `Num`. --- src/circuit/boolean.rs | 3 ++ src/circuit/num.rs | 107 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index c1cfd2d..8a165e5 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -435,6 +435,9 @@ impl Boolean { current_run.truncate(0); } + // TODO: this could be optimized with a k-ary operation + // (all zeros are required in the run if last_run is zero) + // If `last_run` is true, `a` must be false, or it would // not be in the field. // diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 27858b4..2c244e3 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -1,7 +1,8 @@ use pairing::{ Engine, Field, - PrimeField + PrimeField, + BitIterator }; use bellman::{ @@ -15,7 +16,8 @@ use super::{ }; use super::boolean::{ - Boolean + Boolean, + AllocatedBit }; pub struct AllocatedNum { @@ -46,6 +48,67 @@ impl AllocatedNum { }) } + pub fn into_bits( + &self, + mut cs: CS + ) -> Result>, SynthesisError> + where CS: ConstraintSystem + { + let bit_values = match self.value { + Some(value) => { + let mut field_char = BitIterator::new(E::Fr::char()); + + let mut tmp = Vec::with_capacity(E::Fr::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(), E::Fr::NUM_BITS as usize); + + tmp + }, + None => { + vec![None; E::Fr::NUM_BITS as usize] + } + }; + + let mut bits = vec![]; + for (i, b) in bit_values.into_iter().enumerate() { + bits.push(AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + b + )?); + } + + let mut lc = LinearCombination::zero(); + let mut coeff = E::Fr::one(); + + for bit in bits.iter().rev() { + lc = lc + (coeff, bit.get_variable()); + + coeff.double(); + } + + lc = lc - self.variable; + + cs.enforce( + || "unpacking constraint", + LinearCombination::zero(), + LinearCombination::zero(), + lc + ); + + Ok(bits.into_iter().map(|b| Boolean::from(b)).collect()) + } + pub fn from_bits_strict( mut cs: CS, bits: &[Boolean] @@ -239,6 +302,46 @@ mod test { } } + #[test] + fn test_into_bits() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let r = Fr::rand(&mut rng); + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap(); + + let bits = n.into_bits(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + + for (b, a) in BitIterator::new(r.into_repr()).skip(1).zip(bits.iter()) { + 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()); + } + } + } + #[test] fn test_from_bits_strict() { { From eb8803f9ebf96f81f46a370b682ae9a32d0dac0d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 17 Dec 2017 09:31:33 -0700 Subject: [PATCH 016/168] Implementation of `into_bits_strict` for `Num`. --- src/circuit/num.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 2c244e3..73eca1a 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -48,6 +48,18 @@ impl AllocatedNum { }) } + pub fn into_bits_strict( + &self, + mut cs: CS + ) -> Result>, SynthesisError> + where CS: ConstraintSystem + { + let bits = self.into_bits(&mut cs)?; + Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?; + + Ok(bits) + } + pub fn into_bits( &self, mut cs: CS @@ -302,6 +314,35 @@ mod test { } } + #[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_strict(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + + // make the bit representation the characteristic + cs.set("bit 254/boolean", Fr::one()); + + // this makes the unpacking constraint fail + assert_eq!(cs.which_is_unsatisfied().unwrap(), "unpacking constraint"); + + // fix it by making the number zero (congruent to the characteristic) + cs.set("num", Fr::zero()); + + // and constraint is disturbed during enforce in field check + assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/AND 0/and constraint"); + cs.set("nand 121/AND 0/and result", Fr::one()); + + // now the nand should fail (enforce in field is working) + assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/enforce nand"); + } + #[test] fn test_into_bits() { let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); From 7d6a57661be9e23ead48b2dfcb58baefb70ed7e3 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 17 Dec 2017 10:07:00 -0700 Subject: [PATCH 017/168] Add Montgomery point interpretation. --- src/circuit/mont.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++ src/circuit/num.rs | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index e5837a9..1c06aef 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -28,6 +28,34 @@ pub struct MontgomeryPoint { } impl MontgomeryPoint { + pub fn interpret( + mut cs: CS, + x: &AllocatedNum, + y: &AllocatedNum, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // y^2 = x^3 + A.x^2 + x + + let x2 = x.square(cs.namespace(|| "x^2"))?; + let x3 = x2.mul(cs.namespace(|| "x^3"), x)?; + + cs.enforce( + || "on curve check", + LinearCombination::zero() + y.get_variable(), + LinearCombination::zero() + y.get_variable(), + LinearCombination::zero() + x3.get_variable() + + (*params.montgomery_a(), x2.get_variable()) + + x.get_variable() + ); + + Ok(MontgomeryPoint { + x: x.clone(), + y: y.clone() + }) + } + /// Performs an affine point doubling, not defined for /// the point of order two (0, 0). pub fn double( @@ -146,6 +174,54 @@ mod test { }; use super::{MontgomeryPoint, AllocatedNum}; + #[test] + fn test_interpret() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p = montgomery::Point::::rand(rng, ¶ms); + let (mut x, mut y) = p.into_xy().unwrap(); + + { + 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 = MontgomeryPoint::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); + + y.negate(); + cs.set("y/num", y); + assert!(cs.is_satisfied()); + x.negate(); + cs.set("x/num", x); + assert!(!cs.is_satisfied()); + } + + { + 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(); + + MontgomeryPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); + + assert_eq!(cs.which_is_unsatisfied().unwrap(), "on curve check"); + } + } + } + #[test] fn test_doubling_order_2() { let params = &JubjubBls12::new(); diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 73eca1a..bcdb31b 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -25,6 +25,15 @@ pub struct AllocatedNum { variable: Var } +impl Clone for AllocatedNum { + fn clone(&self) -> Self { + AllocatedNum { + value: self.value, + variable: self.variable + } + } +} + impl AllocatedNum { pub fn alloc( mut cs: CS, @@ -190,6 +199,38 @@ impl AllocatedNum { Ok(num) } + 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", + LinearCombination::zero() + self.variable, + LinearCombination::zero() + other.variable, + LinearCombination::zero() + var + ); + + Ok(AllocatedNum { + value: value, + variable: var + }) + } + pub fn square( &self, mut cs: CS @@ -294,6 +335,21 @@ mod test { 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_nonzero() { { From 635bd8104ea4ab79ac722a805cdd78af9b02bd30 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Wed, 13 Dec 2017 14:49:36 -0800 Subject: [PATCH 018/168] Add grouphash --- src/grouphash/mod.rs | 30 ++++++++++++++++++++++++++++++ src/lib.rs | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/grouphash/mod.rs diff --git a/src/grouphash/mod.rs b/src/grouphash/mod.rs new file mode 100644 index 0000000..8e69916 --- /dev/null +++ b/src/grouphash/mod.rs @@ -0,0 +1,30 @@ +// 64 zeros, substitute with random future determined string like a blockhash, or randomness beacom +const U: [u8; 64] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +// option to return None or point +fn grouphash(tag: &[u8], params: &E::Params) -> Option> { + // Check to see that scalar field is 255 bits + assert! (E::Fr::NUM_BITS == 255); + + // Perform hash, get random 32-byte string + let mut h = Blake2s::new_keyed(&[], 32); + h.process(&U); + h.process(tag); + let h = h.fixed_result(); + + // Take first unset first bit of hash + let sign = (h[0] >> 7) == 1; + h[0] &= 0b01111111; + + // cast to prime field representation + let mut x0 = ::Repr::default(); + x0.read_be(&h[..]).unwrap(); + + match E::Fr::from_repr(x0) { + Ok(x0) => { + let tmp = montgomery::Point::get_for_x(x0, sign, params).mul_by_cofactor(params); + if tmp == mongomery::Point.zero() { None } else { Some(tmp) }; + } + Err(_) => None + } +} diff --git a/src/lib.rs b/src/lib.rs index 552fe01..efce841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,4 @@ extern crate rand; pub mod jubjub; pub mod circuit; - +pub mod grouphash; From d143d3230a9a2b3a48d2d5a86cd2a69b52ae8efa Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 18 Dec 2017 11:00:10 -0700 Subject: [PATCH 019/168] Relocate grouphash implementation. --- src/group_hash.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/grouphash/mod.rs | 30 ------------------------------ src/lib.rs | 2 +- 3 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 src/group_hash.rs delete mode 100644 src/grouphash/mod.rs diff --git a/src/group_hash.rs b/src/group_hash.rs new file mode 100644 index 0000000..cc6ed4a --- /dev/null +++ b/src/group_hash.rs @@ -0,0 +1,44 @@ +use jubjub::*; +use pairing::*; +use blake2::{Blake2s}; +use digest::{FixedOutput, Input}; + +/// Produces an (x, y) pair (Montgomery) for 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], + params: &E::Params +) -> Option<(E::Fr, E::Fr)> +{ + // Check to see that scalar field is 255 bits + assert!(E::Fr::NUM_BITS == 255); + + // TODO: personalization/first block + let mut h = Blake2s::new_keyed(&[], 32); + h.process(tag); + let mut h = h.fixed_result().to_vec(); + assert!(h.len() == 32); + + // Take first/unset first bit of hash + let s = h[0] >> 7 == 1; // get s + h[0] &= 0b0111_1111; // unset s from h + + // cast to prime field representation + let mut x0 = ::Repr::default(); + x0.read_be(&h[..]).expect("hash is sufficiently large"); + + if let Ok(x0) = E::Fr::from_repr(x0) { + if let Some(p) = montgomery::Point::::get_for_x(x0, s, params) { + // Enter into the prime order subgroup + let p = p.mul_by_cofactor(params); + + p.into_xy() + } else { + None + } + } else { + None + } +} diff --git a/src/grouphash/mod.rs b/src/grouphash/mod.rs deleted file mode 100644 index 8e69916..0000000 --- a/src/grouphash/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -// 64 zeros, substitute with random future determined string like a blockhash, or randomness beacom -const U: [u8; 64] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - -// option to return None or point -fn grouphash(tag: &[u8], params: &E::Params) -> Option> { - // Check to see that scalar field is 255 bits - assert! (E::Fr::NUM_BITS == 255); - - // Perform hash, get random 32-byte string - let mut h = Blake2s::new_keyed(&[], 32); - h.process(&U); - h.process(tag); - let h = h.fixed_result(); - - // Take first unset first bit of hash - let sign = (h[0] >> 7) == 1; - h[0] &= 0b01111111; - - // cast to prime field representation - let mut x0 = ::Repr::default(); - x0.read_be(&h[..]).unwrap(); - - match E::Fr::from_repr(x0) { - Ok(x0) => { - let tmp = montgomery::Point::get_for_x(x0, sign, params).mul_by_cofactor(params); - if tmp == mongomery::Point.zero() { None } else { Some(tmp) }; - } - Err(_) => None - } -} diff --git a/src/lib.rs b/src/lib.rs index efce841..8dbcb47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,4 @@ extern crate rand; pub mod jubjub; pub mod circuit; -pub mod grouphash; +pub mod group_hash; From 1e56289f198d2218105e709b1ad0ef4caa591196 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 18 Dec 2017 11:34:15 -0700 Subject: [PATCH 020/168] Implementation of group hash in the circuit. --- src/circuit/boolean.rs | 52 ++++++++++++++ src/circuit/mont.rs | 152 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 200 insertions(+), 4 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 8a165e5..c13d9ae 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -249,6 +249,28 @@ pub enum Boolean { } impl Boolean { + pub fn enforce_equal( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result<(), SynthesisError> + where E: Engine, + CS: ConstraintSystem + { + // TODO: this is just a cheap hack + let c = Self::xor(&mut cs, a, b)?; + + Self::enforce_nand(&mut cs, &[c]) + } + + 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) + } + } + /// Construct a boolean from a known constant pub fn constant(b: bool) -> Self { Boolean::Constant(b) @@ -578,6 +600,36 @@ mod test { } } + #[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) + ); + } + } + } + } + } + #[test] fn test_boolean_negation() { let mut cs = TestConstraintSystem::::new(); diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 1c06aef..16e78a0 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -1,8 +1,7 @@ use pairing::{ Engine, Field, -// TODO -// PrimeField + PrimeField }; use bellman::{ @@ -16,10 +15,15 @@ use super::{ }; use super::num::AllocatedNum; +use super::boolean::{ + Boolean +}; +use super::blake2s::blake2s; use ::jubjub::{ JubjubEngine, - JubjubParams + JubjubParams, + montgomery }; pub struct MontgomeryPoint { @@ -28,6 +32,79 @@ pub struct MontgomeryPoint { } impl MontgomeryPoint { + pub fn group_hash( + mut cs: CS, + tag: &[Boolean], + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // This code is specialized for a field of this size + assert_eq!(E::Fr::NUM_BITS, 255); + + assert!(tag.len() % 8 == 0); + + // TODO: first block, personalization + // + // Perform BLAKE2s hash + let h = blake2s(cs.namespace(|| "blake2s"), tag)?; + + // Read the x-coordinate + let x = AllocatedNum::from_bits_strict( + cs.namespace(|| "read x coordinate"), + &h[1..] + )?; + + // Allocate the y-coordinate given the first bit + // of the hash as its parity ("sign bit"). + let y = AllocatedNum::alloc( + cs.namespace(|| "y-coordinate"), + || { + let s: bool = *h[0].get_value().get()?; + let x: E::Fr = *x.get_value().get()?; + let p = montgomery::Point::::get_for_x(x, s, params); + let p = p.get()?; + let (_, y) = p.into_xy().expect("can't be the point at infinity"); + Ok(y) + } + )?; + + // Unpack the y-coordinate + let ybits = y.into_bits_strict(cs.namespace(|| "y-coordinate unpacking"))?; + + // Enforce that the y-coordinate has the right sign + Boolean::enforce_equal( + cs.namespace(|| "correct sign constraint"), + &h[0], + &ybits[E::Fr::NUM_BITS as usize - 1] + )?; + + // interpret the result as a point on the curve + let mut p = Self::interpret( + cs.namespace(|| "point interpretation"), + &x, + &y, + params + )?; + + // Perform three doublings to move the point into the prime + // order subgroup. + for i in 0..3 { + // Assert the y-coordinate is nonzero (the doubling + // doesn't work for y=0). + p.y.assert_nonzero( + cs.namespace(|| format!("nonzero y-coordinate {}", i)) + )?; + + p = p.double( + cs.namespace(|| format!("doubling {}", i)), + params + )?; + } + + Ok(p) + } + pub fn interpret( mut cs: CS, x: &AllocatedNum, @@ -172,7 +249,74 @@ mod test { montgomery, JubjubBls12 }; - use super::{MontgomeryPoint, AllocatedNum}; + use super::{MontgomeryPoint, AllocatedNum, Boolean}; + use super::super::boolean::AllocatedBit; + use ::group_hash::group_hash; + + #[test] + fn test_group_hash() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let mut num_errs = 0; + let mut num_unsatisfied = 0; + let mut num_satisfied = 0; + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let mut tag_bytes = vec![]; + let mut tag = vec![]; + for i in 0..10 { + let mut byte = 0; + for j in 0..8 { + byte <<= 1; + let b: bool = rng.gen(); + if b { + byte |= 1; + } + tag.push(Boolean::from( + AllocatedBit::alloc( + cs.namespace(|| format!("bit {} {}", i, j)), + Some(b) + ).unwrap() + )); + } + tag_bytes.push(byte); + } + + let p = MontgomeryPoint::group_hash( + cs.namespace(|| "gh"), + &tag, + params + ); + + let expected = group_hash::(&tag_bytes, params); + + if p.is_err() { + assert!(expected.is_none()); + num_errs += 1; + } else { + if !cs.is_satisfied() { + assert!(expected.is_none()); + num_unsatisfied += 1; + } else { + let p = p.unwrap(); + let (x, y) = expected.unwrap(); + + assert_eq!(p.x.get_value().unwrap(), x); + assert_eq!(p.y.get_value().unwrap(), y); + + num_satisfied += 1; + } + } + } + + assert_eq!( + (num_errs, num_unsatisfied, num_satisfied), + (47, 4, 49) + ); + } #[test] fn test_interpret() { From 031ae638ce459657a13ea51cfeea3704aee7f2cd Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 18 Dec 2017 22:08:57 -0700 Subject: [PATCH 021/168] Tidy up the codebase (TODOs into github issues). --- src/circuit/boolean.rs | 7 ------- src/circuit/mont.rs | 3 --- src/circuit/num.rs | 1 - src/circuit/uint32.rs | 1 - src/group_hash.rs | 1 - src/jubjub/fs.rs | 16 ---------------- src/jubjub/tests.rs | 11 +++++++++-- 7 files changed, 9 insertions(+), 31 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index c13d9ae..3575cf2 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -257,7 +257,6 @@ impl Boolean { where E: Engine, CS: ConstraintSystem { - // TODO: this is just a cheap hack let c = Self::xor(&mut cs, a, b)?; Self::enforce_nand(&mut cs, &[c]) @@ -351,7 +350,6 @@ impl Boolean { assert!(bits.len() > 0); let mut bits = bits.iter(); - // TODO: optimize let mut cur: Self = bits.next().unwrap().clone(); let mut i = 0; @@ -374,13 +372,11 @@ impl Boolean { { let res = Self::kary_and(&mut cs, bits)?; - // TODO: optimize match res { Boolean::Constant(false) => { Ok(()) }, Boolean::Constant(true) => { - // TODO: more descriptive error Err(SynthesisError::AssignmentMissing) }, Boolean::Is(ref res) => { @@ -457,9 +453,6 @@ impl Boolean { current_run.truncate(0); } - // TODO: this could be optimized with a k-ary operation - // (all zeros are required in the run if last_run is zero) - // If `last_run` is true, `a` must be false, or it would // not be in the field. // diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 16e78a0..526111a 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -44,8 +44,6 @@ impl MontgomeryPoint { assert!(tag.len() % 8 == 0); - // TODO: first block, personalization - // // Perform BLAKE2s hash let h = blake2s(cs.namespace(|| "blake2s"), tag)?; @@ -164,7 +162,6 @@ impl MontgomeryPoint { Ok(t0) }, None => { - // TODO: add a more descriptive error to bellman Err(SynthesisError::AssignmentMissing) } } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index bcdb31b..aeda7c5 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -272,7 +272,6 @@ impl AllocatedNum { let tmp = *self.value.get()?; if tmp.is_zero() { - // TODO: add a more descriptive error to bellman Err(SynthesisError::AssignmentMissing) } else { Ok(tmp.inverse().unwrap()) diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index 7954538..7f93879 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -180,7 +180,6 @@ impl UInt32 { }) } - // TODO: could optimize /// Perform modular addition of several `UInt32` objects. pub fn addmany( mut cs: CS, diff --git a/src/group_hash.rs b/src/group_hash.rs index cc6ed4a..a194d94 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -15,7 +15,6 @@ pub fn group_hash( // Check to see that scalar field is 255 bits assert!(E::Fr::NUM_BITS == 255); - // TODO: personalization/first block let mut h = Blake2s::new_keyed(&[], 32); h.process(tag); let mut h = h.fixed_result().to_vec(); diff --git a/src/jubjub/fs.rs b/src/jubjub/fs.rs index 888660e..2cf6f98 100644 --- a/src/jubjub/fs.rs +++ b/src/jubjub/fs.rs @@ -1222,19 +1222,3 @@ fn test_fs_root_of_unity() { ); assert!(Fs::multiplicative_generator().sqrt().is_none()); } - -// TODO -/* -#[test] -fn fr_field_tests() { - ::tests::field::random_field_tests::(); - ::tests::field::random_sqrt_tests::(); - ::tests::field::random_frobenius_tests::(Fr::char(), 13); - ::tests::field::from_str_tests::(); -} - -#[test] -fn fr_repr_tests() { - ::tests::repr::random_repr_tests::(); -} -*/ diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index c26bbe7..a105f32 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -1,8 +1,15 @@ -// TODO -use super::*; +use super::{ + JubjubEngine, + JubjubParams, + PrimeOrder, + montgomery, + edwards +}; use pairing::{ Field, + PrimeField, + SqrtField, LegendreSymbol }; From 87548f3d1d54ae9dbecb858f9aa7bc20ab187d3c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 02:57:34 -0700 Subject: [PATCH 022/168] Implementation of Montgomery point addition in the circuit. --- src/circuit/mont.rs | 188 +++++++++++++++++++++++++++++++++++++++++++- src/group_hash.rs | 8 +- 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 526111a..8f922df 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -103,6 +103,21 @@ impl MontgomeryPoint { Ok(p) } + /// 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: AllocatedNum, + y: AllocatedNum + ) -> Self + { + MontgomeryPoint { + x: x, + y: y + } + } + pub fn interpret( mut cs: CS, x: &AllocatedNum, @@ -131,6 +146,99 @@ impl MontgomeryPoint { }) } + /// 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 => { + // TODO: add more descriptive error + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + cs.enforce( + || "evaluate lambda", + LinearCombination::::zero() + other.x.get_variable() + - self.x.get_variable(), + + LinearCombination::zero() + lambda.get_variable(), + + LinearCombination::::zero() + other.y.get_variable() + - self.y.get_variable() + ); + + // 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", + LinearCombination::zero() + lambda.get_variable(), + LinearCombination::zero() + lambda.get_variable(), + LinearCombination::::zero() + (*params.montgomery_a(), one) + + self.x.get_variable() + + other.x.get_variable() + + 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", + LinearCombination::zero() + self.x.get_variable() + - xprime.get_variable(), + + LinearCombination::zero() + lambda.get_variable(), + + LinearCombination::::zero() + yprime.get_variable() + + self.y.get_variable() + ); + + Ok(MontgomeryPoint { + x: xprime, + y: yprime + }) + } + /// Performs an affine point doubling, not defined for /// the point of order two (0, 0). pub fn double( @@ -299,7 +407,7 @@ mod test { num_unsatisfied += 1; } else { let p = p.unwrap(); - let (x, y) = expected.unwrap(); + let (x, y) = expected.unwrap().into_xy().unwrap(); assert_eq!(p.x.get_value().unwrap(), x); assert_eq!(p.y.get_value().unwrap(), y); @@ -384,6 +492,84 @@ mod test { assert!(p.double(&mut cs, params).is_err()); } + #[test] + fn test_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, + y: num_y0 + }; + + let p2 = MontgomeryPoint { + 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); + + 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")); + } + } + #[test] fn test_doubling() { let params = &JubjubBls12::new(); diff --git a/src/group_hash.rs b/src/group_hash.rs index a194d94..2b53972 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -10,7 +10,7 @@ use digest::{FixedOutput, Input}; pub fn group_hash( tag: &[u8], params: &E::Params -) -> Option<(E::Fr, E::Fr)> +) -> Option> { // Check to see that scalar field is 255 bits assert!(E::Fr::NUM_BITS == 255); @@ -33,7 +33,11 @@ pub fn group_hash( // Enter into the prime order subgroup let p = p.mul_by_cofactor(params); - p.into_xy() + if p != montgomery::Point::zero() { + Some(p) + } else { + None + } } else { None } From e1ab3be3ccb0304eba5eece71fef79bdfebca889 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 03:10:58 -0700 Subject: [PATCH 023/168] Implementation of conditional negation for AllocatedNum. --- src/circuit/boolean.rs | 19 ++++++ src/circuit/num.rs | 134 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 3575cf2..6b5b9cd 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -270,6 +270,25 @@ impl Boolean { } } + pub fn lc(&self, one: Var, 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) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index aeda7c5..cce455c 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -292,6 +292,39 @@ impl AllocatedNum { Ok(()) } + pub fn conditionally_negate( + &self, + mut cs: CS, + condition: &Boolean + ) -> Result + where CS: ConstraintSystem + { + let r = Self::alloc( + cs.namespace(|| "conditional negation result"), + || { + let mut tmp = *self.value.get()?; + if *condition.get_value().get()? { + tmp.negate(); + } + Ok(tmp) + } + )?; + + // (1-c)(x) + (c)(-x) = r + // x - 2cx = r + // (2x) * (c) = x - r + + let one = cs.one(); + cs.enforce( + || "conditional negation", + LinearCombination::zero() + self.variable + self.variable, + condition.lc(one, E::Fr::one()), + LinearCombination::zero() + self.variable - r.variable + ); + + Ok(r) + } + pub fn get_value(&self) -> Option { self.value } @@ -349,6 +382,107 @@ mod test { assert!(!cs.is_satisfied()); } + #[test] + fn test_num_conditional_negation() { + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::constant(true); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + let mut negone = Fr::one(); + negone.negate(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == negone); + assert!(n2.value.unwrap() == negone); + cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); + assert!(!cs.is_satisfied()); + } + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::constant(false); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == Fr::one()); + assert!(n2.value.unwrap() == Fr::one()); + cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); + assert!(!cs.is_satisfied()); + } + + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(true)).unwrap() + ); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + let mut negone = Fr::one(); + negone.negate(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == negone); + assert!(n2.value.unwrap() == negone); + cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); + assert!(!cs.is_satisfied()); + } + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(false)).unwrap() + ); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == Fr::one()); + assert!(n2.value.unwrap() == Fr::one()); + cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); + assert!(!cs.is_satisfied()); + } + + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(false)).unwrap() + ).not(); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + let mut negone = Fr::one(); + negone.negate(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == negone); + assert!(n2.value.unwrap() == negone); + cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); + assert!(!cs.is_satisfied()); + } + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(true)).unwrap() + ).not(); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == Fr::one()); + assert!(n2.value.unwrap() == Fr::one()); + cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); + assert!(!cs.is_satisfied()); + } + } + #[test] fn test_num_nonzero() { { From 8e3bef80a43b0b7d22568f9e10923f967d7b9ec1 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 03:13:42 -0700 Subject: [PATCH 024/168] Bump version of bellman. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d4c111f..1831943 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ features = ["expose-arith"] rand = "0.3" blake2 = "0.7" digest = "0.7" -bellman = "0.0.6" +bellman = "0.0.7" [features] default = ["u128-support"] From 07f2e553a7980e49b3ceacce96914aca647ccf07 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 11:51:00 -0700 Subject: [PATCH 025/168] Implement twisted Edwards point conversion and addition in the circuit. --- src/circuit/mont.rs | 321 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 319 insertions(+), 2 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 8f922df..d23ea77 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -26,12 +26,216 @@ use ::jubjub::{ montgomery }; +pub struct EdwardsPoint { + x: AllocatedNum, + y: AllocatedNum +} + +impl EdwardsPoint { + /// This extracts the x-coordinate, which is an injective + /// encoding for elements of the prime order subgroup. + pub fn into_num(&self) -> AllocatedNum { + self.x.clone() + } + + /// 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", + LinearCombination::::zero() + self.x.get_variable() + + self.y.get_variable(), + LinearCombination::::zero() + other.x.get_variable() + + other.y.get_variable(), + LinearCombination::::zero() + 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", + LinearCombination::::zero() + (*params.edwards_d(), a.get_variable()), + LinearCombination::::zero() + b.get_variable(), + LinearCombination::::zero() + 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 => { + // TODO: add more descriptive error + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + let one = cs.one(); + cs.enforce( + || "x3 computation", + LinearCombination::::zero() + one + c.get_variable(), + LinearCombination::::zero() + x3.get_variable(), + LinearCombination::::zero() + 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 => { + // TODO: add more descriptive error + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + cs.enforce( + || "y3 computation", + LinearCombination::::zero() + one - c.get_variable(), + LinearCombination::::zero() + y3.get_variable(), + LinearCombination::::zero() + u.get_variable() + - a.get_variable() + - b.get_variable() + ); + + Ok(EdwardsPoint { + x: x3, + y: y3 + }) + } +} + pub struct MontgomeryPoint { x: AllocatedNum, y: AllocatedNum } 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 => { + // TODO: add more descriptive error + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + cs.enforce( + || "u computation", + LinearCombination::::zero() + self.y.get_variable(), + LinearCombination::::zero() + u.get_variable(), + LinearCombination::::zero() + (*params.scale(), self.x.get_variable()) + ); + + // 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 => { + // TODO: add more descriptive error + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + let one = cs.one(); + cs.enforce( + || "v computation", + LinearCombination::::zero() + self.x.get_variable() + + one, + LinearCombination::::zero() + v.get_variable(), + LinearCombination::::zero() + self.x.get_variable() + - one, + ); + + Ok(EdwardsPoint { + x: u, + y: v + }) + } + pub fn group_hash( mut cs: CS, tag: &[Boolean], @@ -352,12 +556,57 @@ mod test { use ::circuit::test::*; use ::jubjub::{ montgomery, + edwards, JubjubBls12 }; - use super::{MontgomeryPoint, AllocatedNum, Boolean}; + use super::{ + MontgomeryPoint, + EdwardsPoint, + AllocatedNum, + Boolean + }; use super::super::boolean::AllocatedBit; use ::group_hash::group_hash; + #[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, numy); + + 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_group_hash() { let params = &JubjubBls12::new(); @@ -493,7 +742,75 @@ mod test { } #[test] - fn test_addition() { + 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_montgomery_addition() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); From e9c9618ef48fded5dc714bb0923e12d7e981e1b0 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 28 Dec 2017 11:06:05 -0700 Subject: [PATCH 026/168] Implement pedersen hashes inside and outside of the circuit. --- src/circuit/mod.rs | 3 +- src/circuit/mont.rs | 5 +- src/circuit/pedersen_hash.rs | 310 +++++++++++++++++++++++++++++++++++ src/jubjub/mod.rs | 69 +++++++- src/jubjub/tests.rs | 30 ++++ src/lib.rs | 1 + src/pedersen_hash.rs | 68 ++++++++ 7 files changed, 480 insertions(+), 6 deletions(-) create mode 100644 src/circuit/pedersen_hash.rs create mode 100644 src/pedersen_hash.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 9849599..c40fefe 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -5,7 +5,8 @@ pub mod boolean; pub mod uint32; pub mod blake2s; pub mod num; -pub mod mont; +pub mod mont; // TODO: rename +pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index d23ea77..9ff32fe 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -27,8 +27,9 @@ use ::jubjub::{ }; pub struct EdwardsPoint { - x: AllocatedNum, - y: AllocatedNum + // TODO: make these not pub + pub x: AllocatedNum, + pub y: AllocatedNum } impl EdwardsPoint { diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs new file mode 100644 index 0000000..00146d5 --- /dev/null +++ b/src/circuit/pedersen_hash.rs @@ -0,0 +1,310 @@ +use pairing::{Engine, Field}; +use super::*; +use super::mont::{ + MontgomeryPoint, + EdwardsPoint +}; +use super::num::AllocatedNum; +use super::boolean::Boolean; +use ::jubjub::*; +use bellman::{ + ConstraintSystem, + LinearCombination +}; + +// 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); + } + } + } +} + +pub fn pedersen_hash( + mut cs: CS, + bits: &[Boolean], + params: &E::Params +) -> Result, SynthesisError> + where CS: ConstraintSystem +{ + let mut edwards_result = None; + let mut bits = 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; + } + + // TODO: maybe assert bits.len() > 0 + Ok(edwards_result.unwrap()) +} + +/// Performs a 3-bit window table lookup, where +/// one of the bits is a sign bit. +fn lookup3_xy_with_conditional_negation( + 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(), 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 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) + } + )?; + + 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); + + cs.enforce( + || "x-coordinate lookup", + LinearCombination::::zero() + (x_coeffs[0b01], one) + + &bits[1].lc::(one, x_coeffs[0b11]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + res_x.get_variable() + - (x_coeffs[0b00], one) + - &bits[1].lc::(one, x_coeffs[0b10]) + ); + + cs.enforce( + || "y-coordinate lookup", + LinearCombination::::zero() + (y_coeffs[0b01], one) + + &bits[1].lc::(one, y_coeffs[0b11]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + res_y.get_variable() + - (y_coeffs[0b00], one) + - &bits[1].lc::(one, y_coeffs[0b10]) + ); + + let final_y = res_y.conditionally_negate(&mut cs, &bits[2])?; + + Ok((res_x, final_y)) +} + +#[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_pedersen_hash() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubBls12::new(); + + for length in 1..1000 { + 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"), + &input_bools, + params + ).unwrap(); + + assert!(cs.is_satisfied()); + + let expected = ::pedersen_hash::pedersen_hash::( + input.into_iter(), + params + ).into_xy(); + + assert_eq!(res.x.get_value().unwrap(), expected.0); + assert_eq!(res.y.get_value().unwrap(), expected.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/src/jubjub/mod.rs b/src/jubjub/mod.rs index d281b96..40cfd3c 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -21,6 +21,8 @@ use pairing::{ SqrtField }; +use super::group_hash::group_hash; + use pairing::bls12_381::{ Bls12, Fr @@ -42,6 +44,9 @@ pub trait JubjubParams: Sized { fn montgomery_a(&self) -> &E::Fr; fn montgomery_2a(&self) -> &E::Fr; fn scale(&self) -> &E::Fr; + fn pedersen_hash_generators(&self) -> &[edwards::Point]; + fn pedersen_hash_chunks_per_generator(&self) -> usize; + fn pedersen_circuit_generators(&self) -> &[Vec>]; } pub enum Unknown { } @@ -58,7 +63,9 @@ pub struct JubjubBls12 { edwards_d: Fr, montgomery_a: Fr, montgomery_2a: Fr, - scale: Fr + scale: Fr, + pedersen_hash_generators: Vec>, + pedersen_circuit_generators: Vec>> } impl JubjubParams for JubjubBls12 { @@ -66,6 +73,15 @@ impl JubjubParams for JubjubBls12 { 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_chunks_per_generator(&self) -> usize { + 62 + } + fn pedersen_circuit_generators(&self) -> &[Vec>] { + &self.pedersen_circuit_generators + } } impl JubjubBls12 { @@ -74,7 +90,7 @@ impl JubjubBls12 { let mut montgomery_2a = montgomery_a; montgomery_2a.double(); - JubjubBls12 { + let mut tmp = JubjubBls12 { // d = -(10240/10241) edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(), // A = 40962 @@ -82,8 +98,55 @@ impl JubjubBls12 { // 2A = 2.A montgomery_2a: montgomery_2a, // scaling factor = sqrt(4 / (a - d)) - scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap() + scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(), + + pedersen_hash_generators: vec![], + pedersen_circuit_generators: vec![] + }; + + { + let mut cur = 0; + let mut pedersen_hash_generators = vec![]; + + // TODO: pre-generate the right amount + while pedersen_hash_generators.len() < 10 { + let gh = group_hash(&[cur], &tmp); + cur += 1; + + if let Some(gh) = gh { + pedersen_hash_generators.push(edwards::Point::from_montgomery(&gh, &tmp)); + } + } + + tmp.pedersen_hash_generators = pedersen_hash_generators; } + + { + let mut pedersen_circuit_generators = vec![]; + + for mut gen in tmp.pedersen_hash_generators.iter().cloned() { + let mut gen = montgomery::Point::from_edwards(&gen, &tmp); + let mut windows = vec![]; + for _ in 0..tmp.pedersen_hash_chunks_per_generator() { + let mut coeffs = vec![]; + let mut g = gen.clone(); + for _ in 0..4 { + coeffs.push(g.into_xy().expect("cannot produce O")); + g = g.add(&gen, &tmp); + } + windows.push(coeffs); + + for _ in 0..4 { + gen = gen.double(&tmp); + } + } + pedersen_circuit_generators.push(windows); + } + + tmp.pedersen_circuit_generators = pedersen_circuit_generators; + } + + tmp } } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index a105f32..749b4cf 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -9,6 +9,7 @@ use super::{ use pairing::{ Field, PrimeField, + PrimeFieldRepr, SqrtField, LegendreSymbol }; @@ -311,4 +312,33 @@ fn test_jubjub_params(params: &E::Params) { 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::Fr::one().into_repr(); + + let mut pacc = E::Fr::zero().into_repr(); + let mut nacc = E::Fr::char(); + + for _ in 0..params.pedersen_hash_chunks_per_generator() + { + // tmp = cur * 4 + let mut tmp = cur; + tmp.mul2(); + tmp.mul2(); + + assert_eq!(pacc.add_nocarry(&tmp), false); + assert_eq!(nacc.sub_noborrow(&tmp), false); + + assert!(pacc < E::Fr::char()); + assert!(pacc < nacc); + + // cur = cur * 16 + for _ in 0..4 { + cur.mul2(); + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index 8dbcb47..6e45309 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,3 +7,4 @@ extern crate rand; pub mod jubjub; pub mod circuit; pub mod group_hash; +pub mod pedersen_hash; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs new file mode 100644 index 0000000..9885b25 --- /dev/null +++ b/src/pedersen_hash.rs @@ -0,0 +1,68 @@ +use jubjub::*; +use pairing::*; + +pub fn pedersen_hash( + bits: I, + params: &E::Params +) -> edwards::Point + where I: IntoIterator, + E: JubjubEngine +{ + let mut bits = bits.into_iter(); + + let mut result = edwards::Point::zero(); + let mut generators = params.pedersen_hash_generators().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; + } + + // TODO: use wNAF or something + let mut tmp = generators.next().expect("we don't have enough generators").clone(); + tmp = tmp.mul(acc, params); + result = result.add(&tmp, params); + } + + result +} From 849f330441d45af55f444d73d5d1ed445f7a92f0 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 28 Dec 2017 12:00:22 -0700 Subject: [PATCH 027/168] Add test to monitor the number of constraints consumed by the pedersen hash (in the context of a merkle tree). --- src/circuit/pedersen_hash.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 00146d5..0989d84 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -206,6 +206,31 @@ mod test { 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"), + &input_bools, + params + ).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 1539); + } #[test] fn test_pedersen_hash() { From 6841763ae70b9f91d440856f4a160d116c9edf6c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 28 Dec 2017 16:10:19 -0700 Subject: [PATCH 028/168] Add TODO for improving lookup performance. --- src/circuit/pedersen_hash.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 0989d84..c05d617 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -131,6 +131,10 @@ fn lookup3_xy_with_conditional_negation( ) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> where CS: ConstraintSystem { + // TODO: This can be made into a 2-constraint lookup + // if it can return linear combinations rather than + // allocated numbers. + assert_eq!(bits.len(), 3); assert_eq!(coords.len(), 4); From 42514e7c476126fc8bbf875b7add64ec359c39af Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 29 Jan 2018 06:06:10 -0700 Subject: [PATCH 029/168] Remove TODO's in favor of github issues. --- src/circuit/mod.rs | 2 +- src/circuit/mont.rs | 6 ------ src/circuit/pedersen_hash.rs | 8 +++----- src/jubjub/mod.rs | 1 - src/pedersen_hash.rs | 1 - 5 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index c40fefe..1141d66 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -5,7 +5,7 @@ pub mod boolean; pub mod uint32; pub mod blake2s; pub mod num; -pub mod mont; // TODO: rename +pub mod mont; pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 9ff32fe..f8b5c61 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -27,7 +27,6 @@ use ::jubjub::{ }; pub struct EdwardsPoint { - // TODO: make these not pub pub x: AllocatedNum, pub y: AllocatedNum } @@ -107,7 +106,6 @@ impl EdwardsPoint { Ok(t0) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } @@ -138,7 +136,6 @@ impl EdwardsPoint { Ok(t0) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } @@ -188,7 +185,6 @@ impl MontgomeryPoint { Ok(t0) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } @@ -215,7 +211,6 @@ impl MontgomeryPoint { Ok(t0) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } @@ -375,7 +370,6 @@ impl MontgomeryPoint { Ok(n) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index c05d617..3b0dc45 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -42,6 +42,9 @@ pub fn pedersen_hash( ) -> Result, SynthesisError> where CS: ConstraintSystem { + // Unnecessary if forced personalization is introduced + assert!(bits.len() > 0); + let mut edwards_result = None; let mut bits = bits.iter(); let mut segment_generators = params.pedersen_circuit_generators().iter(); @@ -118,7 +121,6 @@ pub fn pedersen_hash( segment_i += 1; } - // TODO: maybe assert bits.len() > 0 Ok(edwards_result.unwrap()) } @@ -131,10 +133,6 @@ fn lookup3_xy_with_conditional_negation( ) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> where CS: ConstraintSystem { - // TODO: This can be made into a 2-constraint lookup - // if it can return linear combinations rather than - // allocated numbers. - assert_eq!(bits.len(), 3); assert_eq!(coords.len(), 4); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 40cfd3c..34859ce 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -108,7 +108,6 @@ impl JubjubBls12 { let mut cur = 0; let mut pedersen_hash_generators = vec![]; - // TODO: pre-generate the right amount while pedersen_hash_generators.len() < 10 { let gh = group_hash(&[cur], &tmp); cur += 1; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 9885b25..0bbf7a9 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -58,7 +58,6 @@ pub fn pedersen_hash( break; } - // TODO: use wNAF or something let mut tmp = generators.next().expect("we don't have enough generators").clone(); tmp = tmp.mul(acc, params); result = result.add(&tmp, params); From bfda59f80b84adc02e611204ff7e61d8e83bb276 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 29 Jan 2018 07:24:59 -0700 Subject: [PATCH 030/168] Remove group hash implementation from inside the circuit, as it is no longer necessary. --- src/circuit/mont.rs | 151 +------------------------------------------- 1 file changed, 3 insertions(+), 148 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index f8b5c61..717984e 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -1,7 +1,6 @@ use pairing::{ Engine, - Field, - PrimeField + Field }; use bellman::{ @@ -15,15 +14,10 @@ use super::{ }; use super::num::AllocatedNum; -use super::boolean::{ - Boolean -}; -use super::blake2s::blake2s; use ::jubjub::{ JubjubEngine, - JubjubParams, - montgomery + JubjubParams }; pub struct EdwardsPoint { @@ -232,77 +226,6 @@ impl MontgomeryPoint { }) } - pub fn group_hash( - mut cs: CS, - tag: &[Boolean], - params: &E::Params - ) -> Result - where CS: ConstraintSystem - { - // This code is specialized for a field of this size - assert_eq!(E::Fr::NUM_BITS, 255); - - assert!(tag.len() % 8 == 0); - - // Perform BLAKE2s hash - let h = blake2s(cs.namespace(|| "blake2s"), tag)?; - - // Read the x-coordinate - let x = AllocatedNum::from_bits_strict( - cs.namespace(|| "read x coordinate"), - &h[1..] - )?; - - // Allocate the y-coordinate given the first bit - // of the hash as its parity ("sign bit"). - let y = AllocatedNum::alloc( - cs.namespace(|| "y-coordinate"), - || { - let s: bool = *h[0].get_value().get()?; - let x: E::Fr = *x.get_value().get()?; - let p = montgomery::Point::::get_for_x(x, s, params); - let p = p.get()?; - let (_, y) = p.into_xy().expect("can't be the point at infinity"); - Ok(y) - } - )?; - - // Unpack the y-coordinate - let ybits = y.into_bits_strict(cs.namespace(|| "y-coordinate unpacking"))?; - - // Enforce that the y-coordinate has the right sign - Boolean::enforce_equal( - cs.namespace(|| "correct sign constraint"), - &h[0], - &ybits[E::Fr::NUM_BITS as usize - 1] - )?; - - // interpret the result as a point on the curve - let mut p = Self::interpret( - cs.namespace(|| "point interpretation"), - &x, - &y, - params - )?; - - // Perform three doublings to move the point into the prime - // order subgroup. - for i in 0..3 { - // Assert the y-coordinate is nonzero (the doubling - // doesn't work for y=0). - p.y.assert_nonzero( - cs.namespace(|| format!("nonzero y-coordinate {}", i)) - )?; - - p = p.double( - cs.namespace(|| format!("doubling {}", i)), - params - )?; - } - - Ok(p) - } - /// Interprets an (x, y) pair as a point /// in Montgomery, does not check that it's /// on the curve. Useful for constants and @@ -557,11 +480,8 @@ mod test { use super::{ MontgomeryPoint, EdwardsPoint, - AllocatedNum, - Boolean + AllocatedNum, }; - use super::super::boolean::AllocatedBit; - use ::group_hash::group_hash; #[test] fn test_into_edwards() { @@ -602,71 +522,6 @@ mod test { } } - #[test] - fn test_group_hash() { - let params = &JubjubBls12::new(); - let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let mut num_errs = 0; - let mut num_unsatisfied = 0; - let mut num_satisfied = 0; - - for _ in 0..100 { - let mut cs = TestConstraintSystem::::new(); - - let mut tag_bytes = vec![]; - let mut tag = vec![]; - for i in 0..10 { - let mut byte = 0; - for j in 0..8 { - byte <<= 1; - let b: bool = rng.gen(); - if b { - byte |= 1; - } - tag.push(Boolean::from( - AllocatedBit::alloc( - cs.namespace(|| format!("bit {} {}", i, j)), - Some(b) - ).unwrap() - )); - } - tag_bytes.push(byte); - } - - let p = MontgomeryPoint::group_hash( - cs.namespace(|| "gh"), - &tag, - params - ); - - let expected = group_hash::(&tag_bytes, params); - - if p.is_err() { - assert!(expected.is_none()); - num_errs += 1; - } else { - if !cs.is_satisfied() { - assert!(expected.is_none()); - num_unsatisfied += 1; - } else { - let p = p.unwrap(); - let (x, y) = expected.unwrap().into_xy().unwrap(); - - assert_eq!(p.x.get_value().unwrap(), x); - assert_eq!(p.y.get_value().unwrap(), y); - - num_satisfied += 1; - } - } - } - - assert_eq!( - (num_errs, num_unsatisfied, num_satisfied), - (47, 4, 49) - ); - } - #[test] fn test_interpret() { let params = &JubjubBls12::new(); From 55598e4d4f9923eba3939d022466d16b46074144 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 29 Jan 2018 08:32:06 -0700 Subject: [PATCH 031/168] Define get_for_y for twisted Edwards points. --- src/jubjub/edwards.rs | 87 +++++++++++++++++++++++++------------------ src/jubjub/tests.rs | 39 +++++++++++++++++++ 2 files changed, 89 insertions(+), 37 deletions(-) diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index bf9b3f4..130fd59 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -81,6 +81,53 @@ impl PartialEq for Point { } impl Point { + 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 pub fn mul_by_cofactor(&self, params: &E::Params) -> Point { @@ -94,44 +141,10 @@ impl Point { pub fn rand(rng: &mut R, params: &E::Params) -> Self { loop { - // given an x on the curve, y^2 = (1 + x^2) / (1 - dx^2) - let x: E::Fr = rng.gen(); - let mut x2 = x; - x2.square(); + let y: E::Fr = rng.gen(); - let mut num = E::Fr::one(); - num.add_assign(&x2); - - x2.mul_assign(params.edwards_d()); - - let mut den = E::Fr::one(); - den.sub_assign(&x2); - - match den.inverse() { - Some(invden) => { - num.mul_assign(&invden); - - match num.sqrt() { - Some(mut y) => { - if y.into_repr().is_odd() != rng.gen() { - y.negate(); - } - - let mut t = x; - t.mul_assign(&y); - - return Point { - x: x, - y: y, - t: t, - z: E::Fr::one(), - _marker: PhantomData - } - }, - None => {} - } - }, - None => {} + if let Some(p) = Self::get_for_y(y, rng.gen(), params) { + return p; } } } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index 749b4cf..82b8eb2 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -20,6 +20,7 @@ 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); @@ -225,6 +226,25 @@ fn test_identities(params: &E::Params) { } } +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_rand(params: &E::Params) { let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -288,6 +308,25 @@ fn test_jubjub_params(params: &E::Params) { 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(); From f00e8a8292cdfea69f8bdba68883c691fbe2d046 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 29 Jan 2018 08:56:58 -0700 Subject: [PATCH 032/168] Change group_hash to output points in the twisted Edwards form. --- src/group_hash.rs | 12 ++++++------ src/jubjub/mod.rs | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/group_hash.rs b/src/group_hash.rs index 2b53972..01824c8 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -10,7 +10,7 @@ use digest::{FixedOutput, Input}; pub fn group_hash( tag: &[u8], params: &E::Params -) -> Option> +) -> Option> { // Check to see that scalar field is 255 bits assert!(E::Fr::NUM_BITS == 255); @@ -25,15 +25,15 @@ pub fn group_hash( h[0] &= 0b0111_1111; // unset s from h // cast to prime field representation - let mut x0 = ::Repr::default(); - x0.read_be(&h[..]).expect("hash is sufficiently large"); + let mut y0 = ::Repr::default(); + y0.read_be(&h[..]).expect("hash is sufficiently large"); - if let Ok(x0) = E::Fr::from_repr(x0) { - if let Some(p) = montgomery::Point::::get_for_x(x0, s, params) { + if let Ok(y0) = E::Fr::from_repr(y0) { + if let Some(p) = edwards::Point::::get_for_y(y0, s, params) { // Enter into the prime order subgroup let p = p.mul_by_cofactor(params); - if p != montgomery::Point::zero() { + if p != edwards::Point::zero() { Some(p) } else { None diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 34859ce..a74748f 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -110,10 +110,12 @@ impl JubjubBls12 { while pedersen_hash_generators.len() < 10 { let gh = group_hash(&[cur], &tmp); + // We don't want to overflow and start reusing generators + assert!(cur != u8::max_value()); cur += 1; if let Some(gh) = gh { - pedersen_hash_generators.push(edwards::Point::from_montgomery(&gh, &tmp)); + pedersen_hash_generators.push(gh); } } From 9d49a60f484bb23ab9d2e87d92e7d5d45ac10eb6 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 29 Jan 2018 10:38:18 -0700 Subject: [PATCH 033/168] Replace Montgomery point interpretation with twisted Edwards. --- src/circuit/mont.rs | 121 ++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 717984e..3bbe493 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -32,6 +32,40 @@ impl EdwardsPoint { self.x.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 + + // TODO: This code uses a naive method to determine if the + // point is on the curve, but it could be optimized to three + // constraints. + + 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", + LinearCombination::zero() - x2.get_variable() + + y2.get_variable(), + LinearCombination::zero() + one, + LinearCombination::zero() + one + + (*params.edwards_d(), x2y2.get_variable()) + ); + + Ok(EdwardsPoint { + x: x.clone(), + y: y.clone() + }) + } + /// Perform addition between any two points pub fn add( &self, @@ -241,34 +275,6 @@ impl MontgomeryPoint { } } - pub fn interpret( - mut cs: CS, - x: &AllocatedNum, - y: &AllocatedNum, - params: &E::Params - ) -> Result - where CS: ConstraintSystem - { - // y^2 = x^3 + A.x^2 + x - - let x2 = x.square(cs.namespace(|| "x^2"))?; - let x3 = x2.mul(cs.namespace(|| "x^3"), x)?; - - cs.enforce( - || "on curve check", - LinearCombination::zero() + y.get_variable(), - LinearCombination::zero() + y.get_variable(), - LinearCombination::zero() + x3.get_variable() - + (*params.montgomery_a(), x2.get_variable()) - + x.get_variable() - ); - - Ok(MontgomeryPoint { - x: x.clone(), - y: y.clone() - }) - } - /// Performs an affine point addition, not defined for /// coincident points. pub fn add( @@ -528,45 +534,40 @@ mod test { let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); for _ in 0..100 { - let p = montgomery::Point::::rand(rng, ¶ms); - let (mut x, mut y) = p.into_xy().unwrap(); + 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 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 = MontgomeryPoint::interpret(&mut cs, &numx, &numy, ¶ms).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); + assert!(cs.is_satisfied()); + assert_eq!(p.x.get_value().unwrap(), x); + assert_eq!(p.y.get_value().unwrap(), y); + } - y.negate(); - cs.set("y/num", y); - assert!(cs.is_satisfied()); - x.negate(); - cs.set("x/num", x); - assert!(!cs.is_satisfied()); - } + // 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(); + 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(); - MontgomeryPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); + EdwardsPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); - assert_eq!(cs.which_is_unsatisfied().unwrap(), "on curve check"); - } + assert_eq!(cs.which_is_unsatisfied().unwrap(), "on curve check"); } } From ba3ef9c84c0dc935627cd6e16286dd0fb855efc7 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 2 Feb 2018 09:41:44 -0700 Subject: [PATCH 034/168] Implementation of 3-bit window table lookups. --- src/circuit/pedersen_hash.rs | 128 +++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 3b0dc45..5aa8562 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -124,6 +124,94 @@ pub fn pedersen_hash( Ok(edwards_result.unwrap()) } +/// Performs a 3-bit window table lookup. `bits` is in +/// little-endian order. +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", + LinearCombination::::zero() + (x_coeffs[0b001], one) + + &bits[1].lc::(one, x_coeffs[0b011]) + + &bits[2].lc::(one, x_coeffs[0b101]) + + &precomp.lc::(one, x_coeffs[0b111]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + 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", + LinearCombination::::zero() + (y_coeffs[0b001], one) + + &bits[1].lc::(one, y_coeffs[0b011]) + + &bits[2].lc::(one, y_coeffs[0b101]) + + &precomp.lc::(one, y_coeffs[0b111]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + 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. fn lookup3_xy_with_conditional_negation( @@ -270,6 +358,46 @@ mod test { } } + #[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]); From 67f2cea2009d63a15aba68d3d77971eebe70f40c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 2 Feb 2018 09:50:59 -0700 Subject: [PATCH 035/168] Move window table lookup logic into its own module. --- src/circuit/lookup.rs | 310 +++++++++++++++++++++++++++++++++++ src/circuit/mod.rs | 1 + src/circuit/pedersen_hash.rs | 301 +--------------------------------- 3 files changed, 314 insertions(+), 298 deletions(-) create mode 100644 src/circuit/lookup.rs diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs new file mode 100644 index 0000000..d44d5d3 --- /dev/null +++ b/src/circuit/lookup.rs @@ -0,0 +1,310 @@ +use pairing::{Engine, Field}; +use super::*; +use super::num::AllocatedNum; +use super::boolean::Boolean; +use bellman::{ + ConstraintSystem, + LinearCombination +}; + +// 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", + LinearCombination::::zero() + (x_coeffs[0b001], one) + + &bits[1].lc::(one, x_coeffs[0b011]) + + &bits[2].lc::(one, x_coeffs[0b101]) + + &precomp.lc::(one, x_coeffs[0b111]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + 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", + LinearCombination::::zero() + (y_coeffs[0b001], one) + + &bits[1].lc::(one, y_coeffs[0b011]) + + &bits[2].lc::(one, y_coeffs[0b101]) + + &precomp.lc::(one, y_coeffs[0b111]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + 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<(AllocatedNum, AllocatedNum), 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 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) + } + )?; + + 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); + + cs.enforce( + || "x-coordinate lookup", + LinearCombination::::zero() + (x_coeffs[0b01], one) + + &bits[1].lc::(one, x_coeffs[0b11]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + res_x.get_variable() + - (x_coeffs[0b00], one) + - &bits[1].lc::(one, x_coeffs[0b10]) + ); + + cs.enforce( + || "y-coordinate lookup", + LinearCombination::::zero() + (y_coeffs[0b01], one) + + &bits[1].lc::(one, y_coeffs[0b11]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + res_y.get_variable() + - (y_coeffs[0b00], one) + - &bits[1].lc::(one, y_coeffs[0b10]) + ); + + let final_y = res_y.conditionally_negate(&mut cs, &bits[2])?; + + Ok((res_x, final_y)) +} + +#[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/src/circuit/mod.rs b/src/circuit/mod.rs index 1141d66..c361487 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -6,6 +6,7 @@ pub mod uint32; pub mod blake2s; pub mod num; pub mod mont; +pub mod lookup; pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 5aa8562..ee940a3 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -1,39 +1,14 @@ -use pairing::{Engine, Field}; use super::*; use super::mont::{ MontgomeryPoint, EdwardsPoint }; -use super::num::AllocatedNum; use super::boolean::Boolean; use ::jubjub::*; use bellman::{ - ConstraintSystem, - LinearCombination + 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); - } - } - } -} +use super::lookup::*; pub fn pedersen_hash( mut cs: CS, @@ -124,174 +99,9 @@ pub fn pedersen_hash( Ok(edwards_result.unwrap()) } -/// Performs a 3-bit window table lookup. `bits` is in -/// little-endian order. -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", - LinearCombination::::zero() + (x_coeffs[0b001], one) - + &bits[1].lc::(one, x_coeffs[0b011]) - + &bits[2].lc::(one, x_coeffs[0b101]) - + &precomp.lc::(one, x_coeffs[0b111]), - LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), - LinearCombination::::zero() + 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", - LinearCombination::::zero() + (y_coeffs[0b001], one) - + &bits[1].lc::(one, y_coeffs[0b011]) - + &bits[2].lc::(one, y_coeffs[0b101]) - + &precomp.lc::(one, y_coeffs[0b111]), - LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), - LinearCombination::::zero() + 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. -fn lookup3_xy_with_conditional_negation( - 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(), 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 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) - } - )?; - - 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); - - cs.enforce( - || "x-coordinate lookup", - LinearCombination::::zero() + (x_coeffs[0b01], one) - + &bits[1].lc::(one, x_coeffs[0b11]), - LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), - LinearCombination::::zero() + res_x.get_variable() - - (x_coeffs[0b00], one) - - &bits[1].lc::(one, x_coeffs[0b10]) - ); - - cs.enforce( - || "y-coordinate lookup", - LinearCombination::::zero() + (y_coeffs[0b01], one) - + &bits[1].lc::(one, y_coeffs[0b11]), - LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), - LinearCombination::::zero() + res_y.get_variable() - - (y_coeffs[0b00], one) - - &bits[1].lc::(one, y_coeffs[0b10]) - ); - - let final_y = res_y.conditionally_negate(&mut cs, &bits[2])?; - - Ok((res_x, final_y)) -} - #[cfg(test)] mod test { - use rand::{SeedableRng, Rand, Rng, XorShiftRng}; + use rand::{SeedableRng, Rng, XorShiftRng}; use super::*; use ::circuit::test::*; use ::circuit::boolean::{Boolean, AllocatedBit}; @@ -357,109 +167,4 @@ mod test { } } } - - #[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]); - } - } } From d49c0b0b0dcaa1a9f002ae026fb86b3a75196b0a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 2 Feb 2018 10:01:33 -0700 Subject: [PATCH 036/168] Implement Edwards doubling in the circuit. --- src/circuit/mont.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 3bbe493..d767dab 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -66,6 +66,19 @@ impl EdwardsPoint { }) } + pub fn double( + &self, + cs: CS, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // TODO: doubling can be optimized to just 5 + // constraints. + + self.add(cs, self, params) + } + /// Perform addition between any two points pub fn add( &self, @@ -660,6 +673,41 @@ mod test { } } + #[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(); @@ -739,7 +787,7 @@ mod test { } #[test] - fn test_doubling() { + fn test_montgomery_doubling() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); From f2c74a4b98d02edc79f3f0b263b15431b31df877 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 2 Feb 2018 11:57:49 -0700 Subject: [PATCH 037/168] Implement conditional reversal of two allocated numbers. --- src/circuit/num.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index cce455c..749d49a 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -292,6 +292,61 @@ impl AllocatedNum { 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 + { + // TODO: Technically this need only be 1 constraint. + // However this interface does not currently support + // returning linear combinations. + + let c = Self::alloc( + cs.namespace(|| "conditional reversal result 1"), + || { + if *condition.get_value().get()? { + Ok(*b.value.get()?) + } else { + Ok(*a.value.get()?) + } + } + )?; + + let one = cs.one(); + cs.enforce( + || "first conditional reversal", + LinearCombination::zero() + a.variable - b.variable, + condition.lc(one, E::Fr::one()), + LinearCombination::zero() + 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", + LinearCombination::zero() + b.variable - a.variable, + condition.lc(one, E::Fr::one()), + LinearCombination::zero() + b.variable - d.variable + ); + + Ok((c, d)) + } + pub fn conditionally_negate( &self, mut cs: CS, @@ -382,6 +437,38 @@ mod test { 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_conditional_negation() { { From 55b8f7a57573a978d925e5e71c28a18d155524d0 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 2 Feb 2018 14:24:18 -0700 Subject: [PATCH 038/168] Dynamic base twisted Edwards scalar multiplication in the circuit. --- src/circuit/mont.rs | 253 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 251 insertions(+), 2 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index d767dab..666129f 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -20,11 +20,22 @@ use ::jubjub::{ JubjubParams }; +use super::boolean::Boolean; + pub struct EdwardsPoint { pub x: AllocatedNum, pub y: AllocatedNum } +impl Clone for EdwardsPoint { + fn clone(&self) -> Self { + EdwardsPoint { + x: self.x.clone(), + y: self.y.clone() + } + } +} + impl EdwardsPoint { /// This extracts the x-coordinate, which is an injective /// encoding for elements of the prime order subgroup. @@ -32,6 +43,116 @@ impl EdwardsPoint { self.x.clone() } + /// 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", + LinearCombination::::zero() + self.x.get_variable(), + condition.lc(one, E::Fr::one()), + LinearCombination::::zero() + 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", + LinearCombination::::zero() + self.y.get_variable(), + condition.lc(one, E::Fr::one()), + LinearCombination::::zero() + 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, @@ -487,20 +608,25 @@ impl MontgomeryPoint { #[cfg(test)] mod test { use bellman::{ConstraintSystem}; - use rand::{XorShiftRng, SeedableRng, Rng}; + use rand::{XorShiftRng, SeedableRng, Rand, Rng}; use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Field}; + use pairing::{BitIterator, Field, PrimeField}; use ::circuit::test::*; use ::jubjub::{ montgomery, edwards, JubjubBls12 }; + use ::jubjub::fs::Fs; use super::{ MontgomeryPoint, EdwardsPoint, AllocatedNum, }; + use super::super::boolean::{ + Boolean, + AllocatedBit + }; #[test] fn test_into_edwards() { @@ -605,6 +731,129 @@ mod test { assert!(p.double(&mut cs, params).is_err()); } + #[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(); From 69833e5162bce2c90548436e1ede6a7a89c8b7ff Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Feb 2018 11:08:17 -0700 Subject: [PATCH 039/168] Start initializing generators for various components in the protocol. --- src/jubjub/mod.rs | 71 +++++++++++++++++++++++++++++++++++++++++++-- src/jubjub/tests.rs | 11 +++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index a74748f..9055b98 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -47,6 +47,8 @@ pub trait JubjubParams: Sized { fn pedersen_hash_generators(&self) -> &[edwards::Point]; fn pedersen_hash_chunks_per_generator(&self) -> usize; fn pedersen_circuit_generators(&self) -> &[Vec>]; + + fn fixed_base_chunks_per_generator(&self) -> usize; } pub enum Unknown { } @@ -59,13 +61,24 @@ impl JubjubEngine for Bls12 { type Params = JubjubBls12; } +/// Fixed generators of the Jubjub curve of unknown +/// exponent. +pub enum FixedGenerators { + NoteCommitmentRandomization = 0, + Max = 1 +} + pub struct JubjubBls12 { edwards_d: Fr, montgomery_a: Fr, montgomery_2a: Fr, scale: Fr, + pedersen_hash_generators: Vec>, - pedersen_circuit_generators: Vec>> + pedersen_circuit_generators: Vec>>, + + fixed_base_generators: Vec>, + fixed_base_circuit_generators: Vec>>, } impl JubjubParams for JubjubBls12 { @@ -79,6 +92,9 @@ impl JubjubParams for JubjubBls12 { fn pedersen_hash_chunks_per_generator(&self) -> usize { 62 } + fn fixed_base_chunks_per_generator(&self) -> usize { + 84 + } fn pedersen_circuit_generators(&self) -> &[Vec>] { &self.pedersen_circuit_generators } @@ -101,14 +117,19 @@ impl JubjubBls12 { scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(), pedersen_hash_generators: vec![], - pedersen_circuit_generators: vec![] + pedersen_circuit_generators: vec![], + + fixed_base_generators: vec![], + fixed_base_circuit_generators: vec![], }; + // Create the bases for the Pedersen hashes { let mut cur = 0; let mut pedersen_hash_generators = vec![]; while pedersen_hash_generators.len() < 10 { + // TODO: personalize let gh = group_hash(&[cur], &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); @@ -122,6 +143,28 @@ impl JubjubBls12 { tmp.pedersen_hash_generators = pedersen_hash_generators; } + // Create the bases for other parts of the protocol + { + let mut cur = 0; + let mut fixed_base_generators = vec![]; + + while fixed_base_generators.len() < (FixedGenerators::Max as usize) { + // TODO: personalize + let gh = group_hash(&[cur], &tmp); + // We don't want to overflow and start reusing generators + assert!(cur != u8::max_value()); + cur += 1; + + if let Some(gh) = gh { + fixed_base_generators.push(gh); + } + } + + tmp.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![]; @@ -147,6 +190,30 @@ impl JubjubBls12 { tmp.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.fixed_base_generators.iter().cloned() { + let mut windows = vec![]; + for _ in 0..tmp.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); + } + windows.push(coeffs); + + gen = g; + } + fixed_base_circuit_generators.push(windows); + } + + tmp.fixed_base_circuit_generators = fixed_base_circuit_generators; + } + tmp } } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index 82b8eb2..6de950b 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -380,4 +380,15 @@ fn test_jubjub_params(params: &E::Params) { } } } + + { + // 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); + } } From edc4adc32c0644972247328eb566a650055e325e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 7 Feb 2018 13:33:09 -0700 Subject: [PATCH 040/168] Implementation of fixed-base Edwards scalar multiplication in the circuit. --- src/circuit/mont.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++- src/jubjub/mod.rs | 11 +++++ 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 666129f..37995fb 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -17,7 +17,12 @@ use super::num::AllocatedNum; use ::jubjub::{ JubjubEngine, - JubjubParams + JubjubParams, + FixedGenerators +}; + +use super::lookup::{ + lookup3_xy }; use super::boolean::Boolean; @@ -36,6 +41,56 @@ impl Clone for EdwardsPoint { } } +/// Perform a fixed-base scalar multiplication with +/// `by` being in little-endian bit order. `by` must +/// be a multiple of 3. +pub fn fixed_base_multiplication( + mut cs: CS, + base: FixedGenerators, + by: &[Boolean], + params: &E::Params +) -> Result, SynthesisError> + where CS: ConstraintSystem, + E: JubjubEngine, + Var: Copy +{ + // We're going to chunk the scalar into 3-bit windows, + // so let's force the caller to supply the right number + // of bits for our lookups. + assert!(by.len() % 3 == 0); + + // 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 (x, y) = lookup3_xy( + cs.namespace(|| format!("window table lookup {}", i)), + chunk, + 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 { /// This extracts the x-coordinate, which is an injective /// encoding for elements of the prime order subgroup. @@ -615,13 +670,16 @@ mod test { use ::jubjub::{ montgomery, edwards, - JubjubBls12 + JubjubBls12, + JubjubParams, + FixedGenerators }; use ::jubjub::fs::Fs; use super::{ MontgomeryPoint, EdwardsPoint, AllocatedNum, + fixed_base_multiplication }; use super::super::boolean::{ Boolean, @@ -731,6 +789,41 @@ mod test { assert!(p.double(&mut cs, params).is_err()); } + #[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::NoteCommitmentRandomization); + 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::NoteCommitmentRandomization, + &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(); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 9055b98..0a53660 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -49,6 +49,8 @@ pub trait JubjubParams: Sized { fn pedersen_circuit_generators(&self) -> &[Vec>]; fn fixed_base_chunks_per_generator(&self) -> usize; + fn generator(&self, base: FixedGenerators) -> &edwards::Point; + fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>]; } pub enum Unknown { } @@ -63,6 +65,7 @@ impl JubjubEngine for Bls12 { /// Fixed generators of the Jubjub curve of unknown /// exponent. +#[derive(Copy, Clone)] pub enum FixedGenerators { NoteCommitmentRandomization = 0, Max = 1 @@ -98,6 +101,14 @@ impl JubjubParams for JubjubBls12 { 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][..] + } } impl JubjubBls12 { From 73e73d7c8cdc14632df70791749554c7f4af11a1 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 7 Feb 2018 13:55:34 -0700 Subject: [PATCH 041/168] Remove TODOs by creating tickets. --- src/circuit/mont.rs | 7 ------- src/circuit/num.rs | 4 ---- src/jubjub/mod.rs | 2 -- 3 files changed, 13 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 37995fb..0141c8e 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -218,10 +218,6 @@ impl EdwardsPoint { { // -x^2 + y^2 = 1 + dx^2y^2 - // TODO: This code uses a naive method to determine if the - // point is on the curve, but it could be optimized to three - // constraints. - 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)?; @@ -249,9 +245,6 @@ impl EdwardsPoint { ) -> Result where CS: ConstraintSystem { - // TODO: doubling can be optimized to just 5 - // constraints. - self.add(cs, self, params) } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 749d49a..8656e4b 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -303,10 +303,6 @@ impl AllocatedNum { ) -> Result<(Self, Self), SynthesisError> where CS: ConstraintSystem { - // TODO: Technically this need only be 1 constraint. - // However this interface does not currently support - // returning linear combinations. - let c = Self::alloc( cs.namespace(|| "conditional reversal result 1"), || { diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 0a53660..c265d84 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -140,7 +140,6 @@ impl JubjubBls12 { let mut pedersen_hash_generators = vec![]; while pedersen_hash_generators.len() < 10 { - // TODO: personalize let gh = group_hash(&[cur], &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); @@ -160,7 +159,6 @@ impl JubjubBls12 { let mut fixed_base_generators = vec![]; while fixed_base_generators.len() < (FixedGenerators::Max as usize) { - // TODO: personalize let gh = group_hash(&[cur], &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); From 52829af6d829a79414223d3787cdd46998a283b5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 11 Feb 2018 12:38:17 -0700 Subject: [PATCH 042/168] Fix test for pedersen hash chunks per generator. --- src/jubjub/tests.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index 6de950b..e034854 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -356,10 +356,16 @@ fn test_jubjub_params(params: &E::Params) { // Check that the number of windows per generator // in the Pedersen hash does not allow for collisions - let mut cur = E::Fr::one().into_repr(); + let mut cur = E::Fs::one().into_repr(); - let mut pacc = E::Fr::zero().into_repr(); - let mut nacc = E::Fr::char(); + 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() { @@ -371,7 +377,7 @@ fn test_jubjub_params(params: &E::Params) { assert_eq!(pacc.add_nocarry(&tmp), false); assert_eq!(nacc.sub_noborrow(&tmp), false); - assert!(pacc < E::Fr::char()); + assert!(pacc < max); assert!(pacc < nacc); // cur = cur * 16 From 821d22261eb8cca81d19b7475f1dcb971609909a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 11 Feb 2018 12:59:57 -0700 Subject: [PATCH 043/168] Raise the number of chunks in Pedersen hashes to 63. --- src/jubjub/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index c265d84..bd259ff 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -93,7 +93,7 @@ impl JubjubParams for JubjubBls12 { &self.pedersen_hash_generators } fn pedersen_hash_chunks_per_generator(&self) -> usize { - 62 + 63 } fn fixed_base_chunks_per_generator(&self) -> usize { 84 From 683aa93b4491810a6d8ae1f998c918bafad21527 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 7 Feb 2018 16:26:57 -0700 Subject: [PATCH 044/168] Account for change in bellman's API for enforcement to use closures. --- Cargo.toml | 2 +- src/circuit/boolean.rs | 42 ++++++------ src/circuit/lookup.rs | 67 +++++++++---------- src/circuit/mont.rs | 145 ++++++++++++++++++++-------------------- src/circuit/num.rs | 48 ++++++------- src/circuit/test/mod.rs | 29 +++++--- src/circuit/uint32.rs | 6 +- 7 files changed, 172 insertions(+), 167 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1831943..a60e138 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ features = ["expose-arith"] rand = "0.3" blake2 = "0.7" digest = "0.7" -bellman = "0.0.7" +bellman = "0.0.8" [features] default = ["u128-support"] diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 6b5b9cd..24dd4bd 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -55,9 +55,9 @@ impl AllocatedBit { let one = cs.one(); cs.enforce( || "boolean constraint", - LinearCombination::zero() + one - var, - LinearCombination::zero() + var, - LinearCombination::zero() + |lc| lc + one - var, + |lc| lc + var, + |lc| lc ); Ok(AllocatedBit { @@ -107,9 +107,9 @@ impl AllocatedBit { // (a + a) * b = a + b - c cs.enforce( || "xor constraint", - LinearCombination::zero() + a.variable + a.variable, - LinearCombination::zero() + b.variable, - LinearCombination::zero() + a.variable + b.variable - result_var + |lc| lc + a.variable + a.variable, + |lc| lc + b.variable, + |lc| lc + a.variable + b.variable - result_var ); Ok(AllocatedBit { @@ -146,9 +146,9 @@ impl AllocatedBit { // a AND b are both 1. cs.enforce( || "and constraint", - LinearCombination::zero() + a.variable, - LinearCombination::zero() + b.variable, - LinearCombination::zero() + result_var + |lc| lc + a.variable, + |lc| lc + b.variable, + |lc| lc + result_var ); Ok(AllocatedBit { @@ -185,9 +185,9 @@ impl AllocatedBit { let one = cs.one(); cs.enforce( || "and not constraint", - LinearCombination::zero() + a.variable, - LinearCombination::zero() + one - b.variable, - LinearCombination::zero() + result_var + |lc| lc + a.variable, + |lc| lc + one - b.variable, + |lc| lc + result_var ); Ok(AllocatedBit { @@ -224,9 +224,9 @@ impl AllocatedBit { let one = cs.one(); cs.enforce( || "nor constraint", - LinearCombination::zero() + one - a.variable, - LinearCombination::zero() + one - b.variable, - LinearCombination::zero() + result_var + |lc| lc + one - a.variable, + |lc| lc + one - b.variable, + |lc| lc + result_var ); Ok(AllocatedBit { @@ -401,9 +401,9 @@ impl Boolean { Boolean::Is(ref res) => { cs.enforce( || "enforce nand", - LinearCombination::zero(), - LinearCombination::zero(), - LinearCombination::zero() + res.get_variable() + |lc| lc, + |lc| lc, + |lc| lc + res.get_variable() ); Ok(()) @@ -412,9 +412,9 @@ impl Boolean { let one = cs.one(); cs.enforce( || "enforce nand", - LinearCombination::zero(), - LinearCombination::zero(), - LinearCombination::zero() + one - res.get_variable() + |lc| lc, + |lc| lc, + |lc| lc + one - res.get_variable() ); Ok(()) diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs index d44d5d3..77783ba 100644 --- a/src/circuit/lookup.rs +++ b/src/circuit/lookup.rs @@ -3,8 +3,7 @@ use super::*; use super::num::AllocatedNum; use super::boolean::Boolean; use bellman::{ - ConstraintSystem, - LinearCombination + ConstraintSystem }; // Synthesize the constants for each base pattern. @@ -89,30 +88,30 @@ pub fn lookup3_xy( cs.enforce( || "x-coordinate lookup", - LinearCombination::::zero() + (x_coeffs[0b001], one) - + &bits[1].lc::(one, x_coeffs[0b011]) - + &bits[2].lc::(one, x_coeffs[0b101]) - + &precomp.lc::(one, x_coeffs[0b111]), - LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), - LinearCombination::::zero() + 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]), + |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", - LinearCombination::::zero() + (y_coeffs[0b001], one) - + &bits[1].lc::(one, y_coeffs[0b011]) - + &bits[2].lc::(one, y_coeffs[0b101]) - + &precomp.lc::(one, y_coeffs[0b111]), - LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), - LinearCombination::::zero() + 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]), + |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)) @@ -172,22 +171,22 @@ pub fn lookup3_xy_with_conditional_negation( cs.enforce( || "x-coordinate lookup", - LinearCombination::::zero() + (x_coeffs[0b01], one) - + &bits[1].lc::(one, x_coeffs[0b11]), - LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), - LinearCombination::::zero() + res_x.get_variable() - - (x_coeffs[0b00], one) - - &bits[1].lc::(one, x_coeffs[0b10]) + |lc| lc + (x_coeffs[0b01], one) + + &bits[1].lc::(one, x_coeffs[0b11]), + |lc| lc + &bits[0].lc::(one, E::Fr::one()), + |lc| lc + res_x.get_variable() + - (x_coeffs[0b00], one) + - &bits[1].lc::(one, x_coeffs[0b10]) ); cs.enforce( || "y-coordinate lookup", - LinearCombination::::zero() + (y_coeffs[0b01], one) - + &bits[1].lc::(one, y_coeffs[0b11]), - LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), - LinearCombination::::zero() + res_y.get_variable() - - (y_coeffs[0b00], one) - - &bits[1].lc::(one, y_coeffs[0b10]) + |lc| lc + (y_coeffs[0b01], one) + + &bits[1].lc::(one, y_coeffs[0b11]), + |lc| lc + &bits[0].lc::(one, E::Fr::one()), + |lc| lc + res_y.get_variable() + - (y_coeffs[0b00], one) + - &bits[1].lc::(one, y_coeffs[0b10]) ); let final_y = res_y.conditionally_negate(&mut cs, &bits[2])?; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 0141c8e..d76b97d 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -5,8 +5,7 @@ use pairing::{ use bellman::{ SynthesisError, - ConstraintSystem, - LinearCombination + ConstraintSystem }; use super::{ @@ -122,9 +121,9 @@ impl EdwardsPoint { let one = cs.one(); cs.enforce( || "x' computation", - LinearCombination::::zero() + self.x.get_variable(), - condition.lc(one, E::Fr::one()), - LinearCombination::::zero() + x_prime.get_variable() + |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 @@ -141,9 +140,9 @@ impl EdwardsPoint { // if condition is 1, y' must be y cs.enforce( || "y' computation", - LinearCombination::::zero() + self.y.get_variable(), - condition.lc(one, E::Fr::one()), - LinearCombination::::zero() + y_prime.get_variable() + |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()) ); @@ -225,11 +224,11 @@ impl EdwardsPoint { let one = cs.one(); cs.enforce( || "on curve check", - LinearCombination::zero() - x2.get_variable() - + y2.get_variable(), - LinearCombination::zero() + one, - LinearCombination::zero() + one - + (*params.edwards_d(), x2y2.get_variable()) + |lc| lc - x2.get_variable() + + y2.get_variable(), + |lc| lc + one, + |lc| lc + one + + (*params.edwards_d(), x2y2.get_variable()) ); Ok(EdwardsPoint { @@ -272,11 +271,11 @@ impl EdwardsPoint { cs.enforce( || "U computation", - LinearCombination::::zero() + self.x.get_variable() - + self.y.get_variable(), - LinearCombination::::zero() + other.x.get_variable() - + other.y.get_variable(), - LinearCombination::::zero() + u.get_variable() + |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 @@ -296,9 +295,9 @@ impl EdwardsPoint { cs.enforce( || "C computation", - LinearCombination::::zero() + (*params.edwards_d(), a.get_variable()), - LinearCombination::::zero() + b.get_variable(), - LinearCombination::::zero() + c.get_variable() + |lc| lc + (*params.edwards_d(), a.get_variable()), + |lc| lc + b.get_variable(), + |lc| lc + c.get_variable() ); // Compute x3 = (A + B) / (1 + C) @@ -324,10 +323,10 @@ impl EdwardsPoint { let one = cs.one(); cs.enforce( || "x3 computation", - LinearCombination::::zero() + one + c.get_variable(), - LinearCombination::::zero() + x3.get_variable(), - LinearCombination::::zero() + a.get_variable() - + b.get_variable() + |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) @@ -353,11 +352,11 @@ impl EdwardsPoint { cs.enforce( || "y3 computation", - LinearCombination::::zero() + one - c.get_variable(), - LinearCombination::::zero() + y3.get_variable(), - LinearCombination::::zero() + u.get_variable() - - a.get_variable() - - b.get_variable() + |lc| lc + one - c.get_variable(), + |lc| lc + y3.get_variable(), + |lc| lc + u.get_variable() + - a.get_variable() + - b.get_variable() ); Ok(EdwardsPoint { @@ -402,9 +401,9 @@ impl MontgomeryPoint { cs.enforce( || "u computation", - LinearCombination::::zero() + self.y.get_variable(), - LinearCombination::::zero() + u.get_variable(), - LinearCombination::::zero() + (*params.scale(), self.x.get_variable()) + |lc| lc + self.y.get_variable(), + |lc| lc + u.get_variable(), + |lc| lc + (*params.scale(), self.x.get_variable()) ); // Compute v = (x - 1) / (x + 1) @@ -429,11 +428,11 @@ impl MontgomeryPoint { let one = cs.one(); cs.enforce( || "v computation", - LinearCombination::::zero() + self.x.get_variable() - + one, - LinearCombination::::zero() + v.get_variable(), - LinearCombination::::zero() + self.x.get_variable() - - one, + |lc| lc + self.x.get_variable() + + one, + |lc| lc + v.get_variable(), + |lc| lc + self.x.get_variable() + - one, ); Ok(EdwardsPoint { @@ -488,13 +487,13 @@ impl MontgomeryPoint { cs.enforce( || "evaluate lambda", - LinearCombination::::zero() + other.x.get_variable() - - self.x.get_variable(), + |lc| lc + other.x.get_variable() + - self.x.get_variable(), - LinearCombination::zero() + lambda.get_variable(), + |lc| lc + lambda.get_variable(), - LinearCombination::::zero() + other.y.get_variable() - - self.y.get_variable() + |lc| lc + other.y.get_variable() + - self.y.get_variable() ); // Compute x'' = lambda^2 - A - x - x' @@ -512,12 +511,12 @@ impl MontgomeryPoint { let one = cs.one(); cs.enforce( || "evaluate xprime", - LinearCombination::zero() + lambda.get_variable(), - LinearCombination::zero() + lambda.get_variable(), - LinearCombination::::zero() + (*params.montgomery_a(), one) - + self.x.get_variable() - + other.x.get_variable() - + xprime.get_variable() + |lc| lc + lambda.get_variable(), + |lc| lc + lambda.get_variable(), + |lc| lc + (*params.montgomery_a(), one) + + self.x.get_variable() + + other.x.get_variable() + + xprime.get_variable() ); // Compute y' = -(y + lambda(x' - x)) @@ -534,13 +533,13 @@ impl MontgomeryPoint { // y' + y = lambda(x - x') cs.enforce( || "evaluate yprime", - LinearCombination::zero() + self.x.get_variable() - - xprime.get_variable(), + |lc| lc + self.x.get_variable() + - xprime.get_variable(), - LinearCombination::zero() + lambda.get_variable(), + |lc| lc + lambda.get_variable(), - LinearCombination::::zero() + yprime.get_variable() - + self.y.get_variable() + |lc| lc + yprime.get_variable() + + self.y.get_variable() ); Ok(MontgomeryPoint { @@ -589,16 +588,16 @@ impl MontgomeryPoint { let one = cs.one(); cs.enforce( || "evaluate lambda", - LinearCombination::::zero() + self.y.get_variable() - + self.y.get_variable(), + |lc| lc + self.y.get_variable() + + self.y.get_variable(), - LinearCombination::zero() + lambda.get_variable(), + |lc| lc + lambda.get_variable(), - LinearCombination::::zero() + xx.get_variable() - + xx.get_variable() - + xx.get_variable() - + (*params.montgomery_2a(), self.x.get_variable()) - + one + |lc| lc + xx.get_variable() + + xx.get_variable() + + xx.get_variable() + + (*params.montgomery_2a(), self.x.get_variable()) + + one ); // Compute x' = (lambda^2) - A - 2.x @@ -615,12 +614,12 @@ impl MontgomeryPoint { // (lambda) * (lambda) = (A + 2.x + x') cs.enforce( || "evaluate xprime", - LinearCombination::zero() + lambda.get_variable(), - LinearCombination::zero() + lambda.get_variable(), - LinearCombination::::zero() + (*params.montgomery_a(), one) - + self.x.get_variable() - + self.x.get_variable() - + xprime.get_variable() + |lc| lc + lambda.get_variable(), + |lc| lc + lambda.get_variable(), + |lc| lc + (*params.montgomery_a(), one) + + self.x.get_variable() + + self.x.get_variable() + + xprime.get_variable() ); // Compute y' = -(y + lambda(x' - x)) @@ -637,13 +636,13 @@ impl MontgomeryPoint { // y' + y = lambda(x - x') cs.enforce( || "evaluate yprime", - LinearCombination::zero() + self.x.get_variable() - - xprime.get_variable(), + |lc| lc + self.x.get_variable() + - xprime.get_variable(), - LinearCombination::zero() + lambda.get_variable(), + |lc| lc + lambda.get_variable(), - LinearCombination::::zero() + yprime.get_variable() - + self.y.get_variable() + |lc| lc + yprime.get_variable() + + self.y.get_variable() ); Ok(MontgomeryPoint { diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 8656e4b..0386fe1 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -122,9 +122,9 @@ impl AllocatedNum { cs.enforce( || "unpacking constraint", - LinearCombination::zero(), - LinearCombination::zero(), - lc + |lc| lc, + |lc| lc, + |_| lc ); Ok(bits.into_iter().map(|b| Boolean::from(b)).collect()) @@ -191,9 +191,9 @@ impl AllocatedNum { cs.enforce( || "packing constraint", - LinearCombination::zero(), - LinearCombination::zero(), - lc + |lc| lc, + |lc| lc, + |_| lc ); Ok(num) @@ -220,9 +220,9 @@ impl AllocatedNum { // Constrain: a * b = ab cs.enforce( || "multiplication constraint", - LinearCombination::zero() + self.variable, - LinearCombination::zero() + other.variable, - LinearCombination::zero() + var + |lc| lc + self.variable, + |lc| lc + other.variable, + |lc| lc + var ); Ok(AllocatedNum { @@ -251,9 +251,9 @@ impl AllocatedNum { // Constrain: a * a = aa cs.enforce( || "squaring constraint", - LinearCombination::zero() + self.variable, - LinearCombination::zero() + self.variable, - LinearCombination::zero() + var + |lc| lc + self.variable, + |lc| lc + self.variable, + |lc| lc + var ); Ok(AllocatedNum { @@ -284,9 +284,9 @@ impl AllocatedNum { let one = cs.one(); cs.enforce( || "nonzero assertion constraint", - LinearCombination::zero() + self.variable, - LinearCombination::zero() + inv, - LinearCombination::zero() + one + |lc| lc + self.variable, + |lc| lc + inv, + |lc| lc + one ); Ok(()) @@ -317,9 +317,9 @@ impl AllocatedNum { let one = cs.one(); cs.enforce( || "first conditional reversal", - LinearCombination::zero() + a.variable - b.variable, - condition.lc(one, E::Fr::one()), - LinearCombination::zero() + a.variable - c.variable + |lc| lc + a.variable - b.variable, + |_| condition.lc(one, E::Fr::one()), + |lc| lc + a.variable - c.variable ); let d = Self::alloc( @@ -335,9 +335,9 @@ impl AllocatedNum { cs.enforce( || "second conditional reversal", - LinearCombination::zero() + b.variable - a.variable, - condition.lc(one, E::Fr::one()), - LinearCombination::zero() + b.variable - d.variable + |lc| lc + b.variable - a.variable, + |_| condition.lc(one, E::Fr::one()), + |lc| lc + b.variable - d.variable ); Ok((c, d)) @@ -368,9 +368,9 @@ impl AllocatedNum { let one = cs.one(); cs.enforce( || "conditional negation", - LinearCombination::zero() + self.variable + self.variable, - condition.lc(one, E::Fr::one()), - LinearCombination::zero() + self.variable - r.variable + |lc| lc + self.variable + self.variable, + |_| condition.lc(one, E::Fr::one()), + |lc| lc + self.variable - r.variable ); Ok(r) diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index 22575e9..8e12ff7 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -168,19 +168,26 @@ impl ConstraintSystem for TestConstraintSystem { Ok(var) } - fn enforce( + fn enforce( &mut self, annotation: A, - a: LinearCombination, - b: LinearCombination, - c: LinearCombination + a: LA, + b: LB, + c: LC ) - where A: FnOnce() -> AR, AR: Into + 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)); } @@ -218,9 +225,9 @@ fn test_cs() { cs.enforce( || "mult", - LinearCombination::zero() + a, - LinearCombination::zero() + b, - LinearCombination::zero() + c + |lc| lc + a, + |lc| lc + b, + |lc| lc + c ); assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 1); @@ -230,9 +237,9 @@ fn test_cs() { let one = cs.one(); cs.enforce( || "eq", - LinearCombination::zero() + a, - LinearCombination::zero() + one, - LinearCombination::zero() + b + |lc| lc + a, + |lc| lc + one, + |lc| lc + b ); assert!(!cs.is_satisfied()); diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index 7f93879..fc24a26 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -281,9 +281,9 @@ impl UInt32 { // Enforce that the linear combination equals zero cs.enforce( || "modular addition", - LinearCombination::zero(), - LinearCombination::zero(), - lc + |lc| lc, + |lc| lc, + |_| lc ); // Discard carry bits that we don't care about From 814cfef7698aaeec02df5ee2af2d81bbeaa2d097 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 7 Feb 2018 16:52:24 -0700 Subject: [PATCH 045/168] Use more descriptive synthesis errors now available in bellman. --- src/circuit/boolean.rs | 2 +- src/circuit/mont.rs | 12 ++++++------ src/circuit/num.rs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 24dd4bd..919acbb 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -396,7 +396,7 @@ impl Boolean { Ok(()) }, Boolean::Constant(true) => { - Err(SynthesisError::AssignmentMissing) + Err(SynthesisError::Unsatisfiable) }, Boolean::Is(ref res) => { cs.enforce( diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index d76b97d..85fa495 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -315,7 +315,7 @@ impl EdwardsPoint { Ok(t0) }, None => { - Err(SynthesisError::AssignmentMissing) + Err(SynthesisError::DivisionByZero) } } })?; @@ -345,7 +345,7 @@ impl EdwardsPoint { Ok(t0) }, None => { - Err(SynthesisError::AssignmentMissing) + Err(SynthesisError::DivisionByZero) } } })?; @@ -394,7 +394,7 @@ impl MontgomeryPoint { Ok(t0) }, None => { - Err(SynthesisError::AssignmentMissing) + Err(SynthesisError::DivisionByZero) } } })?; @@ -420,7 +420,7 @@ impl MontgomeryPoint { Ok(t0) }, None => { - Err(SynthesisError::AssignmentMissing) + Err(SynthesisError::DivisionByZero) } } })?; @@ -480,7 +480,7 @@ impl MontgomeryPoint { Ok(n) }, None => { - Err(SynthesisError::AssignmentMissing) + Err(SynthesisError::DivisionByZero) } } })?; @@ -579,7 +579,7 @@ impl MontgomeryPoint { Ok(t0) }, None => { - Err(SynthesisError::AssignmentMissing) + Err(SynthesisError::DivisionByZero) } } })?; diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 0386fe1..f0255aa 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -272,7 +272,7 @@ impl AllocatedNum { let tmp = *self.value.get()?; if tmp.is_zero() { - Err(SynthesisError::AssignmentMissing) + Err(SynthesisError::DivisionByZero) } else { Ok(tmp.inverse().unwrap()) } From 2f95a9094abd8e2bb2228efe2450ba8dd0123760 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 13 Feb 2018 12:38:06 -0700 Subject: [PATCH 046/168] Refactor to handle changes to ConstraintSystem API (no more Variable associated type, one is now static method). --- src/circuit/blake2s.rs | 16 ++++---- src/circuit/boolean.rs | 75 ++++++++++++++++++------------------ src/circuit/lookup.rs | 22 +++++------ src/circuit/mod.rs | 2 +- src/circuit/mont.rs | 73 +++++++++++++++++------------------ src/circuit/num.rs | 56 +++++++++++++-------------- src/circuit/pedersen_hash.rs | 12 +++--- src/circuit/test/mod.rs | 74 ++++++++++++++++++++++------------- src/circuit/uint32.rs | 40 +++++++++++-------- 9 files changed, 199 insertions(+), 171 deletions(-) diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index 9a0d283..855a8c6 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -90,13 +90,13 @@ const SIGMA: [[usize; 16]; 10] = [ fn mixing_g>( mut cs: CS, - v: &mut [UInt32], + v: &mut [UInt32], a: usize, b: usize, c: usize, d: usize, - x: &UInt32, - y: &UInt32 + x: &UInt32, + y: &UInt32 ) -> Result<(), SynthesisError> { v[a] = UInt32::addmany(cs.namespace(|| "mixing step 1"), &[v[a].clone(), v[b].clone(), x.clone()])?; @@ -162,8 +162,8 @@ fn mixing_g>( fn blake2s_compression>( mut cs: CS, - h: &mut [UInt32], - m: &[UInt32], + h: &mut [UInt32], + m: &[UInt32], t: u64, f: bool ) -> Result<(), SynthesisError> @@ -254,8 +254,8 @@ fn blake2s_compression>( pub fn blake2s>( mut cs: CS, - input: &[Boolean] -) -> Result>, SynthesisError> + input: &[Boolean] +) -> Result, SynthesisError> { assert!(input.len() % 8 == 0); @@ -269,7 +269,7 @@ pub fn blake2s>( h.push(UInt32::constant(0x1F83D9AB)); h.push(UInt32::constant(0x5BE0CD19)); - let mut blocks: Vec>> = vec![]; + let mut blocks: Vec> = vec![]; for block in input.chunks(512) { let mut this_block = Vec::with_capacity(16); diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 919acbb..4d1c358 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -9,7 +9,8 @@ use pairing::{ use bellman::{ ConstraintSystem, SynthesisError, - LinearCombination + LinearCombination, + Variable }; use super::{ @@ -19,17 +20,17 @@ use super::{ /// Represents a variable in the constraint system which is guaranteed /// to be either zero or one. #[derive(Clone)] -pub struct AllocatedBit { - variable: Var, +pub struct AllocatedBit { + variable: Variable, value: Option } -impl AllocatedBit { +impl AllocatedBit { pub fn get_value(&self) -> Option { self.value } - pub fn get_variable(&self) -> Var { + pub fn get_variable(&self) -> Variable { self.variable } @@ -40,7 +41,7 @@ impl AllocatedBit { value: Option, ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let var = cs.alloc(|| "boolean", || { if *value.get()? { @@ -52,10 +53,9 @@ impl AllocatedBit { // Constrain: (1 - a) * a = 0 // This constrains a to be either 0 or 1. - let one = cs.one(); cs.enforce( || "boolean constraint", - |lc| lc + one - var, + |lc| lc + CS::one() - var, |lc| lc + var, |lc| lc ); @@ -74,7 +74,7 @@ impl AllocatedBit { b: &Self ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let mut result_value = None; @@ -126,7 +126,7 @@ impl AllocatedBit { b: &Self ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let mut result_value = None; @@ -164,7 +164,7 @@ impl AllocatedBit { b: &Self ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let mut result_value = None; @@ -182,11 +182,10 @@ impl AllocatedBit { // Constrain (a) * (1 - b) = (c), ensuring c is 1 iff // a is true and b is false, and otherwise c is 0. - let one = cs.one(); cs.enforce( || "and not constraint", |lc| lc + a.variable, - |lc| lc + one - b.variable, + |lc| lc + CS::one() - b.variable, |lc| lc + result_var ); @@ -203,7 +202,7 @@ impl AllocatedBit { b: &Self ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let mut result_value = None; @@ -221,11 +220,10 @@ impl AllocatedBit { // Constrain (1 - a) * (1 - b) = (c), ensuring c is 1 iff // a and b are both false, and otherwise c is 0. - let one = cs.one(); cs.enforce( || "nor constraint", - |lc| lc + one - a.variable, - |lc| lc + one - b.variable, + |lc| lc + CS::one() - a.variable, + |lc| lc + CS::one() - b.variable, |lc| lc + result_var ); @@ -239,23 +237,23 @@ impl AllocatedBit { /// This is a boolean value which may be either a constant or /// an interpretation of an `AllocatedBit`. #[derive(Clone)] -pub enum Boolean { +pub enum Boolean { /// Existential view of the boolean variable - Is(AllocatedBit), + Is(AllocatedBit), /// Negated view of the boolean variable - Not(AllocatedBit), + Not(AllocatedBit), /// Constant (not an allocated variable) Constant(bool) } -impl Boolean { +impl Boolean { pub fn enforce_equal( mut cs: CS, a: &Self, b: &Self ) -> Result<(), SynthesisError> where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let c = Self::xor(&mut cs, a, b)?; @@ -270,21 +268,25 @@ impl Boolean { } } - pub fn lc(&self, one: Var, coeff: E::Fr) -> LinearCombination + pub fn lc( + &self, + one: Variable, + coeff: E::Fr + ) -> LinearCombination { match self { &Boolean::Constant(c) => { if c { - LinearCombination::::zero() + (coeff, one) + LinearCombination::::zero() + (coeff, one) } else { - LinearCombination::::zero() + LinearCombination::::zero() } }, &Boolean::Is(ref v) => { - LinearCombination::::zero() + (coeff, v.get_variable()) + LinearCombination::::zero() + (coeff, v.get_variable()) }, &Boolean::Not(ref v) => { - LinearCombination::::zero() + (coeff, one) - (coeff, v.get_variable()) + LinearCombination::::zero() + (coeff, one) - (coeff, v.get_variable()) } } } @@ -310,7 +312,7 @@ impl Boolean { b: &'a Self ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { match (a, b) { (&Boolean::Constant(false), x) | (x, &Boolean::Constant(false)) => Ok(x.clone()), @@ -337,7 +339,7 @@ impl Boolean { b: &'a Self ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { match (a, b) { // false AND x is always false @@ -364,7 +366,7 @@ impl Boolean { bits: &[Self] ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { assert!(bits.len() > 0); let mut bits = bits.iter(); @@ -387,7 +389,7 @@ impl Boolean { bits: &[Self] ) -> Result<(), SynthesisError> where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let res = Self::kary_and(&mut cs, bits)?; @@ -409,12 +411,11 @@ impl Boolean { Ok(()) }, Boolean::Not(ref res) => { - let one = cs.one(); cs.enforce( || "enforce nand", |lc| lc, |lc| lc, - |lc| lc + one - res.get_variable() + |lc| lc + CS::one() - res.get_variable() ); Ok(()) @@ -429,7 +430,7 @@ impl Boolean { bits: &[Self] ) -> Result<(), SynthesisError> where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { assert_eq!(bits.len(), F::NUM_BITS as usize); @@ -440,7 +441,7 @@ impl Boolean { b.sub_noborrow(&1.into()); // Runs of ones in r - let mut last_run = Boolean::::constant(true); + let mut last_run = Boolean::constant(true); let mut current_run = vec![]; let mut found_one = false; @@ -495,8 +496,8 @@ impl Boolean { } } -impl From> for Boolean { - fn from(b: AllocatedBit) -> Boolean { +impl From for Boolean { + fn from(b: AllocatedBit) -> Boolean { Boolean::Is(b) } } diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs index 77783ba..e433cd0 100644 --- a/src/circuit/lookup.rs +++ b/src/circuit/lookup.rs @@ -31,12 +31,12 @@ fn synth<'a, E: Engine, I>( /// Performs a 3-bit window table lookup. `bits` is in /// little-endian order. -pub fn lookup3_xy( +pub fn lookup3_xy( mut cs: CS, - bits: &[Boolean], + bits: &[Boolean], coords: &[(E::Fr, E::Fr)] -) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> - where CS: ConstraintSystem +) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> + where CS: ConstraintSystem { assert_eq!(bits.len(), 3); assert_eq!(coords.len(), 8); @@ -84,7 +84,7 @@ pub fn lookup3_xy( let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[1], &bits[2])?; - let one = cs.one(); + let one = CS::one(); cs.enforce( || "x-coordinate lookup", @@ -119,12 +119,12 @@ pub fn lookup3_xy( /// Performs a 3-bit window table lookup, where /// one of the bits is a sign bit. -pub fn lookup3_xy_with_conditional_negation( +pub fn lookup3_xy_with_conditional_negation( mut cs: CS, - bits: &[Boolean], + bits: &[Boolean], coords: &[(E::Fr, E::Fr)] -) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> - where CS: ConstraintSystem +) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> + where CS: ConstraintSystem { assert_eq!(bits.len(), 3); assert_eq!(coords.len(), 4); @@ -161,7 +161,7 @@ pub fn lookup3_xy_with_conditional_negation( } )?; - let one = cs.one(); + let one = CS::one(); // Compute the coefficients for the lookup constraints let mut x_coeffs = [E::Fr::zero(); 4]; @@ -289,7 +289,7 @@ mod test { let window_size = 4; - let mut assignment = vec![Fr::zero(); (1 << window_size)]; + 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); diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index c361487..45de18f 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -5,8 +5,8 @@ pub mod boolean; pub mod uint32; pub mod blake2s; pub mod num; -pub mod mont; pub mod lookup; +pub mod mont; pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 85fa495..81a2fcb 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -26,12 +26,12 @@ use super::lookup::{ use super::boolean::Boolean; -pub struct EdwardsPoint { - pub x: AllocatedNum, - pub y: AllocatedNum +pub struct EdwardsPoint { + pub x: AllocatedNum, + pub y: AllocatedNum } -impl Clone for EdwardsPoint { +impl Clone for EdwardsPoint { fn clone(&self) -> Self { EdwardsPoint { x: self.x.clone(), @@ -43,15 +43,14 @@ impl Clone for EdwardsPoint { /// Perform a fixed-base scalar multiplication with /// `by` being in little-endian bit order. `by` must /// be a multiple of 3. -pub fn fixed_base_multiplication( +pub fn fixed_base_multiplication( mut cs: CS, base: FixedGenerators, - by: &[Boolean], + by: &[Boolean], params: &E::Params -) -> Result, SynthesisError> - where CS: ConstraintSystem, - E: JubjubEngine, - Var: Copy +) -> Result, SynthesisError> + where CS: ConstraintSystem, + E: JubjubEngine { // We're going to chunk the scalar into 3-bit windows, // so let's force the caller to supply the right number @@ -90,10 +89,10 @@ pub fn fixed_base_multiplication( Ok(result.get()?.clone()) } -impl EdwardsPoint { +impl EdwardsPoint { /// This extracts the x-coordinate, which is an injective /// encoding for elements of the prime order subgroup. - pub fn into_num(&self) -> AllocatedNum { + pub fn into_num(&self) -> AllocatedNum { self.x.clone() } @@ -102,9 +101,9 @@ impl EdwardsPoint { pub fn conditionally_select( &self, mut cs: CS, - condition: &Boolean + condition: &Boolean ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { // Compute x' = self.x if condition, and 0 otherwise let x_prime = AllocatedNum::alloc(cs.namespace(|| "x'"), || { @@ -118,7 +117,7 @@ impl EdwardsPoint { // condition * x = x' // if condition is 0, x' must be 0 // if condition is 1, x' must be x - let one = cs.one(); + let one = CS::one(); cs.enforce( || "x' computation", |lc| lc + self.x.get_variable(), @@ -158,10 +157,10 @@ impl EdwardsPoint { pub fn mul( &self, mut cs: CS, - by: &[Boolean], + by: &[Boolean], params: &E::Params ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { // Represents the current "magnitude" of the base // that we're operating over. Starts at self, @@ -209,11 +208,11 @@ impl EdwardsPoint { pub fn interpret( mut cs: CS, - x: &AllocatedNum, - y: &AllocatedNum, + x: &AllocatedNum, + y: &AllocatedNum, params: &E::Params ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { // -x^2 + y^2 = 1 + dx^2y^2 @@ -221,7 +220,7 @@ impl EdwardsPoint { let y2 = y.square(cs.namespace(|| "y^2"))?; let x2y2 = x2.mul(cs.namespace(|| "x^2 y^2"), &y2)?; - let one = cs.one(); + let one = CS::one(); cs.enforce( || "on curve check", |lc| lc - x2.get_variable() @@ -242,7 +241,7 @@ impl EdwardsPoint { cs: CS, params: &E::Params ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { self.add(cs, self, params) } @@ -254,7 +253,7 @@ impl EdwardsPoint { other: &Self, params: &E::Params ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { // Compute U = (x1 + y1) * (x2 + y2) let u = AllocatedNum::alloc(cs.namespace(|| "U"), || { @@ -320,7 +319,7 @@ impl EdwardsPoint { } })?; - let one = cs.one(); + let one = CS::one(); cs.enforce( || "x3 computation", |lc| lc + one + c.get_variable(), @@ -366,12 +365,12 @@ impl EdwardsPoint { } } -pub struct MontgomeryPoint { - x: AllocatedNum, - y: AllocatedNum +pub struct MontgomeryPoint { + x: AllocatedNum, + y: AllocatedNum } -impl MontgomeryPoint { +impl MontgomeryPoint { /// Converts an element in the prime order subgroup into /// a point in the birationally equivalent twisted /// Edwards curve. @@ -379,8 +378,8 @@ impl MontgomeryPoint { &self, mut cs: CS, params: &E::Params - ) -> Result, SynthesisError> - where CS: ConstraintSystem + ) -> Result, SynthesisError> + where CS: ConstraintSystem { // Compute u = (scale*x) / y let u = AllocatedNum::alloc(cs.namespace(|| "u"), || { @@ -425,7 +424,7 @@ impl MontgomeryPoint { } })?; - let one = cs.one(); + let one = CS::one(); cs.enforce( || "v computation", |lc| lc + self.x.get_variable() @@ -446,8 +445,8 @@ impl MontgomeryPoint { /// on the curve. Useful for constants and /// window table lookups. pub fn interpret_unchecked( - x: AllocatedNum, - y: AllocatedNum + x: AllocatedNum, + y: AllocatedNum ) -> Self { MontgomeryPoint { @@ -464,7 +463,7 @@ impl MontgomeryPoint { other: &Self, params: &E::Params ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { // Compute lambda = (y' - y) / (x' - x) let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || { @@ -508,7 +507,7 @@ impl MontgomeryPoint { })?; // (lambda) * (lambda) = (A + x + x' + x'') - let one = cs.one(); + let one = CS::one(); cs.enforce( || "evaluate xprime", |lc| lc + lambda.get_variable(), @@ -555,7 +554,7 @@ impl MontgomeryPoint { mut cs: CS, params: &E::Params ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { // Square x let xx = self.x.square(&mut cs)?; @@ -585,7 +584,7 @@ impl MontgomeryPoint { })?; // (2.y) * (lambda) = (3.xx + 2.A.x + 1) - let one = cs.one(); + let one = CS::one(); cs.enforce( || "evaluate lambda", |lc| lc + self.y.get_variable() diff --git a/src/circuit/num.rs b/src/circuit/num.rs index f0255aa..aca7562 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -8,7 +8,8 @@ use pairing::{ use bellman::{ SynthesisError, ConstraintSystem, - LinearCombination + LinearCombination, + Variable }; use super::{ @@ -20,12 +21,12 @@ use super::boolean::{ AllocatedBit }; -pub struct AllocatedNum { +pub struct AllocatedNum { value: Option, - variable: Var + variable: Variable } -impl Clone for AllocatedNum { +impl Clone for AllocatedNum { fn clone(&self) -> Self { AllocatedNum { value: self.value, @@ -34,12 +35,12 @@ impl Clone for AllocatedNum { } } -impl AllocatedNum { +impl AllocatedNum { pub fn alloc( mut cs: CS, value: F, ) -> Result - where CS: ConstraintSystem, + where CS: ConstraintSystem, F: FnOnce() -> Result { let mut new_value = None; @@ -60,8 +61,8 @@ impl AllocatedNum { pub fn into_bits_strict( &self, mut cs: CS - ) -> Result>, SynthesisError> - where CS: ConstraintSystem + ) -> Result, SynthesisError> + where CS: ConstraintSystem { let bits = self.into_bits(&mut cs)?; Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?; @@ -72,8 +73,8 @@ impl AllocatedNum { pub fn into_bits( &self, mut cs: CS - ) -> Result>, SynthesisError> - where CS: ConstraintSystem + ) -> Result, SynthesisError> + where CS: ConstraintSystem { let bit_values = match self.value { Some(value) => { @@ -132,16 +133,16 @@ impl AllocatedNum { pub fn from_bits_strict( mut cs: CS, - bits: &[Boolean] + bits: &[Boolean] ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { assert_eq!(bits.len(), E::Fr::NUM_BITS as usize); Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, bits)?; - let one = cs.one(); - let mut lc = LinearCombination::::zero(); + let one = CS::one(); + let mut lc = LinearCombination::::zero(); let mut coeff = E::Fr::one(); let mut value = Some(E::Fr::zero()); for bit in bits.iter().rev() { @@ -204,7 +205,7 @@ impl AllocatedNum { mut cs: CS, other: &Self ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { let mut value = None; @@ -235,7 +236,7 @@ impl AllocatedNum { &self, mut cs: CS ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { let mut value = None; @@ -266,7 +267,7 @@ impl AllocatedNum { &self, mut cs: CS ) -> Result<(), SynthesisError> - where CS: ConstraintSystem + where CS: ConstraintSystem { let inv = cs.alloc(|| "ephemeral inverse", || { let tmp = *self.value.get()?; @@ -281,12 +282,11 @@ impl AllocatedNum { // Constrain a * inv = 1, which is only valid // iff a has a multiplicative inverse, untrue // for zero. - let one = cs.one(); cs.enforce( || "nonzero assertion constraint", |lc| lc + self.variable, |lc| lc + inv, - |lc| lc + one + |lc| lc + CS::one() ); Ok(()) @@ -299,9 +299,9 @@ impl AllocatedNum { mut cs: CS, a: &Self, b: &Self, - condition: &Boolean + condition: &Boolean ) -> Result<(Self, Self), SynthesisError> - where CS: ConstraintSystem + where CS: ConstraintSystem { let c = Self::alloc( cs.namespace(|| "conditional reversal result 1"), @@ -314,11 +314,10 @@ impl AllocatedNum { } )?; - let one = cs.one(); cs.enforce( || "first conditional reversal", |lc| lc + a.variable - b.variable, - |_| condition.lc(one, E::Fr::one()), + |_| condition.lc(CS::one(), E::Fr::one()), |lc| lc + a.variable - c.variable ); @@ -336,7 +335,7 @@ impl AllocatedNum { cs.enforce( || "second conditional reversal", |lc| lc + b.variable - a.variable, - |_| condition.lc(one, E::Fr::one()), + |_| condition.lc(CS::one(), E::Fr::one()), |lc| lc + b.variable - d.variable ); @@ -346,9 +345,9 @@ impl AllocatedNum { pub fn conditionally_negate( &self, mut cs: CS, - condition: &Boolean + condition: &Boolean ) -> Result - where CS: ConstraintSystem + where CS: ConstraintSystem { let r = Self::alloc( cs.namespace(|| "conditional negation result"), @@ -365,11 +364,10 @@ impl AllocatedNum { // x - 2cx = r // (2x) * (c) = x - r - let one = cs.one(); cs.enforce( || "conditional negation", |lc| lc + self.variable + self.variable, - |_| condition.lc(one, E::Fr::one()), + |_| condition.lc(CS::one(), E::Fr::one()), |lc| lc + self.variable - r.variable ); @@ -380,7 +378,7 @@ impl AllocatedNum { self.value } - pub fn get_variable(&self) -> Var { + pub fn get_variable(&self) -> Variable { self.variable } } diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index ee940a3..a405b7a 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -10,12 +10,12 @@ use bellman::{ }; use super::lookup::*; -pub fn pedersen_hash( +pub fn pedersen_hash( mut cs: CS, - bits: &[Boolean], + bits: &[Boolean], params: &E::Params -) -> Result, SynthesisError> - where CS: ConstraintSystem +) -> Result, SynthesisError> + where CS: ConstraintSystem { // Unnecessary if forced personalization is introduced assert!(bits.len() > 0); @@ -116,7 +116,7 @@ mod test { let input: Vec = (0..(Fr::NUM_BITS * 2)).map(|_| rng.gen()).collect(); - let input_bools: Vec> = input.iter().enumerate().map(|(i, b)| { + let input_bools: Vec = input.iter().enumerate().map(|(i, b)| { Boolean::from( AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap() ) @@ -143,7 +143,7 @@ mod test { let mut cs = TestConstraintSystem::::new(); - let input_bools: Vec> = input.iter().enumerate().map(|(i, b)| { + let input_bools: Vec = input.iter().enumerate().map(|(i, b)| { Boolean::from( AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap() ) diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index 8e12ff7..ef042fe 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -6,17 +6,13 @@ use pairing::{ use bellman::{ LinearCombination, SynthesisError, - ConstraintSystem + ConstraintSystem, + Variable, + Index }; use std::collections::HashMap; -#[derive(Debug, Copy, Clone)] -pub enum Variable { - Input(usize), - Aux(usize) -} - #[derive(Debug)] enum NamedObject { Constraint(usize), @@ -28,7 +24,12 @@ enum NamedObject { pub struct TestConstraintSystem { named_objects: HashMap, current_namespace: Vec, - constraints: Vec<(LinearCombination, LinearCombination, LinearCombination, String)>, + constraints: Vec<( + LinearCombination, + LinearCombination, + LinearCombination, + String + )>, inputs: Vec<(E::Fr, String)>, aux: Vec<(E::Fr, String)> } @@ -42,9 +43,9 @@ fn eval_lc( let mut acc = E::Fr::zero(); for &(var, ref coeff) in terms { - let mut tmp = match var { - Variable::Input(index) => inputs[index].0, - Variable::Aux(index) => aux[index].0 + let mut tmp = match var.get_unchecked() { + Index::Input(index) => inputs[index].0, + Index::Aux(index) => aux[index].0 }; tmp.mul_assign(&coeff); @@ -57,7 +58,7 @@ fn eval_lc( impl TestConstraintSystem { pub fn new() -> TestConstraintSystem { let mut map = HashMap::new(); - map.insert("ONE".into(), NamedObject::Var(Variable::Input(0))); + map.insert("ONE".into(), NamedObject::Var(TestConstraintSystem::::one())); TestConstraintSystem { named_objects: map, @@ -97,8 +98,12 @@ impl TestConstraintSystem { pub fn set(&mut self, path: &str, to: E::Fr) { match self.named_objects.get(path) { - Some(&NamedObject::Var(Variable::Input(index))) => self.inputs[index].0 = to, - Some(&NamedObject::Var(Variable::Aux(index))) => self.aux[index].0 = to, + 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) } @@ -107,8 +112,12 @@ impl TestConstraintSystem { pub fn get(&mut self, path: &str) -> E::Fr { match self.named_objects.get(path) { - Some(&NamedObject::Var(Variable::Input(index))) => self.inputs[index].0, - Some(&NamedObject::Var(Variable::Aux(index))) => self.aux[index].0, + 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) } @@ -145,24 +154,35 @@ fn compute_path(ns: &[String], this: String) -> String { } impl ConstraintSystem for TestConstraintSystem { - type Variable = Variable; type Root = Self; - fn one(&self) -> Self::Variable { - Variable::Input(0) - } - fn alloc( &mut self, annotation: A, f: F - ) -> Result + ) -> 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::Aux(index); + 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) @@ -176,9 +196,9 @@ impl ConstraintSystem for TestConstraintSystem { c: LC ) where A: FnOnce() -> AR, AR: Into, - LA: FnOnce(LinearCombination) -> LinearCombination, - LB: FnOnce(LinearCombination) -> LinearCombination, - LC: FnOnce(LinearCombination) -> LinearCombination + 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(); @@ -234,7 +254,7 @@ fn test_cs() { cs.set("a/var", Fr::from_str("4").unwrap()); - let one = cs.one(); + let one = TestConstraintSystem::::one(); cs.enforce( || "eq", |lc| lc + a, diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index fc24a26..ff39433 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -18,13 +18,13 @@ use super::boolean::{ /// Represents an interpretation of 32 `Boolean` objects as an /// unsigned integer. #[derive(Clone)] -pub struct UInt32 { +pub struct UInt32 { // Least significant bit first - bits: Vec>, + bits: Vec, value: Option } -impl UInt32 { +impl UInt32 { /// Construct a constant `UInt32` from a `u32` pub fn constant(value: u32) -> Self { @@ -53,7 +53,7 @@ impl UInt32 { value: Option ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let values = match value { Some(mut val) => { @@ -72,7 +72,10 @@ impl UInt32 { let bits = values.into_iter() .enumerate() .map(|(i, v)| { - Ok(Boolean::from(AllocatedBit::alloc(cs.namespace(|| format!("allocated bit {}", i)), v)?)) + Ok(Boolean::from(AllocatedBit::alloc( + cs.namespace(|| format!("allocated bit {}", i)), + v + )?)) }) .collect::, SynthesisError>>()?; @@ -83,7 +86,7 @@ impl UInt32 { } /// Turns this `UInt32` into its little-endian byte order representation. - pub fn into_bits(&self) -> Vec> { + pub fn into_bits(&self) -> Vec { self.bits.chunks(8) .flat_map(|v| v.iter().rev()) .cloned() @@ -92,7 +95,7 @@ impl UInt32 { /// Converts a little-endian byte order representation of bits into a /// `UInt32`. - pub fn from_bits(bits: &[Boolean]) -> Self + pub fn from_bits(bits: &[Boolean]) -> Self { assert_eq!(bits.len(), 32); @@ -157,7 +160,7 @@ impl UInt32 { other: &Self ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { let new_value = match (self.value, other.value) { (Some(a), Some(b)) => { @@ -170,7 +173,11 @@ impl UInt32 { .zip(other.bits.iter()) .enumerate() .map(|(i, (a, b))| { - Boolean::xor(cs.namespace(|| format!("xor of bit {}", i)), a, b) + Boolean::xor( + cs.namespace(|| format!("xor of bit {}", i)), + a, + b + ) }) .collect::>()?; @@ -186,7 +193,7 @@ impl UInt32 { operands: &[Self] ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem { // Make some arbitrary bounds for ourselves to avoid overflows // in the scalar field @@ -235,11 +242,11 @@ impl UInt32 { all_constants = false; // Add coeff * (1 - bit) = coeff * ONE - coeff * bit - lc = lc + (coeff, cs.one()) - (coeff, bit.get_variable()); + lc = lc + (coeff, CS::one()) - (coeff, bit.get_variable()); }, &Boolean::Constant(bit) => { if bit { - lc = lc + (coeff, cs.one()); + lc = lc + (coeff, CS::one()); } } } @@ -266,7 +273,10 @@ impl UInt32 { 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))?; + let b = AllocatedBit::alloc( + cs.namespace(|| format!("result bit {}", i)), + result_value.map(|v| (v >> i) & 1 == 1) + )?; // Subtract this bit from the linear combination to ensure the sums balance out lc = lc - (coeff, b.get_variable()); @@ -311,7 +321,7 @@ mod test { 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 mut v = (0..32).map(|_| Boolean::constant(rng.gen())).collect::>(); let b = UInt32::from_bits(&v); @@ -473,7 +483,7 @@ mod test { let mut num = rng.gen(); - let a = UInt32::<()>::constant(num); + let a = UInt32::constant(num); for i in 0..32 { let b = a.rotr(i); From 1df7fbeeffa440ee35a78ecd6527eeb4c5f5b576 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Feb 2018 18:56:53 -0700 Subject: [PATCH 047/168] Refactor "booleanization" of objects. --- src/circuit/boolean.rs | 69 +++++++++++++++++++++++++++++++++++++++++- src/circuit/num.rs | 42 +++++-------------------- 2 files changed, 75 insertions(+), 36 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 4d1c358..c6cb088 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -234,6 +234,47 @@ impl AllocatedBit { } } +pub fn field_into_allocated_bits_be, F: PrimeField>( + mut cs: CS, + value: Option +) -> Result, SynthesisError> +{ + 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] + } + }; + + let bits = values.into_iter().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)] @@ -509,7 +550,11 @@ mod test { use pairing::bls12_381::{Bls12, Fr}; use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator}; use ::circuit::test::*; - use super::{AllocatedBit, Boolean}; + use super::{ + AllocatedBit, + Boolean, + field_into_allocated_bits_be + }; #[test] fn test_allocated_bit() { @@ -1129,4 +1174,26 @@ mod test { } } } + + #[test] + fn test_field_into_allocated_bits_be() { + let mut cs = TestConstraintSystem::::new(); + + let r = Fr::from_str("9147677615426976802526883532204139322118074541891858454835346926874644257775").unwrap(); + + let bits = field_into_allocated_bits_be(&mut cs, Some(r)).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(bits.len(), 255); + + assert_eq!(bits[0].value.unwrap(), false); + assert_eq!(bits[1].value.unwrap(), false); + assert_eq!(bits[2].value.unwrap(), true); + assert_eq!(bits[3].value.unwrap(), false); + assert_eq!(bits[4].value.unwrap(), true); + assert_eq!(bits[5].value.unwrap(), false); + assert_eq!(bits[20].value.unwrap(), true); + assert_eq!(bits[23].value.unwrap(), true); + } } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index aca7562..a6505f6 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -17,8 +17,9 @@ use super::{ }; use super::boolean::{ - Boolean, - AllocatedBit + self, + Boolean, + AllocatedBit }; pub struct AllocatedNum { @@ -76,39 +77,10 @@ impl AllocatedNum { ) -> Result, SynthesisError> where CS: ConstraintSystem { - let bit_values = match self.value { - Some(value) => { - let mut field_char = BitIterator::new(E::Fr::char()); - - let mut tmp = Vec::with_capacity(E::Fr::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(), E::Fr::NUM_BITS as usize); - - tmp - }, - None => { - vec![None; E::Fr::NUM_BITS as usize] - } - }; - - let mut bits = vec![]; - for (i, b) in bit_values.into_iter().enumerate() { - bits.push(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - b - )?); - } + let bits = boolean::field_into_allocated_bits_be( + &mut cs, + self.value + )?; let mut lc = LinearCombination::zero(); let mut coeff = E::Fr::one(); From ec7323159c129ac0d5ed3b7a28010077544038fd Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Feb 2018 19:27:03 -0700 Subject: [PATCH 048/168] Booleanize u64 objects. --- src/circuit/boolean.rs | 54 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index c6cb088..23ec447 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -234,6 +234,36 @@ impl AllocatedBit { } } +pub fn u64_into_allocated_bits_be>( + 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).rev() { + tmp.push(Some(*value >> i & 1 == 1)); + } + + tmp + }, + None => { + vec![None; 64] + } + }; + + let bits = values.into_iter().enumerate().map(|(i, b)| { + AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + b + ) + }).collect::, SynthesisError>>()?; + + Ok(bits) +} + pub fn field_into_allocated_bits_be, F: PrimeField>( mut cs: CS, value: Option @@ -553,7 +583,8 @@ mod test { use super::{ AllocatedBit, Boolean, - field_into_allocated_bits_be + field_into_allocated_bits_be, + u64_into_allocated_bits_be }; #[test] @@ -1175,6 +1206,27 @@ mod test { } } + #[test] + fn test_u64_into_allocated_bits_be() { + let mut cs = TestConstraintSystem::::new(); + + let bits = u64_into_allocated_bits_be(&mut cs, Some(17234652694787248421)).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(bits.len(), 64); + + assert_eq!(bits[0].value.unwrap(), true); + assert_eq!(bits[1].value.unwrap(), true); + assert_eq!(bits[2].value.unwrap(), true); + assert_eq!(bits[3].value.unwrap(), false); + assert_eq!(bits[4].value.unwrap(), true); + assert_eq!(bits[5].value.unwrap(), true); + assert_eq!(bits[20].value.unwrap(), true); + assert_eq!(bits[21].value.unwrap(), false); + assert_eq!(bits[22].value.unwrap(), false); + } + #[test] fn test_field_into_allocated_bits_be() { let mut cs = TestConstraintSystem::::new(); From 5118fd500850564eca83b5586fddf689121f742f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 12:54:14 -0700 Subject: [PATCH 049/168] Add new fixed generators for the output circuit --- src/circuit/mont.rs | 4 ++-- src/jubjub/mod.rs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 81a2fcb..f190536 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -788,7 +788,7 @@ mod test { for _ in 0..100 { let mut cs = TestConstraintSystem::::new(); - let p = params.generator(FixedGenerators::NoteCommitmentRandomization); + let p = params.generator(FixedGenerators::NoteCommitmentRandomness); let s = Fs::rand(rng); let q = p.mul(s, params); let (x1, y1) = q.into_xy(); @@ -805,7 +805,7 @@ mod test { let q = fixed_base_multiplication( cs.namespace(|| "multiplication"), - FixedGenerators::NoteCommitmentRandomization, + FixedGenerators::NoteCommitmentRandomness, &s_bits, params ).unwrap(); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index bd259ff..13495bc 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -67,8 +67,11 @@ impl JubjubEngine for Bls12 { /// exponent. #[derive(Copy, Clone)] pub enum FixedGenerators { - NoteCommitmentRandomization = 0, - Max = 1 + NoteCommitmentRandomness = 0, + ProvingPublicKey = 1, + ValueCommitmentValue = 2, + ValueCommitmentRandomness = 3, + Max = 4 } pub struct JubjubBls12 { From 39175a0c2a55b1d74d6dd7123b3f0dcb551b47e3 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 12:54:26 -0700 Subject: [PATCH 050/168] Remove unneeded imports --- src/circuit/num.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index a6505f6..0bd8014 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -2,7 +2,6 @@ use pairing::{ Engine, Field, PrimeField, - BitIterator }; use bellman::{ @@ -19,7 +18,6 @@ use super::{ use super::boolean::{ self, Boolean, - AllocatedBit }; pub struct AllocatedNum { From d779f31ccde71092fae86469930684ded4680f2e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 13:16:41 -0700 Subject: [PATCH 051/168] Force personalization of Pedersen hashes. --- src/circuit/pedersen_hash.rs | 47 ++++++++++++++++++++++++++++++++---- src/pedersen_hash.rs | 5 +++- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index a405b7a..c540a23 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -10,18 +10,42 @@ use bellman::{ }; use super::lookup::*; +pub enum Personalization { + NoteCommitment, + AnotherPersonalization +} + +impl Personalization { + fn get_constant_bools(&self) -> Vec { + self.get_bits() + .into_iter() + .map(|e| Boolean::constant(e)) + .collect() + } + + pub fn get_bits(&self) -> Vec { + match *self { + Personalization::NoteCommitment => + vec![false, false, false, false, false, false], + Personalization::AnotherPersonalization => + vec![false, false, false, false, false, true], + } + } +} + pub fn pedersen_hash( mut cs: CS, + personalization: Personalization, bits: &[Boolean], params: &E::Params ) -> Result, SynthesisError> where CS: ConstraintSystem { - // Unnecessary if forced personalization is introduced - assert!(bits.len() > 0); + let personalization = personalization.get_constant_bools(); + assert_eq!(personalization.len(), 6); let mut edwards_result = None; - let mut bits = bits.iter(); + let mut bits = personalization.iter().chain(bits.iter()); let mut segment_generators = params.pedersen_circuit_generators().iter(); let boolean_false = Boolean::constant(false); @@ -124,12 +148,13 @@ mod test { pedersen_hash( cs.namespace(|| "pedersen hash"), + Personalization::NoteCommitment, &input_bools, params ).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1539); + assert_eq!(cs.num_constraints(), 1551); } #[test] @@ -151,6 +176,7 @@ mod test { let res = pedersen_hash( cs.namespace(|| "pedersen hash"), + Personalization::NoteCommitment, &input_bools, params ).unwrap(); @@ -158,12 +184,23 @@ mod test { assert!(cs.is_satisfied()); let expected = ::pedersen_hash::pedersen_hash::( - input.into_iter(), + Personalization::NoteCommitment, + input.clone().into_iter(), params ).into_xy(); assert_eq!(res.x.get_value().unwrap(), expected.0); assert_eq!(res.y.get_value().unwrap(), expected.1); + + // Test against the output of a different personalization + let unexpected = ::pedersen_hash::pedersen_hash::( + Personalization::AnotherPersonalization, + input.into_iter(), + params + ).into_xy(); + + assert!(res.x.get_value().unwrap() != unexpected.0); + assert!(res.y.get_value().unwrap() != unexpected.1); } } } diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 0bbf7a9..12e5c7d 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -1,14 +1,17 @@ use jubjub::*; use pairing::*; +use circuit::pedersen_hash::Personalization; + pub fn pedersen_hash( + personalization: Personalization, bits: I, params: &E::Params ) -> edwards::Point where I: IntoIterator, E: JubjubEngine { - let mut bits = bits.into_iter(); + let mut bits = personalization.get_bits().into_iter().chain(bits.into_iter()); let mut result = edwards::Point::zero(); let mut generators = params.pedersen_hash_generators().iter(); From 6e80c12365c13db0c902830561c568bcd9130c3f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 14:31:06 -0700 Subject: [PATCH 052/168] Add TODO --- src/circuit/pedersen_hash.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index c540a23..55f9cfb 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -10,6 +10,7 @@ use bellman::{ }; use super::lookup::*; +// TODO: ensure these match the spec pub enum Personalization { NoteCommitment, AnotherPersonalization From b37d9b11cb6b38de9c22182e304e6f7dd3789560 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 15:38:28 -0700 Subject: [PATCH 053/168] More efficient implementation of Edwards doubling in the circuit. --- src/circuit/mont.rs | 108 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index f190536..7c484d5 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -238,12 +238,116 @@ impl EdwardsPoint { pub fn double( &self, - cs: CS, + mut cs: CS, params: &E::Params ) -> Result where CS: ConstraintSystem { - self.add(cs, self, params) + // 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 From 6f66fd3f9d959fa04bcbcf1e10c9bdc2ed80c7f8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 16:11:48 -0700 Subject: [PATCH 054/168] Express x and y coordinates from lookup as linear combinations. --- src/circuit/lookup.rs | 22 ++++++++++++---------- src/circuit/pedersen_hash.rs | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs index e433cd0..e002609 100644 --- a/src/circuit/lookup.rs +++ b/src/circuit/lookup.rs @@ -169,24 +169,26 @@ pub fn lookup3_xy_with_conditional_negation( 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])?; + cs.enforce( || "x-coordinate lookup", - |lc| lc + (x_coeffs[0b01], one) - + &bits[1].lc::(one, x_coeffs[0b11]), - |lc| lc + &bits[0].lc::(one, E::Fr::one()), + |lc| lc + (x_coeffs[0b00], one) + + &bits[0].lc::(one, x_coeffs[0b01]) + + &bits[1].lc::(one, x_coeffs[0b10]) + + &precomp.lc::(one, x_coeffs[0b11]), + |lc| lc + one, |lc| lc + res_x.get_variable() - - (x_coeffs[0b00], one) - - &bits[1].lc::(one, x_coeffs[0b10]) ); cs.enforce( || "y-coordinate lookup", - |lc| lc + (y_coeffs[0b01], one) - + &bits[1].lc::(one, y_coeffs[0b11]), - |lc| lc + &bits[0].lc::(one, E::Fr::one()), + |lc| lc + (y_coeffs[0b00], one) + + &bits[0].lc::(one, y_coeffs[0b01]) + + &bits[1].lc::(one, y_coeffs[0b10]) + + &precomp.lc::(one, y_coeffs[0b11]), + |lc| lc + one, |lc| lc + res_y.get_variable() - - (y_coeffs[0b00], one) - - &bits[1].lc::(one, y_coeffs[0b10]) ); let final_y = res_y.conditionally_negate(&mut cs, &bits[2])?; diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 55f9cfb..68aab98 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -155,7 +155,7 @@ mod test { ).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1551); + assert_eq!(cs.num_constraints(), 1721); } #[test] From 1610bcfbcfe2a23872b7ebcd781e52db402fab50 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 16:31:27 -0700 Subject: [PATCH 055/168] Perform the y-coordinate conditional negation and lookup simultaneously. --- src/circuit/lookup.rs | 25 +++++++++++++++---------- src/circuit/pedersen_hash.rs | 2 +- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs index e002609..d1123fb 100644 --- a/src/circuit/lookup.rs +++ b/src/circuit/lookup.rs @@ -154,10 +154,15 @@ pub fn lookup3_xy_with_conditional_negation( )?; // Allocate the y-coordinate resulting from the lookup + // and conditional negation let res_y = AllocatedNum::alloc( cs.namespace(|| "y"), || { - Ok(coords[*i.get()?].1) + let mut tmp = coords[*i.get()?].1; + if *bits[2].get_value().get()? { + tmp.negate(); + } + Ok(tmp) } )?; @@ -181,19 +186,19 @@ pub fn lookup3_xy_with_conditional_negation( |lc| lc + res_x.get_variable() ); + 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_coeffs[0b00], one) - + &bits[0].lc::(one, y_coeffs[0b01]) - + &bits[1].lc::(one, y_coeffs[0b10]) - + &precomp.lc::(one, y_coeffs[0b11]), - |lc| lc + one, - |lc| lc + res_y.get_variable() + |lc| lc + &y_lc + &y_lc, + |lc| lc + &bits[2].lc::(one, E::Fr::one()), + |lc| lc + &y_lc - res_y.get_variable() ); - let final_y = res_y.conditionally_negate(&mut cs, &bits[2])?; - - Ok((res_x, final_y)) + Ok((res_x, res_y)) } #[cfg(test)] diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 68aab98..19e56b8 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -155,7 +155,7 @@ mod test { ).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1721); + assert_eq!(cs.num_constraints(), 1549); } #[test] From c89d47bb07f9d7d6d14246470b021325462909ff Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 16:38:44 -0700 Subject: [PATCH 056/168] Remove Montgomery point doubling implementation in the circuit. --- src/circuit/mont.rs | 180 -------------------------------------------- 1 file changed, 180 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 7c484d5..798e041 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -650,109 +650,6 @@ impl MontgomeryPoint { y: yprime }) } - - /// Performs an affine point doubling, not defined for - /// the point of order two (0, 0). - pub fn double( - &self, - mut cs: CS, - params: &E::Params - ) -> Result - where CS: ConstraintSystem - { - // Square x - let xx = self.x.square(&mut cs)?; - - // Compute lambda = (3.xx + 2.A.x + 1) / 2.y - let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || { - let mut t0 = *xx.get_value().get()?; - let mut t1 = t0; - t0.double(); // t0 = 2.xx - t0.add_assign(&t1); // t0 = 3.xx - t1 = *self.x.get_value().get()?; // t1 = x - t1.mul_assign(params.montgomery_2a()); // t1 = 2.A.x - t0.add_assign(&t1); - t0.add_assign(&E::Fr::one()); - t1 = *self.y.get_value().get()?; // t1 = y - t1.double(); // t1 = 2.y - match t1.inverse() { - Some(t1) => { - t0.mul_assign(&t1); - - Ok(t0) - }, - None => { - Err(SynthesisError::DivisionByZero) - } - } - })?; - - // (2.y) * (lambda) = (3.xx + 2.A.x + 1) - let one = CS::one(); - cs.enforce( - || "evaluate lambda", - |lc| lc + self.y.get_variable() - + self.y.get_variable(), - - |lc| lc + lambda.get_variable(), - - |lc| lc + xx.get_variable() - + xx.get_variable() - + xx.get_variable() - + (*params.montgomery_2a(), self.x.get_variable()) - + one - ); - - // Compute x' = (lambda^2) - A - 2.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(self.x.get_value().get()?); - - Ok(t0) - })?; - - // (lambda) * (lambda) = (A + 2.x + x') - cs.enforce( - || "evaluate xprime", - |lc| lc + lambda.get_variable(), - |lc| lc + lambda.get_variable(), - |lc| lc + (*params.montgomery_a(), one) - + self.x.get_variable() - + self.x.get_variable() - + 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.get_variable() - - xprime.get_variable(), - - |lc| lc + lambda.get_variable(), - - |lc| lc + yprime.get_variable() - + self.y.get_variable() - ); - - Ok(MontgomeryPoint { - x: xprime, - y: yprime - }) - } } #[cfg(test)] @@ -863,27 +760,6 @@ mod test { } } - #[test] - fn test_doubling_order_2() { - let params = &JubjubBls12::new(); - - let mut cs = TestConstraintSystem::::new(); - - let x = AllocatedNum::alloc(cs.namespace(|| "x"), || { - Ok(Fr::zero()) - }).unwrap(); - let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { - Ok(Fr::zero()) - }).unwrap(); - - let p = MontgomeryPoint { - x: x, - y: y - }; - - assert!(p.double(&mut cs, params).is_err()); - } - #[test] fn test_edwards_fixed_base_multiplication() { let params = &JubjubBls12::new(); @@ -1222,60 +1098,4 @@ mod test { assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate lambda")); } } - - #[test] - fn test_montgomery_doubling() { - let params = &JubjubBls12::new(); - let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - for _ in 0..100 { - let p = 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 = p.double(params); - - let (x0, y0) = p.into_xy().unwrap(); - let (x1, y1) = p2.into_xy().unwrap(); - - let mut cs = TestConstraintSystem::::new(); - - let x = AllocatedNum::alloc(cs.namespace(|| "x"), || { - Ok(x0) - }).unwrap(); - let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { - Ok(y0) - }).unwrap(); - - let p = MontgomeryPoint { - x: x, - y: y - }; - - let p2 = p.double(cs.namespace(|| "doubling"), params).unwrap(); - - assert!(cs.is_satisfied()); - - assert!(p2.x.get_value().unwrap() == x1); - assert!(p2.y.get_value().unwrap() == y1); - - cs.set("doubling/yprime/num", rng.gen()); - assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate yprime")); - cs.set("doubling/yprime/num", y1); - assert!(cs.is_satisfied()); - - cs.set("doubling/xprime/num", rng.gen()); - assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate xprime")); - cs.set("doubling/xprime/num", x1); - assert!(cs.is_satisfied()); - - cs.set("doubling/lambda/num", rng.gen()); - assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate lambda")); - } - } } From 4fa73efc1e6c6e19be108d9fd7f51c6554fccc27 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 16:41:33 -0700 Subject: [PATCH 057/168] Remove conditional negation implementation from AllocatedNum. --- src/circuit/num.rs | 133 --------------------------------------------- 1 file changed, 133 deletions(-) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 0bd8014..310bd95 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -312,38 +312,6 @@ impl AllocatedNum { Ok((c, d)) } - pub fn conditionally_negate( - &self, - mut cs: CS, - condition: &Boolean - ) -> Result - where CS: ConstraintSystem - { - let r = Self::alloc( - cs.namespace(|| "conditional negation result"), - || { - let mut tmp = *self.value.get()?; - if *condition.get_value().get()? { - tmp.negate(); - } - Ok(tmp) - } - )?; - - // (1-c)(x) + (c)(-x) = r - // x - 2cx = r - // (2x) * (c) = x - r - - cs.enforce( - || "conditional negation", - |lc| lc + self.variable + self.variable, - |_| condition.lc(CS::one(), E::Fr::one()), - |lc| lc + self.variable - r.variable - ); - - Ok(r) - } - pub fn get_value(&self) -> Option { self.value } @@ -433,107 +401,6 @@ mod test { } } - #[test] - fn test_num_conditional_negation() { - { - let mut cs = TestConstraintSystem::::new(); - - let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); - let b = Boolean::constant(true); - let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); - - let mut negone = Fr::one(); - negone.negate(); - - assert!(cs.is_satisfied()); - assert!(cs.get("conditional negation result/num") == negone); - assert!(n2.value.unwrap() == negone); - cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); - assert!(!cs.is_satisfied()); - } - { - let mut cs = TestConstraintSystem::::new(); - - let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); - let b = Boolean::constant(false); - let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); - - assert!(cs.is_satisfied()); - assert!(cs.get("conditional negation result/num") == Fr::one()); - assert!(n2.value.unwrap() == Fr::one()); - cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); - assert!(!cs.is_satisfied()); - } - - { - let mut cs = TestConstraintSystem::::new(); - - let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); - let b = Boolean::from( - AllocatedBit::alloc(cs.namespace(|| "condition"), Some(true)).unwrap() - ); - let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); - - let mut negone = Fr::one(); - negone.negate(); - - assert!(cs.is_satisfied()); - assert!(cs.get("conditional negation result/num") == negone); - assert!(n2.value.unwrap() == negone); - cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); - assert!(!cs.is_satisfied()); - } - { - let mut cs = TestConstraintSystem::::new(); - - let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); - let b = Boolean::from( - AllocatedBit::alloc(cs.namespace(|| "condition"), Some(false)).unwrap() - ); - let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); - - assert!(cs.is_satisfied()); - assert!(cs.get("conditional negation result/num") == Fr::one()); - assert!(n2.value.unwrap() == Fr::one()); - cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); - assert!(!cs.is_satisfied()); - } - - { - let mut cs = TestConstraintSystem::::new(); - - let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); - let b = Boolean::from( - AllocatedBit::alloc(cs.namespace(|| "condition"), Some(false)).unwrap() - ).not(); - let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); - - let mut negone = Fr::one(); - negone.negate(); - - assert!(cs.is_satisfied()); - assert!(cs.get("conditional negation result/num") == negone); - assert!(n2.value.unwrap() == negone); - cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); - assert!(!cs.is_satisfied()); - } - { - let mut cs = TestConstraintSystem::::new(); - - let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); - let b = Boolean::from( - AllocatedBit::alloc(cs.namespace(|| "condition"), Some(true)).unwrap() - ).not(); - let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); - - assert!(cs.is_satisfied()); - assert!(cs.get("conditional negation result/num") == Fr::one()); - assert!(n2.value.unwrap() == Fr::one()); - cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); - assert!(!cs.is_satisfied()); - } - } - #[test] fn test_num_nonzero() { { From 88bdff6ce988cab42e60115a1a5f0ff6b149d886 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 17:22:00 -0700 Subject: [PATCH 058/168] Pedersen hashes with full optimization --- src/circuit/lookup.rs | 35 +++++++++-------------- src/circuit/mont.rs | 51 +++++++++++++++++---------------- src/circuit/num.rs | 55 ++++++++++++++++++++++++++++++++++++ src/circuit/pedersen_hash.rs | 2 +- 4 files changed, 96 insertions(+), 47 deletions(-) diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs index d1123fb..1ffc7f7 100644 --- a/src/circuit/lookup.rs +++ b/src/circuit/lookup.rs @@ -1,6 +1,9 @@ use pairing::{Engine, Field}; use super::*; -use super::num::AllocatedNum; +use super::num::{ + AllocatedNum, + Num +}; use super::boolean::Boolean; use bellman::{ ConstraintSystem @@ -123,7 +126,7 @@ pub fn lookup3_xy_with_conditional_negation( mut cs: CS, bits: &[Boolean], coords: &[(E::Fr, E::Fr)] -) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> +) -> Result<(Num, Num), SynthesisError> where CS: ConstraintSystem { assert_eq!(bits.len(), 3); @@ -145,17 +148,9 @@ pub fn lookup3_xy_with_conditional_negation( _ => 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 // and conditional negation - let res_y = AllocatedNum::alloc( + let y = AllocatedNum::alloc( cs.namespace(|| "y"), || { let mut tmp = coords[*i.get()?].1; @@ -176,15 +171,11 @@ pub fn lookup3_xy_with_conditional_negation( let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[0], &bits[1])?; - cs.enforce( - || "x-coordinate lookup", - |lc| lc + (x_coeffs[0b00], one) - + &bits[0].lc::(one, x_coeffs[0b01]) - + &bits[1].lc::(one, x_coeffs[0b10]) - + &precomp.lc::(one, x_coeffs[0b11]), - |lc| lc + one, - |lc| lc + res_x.get_variable() - ); + 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]) + @@ -195,10 +186,10 @@ pub fn lookup3_xy_with_conditional_negation( || "y-coordinate lookup", |lc| lc + &y_lc + &y_lc, |lc| lc + &bits[2].lc::(one, E::Fr::one()), - |lc| lc + &y_lc - res_y.get_variable() + |lc| lc + &y_lc - y.get_variable() ); - Ok((res_x, res_y)) + Ok((x, y.into())) } #[cfg(test)] diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 798e041..b470a6c 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -12,7 +12,10 @@ use super::{ Assignment }; -use super::num::AllocatedNum; +use super::num::{ + AllocatedNum, + Num +}; use ::jubjub::{ JubjubEngine, @@ -470,8 +473,8 @@ impl EdwardsPoint { } pub struct MontgomeryPoint { - x: AllocatedNum, - y: AllocatedNum + x: Num, + y: Num } impl MontgomeryPoint { @@ -504,9 +507,9 @@ impl MontgomeryPoint { cs.enforce( || "u computation", - |lc| lc + self.y.get_variable(), + |lc| lc + &self.y.lc(E::Fr::one()), |lc| lc + u.get_variable(), - |lc| lc + (*params.scale(), self.x.get_variable()) + |lc| lc + &self.x.lc(*params.scale()) ); // Compute v = (x - 1) / (x + 1) @@ -531,10 +534,10 @@ impl MontgomeryPoint { let one = CS::one(); cs.enforce( || "v computation", - |lc| lc + self.x.get_variable() + |lc| lc + &self.x.lc(E::Fr::one()) + one, |lc| lc + v.get_variable(), - |lc| lc + self.x.get_variable() + |lc| lc + &self.x.lc(E::Fr::one()) - one, ); @@ -549,8 +552,8 @@ impl MontgomeryPoint { /// on the curve. Useful for constants and /// window table lookups. pub fn interpret_unchecked( - x: AllocatedNum, - y: AllocatedNum + x: Num, + y: Num ) -> Self { MontgomeryPoint { @@ -590,13 +593,13 @@ impl MontgomeryPoint { cs.enforce( || "evaluate lambda", - |lc| lc + other.x.get_variable() - - self.x.get_variable(), + |lc| lc + &other.x.lc(E::Fr::one()) + - &self.x.lc(E::Fr::one()), |lc| lc + lambda.get_variable(), - |lc| lc + other.y.get_variable() - - self.y.get_variable() + |lc| lc + &other.y.lc(E::Fr::one()) + - &self.y.lc(E::Fr::one()) ); // Compute x'' = lambda^2 - A - x - x' @@ -617,8 +620,8 @@ impl MontgomeryPoint { |lc| lc + lambda.get_variable(), |lc| lc + lambda.get_variable(), |lc| lc + (*params.montgomery_a(), one) - + self.x.get_variable() - + other.x.get_variable() + + &self.x.lc(E::Fr::one()) + + &other.x.lc(E::Fr::one()) + xprime.get_variable() ); @@ -636,18 +639,18 @@ impl MontgomeryPoint { // y' + y = lambda(x - x') cs.enforce( || "evaluate yprime", - |lc| lc + self.x.get_variable() + |lc| lc + &self.x.lc(E::Fr::one()) - xprime.get_variable(), |lc| lc + lambda.get_variable(), |lc| lc + yprime.get_variable() - + self.y.get_variable() + + &self.y.lc(E::Fr::one()) ); Ok(MontgomeryPoint { - x: xprime, - y: yprime + x: xprime.into(), + y: yprime.into() }) } } @@ -697,7 +700,7 @@ mod test { Ok(y) }).unwrap(); - let p = MontgomeryPoint::interpret_unchecked(numx, numy); + let p = MontgomeryPoint::interpret_unchecked(numx.into(), numy.into()); let q = p.into_edwards(&mut cs, params).unwrap(); @@ -1068,13 +1071,13 @@ mod test { }).unwrap(); let p1 = MontgomeryPoint { - x: num_x0, - y: num_y0 + x: num_x0.into(), + y: num_y0.into() }; let p2 = MontgomeryPoint { - x: num_x1, - y: num_y1 + x: num_x1.into(), + y: num_y1.into() }; let p3 = p1.add(cs.namespace(|| "addition"), &p2, params).unwrap(); diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 310bd95..fe20050 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -321,6 +321,61 @@ impl AllocatedNum { } } +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(mut 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}; diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 19e56b8..258ed94 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -155,7 +155,7 @@ mod test { ).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1549); + assert_eq!(cs.num_constraints(), 1377); } #[test] From a1c749e6a0f80af531b89fac46e7caa1ae3a5779 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 18:12:27 -0700 Subject: [PATCH 059/168] Change fixed-base exponentiation API to handle scalars better. --- src/circuit/mont.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index b470a6c..48e419e 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -44,8 +44,7 @@ impl Clone for EdwardsPoint { } /// Perform a fixed-base scalar multiplication with -/// `by` being in little-endian bit order. `by` must -/// be a multiple of 3. +/// `by` being in little-endian bit order. pub fn fixed_base_multiplication( mut cs: CS, base: FixedGenerators, @@ -55,11 +54,6 @@ pub fn fixed_base_multiplication( where CS: ConstraintSystem, E: JubjubEngine { - // We're going to chunk the scalar into 3-bit windows, - // so let's force the caller to supply the right number - // of bits for our lookups. - assert!(by.len() % 3 == 0); - // Represents the result of the multiplication let mut result = None; @@ -67,9 +61,13 @@ pub fn fixed_base_multiplication( .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, + &[chunk_a, chunk_b, chunk_c], window )?; From c221bc912613037b3a50ddcffd5f629f6bca789e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 18:18:29 -0700 Subject: [PATCH 060/168] Rename `mont` to `ecc` in circuit code. --- src/circuit/{mont.rs => ecc.rs} | 0 src/circuit/mod.rs | 2 +- src/circuit/pedersen_hash.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/circuit/{mont.rs => ecc.rs} (100%) diff --git a/src/circuit/mont.rs b/src/circuit/ecc.rs similarity index 100% rename from src/circuit/mont.rs rename to src/circuit/ecc.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 45de18f..cf27c34 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -6,7 +6,7 @@ pub mod uint32; pub mod blake2s; pub mod num; pub mod lookup; -pub mod mont; +pub mod ecc; pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 258ed94..9dfb460 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -1,5 +1,5 @@ use super::*; -use super::mont::{ +use super::ecc::{ MontgomeryPoint, EdwardsPoint }; From e8480a2b2cbf3be5ad7f1f3f994be3fb82471978 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 18:36:53 -0700 Subject: [PATCH 061/168] Utility for witnessing points on the curve. --- src/circuit/ecc.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 48e419e..fbb9b72 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -18,6 +18,7 @@ use super::num::{ }; use ::jubjub::{ + edwards, JubjubEngine, JubjubParams, FixedGenerators @@ -91,6 +92,41 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + /// 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 + ) + } + /// This extracts the x-coordinate, which is an injective /// encoding for elements of the prime order subgroup. pub fn into_num(&self) -> AllocatedNum { @@ -723,6 +759,23 @@ mod test { 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(); From 4b6623cf44ed8020845f67539bb6cefeb7539e48 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 22 Feb 2018 07:29:55 -0700 Subject: [PATCH 062/168] Add some personalization and a generator. --- src/circuit/pedersen_hash.rs | 5 ++++- src/jubjub/mod.rs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 9dfb460..7eec3bb 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -13,7 +13,8 @@ use super::lookup::*; // TODO: ensure these match the spec pub enum Personalization { NoteCommitment, - AnotherPersonalization + AnotherPersonalization, + MerkleTree(usize) } impl Personalization { @@ -30,6 +31,8 @@ impl Personalization { vec![false, false, false, false, false, false], Personalization::AnotherPersonalization => vec![false, false, false, false, false, true], + Personalization::MerkleTree(_) => + vec![false, false, false, false, true, false], } } } diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 13495bc..4782d90 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -71,7 +71,8 @@ pub enum FixedGenerators { ProvingPublicKey = 1, ValueCommitmentValue = 2, ValueCommitmentRandomness = 3, - Max = 4 + NullifierPosition = 4, + Max = 5 } pub struct JubjubBls12 { From ba7298de3ff629c0326969189fd922f177e558b5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 22 Feb 2018 11:36:44 -0700 Subject: [PATCH 063/168] Crappy mock-up of the circuit. --- src/circuit/mod.rs | 832 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 831 insertions(+), 1 deletion(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index cf27c34..073926a 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -9,7 +9,23 @@ pub mod lookup; pub mod ecc; pub mod pedersen_hash; -use bellman::SynthesisError; +use pairing::{ + PrimeField, + PrimeFieldRepr, +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + Circuit +}; + +use jubjub::{ + JubjubEngine, + Unknown, + FixedGenerators, + edwards +}; trait Assignment { fn get(&self) -> Result<&T, SynthesisError>; @@ -23,3 +39,817 @@ impl Assignment for Option { } } } + +const MERKLE_TREE_DEPTH: usize = 29; + +pub struct Spend<'a, E: JubjubEngine> { + pub params: &'a E::Params, + /// Value of the note being spent + pub value: Option, + /// Randomness that will hide the value + pub value_randomness: Option, + /// Key which allows the proof to be constructed + /// as defense-in-depth against a flaw in the + /// protocol that would otherwise be exploitable + /// by a holder of a viewing key. + pub rsk: Option, + /// The public key that will be re-randomized for + /// use as a nullifier and signing key for the + /// transaction. + pub ak: Option>, + /// The diversified base used to compute pk_d. + pub g_d: Option>, + /// The randomness used to hide the note commitment data + pub commitment_randomness: Option, + /// The authentication path of the commitment in the tree + pub auth_path: Vec> +} + +impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_allocated_bits_be( + cs.namespace(|| "value"), + self.value + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + { + let gv = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + self.params + )?; + + // Booleanize the randomness + let hr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "hr"), + self.value_randomness + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + self.params + )?; + + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + self.params + )?; + + // Expose the value commitment publicly + let value_commitment_x = cs.alloc_input( + || "value commitment x", + || { + Ok(*gvhr.x.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment x equals input", + |lc| lc + value_commitment_x, + |lc| lc + CS::one(), + |lc| lc + gvhr.x.get_variable() + ); + + let value_commitment_y = cs.alloc_input( + || "value commitment y", + || { + Ok(*gvhr.y.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment y equals input", + |lc| lc + value_commitment_y, + |lc| lc + CS::one(), + |lc| lc + gvhr.y.get_variable() + ); + } + + // Compute rk = [rsk] ProvingPublicKey + let rk; + { + // Witness rsk as bits + let rsk = boolean::field_into_allocated_bits_be( + cs.namespace(|| "rsk"), + self.rsk + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)).collect::>(); + + // NB: We don't ensure that the bit representation of rsk + // 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. + + rk = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of rk"), + FixedGenerators::ProvingPublicKey, + &rsk, + self.params + )?; + } + + // Prover witnesses ak (ensures that it's on the curve) + let ak = ecc::EdwardsPoint::witness( + cs.namespace(|| "ak"), + self.ak, + self.params + )?; + + // Unpack ak and rk for input to BLAKE2s + let mut vk = vec![]; + { + let mut ak_x = ak.x.into_bits_strict( + cs.namespace(|| "unpack ak.x") + )?; + let mut ak_y = ak.y.into_bits_strict( + cs.namespace(|| "unpack ak.y") + )?; + + // We want the representation in little endian bit order + ak_x.reverse(); + ak_y.reverse(); + + vk.extend(ak_y); + vk.push(ak_x[0].clone()); + } + let mut rho_preimage = vec![]; + { + let mut rk_x = rk.x.into_bits_strict( + cs.namespace(|| "unpack rk.x") + )?; + let mut rk_y = rk.y.into_bits_strict( + cs.namespace(|| "unpack rk.y") + )?; + + // We want the representation in little endian bit order + rk_x.reverse(); + rk_y.reverse(); + + vk.extend(rk_y.iter().cloned()); + vk.push(rk_x[0].clone()); + + rho_preimage.extend(rk_y.iter().cloned()); + rho_preimage.push(rk_x[0].clone()); + } + + assert_eq!(vk.len(), 512); + + // Compute the incoming viewing key + let mut ivk = blake2s::blake2s( + cs.namespace(|| "computation of ivk"), + &vk + )?; + + // Little endian bit order + ivk.reverse(); + ivk.truncate(251); // drop_5 + + // Witness g_d + let g_d = ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.g_d, + self.params + )?; + + // Compute pk_d + let pk_d = g_d.mul( + cs.namespace(|| "compute pk_d"), + &ivk, + self.params + )?; + + // Compute note contents + let mut note_contents = vec![]; + note_contents.extend(value_bits); + { + // Unpack g_d for inclusion in the note. + let mut g_d_x = g_d.x.into_bits_strict( + cs.namespace(|| "unpack g_d.x") + )?; + let mut g_d_y = g_d.y.into_bits_strict( + cs.namespace(|| "unpack g_d.y") + )?; + + // We want the representation in little endian bit order + g_d_x.reverse(); + g_d_y.reverse(); + + note_contents.extend(g_d_y); + note_contents.push(g_d_x[0].clone()); + } + { + // Unpack g_d for inclusion in the note. + let mut pk_d_x = pk_d.x.into_bits_strict( + cs.namespace(|| "unpack pk_d.x") + )?; + let mut pk_d_y = pk_d.y.into_bits_strict( + cs.namespace(|| "unpack pk_d.y") + )?; + + // We want the representation in little endian bit order + pk_d_x.reverse(); + pk_d_y.reverse(); + + note_contents.extend(pk_d_y); + note_contents.push(pk_d_x[0].clone()); + } + + 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 + let cmr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "cmr"), + self.commitment_randomness + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + self.params + )?; + } + + assert_eq!(self.auth_path.len(), MERKLE_TREE_DEPTH); + + let mut position_bits = vec![]; + + // Injective encoding. + let mut cur = cm.x.clone(); + + for (i, e) in self.auth_path.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); + + let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "position bit"), + e.map(|e| e.1) + )?); + + position_bits.push(cur_is_right.clone()); + + let path_element = num::AllocatedNum::alloc( + cs.namespace(|| "path element"), + || { + Ok(e.get()?.0) + } + )?; + + 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. + let mut preimage = vec![]; + preimage.extend(xl.into_bits(cs.namespace(|| "xl into bits"))?); + preimage.extend(xr.into_bits(cs.namespace(|| "xr into bits"))?); + + cur = pedersen_hash::pedersen_hash( + cs.namespace(|| "computation of pedersen hash"), + pedersen_hash::Personalization::MerkleTree(MERKLE_TREE_DEPTH - i), + &preimage, + self.params + )?.x; // Injective encoding + } + + assert_eq!(position_bits.len(), MERKLE_TREE_DEPTH); + + // TODO: cur is now the root of the tree, expose it as public input + + let tmp = ecc::fixed_base_multiplication( + cs.namespace(|| "g^position"), + FixedGenerators::NullifierPosition, + &position_bits, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "faerie gold prevention"), + &tmp, + self.params + )?; + + // Let's compute rho = BLAKE2s(rk || cm + position) + { + // Unpack g_d for inclusion in the note. + let mut cm_x = cm.x.into_bits_strict( + cs.namespace(|| "unpack (cm + position).x") + )?; + let mut cm_y = cm.y.into_bits_strict( + cs.namespace(|| "unpack (cm + position).y") + )?; + + // We want the representation in little endian bit order + cm_x.reverse(); + cm_y.reverse(); + + rho_preimage.extend(cm_y); + rho_preimage.push(cm_x[0].clone()); + } + + let mut rho = blake2s::blake2s( + cs.namespace(|| "rho computation"), + &rho_preimage + )?; + + // Little endian bit order + rho.reverse(); + rho.truncate(251); // drop_5 + + // Compute nullifier + let nf = ak.mul( + cs.namespace(|| "computation of nf"), + &rho, + self.params + )?; + + // TODO: expose nf as public input + + Ok(()) + } +} + +#[test] +fn test_input_circuit_with_bls12_381() { + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let value: u64 = 1; + let value_randomness: fs::Fs = rng.gen(); + let ak: edwards::Point = edwards::Point::rand(rng, params); + let g_d: edwards::Point = edwards::Point::rand(rng, params); + let p_d: edwards::Point = edwards::Point::rand(rng, params); + let commitment_randomness: fs::Fs = rng.gen(); + let esk: fs::Fs = rng.gen(); + let rsk: fs::Fs = rng.gen(); + let auth_path = vec![Some((rng.gen(), false)); 29]; + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Spend { + params: params, + value: Some(1), + value_randomness: Some(value_randomness), + rsk: Some(rsk), + ak: Some(ak), + g_d: Some(g_d), + commitment_randomness: Some(commitment_randomness), + auth_path: auth_path + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(cs.num_constraints(), 99816); + } + + // use bellman::groth16::*; + + // let groth_params = generate_random_parameters::(Spend { + // params: params, + // value: None, + // value_randomness: None, + // rsk: None, + // ak: None, + // g_d: None, + // commitment_randomness: None, + // auth_path: vec![None; 29] + // }, rng).unwrap(); + + // let pvk = prepare_verifying_key(&groth_params.vk); + + // use std::time::{Duration, Instant}; + + // // Let's benchmark stuff! + // const SAMPLES: u32 = 50; + // let mut total_proving = Duration::new(0, 0); + + // for _ in 0..SAMPLES { + // let start = Instant::now(); + // { + // let c = Spend { + // params: params, + // value: Some(1), + // value_randomness: Some(value_randomness.clone()), + // rsk: Some(rsk.clone()), + // ak: Some(ak.clone()), + // g_d: Some(g_d.clone()), + // commitment_randomness: Some(commitment_randomness.clone()), + // auth_path: auth_path.clone() + // }; + + // create_random_proof(c, &groth_params, rng).unwrap(); + // } + // total_proving += start.elapsed(); + // } + + // let proving_avg = total_proving / SAMPLES; + // let proving_avg = proving_avg.subsec_nanos() as f64 / 1_000_000_000f64 + // + (proving_avg.as_secs() as f64); + + // panic!("Average proving time: {:?} seconds", proving_avg); +} + +/// This is an output circuit instance. +pub struct Output<'a, E: JubjubEngine> { + pub params: &'a E::Params, + /// Value of the note being created + pub value: Option, + /// Randomness that will hide the value + pub value_randomness: Option, + /// The diversified base, computed by GH(d) + pub g_d: Option>, + /// The diversified address point, computed by GH(d)^ivk + pub p_d: 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 +} + +impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_allocated_bits_be( + cs.namespace(|| "value"), + self.value + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + { + let gv = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + self.params + )?; + + // Booleanize the randomness + let hr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "hr"), + self.value_randomness + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + self.params + )?; + + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + self.params + )?; + + // Expose the value commitment publicly + let value_commitment_x = cs.alloc_input( + || "value commitment x", + || { + Ok(*gvhr.x.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment x equals input", + |lc| lc + value_commitment_x, + |lc| lc + CS::one(), + |lc| lc + gvhr.x.get_variable() + ); + + let value_commitment_y = cs.alloc_input( + || "value commitment y", + || { + Ok(*gvhr.y.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment y equals input", + |lc| lc + value_commitment_y, + |lc| lc + CS::one(), + |lc| lc + gvhr.y.get_variable() + ); + } + + // Let's start to construct our note + let mut note_contents = vec![]; + note_contents.extend(value_bits); + + // Let's deal with g_d + { + let g_d = ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.g_d, + self.params + )?; + + // Check that g_d is not of small order + { + let g_d = g_d.double( + cs.namespace(|| "first doubling of g_d"), + self.params + )?; + let g_d = g_d.double( + cs.namespace(|| "second doubling of g_d"), + self.params + )?; + let g_d = g_d.double( + cs.namespace(|| "third doubling of g_d"), + self.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. + g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?; + } + + // Unpack g_d for inclusion in the note. + let mut g_d_x = g_d.x.into_bits_strict( + cs.namespace(|| "unpack g_d.x") + )?; + let mut g_d_y = g_d.y.into_bits_strict( + cs.namespace(|| "unpack g_d.y") + )?; + + // We want the representation in little endian bit order + g_d_x.reverse(); + g_d_y.reverse(); + + note_contents.extend(g_d_y); + note_contents.push(g_d_x[0].clone()); + + // Compute epk from esk + let esk = boolean::field_into_allocated_bits_be( + cs.namespace(|| "esk"), + self.esk + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let epk = g_d.mul( + cs.namespace(|| "epk computation"), + &esk, + self.params + )?; + + // Expose epk publicly + let epk_x = cs.alloc_input( + || "epk x", + || { + Ok(*epk.x.get_value().get()?) + } + )?; + + cs.enforce( + || "epk x equals input", + |lc| lc + epk_x, + |lc| lc + CS::one(), + |lc| lc + epk.x.get_variable() + ); + + let epk_y = cs.alloc_input( + || "epk y", + || { + Ok(*epk.y.get_value().get()?) + } + )?; + + cs.enforce( + || "epk y equals input", + |lc| lc + epk_y, + |lc| lc + CS::one(), + |lc| lc + epk.y.get_variable() + ); + } + + // Now let's deal with p_d. We don't do any checks and + // essentially allow the prover to witness any 256 bits + // they would like. + { + let p_d = self.p_d.map(|e| e.into_xy()); + + let y_contents = boolean::field_into_allocated_bits_be( + cs.namespace(|| "p_d bits of y"), + p_d.map(|e| e.1) + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "p_d bit of x"), + p_d.map(|e| e.0.into_repr().is_odd()) + )?); + + note_contents.extend(y_contents); + note_contents.push(sign_bit); + } + + 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 + let cmr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "cmr"), + self.commitment_randomness + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + 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. + let commitment_input = cs.alloc_input( + || "commitment input", + || { + Ok(*cm.x.get_value().get()?) + } + )?; + + cs.enforce( + || "commitment input correct", + |lc| lc + commitment_input, + |lc| lc + CS::one(), + |lc| lc + cm.x.get_variable() + ); + + Ok(()) + } +} + +#[test] +fn test_output_circuit_with_bls12_381() { + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let value: u64 = 1; + let value_randomness: fs::Fs = rng.gen(); + let g_d: edwards::Point = edwards::Point::rand(rng, params); + let p_d: edwards::Point = edwards::Point::rand(rng, params); + let commitment_randomness: fs::Fs = rng.gen(); + let esk: fs::Fs = rng.gen(); + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Output { + params: params, + value: Some(1), + value_randomness: Some(value_randomness), + g_d: Some(g_d.clone()), + p_d: Some(p_d.clone()), + commitment_randomness: Some(commitment_randomness), + esk: Some(esk.clone()) + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(cs.num_constraints(), 8315); + } + + // use bellman::groth16::*; + + // let groth_params = generate_random_parameters::(Output { + // params: params, + // value: None, + // value_randomness: None, + // g_d: None, + // p_d: None, + // commitment_randomness: None, + // esk: None + // }, rng).unwrap(); + + // let pvk = prepare_verifying_key(&groth_params.vk); + + // use std::time::{Duration, Instant}; + + // // Let's benchmark stuff! + // const SAMPLES: u32 = 50; + // let mut total_proving = Duration::new(0, 0); + + // for _ in 0..SAMPLES { + // let start = Instant::now(); + // { + // let c = Output { + // params: params, + // value: Some(1), + // value_randomness: Some(value_randomness), + // g_d: Some(g_d.clone()), + // p_d: Some(p_d.clone()), + // commitment_randomness: Some(commitment_randomness), + // esk: Some(esk.clone()) + // }; + + // create_random_proof(c, &groth_params, rng).unwrap(); + // } + // total_proving += start.elapsed(); + // } + + // let proving_avg = total_proving / SAMPLES; + // let proving_avg = proving_avg.subsec_nanos() as f64 / 1_000_000_000f64 + // + (proving_avg.as_secs() as f64); + + // panic!("Average proving time: {:?} seconds", proving_avg); +} From c812805b3139abfeec36bc5caa02e77397873a28 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 22 Feb 2018 18:43:07 -0700 Subject: [PATCH 064/168] Slight refactor of representations --- src/circuit/ecc.rs | 27 +++++++++ src/circuit/mod.rs | 137 +++++++++++---------------------------------- 2 files changed, 61 insertions(+), 103 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index fbb9b72..79b2813 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -92,6 +92,33 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + /// This converts the point into a representation. + pub fn repr( + &self, + mut cs: CS + ) -> Result, SynthesisError> + where CS: ConstraintSystem + { + let mut tmp = vec![]; + + let mut x = self.x.into_bits_strict( + cs.namespace(|| "unpack x") + )?; + + let mut y = self.y.into_bits_strict( + cs.namespace(|| "unpack y") + )?; + + // We want the representation in little endian bit order + x.reverse(); + y.reverse(); + + 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( diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 073926a..96a9c08 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -173,39 +173,17 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Unpack ak and rk for input to BLAKE2s let mut vk = vec![]; - { - let mut ak_x = ak.x.into_bits_strict( - cs.namespace(|| "unpack ak.x") - )?; - let mut ak_y = ak.y.into_bits_strict( - cs.namespace(|| "unpack ak.y") - )?; - - // We want the representation in little endian bit order - ak_x.reverse(); - ak_y.reverse(); - - vk.extend(ak_y); - vk.push(ak_x[0].clone()); - } let mut rho_preimage = vec![]; + vk.extend( + ak.repr(cs.namespace(|| "representation of ak"))? + ); { - let mut rk_x = rk.x.into_bits_strict( - cs.namespace(|| "unpack rk.x") - )?; - let mut rk_y = rk.y.into_bits_strict( - cs.namespace(|| "unpack rk.y") + let repr_rk = rk.repr( + cs.namespace(|| "representation of rk") )?; - // We want the representation in little endian bit order - rk_x.reverse(); - rk_y.reverse(); - - vk.extend(rk_y.iter().cloned()); - vk.push(rk_x[0].clone()); - - rho_preimage.extend(rk_y.iter().cloned()); - rho_preimage.push(rk_x[0].clone()); + vk.extend(repr_rk.iter().cloned()); + rho_preimage.extend(repr_rk); } assert_eq!(vk.len(), 512); @@ -218,7 +196,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Little endian bit order ivk.reverse(); - ivk.truncate(251); // drop_5 + ivk.truncate(E::Fs::CAPACITY as usize); // drop_5 // Witness g_d let g_d = ecc::EdwardsPoint::witness( @@ -237,38 +215,12 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Compute note contents let mut note_contents = vec![]; note_contents.extend(value_bits); - { - // Unpack g_d for inclusion in the note. - let mut g_d_x = g_d.x.into_bits_strict( - cs.namespace(|| "unpack g_d.x") - )?; - let mut g_d_y = g_d.y.into_bits_strict( - cs.namespace(|| "unpack g_d.y") - )?; - - // We want the representation in little endian bit order - g_d_x.reverse(); - g_d_y.reverse(); - - note_contents.extend(g_d_y); - note_contents.push(g_d_x[0].clone()); - } - { - // Unpack g_d for inclusion in the note. - let mut pk_d_x = pk_d.x.into_bits_strict( - cs.namespace(|| "unpack pk_d.x") - )?; - let mut pk_d_y = pk_d.y.into_bits_strict( - cs.namespace(|| "unpack pk_d.y") - )?; - - // We want the representation in little endian bit order - pk_d_x.reverse(); - pk_d_y.reverse(); - - note_contents.extend(pk_d_y); - note_contents.push(pk_d_x[0].clone()); - } + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); + note_contents.extend( + pk_d.repr(cs.namespace(|| "representation of pk_d"))? + ); assert_eq!( note_contents.len(), @@ -359,36 +311,26 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // TODO: cur is now the root of the tree, expose it as public input - let tmp = ecc::fixed_base_multiplication( - cs.namespace(|| "g^position"), - FixedGenerators::NullifierPosition, - &position_bits, - self.params - )?; + { + let position = ecc::fixed_base_multiplication( + cs.namespace(|| "g^position"), + FixedGenerators::NullifierPosition, + &position_bits, + self.params + )?; - cm = cm.add( - cs.namespace(|| "faerie gold prevention"), - &tmp, - self.params - )?; + cm = cm.add( + cs.namespace(|| "faerie gold prevention"), + &position, + self.params + )?; + } + // Let's compute rho = BLAKE2s(rk || cm + position) - { - // Unpack g_d for inclusion in the note. - let mut cm_x = cm.x.into_bits_strict( - cs.namespace(|| "unpack (cm + position).x") - )?; - let mut cm_y = cm.y.into_bits_strict( - cs.namespace(|| "unpack (cm + position).y") - )?; - - // We want the representation in little endian bit order - cm_x.reverse(); - cm_y.reverse(); - - rho_preimage.extend(cm_y); - rho_preimage.push(cm_x[0].clone()); - } + rho_preimage.extend( + cm.repr(cs.namespace(|| "representation of cm"))? + ); let mut rho = blake2s::blake2s( cs.namespace(|| "rho computation"), @@ -625,20 +567,9 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?; } - // Unpack g_d for inclusion in the note. - let mut g_d_x = g_d.x.into_bits_strict( - cs.namespace(|| "unpack g_d.x") - )?; - let mut g_d_y = g_d.y.into_bits_strict( - cs.namespace(|| "unpack g_d.y") - )?; - - // We want the representation in little endian bit order - g_d_x.reverse(); - g_d_y.reverse(); - - note_contents.extend(g_d_y); - note_contents.push(g_d_x[0].clone()); + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); // Compute epk from esk let esk = boolean::field_into_allocated_bits_be( From 97585a30ad45987fab41888de6077853d6f1749b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 09:26:12 -0700 Subject: [PATCH 065/168] Remove from_bits_strict from AllocatedNum. --- src/circuit/num.rs | 120 --------------------------------------------- 1 file changed, 120 deletions(-) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index fe20050..c24fb09 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -101,75 +101,6 @@ impl AllocatedNum { Ok(bits.into_iter().map(|b| Boolean::from(b)).collect()) } - pub fn from_bits_strict( - mut cs: CS, - bits: &[Boolean] - ) -> Result - where CS: ConstraintSystem - { - assert_eq!(bits.len(), E::Fr::NUM_BITS as usize); - - Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, bits)?; - - let one = CS::one(); - let mut lc = LinearCombination::::zero(); - let mut coeff = E::Fr::one(); - let mut value = Some(E::Fr::zero()); - for bit in bits.iter().rev() { - match bit { - &Boolean::Constant(false) => {}, - &Boolean::Constant(true) => { - value.as_mut().map(|value| value.add_assign(&coeff)); - - lc = lc + (coeff, one); - }, - &Boolean::Is(ref bit) => { - match bit.get_value() { - Some(bit) => { - if bit { - value.as_mut().map(|value| value.add_assign(&coeff)); - } - }, - None => { - value = None; - } - } - - lc = lc + (coeff, bit.get_variable()); - }, - &Boolean::Not(ref bit) => { - match bit.get_value() { - Some(bit) => { - if !bit { - value.as_mut().map(|value| value.add_assign(&coeff)); - } - }, - None => { - value = None; - } - } - - lc = lc + (coeff, one) - (coeff, bit.get_variable()); - } - } - - coeff.double(); - } - - let num = Self::alloc(&mut cs, || value.get().map(|v| *v))?; - - lc = lc - num.get_variable(); - - cs.enforce( - || "packing constraint", - |lc| lc, - |lc| lc, - |_| lc - ); - - Ok(num) - } - pub fn mul( &self, mut cs: CS, @@ -544,55 +475,4 @@ mod test { } } } - - #[test] - fn test_from_bits_strict() { - { - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap(); - assert!(num.value.unwrap().is_zero()); - assert!(!cs.is_satisfied()); - } - - let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - for _ in 0..1000 { - let r = Fr::rand(&mut rng); - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { - let parity: bool = rng.gen(); - - if parity { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } else { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(!b) - ).unwrap()).not()); - } - } - - let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap(); - assert!(cs.is_satisfied()); - assert_eq!(num.value.unwrap(), r); - assert_eq!(cs.get("num"), r); - - cs.set("num", Fr::rand(&mut rng)); - assert_eq!(cs.which_is_unsatisfied().unwrap(), "packing constraint"); - } - } } From 69abd0391f7faa8f1d95afdc292d2584decc192b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:15:14 -0700 Subject: [PATCH 066/168] Optimize into_bits_strict. --- src/circuit/boolean.rs | 38 +++++++++++ src/circuit/mod.rs | 4 +- src/circuit/num.rs | 148 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 170 insertions(+), 20 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 23ec447..48d243d 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -34,6 +34,44 @@ impl AllocatedBit { 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( diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 96a9c08..84d768b 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -392,7 +392,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 99816); + assert_eq!(cs.num_constraints(), 97376); } // use bellman::groth16::*; @@ -737,7 +737,7 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 8315); + assert_eq!(cs.num_constraints(), 7827); } // use bellman::groth16::*; diff --git a/src/circuit/num.rs b/src/circuit/num.rs index c24fb09..92e25c7 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -2,6 +2,8 @@ use pairing::{ Engine, Field, PrimeField, + PrimeFieldRepr, + BitIterator }; use bellman::{ @@ -18,6 +20,7 @@ use super::{ use super::boolean::{ self, Boolean, + AllocatedBit }; pub struct AllocatedNum { @@ -63,10 +66,126 @@ impl AllocatedNum { ) -> Result, SynthesisError> where CS: ConstraintSystem { - let bits = self.into_bits(&mut cs)?; - Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?; + pub fn kary_and( + mut cs: CS, + v: &[AllocatedBit] + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + assert!(v.len() > 0); - Ok(bits) + // 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 + ); + + Ok(result.into_iter().map(|b| Boolean::from(b)).collect()) } pub fn into_bits( @@ -315,7 +434,6 @@ mod test { use pairing::{Field, PrimeField, BitIterator}; use ::circuit::test::*; use super::{AllocatedNum, Boolean}; - use super::super::boolean::AllocatedBit; #[test] fn test_allocated_num() { @@ -422,31 +540,25 @@ mod test { // make the bit representation the characteristic cs.set("bit 254/boolean", Fr::one()); - // this makes the unpacking constraint fail - assert_eq!(cs.which_is_unsatisfied().unwrap(), "unpacking constraint"); - - // fix it by making the number zero (congruent to the characteristic) - cs.set("num", Fr::zero()); - - // and constraint is disturbed during enforce in field check - assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/AND 0/and constraint"); - cs.set("nand 121/AND 0/and result", Fr::one()); - - // now the nand should fail (enforce in field is working) - assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/enforce nand"); + // 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 _ in 0..100 { + 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 = n.into_bits(&mut cs).unwrap(); + let bits = if i % 2 == 0 { + n.into_bits(&mut cs).unwrap() + } else { + n.into_bits_strict(&mut cs).unwrap() + }; assert!(cs.is_satisfied()); From 0d56c7a90251cee396883c0230ea7e332d714c72 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:18:39 -0700 Subject: [PATCH 067/168] Remove enforce_in_field. --- src/circuit/boolean.rs | 139 ----------------------------------------- 1 file changed, 139 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 48d243d..c8308ab 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -531,78 +531,6 @@ impl Boolean { }, } } - - /// Asserts that this bit representation is "in - /// the field" when interpreted in big endian. - pub fn enforce_in_field( - mut cs: CS, - bits: &[Self] - ) -> Result<(), SynthesisError> - where E: Engine, - CS: ConstraintSystem - { - assert_eq!(bits.len(), F::NUM_BITS as usize); - - let mut a = bits.iter(); - - // b = char() - 1 - let mut b = F::char(); - b.sub_noborrow(&1.into()); - - // Runs of ones in r - let mut last_run = Boolean::constant(true); - let mut current_run = vec![]; - - let mut found_one = false; - let mut run_i = 0; - let mut nand_i = 0; - for b in BitIterator::new(b) { - // Skip over unset bits at the beginning - found_one |= b; - if !found_one { - continue; - } - - let a = a.next().unwrap(); - - if b { - // This is part of a run of ones. - current_run.push(a.clone()); - } 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. - - current_run.push(last_run.clone()); - last_run = Self::kary_and( - cs.namespace(|| format!("run {}", run_i)), - ¤t_run - )?; - run_i += 1; - 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. - // - // Ergo, at least one of `last_run` and `a` must be false. - Self::enforce_nand( - cs.namespace(|| format!("nand {}", nand_i)), - &[last_run.clone(), a.clone()] - )?; - nand_i += 1; - } - } - - // We should always end in a "run" of zeros, because - // the characteristic is an odd prime. So, this should - // be empty. - assert_eq!(current_run.len(), 0); - - Ok(()) - } } impl From for Boolean { @@ -1090,73 +1018,6 @@ mod test { } } - #[test] - fn test_enforce_in_field() { - { - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(!cs.is_satisfied()); - } - - let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - for _ in 0..1000 { - let r = Fr::rand(&mut rng); - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(cs.is_satisfied()); - } - - for _ in 0..1000 { - // Sample a random element not in the field - let r = loop { - let mut a = Fr::rand(&mut rng).into_repr(); - let b = Fr::rand(&mut rng).into_repr(); - - a.add_nocarry(&b); - // we're shaving off the high bit later - a.as_mut()[3] &= 0x7fffffffffffffff; - if Fr::from_repr(a).is_err() { - break a; - } - }; - - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(!cs.is_satisfied()); - } - } - #[test] fn test_enforce_nand() { { From a45986ff97a4fd4fde54c290e9913608b732a97a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:24:42 -0700 Subject: [PATCH 068/168] Remove enforce_nand. --- src/circuit/boolean.rs | 121 ++++++++++------------------------------- 1 file changed, 28 insertions(+), 93 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index c8308ab..4fa1818 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -366,7 +366,34 @@ impl Boolean { { let c = Self::xor(&mut cs, a, b)?; - Self::enforce_nand(&mut cs, &[c]) + match c { + Boolean::Constant(false) => { + Ok(()) + }, + Boolean::Constant(true) => { + Err(SynthesisError::Unsatisfiable) + }, + Boolean::Is(ref res) => { + cs.enforce( + || "enforce equals zero", + |lc| lc, + |lc| lc, + |lc| lc + res.get_variable() + ); + + Ok(()) + }, + Boolean::Not(ref res) => { + cs.enforce( + || "enforce equals one", + |lc| lc, + |lc| lc, + |lc| lc + CS::one() - res.get_variable() + ); + + Ok(()) + }, + } } pub fn get_value(&self) -> Option { @@ -491,46 +518,6 @@ impl Boolean { Ok(cur) } - - /// Asserts that at least one operand is false. - pub fn enforce_nand( - mut cs: CS, - bits: &[Self] - ) -> Result<(), SynthesisError> - where E: Engine, - CS: ConstraintSystem - { - let res = Self::kary_and(&mut cs, bits)?; - - match res { - Boolean::Constant(false) => { - Ok(()) - }, - Boolean::Constant(true) => { - Err(SynthesisError::Unsatisfiable) - }, - Boolean::Is(ref res) => { - cs.enforce( - || "enforce nand", - |lc| lc, - |lc| lc, - |lc| lc + res.get_variable() - ); - - Ok(()) - }, - Boolean::Not(ref res) => { - cs.enforce( - || "enforce nand", - |lc| lc, - |lc| lc, - |lc| lc + CS::one() - res.get_variable() - ); - - Ok(()) - }, - } - } } impl From for Boolean { @@ -1018,58 +1005,6 @@ mod test { } } - #[test] - fn test_enforce_nand() { - { - let mut cs = TestConstraintSystem::::new(); - - Boolean::enforce_nand(&mut cs, &[Boolean::constant(false)]).is_ok(); - Boolean::enforce_nand(&mut cs, &[Boolean::constant(true)]).is_err(); - } - - for i in 1..5 { - // with every possible assignment for them - for mut b in 0..(1 << i) { - // with every possible negation - for mut n in 0..(1 << i) { - let mut cs = TestConstraintSystem::::new(); - - let mut expected = true; - - let mut bits = vec![]; - for j in 0..i { - expected &= b & 1 == 1; - - if n & 1 == 1 { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 1) - ).unwrap())); - } else { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 0) - ).unwrap()).not()); - } - - b >>= 1; - n >>= 1; - } - - let expected = !expected; - - Boolean::enforce_nand(&mut cs, &bits).unwrap(); - - if expected { - assert!(cs.is_satisfied()); - } else { - assert!(!cs.is_satisfied()); - } - } - } - } - } - #[test] fn test_kary_and() { // test different numbers of operands From 8c372126453f3ab4d993314e5d146d265dea4fe7 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:25:59 -0700 Subject: [PATCH 069/168] Remove kary_and from Boolean. --- src/circuit/boolean.rs | 57 ------------------------------------------ 1 file changed, 57 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 4fa1818..20342bf 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -496,28 +496,6 @@ impl Boolean { } } } - - pub fn kary_and( - mut cs: CS, - bits: &[Self] - ) -> Result - where E: Engine, - CS: ConstraintSystem - { - assert!(bits.len() > 0); - let mut bits = bits.iter(); - - let mut cur: Self = bits.next().unwrap().clone(); - - let mut i = 0; - while let Some(next) = bits.next() { - cur = Boolean::and(cs.namespace(|| format!("AND {}", i)), &cur, next)?; - - i += 1; - } - - Ok(cur) - } } impl From for Boolean { @@ -1005,41 +983,6 @@ mod test { } } - #[test] - fn test_kary_and() { - // test different numbers of operands - for i in 1..15 { - // with every possible assignment for them - for mut b in 0..(1 << i) { - let mut cs = TestConstraintSystem::::new(); - - let mut expected = true; - - let mut bits = vec![]; - for j in 0..i { - expected &= b & 1 == 1; - - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 1) - ).unwrap())); - b >>= 1; - } - - let r = Boolean::kary_and(&mut cs, &bits).unwrap(); - - assert!(cs.is_satisfied()); - - match r { - Boolean::Is(ref r) => { - assert_eq!(r.value.unwrap(), expected); - }, - _ => unreachable!() - } - } - } - } - #[test] fn test_u64_into_allocated_bits_be() { let mut cs = TestConstraintSystem::::new(); From 7a7774d8afd1d80a11e3f5b254f48ddb1171c45d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:28:17 -0700 Subject: [PATCH 070/168] Remove unused imports in Boolean. --- src/circuit/boolean.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 20342bf..18bb4d0 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -2,7 +2,6 @@ use pairing::{ Engine, Field, PrimeField, - PrimeFieldRepr, BitIterator }; @@ -506,10 +505,9 @@ impl From for Boolean { #[cfg(test)] mod test { - use rand::{SeedableRng, Rand, XorShiftRng}; use bellman::{ConstraintSystem}; use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator}; + use pairing::{Field, PrimeField}; use ::circuit::test::*; use super::{ AllocatedBit, From 23d17b904267737ccad54b66abb293170ae539f8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 18:09:26 -0700 Subject: [PATCH 071/168] Minor adjustments. --- src/circuit/mod.rs | 71 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 84d768b..58acf1f 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -40,8 +40,6 @@ impl Assignment for Option { } } -const MERKLE_TREE_DEPTH: usize = 29; - pub struct Spend<'a, E: JubjubEngine> { pub params: &'a E::Params, /// Value of the note being spent @@ -262,7 +260,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; } - assert_eq!(self.auth_path.len(), MERKLE_TREE_DEPTH); + let tree_depth = self.auth_path.len(); let mut position_bits = vec![]; @@ -301,15 +299,30 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { cur = pedersen_hash::pedersen_hash( cs.namespace(|| "computation of pedersen hash"), - pedersen_hash::Personalization::MerkleTree(MERKLE_TREE_DEPTH - i), + pedersen_hash::Personalization::MerkleTree(tree_depth - i), &preimage, self.params )?.x; // Injective encoding } - assert_eq!(position_bits.len(), MERKLE_TREE_DEPTH); + assert_eq!(position_bits.len(), tree_depth); - // TODO: cur is now the root of the tree, expose it as public input + { + // Expose the anchor + let anchor = cs.alloc_input( + || "anchor x", + || { + Ok(*cur.get_value().get()?) + } + )?; + + cs.enforce( + || "anchor x equals anchor", + |lc| lc + anchor, + |lc| lc + CS::one(), + |lc| lc + cur.get_variable() + ); + } { let position = ecc::fixed_base_multiplication( @@ -326,11 +339,12 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; } - // Let's compute rho = BLAKE2s(rk || cm + position) rho_preimage.extend( cm.repr(cs.namespace(|| "representation of cm"))? ); + + assert_eq!(rho_preimage.len(), 512); let mut rho = blake2s::blake2s( cs.namespace(|| "rho computation"), @@ -348,7 +362,36 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // TODO: expose nf as public input + { + // Expose the nullifier publicly + let nf_x = cs.alloc_input( + || "nf_x", + || { + Ok(*nf.x.get_value().get()?) + } + )?; + + cs.enforce( + || "nf_x equals input", + |lc| lc + nf_x, + |lc| lc + CS::one(), + |lc| lc + nf.x.get_variable() + ); + + let nf_y = cs.alloc_input( + || "nf_y", + || { + Ok(*nf.y.get_value().get()?) + } + )?; + + cs.enforce( + || "nf_y equals input", + |lc| lc + nf_y, + |lc| lc + CS::one(), + |lc| lc + nf.y.get_variable() + ); + } Ok(()) } @@ -364,22 +407,22 @@ fn test_input_circuit_with_bls12_381() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let tree_depth = 29; + let value: u64 = 1; let value_randomness: fs::Fs = rng.gen(); let ak: edwards::Point = edwards::Point::rand(rng, params); let g_d: edwards::Point = edwards::Point::rand(rng, params); - let p_d: edwards::Point = edwards::Point::rand(rng, params); let commitment_randomness: fs::Fs = rng.gen(); - let esk: fs::Fs = rng.gen(); let rsk: fs::Fs = rng.gen(); - let auth_path = vec![Some((rng.gen(), false)); 29]; + let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; { let mut cs = TestConstraintSystem::::new(); let instance = Spend { params: params, - value: Some(1), + value: Some(value), value_randomness: Some(value_randomness), rsk: Some(rsk), ak: Some(ak), @@ -392,7 +435,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 97376); + assert_eq!(cs.num_constraints(), 97379); } // use bellman::groth16::*; @@ -725,7 +768,7 @@ fn test_output_circuit_with_bls12_381() { let instance = Output { params: params, - value: Some(1), + value: Some(value), value_randomness: Some(value_randomness), g_d: Some(g_d.clone()), p_d: Some(p_d.clone()), From 4441a0da41457955715afe6a2ff7093786c65dd8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 08:01:16 -0700 Subject: [PATCH 072/168] Hash the constraint systems to check integrity. --- Cargo.toml | 2 + src/circuit/mod.rs | 178 ++++++++++---------------------------- src/circuit/test/mod.rs | 187 +++++++++++++++++++++++++++++++++++++++- src/lib.rs | 2 + 4 files changed, 234 insertions(+), 135 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a60e138..4999341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ blake2 = "0.7" digest = "0.7" bellman = "0.0.8" +byteorder = "1" + [features] default = ["u128-support"] u128-support = ["pairing/u128-support"] diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 58acf1f..e1256ef 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -397,94 +397,6 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { } } -#[test] -fn test_input_circuit_with_bls12_381() { - use pairing::bls12_381::*; - use rand::{SeedableRng, Rng, XorShiftRng}; - use ::circuit::test::*; - use jubjub::{JubjubBls12, fs}; - - let params = &JubjubBls12::new(); - let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let tree_depth = 29; - - let value: u64 = 1; - let value_randomness: fs::Fs = rng.gen(); - let ak: edwards::Point = edwards::Point::rand(rng, params); - let g_d: edwards::Point = edwards::Point::rand(rng, params); - let commitment_randomness: fs::Fs = rng.gen(); - let rsk: fs::Fs = rng.gen(); - let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; - - { - let mut cs = TestConstraintSystem::::new(); - - let instance = Spend { - params: params, - value: Some(value), - value_randomness: Some(value_randomness), - rsk: Some(rsk), - ak: Some(ak), - g_d: Some(g_d), - commitment_randomness: Some(commitment_randomness), - auth_path: auth_path - }; - - instance.synthesize(&mut cs).unwrap(); - - assert!(cs.is_satisfied()); - - assert_eq!(cs.num_constraints(), 97379); - } - - // use bellman::groth16::*; - - // let groth_params = generate_random_parameters::(Spend { - // params: params, - // value: None, - // value_randomness: None, - // rsk: None, - // ak: None, - // g_d: None, - // commitment_randomness: None, - // auth_path: vec![None; 29] - // }, rng).unwrap(); - - // let pvk = prepare_verifying_key(&groth_params.vk); - - // use std::time::{Duration, Instant}; - - // // Let's benchmark stuff! - // const SAMPLES: u32 = 50; - // let mut total_proving = Duration::new(0, 0); - - // for _ in 0..SAMPLES { - // let start = Instant::now(); - // { - // let c = Spend { - // params: params, - // value: Some(1), - // value_randomness: Some(value_randomness.clone()), - // rsk: Some(rsk.clone()), - // ak: Some(ak.clone()), - // g_d: Some(g_d.clone()), - // commitment_randomness: Some(commitment_randomness.clone()), - // auth_path: auth_path.clone() - // }; - - // create_random_proof(c, &groth_params, rng).unwrap(); - // } - // total_proving += start.elapsed(); - // } - - // let proving_avg = total_proving / SAMPLES; - // let proving_avg = proving_avg.subsec_nanos() as f64 / 1_000_000_000f64 - // + (proving_avg.as_secs() as f64); - - // panic!("Average proving time: {:?} seconds", proving_avg); -} - /// This is an output circuit instance. pub struct Output<'a, E: JubjubEngine> { pub params: &'a E::Params, @@ -747,7 +659,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { } #[test] -fn test_output_circuit_with_bls12_381() { +fn test_input_circuit_with_bls12_381() { use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; @@ -756,6 +668,48 @@ fn test_output_circuit_with_bls12_381() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let tree_depth = 29; + + let value: u64 = 1; + let value_randomness: fs::Fs = rng.gen(); + let ak: edwards::Point = edwards::Point::rand(rng, params); + let g_d: edwards::Point = edwards::Point::rand(rng, params); + let commitment_randomness: fs::Fs = rng.gen(); + let rsk: fs::Fs = rng.gen(); + let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Spend { + params: params, + value: Some(value), + value_randomness: Some(value_randomness), + rsk: Some(rsk), + ak: Some(ak), + g_d: Some(g_d), + commitment_randomness: Some(commitment_randomness), + auth_path: auth_path + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 97379); + assert_eq!(cs.hash(), "4d8e71c91a621e41599ea488ee89f035c892a260a595d3c85a20a82daa2d1654"); + } +} + +#[test] +fn test_output_circuit_with_bls12_381() { + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let value: u64 = 1; let value_randomness: fs::Fs = rng.gen(); let g_d: edwards::Point = edwards::Point::rand(rng, params); @@ -779,51 +733,7 @@ fn test_output_circuit_with_bls12_381() { instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 7827); + assert_eq!(cs.hash(), "225a2df7e21b9af8b436ffb9dadd645e4df843a5151c7481b0553422d5eaa793"); } - - // use bellman::groth16::*; - - // let groth_params = generate_random_parameters::(Output { - // params: params, - // value: None, - // value_randomness: None, - // g_d: None, - // p_d: None, - // commitment_randomness: None, - // esk: None - // }, rng).unwrap(); - - // let pvk = prepare_verifying_key(&groth_params.vk); - - // use std::time::{Duration, Instant}; - - // // Let's benchmark stuff! - // const SAMPLES: u32 = 50; - // let mut total_proving = Duration::new(0, 0); - - // for _ in 0..SAMPLES { - // let start = Instant::now(); - // { - // let c = Output { - // params: params, - // value: Some(1), - // value_randomness: Some(value_randomness), - // g_d: Some(g_d.clone()), - // p_d: Some(p_d.clone()), - // commitment_randomness: Some(commitment_randomness), - // esk: Some(esk.clone()) - // }; - - // create_random_proof(c, &groth_params, rng).unwrap(); - // } - // total_proving += start.elapsed(); - // } - - // let proving_avg = total_proving / SAMPLES; - // let proving_avg = proving_avg.subsec_nanos() as f64 / 1_000_000_000f64 - // + (proving_avg.as_secs() as f64); - - // panic!("Average proving time: {:?} seconds", proving_avg); } diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index ef042fe..e7f7515 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -1,6 +1,8 @@ use pairing::{ Engine, - Field + Field, + PrimeField, + PrimeFieldRepr }; use bellman::{ @@ -12,6 +14,13 @@ use bellman::{ }; use std::collections::HashMap; +use std::fmt::Write; + +use blake2::{Blake2s}; +use digest::{FixedOutput, Input}; +use byteorder::{BigEndian, ByteOrder}; +use std::cmp::Ordering; +use std::collections::BTreeMap; #[derive(Debug)] enum NamedObject { @@ -34,6 +43,90 @@ pub struct TestConstraintSystem { 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.process(&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.process(&buf); + } +} + fn eval_lc( terms: &[(Variable, E::Fr)], inputs: &[(E::Fr, String)], @@ -69,6 +162,98 @@ impl TestConstraintSystem { } } + 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_keyed(&[], 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.process(&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.fixed_result().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); diff --git a/src/lib.rs b/src/lib.rs index 6e45309..60e5972 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ extern crate blake2; extern crate digest; extern crate rand; +extern crate byteorder; + pub mod jubjub; pub mod circuit; pub mod group_hash; From 3346fba915a99f09c51847bdac7086516ee24f15 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 14:11:01 -0700 Subject: [PATCH 073/168] Implementation of Jubjub point representation. --- Cargo.toml | 3 ++ src/jubjub/edwards.rs | 90 +++++++++++++++++++++++++++++++++++++++++++ src/jubjub/mod.rs | 13 ++++++- src/jubjub/tests.rs | 16 ++++++++ src/lib.rs | 4 ++ 5 files changed, 125 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4999341..0135682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ bellman = "0.0.8" byteorder = "1" +[dev-dependencies] +hex-literal = "0.1" + [features] default = ["u128-support"] u128-support = ["pairing/u128-support"] diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index 130fd59..eeabe9d 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -20,6 +20,12 @@ use rand::{ use std::marker::PhantomData; +use std::io::{ + self, + Write, + Read +}; + // Represents the affine point (X/Z, Y/Z) via the extended // twisted Edwards coordinates. pub struct Point { @@ -80,7 +86,67 @@ impl PartialEq for Point { } } +fn swap_bits_u64(x: u64) -> u64 +{ + let mut tmp = 0; + for i in 0..64 { + tmp |= ((x >> i) & 1) << (63 - i); + } + tmp +} + +#[test] +fn test_swap_bits_u64() { + assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111); + assert_eq!(swap_bits_u64(15135675916470734665), 0b1001001011110010001101010010001110110000100111010011000001001011); + assert_eq!(swap_bits_u64(6724233301461108393), 0b1001010101100000100011100001010111110001011000101000101010111010); + assert_eq!(swap_bits_u64(206708183275952289), 0b1000010100011010001010100011101011111111111110100111101101000000); + assert_eq!(swap_bits_u64(12712751566144824320), 0b0000000000100110010110111000001110001100001000110011011000001101); + + let mut a = 15863238721320035327u64; + for _ in 0..1000 { + a = a.wrapping_mul(a); + + let swapped = swap_bits_u64(a); + let unswapped = swap_bits_u64(swapped); + + assert_eq!(a, unswapped); + } +} + impl Point { + pub fn read( + reader: R, + params: &E::Params + ) -> io::Result + { + let mut y_repr = ::Repr::default(); + y_repr.read_be(reader)?; + + y_repr.as_mut().reverse(); + + for b in y_repr.as_mut() { + *b = swap_bits_u64(*b); + } + + 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) @@ -151,6 +217,30 @@ impl Point { } 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.as_mut().reverse(); + + for b in y_repr.as_mut() { + *b = swap_bits_u64(*b); + } + + y_repr.write_be(writer) + } + /// Convert from a Montgomery point pub fn from_montgomery( m: &montgomery::Point, diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 4782d90..189d434 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -72,7 +72,8 @@ pub enum FixedGenerators { ValueCommitmentValue = 2, ValueCommitmentRandomness = 3, NullifierPosition = 4, - Max = 5 + SpendingKeyGenerator = 5, + Max = 6 } pub struct JubjubBls12 { @@ -236,4 +237,14 @@ fn test_jubjub_bls12() { let params = JubjubBls12::new(); tests::test_suite::(¶ms); + + let test_repr = hex!("b9481dd1103b7d1f8578078eb429d3c476472f53e88c0eaefdf51334c7c8b98c"); + 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); } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index e034854..dfd44d0 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -26,6 +26,7 @@ pub fn test_suite(params: &E::Params) { test_order::(params); test_mul_associativity::(params); test_loworder::(params); + test_read_write::(params); } fn is_on_mont_curve>( @@ -245,6 +246,21 @@ fn test_get_for(params: &E::Params) { } } +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]); diff --git a/src/lib.rs b/src/lib.rs index 60e5972..48af45c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,10 @@ extern crate rand; extern crate byteorder; +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + pub mod jubjub; pub mod circuit; pub mod group_hash; From 27674bf8ff59b7c4427af7839029742e3a9333ef Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 17:11:17 -0700 Subject: [PATCH 074/168] Derive Clone for EdwardsPoint. --- src/circuit/ecc.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 79b2813..0828812 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -30,20 +30,12 @@ use super::lookup::{ use super::boolean::Boolean; +#[derive(Clone)] pub struct EdwardsPoint { pub x: AllocatedNum, pub y: AllocatedNum } -impl Clone for EdwardsPoint { - fn clone(&self) -> Self { - EdwardsPoint { - x: self.x.clone(), - y: self.y.clone() - } - } -} - /// Perform a fixed-base scalar multiplication with /// `by` being in little-endian bit order. pub fn fixed_base_multiplication( From 57687cf70fee8c797211900226dbe406d0cc57fb Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 22:53:00 -0700 Subject: [PATCH 075/168] Creation of the Note primitive. --- src/lib.rs | 1 + src/pedersen_hash.rs | 2 +- src/primitives/mod.rs | 65 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/primitives/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 48af45c..4a08bf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,3 +14,4 @@ pub mod jubjub; pub mod circuit; pub mod group_hash; pub mod pedersen_hash; +pub mod primitives; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 12e5c7d..1eb75f6 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -1,7 +1,7 @@ use jubjub::*; use pairing::*; -use circuit::pedersen_hash::Personalization; +pub use circuit::pedersen_hash::Personalization; pub fn pedersen_hash( personalization: Personalization, diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs new file mode 100644 index 0000000..7a99092 --- /dev/null +++ b/src/primitives/mod.rs @@ -0,0 +1,65 @@ +use pedersen_hash::{ + pedersen_hash, + Personalization +}; + +use byteorder::{ + BigEndian, + ByteOrder +}; + +use jubjub::{ + JubjubEngine, + JubjubParams, + edwards, + PrimeOrder, + FixedGenerators +}; + +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 { + /// Computes the note commitment + pub fn cm(&self, params: &E::Params) -> E::Fr + { + // Calculate the note contents, as bytes + let mut note_contents = vec![]; + + // Write the value in big endian + BigEndian::write_u64(&mut note_contents, self.value); + + // Write g_d + self.g_d.write(&mut note_contents).unwrap(); + + // Write pk_d + self.pk_d.write(&mut note_contents).unwrap(); + + // 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).rev().map(move |i| ((byte >> i) & 1) == 1) + }), + params + ); + + // Compute final commitment + let cm = params.generator(FixedGenerators::NoteCommitmentRandomness) + .mul(self.r, params) + .add(&hash_of_contents, params); + + // The commitment is in the prime order subgroup, so mapping the + // commitment to the x-coordinate is an injective encoding. + cm.into_xy().0 + } +} From 51c35a9bcfb52766c6fa5726f17e0ff96998f0f2 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 4 Mar 2018 22:25:04 -0700 Subject: [PATCH 076/168] Adopt new versions of pairing and bellman. --- Cargo.toml | 6 +++--- src/jubjub/fs.rs | 40 ++++++++++------------------------------ src/jubjub/tests.rs | 4 ++-- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0135682..cdb161b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,14 @@ repository = "https://github.com/zcash-hackworks/sapling" version = "0.0.1" [dependencies.pairing] -version = "~0.13.2" +version = "0.14" features = ["expose-arith"] [dependencies] -rand = "0.3" +rand = "0.4" blake2 = "0.7" digest = "0.7" -bellman = "0.0.8" +bellman = "0.0.9" byteorder = "1" diff --git a/src/jubjub/fs.rs b/src/jubjub/fs.rs index 2cf6f98..051978b 100644 --- a/src/jubjub/fs.rs +++ b/src/jubjub/fs.rs @@ -118,7 +118,7 @@ impl PrimeFieldRepr for FsRepr { } #[inline(always)] - fn divn(&mut self, mut n: u32) { + fn shr(&mut self, mut n: u32) { if n >= 64 * 4 { *self = Self::from(0); return; @@ -166,7 +166,7 @@ impl PrimeFieldRepr for FsRepr { } #[inline(always)] - fn muln(&mut self, mut n: u32) { + fn shl(&mut self, mut n: u32) { if n >= 64 * 4 { *self = Self::from(0); return; @@ -206,25 +206,21 @@ impl PrimeFieldRepr for FsRepr { } #[inline(always)] - fn add_nocarry(&mut self, other: &FsRepr) -> bool { + 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); } - - carry != 0 } #[inline(always)] - fn sub_noborrow(&mut self, other: &FsRepr) -> bool { + 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); } - - borrow != 0 } } @@ -668,29 +664,29 @@ fn test_fs_repr_div2() { } #[test] -fn test_fs_repr_divn() { +fn test_fs_repr_shr() { let mut a = FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]); - a.divn(0); + a.shr(0); assert_eq!( a, FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]) ); - a.divn(1); + a.shr(1); assert_eq!( a, FsRepr([0xd99fdd762415141f, 0xccbef069d44659ef, 0xcd7b16954d072a92, 0x1b001d5846f386d0]) ); - a.divn(50); + a.shr(50); assert_eq!( a, FsRepr([0xbc1a7511967bf667, 0xc5a55341caa4b32f, 0x75611bce1b4335e, 0x6c0]) ); - a.divn(130); + a.shr(130); assert_eq!( a, FsRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0]) ); - a.divn(64); + a.shr(64); assert_eq!( a, FsRepr([0x1b0, 0x0, 0x0, 0x0]) @@ -765,14 +761,6 @@ fn test_fs_repr_sub_noborrow() { assert_eq!(csub_ab, csub_ba); } - - // Subtracting r+1 from r should produce a borrow - let mut qplusone = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(qplusone.sub_noborrow(&FsRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))); - - // Subtracting x from x should produce no borrow - let mut x = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(!x.sub_noborrow(&FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))) } #[test] @@ -835,14 +823,6 @@ fn test_fr_repr_add_nocarry() { assert_eq!(abc, cab); assert_eq!(abc, cba); } - - // Adding 1 to (2^256 - 1) should produce a carry - let mut x = FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); - assert!(x.add_nocarry(&FsRepr::from(1))); - - // Adding 1 to r should not produce a carry - let mut x = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(!x.add_nocarry(&FsRepr::from(1))); } #[test] diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index dfd44d0..421a8f7 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -390,8 +390,8 @@ fn test_jubjub_params(params: &E::Params) { tmp.mul2(); tmp.mul2(); - assert_eq!(pacc.add_nocarry(&tmp), false); - assert_eq!(nacc.sub_noborrow(&tmp), false); + pacc.add_nocarry(&tmp); + nacc.sub_noborrow(&tmp); assert!(pacc < max); assert!(pacc < nacc); From e52befb58edd0e8f1f00f01e831bae978ad305ee Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 4 Mar 2018 23:33:05 -0700 Subject: [PATCH 077/168] Introduce inputize abstractions to simplify circuit code. --- src/circuit/ecc.rs | 12 ++++ src/circuit/mod.rs | 152 +++------------------------------------------ src/circuit/num.rs | 23 +++++++ 3 files changed, 43 insertions(+), 144 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 0828812..7642db4 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -84,6 +84,18 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + 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, diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index e1256ef..d16c48c 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -107,34 +107,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // Expose the value commitment publicly - let value_commitment_x = cs.alloc_input( - || "value commitment x", - || { - Ok(*gvhr.x.get_value().get()?) - } - )?; - - cs.enforce( - || "value commitment x equals input", - |lc| lc + value_commitment_x, - |lc| lc + CS::one(), - |lc| lc + gvhr.x.get_variable() - ); - - let value_commitment_y = cs.alloc_input( - || "value commitment y", - || { - Ok(*gvhr.y.get_value().get()?) - } - )?; - - cs.enforce( - || "value commitment y equals input", - |lc| lc + value_commitment_y, - |lc| lc + CS::one(), - |lc| lc + gvhr.y.get_variable() - ); + gvhr.inputize(cs.namespace(|| "value commitment"))?; } // Compute rk = [rsk] ProvingPublicKey @@ -307,22 +280,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { assert_eq!(position_bits.len(), tree_depth); - { - // Expose the anchor - let anchor = cs.alloc_input( - || "anchor x", - || { - Ok(*cur.get_value().get()?) - } - )?; - - cs.enforce( - || "anchor x equals anchor", - |lc| lc + anchor, - |lc| lc + CS::one(), - |lc| lc + cur.get_variable() - ); - } + // Expose the anchor + cur.inputize(cs.namespace(|| "anchor"))?; { let position = ecc::fixed_base_multiplication( @@ -353,7 +312,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Little endian bit order rho.reverse(); - rho.truncate(251); // drop_5 + rho.truncate(E::Fs::CAPACITY as usize); // drop_5 // Compute nullifier let nf = ak.mul( @@ -362,36 +321,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - { - // Expose the nullifier publicly - let nf_x = cs.alloc_input( - || "nf_x", - || { - Ok(*nf.x.get_value().get()?) - } - )?; - - cs.enforce( - || "nf_x equals input", - |lc| lc + nf_x, - |lc| lc + CS::one(), - |lc| lc + nf.x.get_variable() - ); - - let nf_y = cs.alloc_input( - || "nf_y", - || { - Ok(*nf.y.get_value().get()?) - } - )?; - - cs.enforce( - || "nf_y equals input", - |lc| lc + nf_y, - |lc| lc + CS::one(), - |lc| lc + nf.y.get_variable() - ); - } + nf.inputize(cs.namespace(|| "nullifier"))?; Ok(()) } @@ -458,34 +388,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; - // Expose the value commitment publicly - let value_commitment_x = cs.alloc_input( - || "value commitment x", - || { - Ok(*gvhr.x.get_value().get()?) - } - )?; - - cs.enforce( - || "value commitment x equals input", - |lc| lc + value_commitment_x, - |lc| lc + CS::one(), - |lc| lc + gvhr.x.get_variable() - ); - - let value_commitment_y = cs.alloc_input( - || "value commitment y", - || { - Ok(*gvhr.y.get_value().get()?) - } - )?; - - cs.enforce( - || "value commitment y equals input", - |lc| lc + value_commitment_y, - |lc| lc + CS::one(), - |lc| lc + gvhr.y.get_variable() - ); + gvhr.inputize(cs.namespace(|| "value commitment"))?; } // Let's start to construct our note @@ -542,34 +445,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; - // Expose epk publicly - let epk_x = cs.alloc_input( - || "epk x", - || { - Ok(*epk.x.get_value().get()?) - } - )?; - - cs.enforce( - || "epk x equals input", - |lc| lc + epk_x, - |lc| lc + CS::one(), - |lc| lc + epk.x.get_variable() - ); - - let epk_y = cs.alloc_input( - || "epk y", - || { - Ok(*epk.y.get_value().get()?) - } - )?; - - cs.enforce( - || "epk y equals input", - |lc| lc + epk_y, - |lc| lc + CS::one(), - |lc| lc + epk.y.get_variable() - ); + epk.inputize(cs.namespace(|| "epk"))?; } // Now let's deal with p_d. We don't do any checks and @@ -640,19 +516,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // since we know it is prime order, and we know that // the x-coordinate is an injective encoding for // prime-order elements. - let commitment_input = cs.alloc_input( - || "commitment input", - || { - Ok(*cm.x.get_value().get()?) - } - )?; - - cs.enforce( - || "commitment input correct", - |lc| lc + commitment_input, - |lc| lc + CS::one(), - |lc| lc + cm.x.get_variable() - ); + cm.x.inputize(cs.namespace(|| "commitment"))?; Ok(()) } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 92e25c7..48f1fda 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -60,6 +60,29 @@ impl AllocatedNum { }) } + 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(()) + } + pub fn into_bits_strict( &self, mut cs: CS From 3e15751fd1385478a28b3bf59192f4aae9c1b946 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 09:37:13 -0700 Subject: [PATCH 078/168] Allocate the note value directly in little-endian bit order. --- src/circuit/boolean.rs | 34 +++++++++++++++++----------------- src/circuit/mod.rs | 20 ++++++-------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 18bb4d0..9209773 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -271,16 +271,16 @@ impl AllocatedBit { } } -pub fn u64_into_allocated_bits_be>( +pub fn u64_into_boolean_vec_le>( mut cs: CS, value: Option -) -> Result, SynthesisError> +) -> Result, SynthesisError> { let values = match value { Some(ref value) => { let mut tmp = Vec::with_capacity(64); - for i in (0..64).rev() { + for i in 0..64 { tmp.push(Some(*value >> i & 1 == 1)); } @@ -292,10 +292,10 @@ pub fn u64_into_allocated_bits_be>( }; let bits = values.into_iter().enumerate().map(|(i, b)| { - AllocatedBit::alloc( + Ok(Boolean::from(AllocatedBit::alloc( cs.namespace(|| format!("bit {}", i)), b - ) + )?)) }).collect::, SynthesisError>>()?; Ok(bits) @@ -513,7 +513,7 @@ mod test { AllocatedBit, Boolean, field_into_allocated_bits_be, - u64_into_allocated_bits_be + u64_into_boolean_vec_le }; #[test] @@ -982,24 +982,24 @@ mod test { } #[test] - fn test_u64_into_allocated_bits_be() { + fn test_u64_into_boolean_vec_le() { let mut cs = TestConstraintSystem::::new(); - let bits = u64_into_allocated_bits_be(&mut cs, Some(17234652694787248421)).unwrap(); + let bits = u64_into_boolean_vec_le(&mut cs, Some(17234652694787248421)).unwrap(); assert!(cs.is_satisfied()); assert_eq!(bits.len(), 64); - assert_eq!(bits[0].value.unwrap(), true); - assert_eq!(bits[1].value.unwrap(), true); - assert_eq!(bits[2].value.unwrap(), true); - assert_eq!(bits[3].value.unwrap(), false); - assert_eq!(bits[4].value.unwrap(), true); - assert_eq!(bits[5].value.unwrap(), true); - assert_eq!(bits[20].value.unwrap(), true); - assert_eq!(bits[21].value.unwrap(), false); - assert_eq!(bits[22].value.unwrap(), false); + 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] diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index d16c48c..093aa9d 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -67,14 +67,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { // Booleanize the value into little-endian bit order - let value_bits = boolean::u64_into_allocated_bits_be( + let value_bits = boolean::u64_into_boolean_vec_le( cs.namespace(|| "value"), self.value - )? - .into_iter() - .rev() // Little endian bit order - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; { let gv = ecc::fixed_base_multiplication( @@ -348,14 +344,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { // Booleanize the value into little-endian bit order - let value_bits = boolean::u64_into_allocated_bits_be( + let value_bits = boolean::u64_into_boolean_vec_le( cs.namespace(|| "value"), self.value - )? - .into_iter() - .rev() // Little endian bit order - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; { let gv = ecc::fixed_base_multiplication( @@ -560,7 +552,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "4d8e71c91a621e41599ea488ee89f035c892a260a595d3c85a20a82daa2d1654"); + assert_eq!(cs.hash(), "a3ac418bbbe38d08295995c8cdcaebd6902fcfa9e4f7212c9742ed033c1edec3"); } } @@ -598,6 +590,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "225a2df7e21b9af8b436ffb9dadd645e4df843a5151c7481b0553422d5eaa793"); + assert_eq!(cs.hash(), "b74e3ee749e1cbc405b5b4a1de3b11119084afda9b6f5e3a6865cbcc5c35e3d4"); } } From 543f5cd49c6cab469cbafc6f2b9d714d5bdb385d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 10:27:14 -0700 Subject: [PATCH 079/168] Change bit-endianness of into_bits/into_bits_strict. --- src/circuit/boolean.rs | 28 +++++++++++++++------------- src/circuit/ecc.rs | 8 ++------ src/circuit/mod.rs | 28 +++++++++++----------------- src/circuit/num.rs | 17 +++++++++++++---- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 9209773..6da5398 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -301,11 +301,12 @@ pub fn u64_into_boolean_vec_le>( Ok(bits) } -pub fn field_into_allocated_bits_be, F: PrimeField>( +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()); @@ -332,7 +333,8 @@ pub fn field_into_allocated_bits_be, F: Prime } }; - let bits = values.into_iter().enumerate().map(|(i, b)| { + // Allocate in little-endian order + let bits = values.into_iter().rev().enumerate().map(|(i, b)| { AllocatedBit::alloc( cs.namespace(|| format!("bit {}", i)), b @@ -512,7 +514,7 @@ mod test { use super::{ AllocatedBit, Boolean, - field_into_allocated_bits_be, + field_into_allocated_bits_le, u64_into_boolean_vec_le }; @@ -1003,24 +1005,24 @@ mod test { } #[test] - fn test_field_into_allocated_bits_be() { + 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_be(&mut cs, Some(r)).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[0].value.unwrap(), false); - assert_eq!(bits[1].value.unwrap(), false); - assert_eq!(bits[2].value.unwrap(), true); - assert_eq!(bits[3].value.unwrap(), false); - assert_eq!(bits[4].value.unwrap(), true); - assert_eq!(bits[5].value.unwrap(), false); - assert_eq!(bits[20].value.unwrap(), true); - assert_eq!(bits[23].value.unwrap(), true); + 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); } } diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 7642db4..3e84ede 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -105,18 +105,14 @@ impl EdwardsPoint { { let mut tmp = vec![]; - let mut x = self.x.into_bits_strict( + let x = self.x.into_bits_strict( cs.namespace(|| "unpack x") )?; - let mut y = self.y.into_bits_strict( + let y = self.y.into_bits_strict( cs.namespace(|| "unpack y") )?; - // We want the representation in little endian bit order - x.reverse(); - y.reverse(); - tmp.extend(y); tmp.push(x[0].clone()); diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 093aa9d..9f8e1d2 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -81,12 +81,11 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // Booleanize the randomness - let hr = boolean::field_into_allocated_bits_be( + let hr = boolean::field_into_allocated_bits_le( cs.namespace(|| "hr"), self.value_randomness )? .into_iter() - .rev() // Little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -110,13 +109,13 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let rk; { // Witness rsk as bits - let rsk = boolean::field_into_allocated_bits_be( + let rsk = boolean::field_into_allocated_bits_le( cs.namespace(|| "rsk"), self.rsk )? .into_iter() - .rev() // We need it in little endian bit order - .map(|e| boolean::Boolean::from(e)).collect::>(); + .map(|e| boolean::Boolean::from(e)) + .collect::>(); // NB: We don't ensure that the bit representation of rsk // is "in the field" (Fs) because it's not used except to @@ -206,12 +205,11 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_allocated_bits_be( + let cmr = boolean::field_into_allocated_bits_le( cs.namespace(|| "cmr"), self.commitment_randomness )? .into_iter() - .rev() // We need it in little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -358,12 +356,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { )?; // Booleanize the randomness - let hr = boolean::field_into_allocated_bits_be( + let hr = boolean::field_into_allocated_bits_le( cs.namespace(|| "hr"), self.value_randomness )? .into_iter() - .rev() // Little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -422,12 +419,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { ); // Compute epk from esk - let esk = boolean::field_into_allocated_bits_be( + let esk = boolean::field_into_allocated_bits_le( cs.namespace(|| "esk"), self.esk )? .into_iter() - .rev() // We need it in little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -446,12 +442,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { let p_d = self.p_d.map(|e| e.into_xy()); - let y_contents = boolean::field_into_allocated_bits_be( + let y_contents = boolean::field_into_allocated_bits_le( cs.namespace(|| "p_d bits of y"), p_d.map(|e| e.1) )? .into_iter() - .rev() // We need it in little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -481,12 +476,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_allocated_bits_be( + let cmr = boolean::field_into_allocated_bits_le( cs.namespace(|| "cmr"), self.commitment_randomness )? .into_iter() - .rev() // We need it in little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -552,7 +546,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "a3ac418bbbe38d08295995c8cdcaebd6902fcfa9e4f7212c9742ed033c1edec3"); + assert_eq!(cs.hash(), "db283e10d01d6c3c4d23cd3c05a7ae8f1a7d8091a39f8d8b604e610ca6a3e496"); } } @@ -590,6 +584,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "b74e3ee749e1cbc405b5b4a1de3b11119084afda9b6f5e3a6865cbcc5c35e3d4"); + assert_eq!(cs.hash(), "ccb2ad9a6d492e708da155305064a3b8af5d29b4b766cf08ac415a478aae4cc6"); } } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 48f1fda..1325e90 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -83,6 +83,11 @@ impl AllocatedNum { 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_strict( &self, mut cs: CS @@ -208,16 +213,20 @@ impl AllocatedNum { |_| lc ); - Ok(result.into_iter().map(|b| Boolean::from(b)).collect()) + // 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( &self, mut cs: CS ) -> Result, SynthesisError> where CS: ConstraintSystem { - let bits = boolean::field_into_allocated_bits_be( + let bits = boolean::field_into_allocated_bits_le( &mut cs, self.value )?; @@ -225,7 +234,7 @@ impl AllocatedNum { let mut lc = LinearCombination::zero(); let mut coeff = E::Fr::one(); - for bit in bits.iter().rev() { + for bit in bits.iter() { lc = lc + (coeff, bit.get_variable()); coeff.double(); @@ -585,7 +594,7 @@ mod test { assert!(cs.is_satisfied()); - for (b, a) in BitIterator::new(r.into_repr()).skip(1).zip(bits.iter()) { + 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 { From 3971ecd375b8854da71d764c2d2e166a5efca747 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 10:52:56 -0700 Subject: [PATCH 080/168] Abstract away the boolean conversion of field witnessing. --- src/circuit/boolean.rs | 10 +++++++++ src/circuit/mod.rs | 49 ++++++++++++------------------------------ 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 6da5398..239d404 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -301,6 +301,16 @@ pub fn u64_into_boolean_vec_le>( 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 diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 9f8e1d2..8058d02 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -81,13 +81,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // Booleanize the randomness - let hr = boolean::field_into_allocated_bits_le( + let hr = boolean::field_into_boolean_vec_le( cs.namespace(|| "hr"), self.value_randomness - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let hr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of randomization for value commitment"), @@ -109,13 +106,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let rk; { // Witness rsk as bits - let rsk = boolean::field_into_allocated_bits_le( + let rsk = boolean::field_into_boolean_vec_le( cs.namespace(|| "rsk"), self.rsk - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; // NB: We don't ensure that the bit representation of rsk // is "in the field" (Fs) because it's not used except to @@ -205,13 +199,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_allocated_bits_le( + let cmr = boolean::field_into_boolean_vec_le( cs.namespace(|| "cmr"), self.commitment_randomness - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), @@ -356,13 +347,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { )?; // Booleanize the randomness - let hr = boolean::field_into_allocated_bits_le( + let hr = boolean::field_into_boolean_vec_le( cs.namespace(|| "hr"), self.value_randomness - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let hr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of randomization for value commitment"), @@ -419,13 +407,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { ); // Compute epk from esk - let esk = boolean::field_into_allocated_bits_le( + let esk = boolean::field_into_boolean_vec_le( cs.namespace(|| "esk"), self.esk - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let epk = g_d.mul( cs.namespace(|| "epk computation"), @@ -442,13 +427,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { let p_d = self.p_d.map(|e| e.into_xy()); - let y_contents = boolean::field_into_allocated_bits_le( + let y_contents = boolean::field_into_boolean_vec_le( cs.namespace(|| "p_d bits of y"), p_d.map(|e| e.1) - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( cs.namespace(|| "p_d bit of x"), @@ -476,13 +458,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_allocated_bits_le( + let cmr = boolean::field_into_boolean_vec_le( cs.namespace(|| "cmr"), self.commitment_randomness - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), From 8cbcd7739c9911b183aa5e93e26ef832099d1870 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 15:12:51 -0700 Subject: [PATCH 081/168] Rename into_bits and into_bits_strict to signify endianness. --- src/circuit/ecc.rs | 4 ++-- src/circuit/mod.rs | 8 +++++--- src/circuit/num.rs | 10 +++++----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 3e84ede..07640cd 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -105,11 +105,11 @@ impl EdwardsPoint { { let mut tmp = vec![]; - let x = self.x.into_bits_strict( + let x = self.x.into_bits_le_strict( cs.namespace(|| "unpack x") )?; - let y = self.y.into_bits_strict( + let y = self.y.into_bits_le_strict( cs.namespace(|| "unpack y") )?; diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 8058d02..1d0bc7b 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -250,10 +250,12 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // We don't need to be strict, because the function is - // collision-resistant. + // 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(cs.namespace(|| "xl into bits"))?); - preimage.extend(xr.into_bits(cs.namespace(|| "xr into bits"))?); + preimage.extend(xl.into_bits_le(cs.namespace(|| "xl into bits"))?); + preimage.extend(xr.into_bits_le(cs.namespace(|| "xr into bits"))?); cur = pedersen_hash::pedersen_hash( cs.namespace(|| "computation of pedersen hash"), diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 1325e90..35b12da 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -88,7 +88,7 @@ impl AllocatedNum { /// order, requiring that the representation /// strictly exists "in the field" (i.e., a /// congruency is not allowed.) - pub fn into_bits_strict( + pub fn into_bits_le_strict( &self, mut cs: CS ) -> Result, SynthesisError> @@ -220,7 +220,7 @@ impl AllocatedNum { /// 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( + pub fn into_bits_le( &self, mut cs: CS ) -> Result, SynthesisError> @@ -565,7 +565,7 @@ mod test { let mut cs = TestConstraintSystem::::new(); let n = AllocatedNum::alloc(&mut cs, || Ok(negone)).unwrap(); - n.into_bits_strict(&mut cs).unwrap(); + n.into_bits_le_strict(&mut cs).unwrap(); assert!(cs.is_satisfied()); @@ -587,9 +587,9 @@ mod test { let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap(); let bits = if i % 2 == 0 { - n.into_bits(&mut cs).unwrap() + n.into_bits_le(&mut cs).unwrap() } else { - n.into_bits_strict(&mut cs).unwrap() + n.into_bits_le_strict(&mut cs).unwrap() }; assert!(cs.is_satisfied()); From 69010d1502300aa80f632d2862c2ebcecaad4cf8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 15:25:40 -0700 Subject: [PATCH 082/168] Relocate Pedersen hash personalization enum. --- src/circuit/pedersen_hash.rs | 19 +------------------ src/pedersen_hash.rs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 7eec3bb..6daafeb 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -9,13 +9,7 @@ use bellman::{ ConstraintSystem }; use super::lookup::*; - -// TODO: ensure these match the spec -pub enum Personalization { - NoteCommitment, - AnotherPersonalization, - MerkleTree(usize) -} +pub use pedersen_hash::Personalization; impl Personalization { fn get_constant_bools(&self) -> Vec { @@ -24,17 +18,6 @@ impl Personalization { .map(|e| Boolean::constant(e)) .collect() } - - pub fn get_bits(&self) -> Vec { - match *self { - Personalization::NoteCommitment => - vec![false, false, false, false, false, false], - Personalization::AnotherPersonalization => - vec![false, false, false, false, false, true], - Personalization::MerkleTree(_) => - vec![false, false, false, false, true, false], - } - } } pub fn pedersen_hash( diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 1eb75f6..83c3965 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -1,7 +1,24 @@ use jubjub::*; use pairing::*; -pub use circuit::pedersen_hash::Personalization; +pub enum Personalization { + NoteCommitment, + AnotherPersonalization, + MerkleTree(usize) +} + +impl Personalization { + pub fn get_bits(&self) -> Vec { + match *self { + Personalization::NoteCommitment => + vec![false, false, false, false, false, false], + Personalization::AnotherPersonalization => + vec![false, false, false, false, false, true], + Personalization::MerkleTree(_) => + vec![false, false, false, false, true, false], + } + } +} pub fn pedersen_hash( personalization: Personalization, From b45a37febb736d84064b35d71c2e8416622298f5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 16:00:04 -0700 Subject: [PATCH 083/168] Add comments and reorder some generators. --- src/circuit/mod.rs | 4 +-- src/jubjub/mod.rs | 67 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 1d0bc7b..e41736b 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -527,7 +527,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "db283e10d01d6c3c4d23cd3c05a7ae8f1a7d8091a39f8d8b604e610ca6a3e496"); + assert_eq!(cs.hash(), "cae701c7acd6fee80b8dfc547855f44dcb3eb6cf64e434afa8c77a93bafd9d0e"); } } @@ -565,6 +565,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "ccb2ad9a6d492e708da155305064a3b8af5d29b4b766cf08ac415a478aae4cc6"); + assert_eq!(cs.hash(), "f9c01583d089117e01ee5d0dcc8d8d0d1f6c4af0a420a9981a5af9a572df26f1"); } } diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 189d434..dfc34a2 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -34,26 +34,80 @@ pub mod montgomery; #[cfg(test)] pub mod tests; +/// 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. + ProvingPublicKey = 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 +} + +/// 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 { type Fs: PrimeField + SqrtField; 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 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 magntitudes of some + /// fixed generator. fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>]; } +/// Point of unknown order. pub enum Unknown { } + +/// Point of prime order. pub enum PrimeOrder { } pub mod fs; @@ -63,19 +117,6 @@ impl JubjubEngine for Bls12 { type Params = JubjubBls12; } -/// Fixed generators of the Jubjub curve of unknown -/// exponent. -#[derive(Copy, Clone)] -pub enum FixedGenerators { - NoteCommitmentRandomness = 0, - ProvingPublicKey = 1, - ValueCommitmentValue = 2, - ValueCommitmentRandomness = 3, - NullifierPosition = 4, - SpendingKeyGenerator = 5, - Max = 6 -} - pub struct JubjubBls12 { edwards_d: Fr, montgomery_a: Fr, From c6cf0c718c7c9dfe2b0e1ebb9d1031675d3b671f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 16:10:55 -0700 Subject: [PATCH 084/168] Value placed in note contents should be in big endian bit order. --- src/circuit/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index e41736b..17ee6d4 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -174,7 +174,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Compute note contents let mut note_contents = vec![]; - note_contents.extend(value_bits); + note_contents.extend(value_bits.into_iter().rev()); note_contents.extend( g_d.repr(cs.namespace(|| "representation of g_d"))? ); @@ -372,7 +372,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // Let's start to construct our note let mut note_contents = vec![]; - note_contents.extend(value_bits); + note_contents.extend(value_bits.into_iter().rev()); // Let's deal with g_d { @@ -527,7 +527,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "cae701c7acd6fee80b8dfc547855f44dcb3eb6cf64e434afa8c77a93bafd9d0e"); + assert_eq!(cs.hash(), "1c5298e7f9ec46f227d3622968b092bfbc1d15a9f45fcf4910b6edb60fe4f0f8"); } } @@ -565,6 +565,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "f9c01583d089117e01ee5d0dcc8d8d0d1f6c4af0a420a9981a5af9a572df26f1"); + assert_eq!(cs.hash(), "a76f4ae0b3e078b6d3d44bf6d9c1d121884b30f74c97f77f114978196f4949b3"); } } From 7bb630a4b16afff8e24c4303995d2ba8e626f6ae Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 17:27:55 -0700 Subject: [PATCH 085/168] Use correct personalization for merkle tree. --- src/circuit/mod.rs | 6 +++--- src/circuit/pedersen_hash.rs | 6 +++--- src/pedersen_hash.rs | 9 +++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 17ee6d4..4aeec40 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -259,7 +259,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { cur = pedersen_hash::pedersen_hash( cs.namespace(|| "computation of pedersen hash"), - pedersen_hash::Personalization::MerkleTree(tree_depth - i), + pedersen_hash::Personalization::MerkleTree(i), &preimage, self.params )?.x; // Injective encoding @@ -527,7 +527,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "1c5298e7f9ec46f227d3622968b092bfbc1d15a9f45fcf4910b6edb60fe4f0f8"); + assert_eq!(cs.hash(), "3920570cfb4c9cec807d09f996d6d0745176d50e8adea0e66709628b1dd31267"); } } @@ -565,6 +565,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "a76f4ae0b3e078b6d3d44bf6d9c1d121884b30f74c97f77f114978196f4949b3"); + assert_eq!(cs.hash(), "155b1aaf4ed4abb1af67481c7e099adafd6a7edd097926b1f9f6b68b1cbe2742"); } } diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 6daafeb..8b3d715 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -163,7 +163,7 @@ mod test { let res = pedersen_hash( cs.namespace(|| "pedersen hash"), - Personalization::NoteCommitment, + Personalization::MerkleTree(1), &input_bools, params ).unwrap(); @@ -171,7 +171,7 @@ mod test { assert!(cs.is_satisfied()); let expected = ::pedersen_hash::pedersen_hash::( - Personalization::NoteCommitment, + Personalization::MerkleTree(1), input.clone().into_iter(), params ).into_xy(); @@ -181,7 +181,7 @@ mod test { // Test against the output of a different personalization let unexpected = ::pedersen_hash::pedersen_hash::( - Personalization::AnotherPersonalization, + Personalization::MerkleTree(0), input.into_iter(), params ).into_xy(); diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 83c3965..a1eda3b 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -3,7 +3,6 @@ use pairing::*; pub enum Personalization { NoteCommitment, - AnotherPersonalization, MerkleTree(usize) } @@ -11,11 +10,9 @@ impl Personalization { pub fn get_bits(&self) -> Vec { match *self { Personalization::NoteCommitment => - vec![false, false, false, false, false, false], - Personalization::AnotherPersonalization => - vec![false, false, false, false, false, true], - Personalization::MerkleTree(_) => - vec![false, false, false, false, true, false], + vec![true, true, true, true, true, true], + Personalization::MerkleTree(num) => + (0..6).map(|i| (num >> i) & 1 == 1).collect() } } } From 2e846844e7e97490344e0e6d61229fd009637718 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 17:46:12 -0700 Subject: [PATCH 086/168] Perform ak group order check in Spend circuit. --- src/circuit/ecc.rs | 29 +++++++++++++++++++++++++++++ src/circuit/mod.rs | 34 +++++++++++----------------------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 07640cd..c05e6ca 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -84,6 +84,35 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + 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 diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 4aeec40..34fadfd 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -131,6 +131,11 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; + ak.assert_not_small_order( + cs.namespace(|| "ak not small order"), + self.params + )?; + // Unpack ak and rk for input to BLAKE2s let mut vk = vec![]; let mut rho_preimage = vec![]; @@ -382,27 +387,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; - // Check that g_d is not of small order - { - let g_d = g_d.double( - cs.namespace(|| "first doubling of g_d"), - self.params - )?; - let g_d = g_d.double( - cs.namespace(|| "second doubling of g_d"), - self.params - )?; - let g_d = g_d.double( - cs.namespace(|| "third doubling of g_d"), - self.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. - g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?; - } + g_d.assert_not_small_order( + cs.namespace(|| "g_d not small order"), + self.params + )?; note_contents.extend( g_d.repr(cs.namespace(|| "representation of g_d"))? @@ -526,8 +514,8 @@ fn test_input_circuit_with_bls12_381() { instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "3920570cfb4c9cec807d09f996d6d0745176d50e8adea0e66709628b1dd31267"); + assert_eq!(cs.num_constraints(), 97395); + assert_eq!(cs.hash(), "29aee738a11546a94c3dde68cede66eebcf2b447104a199aab22bf571735092a"); } } From 058801bdfc00374474a73f246b641a6baa441149 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 17:58:34 -0700 Subject: [PATCH 087/168] Switch to using the blake2-rfc crate instead. --- Cargo.toml | 2 +- src/circuit/blake2s.rs | 9 ++++----- src/circuit/test/mod.rs | 14 +++++++------- src/group_hash.rs | 9 ++++----- src/lib.rs | 2 +- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cdb161b..c20cb10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ features = ["expose-arith"] [dependencies] rand = "0.4" -blake2 = "0.7" +blake2-rfc = "0.2.18" digest = "0.7" bellman = "0.0.9" diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index 855a8c6..c46b243 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -313,8 +313,7 @@ mod test { use ::circuit::test::TestConstraintSystem; use super::blake2s; use bellman::{ConstraintSystem}; - use blake2::{Blake2s}; - use digest::{FixedOutput, Input}; + use blake2_rfc::blake2s::Blake2s; #[test] fn test_blake2s_constraints() { @@ -357,13 +356,13 @@ mod test { for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) { - let mut h = Blake2s::new_keyed(&[], 32); + let mut h = Blake2s::new(32); let data: Vec = (0..input_len).map(|_| rng.gen()).collect(); - h.process(&data); + h.update(&data); - let hash_result = h.fixed_result(); + let hash_result = h.finalize(); let mut cs = TestConstraintSystem::::new(); diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index e7f7515..01fda4a 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -16,12 +16,12 @@ use bellman::{ use std::collections::HashMap; use std::fmt::Write; -use blake2::{Blake2s}; -use digest::{FixedOutput, Input}; use byteorder::{BigEndian, ByteOrder}; use std::cmp::Ordering; use std::collections::BTreeMap; +use blake2_rfc::blake2s::Blake2s; + #[derive(Debug)] enum NamedObject { Constraint(usize), @@ -107,7 +107,7 @@ fn hash_lc( let mut buf = [0u8; 9 + 32]; BigEndian::write_u64(&mut buf[0..8], map.len() as u64); - h.process(&buf[0..8]); + h.update(&buf[0..8]); for (var, coeff) in map { match var.0.get_unchecked() { @@ -123,7 +123,7 @@ fn hash_lc( coeff.into_repr().write_be(&mut buf[9..]).unwrap(); - h.process(&buf); + h.update(&buf); } } @@ -230,14 +230,14 @@ impl TestConstraintSystem { } pub fn hash(&self) -> String { - let mut h = Blake2s::new_keyed(&[], 32); + 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.process(&buf); + h.update(&buf); } for constraint in &self.constraints { @@ -247,7 +247,7 @@ impl TestConstraintSystem { } let mut s = String::new(); - for b in h.fixed_result().as_ref() { + for b in h.finalize().as_ref() { s += &format!("{:02x}", b); } diff --git a/src/group_hash.rs b/src/group_hash.rs index 01824c8..04faecb 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -1,7 +1,6 @@ use jubjub::*; use pairing::*; -use blake2::{Blake2s}; -use digest::{FixedOutput, Input}; +use blake2_rfc::blake2s::Blake2s; /// Produces an (x, y) pair (Montgomery) for a /// random point in the Jubjub curve. The point @@ -15,9 +14,9 @@ pub fn group_hash( // Check to see that scalar field is 255 bits assert!(E::Fr::NUM_BITS == 255); - let mut h = Blake2s::new_keyed(&[], 32); - h.process(tag); - let mut h = h.fixed_result().to_vec(); + let mut h = Blake2s::new(32); + h.update(tag); + let mut h = h.finalize().as_ref().to_vec(); assert!(h.len() == 32); // Take first/unset first bit of hash diff --git a/src/lib.rs b/src/lib.rs index 4a08bf9..8501bde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ extern crate pairing; extern crate bellman; -extern crate blake2; +extern crate blake2_rfc; extern crate digest; extern crate rand; From d21ff081767e08593044d7613a47636f1013e7d0 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 18:08:49 -0700 Subject: [PATCH 088/168] Group hash should use a first block containing random data as per spec. --- src/circuit/mod.rs | 4 ++-- src/group_hash.rs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 34fadfd..f057a83 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -515,7 +515,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97395); - assert_eq!(cs.hash(), "29aee738a11546a94c3dde68cede66eebcf2b447104a199aab22bf571735092a"); + assert_eq!(cs.hash(), "cdd3cde0a4e076b46a59ef85fb70369eb14e3ee921a06d88bad6be4f78b5f261"); } } @@ -553,6 +553,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "155b1aaf4ed4abb1af67481c7e099adafd6a7edd097926b1f9f6b68b1cbe2742"); + assert_eq!(cs.hash(), "67518baade37a3cf76453fa474cb8c9b2ee4223ed5502151e3b83dd1ec98a261"); } } diff --git a/src/group_hash.rs b/src/group_hash.rs index 04faecb..7d04e36 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -2,6 +2,10 @@ use jubjub::*; use pairing::*; use blake2_rfc::blake2s::Blake2s; +/// This is chosen to be some random string that we couldn't have anticipated when we designed +/// the algorithm, for rigidity purposes. +pub const FIRST_BLOCK: &'static [u8; 64] = b"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; + /// Produces an (x, y) pair (Montgomery) for a /// random point in the Jubjub curve. The point /// is guaranteed to be prime order and not the @@ -15,6 +19,7 @@ pub fn group_hash( assert!(E::Fr::NUM_BITS == 255); let mut h = Blake2s::new(32); + h.update(FIRST_BLOCK); h.update(tag); let mut h = h.finalize().as_ref().to_vec(); assert!(h.len() == 32); From b831942501363658131a3ae6168e0964809dcd9c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 19:21:41 -0700 Subject: [PATCH 089/168] Adopt BLAKE2s personalization throughout protocol. --- Cargo.toml | 6 ++++-- src/circuit/blake2s.rs | 45 ++++++++++++++++++++++++++++++++++-------- src/circuit/mod.rs | 10 ++++++---- src/group_hash.rs | 5 ++++- src/jubjub/mod.rs | 4 ++-- src/lib.rs | 8 ++++++++ src/pedersen_hash.rs | 5 ++++- 7 files changed, 65 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c20cb10..4a2aefd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,14 @@ features = ["expose-arith"] [dependencies] rand = "0.4" -blake2-rfc = "0.2.18" digest = "0.7" bellman = "0.0.9" - byteorder = "1" +[dependencies.blake2-rfc] +git = "https://github.com/gtank/blake2-rfc" +rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9" + [dev-dependencies] hex-literal = "0.1" diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index c46b243..f75f5c7 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -254,9 +254,13 @@ fn blake2s_compression>( pub fn blake2s>( mut cs: CS, - input: &[Boolean] + 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); @@ -266,8 +270,10 @@ pub fn blake2s>( h.push(UInt32::constant(0xA54FF53A)); h.push(UInt32::constant(0x510E527F)); h.push(UInt32::constant(0x9B05688C)); - h.push(UInt32::constant(0x1F83D9AB)); - h.push(UInt32::constant(0x5BE0CD19)); + + // 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![]; @@ -315,11 +321,34 @@ mod test { 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).rev() { + 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).unwrap(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 21792); } @@ -336,7 +365,7 @@ mod test { .chain((0..512) .map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into())) .collect(); - blake2s(&mut cs, &input_bits).unwrap(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 21792); } @@ -346,7 +375,7 @@ mod test { 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).unwrap(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert_eq!(cs.num_constraints(), 0); } @@ -356,7 +385,7 @@ mod test { for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) { - let mut h = Blake2s::new(32); + let mut h = Blake2s::with_params(32, &[], &[], b"12345678"); let data: Vec = (0..input_len).map(|_| rng.gen()).collect(); @@ -376,7 +405,7 @@ mod test { } } - let r = blake2s(&mut cs, &input_bits).unwrap(); + let r = blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index f057a83..6ee8853 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -156,7 +156,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Compute the incoming viewing key let mut ivk = blake2s::blake2s( cs.namespace(|| "computation of ivk"), - &vk + &vk, + ::CRH_IVK_PERSONALIZATION )?; // Little endian bit order @@ -299,7 +300,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let mut rho = blake2s::blake2s( cs.namespace(|| "rho computation"), - &rho_preimage + &rho_preimage, + ::PRF_NR_PERSONALIZATION )?; // Little endian bit order @@ -515,7 +517,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97395); - assert_eq!(cs.hash(), "cdd3cde0a4e076b46a59ef85fb70369eb14e3ee921a06d88bad6be4f78b5f261"); + assert_eq!(cs.hash(), "9f730803965612392772c3c1fbb110c1539656e1bab40d5a9a124b06e927ef40"); } } @@ -553,6 +555,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "67518baade37a3cf76453fa474cb8c9b2ee4223ed5502151e3b83dd1ec98a261"); + assert_eq!(cs.hash(), "f4219872738a81ef3ea66199ea5019d87f53ec369ee7f64d0b7c63ade6014114"); } } diff --git a/src/group_hash.rs b/src/group_hash.rs index 7d04e36..58ece78 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -12,13 +12,16 @@ pub const FIRST_BLOCK: &'static [u8; 64] = b"0000000000000000002ffe76b973aabaff1 /// 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::new(32); + let mut h = Blake2s::with_params(32, &[], &[], personalization); h.update(FIRST_BLOCK); h.update(tag); let mut h = h.finalize().as_ref().to_vec(); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index dfc34a2..c293b34 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -186,7 +186,7 @@ impl JubjubBls12 { let mut pedersen_hash_generators = vec![]; while pedersen_hash_generators.len() < 10 { - let gh = group_hash(&[cur], &tmp); + let gh = group_hash(&[cur], ::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); cur += 1; @@ -205,7 +205,7 @@ impl JubjubBls12 { let mut fixed_base_generators = vec![]; while fixed_base_generators.len() < (FixedGenerators::Max as usize) { - let gh = group_hash(&[cur], &tmp); + let gh = group_hash(&[cur], ::OTHER_PERSONALIZATION, &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); cur += 1; diff --git a/src/lib.rs b/src/lib.rs index 8501bde..e14cd9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,3 +15,11 @@ pub mod circuit; pub mod group_hash; pub mod pedersen_hash; pub mod primitives; + +// BLAKE2s personalizations +pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; +pub const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; +pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; + +// TODO: Expand the personalizations to the specific generators +pub const OTHER_PERSONALIZATION: &'static [u8; 8] = b"GOTOFAIL"; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index a1eda3b..5c3bb90 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -11,8 +11,11 @@ impl Personalization { match *self { Personalization::NoteCommitment => vec![true, true, true, true, true, true], - Personalization::MerkleTree(num) => + Personalization::MerkleTree(num) => { + assert!(num < 63); + (0..6).map(|i| (num >> i) & 1 == 1).collect() + } } } } From f155c01cf540437ccab2a2575cc17a431a36dc7b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 08:30:28 -0700 Subject: [PATCH 090/168] Personalize GH for each generator independently. --- src/circuit/mod.rs | 4 +-- src/jubjub/mod.rs | 72 ++++++++++++++++++++++++++++++++++++++++------ src/lib.rs | 26 +++++++++++++---- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 6ee8853..e461257 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -517,7 +517,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97395); - assert_eq!(cs.hash(), "9f730803965612392772c3c1fbb110c1539656e1bab40d5a9a124b06e927ef40"); + assert_eq!(cs.hash(), "9abc0559abf54a41da789313b1692dc744d940646bb7dd3e6c01ceb54d0cc261"); } } @@ -555,6 +555,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "f4219872738a81ef3ea66199ea5019d87f53ec369ee7f64d0b7c63ade6014114"); + assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); } } diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index c293b34..3b9a8d3 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -201,17 +201,65 @@ impl JubjubBls12 { // Create the bases for other parts of the protocol { - let mut cur = 0; - let mut fixed_base_generators = vec![]; + let mut fixed_base_generators = vec![edwards::Point::zero(); FixedGenerators::Max as usize]; - while fixed_base_generators.len() < (FixedGenerators::Max as usize) { - let gh = group_hash(&[cur], ::OTHER_PERSONALIZATION, &tmp); - // We don't want to overflow and start reusing generators - assert!(cur != u8::max_value()); - cur += 1; + { + // Each generator is found by invoking the group hash + // on tag 0x00, 0x01, ... until we find a valid result. + let find_first_gh = |personalization| { + let mut cur = 0; - if let Some(gh) = gh { - fixed_base_generators.push(gh); + loop { + let gh = group_hash::(&[cur], personalization, &tmp); + // We don't want to overflow. + assert!(cur != u8::max_value()); + cur += 1; + + if let Some(gh) = gh { + break gh; + } + } + }; + + // Written this way for exhaustion (double entendre). There's no + // way to iterate over the variants of an enum, so it's hideous. + for c in 0..(FixedGenerators::Max as usize) { + let p = match c { + c if c == (FixedGenerators::ProvingPublicKey as usize) => { + ::PROVING_KEY_BASE_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::NoteCommitmentRandomness as usize) => { + ::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::NullifierPosition as usize) => { + ::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::ValueCommitmentValue as usize) => { + ::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::ValueCommitmentRandomness as usize) => { + ::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::SpendingKeyGenerator as usize) => { + ::SPENDING_KEY_GENERATOR_PERSONALIZATION + }, + _ => unreachable!() + }; + + fixed_base_generators[c] = find_first_gh(p); + } + } + + // 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!"); + } } } @@ -223,18 +271,23 @@ impl JubjubBls12 { { let mut pedersen_circuit_generators = vec![]; + // Process each segment for mut gen in tmp.pedersen_hash_generators.iter().cloned() { let mut gen = montgomery::Point::from_edwards(&gen, &tmp); let mut windows = vec![]; for _ in 0..tmp.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); } windows.push(coeffs); + // Our chunks are separated by 2 bits to prevent overlap. for _ in 0..4 { gen = gen.double(&tmp); } @@ -261,6 +314,7 @@ impl JubjubBls12 { } windows.push(coeffs); + // gen = gen * 8 gen = g; } fixed_base_circuit_generators.push(windows); diff --git a/src/lib.rs b/src/lib.rs index e14cd9b..1fa9fb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,10 +16,24 @@ pub mod group_hash; pub mod pedersen_hash; pub mod primitives; -// BLAKE2s personalizations -pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; -pub const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; -pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; +// BLAKE2s invocation personalizations +/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | rk) +const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; +/// BLAKE2s Personalization for PRF^nr = BLAKE2s(rk | cm + position) +const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; -// TODO: Expand the personalizations to the specific generators -pub const OTHER_PERSONALIZATION: &'static [u8; 8] = b"GOTOFAIL"; +// Group hash personalizations +/// BLAKE2s Personalization for Pedersen hash generators. +const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; +/// BLAKE2s Personalization for the proof generation key base point +const PROVING_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; +/// BLAKE2s Personalization for the note commitment randomness generator +const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"abcdefgh"; +/// BLAKE2s Personalization for the nullifier position generator (for PRF^nr) +const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"nfnfnfnf"; +/// BLAKE2s Personalization for the value commitment generator for the value +const VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"45u8gh45"; +/// BLAKE2s Personalization for the value commitment randomness generator +const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"11111111"; +/// BLAKE2s Personalization for the spending key base point +const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"sksksksk"; From 7a9879eb542f9e5f6196f081cd17efde6a2a2e53 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 08:38:34 -0700 Subject: [PATCH 091/168] Make EdwardsPoint (x, y) not public. --- src/circuit/ecc.rs | 18 ++++++++++-------- src/circuit/mod.rs | 6 +++--- src/circuit/pedersen_hash.rs | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index c05e6ca..71f1caa 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -32,8 +32,8 @@ use super::boolean::Boolean; #[derive(Clone)] pub struct EdwardsPoint { - pub x: AllocatedNum, - pub y: AllocatedNum + x: AllocatedNum, + y: AllocatedNum } /// Perform a fixed-base scalar multiplication with @@ -84,6 +84,14 @@ pub fn fixed_base_multiplication( } 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, @@ -183,12 +191,6 @@ impl EdwardsPoint { ) } - /// This extracts the x-coordinate, which is an injective - /// encoding for elements of the prime order subgroup. - pub fn into_num(&self) -> AllocatedNum { - self.x.clone() - } - /// Returns `self` if condition is true, and the neutral /// element (0, 1) otherwise. pub fn conditionally_select( diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index e461257..fa7df72 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -229,7 +229,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let mut position_bits = vec![]; // Injective encoding. - let mut cur = cm.x.clone(); + let mut cur = cm.get_x().clone(); for (i, e) in self.auth_path.into_iter().enumerate() { let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); @@ -268,7 +268,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { pedersen_hash::Personalization::MerkleTree(i), &preimage, self.params - )?.x; // Injective encoding + )?.get_x().clone(); // Injective encoding } assert_eq!(position_bits.len(), tree_depth); @@ -473,7 +473,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // since we know it is prime order, and we know that // the x-coordinate is an injective encoding for // prime-order elements. - cm.x.inputize(cs.namespace(|| "commitment"))?; + cm.get_x().inputize(cs.namespace(|| "commitment"))?; Ok(()) } diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 8b3d715..407ef93 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -176,8 +176,8 @@ mod test { params ).into_xy(); - assert_eq!(res.x.get_value().unwrap(), expected.0); - assert_eq!(res.y.get_value().unwrap(), expected.1); + 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::( @@ -186,8 +186,8 @@ mod test { params ).into_xy(); - assert!(res.x.get_value().unwrap() != unexpected.0); - assert!(res.y.get_value().unwrap() != unexpected.1); + assert!(res.get_x().get_value().unwrap() != unexpected.0); + assert!(res.get_y().get_value().unwrap() != unexpected.1); } } } From 63c6830429d217ffa4c7925fe8e1e16c35b74133 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 09:03:29 -0700 Subject: [PATCH 092/168] Cache fewer pedersen hash generators. --- src/circuit/pedersen_hash.rs | 2 +- src/jubjub/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 407ef93..eb1745f 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -149,7 +149,7 @@ mod test { let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); let params = &JubjubBls12::new(); - for length in 1..1000 { + for length in 0..751 { for _ in 0..5 { let mut input: Vec = (0..length).map(|_| rng.gen()).collect(); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 3b9a8d3..46ecca0 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -185,7 +185,7 @@ impl JubjubBls12 { let mut cur = 0; let mut pedersen_hash_generators = vec![]; - while pedersen_hash_generators.len() < 10 { + while pedersen_hash_generators.len() < 5 { let gh = group_hash(&[cur], ::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); From c31092ce7fbce97297a36d77f5b646168a22fd3f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 09:39:56 -0700 Subject: [PATCH 093/168] Remove unnecessary mut binding. --- src/circuit/num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 35b12da..53a2f6c 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -441,7 +441,7 @@ impl Num { ) -> Self { let newval = match (self.value, bit.get_value()) { - (Some(mut curval), Some(mut bval)) => { + (Some(mut curval), Some(bval)) => { if bval { curval.add_assign(&coeff); } From 1f2bb626575d3c74ddd9a2cf62ab548e87319de4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 09:56:29 -0700 Subject: [PATCH 094/168] Rename proving key to proof generation key to disambiguate from the SNARK. --- src/circuit/mod.rs | 2 +- src/jubjub/mod.rs | 6 +++--- src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index fa7df72..8983222 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -118,7 +118,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { rk = ecc::fixed_base_multiplication( cs.namespace(|| "computation of rk"), - FixedGenerators::ProvingPublicKey, + FixedGenerators::ProofGenerationKey, &rsk, self.params )?; diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 46ecca0..b9bdfaf 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -41,7 +41,7 @@ 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. - ProvingPublicKey = 0, + ProofGenerationKey = 0, /// The note commitment is randomized over this generator. NoteCommitmentRandomness = 1, @@ -225,8 +225,8 @@ impl JubjubBls12 { // way to iterate over the variants of an enum, so it's hideous. for c in 0..(FixedGenerators::Max as usize) { let p = match c { - c if c == (FixedGenerators::ProvingPublicKey as usize) => { - ::PROVING_KEY_BASE_GENERATOR_PERSONALIZATION + c if c == (FixedGenerators::ProofGenerationKey as usize) => { + ::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::NoteCommitmentRandomness as usize) => { ::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION diff --git a/src/lib.rs b/src/lib.rs index 1fa9fb4..686f93a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; /// BLAKE2s Personalization for Pedersen hash generators. const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; /// BLAKE2s Personalization for the proof generation key base point -const PROVING_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; +const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; /// BLAKE2s Personalization for the note commitment randomness generator const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"abcdefgh"; /// BLAKE2s Personalization for the nullifier position generator (for PRF^nr) From 1f65d4b353815f4702a469d93f1107a99781c5f8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 10:04:02 -0700 Subject: [PATCH 095/168] Change tree depth to 32 in test. --- src/circuit/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 8983222..def6d9e 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -489,7 +489,7 @@ fn test_input_circuit_with_bls12_381() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let tree_depth = 29; + let tree_depth = 32; let value: u64 = 1; let value_randomness: fs::Fs = rng.gen(); @@ -516,8 +516,8 @@ fn test_input_circuit_with_bls12_381() { instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 97395); - assert_eq!(cs.hash(), "9abc0559abf54a41da789313b1692dc744d940646bb7dd3e6c01ceb54d0cc261"); + assert_eq!(cs.num_constraints(), 101550); + assert_eq!(cs.hash(), "3cc6d9383ca882ae3666267618e826e9d51a3177fc89ef6d42d9f63b84179f77"); } } From b0b3514fa7a9d9e97b6de70081c9bc4548d5ab7c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 10:21:30 -0700 Subject: [PATCH 096/168] Add bench_50 example. --- examples/bench.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 examples/bench.rs diff --git a/examples/bench.rs b/examples/bench.rs new file mode 100644 index 0000000..04a72f8 --- /dev/null +++ b/examples/bench.rs @@ -0,0 +1,83 @@ +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, + Unknown +}; +use sapling_crypto::circuit::{ + Spend +}; +use bellman::groth16::*; +use rand::{XorShiftRng, SeedableRng, Rng}; +use pairing::bls12_381::Bls12; + +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 of the note being spent + value: None, + /// Randomness that will hide the value + value_randomness: None, + /// Key which allows the proof to be constructed + /// as defense-in-depth against a flaw in the + /// protocol that would otherwise be exploitable + /// by a holder of a viewing key. + rsk: None, + /// The public key that will be re-randomized for + /// use as a nullifier and signing key for the + /// transaction. + ak: None, + /// The diversified base used to compute pk_d. + g_d: None, + /// The randomness used to hide the note commitment data + commitment_randomness: None, + /// The authentication path of the commitment in the tree + auth_path: vec![None; TREE_DEPTH] + }, + rng + ).unwrap(); + + const SAMPLES: u32 = 50; + + let mut total_time = Duration::new(0, 0); + for _ in 0..SAMPLES { + let value: u64 = 1; + let value_randomness: fs::Fs = rng.gen(); + let ak: edwards::Point = edwards::Point::rand(rng, jubjub_params); + let g_d: edwards::Point = edwards::Point::rand(rng, jubjub_params); + let commitment_randomness: fs::Fs = rng.gen(); + let rsk: fs::Fs = rng.gen(); + let auth_path = (0..TREE_DEPTH).map(|_| Some((rng.gen(), rng.gen()))).collect(); + + let start = Instant::now(); + let _ = create_random_proof(Spend { + params: jubjub_params, + value: Some(value), + value_randomness: Some(value_randomness), + ak: Some(ak), + g_d: Some(g_d), + commitment_randomness: Some(commitment_randomness), + rsk: Some(rsk), + auth_path: auth_path + }, &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); +} From ef85173df54f37774a30290b6aa77d4507cdea5c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 22:25:15 -0700 Subject: [PATCH 097/168] Fix comments in jubjub code. --- src/jubjub/edwards.rs | 20 ++++++++++++++++++++ src/jubjub/mod.rs | 36 +++++++++++++++++++----------------- src/jubjub/montgomery.rs | 19 ++++++++++++++++--- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index eeabe9d..e73c0ef 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -28,6 +28,9 @@ use std::io::{ // 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, @@ -120,7 +123,14 @@ impl Point { params: &E::Params ) -> io::Result { + // Jubjub points are encoded least significant bit first. + // The most significant bit (bit 254) encodes the parity + // of the x-coordinate. + let mut y_repr = ::Repr::default(); + + // This reads in big-endian, so we perform a swap of the + // limbs in the representation and swap the bit order. y_repr.read_be(reader)?; y_repr.as_mut().reverse(); @@ -393,11 +403,19 @@ impl Point { } pub fn double(&self, params: &E::Params) -> Self { + // Point addition is unified and complete. + // There are dedicated formulae, but we do + // not implement these now. + self.add(self, params) } 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); @@ -470,6 +488,8 @@ impl Point { params: &E::Params ) -> Self { + // Standard double-and-add scalar multiplication + let mut res = Self::zero(); for b in BitIterator::new(scalar.into()) { diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index b9bdfaf..9c4c864 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -1,18 +1,21 @@ -//! Jubjub is an elliptic curve defined over the BLS12-381 scalar field, Fr. -//! It is a Montgomery curve that takes the form `y^2 = x^3 + Ax^2 + x` where -//! `A = 40962`. This is the smallest integer choice of A such that: +//! 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 residue. -//! * The group order of the curve and its quadratic twist has a large prime factor. +//! * 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.) +//! as the prime subgroup order, with cofactor 8. (The twist has +//! cofactor 4.) //! -//! This curve is birationally equivalent to a twisted Edwards curve of the -//! form `-x^2 + y^2 = 1 + dx^2y^2` with `d = -(10240/10241)`. In fact, this equivalence -//! forms a group isomorphism, so points can be freely converted between the Montgomery -//! and twisted Edwards forms. +//! 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, @@ -30,10 +33,17 @@ use pairing::bls12_381::{ pub mod edwards; pub mod montgomery; +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)] @@ -104,14 +114,6 @@ pub trait JubjubParams: Sized { fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>]; } -/// Point of unknown order. -pub enum Unknown { } - -/// Point of prime order. -pub enum PrimeOrder { } - -pub mod fs; - impl JubjubEngine for Bls12 { type Fs = self::fs::Fs; type Params = JubjubBls12; diff --git a/src/jubjub/montgomery.rs b/src/jubjub/montgomery.rs index e827111..2243877 100644 --- a/src/jubjub/montgomery.rs +++ b/src/jubjub/montgomery.rs @@ -20,8 +20,7 @@ use rand::{ use std::marker::PhantomData; -// Represents the affine point (X/Z, Y/Z) via the extended -// twisted Edwards coordinates. +// Represents the affine point (X, Y) pub struct Point { x: E::Fr, y: E::Fr, @@ -69,7 +68,7 @@ impl PartialEq for Point { impl Point { pub fn get_for_x(x: E::Fr, sign: bool, params: &E::Params) -> Option { - // given an x on the curve, y^2 = x^3 + A*x^2 + x + // Given an x on the curve, y = sqrt(x^3 + A*x^2 + x) let mut x2 = x; x2.square(); @@ -230,10 +229,17 @@ impl Point { 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(); @@ -276,6 +282,11 @@ impl Point { 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(), @@ -325,6 +336,8 @@ impl Point { params: &E::Params ) -> Self { + // Standard double-and-add scalar multiplication + let mut res = Self::zero(); for b in BitIterator::new(scalar.into()) { From 2d4be07560361882144cd41102c86f3a9e36aa09 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 22:26:03 -0700 Subject: [PATCH 098/168] Fix group hash comment. --- src/group_hash.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/group_hash.rs b/src/group_hash.rs index 58ece78..8dd0df5 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -6,10 +6,9 @@ use blake2_rfc::blake2s::Blake2s; /// the algorithm, for rigidity purposes. pub const FIRST_BLOCK: &'static [u8; 64] = b"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; -/// Produces an (x, y) pair (Montgomery) for a -/// random point in the Jubjub curve. The point -/// is guaranteed to be prime order and not the -/// identity. +/// 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], From 0242ed35ab6036f0f40d8d60a69b4bfacb988b41 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 7 Mar 2018 12:19:56 -0700 Subject: [PATCH 099/168] Add some circuit tests for inputs. --- src/circuit/mod.rs | 76 +++++++++++++++++++++++++++++++++-------- src/circuit/test/mod.rs | 13 +++++++ src/primitives/mod.rs | 6 ++-- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index def6d9e..a81d6c7 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -23,6 +23,7 @@ use bellman::{ use jubjub::{ JubjubEngine, Unknown, + PrimeOrder, FixedGenerators, edwards }; @@ -329,9 +330,9 @@ pub struct Output<'a, E: JubjubEngine> { /// Randomness that will hide the value pub value_randomness: Option, /// The diversified base, computed by GH(d) - pub g_d: Option>, + pub g_d: Option>, /// The diversified address point, computed by GH(d)^ivk - pub p_d: Option>, + pub pk_d: Option>, /// The randomness used to hide the note commitment data pub commitment_randomness: Option, /// The ephemeral secret key for DH with recipient @@ -413,20 +414,20 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { epk.inputize(cs.namespace(|| "epk"))?; } - // Now let's deal with p_d. We don't do any checks and + // 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. { - let p_d = self.p_d.map(|e| e.into_xy()); + let pk_d = self.pk_d.map(|e| e.into_xy()); let y_contents = boolean::field_into_boolean_vec_le( - cs.namespace(|| "p_d bits of y"), - p_d.map(|e| e.1) + cs.namespace(|| "pk_d bits of y"), + pk_d.map(|e| e.1) )?; let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( - cs.namespace(|| "p_d bit of x"), - p_d.map(|e| e.0.into_repr().is_odd()) + cs.namespace(|| "pk_d bit of x"), + pk_d.map(|e| e.0.into_repr().is_odd()) )?); note_contents.extend(y_contents); @@ -481,10 +482,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { #[test] fn test_input_circuit_with_bls12_381() { + use pairing::{Field}; use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - use jubjub::{JubjubBls12, fs}; + use jubjub::{JubjubParams, JubjubBls12, fs}; let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -510,7 +512,7 @@ fn test_input_circuit_with_bls12_381() { ak: Some(ak), g_d: Some(g_d), commitment_randomness: Some(commitment_randomness), - auth_path: auth_path + auth_path: auth_path.clone() }; instance.synthesize(&mut cs).unwrap(); @@ -518,23 +520,42 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 101550); assert_eq!(cs.hash(), "3cc6d9383ca882ae3666267618e826e9d51a3177fc89ef6d42d9f63b84179f77"); + + let expected_value_cm = params.generator(FixedGenerators::ValueCommitmentValue) + .mul(fs::FsRepr::from(value), params) + .add( + ¶ms.generator(FixedGenerators::ValueCommitmentRandomness) + .mul(value_randomness, params), + params + ); + let expected_value_cm_xy = expected_value_cm.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/x/input variable"), expected_value_cm_xy.0); + assert_eq!(cs.get_input(2, "value commitment/y/input variable"), expected_value_cm_xy.1); + + cs.get_input(3, "anchor/input variable"); + cs.get_input(4, "nullifier/x/input variable"); + cs.get_input(5, "nullifier/y/input variable"); } } #[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}; + use jubjub::{JubjubParams, JubjubBls12, fs}; let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); let value: u64 = 1; let value_randomness: fs::Fs = rng.gen(); - let g_d: edwards::Point = edwards::Point::rand(rng, params); - let p_d: edwards::Point = edwards::Point::rand(rng, params); + let g_d: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); + let pk_d: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); let commitment_randomness: fs::Fs = rng.gen(); let esk: fs::Fs = rng.gen(); @@ -546,7 +567,7 @@ fn test_output_circuit_with_bls12_381() { value: Some(value), value_randomness: Some(value_randomness), g_d: Some(g_d.clone()), - p_d: Some(p_d.clone()), + pk_d: Some(pk_d.clone()), commitment_randomness: Some(commitment_randomness), esk: Some(esk.clone()) }; @@ -556,5 +577,32 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); + + let expected_cm = ::primitives::Note { + value: value, + g_d: g_d.clone(), + pk_d: pk_d.clone(), + r: commitment_randomness.clone() + }.cm(params); + + let expected_value_cm = params.generator(FixedGenerators::ValueCommitmentValue) + .mul(fs::FsRepr::from(value), params) + .add( + ¶ms.generator(FixedGenerators::ValueCommitmentRandomness) + .mul(value_randomness, params), + params + ); + let expected_value_cm_xy = expected_value_cm.into_xy(); + + let expected_epk = g_d.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/x/input variable"), expected_value_cm_xy.0); + assert_eq!(cs.get_input(2, "value commitment/y/input variable"), expected_value_cm_xy.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/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index 01fda4a..9728e04 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -294,6 +294,19 @@ impl TestConstraintSystem { } } + 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) { diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 7a99092..237b633 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -5,7 +5,7 @@ use pedersen_hash::{ use byteorder::{ BigEndian, - ByteOrder + WriteBytesExt }; use jubjub::{ @@ -35,7 +35,7 @@ impl Note { let mut note_contents = vec![]; // Write the value in big endian - BigEndian::write_u64(&mut note_contents, self.value); + (&mut note_contents).write_u64::(self.value).unwrap(); // Write g_d self.g_d.write(&mut note_contents).unwrap(); @@ -43,6 +43,8 @@ impl Note { // 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, From b998190f9ea4b2b3ce4228a167857253e2e3a880 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 7 Mar 2018 23:45:08 -0700 Subject: [PATCH 100/168] Disable this, for now. --- examples/{bench.rs => bench.rs.disabled} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{bench.rs => bench.rs.disabled} (100%) diff --git a/examples/bench.rs b/examples/bench.rs.disabled similarity index 100% rename from examples/bench.rs rename to examples/bench.rs.disabled From 25a8050df8bfddc9b55360c84d5570de7805d310 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 7 Mar 2018 23:59:04 -0700 Subject: [PATCH 101/168] Remaining tests for input circuit --- src/circuit/mod.rs | 90 +++++++++++++++++++++++---- src/lib.rs | 2 + src/primitives/mod.rs | 137 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 212 insertions(+), 17 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index a81d6c7..b407b69 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -22,7 +22,6 @@ use bellman::{ use jubjub::{ JubjubEngine, - Unknown, PrimeOrder, FixedGenerators, edwards @@ -55,9 +54,9 @@ pub struct Spend<'a, E: JubjubEngine> { /// The public key that will be re-randomized for /// use as a nullifier and signing key for the /// transaction. - pub ak: Option>, + pub ak: Option>, /// The diversified base used to compute pk_d. - pub g_d: Option>, + pub g_d: Option>, /// The randomness used to hide the note commitment data pub commitment_randomness: Option, /// The authentication path of the commitment in the tree @@ -482,7 +481,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { #[test] fn test_input_circuit_with_bls12_381() { - use pairing::{Field}; + use pairing::{Field, BitIterator}; use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; @@ -495,10 +494,34 @@ fn test_input_circuit_with_bls12_381() { let value: u64 = 1; let value_randomness: fs::Fs = rng.gen(); - let ak: edwards::Point = edwards::Point::rand(rng, params); - let g_d: edwards::Point = edwards::Point::rand(rng, params); - let commitment_randomness: fs::Fs = rng.gen(); + let rsk: fs::Fs = rng.gen(); + let ak: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + rsk: rsk.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]; { @@ -510,7 +533,7 @@ fn test_input_circuit_with_bls12_381() { value_randomness: Some(value_randomness), rsk: Some(rsk), ak: Some(ak), - g_d: Some(g_d), + g_d: Some(g_d.clone()), commitment_randomness: Some(commitment_randomness), auth_path: auth_path.clone() }; @@ -535,9 +558,54 @@ fn test_input_circuit_with_bls12_381() { assert_eq!(cs.get_input(1, "value commitment/x/input variable"), expected_value_cm_xy.0); assert_eq!(cs.get_input(2, "value commitment/y/input variable"), expected_value_cm_xy.1); - cs.get_input(3, "anchor/input variable"); - cs.get_input(4, "nullifier/x/input variable"); - cs.get_input(5, "nullifier/y/input variable"); + let note = ::primitives::Note { + value: value, + g_d: g_d.clone(), + pk_d: payment_address.pk_d.clone(), + r: commitment_randomness.clone() + }; + + let mut position = 0u64; + let mut cur = note.cm(params); + + assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); + + for (i, val) in auth_path.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_xy = expected_nf.into_xy(); + + assert_eq!(cs.get_input(3, "anchor/input variable"), cur); + assert_eq!(cs.get_input(4, "nullifier/x/input variable"), expected_nf_xy.0); + assert_eq!(cs.get_input(5, "nullifier/y/input variable"), expected_nf_xy.1); } } diff --git a/src/lib.rs b/src/lib.rs index 686f93a..100bbb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,8 @@ const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; // Group hash personalizations /// BLAKE2s Personalization for Pedersen hash generators. const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; +/// BLAKE2s Personalization for the group hash for key diversification +const KEY_DIVERSIFICATION_PERSONALIZATION: &'static [u8; 8] = b"Zcash_gh"; /// BLAKE2s Personalization for the proof generation key base point const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; /// BLAKE2s Personalization for the note commitment randomness generator diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 237b633..d5bb550 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,3 +1,10 @@ +use pairing::{ + PrimeField, + PrimeFieldRepr +}; + +use group_hash::group_hash; + use pedersen_hash::{ pedersen_hash, Personalization @@ -16,6 +23,83 @@ use jubjub::{ FixedGenerators }; +use blake2_rfc::blake2s::Blake2s; + +pub struct ProofGenerationKey { + pub ak: edwards::Point, + pub rsk: E::Fs +} + +impl ProofGenerationKey { + pub fn into_viewing_key(&self, params: &E::Params) -> ViewingKey { + ViewingKey { + ak: self.ak.clone(), + rk: params.generator(FixedGenerators::ProofGenerationKey) + .mul(self.rsk, params) + } + } +} + +pub struct ViewingKey { + pub ak: edwards::Point, + pub rk: edwards::Point +} + +impl ViewingKey { + fn ivk(&self) -> E::Fs { + let mut preimage = [0; 64]; + + self.ak.write(&mut preimage[0..32]).unwrap(); + self.rk.write(&mut preimage[32..64]).unwrap(); + + let mut h = Blake2s::with_params(32, &[], &[], ::CRH_IVK_PERSONALIZATION); + h.update(&preimage); + let mut h = h.finalize().as_ref().to_vec(); + + // Drop the first five bits, so it can be interpreted as a scalar. + h[0] &= 0b0000_0111; + + let mut e = ::Repr::default(); + e.read_be(&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, ::KEY_DIVERSIFICATION_PERSONALIZATION, params) + } +} + +pub struct PaymentAddress { + pub pk_d: edwards::Point, + pub diversifier: Diversifier +} + pub struct Note { /// The value of the note pub value: u64, @@ -28,8 +112,8 @@ pub struct Note { } impl Note { - /// Computes the note commitment - pub fn cm(&self, params: &E::Params) -> E::Fr + /// 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![]; @@ -56,12 +140,53 @@ impl Note { ); // Compute final commitment - let cm = params.generator(FixedGenerators::NoteCommitmentRandomness) - .mul(self.r, params) - .add(&hash_of_contents, params); + 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 + ) -> edwards::Point + { + // Compute cm + position + let cm_plus_position = self + .cm_full_point(params) + .add( + ¶ms.generator(FixedGenerators::NullifierPosition) + .mul(position, params), + params + ); + + // Compute nr = drop_5(BLAKE2s(rk | cm_plus_position)) + let mut nr_preimage = [0u8; 64]; + viewing_key.rk.write(&mut nr_preimage[0..32]).unwrap(); + cm_plus_position.write(&mut nr_preimage[32..64]).unwrap(); + let mut h = Blake2s::with_params(32, &[], &[], ::PRF_NR_PERSONALIZATION); + h.update(&nr_preimage); + let mut h = h.finalize().as_ref().to_vec(); + + // Drop the first five bits, so it can be interpreted as a scalar. + h[0] &= 0b0000_0111; + + let mut e = ::Repr::default(); + e.read_be(&h[..]).unwrap(); + + let nr = E::Fs::from_repr(e).expect("should be a valid scalar"); + + viewing_key.ak.mul(nr, params) + } + + /// 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. - cm.into_xy().0 + self.cm_full_point(params).into_xy().0 } } From c7c8d3c039e060a4b9293ea36398f649ebf684e4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 00:06:53 -0700 Subject: [PATCH 102/168] Move personalization constants to submodule. --- src/circuit/mod.rs | 6 ++++-- src/constants.rs | 23 +++++++++++++++++++++++ src/jubjub/mod.rs | 18 ++++++++++-------- src/lib.rs | 26 +------------------------- src/primitives/mod.rs | 8 +++++--- 5 files changed, 43 insertions(+), 38 deletions(-) create mode 100644 src/constants.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index b407b69..42ec6bf 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -27,6 +27,8 @@ use jubjub::{ edwards }; +use constants; + trait Assignment { fn get(&self) -> Result<&T, SynthesisError>; } @@ -157,7 +159,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let mut ivk = blake2s::blake2s( cs.namespace(|| "computation of ivk"), &vk, - ::CRH_IVK_PERSONALIZATION + constants::CRH_IVK_PERSONALIZATION )?; // Little endian bit order @@ -301,7 +303,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let mut rho = blake2s::blake2s( cs.namespace(|| "rho computation"), &rho_preimage, - ::PRF_NR_PERSONALIZATION + constants::PRF_NR_PERSONALIZATION )?; // Little endian bit order diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..4b1e1ac --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,23 @@ +// BLAKE2s invocation personalizations +/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | rk) +pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; +/// BLAKE2s Personalization for PRF^nr = BLAKE2s(rk | cm + position) +pub const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; + +// Group hash personalizations +/// BLAKE2s Personalization for Pedersen hash generators. +pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; +/// BLAKE2s Personalization for the group hash for key diversification +pub const KEY_DIVERSIFICATION_PERSONALIZATION: &'static [u8; 8] = b"Zcash_gh"; +/// BLAKE2s Personalization for the proof generation key base point +pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; +/// BLAKE2s Personalization for the note commitment randomness generator +pub const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"abcdefgh"; +/// BLAKE2s Personalization for the nullifier position generator (for PRF^nr) +pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"nfnfnfnf"; +/// BLAKE2s Personalization for the value commitment generator for the value +pub const VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"45u8gh45"; +/// BLAKE2s Personalization for the value commitment randomness generator +pub const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"11111111"; +/// BLAKE2s Personalization for the spending key base point +pub const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"sksksksk"; diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 9c4c864..7e1374b 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -24,7 +24,9 @@ use pairing::{ SqrtField }; -use super::group_hash::group_hash; +use group_hash::group_hash; + +use constants; use pairing::bls12_381::{ Bls12, @@ -188,7 +190,7 @@ impl JubjubBls12 { let mut pedersen_hash_generators = vec![]; while pedersen_hash_generators.len() < 5 { - let gh = group_hash(&[cur], ::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp); + let gh = group_hash(&[cur], constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); cur += 1; @@ -228,22 +230,22 @@ impl JubjubBls12 { for c in 0..(FixedGenerators::Max as usize) { let p = match c { c if c == (FixedGenerators::ProofGenerationKey as usize) => { - ::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION + constants::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::NoteCommitmentRandomness as usize) => { - ::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION + constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::NullifierPosition as usize) => { - ::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION + constants::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::ValueCommitmentValue as usize) => { - ::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION + constants::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::ValueCommitmentRandomness as usize) => { - ::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION + constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION }, c if c == (FixedGenerators::SpendingKeyGenerator as usize) => { - ::SPENDING_KEY_GENERATOR_PERSONALIZATION + constants::SPENDING_KEY_GENERATOR_PERSONALIZATION }, _ => unreachable!() }; diff --git a/src/lib.rs b/src/lib.rs index 100bbb0..2a5230b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ extern crate bellman; extern crate blake2_rfc; extern crate digest; extern crate rand; - extern crate byteorder; #[cfg(test)] @@ -15,27 +14,4 @@ pub mod circuit; pub mod group_hash; pub mod pedersen_hash; pub mod primitives; - -// BLAKE2s invocation personalizations -/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | rk) -const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; -/// BLAKE2s Personalization for PRF^nr = BLAKE2s(rk | cm + position) -const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; - -// Group hash personalizations -/// BLAKE2s Personalization for Pedersen hash generators. -const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; -/// BLAKE2s Personalization for the group hash for key diversification -const KEY_DIVERSIFICATION_PERSONALIZATION: &'static [u8; 8] = b"Zcash_gh"; -/// BLAKE2s Personalization for the proof generation key base point -const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; -/// BLAKE2s Personalization for the note commitment randomness generator -const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"abcdefgh"; -/// BLAKE2s Personalization for the nullifier position generator (for PRF^nr) -const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"nfnfnfnf"; -/// BLAKE2s Personalization for the value commitment generator for the value -const VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"45u8gh45"; -/// BLAKE2s Personalization for the value commitment randomness generator -const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"11111111"; -/// BLAKE2s Personalization for the spending key base point -const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"sksksksk"; +mod constants; diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index d5bb550..5aa2260 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -3,6 +3,8 @@ use pairing::{ PrimeFieldRepr }; +use constants; + use group_hash::group_hash; use pedersen_hash::{ @@ -52,7 +54,7 @@ impl ViewingKey { self.ak.write(&mut preimage[0..32]).unwrap(); self.rk.write(&mut preimage[32..64]).unwrap(); - let mut h = Blake2s::with_params(32, &[], &[], ::CRH_IVK_PERSONALIZATION); + let mut h = Blake2s::with_params(32, &[], &[], constants::CRH_IVK_PERSONALIZATION); h.update(&preimage); let mut h = h.finalize().as_ref().to_vec(); @@ -91,7 +93,7 @@ impl Diversifier { params: &E::Params ) -> Option> { - group_hash::(&self.0, ::KEY_DIVERSIFICATION_PERSONALIZATION, params) + group_hash::(&self.0, constants::KEY_DIVERSIFICATION_PERSONALIZATION, params) } } @@ -167,7 +169,7 @@ impl Note { let mut nr_preimage = [0u8; 64]; viewing_key.rk.write(&mut nr_preimage[0..32]).unwrap(); cm_plus_position.write(&mut nr_preimage[32..64]).unwrap(); - let mut h = Blake2s::with_params(32, &[], &[], ::PRF_NR_PERSONALIZATION); + let mut h = Blake2s::with_params(32, &[], &[], constants::PRF_NR_PERSONALIZATION); h.update(&nr_preimage); let mut h = h.finalize().as_ref().to_vec(); From 896b144a7da16781fb8279e62d288ced58a579bb Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 00:09:34 -0700 Subject: [PATCH 103/168] Move first block of group hash to constants submodule. --- src/constants.rs | 6 ++++++ src/group_hash.rs | 7 ++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 4b1e1ac..71b96e1 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,3 +1,9 @@ +/// 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"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; + // BLAKE2s invocation personalizations /// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | rk) pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; diff --git a/src/group_hash.rs b/src/group_hash.rs index 8dd0df5..a545d5f 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -1,10 +1,7 @@ use jubjub::*; use pairing::*; use blake2_rfc::blake2s::Blake2s; - -/// This is chosen to be some random string that we couldn't have anticipated when we designed -/// the algorithm, for rigidity purposes. -pub const FIRST_BLOCK: &'static [u8; 64] = b"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; +use constants; /// Produces a random point in the Jubjub curve. /// The point is guaranteed to be prime order @@ -21,7 +18,7 @@ pub fn group_hash( assert!(E::Fr::NUM_BITS == 255); let mut h = Blake2s::with_params(32, &[], &[], personalization); - h.update(FIRST_BLOCK); + h.update(constants::GH_FIRST_BLOCK); h.update(tag); let mut h = h.finalize().as_ref().to_vec(); assert!(h.len() == 32); From b6ef12b0771c29358278a7712859b39d24e8d6c5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 00:41:47 -0700 Subject: [PATCH 104/168] General code quality improvements. --- src/circuit/mod.rs | 95 +++++++++++++++++++++++++++++++++++++--------- src/group_hash.rs | 13 ++++++- src/jubjub/mod.rs | 61 ++++++++++++++++++++--------- src/lib.rs | 4 +- 4 files changed, 133 insertions(+), 40 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 42ec6bf..1efc8ac 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -29,6 +29,13 @@ use jubjub::{ use constants; + +// 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>; } @@ -42,6 +49,7 @@ impl Assignment for Option { } } +/// This is an instance of the `Spend` circuit. pub struct Spend<'a, E: JubjubEngine> { pub params: &'a E::Params, /// Value of the note being spent @@ -75,6 +83,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; { + // Compute the note value in the exponent let gv = ecc::fixed_base_multiplication( cs.namespace(|| "compute the value in the exponent"), FixedGenerators::ValueCommitmentValue, @@ -82,12 +91,15 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // Booleanize the randomness + // Booleanize the randomness. This does not ensure + // the bit representation is "in the field" because + // it doesn't matter for security. let hr = boolean::field_into_boolean_vec_le( cs.namespace(|| "hr"), self.value_randomness )?; + // Compute the randomness in the exponent let hr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of randomization for value commitment"), FixedGenerators::ValueCommitmentRandomness, @@ -95,12 +107,14 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; + // Compute the Pedersen commitment to the value let gvhr = gv.add( cs.namespace(|| "computation of value commitment"), &hr, self.params )?; + // Expose the commitment as an input to the circuit gvhr.inputize(cs.namespace(|| "value commitment"))?; } @@ -118,6 +132,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // demonstrate the prover knows it. If they know a // congruency then that's equivalent. + // Compute rk = [rsk] ProvingPublicKey rk = ecc::fixed_base_multiplication( cs.namespace(|| "computation of rk"), FixedGenerators::ProofGenerationKey, @@ -133,29 +148,40 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { 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 )?; // Unpack ak and rk for input to BLAKE2s + + // This is the "viewing key" preimage for CRH^ivk let mut vk = vec![]; - let mut rho_preimage = vec![]; vk.extend( ak.repr(cs.namespace(|| "representation of ak"))? ); + + // This is the nullifier randomness preimage for PRF^nr + let mut nr_preimage = vec![]; + + // Extend vk and nr preimages with the representation of + // rk. { let repr_rk = rk.repr( cs.namespace(|| "representation of rk") )?; vk.extend(repr_rk.iter().cloned()); - rho_preimage.extend(repr_rk); + nr_preimage.extend(repr_rk); } assert_eq!(vk.len(), 512); + assert_eq!(nr_preimage.len(), 256); - // Compute the incoming viewing key + // Compute the incoming viewing key ivk let mut ivk = blake2s::blake2s( cs.namespace(|| "computation of ivk"), &vk, @@ -164,16 +190,24 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Little endian bit order ivk.reverse(); - ivk.truncate(E::Fs::CAPACITY as usize); // drop_5 - // Witness g_d + // drop_5 to ensure it's in the field + ivk.truncate(E::Fs::CAPACITY as usize); + + // Witness g_d. Ensures the point is on the + // curve, but not its order. If the prover + // manages to witness a commitment in the + // tree, then the Output circuit would have + // already guaranteed this. + // TODO: We might as well just perform the + // check again here, since it's not expensive. let g_d = ecc::EdwardsPoint::witness( cs.namespace(|| "witness g_d"), self.g_d, self.params )?; - // Compute pk_d + // Compute pk_d = g_d^ivk let pk_d = g_d.mul( cs.namespace(|| "compute pk_d"), &ivk, @@ -181,6 +215,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // Compute note contents + // value (in big endian) followed by g_d and pk_d let mut note_contents = vec![]; note_contents.extend(value_bits.into_iter().rev()); note_contents.extend( @@ -206,12 +241,13 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; { - // Booleanize the randomness + // Booleanize the randomness for the note commitment let cmr = boolean::field_into_boolean_vec_le( cs.namespace(|| "cmr"), self.commitment_randomness )?; + // Compute the note commitment randomness in the exponent let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), FixedGenerators::NoteCommitmentRandomness, @@ -219,6 +255,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; + // Randomize the note commitment. Pedersen hashes are not + // themselves hiding commitments. cm = cm.add( cs.namespace(|| "randomization of note commitment"), &cmr, @@ -228,21 +266,30 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let tree_depth = self.auth_path.len(); + // 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![]; - // Injective encoding. + // This is an injective encoding, as cur is a + // point in the prime order subgroup. let mut cur = cm.get_x().clone(); 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"), || { @@ -250,6 +297,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { } )?; + // 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, @@ -265,6 +313,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { 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), @@ -278,7 +327,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Expose the anchor cur.inputize(cs.namespace(|| "anchor"))?; + // Compute the cm + g^position for preventing + // faerie gold attacks { + // Compute the position in the exponent let position = ecc::fixed_base_multiplication( cs.namespace(|| "g^position"), FixedGenerators::NullifierPosition, @@ -286,6 +338,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; + // Add the position to the commitment cm = cm.add( cs.namespace(|| "faerie gold prevention"), &position, @@ -293,30 +346,36 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; } - // Let's compute rho = BLAKE2s(rk || cm + position) - rho_preimage.extend( + // Let's compute nr = BLAKE2s(rk || cm + position) + nr_preimage.extend( cm.repr(cs.namespace(|| "representation of cm"))? ); - assert_eq!(rho_preimage.len(), 512); + assert_eq!(nr_preimage.len(), 512); - let mut rho = blake2s::blake2s( - cs.namespace(|| "rho computation"), - &rho_preimage, + // Compute nr + let mut nr = blake2s::blake2s( + cs.namespace(|| "nr computation"), + &nr_preimage, constants::PRF_NR_PERSONALIZATION )?; // Little endian bit order - rho.reverse(); - rho.truncate(E::Fs::CAPACITY as usize); // drop_5 + nr.reverse(); + + // We want the randomization in the field to + // simplify outside code. + // TODO: This isn't uniformly random. + nr.truncate(E::Fs::CAPACITY as usize); // Compute nullifier let nf = ak.mul( cs.namespace(|| "computation of nf"), - &rho, + &nr, self.params )?; + // Expose the nullifier publicly nf.inputize(cs.namespace(|| "nullifier"))?; Ok(()) diff --git a/src/group_hash.rs b/src/group_hash.rs index a545d5f..9e6b2f8 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -1,5 +1,14 @@ -use jubjub::*; -use pairing::*; +use jubjub::{ + JubjubEngine, + PrimeOrder, + edwards +}; + +use pairing::{ + PrimeField, + PrimeFieldRepr +}; + use blake2_rfc::blake2s::Blake2s; use constants; diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 7e1374b..cff5c4a 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -33,8 +33,14 @@ use pairing::bls12_381::{ 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)] @@ -83,7 +89,9 @@ pub enum FixedGenerators { /// 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; + /// The parameters of Jubjub and the Sapling protocol type Params: JubjubParams; } @@ -167,7 +175,7 @@ impl JubjubBls12 { let mut montgomery_2a = montgomery_a; montgomery_2a.double(); - let mut tmp = JubjubBls12 { + let mut tmp_params = JubjubBls12 { // d = -(10240/10241) edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(), // A = 40962 @@ -177,20 +185,24 @@ impl JubjubBls12 { // scaling factor = sqrt(4 / (a - d)) scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(), + // We'll initialize these below pedersen_hash_generators: vec![], pedersen_circuit_generators: vec![], - fixed_base_generators: vec![], fixed_base_circuit_generators: vec![], }; // Create the bases for the Pedersen hashes { + // TODO: This currently does not match the specification let mut cur = 0; let mut pedersen_hash_generators = vec![]; + // TODO: This generates more bases for the Pedersen hashes + // than necessary, which is just a performance issue in + // practice. while pedersen_hash_generators.len() < 5 { - let gh = group_hash(&[cur], constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp); + let gh = group_hash(&[cur], constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp_params); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); cur += 1; @@ -200,7 +212,20 @@ impl JubjubBls12 { } } - tmp.pedersen_hash_generators = pedersen_hash_generators; + // 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 bases for other parts of the protocol @@ -211,10 +236,10 @@ impl JubjubBls12 { // Each generator is found by invoking the group hash // on tag 0x00, 0x01, ... until we find a valid result. let find_first_gh = |personalization| { - let mut cur = 0; + let mut cur = 0u8; loop { - let gh = group_hash::(&[cur], personalization, &tmp); + let gh = group_hash::(&[cur], personalization, &tmp_params); // We don't want to overflow. assert!(cur != u8::max_value()); cur += 1; @@ -267,7 +292,7 @@ impl JubjubBls12 { } } - tmp.fixed_base_generators = fixed_base_generators; + tmp_params.fixed_base_generators = fixed_base_generators; } // Create the 2-bit window table lookups for each 4-bit @@ -276,10 +301,10 @@ impl JubjubBls12 { let mut pedersen_circuit_generators = vec![]; // Process each segment - for mut gen in tmp.pedersen_hash_generators.iter().cloned() { - let mut gen = montgomery::Point::from_edwards(&gen, &tmp); + 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.pedersen_hash_chunks_per_generator() { + 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(); @@ -287,19 +312,19 @@ impl JubjubBls12 { // 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); + 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); + gen = gen.double(&tmp_params); } } pedersen_circuit_generators.push(windows); } - tmp.pedersen_circuit_generators = pedersen_circuit_generators; + tmp_params.pedersen_circuit_generators = pedersen_circuit_generators; } // Create the 3-bit window table lookups for fixed-base @@ -307,14 +332,14 @@ impl JubjubBls12 { { let mut fixed_base_circuit_generators = vec![]; - for mut gen in tmp.fixed_base_generators.iter().cloned() { + for mut gen in tmp_params.fixed_base_generators.iter().cloned() { let mut windows = vec![]; - for _ in 0..tmp.fixed_base_chunks_per_generator() { + 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); + g = g.add(&gen, &tmp_params); } windows.push(coeffs); @@ -324,10 +349,10 @@ impl JubjubBls12 { fixed_base_circuit_generators.push(windows); } - tmp.fixed_base_circuit_generators = fixed_base_circuit_generators; + tmp_params.fixed_base_circuit_generators = fixed_base_circuit_generators; } - tmp + tmp_params } } diff --git a/src/lib.rs b/src/lib.rs index 2a5230b..0f5fded 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,8 @@ extern crate byteorder; extern crate hex_literal; pub mod jubjub; -pub mod circuit; pub mod group_hash; +pub mod circuit; pub mod pedersen_hash; pub mod primitives; -mod constants; +pub mod constants; From f61cc88a71ec428fd906d84c13becbc5c521c6dc Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 00:51:53 -0700 Subject: [PATCH 105/168] More comment improvements. --- src/circuit/mod.rs | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 1efc8ac..be115ab 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -409,6 +409,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { )?; { + // Compute the note value in the exponent let gv = ecc::fixed_base_multiplication( cs.namespace(|| "compute the value in the exponent"), FixedGenerators::ValueCommitmentValue, @@ -416,12 +417,15 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; - // Booleanize the randomness + // Booleanize the randomness. This does not ensure + // the bit representation is "in the field" because + // it doesn't matter for security. let hr = boolean::field_into_boolean_vec_le( cs.namespace(|| "hr"), self.value_randomness )?; + // Compute the randomness in the exponent let hr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of randomization for value commitment"), FixedGenerators::ValueCommitmentRandomness, @@ -429,48 +433,66 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; + // Compute the Pedersen commitment to the value let gvhr = gv.add( cs.namespace(|| "computation of value commitment"), &hr, self.params )?; + // Expose the commitment as an input to the circuit gvhr.inputize(cs.namespace(|| "value commitment"))?; } - // Let's start to construct our note + // Let's start to construct our note, which contains + // value (big endian) let mut note_contents = vec![]; note_contents.extend(value_bits.into_iter().rev()); // Let's deal with g_d { + // Prover witnesses g_d, ensuring it's on the + // curve. let g_d = ecc::EdwardsPoint::witness( cs.namespace(|| "witness g_d"), self.g_d, 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"))? ); - // Compute epk from esk + // 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"))?; } @@ -478,18 +500,23 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // essentially allow the prover to witness any 256 bits // they would like. { + // Just grab pk_d from the witness let pk_d = self.pk_d.map(|e| e.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); } @@ -516,6 +543,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.commitment_randomness )?; + // Compute the note commitment randomness in the exponent let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), FixedGenerators::NoteCommitmentRandomness, @@ -523,6 +551,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; + // Randomize our note commitment cm = cm.add( cs.namespace(|| "randomization of note commitment"), &cmr, From d6d86737c8b6b1c19de9c23b9c3ea81302c351ca Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 01:01:00 -0700 Subject: [PATCH 106/168] Remove some code duplication for value commitment witnessing. --- src/circuit/mod.rs | 152 ++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 84 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index be115ab..3c1826e 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -49,6 +49,60 @@ impl Assignment for Option { } } +/// Exposes a Pedersen commitment to the value as an +/// input to the circuit +fn expose_value_commitment( + mut cs: CS, + value: Option, + randomness: 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 + )?; + + // Compute the note value in the exponent + let gv = 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 hr = boolean::field_into_boolean_vec_le( + cs.namespace(|| "hr"), + randomness + )?; + + // Compute the randomness in the exponent + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + params + )?; + + // Compute the Pedersen commitment to the value + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + params + )?; + + // Expose the commitment as an input to the circuit + gvhr.inputize(cs.namespace(|| "commitment point"))?; + + Ok(value_bits) +} + /// This is an instance of the `Spend` circuit. pub struct Spend<'a, E: JubjubEngine> { pub params: &'a E::Params, @@ -76,48 +130,13 @@ pub struct Spend<'a, E: JubjubEngine> { impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { - // Booleanize the value into little-endian bit order - let value_bits = boolean::u64_into_boolean_vec_le( - cs.namespace(|| "value"), - self.value + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value, + self.value_randomness, + self.params )?; - { - // Compute the note value in the exponent - let gv = ecc::fixed_base_multiplication( - cs.namespace(|| "compute the value in the exponent"), - FixedGenerators::ValueCommitmentValue, - &value_bits, - self.params - )?; - - // Booleanize the randomness. This does not ensure - // the bit representation is "in the field" because - // it doesn't matter for security. - let hr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "hr"), - self.value_randomness - )?; - - // Compute the randomness in the exponent - let hr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of randomization for value commitment"), - FixedGenerators::ValueCommitmentRandomness, - &hr, - self.params - )?; - - // Compute the Pedersen commitment to the value - let gvhr = gv.add( - cs.namespace(|| "computation of value commitment"), - &hr, - self.params - )?; - - // Expose the commitment as an input to the circuit - gvhr.inputize(cs.namespace(|| "value commitment"))?; - } - // Compute rk = [rsk] ProvingPublicKey let rk; { @@ -402,48 +421,13 @@ pub struct Output<'a, E: JubjubEngine> { impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { - // Booleanize the value into little-endian bit order - let value_bits = boolean::u64_into_boolean_vec_le( - cs.namespace(|| "value"), - self.value + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value, + self.value_randomness, + self.params )?; - { - // Compute the note value in the exponent - let gv = ecc::fixed_base_multiplication( - cs.namespace(|| "compute the value in the exponent"), - FixedGenerators::ValueCommitmentValue, - &value_bits, - self.params - )?; - - // Booleanize the randomness. This does not ensure - // the bit representation is "in the field" because - // it doesn't matter for security. - let hr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "hr"), - self.value_randomness - )?; - - // Compute the randomness in the exponent - let hr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of randomization for value commitment"), - FixedGenerators::ValueCommitmentRandomness, - &hr, - self.params - )?; - - // Compute the Pedersen commitment to the value - let gvhr = gv.add( - cs.namespace(|| "computation of value commitment"), - &hr, - self.params - )?; - - // Expose the commitment as an input to the circuit - gvhr.inputize(cs.namespace(|| "value commitment"))?; - } - // Let's start to construct our note, which contains // value (big endian) let mut note_contents = vec![]; @@ -645,8 +629,8 @@ fn test_input_circuit_with_bls12_381() { assert_eq!(cs.num_inputs(), 6); assert_eq!(cs.get_input(0, "ONE"), Fr::one()); - assert_eq!(cs.get_input(1, "value commitment/x/input variable"), expected_value_cm_xy.0); - assert_eq!(cs.get_input(2, "value commitment/y/input variable"), expected_value_cm_xy.1); + assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm_xy.0); + assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm_xy.1); let note = ::primitives::Note { value: value, @@ -757,8 +741,8 @@ fn test_output_circuit_with_bls12_381() { assert_eq!(cs.num_inputs(), 6); assert_eq!(cs.get_input(0, "ONE"), Fr::one()); - assert_eq!(cs.get_input(1, "value commitment/x/input variable"), expected_value_cm_xy.0); - assert_eq!(cs.get_input(2, "value commitment/y/input variable"), expected_value_cm_xy.1); + assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm_xy.0); + assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm_xy.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); From 3fbbd933cfaf9087b82782022c341f89d11c1e3e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 01:16:21 -0700 Subject: [PATCH 107/168] Simplify value commitment abstraction. --- src/circuit/mod.rs | 93 +++++++++++++++++++++---------------------- src/primitives/mod.rs | 22 ++++++++++ 2 files changed, 67 insertions(+), 48 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 3c1826e..c4f0db2 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -29,6 +29,10 @@ use jubjub::{ use constants; +use primitives::{ + ValueCommitment +}; + // TODO: This should probably be removed and we // should use existing helper methods on `Option` @@ -53,8 +57,7 @@ impl Assignment for Option { /// input to the circuit fn expose_value_commitment( mut cs: CS, - value: Option, - randomness: Option, + value_commitment: Option>, params: &E::Params ) -> Result, SynthesisError> where E: JubjubEngine, @@ -63,7 +66,7 @@ fn expose_value_commitment( // Booleanize the value into little-endian bit order let value_bits = boolean::u64_into_boolean_vec_le( cs.namespace(|| "value"), - value + value_commitment.as_ref().map(|c| c.value) )?; // Compute the note value in the exponent @@ -79,7 +82,7 @@ fn expose_value_commitment( // it doesn't matter for security. let hr = boolean::field_into_boolean_vec_le( cs.namespace(|| "hr"), - randomness + value_commitment.as_ref().map(|c| c.randomness) )?; // Compute the randomness in the exponent @@ -106,23 +109,27 @@ fn expose_value_commitment( /// This is an instance of the `Spend` circuit. pub struct Spend<'a, E: JubjubEngine> { pub params: &'a E::Params, - /// Value of the note being spent - pub value: Option, - /// Randomness that will hide the value - pub value_randomness: Option, + + /// Pedersen commitment to the value being spent + pub value_commitment: Option>, + /// Key which allows the proof to be constructed /// as defense-in-depth against a flaw in the /// protocol that would otherwise be exploitable /// by a holder of a viewing key. pub rsk: Option, + /// The public key that will be re-randomized for /// use as a nullifier and signing key for the /// transaction. pub ak: Option>, + /// The diversified base used to compute pk_d. pub g_d: Option>, + /// The randomness used to hide the note commitment data pub commitment_randomness: Option, + /// The authentication path of the commitment in the tree pub auth_path: Vec> } @@ -132,8 +139,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { { let value_bits = expose_value_commitment( cs.namespace(|| "value commitment"), - self.value, - self.value_randomness, + self.value_commitment, self.params )?; @@ -404,16 +410,19 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { /// This is an output circuit instance. pub struct Output<'a, E: JubjubEngine> { pub params: &'a E::Params, - /// Value of the note being created - pub value: Option, - /// Randomness that will hide the value - pub value_randomness: Option, + + /// Pedersen commitment to the value being spent + pub value_commitment: Option>, + /// The diversified base, computed by GH(d) pub g_d: Option>, + /// The diversified address point, computed by GH(d)^ivk pub pk_d: 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 } @@ -423,8 +432,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { let value_bits = expose_value_commitment( cs.namespace(|| "value commitment"), - self.value, - self.value_randomness, + self.value_commitment, self.params )?; @@ -559,15 +567,17 @@ fn test_input_circuit_with_bls12_381() { use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - use jubjub::{JubjubParams, JubjubBls12, fs}; + use jubjub::{JubjubBls12, fs}; let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); let tree_depth = 32; - let value: u64 = 1; - let value_randomness: fs::Fs = rng.gen(); + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; let rsk: fs::Fs = rng.gen(); let ak: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); @@ -603,8 +613,7 @@ fn test_input_circuit_with_bls12_381() { let instance = Spend { params: params, - value: Some(value), - value_randomness: Some(value_randomness), + value_commitment: Some(value_commitment.clone()), rsk: Some(rsk), ak: Some(ak), g_d: Some(g_d.clone()), @@ -618,22 +627,15 @@ fn test_input_circuit_with_bls12_381() { assert_eq!(cs.num_constraints(), 101550); assert_eq!(cs.hash(), "3cc6d9383ca882ae3666267618e826e9d51a3177fc89ef6d42d9f63b84179f77"); - let expected_value_cm = params.generator(FixedGenerators::ValueCommitmentValue) - .mul(fs::FsRepr::from(value), params) - .add( - ¶ms.generator(FixedGenerators::ValueCommitmentRandomness) - .mul(value_randomness, params), - params - ); - let expected_value_cm_xy = expected_value_cm.into_xy(); + let expected_value_cm = value_commitment.cm(params).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_xy.0); - assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm_xy.1); + 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); let note = ::primitives::Note { - value: value, + value: value_commitment.value, g_d: g_d.clone(), pk_d: payment_address.pk_d.clone(), r: commitment_randomness.clone() @@ -689,13 +691,16 @@ fn test_output_circuit_with_bls12_381() { use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - use jubjub::{JubjubParams, JubjubBls12, fs}; + use jubjub::{JubjubBls12, fs}; let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - let value: u64 = 1; - let value_randomness: fs::Fs = rng.gen(); + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + let g_d: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); let pk_d: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); let commitment_randomness: fs::Fs = rng.gen(); @@ -706,8 +711,7 @@ fn test_output_circuit_with_bls12_381() { let instance = Output { params: params, - value: Some(value), - value_randomness: Some(value_randomness), + value_commitment: Some(value_commitment.clone()), g_d: Some(g_d.clone()), pk_d: Some(pk_d.clone()), commitment_randomness: Some(commitment_randomness), @@ -721,28 +725,21 @@ fn test_output_circuit_with_bls12_381() { assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); let expected_cm = ::primitives::Note { - value: value, + value: value_commitment.value, g_d: g_d.clone(), pk_d: pk_d.clone(), r: commitment_randomness.clone() }.cm(params); - let expected_value_cm = params.generator(FixedGenerators::ValueCommitmentValue) - .mul(fs::FsRepr::from(value), params) - .add( - ¶ms.generator(FixedGenerators::ValueCommitmentRandomness) - .mul(value_randomness, params), - params - ); - let expected_value_cm_xy = expected_value_cm.into_xy(); + let expected_value_cm = value_commitment.cm(params).into_xy(); let expected_epk = g_d.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_xy.0); - assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm_xy.1); + 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/src/primitives/mod.rs b/src/primitives/mod.rs index 5aa2260..6c12892 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -27,6 +27,28 @@ use jubjub::{ 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 + ) + } +} + pub struct ProofGenerationKey { pub ak: edwards::Point, pub rsk: E::Fs From 512a394b303efaed12fb16c336a3b754d830496e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 01:36:16 -0700 Subject: [PATCH 108/168] Simplify witness for Spend statement. --- src/circuit/mod.rs | 51 +++++++++++++++++++++++-------------------- src/primitives/mod.rs | 12 ++++++++++ 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index c4f0db2..30a84c0 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -30,7 +30,9 @@ use jubjub::{ use constants; use primitives::{ - ValueCommitment + ValueCommitment, + ProofGenerationKey, + PaymentAddress }; @@ -113,21 +115,14 @@ pub struct Spend<'a, E: JubjubEngine> { /// Pedersen commitment to the value being spent pub value_commitment: Option>, - /// Key which allows the proof to be constructed - /// as defense-in-depth against a flaw in the - /// protocol that would otherwise be exploitable - /// by a holder of a viewing key. - pub rsk: Option, + /// Key required to construct proofs for spending notes + /// for a particular spending key + pub proof_generation_key: Option>, - /// The public key that will be re-randomized for - /// use as a nullifier and signing key for the - /// transaction. - pub ak: Option>, + /// The payment address associated with the note + pub payment_address: Option>, - /// The diversified base used to compute pk_d. - pub g_d: Option>, - - /// The randomness used to hide the note commitment data + /// The randomness of the note commitment pub commitment_randomness: Option, /// The authentication path of the commitment in the tree @@ -149,7 +144,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Witness rsk as bits let rsk = boolean::field_into_boolean_vec_le( cs.namespace(|| "rsk"), - self.rsk + self.proof_generation_key.as_ref().map(|k| k.rsk.clone()) )?; // NB: We don't ensure that the bit representation of rsk @@ -169,7 +164,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Prover witnesses ak (ensures that it's on the curve) let ak = ecc::EdwardsPoint::witness( cs.namespace(|| "ak"), - self.ak, + self.proof_generation_key.as_ref().map(|k| k.ak.clone()), self.params )?; @@ -226,11 +221,20 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // already guaranteed this. // TODO: We might as well just perform the // check again here, since it's not expensive. - let g_d = ecc::EdwardsPoint::witness( - cs.namespace(|| "witness g_d"), - self.g_d, - self.params - )?; + 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 + )? + }; // Compute pk_d = g_d^ivk let pk_d = g_d.mul( @@ -614,9 +618,8 @@ fn test_input_circuit_with_bls12_381() { let instance = Spend { params: params, value_commitment: Some(value_commitment.clone()), - rsk: Some(rsk), - ak: Some(ak), - g_d: Some(g_d.clone()), + proof_generation_key: Some(proof_generation_key.clone()), + payment_address: Some(payment_address.clone()), commitment_randomness: Some(commitment_randomness), auth_path: auth_path.clone() }; diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 6c12892..028c6b7 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -49,6 +49,7 @@ impl ValueCommitment { } } +#[derive(Clone)] pub struct ProofGenerationKey { pub ak: edwards::Point, pub rsk: E::Fs @@ -119,11 +120,22 @@ impl Diversifier { } } +#[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 struct Note { /// The value of the note pub value: u64, From 999840011707b5dfe6323c079c79f4085bff68c5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 01:37:55 -0700 Subject: [PATCH 109/168] Relocate structs for cleanliness. --- src/circuit/mod.rs | 83 +++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 30a84c0..19d0940 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -35,7 +35,6 @@ use primitives::{ PaymentAddress }; - // TODO: This should probably be removed and we // should use existing helper methods on `Option` // for mapping with an error. @@ -55,6 +54,47 @@ impl Assignment for Option { } } +/// 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, + + /// The authentication path of the commitment in the tree + pub auth_path: Vec> +} + +/// 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 diversified base, computed by GH(d) + pub g_d: Option>, + + /// The diversified address point, computed by GH(d)^ivk + pub pk_d: 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( @@ -108,27 +148,6 @@ fn expose_value_commitment( Ok(value_bits) } -/// 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, - - /// The authentication path of the commitment in the tree - pub auth_path: Vec> -} - impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { @@ -411,26 +430,6 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { } } -/// 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 diversified base, computed by GH(d) - pub g_d: Option>, - - /// The diversified address point, computed by GH(d)^ivk - pub pk_d: 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 -} - impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { From db28ff7ba16159384e6b88032cd173aabd6628ff Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 01:49:27 -0700 Subject: [PATCH 110/168] Simplify the Output witness. --- src/circuit/mod.rs | 66 +++++++++++++++++++++++++++---------------- src/primitives/mod.rs | 17 +++++++++++ 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 19d0940..828fa31 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -22,9 +22,7 @@ use bellman::{ use jubjub::{ JubjubEngine, - PrimeOrder, - FixedGenerators, - edwards + FixedGenerators }; use constants; @@ -82,11 +80,8 @@ pub struct Output<'a, E: JubjubEngine> { /// Pedersen commitment to the value being spent pub value_commitment: Option>, - /// The diversified base, computed by GH(d) - pub g_d: Option>, - - /// The diversified address point, computed by GH(d)^ivk - pub pk_d: Option>, + /// The payment address of the recipient + pub payment_address: Option>, /// The randomness used to hide the note commitment data pub commitment_randomness: Option, @@ -446,11 +441,13 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // 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.g_d, + self.payment_address.as_ref().and_then(|a| a.g_d(params)), self.params )?; @@ -496,7 +493,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // they would like. { // Just grab pk_d from the witness - let pk_d = self.pk_d.map(|e| e.into_xy()); + 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) @@ -570,7 +567,7 @@ fn test_input_circuit_with_bls12_381() { use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - use jubjub::{JubjubBls12, fs}; + use jubjub::{JubjubBls12, fs, edwards}; let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -583,7 +580,7 @@ fn test_input_circuit_with_bls12_381() { }; let rsk: fs::Fs = rng.gen(); - let ak: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); let proof_generation_key = ::primitives::ProofGenerationKey { ak: ak.clone(), @@ -693,7 +690,7 @@ fn test_output_circuit_with_bls12_381() { use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - use jubjub::{JubjubBls12, fs}; + use jubjub::{JubjubBls12, fs, edwards}; let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -703,8 +700,31 @@ fn test_output_circuit_with_bls12_381() { randomness: rng.gen() }; - let g_d: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); - let pk_d: edwards::Point = edwards::Point::rand(rng, params).mul_by_cofactor(params); + let rsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + rsk: rsk.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(); @@ -714,8 +734,7 @@ fn test_output_circuit_with_bls12_381() { let instance = Output { params: params, value_commitment: Some(value_commitment.clone()), - g_d: Some(g_d.clone()), - pk_d: Some(pk_d.clone()), + payment_address: Some(payment_address.clone()), commitment_randomness: Some(commitment_randomness), esk: Some(esk.clone()) }; @@ -726,16 +745,15 @@ fn test_output_circuit_with_bls12_381() { assert_eq!(cs.num_constraints(), 7827); assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); - let expected_cm = ::primitives::Note { - value: value_commitment.value, - g_d: g_d.clone(), - pk_d: pk_d.clone(), - r: commitment_randomness.clone() - }.cm(params); + 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 = g_d.mul(esk, params); + 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); diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 028c6b7..6ac1040 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -134,6 +134,23 @@ impl PaymentAddress { { 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 { From b6e1b52a44071e736ea2329559250070ed80607d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 8 Mar 2018 13:03:07 -0700 Subject: [PATCH 111/168] Fix comment about Montgomery curve selection --- src/jubjub/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index cff5c4a..ca874c6 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -5,7 +5,7 @@ //! value `A` is the smallest integer choice such that: //! //! * `(A - 2) / 4` is a small integer (`10240`). -//! * `A^2 - 4` is quadratic residue. +//! * `A^2 - 4` is quadratic nonresidue. //! * The group order of the curve and its quadratic twist has a large //! prime factor. //! From c1784f0fdf2870ad0475d4fd59f93639fe391e3f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 9 Mar 2018 14:08:01 -0700 Subject: [PATCH 112/168] Check g_d is not small order in Spend circuit. --- src/circuit/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 828fa31..f12ad9e 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -233,8 +233,6 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // manages to witness a commitment in the // tree, then the Output circuit would have // already guaranteed this. - // TODO: We might as well just perform the - // check again here, since it's not expensive. let g_d = { // This binding is to avoid a weird edge case in Rust's // ownership/borrowing rules. self is partially moved @@ -250,6 +248,12 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )? }; + // 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"), @@ -623,8 +627,8 @@ fn test_input_circuit_with_bls12_381() { instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 101550); - assert_eq!(cs.hash(), "3cc6d9383ca882ae3666267618e826e9d51a3177fc89ef6d42d9f63b84179f77"); + assert_eq!(cs.num_constraints(), 101566); + assert_eq!(cs.hash(), "e3d226975c99e17ef30f5a4b7e87d355ef3dbd80eed0c8de43780f3028946d82"); let expected_value_cm = value_commitment.cm(params).into_xy(); From 2c69abe268e65034da183fc2b46b5be47c13f542 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 9 Mar 2018 14:17:45 -0700 Subject: [PATCH 113/168] Add Uncommitted^Sapling. --- src/primitives/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 6ac1040..2ac43b1 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,4 +1,5 @@ use pairing::{ + Field, PrimeField, PrimeFieldRepr }; @@ -165,6 +166,15 @@ pub struct Note { } 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 { From 661f318eb7424e4b5b7982878e2c07a9f063510c Mon Sep 17 00:00:00 2001 From: Jason Davies Date: Sun, 11 Feb 2018 20:38:52 +0000 Subject: [PATCH 114/168] Optimise Boolean::enforce_equal. Use a single constraint instead of two. Fixes #6. --- src/circuit/boolean.rs | 125 ++++++++++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 26 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 239d404..f06fc99 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -375,35 +375,44 @@ impl Boolean { where E: Engine, CS: ConstraintSystem { - let c = Self::xor(&mut cs, a, b)?; - - match c { - Boolean::Constant(false) => { - Ok(()) + match (a, b) { + (&Boolean::Constant(a), &Boolean::Constant(b)) => { + if a == b { + Ok(()) + } else { + Err(SynthesisError::Unsatisfiable) + } }, - Boolean::Constant(true) => { - Err(SynthesisError::Unsatisfiable) - }, - Boolean::Is(ref res) => { + (&Boolean::Constant(true), a) | (a, &Boolean::Constant(true)) => { cs.enforce( - || "enforce equals zero", + || "enforce equal to one", |lc| lc, |lc| lc, - |lc| lc + res.get_variable() + |lc| lc + CS::one() - &a.lc(CS::one(), E::Fr::one()) ); Ok(()) }, - Boolean::Not(ref res) => { + (&Boolean::Constant(false), a) | (a, &Boolean::Constant(false)) => { cs.enforce( - || "enforce equals one", + || "enforce equal to zero", |lc| lc, |lc| lc, - |lc| lc + CS::one() - res.get_variable() + |_| 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(()) + } } } @@ -636,24 +645,88 @@ mod test { 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 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()); + 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 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) + ); } - if b_neg { - b = b.not(); + { + 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(); - Boolean::enforce_equal(&mut cs, &a, &b).unwrap(); + let mut a = Boolean::from(AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_bool)).unwrap()); + let mut b = Boolean::Constant(b_bool); - assert_eq!( - cs.is_satisfied(), - (a_bool ^ a_neg) == (b_bool ^ b_neg) - ); + 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()); + } + } } } } From 827e85547e5aaf6bce2b984ee3a0e76da3034af2 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 11:42:22 -0600 Subject: [PATCH 115/168] Public input verification utility for the test framework --- src/circuit/test/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index 9728e04..12fe0ca 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -294,6 +294,20 @@ impl TestConstraintSystem { } } + 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() } From 52eb59766b4ec4205830d4d2619c5988d3a3985a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:31:10 -0600 Subject: [PATCH 116/168] Optimize UInt32::addmany/BLAKE2s to combine equality constraints. (Closes #5) --- src/circuit/blake2s.rs | 37 ++++++----- src/circuit/boolean.rs | 7 +++ src/circuit/mod.rs | 5 +- src/circuit/multieq.rs | 137 +++++++++++++++++++++++++++++++++++++++++ src/circuit/uint32.rs | 62 +++++++++---------- 5 files changed, 198 insertions(+), 50 deletions(-) create mode 100644 src/circuit/multieq.rs diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index f75f5c7..a78f3cf 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -15,6 +15,8 @@ use super::uint32::{ UInt32 }; +use super::multieq::MultiEq; + /* 2.1. Parameters The following table summarizes various parameters and their ranges: @@ -88,8 +90,8 @@ const SIGMA: [[usize; 16]; 10] = [ END FUNCTION. */ -fn mixing_g>( - mut cs: CS, +fn mixing_g, M>( + mut cs: M, v: &mut [UInt32], a: usize, b: usize, @@ -98,6 +100,7 @@ fn mixing_g>( 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); @@ -199,20 +202,24 @@ fn blake2s_compression>( v[14] = v[14].xor(cs.namespace(|| "third xor"), &UInt32::constant(u32::max_value()))?; } - for i in 0..10 { - let mut cs = cs.namespace(|| format!("round {}", i)); + { + let mut cs = MultiEq::new(&mut cs); - let s = SIGMA[i % 10]; + for i in 0..10 { + let mut cs = cs.namespace(|| format!("round {}", i)); - 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]])?; + let s = SIGMA[i % 10]; - 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]])?; + 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 { @@ -350,7 +357,7 @@ mod test { 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(), 21792); + assert_eq!(cs.num_constraints(), 21518); } #[test] @@ -367,7 +374,7 @@ mod test { .collect(); blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 21792); + assert_eq!(cs.num_constraints(), 21518); } #[test] diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 239d404..e5fa435 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -367,6 +367,13 @@ pub enum Boolean { } impl Boolean { + pub fn is_constant(&self) -> bool { + match *self { + Boolean::Constant(_) => true, + _ => false + } + } + pub fn enforce_equal( mut cs: CS, a: &Self, diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index f12ad9e..f928820 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -2,6 +2,7 @@ pub mod test; pub mod boolean; +pub mod multieq; pub mod uint32; pub mod blake2s; pub mod num; @@ -627,8 +628,8 @@ fn test_input_circuit_with_bls12_381() { instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 101566); - assert_eq!(cs.hash(), "e3d226975c99e17ef30f5a4b7e87d355ef3dbd80eed0c8de43780f3028946d82"); + assert_eq!(cs.num_constraints(), 101018); + assert_eq!(cs.hash(), "eedcef5fd638e0168ae4d53ac58df66f0acdabea46749cc5f4b39459c8377804"); let expected_value_cm = value_commitment.cm(params).into_xy(); diff --git a/src/circuit/multieq.rs b/src/circuit/multieq.rs new file mode 100644 index 0000000..0f9c755 --- /dev/null +++ b/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/src/circuit/uint32.rs b/src/circuit/uint32.rs index ff39433..4714724 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -15,6 +15,8 @@ use super::boolean::{ AllocatedBit }; +use super::multieq::MultiEq; + /// Represents an interpretation of 32 `Boolean` objects as an /// unsigned integer. #[derive(Clone)] @@ -188,12 +190,13 @@ impl UInt32 { } /// Perform modular addition of several `UInt32` objects. - pub fn addmany( - mut cs: CS, + pub fn addmany( + mut cs: M, operands: &[Self] ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem, + M: ConstraintSystem> { // Make some arbitrary bounds for ourselves to avoid overflows // in the scalar field @@ -208,7 +211,8 @@ impl UInt32 { // Keep track of the resulting value let mut result_value = Some(0u64); - // This is a linear combination that we will enforce to be "zero" + // This is a linear combination that we will enforce to equal the + // output let mut lc = LinearCombination::zero(); let mut all_constants = true; @@ -231,25 +235,9 @@ impl UInt32 { // the linear combination let mut coeff = E::Fr::one(); for bit in &op.bits { - match bit { - &Boolean::Is(ref bit) => { - all_constants = false; + lc = lc + &bit.lc(CS::one(), coeff); - // Add coeff * bit - lc = lc + (coeff, bit.get_variable()); - }, - &Boolean::Not(ref bit) => { - all_constants = false; - - // Add coeff * (1 - bit) = coeff * ONE - coeff * bit - lc = lc + (coeff, CS::one()) - (coeff, bit.get_variable()); - }, - &Boolean::Constant(bit) => { - if bit { - lc = lc + (coeff, CS::one()); - } - } - } + all_constants &= bit.is_constant(); coeff.double(); } @@ -268,6 +256,10 @@ impl UInt32 { // 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; @@ -278,8 +270,8 @@ impl UInt32 { result_value.map(|v| (v >> i) & 1 == 1) )?; - // Subtract this bit from the linear combination to ensure the sums balance out - lc = lc - (coeff, b.get_variable()); + // Add this bit to the result combination + result_lc = result_lc + (coeff, b.get_variable()); result_bits.push(b.into()); @@ -288,13 +280,8 @@ impl UInt32 { coeff.double(); } - // Enforce that the linear combination equals zero - cs.enforce( - || "modular addition", - |lc| lc, - |lc| lc, - |_| lc - ); + // 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); @@ -315,6 +302,7 @@ mod test { use pairing::{Field}; use ::circuit::test::*; use bellman::{ConstraintSystem}; + use circuit::multieq::MultiEq; #[test] fn test_uint32_from_bits() { @@ -406,7 +394,11 @@ mod test { let mut expected = a.wrapping_add(b).wrapping_add(c); - let r = UInt32::addmany(cs.namespace(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap(); + 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)); @@ -444,7 +436,11 @@ mod test { 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 = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_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()); From ca202ef3045dd85fa1a5fed8384617cb30bd4143 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:36:05 -0600 Subject: [PATCH 117/168] Introduce input multipacking abstraction for nullifiers. --- src/circuit/mod.rs | 1 + src/circuit/multipack.rs | 106 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/circuit/multipack.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index f928820..012a37c 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -9,6 +9,7 @@ pub mod num; pub mod lookup; pub mod ecc; pub mod pedersen_hash; +pub mod multipack; use pairing::{ PrimeField, diff --git a/src/circuit/multipack.rs b/src/circuit/multipack.rs new file mode 100644 index 0000000..04f6260 --- /dev/null +++ b/src/circuit/multipack.rs @@ -0,0 +1,106 @@ +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 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)); + } +} From 21625d69e0b4c467aaff7f26418d68f4926d407f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:44:19 -0600 Subject: [PATCH 118/168] Place Sapling circuit into submodule. --- src/circuit/mod.rs | 740 +----------------------------------- src/circuit/sapling/mod.rs | 748 +++++++++++++++++++++++++++++++++++++ 2 files changed, 750 insertions(+), 738 deletions(-) create mode 100644 src/circuit/sapling/mod.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 012a37c..4cde222 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -11,28 +11,10 @@ pub mod ecc; pub mod pedersen_hash; pub mod multipack; -use pairing::{ - PrimeField, - PrimeFieldRepr, -}; +pub mod sapling; use bellman::{ - SynthesisError, - ConstraintSystem, - Circuit -}; - -use jubjub::{ - JubjubEngine, - FixedGenerators -}; - -use constants; - -use primitives::{ - ValueCommitment, - ProofGenerationKey, - PaymentAddress + SynthesisError }; // TODO: This should probably be removed and we @@ -53,721 +35,3 @@ impl Assignment for Option { } } } - -/// 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, - - /// The authentication path of the commitment in the tree - pub auth_path: Vec> -} - -/// 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 gv = 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 hr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "hr"), - value_commitment.as_ref().map(|c| c.randomness) - )?; - - // Compute the randomness in the exponent - let hr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of randomization for value commitment"), - FixedGenerators::ValueCommitmentRandomness, - &hr, - params - )?; - - // Compute the Pedersen commitment to the value - let gvhr = gv.add( - cs.namespace(|| "computation of value commitment"), - &hr, - params - )?; - - // Expose the commitment as an input to the circuit - gvhr.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> - { - let value_bits = expose_value_commitment( - cs.namespace(|| "value commitment"), - self.value_commitment, - self.params - )?; - - // Compute rk = [rsk] ProvingPublicKey - let rk; - { - // Witness rsk as bits - let rsk = boolean::field_into_boolean_vec_le( - cs.namespace(|| "rsk"), - self.proof_generation_key.as_ref().map(|k| k.rsk.clone()) - )?; - - // NB: We don't ensure that the bit representation of rsk - // 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 rk = [rsk] ProvingPublicKey - rk = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of rk"), - FixedGenerators::ProofGenerationKey, - &rsk, - self.params - )?; - } - - // 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 - )?; - - // Unpack ak and rk for input to BLAKE2s - - // This is the "viewing key" preimage for CRH^ivk - let mut vk = vec![]; - vk.extend( - ak.repr(cs.namespace(|| "representation of ak"))? - ); - - // This is the nullifier randomness preimage for PRF^nr - let mut nr_preimage = vec![]; - - // Extend vk and nr preimages with the representation of - // rk. - { - let repr_rk = rk.repr( - cs.namespace(|| "representation of rk") - )?; - - vk.extend(repr_rk.iter().cloned()); - nr_preimage.extend(repr_rk); - } - - assert_eq!(vk.len(), 512); - assert_eq!(nr_preimage.len(), 256); - - // Compute the incoming viewing key ivk - let mut ivk = blake2s::blake2s( - cs.namespace(|| "computation of ivk"), - &vk, - constants::CRH_IVK_PERSONALIZATION - )?; - - // Little endian bit order - ivk.reverse(); - - // drop_5 to ensure it's in the field - ivk.truncate(E::Fs::CAPACITY as usize); - - // Witness g_d. Ensures the point is on the - // curve, but not its order. If the prover - // manages to witness a commitment in the - // tree, then the Output circuit would have - // already guaranteed this. - 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![]; - note_contents.extend(value_bits.into_iter().rev()); - note_contents.extend( - g_d.repr(cs.namespace(|| "representation of g_d"))? - ); - 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 cmr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "cmr"), - self.commitment_randomness - )?; - - // Compute the note commitment randomness in the exponent - let cmr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of commitment randomness"), - FixedGenerators::NoteCommitmentRandomness, - &cmr, - self.params - )?; - - // Randomize the note commitment. Pedersen hashes are not - // themselves hiding commitments. - cm = cm.add( - cs.namespace(|| "randomization of note commitment"), - &cmr, - self.params - )?; - } - - let tree_depth = self.auth_path.len(); - - // 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(); - - 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 - } - - assert_eq!(position_bits.len(), tree_depth); - - // Expose the anchor - cur.inputize(cs.namespace(|| "anchor"))?; - - // Compute the cm + g^position for preventing - // faerie gold attacks - { - // 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 - cm = cm.add( - cs.namespace(|| "faerie gold prevention"), - &position, - self.params - )?; - } - - // Let's compute nr = BLAKE2s(rk || cm + position) - nr_preimage.extend( - cm.repr(cs.namespace(|| "representation of cm"))? - ); - - assert_eq!(nr_preimage.len(), 512); - - // Compute nr - let mut nr = blake2s::blake2s( - cs.namespace(|| "nr computation"), - &nr_preimage, - constants::PRF_NR_PERSONALIZATION - )?; - - // Little endian bit order - nr.reverse(); - - // We want the randomization in the field to - // simplify outside code. - // TODO: This isn't uniformly random. - nr.truncate(E::Fs::CAPACITY as usize); - - // Compute nullifier - let nf = ak.mul( - cs.namespace(|| "computation of nf"), - &nr, - self.params - )?; - - // Expose the nullifier publicly - nf.inputize(cs.namespace(|| "nullifier"))?; - - Ok(()) - } -} - -impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { - fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> - { - let value_bits = expose_value_commitment( - cs.namespace(|| "value commitment"), - self.value_commitment, - self.params - )?; - - // Let's start to construct our note, which contains - // value (big endian) - let mut note_contents = vec![]; - note_contents.extend(value_bits.into_iter().rev()); - - // 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 // 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 - let cmr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "cmr"), - self.commitment_randomness - )?; - - // Compute the note commitment randomness in the exponent - let cmr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of commitment randomness"), - FixedGenerators::NoteCommitmentRandomness, - &cmr, - self.params - )?; - - // Randomize our note commitment - cm = cm.add( - cs.namespace(|| "randomization of note commitment"), - &cmr, - 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; - - let value_commitment = ValueCommitment { - value: rng.gen(), - randomness: rng.gen() - }; - - let rsk: fs::Fs = rng.gen(); - let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); - - let proof_generation_key = ::primitives::ProofGenerationKey { - ak: ak.clone(), - rsk: rsk.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 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), - auth_path: auth_path.clone() - }; - - instance.synthesize(&mut cs).unwrap(); - - assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 101018); - assert_eq!(cs.hash(), "eedcef5fd638e0168ae4d53ac58df66f0acdabea46749cc5f4b39459c8377804"); - - let expected_value_cm = value_commitment.cm(params).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); - - 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 mut cur = note.cm(params); - - assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); - - for (i, val) in auth_path.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_xy = expected_nf.into_xy(); - - assert_eq!(cs.get_input(3, "anchor/input variable"), cur); - assert_eq!(cs.get_input(4, "nullifier/x/input variable"), expected_nf_xy.0); - assert_eq!(cs.get_input(5, "nullifier/y/input variable"), expected_nf_xy.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]); - - let value_commitment = ValueCommitment { - value: rng.gen(), - randomness: rng.gen() - }; - - let rsk: fs::Fs = rng.gen(); - let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); - - let proof_generation_key = ::primitives::ProofGenerationKey { - ak: ak.clone(), - rsk: rsk.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(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); - - 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/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs new file mode 100644 index 0000000..9b3b6de --- /dev/null +++ b/src/circuit/sapling/mod.rs @@ -0,0 +1,748 @@ +use pairing::{ + PrimeField, + PrimeFieldRepr, +}; + +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; + +/// 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, + + /// The authentication path of the commitment in the tree + pub auth_path: Vec> +} + +/// 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 gv = 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 hr = boolean::field_into_boolean_vec_le( + cs.namespace(|| "hr"), + value_commitment.as_ref().map(|c| c.randomness) + )?; + + // Compute the randomness in the exponent + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + params + )?; + + // Compute the Pedersen commitment to the value + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + params + )?; + + // Expose the commitment as an input to the circuit + gvhr.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> + { + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params + )?; + + // Compute rk = [rsk] ProvingPublicKey + let rk; + { + // Witness rsk as bits + let rsk = boolean::field_into_boolean_vec_le( + cs.namespace(|| "rsk"), + self.proof_generation_key.as_ref().map(|k| k.rsk.clone()) + )?; + + // NB: We don't ensure that the bit representation of rsk + // 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 rk = [rsk] ProvingPublicKey + rk = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of rk"), + FixedGenerators::ProofGenerationKey, + &rsk, + self.params + )?; + } + + // 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 + )?; + + // Unpack ak and rk for input to BLAKE2s + + // This is the "viewing key" preimage for CRH^ivk + let mut vk = vec![]; + vk.extend( + ak.repr(cs.namespace(|| "representation of ak"))? + ); + + // This is the nullifier randomness preimage for PRF^nr + let mut nr_preimage = vec![]; + + // Extend vk and nr preimages with the representation of + // rk. + { + let repr_rk = rk.repr( + cs.namespace(|| "representation of rk") + )?; + + vk.extend(repr_rk.iter().cloned()); + nr_preimage.extend(repr_rk); + } + + assert_eq!(vk.len(), 512); + assert_eq!(nr_preimage.len(), 256); + + // Compute the incoming viewing key ivk + let mut ivk = blake2s::blake2s( + cs.namespace(|| "computation of ivk"), + &vk, + constants::CRH_IVK_PERSONALIZATION + )?; + + // Little endian bit order + ivk.reverse(); + + // drop_5 to ensure it's in the field + ivk.truncate(E::Fs::CAPACITY as usize); + + // Witness g_d. Ensures the point is on the + // curve, but not its order. If the prover + // manages to witness a commitment in the + // tree, then the Output circuit would have + // already guaranteed this. + 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![]; + note_contents.extend(value_bits.into_iter().rev()); + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); + 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 cmr = boolean::field_into_boolean_vec_le( + cs.namespace(|| "cmr"), + self.commitment_randomness + )?; + + // Compute the note commitment randomness in the exponent + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + // Randomize the note commitment. Pedersen hashes are not + // themselves hiding commitments. + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + self.params + )?; + } + + let tree_depth = self.auth_path.len(); + + // 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(); + + 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 + } + + assert_eq!(position_bits.len(), tree_depth); + + // Expose the anchor + cur.inputize(cs.namespace(|| "anchor"))?; + + // Compute the cm + g^position for preventing + // faerie gold attacks + { + // 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 + cm = cm.add( + cs.namespace(|| "faerie gold prevention"), + &position, + self.params + )?; + } + + // Let's compute nr = BLAKE2s(rk || cm + position) + nr_preimage.extend( + cm.repr(cs.namespace(|| "representation of cm"))? + ); + + assert_eq!(nr_preimage.len(), 512); + + // Compute nr + let mut nr = blake2s::blake2s( + cs.namespace(|| "nr computation"), + &nr_preimage, + constants::PRF_NR_PERSONALIZATION + )?; + + // Little endian bit order + nr.reverse(); + + // We want the randomization in the field to + // simplify outside code. + // TODO: This isn't uniformly random. + nr.truncate(E::Fs::CAPACITY as usize); + + // Compute nullifier + let nf = ak.mul( + cs.namespace(|| "computation of nf"), + &nr, + self.params + )?; + + // Expose the nullifier publicly + nf.inputize(cs.namespace(|| "nullifier"))?; + + Ok(()) + } +} + +impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params + )?; + + // Let's start to construct our note, which contains + // value (big endian) + let mut note_contents = vec![]; + note_contents.extend(value_bits.into_iter().rev()); + + // 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 // 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 + let cmr = boolean::field_into_boolean_vec_le( + cs.namespace(|| "cmr"), + self.commitment_randomness + )?; + + // Compute the note commitment randomness in the exponent + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + // Randomize our note commitment + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + 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; + + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + + let rsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + rsk: rsk.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 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), + auth_path: auth_path.clone() + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 101018); + assert_eq!(cs.hash(), "eedcef5fd638e0168ae4d53ac58df66f0acdabea46749cc5f4b39459c8377804"); + + let expected_value_cm = value_commitment.cm(params).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); + + 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 mut cur = note.cm(params); + + assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); + + for (i, val) in auth_path.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_xy = expected_nf.into_xy(); + + assert_eq!(cs.get_input(3, "anchor/input variable"), cur); + assert_eq!(cs.get_input(4, "nullifier/x/input variable"), expected_nf_xy.0); + assert_eq!(cs.get_input(5, "nullifier/y/input variable"), expected_nf_xy.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]); + + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + + let rsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + rsk: rsk.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(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); + + 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); + } +} From a7d704cd10d4e5a3ddf45af72616e0d6f1f73e9b Mon Sep 17 00:00:00 2001 From: Dimitris Apostolou Date: Fri, 16 Mar 2018 23:25:36 +0200 Subject: [PATCH 119/168] Fix typos --- src/jubjub/edwards.rs | 2 +- src/jubjub/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index e73c0ef..e4f5e85 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -318,7 +318,7 @@ impl Point { // // ... which represents the point ( x / y , (x - 1) / (x + 1) ) // as required by the mapping and preserves the property of - // the auxillary coordinate t. + // the auxiliary coordinate t. // // We need to scale the coordinate, so u and t will have // an extra factor s. diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index ca874c6..a04fe40 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -119,7 +119,7 @@ pub trait JubjubParams: Sized { 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 magntitudes of some + /// Returns a window table [0, 1, ..., 8] for different magnitudes of some /// fixed generator. fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>]; } From 7e05feb90bd6880d90313d1b123eff939a3341b8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 16 Mar 2018 10:31:14 -0600 Subject: [PATCH 120/168] Changes to names and circuit design to match spec. --- src/circuit/sapling/mod.rs | 142 +++++++++++++++++++++---------------- src/primitives/mod.rs | 51 ++++++------- 2 files changed, 108 insertions(+), 85 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 9b3b6de..2d51312 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -28,6 +28,7 @@ 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> { @@ -46,6 +47,9 @@ pub struct Spend<'a, E: JubjubEngine> { /// 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> } @@ -129,25 +133,25 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // Compute rk = [rsk] ProvingPublicKey - let rk; + // Compute nk = [nsk] ProvingPublicKey + let nk; { - // Witness rsk as bits - let rsk = boolean::field_into_boolean_vec_le( - cs.namespace(|| "rsk"), - self.proof_generation_key.as_ref().map(|k| k.rsk.clone()) + // 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 rsk + // 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 rk = [rsk] ProvingPublicKey - rk = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of rk"), + // Compute nk = [nsk] ProvingPublicKey + nk = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of nk"), FixedGenerators::ProofGenerationKey, - &rsk, + &nsk, self.params )?; } @@ -175,22 +179,46 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { ak.repr(cs.namespace(|| "representation of ak"))? ); - // This is the nullifier randomness preimage for PRF^nr - let mut nr_preimage = vec![]; - - // Extend vk and nr preimages with the representation of - // rk. + // Rerandomize ak and expose it as an input to the circuit { - let repr_rk = rk.repr( - cs.namespace(|| "representation of rk") + let ar = boolean::field_into_boolean_vec_le( + cs.namespace(|| "ar"), + self.ar )?; - vk.extend(repr_rk.iter().cloned()); - nr_preimage.extend(repr_rk); + // 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"))?; + } + + // This is the nullifier preimage for PRF^nf + let mut nf_preimage = vec![]; + + // Extend vk and nr preimages with the representation of + // nk. + { + let repr_nk = nk.repr( + cs.namespace(|| "representation of nk") + )?; + + vk.extend(repr_nk.iter().cloned()); + nf_preimage.extend(repr_nk); } assert_eq!(vk.len(), 512); - assert_eq!(nr_preimage.len(), 256); + assert_eq!(nf_preimage.len(), 256); // Compute the incoming viewing key ivk let mut ivk = blake2s::blake2s( @@ -353,6 +381,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // 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( @@ -363,46 +392,28 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // Add the position to the commitment - cm = cm.add( + rho = rho.add( cs.namespace(|| "faerie gold prevention"), &position, self.params )?; } - // Let's compute nr = BLAKE2s(rk || cm + position) - nr_preimage.extend( - cm.repr(cs.namespace(|| "representation of cm"))? + // Let's compute nf = BLAKE2s(rk || rho) + nf_preimage.extend( + rho.repr(cs.namespace(|| "representation of rho"))? ); - assert_eq!(nr_preimage.len(), 512); + assert_eq!(nf_preimage.len(), 512); - // Compute nr - let mut nr = blake2s::blake2s( - cs.namespace(|| "nr computation"), - &nr_preimage, + // Compute nf + let nf = blake2s::blake2s( + cs.namespace(|| "nf computation"), + &nf_preimage, constants::PRF_NR_PERSONALIZATION )?; - // Little endian bit order - nr.reverse(); - - // We want the randomization in the field to - // simplify outside code. - // TODO: This isn't uniformly random. - nr.truncate(E::Fs::CAPACITY as usize); - - // Compute nullifier - let nf = ak.mul( - cs.namespace(|| "computation of nf"), - &nr, - self.params - )?; - - // Expose the nullifier publicly - nf.inputize(cs.namespace(|| "nullifier"))?; - - Ok(()) + multipack::pack_into_inputs(cs.namespace(|| "pack nullifier"), &nf) } } @@ -560,12 +571,12 @@ fn test_input_circuit_with_bls12_381() { randomness: rng.gen() }; - let rsk: fs::Fs = 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(), - rsk: rsk.clone() + nsk: nsk.clone() }; let viewing_key = proof_generation_key.into_viewing_key(params); @@ -588,6 +599,7 @@ fn test_input_circuit_with_bls12_381() { 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 mut cs = TestConstraintSystem::::new(); @@ -598,22 +610,27 @@ fn test_input_circuit_with_bls12_381() { 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() }; instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 101018); - assert_eq!(cs.hash(), "eedcef5fd638e0168ae4d53ac58df66f0acdabea46749cc5f4b39459c8377804"); + assert_eq!(cs.num_constraints(), 98776); + assert_eq!(cs.hash(), "c5c377cad6310a5caa74305b2fe72b53e27a9c1db110edd9c4af164e99c0db71"); let expected_value_cm = value_commitment.cm(params).into_xy(); - assert_eq!(cs.num_inputs(), 6); + assert_eq!(cs.num_inputs(), 8); 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); + let rk = viewing_key.rk(ar, params).into_xy(); + assert_eq!(cs.get_input(3, "rk/x/input variable"), rk.0); + assert_eq!(cs.get_input(4, "rk/y/input variable"), rk.1); + let note = ::primitives::Note { value: value_commitment.value, g_d: g_d.clone(), @@ -656,12 +673,15 @@ fn test_input_circuit_with_bls12_381() { } } - let expected_nf = note.nf(&viewing_key, position, params); - let expected_nf_xy = expected_nf.into_xy(); + assert_eq!(cs.get_input(5, "anchor/input variable"), cur); - assert_eq!(cs.get_input(3, "anchor/input variable"), cur); - assert_eq!(cs.get_input(4, "nullifier/x/input variable"), expected_nf_xy.0); - assert_eq!(cs.get_input(5, "nullifier/y/input variable"), expected_nf_xy.1); + let expected_nf = note.nf(&viewing_key, position, params); + let expected_nf = multipack::bytes_to_bits(&expected_nf); + let expected_nf = multipack::compute_multipacking::(&expected_nf); + assert_eq!(expected_nf.len(), 2); + + 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]); } } @@ -681,12 +701,12 @@ fn test_output_circuit_with_bls12_381() { randomness: rng.gen() }; - let rsk: fs::Fs = 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(), - rsk: rsk.clone() + nsk: nsk.clone() }; let viewing_key = proof_generation_key.into_viewing_key(params); diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 2ac43b1..53d05b3 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -53,30 +53,42 @@ impl ValueCommitment { #[derive(Clone)] pub struct ProofGenerationKey { pub ak: edwards::Point, - pub rsk: E::Fs + pub nsk: E::Fs } impl ProofGenerationKey { pub fn into_viewing_key(&self, params: &E::Params) -> ViewingKey { ViewingKey { ak: self.ak.clone(), - rk: params.generator(FixedGenerators::ProofGenerationKey) - .mul(self.rsk, params) + nk: params.generator(FixedGenerators::ProofGenerationKey) + .mul(self.nsk, params) } } } pub struct ViewingKey { pub ak: edwards::Point, - pub rk: 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 + ) + } + fn ivk(&self) -> E::Fs { let mut preimage = [0; 64]; self.ak.write(&mut preimage[0..32]).unwrap(); - self.rk.write(&mut preimage[32..64]).unwrap(); + self.nk.write(&mut preimage[32..64]).unwrap(); let mut h = Blake2s::with_params(32, &[], &[], constants::CRH_IVK_PERSONALIZATION); h.update(&preimage); @@ -215,10 +227,10 @@ impl Note { viewing_key: &ViewingKey, position: u64, params: &E::Params - ) -> edwards::Point + ) -> Vec { - // Compute cm + position - let cm_plus_position = self + // Compute rho = cm + position.G + let rho = self .cm_full_point(params) .add( ¶ms.generator(FixedGenerators::NullifierPosition) @@ -226,23 +238,14 @@ impl Note { params ); - // Compute nr = drop_5(BLAKE2s(rk | cm_plus_position)) - let mut nr_preimage = [0u8; 64]; - viewing_key.rk.write(&mut nr_preimage[0..32]).unwrap(); - cm_plus_position.write(&mut nr_preimage[32..64]).unwrap(); + // 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_NR_PERSONALIZATION); - h.update(&nr_preimage); - let mut h = h.finalize().as_ref().to_vec(); - - // Drop the first five bits, so it can be interpreted as a scalar. - h[0] &= 0b0000_0111; - - let mut e = ::Repr::default(); - e.read_be(&h[..]).unwrap(); - - let nr = E::Fs::from_repr(e).expect("should be a valid scalar"); - - viewing_key.ak.mul(nr, params) + h.update(&nf_preimage); + + h.finalize().as_ref().to_vec() } /// Computes the note commitment From 8b2f231e2f6cc887ba9d752615fa6cb9f4094275 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 16 Mar 2018 10:58:08 -0600 Subject: [PATCH 121/168] Change personalization to match specification. --- src/circuit/sapling/mod.rs | 6 ++--- src/constants.rs | 55 +++++++++++++++++++++++++------------- src/primitives/mod.rs | 2 +- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 2d51312..83a6d58 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -410,7 +410,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let nf = blake2s::blake2s( cs.namespace(|| "nf computation"), &nf_preimage, - constants::PRF_NR_PERSONALIZATION + constants::PRF_NF_PERSONALIZATION )?; multipack::pack_into_inputs(cs.namespace(|| "pack nullifier"), &nf) @@ -618,7 +618,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "c5c377cad6310a5caa74305b2fe72b53e27a9c1db110edd9c4af164e99c0db71"); + assert_eq!(cs.hash(), "e6d326669533baf3f771267e86bd752b246184d34b1f2a68f9a6b9283f42e325"); let expected_value_cm = value_commitment.cm(params).into_xy(); @@ -744,7 +744,7 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); + assert_eq!(cs.hash(), "0c3d4ee7b0ac346836f177a471b2453c3558ea5760c526faad72feb65caf275b"); let expected_cm = payment_address.create_note( value_commitment.value, diff --git a/src/constants.rs b/src/constants.rs index 71b96e1..b0ba9d5 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,28 +2,47 @@ /// 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"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; +pub const GH_FIRST_BLOCK: &'static [u8; 64] + = b"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; // BLAKE2s invocation personalizations -/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | rk) -pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; -/// BLAKE2s Personalization for PRF^nr = BLAKE2s(rk | cm + position) -pub const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; +/// 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"PEDERSEN"; +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_gh"; -/// BLAKE2s Personalization for the proof generation key base point -pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; -/// BLAKE2s Personalization for the note commitment randomness generator -pub const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"abcdefgh"; -/// BLAKE2s Personalization for the nullifier position generator (for PRF^nr) -pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"nfnfnfnf"; -/// BLAKE2s Personalization for the value commitment generator for the value -pub const VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"45u8gh45"; -/// BLAKE2s Personalization for the value commitment randomness generator -pub const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"11111111"; +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"sksksksk"; +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 note commitment randomness generator +pub const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] + = b"Zcashrcm"; + +/// BLAKE2s Personalization for the value commitment randomness generator +pub const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] + = b"Zcashrcv"; + +/// BLAKE2s Personalization for the value commitment generator for the value +pub const VALUE_COMMITMENT_VALUE_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"Zcashrho"; diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 53d05b3..bb1298b 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -242,7 +242,7 @@ impl Note { 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_NR_PERSONALIZATION); + let mut h = Blake2s::with_params(32, &[], &[], constants::PRF_NF_PERSONALIZATION); h.update(&nf_preimage); h.finalize().as_ref().to_vec() From 00ee962429508830f397fd96da9cf19501285f11 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 16 Mar 2018 12:30:00 -0600 Subject: [PATCH 122/168] Fix some names of variables. --- src/circuit/sapling/mod.rs | 55 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 83a6d58..00e6c70 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -88,7 +88,7 @@ fn expose_value_commitment( )?; // Compute the note value in the exponent - let gv = ecc::fixed_base_multiplication( + let value = ecc::fixed_base_multiplication( cs.namespace(|| "compute the value in the exponent"), FixedGenerators::ValueCommitmentValue, &value_bits, @@ -98,28 +98,28 @@ fn expose_value_commitment( // Booleanize the randomness. This does not ensure // the bit representation is "in the field" because // it doesn't matter for security. - let hr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "hr"), + 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 hr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of randomization for value commitment"), + let rcv = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of rcv"), FixedGenerators::ValueCommitmentRandomness, - &hr, + &rcv, params )?; // Compute the Pedersen commitment to the value - let gvhr = gv.add( - cs.namespace(|| "computation of value commitment"), - &hr, + let cv = value.add( + cs.namespace(|| "computation of cv"), + &rcv, params )?; // Expose the commitment as an input to the circuit - gvhr.inputize(cs.namespace(|| "commitment point"))?; + cv.inputize(cs.namespace(|| "commitment point"))?; Ok(value_bits) } @@ -133,7 +133,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // Compute nk = [nsk] ProvingPublicKey + // Compute nk = [nsk] ProofGenerationKey let nk; { // Witness nsk as bits @@ -174,8 +174,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Unpack ak and rk for input to BLAKE2s // This is the "viewing key" preimage for CRH^ivk - let mut vk = vec![]; - vk.extend( + let mut ivk_preimage = vec![]; + ivk_preimage.extend( ak.repr(cs.namespace(|| "representation of ak"))? ); @@ -206,24 +206,24 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // This is the nullifier preimage for PRF^nf let mut nf_preimage = vec![]; - // Extend vk and nr preimages with the representation of + // Extend ivk and nf preimages with the representation of // nk. { let repr_nk = nk.repr( cs.namespace(|| "representation of nk") )?; - vk.extend(repr_nk.iter().cloned()); + ivk_preimage.extend(repr_nk.iter().cloned()); nf_preimage.extend(repr_nk); } - assert_eq!(vk.len(), 512); + 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"), - &vk, + &ivk_preimage, constants::CRH_IVK_PERSONALIZATION )?; @@ -233,11 +233,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // drop_5 to ensure it's in the field ivk.truncate(E::Fs::CAPACITY as usize); - // Witness g_d. Ensures the point is on the - // curve, but not its order. If the prover - // manages to witness a commitment in the - // tree, then the Output circuit would have - // already guaranteed this. + // 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 @@ -257,7 +253,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // 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)?; + 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( @@ -294,16 +293,16 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { { // Booleanize the randomness for the note commitment - let cmr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "cmr"), + let rcm = boolean::field_into_boolean_vec_le( + cs.namespace(|| "rcm"), self.commitment_randomness )?; // Compute the note commitment randomness in the exponent - let cmr = ecc::fixed_base_multiplication( + let rcm = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), FixedGenerators::NoteCommitmentRandomness, - &cmr, + &rcm, self.params )?; @@ -311,7 +310,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // themselves hiding commitments. cm = cm.add( cs.namespace(|| "randomization of note commitment"), - &cmr, + &rcm, self.params )?; } From 13b03a0a1a4fdd60d1cbacc3bb89688dbfb3b6b8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 16 Mar 2018 22:21:29 -0600 Subject: [PATCH 123/168] Relocate circuit components for intuitive code paths. --- src/circuit/sapling/mod.rs | 95 +++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 00e6c70..4300bb8 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -127,35 +127,6 @@ fn expose_value_commitment( impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { - let value_bits = expose_value_commitment( - cs.namespace(|| "value commitment"), - self.value_commitment, - self.params - )?; - - // 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 - )?; - } - // Prover witnesses ak (ensures that it's on the curve) let ak = ecc::EdwardsPoint::witness( cs.namespace(|| "ak"), @@ -171,14 +142,6 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // Unpack ak and rk for input to BLAKE2s - - // This is the "viewing key" preimage for CRH^ivk - let mut ivk_preimage = vec![]; - ivk_preimage.extend( - ak.repr(cs.namespace(|| "representation of ak"))? - ); - // Rerandomize ak and expose it as an input to the circuit { let ar = boolean::field_into_boolean_vec_le( @@ -203,6 +166,37 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { 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![]; @@ -265,6 +259,14 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; + // Expose the value commitment and get the + // bits of the value (in little endian order) + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params + )?; + // Compute note contents // value (in big endian) followed by g_d and pk_d let mut note_contents = vec![]; @@ -315,8 +317,6 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; } - let tree_depth = self.auth_path.len(); - // This will store (least significant bit first) // the position of the note in the tree, for use // in nullifier computation. @@ -326,6 +326,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // 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)); @@ -373,8 +374,6 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?.get_x().clone(); // Injective encoding } - assert_eq!(position_bits.len(), tree_depth); - // Expose the anchor cur.inputize(cs.namespace(|| "anchor"))?; @@ -617,18 +616,18 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "e6d326669533baf3f771267e86bd752b246184d34b1f2a68f9a6b9283f42e325"); - - let expected_value_cm = value_commitment.cm(params).into_xy(); + assert_eq!(cs.hash(), "729850617d4e6d95cbf348f07cbe0c63b01d35718f24cbcf7df79e2c3e1a7648"); assert_eq!(cs.num_inputs(), 8); 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); let rk = viewing_key.rk(ar, params).into_xy(); - assert_eq!(cs.get_input(3, "rk/x/input variable"), rk.0); - assert_eq!(cs.get_input(4, "rk/y/input variable"), rk.1); + assert_eq!(cs.get_input(1, "rk/x/input variable"), rk.0); + assert_eq!(cs.get_input(2, "rk/y/input variable"), rk.1); + + let expected_value_cm = value_commitment.cm(params).into_xy(); + 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); let note = ::primitives::Note { value: value_commitment.value, From 6d01e78711b8ae8cc63fbb3406ecc54dd7ecd168 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 17 Mar 2018 09:02:29 -0600 Subject: [PATCH 124/168] Fix variable names in output circuit --- src/circuit/sapling/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 4300bb8..936930d 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -507,7 +507,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { note_contents.len(), 64 + // value 256 + // g_d - 256 // p_d + 256 // pk_d ); // Compute the hash of the note contents @@ -520,23 +520,23 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "cmr"), + let rcm = boolean::field_into_boolean_vec_le( + cs.namespace(|| "rcm"), self.commitment_randomness )?; // Compute the note commitment randomness in the exponent - let cmr = ecc::fixed_base_multiplication( + let rcm = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), FixedGenerators::NoteCommitmentRandomness, - &cmr, + &rcm, self.params )?; // Randomize our note commitment cm = cm.add( cs.namespace(|| "randomization of note commitment"), - &cmr, + &rcm, self.params )?; } From d09c4d6ce8e4bb313951a12d904488defb203ba0 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 17 Mar 2018 09:10:13 -0600 Subject: [PATCH 125/168] Simplify value commitment gadget. --- src/circuit/sapling/mod.rs | 39 ++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 936930d..bc2178f 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -82,7 +82,7 @@ fn expose_value_commitment( CS: ConstraintSystem { // Booleanize the value into little-endian bit order - let value_bits = boolean::u64_into_boolean_vec_le( + let mut value_bits = boolean::u64_into_boolean_vec_le( cs.namespace(|| "value"), value_commitment.as_ref().map(|c| c.value) )?; @@ -121,6 +121,9 @@ fn expose_value_commitment( // Expose the commitment as an input to the circuit cv.inputize(cs.namespace(|| "commitment point"))?; + // Reorder value_bits so that it's big-endian + value_bits.reverse(); + Ok(value_bits) } @@ -259,21 +262,24 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // Expose the value commitment and get the - // bits of the value (in little endian order) - let value_bits = expose_value_commitment( + // Compute note contents: + // value (in big endian) followed by g_d and pk_d + 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 - )?; + )?); - // Compute note contents - // value (in big endian) followed by g_d and pk_d - let mut note_contents = vec![]; - note_contents.extend(value_bits.into_iter().rev()); + // 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"))? ); @@ -418,16 +424,17 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { - let value_bits = expose_value_commitment( - cs.namespace(|| "value commitment"), - self.value_commitment, - self.params - )?; - // Let's start to construct our note, which contains // value (big endian) let mut note_contents = vec![]; - note_contents.extend(value_bits.into_iter().rev()); + + // 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 { From 8b6f113052ecd4c96d0490509c88b2395856dbde Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 17 Mar 2018 10:24:55 -0600 Subject: [PATCH 126/168] Change personalization to more closely align with the spec. --- src/circuit/sapling/mod.rs | 4 +- src/jubjub/mod.rs | 105 ++++++++++++++++++------------------- 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index bc2178f..eacb48c 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -623,7 +623,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "729850617d4e6d95cbf348f07cbe0c63b01d35718f24cbcf7df79e2c3e1a7648"); + assert_eq!(cs.hash(), "d810fa887178359f3fc5723781a0750b750dd0c02aeb0b14ff19a343db9868f1"); assert_eq!(cs.num_inputs(), 8); assert_eq!(cs.get_input(0, "ONE"), Fr::one()); @@ -749,7 +749,7 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "0c3d4ee7b0ac346836f177a471b2453c3558ea5760c526faad72feb65caf275b"); + assert_eq!(cs.hash(), "e49724488227ae83b2360a5ddbda7e44c83e6f526a369cefeb747c5dd6aab7c7"); let expected_cm = payment_address.create_note( value_commitment.value, diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index a04fe40..8966c7b 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -192,25 +192,51 @@ impl JubjubBls12 { fixed_base_circuit_generators: vec![], }; - // Create the bases for the Pedersen hashes + fn find_group_hash( + m: &[u8], + personalization: &[u8; 8], + params: &E::Params + ) -> edwards::Point { - // TODO: This currently does not match the specification - let mut cur = 0; - let mut pedersen_hash_generators = vec![]; + let mut tag = m.to_vec(); + let i = tag.len(); + tag.push(0u8); + + loop { + let gh = group_hash( + &tag, + personalization, + params + ); - // TODO: This generates more bases for the Pedersen hashes - // than necessary, which is just a performance issue in - // practice. - while pedersen_hash_generators.len() < 5 { - let gh = group_hash(&[cur], constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp_params); // We don't want to overflow and start reusing generators - assert!(cur != u8::max_value()); - cur += 1; + assert!(tag[i] != u8::max_value()); + tag[i] += 1; if let Some(gh) = gh { - pedersen_hash_generators.push(gh); + break gh; } } + } + + // Create the bases for the Pedersen hashes + { + let mut pedersen_hash_generators = vec![]; + + for m in 0..5 { + use byteorder::{WriteBytesExt, BigEndian}; + + 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() { @@ -232,52 +258,23 @@ impl JubjubBls12 { { let mut fixed_base_generators = vec![edwards::Point::zero(); FixedGenerators::Max as usize]; - { - // Each generator is found by invoking the group hash - // on tag 0x00, 0x01, ... until we find a valid result. - let find_first_gh = |personalization| { - let mut cur = 0u8; + fixed_base_generators[FixedGenerators::ProofGenerationKey as usize] = + find_group_hash(b"0", constants::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION, &tmp_params); - loop { - let gh = group_hash::(&[cur], personalization, &tmp_params); - // We don't want to overflow. - assert!(cur != u8::max_value()); - cur += 1; + fixed_base_generators[FixedGenerators::NoteCommitmentRandomness as usize] = + find_group_hash(b"0", constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION, &tmp_params); - if let Some(gh) = gh { - break gh; - } - } - }; + fixed_base_generators[FixedGenerators::NullifierPosition as usize] = + find_group_hash(b"0", constants::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION, &tmp_params); - // Written this way for exhaustion (double entendre). There's no - // way to iterate over the variants of an enum, so it's hideous. - for c in 0..(FixedGenerators::Max as usize) { - let p = match c { - c if c == (FixedGenerators::ProofGenerationKey as usize) => { - constants::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION - }, - c if c == (FixedGenerators::NoteCommitmentRandomness as usize) => { - constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION - }, - c if c == (FixedGenerators::NullifierPosition as usize) => { - constants::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION - }, - c if c == (FixedGenerators::ValueCommitmentValue as usize) => { - constants::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION - }, - c if c == (FixedGenerators::ValueCommitmentRandomness as usize) => { - constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION - }, - c if c == (FixedGenerators::SpendingKeyGenerator as usize) => { - constants::SPENDING_KEY_GENERATOR_PERSONALIZATION - }, - _ => unreachable!() - }; + fixed_base_generators[FixedGenerators::ValueCommitmentValue as usize] = + find_group_hash(b"0", constants::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION, &tmp_params); - fixed_base_generators[c] = find_first_gh(p); - } - } + fixed_base_generators[FixedGenerators::ValueCommitmentRandomness as usize] = + find_group_hash(b"0", constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION, &tmp_params); + + fixed_base_generators[FixedGenerators::SpendingKeyGenerator as usize] = + find_group_hash(b"0", constants::SPENDING_KEY_GENERATOR_PERSONALIZATION, &tmp_params); // Check for duplicates, far worse than spec inconsistencies! for (i, p1) in fixed_base_generators.iter().enumerate() { From 219d03cc11e04940847b2b47541cda11f3397873 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Mar 2018 17:05:51 -0600 Subject: [PATCH 127/168] Perform multiple checks of circuit in test. --- src/circuit/sapling/mod.rs | 330 +++++++++++++++++++------------------ 1 file changed, 167 insertions(+), 163 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index eacb48c..d58e6e7 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -571,122 +571,124 @@ fn test_input_circuit_with_bls12_381() { let tree_depth = 32; - 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 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() + for _ in 0..10 { + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() }; - instance.synthesize(&mut cs).unwrap(); + let nsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); - assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "d810fa887178359f3fc5723781a0750b750dd0c02aeb0b14ff19a343db9868f1"); - - assert_eq!(cs.num_inputs(), 8); - assert_eq!(cs.get_input(0, "ONE"), Fr::one()); - - let rk = viewing_key.rk(ar, params).into_xy(); - assert_eq!(cs.get_input(1, "rk/x/input variable"), rk.0); - assert_eq!(cs.get_input(2, "rk/y/input variable"), rk.1); - - let expected_value_cm = value_commitment.cm(params).into_xy(); - 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); - - 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 proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + nsk: nsk.clone() }; - let mut position = 0u64; - let mut cur = note.cm(params); + let viewing_key = proof_generation_key.into_viewing_key(params); - assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); + let payment_address; - for (i, val) in auth_path.into_iter().enumerate() - { - let (uncle, b) = val.unwrap(); + loop { + let diversifier = ::primitives::Diversifier(rng.gen()); - 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)), + if let Some(p) = viewing_key.into_payment_address( + diversifier, params - ).into_xy().0; - - if b { - position |= 1 << i; + ) + { + payment_address = p; + break; } } - assert_eq!(cs.get_input(5, "anchor/input variable"), cur); + 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 expected_nf = note.nf(&viewing_key, position, params); - let expected_nf = multipack::bytes_to_bits(&expected_nf); - let expected_nf = multipack::compute_multipacking::(&expected_nf); - assert_eq!(expected_nf.len(), 2); + { + let mut cs = TestConstraintSystem::::new(); - 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]); + 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() + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 98776); + assert_eq!(cs.hash(), "d810fa887178359f3fc5723781a0750b750dd0c02aeb0b14ff19a343db9868f1"); + + assert_eq!(cs.num_inputs(), 8); + assert_eq!(cs.get_input(0, "ONE"), Fr::one()); + + let rk = viewing_key.rk(ar, params).into_xy(); + assert_eq!(cs.get_input(1, "rk/x/input variable"), rk.0); + assert_eq!(cs.get_input(2, "rk/y/input variable"), rk.1); + + let expected_value_cm = value_commitment.cm(params).into_xy(); + 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); + + 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 mut cur = note.cm(params); + + assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); + + for (i, val) in auth_path.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; + } + } + + assert_eq!(cs.get_input(5, "anchor/input variable"), cur); + + let expected_nf = note.nf(&viewing_key, position, params); + let expected_nf = multipack::bytes_to_bits(&expected_nf); + let expected_nf = multipack::compute_multipacking::(&expected_nf); + assert_eq!(expected_nf.len(), 2); + + 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]); + } } } @@ -701,73 +703,75 @@ fn test_output_circuit_with_bls12_381() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - 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()) + for _ in 0..100 { + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() }; - instance.synthesize(&mut cs).unwrap(); + let nsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); - assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "e49724488227ae83b2360a5ddbda7e44c83e6f526a369cefeb747c5dd6aab7c7"); + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + nsk: nsk.clone() + }; - let expected_cm = payment_address.create_note( - value_commitment.value, - commitment_randomness, - params - ).expect("should be valid").cm(params); + let viewing_key = proof_generation_key.into_viewing_key(params); - let expected_value_cm = value_commitment.cm(params).into_xy(); + let payment_address; - let expected_epk = payment_address.g_d(params).expect("should be valid").mul(esk, params); - let expected_epk_xy = expected_epk.into_xy(); + loop { + let diversifier = ::primitives::Diversifier(rng.gen()); - 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); + 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(), "e49724488227ae83b2360a5ddbda7e44c83e6f526a369cefeb747c5dd6aab7c7"); + + 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); + } } } From b14c9f8d68eeb17dd50f39546ff6b50866e9d915 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Mar 2018 17:26:28 -0600 Subject: [PATCH 128/168] Change personalizations to actually match spec this time. --- src/circuit/sapling/mod.rs | 4 ++-- src/constants.rs | 12 ++---------- src/jubjub/mod.rs | 16 ++++++++-------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index d58e6e7..840ad9f 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -624,7 +624,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "d810fa887178359f3fc5723781a0750b750dd0c02aeb0b14ff19a343db9868f1"); + assert_eq!(cs.hash(), "2080d5f350cd7eff7742ab05dff18f82c0a2f29a5d2a758d805236067b2ed31f"); assert_eq!(cs.num_inputs(), 8); assert_eq!(cs.get_input(0, "ONE"), Fr::one()); @@ -752,7 +752,7 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "e49724488227ae83b2360a5ddbda7e44c83e6f526a369cefeb747c5dd6aab7c7"); + assert_eq!(cs.hash(), "a7810a444f7ef6d0caa8ba026ce06e64654863cd0557241282ca337858039a53"); let expected_cm = payment_address.create_note( value_commitment.value, diff --git a/src/constants.rs b/src/constants.rs index b0ba9d5..fa717c9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -31,18 +31,10 @@ pub const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8] pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"Zcash_H_"; -/// BLAKE2s Personalization for the note commitment randomness generator -pub const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] - = b"Zcashrcm"; - -/// BLAKE2s Personalization for the value commitment randomness generator -pub const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] - = b"Zcashrcv"; - /// BLAKE2s Personalization for the value commitment generator for the value -pub const VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION: &'static [u8; 8] +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"Zcashrho"; + = b"Zcash_J_"; diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 8966c7b..3b786c1 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -224,10 +224,10 @@ impl JubjubBls12 { let mut pedersen_hash_generators = vec![]; for m in 0..5 { - use byteorder::{WriteBytesExt, BigEndian}; + use byteorder::{WriteBytesExt, LittleEndian}; let mut segment_number = [0u8; 4]; - (&mut segment_number[0..4]).write_u32::(m).unwrap(); + (&mut segment_number[0..4]).write_u32::(m).unwrap(); pedersen_hash_generators.push( find_group_hash( @@ -259,22 +259,22 @@ impl JubjubBls12 { let mut fixed_base_generators = vec![edwards::Point::zero(); FixedGenerators::Max as usize]; fixed_base_generators[FixedGenerators::ProofGenerationKey as usize] = - find_group_hash(b"0", constants::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION, &tmp_params); + find_group_hash(&[], constants::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION, &tmp_params); fixed_base_generators[FixedGenerators::NoteCommitmentRandomness as usize] = - find_group_hash(b"0", constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION, &tmp_params); + find_group_hash(b"r", constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp_params); fixed_base_generators[FixedGenerators::NullifierPosition as usize] = - find_group_hash(b"0", constants::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION, &tmp_params); + find_group_hash(&[], constants::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION, &tmp_params); fixed_base_generators[FixedGenerators::ValueCommitmentValue as usize] = - find_group_hash(b"0", constants::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION, &tmp_params); + find_group_hash(b"v", constants::VALUE_COMMITMENT_GENERATOR_PERSONALIZATION, &tmp_params); fixed_base_generators[FixedGenerators::ValueCommitmentRandomness as usize] = - find_group_hash(b"0", constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION, &tmp_params); + find_group_hash(b"r", constants::VALUE_COMMITMENT_GENERATOR_PERSONALIZATION, &tmp_params); fixed_base_generators[FixedGenerators::SpendingKeyGenerator as usize] = - find_group_hash(b"0", constants::SPENDING_KEY_GENERATOR_PERSONALIZATION, &tmp_params); + 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() { From f9e58c01cec86cb6e7270306dbe7b044793936b8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Mar 2018 17:54:44 -0600 Subject: [PATCH 129/168] Swap bit-endianness of value in note commitment. --- src/circuit/sapling/mod.rs | 9 +++------ src/jubjub/edwards.rs | 30 ++---------------------------- src/lib.rs | 1 + src/primitives/mod.rs | 11 +++++++++-- src/util.rs | 27 +++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 36 deletions(-) create mode 100644 src/util.rs diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 840ad9f..159381f 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -82,7 +82,7 @@ fn expose_value_commitment( CS: ConstraintSystem { // Booleanize the value into little-endian bit order - let mut value_bits = boolean::u64_into_boolean_vec_le( + let value_bits = boolean::u64_into_boolean_vec_le( cs.namespace(|| "value"), value_commitment.as_ref().map(|c| c.value) )?; @@ -121,9 +121,6 @@ fn expose_value_commitment( // Expose the commitment as an input to the circuit cv.inputize(cs.namespace(|| "commitment point"))?; - // Reorder value_bits so that it's big-endian - value_bits.reverse(); - Ok(value_bits) } @@ -624,7 +621,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "2080d5f350cd7eff7742ab05dff18f82c0a2f29a5d2a758d805236067b2ed31f"); + assert_eq!(cs.hash(), "ba8b2232a910b00399e90030c87c16a770e6e692fe3b4316675bdd7795df6e50"); assert_eq!(cs.num_inputs(), 8); assert_eq!(cs.get_input(0, "ONE"), Fr::one()); @@ -752,7 +749,7 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "a7810a444f7ef6d0caa8ba026ce06e64654863cd0557241282ca337858039a53"); + assert_eq!(cs.hash(), "8db50ff0e14fae19a7d83ef47f6da3a7e3e2644d251e37b387c6408d85df3ae7"); let expected_cm = payment_address.create_note( value_commitment.value, diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index e4f5e85..d1d2772 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -26,6 +26,8 @@ use std::io::{ Read }; +use util::swap_bits_u64; + // Represents the affine point (X/Z, Y/Z) via the extended // twisted Edwards coordinates. // @@ -89,34 +91,6 @@ impl PartialEq for Point { } } -fn swap_bits_u64(x: u64) -> u64 -{ - let mut tmp = 0; - for i in 0..64 { - tmp |= ((x >> i) & 1) << (63 - i); - } - tmp -} - -#[test] -fn test_swap_bits_u64() { - assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111); - assert_eq!(swap_bits_u64(15135675916470734665), 0b1001001011110010001101010010001110110000100111010011000001001011); - assert_eq!(swap_bits_u64(6724233301461108393), 0b1001010101100000100011100001010111110001011000101000101010111010); - assert_eq!(swap_bits_u64(206708183275952289), 0b1000010100011010001010100011101011111111111110100111101101000000); - assert_eq!(swap_bits_u64(12712751566144824320), 0b0000000000100110010110111000001110001100001000110011011000001101); - - let mut a = 15863238721320035327u64; - for _ in 0..1000 { - a = a.wrapping_mul(a); - - let swapped = swap_bits_u64(a); - let unswapped = swap_bits_u64(swapped); - - assert_eq!(a, unswapped); - } -} - impl Point { pub fn read( reader: R, diff --git a/src/lib.rs b/src/lib.rs index 0f5fded..a053dd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,3 +15,4 @@ pub mod circuit; pub mod pedersen_hash; pub mod primitives; pub mod constants; +mod util; diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index bb1298b..d631eea 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -28,6 +28,8 @@ use jubjub::{ use blake2_rfc::blake2s::Blake2s; +use util::swap_bits_u64; + #[derive(Clone)] pub struct ValueCommitment { pub value: u64, @@ -193,8 +195,13 @@ impl Note { // Calculate the note contents, as bytes let mut note_contents = vec![]; - // Write the value in big endian - (&mut note_contents).write_u64::(self.value).unwrap(); + // Write the value in little-endian bit order + // swapping the bits to ensure the order is + // correct (LittleEndian byte order would + // be incorrect here.) + (&mut note_contents).write_u64::( + swap_bits_u64(self.value) + ).unwrap(); // Write g_d self.g_d.write(&mut note_contents).unwrap(); diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..5291fef --- /dev/null +++ b/src/util.rs @@ -0,0 +1,27 @@ +pub fn swap_bits_u64(x: u64) -> u64 +{ + let mut tmp = 0; + for i in 0..64 { + tmp |= ((x >> i) & 1) << (63 - i); + } + tmp +} + +#[test] +fn test_swap_bits_u64() { + assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111); + assert_eq!(swap_bits_u64(15135675916470734665), 0b1001001011110010001101010010001110110000100111010011000001001011); + assert_eq!(swap_bits_u64(6724233301461108393), 0b1001010101100000100011100001010111110001011000101000101010111010); + assert_eq!(swap_bits_u64(206708183275952289), 0b1000010100011010001010100011101011111111111110100111101101000000); + assert_eq!(swap_bits_u64(12712751566144824320), 0b0000000000100110010110111000001110001100001000110011011000001101); + + let mut a = 15863238721320035327u64; + for _ in 0..1000 { + a = a.wrapping_mul(a); + + let swapped = swap_bits_u64(a); + let unswapped = swap_bits_u64(swapped); + + assert_eq!(a, unswapped); + } +} From 601e8e38f861a98102b4a81c4fb333dc190eddbf Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Mar 2018 18:06:44 -0600 Subject: [PATCH 130/168] Little-endian byte order interpretation of the output of CRH^ivk. --- src/circuit/sapling/mod.rs | 8 +++++--- src/primitives/mod.rs | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 159381f..4e2ea40 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -221,8 +221,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { constants::CRH_IVK_PERSONALIZATION )?; - // Little endian bit order - ivk.reverse(); + // Swap bit-endianness in each byte + for ivk_byte in ivk.chunks_mut(8) { + ivk_byte.reverse(); + } // drop_5 to ensure it's in the field ivk.truncate(E::Fs::CAPACITY as usize); @@ -621,7 +623,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "ba8b2232a910b00399e90030c87c16a770e6e692fe3b4316675bdd7795df6e50"); + assert_eq!(cs.hash(), "8211d52b5ad2618b2f8106c7c3f9ab213f6206e3ddbbb39e786167de5ea85dc3"); assert_eq!(cs.num_inputs(), 8); assert_eq!(cs.get_input(0, "ONE"), Fr::one()); diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index d631eea..825aed5 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -96,6 +96,9 @@ impl ViewingKey { h.update(&preimage); let mut h = h.finalize().as_ref().to_vec(); + // Reverse the bytes to interpret it in little-endian byte order + h.reverse(); + // Drop the first five bits, so it can be interpreted as a scalar. h[0] &= 0b0000_0111; From 51bb5f0f709422db4b959c173117ce77da098a95 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:53:36 -0600 Subject: [PATCH 131/168] Implement UInt32::shr() for SHA256. --- src/circuit/uint32.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index 4714724..ad2dc84 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -155,6 +155,25 @@ impl UInt32 { } } + 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) + } + } + /// XOR this `UInt32` with another `UInt32` pub fn xor( &self, @@ -483,6 +502,7 @@ mod test { for i in 0..32 { let b = a.rotr(i); + assert_eq!(a.bits.len(), b.bits.len()); assert!(b.value.unwrap() == num); @@ -501,4 +521,24 @@ mod test { 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()); + } + } + } + } } From 4f0a553fbb36802dbe592cd198a69ce6938e98af Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:57:02 -0600 Subject: [PATCH 132/168] Implement UInt32 encoding/decoding with big-endian representation. --- src/circuit/uint32.rs | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index ad2dc84..6bb4847 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -87,6 +87,31 @@ impl UInt32 { }) } + 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.chunks(8) @@ -323,6 +348,37 @@ mod 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]); From abca61401e43239c2b70a9664e3aa176d22a8b88 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:01:15 -0600 Subject: [PATCH 133/168] Implementation of SHA256 choice operation for Boolean. --- src/circuit/boolean.rs | 234 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 22eb9b0..f5d9827 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -523,6 +523,132 @@ impl Boolean { } } } + + /// 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()) + } } impl From for Boolean { @@ -797,6 +923,31 @@ mod test { 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 = [ @@ -1115,4 +1266,87 @@ mod test { 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"); + } + } + } + } + } } From 36a6b5fd90fed1925dd574279a803116644f4730 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:03:18 -0600 Subject: [PATCH 134/168] Implementation of SHA256 majority operation for Boolean. --- src/circuit/boolean.rs | 230 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index f5d9827..08f407e 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -649,6 +649,152 @@ impl Boolean { 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 { @@ -1349,4 +1495,88 @@ mod test { } } } + + #[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"); + } + } + } + } + } } From 75c5269d3b1b1d2d0c2e91e2eaf0ff1670e09b52 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:05:51 -0600 Subject: [PATCH 135/168] Implementation of SHA256 choice/majority for UInt32. --- src/circuit/uint32.rs | 161 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index 6bb4847..e254132 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -199,6 +199,85 @@ impl UInt32 { } } + 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, @@ -597,4 +676,86 @@ mod test { } } } + + #[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; + } + } + } } From ac13cb05bcff634621eb0d9d8b478ee9d2fee5f4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:08:12 -0600 Subject: [PATCH 136/168] Implementation of SHA256. --- Cargo.toml | 1 + src/circuit/mod.rs | 1 + src/circuit/sha256.rs | 417 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + 4 files changed, 422 insertions(+) create mode 100644 src/circuit/sha256.rs diff --git a/Cargo.toml b/Cargo.toml index 4a2aefd..b32997d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9" [dev-dependencies] hex-literal = "0.1" +rust-crypto = "0.2" [features] default = ["u128-support"] diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 4cde222..a82cb44 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -10,6 +10,7 @@ pub mod lookup; pub mod ecc; pub mod pedersen_hash; pub mod multipack; +pub mod sha256; pub mod sapling; diff --git a/src/circuit/sha256.rs b/src/circuit/sha256.rs new file mode 100644 index 0000000..7b55fc8 --- /dev/null +++ b/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/src/lib.rs b/src/lib.rs index a053dd1..44e10c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,9 @@ extern crate byteorder; #[macro_use] extern crate hex_literal; +#[cfg(test)] +extern crate crypto; + pub mod jubjub; pub mod group_hash; pub mod circuit; From 162a3877e5e262aa9fec1dac1ca361dd9305ff38 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 13:10:29 -0600 Subject: [PATCH 137/168] JoinSplit circuit implementation for Sprout. --- src/circuit/mod.rs | 1 + src/circuit/sprout/commitment.rs | 43 ++++ src/circuit/sprout/input.rs | 226 +++++++++++++++++ src/circuit/sprout/mod.rs | 414 +++++++++++++++++++++++++++++++ src/circuit/sprout/output.rs | 54 ++++ src/circuit/sprout/prfs.rs | 79 ++++++ 6 files changed, 817 insertions(+) create mode 100644 src/circuit/sprout/commitment.rs create mode 100644 src/circuit/sprout/input.rs create mode 100644 src/circuit/sprout/mod.rs create mode 100644 src/circuit/sprout/output.rs create mode 100644 src/circuit/sprout/prfs.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index a82cb44..fe0fe50 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -13,6 +13,7 @@ pub mod multipack; pub mod sha256; pub mod sapling; +pub mod sprout; use bellman::{ SynthesisError diff --git a/src/circuit/sprout/commitment.rs b/src/circuit/sprout/commitment.rs new file mode 100644 index 0000000..be8528e --- /dev/null +++ b/src/circuit/sprout/commitment.rs @@ -0,0 +1,43 @@ +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(rho.iter().cloned()); + image.extend(value.iter().cloned()); + image.extend(rho.iter().cloned()); + image.extend(r.iter().cloned()); + + sha256( + cs, + &image + ) +} diff --git a/src/circuit/sprout/input.rs b/src/circuit/sprout/input.rs new file mode 100644 index 0000000..ce69bc0 --- /dev/null +++ b/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/src/circuit/sprout/mod.rs b/src/circuit/sprout/mod.rs new file mode 100644 index 0000000..5ec9420 --- /dev/null +++ b/src/circuit/sprout/mod.rs @@ -0,0 +1,414 @@ +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 rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + + let rng = &mut XorShiftRng::from_seed([0x3dbe6257, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let mut cs = TestConstraintSystem::::new(); + + let mut inputs = vec![]; + inputs.push( + JSInput { + value: Some(0), + a_sk: Some(SpendingKey(rng.gen())), + rho: Some(UniqueRandomness(rng.gen())), + r: Some(CommitmentRandomness(rng.gen())), + auth_path: [Some(rng.gen()); TREE_DEPTH] + } + ); + inputs.push( + JSInput { + value: Some(0), + a_sk: Some(SpendingKey(rng.gen())), + rho: Some(UniqueRandomness(rng.gen())), + r: Some(CommitmentRandomness(rng.gen())), + auth_path: [Some(rng.gen()); TREE_DEPTH] + } + ); + let mut outputs = vec![]; + outputs.push( + JSOutput { + value: Some(50), + a_pk: Some(PayingKey(rng.gen())), + r: Some(CommitmentRandomness(rng.gen())) + } + ); + outputs.push( + JSOutput { + value: Some(50), + a_pk: Some(PayingKey(rng.gen())), + r: Some(CommitmentRandomness(rng.gen())) + } + ); + + let js = JoinSplit { + vpub_old: Some(100), + vpub_new: Some(0), + h_sig: Some(rng.gen()), + phi: Some(rng.gen()), + inputs: inputs, + outputs: outputs, + rt: Some(rng.gen()) + }; + + js.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 2091901); + assert_eq!(cs.num_inputs(), 10); + assert_eq!(cs.hash(), "9d2d7bcffe5bd838009493ecf57a0401dc9b57e71d016e83744c2e1d32dc00c3"); +} diff --git a/src/circuit/sprout/output.rs b/src/circuit/sprout/output.rs new file mode 100644 index 0000000..9cdbf52 --- /dev/null +++ b/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/src/circuit/sprout/prfs.rs b/src/circuit/sprout/prfs.rs new file mode 100644 index 0000000..fff8648 --- /dev/null +++ b/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) +} From 4de908b1e5483c54a5c1c9e5433a52321343b926 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Mar 2018 23:45:13 -0600 Subject: [PATCH 138/168] Fix bug in circuit uncovered by test vector. --- src/circuit/sprout/commitment.rs | 1 - src/circuit/sprout/mod.rs | 152 +++++++++++++++++++++++-------- 2 files changed, 113 insertions(+), 40 deletions(-) diff --git a/src/circuit/sprout/commitment.rs b/src/circuit/sprout/commitment.rs index be8528e..a32f05c 100644 --- a/src/circuit/sprout/commitment.rs +++ b/src/circuit/sprout/commitment.rs @@ -31,7 +31,6 @@ pub fn note_comm( image.push(Boolean::constant(false)); image.push(Boolean::constant(false)); image.extend(a_pk.iter().cloned()); - image.extend(rho.iter().cloned()); image.extend(value.iter().cloned()); image.extend(rho.iter().cloned()); image.extend(r.iter().cloned()); diff --git a/src/circuit/sprout/mod.rs b/src/circuit/sprout/mod.rs index 5ec9420..3ae8a17 100644 --- a/src/circuit/sprout/mod.rs +++ b/src/circuit/sprout/mod.rs @@ -354,61 +354,135 @@ fn witness_u252( #[test] fn test_sprout_constraints() { use pairing::bls12_381::{Bls12}; - use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; - let rng = &mut XorShiftRng::from_seed([0x3dbe6257, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; + let mut cs = TestConstraintSystem::::new(); + let test_vector = hex!("0da71d04d1a5fa649239c73de9c516d2f54958f0f92118113671633872346a31d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd2591e6b61f9f84d6f70fc34838a5a898df04c9a13f1fbfc2b1433de718046efd7d61d20c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b51120c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f502008f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe20925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d5212058a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a120f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd1620bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a20671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff200323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a205145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a2011aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd05820bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd70920bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d20507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4205dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022207333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c152089a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c123320c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e201ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c002268220a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298208c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e2040460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f203f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325200109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e312026b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab203f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f9220dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c20da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d82000000000000000000000000000000000000000000000000000000000000000000000000000000000788445f3120af7846f3625a24ce18250905d9007150e276795271f2572293b20000000000000000082dbed5b3b4048851fb78feb4601ff03be53d6a4b93e97599f4fb6a23db710c41d7193cecb40f4601fc2214b14e0435646a61f0ad1eced90d69911c29850661c074dfe1a712c29b36441a71969c34c7ef1d1c6ec89dfe3c7ceb647c4a2c891061d20c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b51120c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f502008f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe20925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d5212058a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a120f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd1620bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a20671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff200323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a205145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a2011aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd05820bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd70920bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d20507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4205dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022207333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c152089a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c123320c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e201ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c002268220a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298208c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e2040460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f203f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325200109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e312026b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab203f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f9220dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c20da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d8200000000000000000000000000000000000000000000000000000000000000000000000000000000074a32261e497b4a90a786b3c86593d1538d1cebfca0c558c1f00a15cd1726df000000000000000002ce6a5a2b90f0d84f0abefe276a9306b30e4efc3cc4c1a3d2461d5fe5aef48491e1a35d1e0c1b8bd124fec233b0e5e6446b33b998b5c055161239e740459581606241488b3b9a0cc5ce17f7c08a2862b3cb64328de743eef32215210c57d657f037ae0cb7390166b0b327db98b06eb8b018a948a952e9a2b1772552f60738be200000000000000004f04a021600858d805c9ec555dbfe37eb7680bec562848e346495989e99617a691bff31ffb4ae2009e3106a8e23e5ed365b6aa05f27ce43c709dc9977a71678e3853150ec9e5782948eae5cd6c6b92f9e52e9e7f340dbf9ed50a4ea91456150a00000000000000006669719d78ac8126c22afb145d1bc7bd4c35fcb0363ffd1cd42e01a67abb4339f84507f6918b18c984574a9c4ee5a0fa192fb64a26054dfec97a487ee3a0b1720000000000000000000000000000000026c601601d75c28aaa786fb4f3b97fdb21f2d6fd5ff7a1ddf698f262f9be2e98d0184d08ee099711504094a639f4e65fd4c11e003b6ef9547465cec7452ad06435999ba8a4eb6018747ed1cac7e6dbda8260d4d4467572a7e5440d4337eeb0d994c1aca55e1dfe0b4dd7664ee652c6c4ffbdf9f284f3932fdc89b1aef23559fe4fa50b383248c24e50b8193406a97e6d709a6a9d076656b77b000b3ef178084da307423830ffe8afffdf18656cf5ebc197c5e140a952bebe8d52fbb5474fb30a"); + 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 + } + + 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![]; - inputs.push( - JSInput { - value: Some(0), - a_sk: Some(SpendingKey(rng.gen())), - rho: Some(UniqueRandomness(rng.gen())), - r: Some(CommitmentRandomness(rng.gen())), - auth_path: [Some(rng.gen()); TREE_DEPTH] + 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)); } - ); - inputs.push( - JSInput { - value: Some(0), - a_sk: Some(SpendingKey(rng.gen())), - rho: Some(UniqueRandomness(rng.gen())), - r: Some(CommitmentRandomness(rng.gen())), - auth_path: [Some(rng.gen()); TREE_DEPTH] + let mut position = test_vector.read_u64::().unwrap(); + for i in (0..TREE_DEPTH).rev() { + 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![]; - outputs.push( - JSOutput { - value: Some(50), - a_pk: Some(PayingKey(rng.gen())), - r: Some(CommitmentRandomness(rng.gen())) - } - ); - outputs.push( - JSOutput { - value: Some(50), - a_pk: Some(PayingKey(rng.gen())), - r: Some(CommitmentRandomness(rng.gen())) - } - ); + + 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); + + assert_eq!(test_vector.len(), 0); let js = JoinSplit { - vpub_old: Some(100), - vpub_new: Some(0), - h_sig: Some(rng.gen()), - phi: Some(rng.gen()), + vpub_old: vpub_old, + vpub_new: vpub_new, + h_sig: h_sig, + phi: phi, inputs: inputs, outputs: outputs, - rt: Some(rng.gen()) + 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(), 2091901); + assert_eq!(cs.num_constraints(), 1989085); assert_eq!(cs.num_inputs(), 10); - assert_eq!(cs.hash(), "9d2d7bcffe5bd838009493ecf57a0401dc9b57e71d016e83744c2e1d32dc00c3"); + 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)); } From d715e812bd0446186af0442ac10d25687f76da5d Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 24 Mar 2018 00:40:16 +0100 Subject: [PATCH 139/168] Un-disable benchmark --- examples/{bench.rs.disabled => bench.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{bench.rs.disabled => bench.rs} (100%) diff --git a/examples/bench.rs.disabled b/examples/bench.rs similarity index 100% rename from examples/bench.rs.disabled rename to examples/bench.rs From def5de3f1607718d2d608abe9d0c959f66ca3e19 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 25 Mar 2018 23:09:11 -0600 Subject: [PATCH 140/168] Allow the authentication path to be not enforced for zero-value notes, for indistinguishability. --- src/circuit/sapling/mod.rs | 130 +++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 43 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 4e2ea40..ef4ef3f 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -1,6 +1,7 @@ use pairing::{ PrimeField, PrimeFieldRepr, + Field, }; use bellman::{ @@ -51,7 +52,11 @@ pub struct Spend<'a, E: JubjubEngine> { pub ar: Option, /// The authentication path of the commitment in the tree - pub auth_path: Vec> + 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. @@ -265,13 +270,32 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // value (in big endian) followed by g_d and pk_d 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 - )?); + // 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( @@ -379,8 +403,30 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?.get_x().clone(); // Injective encoding } - // Expose the anchor - cur.inputize(cs.namespace(|| "anchor"))?; + { + 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 @@ -607,35 +653,8 @@ fn test_input_circuit_with_bls12_381() { let ar: fs::Fs = rng.gen(); { - 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() - }; - - instance.synthesize(&mut cs).unwrap(); - - assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "8211d52b5ad2618b2f8106c7c3f9ab213f6206e3ddbbb39e786167de5ea85dc3"); - - assert_eq!(cs.num_inputs(), 8); - assert_eq!(cs.get_input(0, "ONE"), Fr::one()); - let rk = viewing_key.rk(ar, params).into_xy(); - assert_eq!(cs.get_input(1, "rk/x/input variable"), rk.0); - assert_eq!(cs.get_input(2, "rk/y/input variable"), rk.1); - let expected_value_cm = value_commitment.cm(params).into_xy(); - 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); - let note = ::primitives::Note { value: value_commitment.value, g_d: g_d.clone(), @@ -644,11 +663,10 @@ fn test_input_circuit_with_bls12_381() { }; let mut position = 0u64; - let mut cur = note.cm(params); + let cm: Fr = note.cm(params); + let mut cur = cm.clone(); - assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); - - for (i, val) in auth_path.into_iter().enumerate() + for (i, val) in auth_path.clone().into_iter().enumerate() { let (uncle, b) = val.unwrap(); @@ -678,13 +696,39 @@ fn test_input_circuit_with_bls12_381() { } } - assert_eq!(cs.get_input(5, "anchor/input variable"), cur); - let expected_nf = note.nf(&viewing_key, position, params); let expected_nf = multipack::bytes_to_bits(&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(), "aedc6d7646e8e019db327bf256c322e54bc72aa9ac4e86943899557eb96507f3"); + + 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]); } From cfd378685f7df8054bf9e2ece457f05975e29d6a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 26 Mar 2018 21:47:40 -0600 Subject: [PATCH 141/168] Add more test vectors to Sprout circuit implementation. --- src/circuit/sprout/mod.rs | 210 ++++++++++++++-------------- src/circuit/sprout/test_vectors.dat | Bin 0 -> 10864 bytes 2 files changed, 105 insertions(+), 105 deletions(-) create mode 100644 src/circuit/sprout/test_vectors.dat diff --git a/src/circuit/sprout/mod.rs b/src/circuit/sprout/mod.rs index 3ae8a17..586de8c 100644 --- a/src/circuit/sprout/mod.rs +++ b/src/circuit/sprout/mod.rs @@ -358,9 +358,7 @@ fn test_sprout_constraints() { use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian}; - let mut cs = TestConstraintSystem::::new(); - - let test_vector = hex!("0da71d04d1a5fa649239c73de9c516d2f54958f0f92118113671633872346a31d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd2591e6b61f9f84d6f70fc34838a5a898df04c9a13f1fbfc2b1433de718046efd7d61d20c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b51120c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f502008f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe20925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d5212058a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a120f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd1620bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a20671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff200323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a205145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a2011aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd05820bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd70920bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d20507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4205dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022207333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c152089a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c123320c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e201ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c002268220a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298208c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e2040460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f203f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325200109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e312026b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab203f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f9220dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c20da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d82000000000000000000000000000000000000000000000000000000000000000000000000000000000788445f3120af7846f3625a24ce18250905d9007150e276795271f2572293b20000000000000000082dbed5b3b4048851fb78feb4601ff03be53d6a4b93e97599f4fb6a23db710c41d7193cecb40f4601fc2214b14e0435646a61f0ad1eced90d69911c29850661c074dfe1a712c29b36441a71969c34c7ef1d1c6ec89dfe3c7ceb647c4a2c891061d20c0db2a74998c50eb7ba6534f6d410efc27c4bb88acb0222c7906ea28a327b51120c92b32db42f42e2bf0a59df9055be5c669d3242df45357659b75ae2c27a76f502008f279618616bcdd4eadc9c7a9062691a59b43b07e2c1e237f17bd189cd6a8fe20925e6d474a5d8d3004f29da0dd78d30ae3824ce79dfe4934bb29ec3afaf3d5212058a2753dade103cecbcda50b5ebfce31e12d41d5841dcc95620f7b3d50a1b9a120f30cc836b9f71b4e7ee3c72b1fd253268af9a27e9d7291a23d02821b21ddfd1620bb23a9bba56de57cb284b0d2b01c642cf79c9a5563f0067a21292412145bd78a20671546e26b1da1af754531e26d8a6a51073a57ddd72dc472efb43fcb257cffff200323f2850bf3444f4b4c5c09a6057ec7169190f45acb9e46984ab3dfcec4f06a205145b1b055c2df02b95675e3797b91de1b846d25003c0a803d08900728f2cd6a2011aa0b4ad29b13b057a31619d6500d636cd735cdd07d811ea265ec4bcbbbd05820bab5800972a16c2c22530c66066d0a5867e987bed21a6d5a450b683cf1cfd70920bdcdb3293188c9807d808267018684cfece07ac35a42c00f2c79b4003825305d20507e0dae81cbfbe457fd370ef1ca4201c2b6401083ddab440e4a038dc1e358c4205dad844ab9466b70f745137195ca221b48f346abd145fb5efc23a8b4ba508022207333dbffbd11f09247a2b33a013ec4c4342029d851e22ba485d4461851370c152089a434ae1febd7687eceea21d07f20a2512449d08ce2eee55871cdb9d46c123320c22d8f0b5e4056e5f318ba22091cc07db5694fbeb5e87ef0d7e2c57ca352359e201ddddabc2caa2de9eff9e18c8c5a39406d7936e889bc16cfabb144f5c002268220a083450c1ba2a3a7be76fad9d13bc37be4bf83bd3e59fc375a36ba62dc620298208c085674249b43da1b9a31a0e820e81e75f342807b03b6b9e64983217bc2b38e2040460fa6bc692a06f47521a6725a547c028a6a240d8409f165e63cb54da2d23f203f909b8ce3d7ffd8a5b30908f605a03b0db85169558ddc1da7bbbcc9b09fd325200109ecc0722659ff83450b8f7b8846e67b2859f33c30d9b7acd5bf39cae54e312026b0052694fc42fdff93e6fb5a71d38c3dd7dc5b6ad710eb048c660233137fab203f0a406181105968fdaee30679e3273c66b72bf9a7f5debbf3b5a0a26e359f9220dc766fab492ccf3d1e49d4f374b5235fa56506aac2224d39f943fcd49202974c20da5698be17b9b46962335799779fbeca8ce5d491c0d26243bafef9ea1837a9d8200000000000000000000000000000000000000000000000000000000000000000000000000000000074a32261e497b4a90a786b3c86593d1538d1cebfca0c558c1f00a15cd1726df000000000000000002ce6a5a2b90f0d84f0abefe276a9306b30e4efc3cc4c1a3d2461d5fe5aef48491e1a35d1e0c1b8bd124fec233b0e5e6446b33b998b5c055161239e740459581606241488b3b9a0cc5ce17f7c08a2862b3cb64328de743eef32215210c57d657f037ae0cb7390166b0b327db98b06eb8b018a948a952e9a2b1772552f60738be200000000000000004f04a021600858d805c9ec555dbfe37eb7680bec562848e346495989e99617a691bff31ffb4ae2009e3106a8e23e5ed365b6aa05f27ce43c709dc9977a71678e3853150ec9e5782948eae5cd6c6b92f9e52e9e7f340dbf9ed50a4ea91456150a00000000000000006669719d78ac8126c22afb145d1bc7bd4c35fcb0363ffd1cd42e01a67abb4339f84507f6918b18c984574a9c4ee5a0fa192fb64a26054dfec97a487ee3a0b1720000000000000000000000000000000026c601601d75c28aaa786fb4f3b97fdb21f2d6fd5ff7a1ddf698f262f9be2e98d0184d08ee099711504094a639f4e65fd4c11e003b6ef9547465cec7452ad06435999ba8a4eb6018747ed1cac7e6dbda8260d4d4467572a7e5440d4337eeb0d994c1aca55e1dfe0b4dd7664ee652c6c4ffbdf9f284f3932fdc89b1aef23559fe4fa50b383248c24e50b8193406a97e6d709a6a9d076656b77b000b3ef178084da307423830ffe8afffdf18656cf5ebc197c5e140a952bebe8d52fbb5474fb30a"); + let test_vector = include_bytes!("test_vectors.dat"); let mut test_vector = &test_vector[..]; fn get_u256(mut reader: R) -> [u8; 32] { @@ -373,116 +371,118 @@ fn test_sprout_constraints() { result } - 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)); + while test_vector.len() != 0 { + let mut cs = TestConstraintSystem::::new(); - let mut inputs = vec![]; - for _ in 0..2 { - test_vector.read_u8().unwrap(); + 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 auth_path = [None; TREE_DEPTH]; - for i in (0..TREE_DEPTH).rev() { + let mut inputs = vec![]; + for _ in 0..2 { test_vector.read_u8().unwrap(); - let sibling = get_u256(&mut test_vector); + let mut auth_path = [None; TREE_DEPTH]; + for i in (0..TREE_DEPTH).rev() { + test_vector.read_u8().unwrap(); - auth_path[i] = Some((sibling, false)); - } - let mut position = test_vector.read_u64::().unwrap(); - for i in (0..TREE_DEPTH).rev() { - auth_path[i].as_mut().map(|p| { - p.1 = (position & 1) == 1 - }); + let sibling = get_u256(&mut test_vector); - position >>= 1; + 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 + } + ); } - // 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))); + let mut outputs = vec![]; - inputs.push( - JSInput { - value: value, - a_sk: a_sk, - rho: rho, - r: r, - auth_path: auth_path - } - ); + 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)); } - - 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); - - assert_eq!(test_vector.len(), 0); - - 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/src/circuit/sprout/test_vectors.dat b/src/circuit/sprout/test_vectors.dat new file mode 100644 index 0000000000000000000000000000000000000000..1316955771eb17e9a3e11352f1252b6591c151da GIT binary patch literal 10864 zcmeI%S2UdK{s-_;MrRO=M946rLJz}0ZS_NQuyIr$(oo}1j?QxA=XgM6P2xnMZ?hKGGJeg;P?^$J zdV6G=9MX)b;uQ|>_xJG+v>^x$9{KUrv(r$a4aDzJ4UoJkWCGFlr7jB?{I!hwEkUzA zpg_=3D@PkNSFA*%K@`=#q~8TGDGk=BS8;f9sC?z=n*k0MwOy6sG38&Tr_jRc25o;1 zh>OVVc{APCE7j~ODFWH9E>Q^YN8}=3ZU|O|87V=<2zik!T6W|6w5{*(I{T;}Bpb=8 zJ{q~WiPL|IsmD6fi$FRAVkk}J^l-b(4IJdx+k9&55G^&UzFX6a-QGDmV(AdpxtS*Z zB7wD^#|x2>hIi$hJj7RDG%<}7S19eb5po4WGJ}+Cf~MNk*?9o9*03-u_=*D)8`-as>sO>fSLEGKOpUNqdv11G&c2@ zXH49L5Rm_9LvtCN2Psp=c!jI1c<~tQN(+6eE17J)JAk_{4B;vVa=kuP_)%iZtt4UmYJR;p6iQOLzf0eCFMg5fVzAV92c5&uYrAwBeLMa=eS$sw@s!| ziTXKb$BkjvMV7;DdIC;fV8${+xr@4gyG2nW1C0DPTMQ1c=MkdYVWBOdZ2cNkHk(QU z)QK84(O_;YOOE!Y=bJ;=jSL20N%+p|{(XKBcIIQq+%HsyRdG-0T*vB`uYpfShE<_k z^|t!+x-G=T92J85Z^Ny7?dHliaQ#%c+&t z&=Z!_FNksg+t@=HOQlnRi*qja>(gJ|ZYOXMocI^MfIrK#qk<7U6wqgfPS2x^F|;oB z`(6d@BuA20(i}6=dBxKUczV6Y6Qq8N8nR$zm2BB&!o$K&)R|ixX?BW|Wc40!exyn4 zE1MU!t9LouSFp+o{WSr6v)qjNM!p>wLiQOyKNxPP8v7318#0#Nzk5<< zaMMwm<_6Rq5*oW;kh>&!1nS4kPx z?{>hUt3<{+HT#|jOZP~L9v7DoD@=>MeDfolAhpoBru5aYJC9w3Cj+0EmE~{hX;2oC z0rmoJ#FPYy!aG^3mqZiKtoENd-Uj8OM$YNCW@EwdTQ>|}T=%;lYMEC|%$F5PFNseR ze460X5W=Wb#7a9(1WSEwFdUC3% ziWS1tPGqMQ-a>r}-Ib}M39>IA3h%SE5Sx5SG*vhp!S}|tRvS>Q81W_l% zZNNh^e>&oYa4A(slVl*1*4w@J((h;Vnvpf+xdqlV9U0&?!E1uoNRykD zUDJ-)I+Sx=^;Iz-_7T!s$h`gNhi6Yxz`{-QO-hl529-ASHsM&2L;y$Fs!zfl`)hyB zK@4F32=HyfT3(GL@q|Xv6|Pu>Idz=&_sJJOW~I;3WB>YswZ}HvWk8wJ2)P~ZDe6#V zI$@b1jx|zvd-HB`YbNDo_YnO1fnyjVvOU<1vvOjbedx5|-;^Dfr}vCp20G1c*k(G^ zY?GWFe+V8pk{zK*i)H$MyJLzD^5d$FH#ll7XzXEXHh-Q&eDV4K0(~n&UBNG5E$^8l zc)wD)EYErt)D^eC4zS2H>QH>$7|aUse>?ta(Zatl(bdmY$nhgnEC9!Zsx0RzH2(gs zTC`G&kA-6LrtaoQg=ih@xolQMa()3{UJ##QYodGIf z$CZ_Bwp#02-x^psm$qi9_VSoj>O5SQr&HDY+Z&<+SLem{y_EgF!;)X-Xz_CHVwfyb z;ilzcjl}D7H;JJk0>Dl>ql&0soK6UoIxF4KkZ-_Bj8>))j~VR}H;J}|JHh}~(S{7sJ4V+ok2_nZdZ%aU92@KzuP zUK6|~cuoFSHMy+I5dB3>{;sZenI;Mdix$_(bn}L2O&aPu*c)X#9xZ(Y-}|~NgLW)$ z)J9*-f#`{pmUa}&f1D=62d{8j_pwBO=;?Ca@Pa|!I?gv&2OY-r+=lf6?(+-Wq27bX z`E_bf?E5tsBt<2A?*|Z=U8d*x|1BcGa)ibTG*nlg+ka0Y!GS!+v_w@3N7ryl87x~` zGf{u<7yv-7exa@5EeMqn;~RomCXSMXpf-fF6HG$AM|+DdO6kSi@U-w6MAxctD!sb6 zuM5D3P!Q)SLHwmaY%u^7>sNPM`yN|&{P#FX=BU92O|aO{9UEOQ#t-w|NzlB9wthVK zdKlYVB2osfg8D$)WNcHnL&&)Jj1VPr1V+KMOhRNIltHn{YX=9xS5Twg-2)E*-w*GQ zMyIs#xG6o1p912T4&Jz8$Ab1c*TfuOha9Ggk*j9U#T5>s35W!Aw2yy|k8WuBDy~$% z?{w@xIgl-({&;J0O0SRqBmn(^3p{88qzNqQdbF`Fd4!()Y^mt#!J}hi0{<3#Pb2S| z_M&{u^o*h9)qEsxQheHn(eSdAJ(G*g+$Dh|!&>37q_ z7OjB?md(oK>AzNWyS__W!lwy7P4H>*|C1&rRA%PUyvmvjAz-ldqz}8CMx4<*HHNOz zM;62!7{mI@|LI?*%f&bNe@by8#yIUwSu;9>g;ZCrfwf;}ICR!&dVix7If;?f8m_`? zg4YDE$^WV*mvtH7U+kuTS692Nn~tEVJyJWN3{M5**u7nycG$b3GK(aGJ~LQIk0H1O z9WJ__>vLN&hJYd7$PS$R_RDW{NR{F2)Wg(_k|DRUWH>cQN25)4v(lh|iYT`2_AL1Q zH8`wMv&n&$>)ZoJAoavXrOQ36E;bP;xM?xeP37_&hh2>wVuZR+I8#wEQAGG(+yl;8 zi;!K_cp^xv$Bz|XD_2C{A3H0tPPiqNz0(+*f+fLGXVP{%w__C;p|K03RELG|@aOY) zGN~nH>Y1SRq!;J>xf25dTqL%J6-AU9gK`AZC zA%i&4QYQ@#4Q?d}O}FU!dCiMj&t1QLk<&hGp`vkhunt!~^j76b5&B$&+-3G2=#Rx? ztNLXBcvr5;+di^GVchjZ@jlgW+^^R?>VZ}rgw3HR+;#in*EQH%ft@e5zY32wB2DuD E1!z(1KmY&$ literal 0 HcmV?d00001 From 97bead9977916c603d02c439b545a80348166b13 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 27 Mar 2018 16:03:02 +0200 Subject: [PATCH 142/168] Update benchmark to use final Sapling circuit implementation --- examples/bench.rs | 76 ++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/examples/bench.rs b/examples/bench.rs index 04a72f8..d455a9a 100644 --- a/examples/bench.rs +++ b/examples/bench.rs @@ -8,11 +8,15 @@ use sapling_crypto::jubjub::{ JubjubBls12, edwards, fs, - Unknown }; -use sapling_crypto::circuit::{ +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; @@ -27,24 +31,11 @@ fn main() { let groth_params = generate_random_parameters::( Spend { params: jubjub_params, - /// Value of the note being spent - value: None, - /// Randomness that will hide the value - value_randomness: None, - /// Key which allows the proof to be constructed - /// as defense-in-depth against a flaw in the - /// protocol that would otherwise be exploitable - /// by a holder of a viewing key. - rsk: None, - /// The public key that will be re-randomized for - /// use as a nullifier and signing key for the - /// transaction. - ak: None, - /// The diversified base used to compute pk_d. - g_d: None, - /// The randomness used to hide the note commitment data + value_commitment: None, + proof_generation_key: None, + payment_address: None, commitment_randomness: None, - /// The authentication path of the commitment in the tree + ar: None, auth_path: vec![None; TREE_DEPTH] }, rng @@ -54,23 +45,48 @@ fn main() { let mut total_time = Duration::new(0, 0); for _ in 0..SAMPLES { - let value: u64 = 1; - let value_randomness: fs::Fs = rng.gen(); - let ak: edwards::Point = edwards::Point::rand(rng, jubjub_params); - let g_d: edwards::Point = edwards::Point::rand(rng, jubjub_params); + 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 rsk: fs::Fs = rng.gen(); - let auth_path = (0..TREE_DEPTH).map(|_| Some((rng.gen(), rng.gen()))).collect(); + let auth_path = vec![Some((rng.gen(), rng.gen())); TREE_DEPTH]; + let ar: fs::Fs = rng.gen(); let start = Instant::now(); let _ = create_random_proof(Spend { params: jubjub_params, - value: Some(value), - value_randomness: Some(value_randomness), - ak: Some(ak), - g_d: Some(g_d), + value_commitment: Some(value_commitment), + proof_generation_key: Some(proof_generation_key), + payment_address: Some(payment_address), commitment_randomness: Some(commitment_randomness), - rsk: Some(rsk), + ar: Some(ar), auth_path: auth_path }, &groth_params, rng).unwrap(); total_time += start.elapsed(); From 9418001d0e92f5961f451ed020de4393209c10cb Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 27 Mar 2018 14:30:08 -0600 Subject: [PATCH 143/168] First block of GH invocation using random beacon output. --- src/constants.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.rs b/src/constants.rs index fa717c9..dfdd36f 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -3,7 +3,7 @@ /// 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"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; + = b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0"; // BLAKE2s invocation personalizations /// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | nk) From 3704b2422e911377c4e5ec0252dbfa197793ff9d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 27 Mar 2018 21:31:32 -0600 Subject: [PATCH 144/168] Fix benchmark due to anchor change in witness. --- examples/bench.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/bench.rs b/examples/bench.rs index d455a9a..4b7a707 100644 --- a/examples/bench.rs +++ b/examples/bench.rs @@ -19,7 +19,7 @@ use sapling_crypto::primitives::{ }; use bellman::groth16::*; use rand::{XorShiftRng, SeedableRng, Rng}; -use pairing::bls12_381::Bls12; +use pairing::bls12_381::{Bls12, Fr}; const TREE_DEPTH: usize = 32; @@ -36,7 +36,8 @@ fn main() { payment_address: None, commitment_randomness: None, ar: None, - auth_path: vec![None; TREE_DEPTH] + auth_path: vec![None; TREE_DEPTH], + anchor: None }, rng ).unwrap(); @@ -78,6 +79,7 @@ fn main() { 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 { @@ -87,7 +89,8 @@ fn main() { payment_address: Some(payment_address), commitment_randomness: Some(commitment_randomness), ar: Some(ar), - auth_path: auth_path + auth_path: auth_path, + anchor: Some(anchor) }, &groth_params, rng).unwrap(); total_time += start.elapsed(); } From 9f24b68b3a79290815e791693393b3b0bd982ca7 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 27 Mar 2018 21:41:11 -0600 Subject: [PATCH 145/168] Update constraint system hashes. --- src/circuit/sapling/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index ef4ef3f..1dbadd9 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -718,7 +718,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 98777); - assert_eq!(cs.hash(), "aedc6d7646e8e019db327bf256c322e54bc72aa9ac4e86943899557eb96507f3"); + assert_eq!(cs.hash(), "499305e409599a3e4fe0a885f6adf674e9f49ba4a21e47362356d2a89f15dc1f"); assert_eq!(cs.get("randomization of note commitment/x3/num"), cm); @@ -795,7 +795,7 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "8db50ff0e14fae19a7d83ef47f6da3a7e3e2644d251e37b387c6408d85df3ae7"); + assert_eq!(cs.hash(), "d18e83255220328a688134038ba4f82d5ce67ffe9f97b2ae2678042da0efad43"); let expected_cm = payment_address.create_note( value_commitment.value, From e554b473dd10885d232f42237c13282f5b6fee43 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 6 Apr 2018 13:29:58 -0600 Subject: [PATCH 146/168] Update to bellman 0.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b32997d..a8a7d13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ features = ["expose-arith"] [dependencies] rand = "0.4" digest = "0.7" -bellman = "0.0.9" +bellman = "0.1" byteorder = "1" [dependencies.blake2-rfc] From 0f230a70b93728fd1d07e117760e5b9334491474 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 15 Apr 2018 15:52:45 -0600 Subject: [PATCH 147/168] Implement uniform sampling of Jubjub scalars Co-authored-by: Sean Bowe --- src/jubjub/fs.rs | 30 +++++++++++++++++++++++++++++- src/jubjub/mod.rs | 6 +++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/jubjub/fs.rs b/src/jubjub/fs.rs index 051978b..eb10e65 100644 --- a/src/jubjub/fs.rs +++ b/src/jubjub/fs.rs @@ -1,7 +1,10 @@ -use pairing::{Field, PrimeField, SqrtField, PrimeFieldRepr, PrimeFieldDecodingError, LegendreSymbol}; +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]); @@ -548,6 +551,31 @@ impl Fs { (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 { diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 3b786c1..1c21192 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -85,12 +85,16 @@ pub enum FixedGenerators { 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; + type Fs: PrimeField + SqrtField + ToUniform; /// The parameters of Jubjub and the Sapling protocol type Params: JubjubParams; } From 916dbce2dff3ae6c9c31a19f529543e5a0389f67 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 15 Apr 2018 15:59:09 -0600 Subject: [PATCH 148/168] Implement RedJubjub Co-authored-by: Sean Bowe --- src/lib.rs | 1 + src/redjubjub.rs | 133 +++++++++++++++++++++++++++++++++++++++++++++++ src/util.rs | 12 +++++ 3 files changed, 146 insertions(+) create mode 100644 src/redjubjub.rs diff --git a/src/lib.rs b/src/lib.rs index 44e10c0..7000292 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,4 +18,5 @@ pub mod circuit; pub mod pedersen_hash; pub mod primitives; pub mod constants; +pub mod redjubjub; mod util; diff --git a/src/redjubjub.rs b/src/redjubjub.rs new file mode 100644 index 0000000..e9158a8 --- /dev/null +++ b/src/redjubjub.rs @@ -0,0 +1,133 @@ +//! Implementation of RedJubjub, a specialization of RedDSA to the Jubjub curve. +//! See section 5.4.6 of the Sapling protocol specification. + +use pairing::Field; +use rand::Rng; + +use jubjub::{FixedGenerators, JubjubEngine, JubjubParams, Unknown, edwards::Point}; +use util::hash_to_scalar; + +fn h_star(a: &[u8], b: &[u8]) -> E::Fs { + hash_to_scalar::(b"Zcash_RedJubjubH", a, b) +} + +pub struct Signature { + r: Point, + s: E::Fs, +} + +pub struct PrivateKey(E::Fs); + +pub struct PublicKey(Point); + +impl PrivateKey { + pub fn randomize(&self, alpha: E::Fs) -> Self { + let mut tmp = self.0; + tmp.add_assign(&alpha); + PrivateKey(tmp) + } + + pub fn sign(&self, msg: &[u8], rng: &mut R, 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 . G + let g_r = params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(r, params); + let mut rbar = [0u8; 32]; + g_r.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); + + Signature { r: g_r.into(), s } + } +} + +impl PublicKey { + pub fn from_private(privkey: &PrivateKey, params: &E::Params) -> Self { + let res = params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(privkey.0, params) + .into(); + PublicKey(res) + } + + pub fn randomize(&self, alpha: E::Fs, params: &E::Params) -> Self { + let res: Point = params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(alpha, params) + .into(); + let res = res.add(&self.0, params); + PublicKey(res) + } + + pub fn verify(&self, msg: &[u8], sig: &Signature, params: &E::Params) -> bool { + // c = H*(Rbar || M) + let mut rbar = [0u8; 32]; + sig.r + .write(&mut rbar[..]) + .expect("Jubjub points should serialize to 32 bytes"); + let c = h_star::(&rbar[..], msg); + + // S . G = R + c . vk + self.0.mul(c, params).add(&sig.r, params) + == params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(sig.s, params) + .into() + } +} + +#[cfg(test)] +mod tests { + use pairing::bls12_381::Bls12; + use rand::thread_rng; + + use jubjub::JubjubBls12; + + use super::*; + + #[test] + fn random_signatures() { + let rng = &mut thread_rng(); + let params = &JubjubBls12::new(); + + for _ in 0..1000 { + let sk = PrivateKey::(rng.gen()); + let vk = PublicKey::from_private(&sk, params); + + let msg1 = b"Foo bar"; + let msg2 = b"Spam eggs"; + + let sig1 = sk.sign(msg1, rng, params); + let sig2 = sk.sign(msg2, rng, params); + + assert!(vk.verify(msg1, &sig1, params)); + assert!(vk.verify(msg2, &sig2, params)); + assert!(!vk.verify(msg1, &sig2, params)); + assert!(!vk.verify(msg2, &sig1, params)); + + let alpha = rng.gen(); + let rsk = sk.randomize(alpha); + let rvk = vk.randomize(alpha, params); + + let sig1 = rsk.sign(msg1, rng, params); + let sig2 = rsk.sign(msg2, rng, params); + + assert!(rvk.verify(msg1, &sig1, params)); + assert!(rvk.verify(msg2, &sig2, params)); + assert!(!rvk.verify(msg1, &sig2, params)); + assert!(!rvk.verify(msg2, &sig1, params)); + } + } +} diff --git a/src/util.rs b/src/util.rs index 5291fef..b5cc4e8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,7 @@ +use blake2_rfc::blake2b::Blake2b; + +use jubjub::{JubjubEngine, ToUniform}; + pub fn swap_bits_u64(x: u64) -> u64 { let mut tmp = 0; @@ -7,6 +11,14 @@ pub fn swap_bits_u64(x: u64) -> u64 tmp } +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()) +} + #[test] fn test_swap_bits_u64() { assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111); From 45e4ca38e49fcdcc3a12eb7532ee4270b0e4b932 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 18 Apr 2018 23:59:22 +0100 Subject: [PATCH 149/168] Match additive notation with variable name --- src/redjubjub.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index e9158a8..757ad9c 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -37,11 +37,11 @@ impl PrivateKey { let r = h_star::(&t[..], msg); // R = r . G - let g_r = params + let r_g = params .generator(FixedGenerators::SpendingKeyGenerator) .mul(r, params); let mut rbar = [0u8; 32]; - g_r.write(&mut rbar[..]) + r_g.write(&mut rbar[..]) .expect("Jubjub points should serialize to 32 bytes"); // S = r + H*(Rbar || M) . sk @@ -49,7 +49,7 @@ impl PrivateKey { s.mul_assign(&self.0); s.add_assign(&r); - Signature { r: g_r.into(), s } + Signature { r: r_g.into(), s } } } From 840c23bd238a25e36b059ebe40adfe6397be3544 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 18 Apr 2018 23:59:48 +0100 Subject: [PATCH 150/168] Specify pre-conditions for signature validation --- src/redjubjub.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index 757ad9c..f8c13d4 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -71,6 +71,10 @@ impl PublicKey { PublicKey(res) } + // Pre-conditions: + // - rbar was the canonical representation of a point on the curve. + // - sig.s < order(G) + // TODO(str4d): Enforce these during deserialization of Signature pub fn verify(&self, msg: &[u8], sig: &Signature, params: &E::Params) -> bool { // c = H*(Rbar || M) let mut rbar = [0u8; 32]; From f54feda94fb6ea9d75b154e4770c9a6fcf131cf7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 26 Apr 2018 12:30:08 +0100 Subject: [PATCH 151/168] Fix comment h/t omershlo from Zcash Community Chat for spotting it! --- src/circuit/sapling/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 1dbadd9..dbfc4ce 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -448,7 +448,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; } - // Let's compute nf = BLAKE2s(rk || rho) + // Let's compute nf = BLAKE2s(nk || rho) nf_preimage.extend( rho.repr(cs.namespace(|| "representation of rho"))? ); From 4eab1fc68ab6a6feb2d4cbb6d6d1720ba2df2755 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 26 Apr 2018 22:20:08 +0100 Subject: [PATCH 152/168] Implement RedJubjub serialization Also alters the Signature struct to store Rbar and Sbar instead of R and S, to more closely match the specification. --- src/redjubjub.rs | 143 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 123 insertions(+), 20 deletions(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index f8c13d4..a0e6ff9 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -1,25 +1,73 @@ //! Implementation of RedJubjub, a specialization of RedDSA to the Jubjub curve. //! See section 5.4.6 of the Sapling protocol specification. -use pairing::Field; +use pairing::{Field, PrimeField, PrimeFieldRepr}; use rand::Rng; +use std::io::{self, Read, Write}; use jubjub::{FixedGenerators, JubjubEngine, JubjubParams, Unknown, edwards::Point}; -use util::hash_to_scalar; +use util::{hash_to_scalar, swap_bits_u64}; + +fn read_scalar(reader: R) -> io::Result { + let mut s_repr = ::Repr::default(); + + // This reads in big-endian, so we perform a swap of the + // limbs in the representation and swap the bit order. + s_repr.read_be(reader)?; + s_repr.as_mut().reverse(); + for b in s_repr.as_mut() { + *b = swap_bits_u64(*b); + } + + 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<()> { + let mut s_repr = s.into_repr(); + + // This writes in big-endian, so we perform a swap of the + // limbs in the representation and swap the bit order. + s_repr.as_mut().reverse(); + for b in s_repr.as_mut() { + *b = swap_bits_u64(*b); + } + s_repr.write_be(writer) +} fn h_star(a: &[u8], b: &[u8]) -> E::Fs { hash_to_scalar::(b"Zcash_RedJubjubH", a, b) } -pub struct Signature { - r: Point, - s: E::Fs, +pub struct Signature { + rbar: [u8; 32], + sbar: [u8; 32], } pub struct PrivateKey(E::Fs); pub struct PublicKey(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; @@ -27,7 +75,16 @@ impl PrivateKey { PrivateKey(tmp) } - pub fn sign(&self, msg: &[u8], rng: &mut R, params: &E::Params) -> Signature { + 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, params: &E::Params) -> Signature { // T = (l_H + 128) bits of randomness // For H*, l_H = 512 bits let mut t = [0u8; 80]; @@ -48,8 +105,11 @@ impl PrivateKey { 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 { r: r_g.into(), s } + Signature { rbar, sbar } } } @@ -71,23 +131,34 @@ impl PublicKey { PublicKey(res) } - // Pre-conditions: - // - rbar was the canonical representation of a point on the curve. - // - sig.s < order(G) - // TODO(str4d): Enforce these during deserialization of Signature - pub fn verify(&self, msg: &[u8], sig: &Signature, params: &E::Params) -> bool { - // c = H*(Rbar || M) - let mut rbar = [0u8; 32]; - sig.r - .write(&mut rbar[..]) - .expect("Jubjub points should serialize to 32 bytes"); - let c = h_star::(&rbar[..], msg); + 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, params: &E::Params) -> bool { + // c = H*(Rbar || M) + let c = h_star::(&sig.rbar[..], msg); + + let r = match Point::read(&sig.rbar[..], params) { + Ok(r) => r, + Err(_) => return false, + }; + let s = match read_scalar::(&sig.sbar[..]) { + Ok(s) => s, + Err(_) => return false, + }; + // S < order(G) + s.into_repr() < E::Fs::char() && // S . G = R + c . vk - self.0.mul(c, params).add(&sig.r, params) + self.0.mul(c, params).add(&r, params) == params .generator(FixedGenerators::SpendingKeyGenerator) - .mul(sig.s, params) + .mul(s, params) .into() } } @@ -101,6 +172,38 @@ mod tests { use super::*; + #[test] + fn round_trip_serialization() { + let rng = &mut thread_rng(); + let params = &JubjubBls12::new(); + + for _ in 0..1000 { + let sk = PrivateKey::(rng.gen()); + let vk = PublicKey::from_private(&sk, params); + let msg = b"Foo bar"; + let sig = sk.sign(msg, rng, 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, 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, params)); + assert!(vk_2.verify(msg, &sig, params)); + assert!(vk_2.verify(msg, &sig_2, params)); + } + } + #[test] fn random_signatures() { let rng = &mut thread_rng(); From e94dbf2523876222586a479ff0bfbbc1961e7de7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 26 Apr 2018 22:30:37 +0100 Subject: [PATCH 153/168] Parameterize the generator in RedJubjub Per the specification, the generator is different between BindingSig and SpendAuthSig. --- src/redjubjub.rs | 86 +++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index a0e6ff9..33324df 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -84,7 +84,13 @@ impl PrivateKey { write_scalar::(&self.0, writer) } - pub fn sign(&self, msg: &[u8], rng: &mut R, params: &E::Params) -> Signature { + 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]; @@ -93,10 +99,8 @@ impl PrivateKey { // r = H*(T || M) let r = h_star::(&t[..], msg); - // R = r . G - let r_g = params - .generator(FixedGenerators::SpendingKeyGenerator) - .mul(r, params); + // 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"); @@ -114,19 +118,13 @@ impl PrivateKey { } impl PublicKey { - pub fn from_private(privkey: &PrivateKey, params: &E::Params) -> Self { - let res = params - .generator(FixedGenerators::SpendingKeyGenerator) - .mul(privkey.0, params) - .into(); + 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, params: &E::Params) -> Self { - let res: Point = params - .generator(FixedGenerators::SpendingKeyGenerator) - .mul(alpha, params) - .into(); + 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) } @@ -140,7 +138,13 @@ impl PublicKey { self.0.write(writer) } - pub fn verify(&self, msg: &[u8], sig: &Signature, params: &E::Params) -> bool { + 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); @@ -154,12 +158,8 @@ impl PublicKey { }; // S < order(G) s.into_repr() < E::Fs::char() && - // S . G = R + c . vk - self.0.mul(c, params).add(&r, params) - == params - .generator(FixedGenerators::SpendingKeyGenerator) - .mul(s, params) - .into() + // S . P_G = R + c . vk + self.0.mul(c, params).add(&r, params) == params.generator(p_g).mul(s, params).into() } } @@ -175,13 +175,14 @@ mod tests { #[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, params); + let vk = PublicKey::from_private(&sk, p_g, params); let msg = b"Foo bar"; - let sig = sk.sign(msg, rng, params); + let sig = sk.sign(msg, rng, p_g, params); let mut sk_bytes = [0u8; 32]; let mut vk_bytes = [0u8; 32]; @@ -191,50 +192,51 @@ mod tests { sig.write(&mut sig_bytes[..]).unwrap(); let sk_2 = PrivateKey::::read(&sk_bytes[..]).unwrap(); - let vk_2 = PublicKey::from_private(&sk_2, params); + 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, params)); - assert!(vk_2.verify(msg, &sig, params)); - assert!(vk_2.verify(msg, &sig_2, params)); + 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, params); + 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, params); - let sig2 = sk.sign(msg2, rng, params); + let sig1 = sk.sign(msg1, rng, p_g, params); + let sig2 = sk.sign(msg2, rng, p_g, params); - assert!(vk.verify(msg1, &sig1, params)); - assert!(vk.verify(msg2, &sig2, params)); - assert!(!vk.verify(msg1, &sig2, params)); - assert!(!vk.verify(msg2, &sig1, 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, params); + let rvk = vk.randomize(alpha, p_g, params); - let sig1 = rsk.sign(msg1, rng, params); - let sig2 = rsk.sign(msg2, rng, params); + let sig1 = rsk.sign(msg1, rng, p_g, params); + let sig2 = rsk.sign(msg2, rng, p_g, params); - assert!(rvk.verify(msg1, &sig1, params)); - assert!(rvk.verify(msg2, &sig2, params)); - assert!(!rvk.verify(msg1, &sig2, params)); - assert!(!rvk.verify(msg2, &sig1, 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)); } } } From b27dc2914b71f9d02717ed73fe0f63afd93ef519 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 30 Apr 2018 13:55:41 +0100 Subject: [PATCH 154/168] Remove redundant signature check E::Fs guarantees its representation is in the field, implicitly enforcing that S < order(G). --- src/redjubjub.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index 33324df..0fe30e5 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -148,16 +148,18 @@ impl PublicKey { // 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, }; - // S < order(G) - s.into_repr() < E::Fs::char() && // S . P_G = R + c . vk self.0.mul(c, params).add(&r, params) == params.generator(p_g).mul(s, params).into() } From 5687acfaf83438a993fccc14ab487b67e4afbc68 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 1 May 2018 15:23:13 -0600 Subject: [PATCH 155/168] Make PublicKey inner Point public so that we can use it during zk-SNARK verification. --- src/redjubjub.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index 0fe30e5..5b3d8a5 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -51,7 +51,7 @@ pub struct Signature { pub struct PrivateKey(E::Fs); -pub struct PublicKey(Point); +pub struct PublicKey(pub Point); impl Signature { pub fn read(mut reader: R) -> io::Result { From 36cd38e2392a3a91700c4eca14e946fdff984636 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 7 May 2018 17:47:04 -0600 Subject: [PATCH 156/168] Make util module public. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7000292..27d306c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,4 +19,4 @@ pub mod pedersen_hash; pub mod primitives; pub mod constants; pub mod redjubjub; -mod util; +pub mod util; From 87c62e2248b7fd4e3871c5a22adae8dd6500dbe3 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 16 May 2018 23:30:09 -0600 Subject: [PATCH 157/168] Update to the latest pairing crate version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a8a7d13..79a5ed7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/zcash-hackworks/sapling" version = "0.0.1" [dependencies.pairing] -version = "0.14" +version = "0.14.2" features = ["expose-arith"] [dependencies] From f491e02b56d5df2c1512b0a7439b3e5dd03e0b52 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 17 May 2018 00:05:32 -0600 Subject: [PATCH 158/168] Correctly interpret BLAKE2s inputs and outputs as little endian. --- src/circuit/blake2s.rs | 6 +++--- src/circuit/uint32.rs | 10 ++-------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index a78f3cf..93af806 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -343,7 +343,7 @@ mod test { let mut out = out.into_iter(); for b in expected.into_iter() { - for i in (0..8).rev() { + for i in 0..8 { let c = out.next().unwrap().get_value().unwrap(); assert_eq!(c, (b >> i) & 1u8 == 1u8); @@ -405,7 +405,7 @@ mod test { let mut input_bits = vec![]; for (byte_i, input_byte) in data.into_iter().enumerate() { - for bit_i in (0..8).rev() { + 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()); @@ -417,7 +417,7 @@ mod test { 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)); + .flat_map(|&byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8)); for b in r { match b { diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index e254132..fb0bfa9 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -114,10 +114,7 @@ impl UInt32 { /// Turns this `UInt32` into its little-endian byte order representation. pub fn into_bits(&self) -> Vec { - self.bits.chunks(8) - .flat_map(|v| v.iter().rev()) - .cloned() - .collect() + self.bits.clone() } /// Converts a little-endian byte order representation of bits into a @@ -126,10 +123,7 @@ impl UInt32 { { assert_eq!(bits.len(), 32); - let new_bits = bits.chunks(8) - .flat_map(|v| v.iter().rev()) - .cloned() - .collect::>(); + let new_bits = bits.to_vec(); let mut value = Some(0u32); for b in new_bits.iter().rev() { From 2ff318eecba63a235d8de3fb8f6b435e29235818 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 17 May 2018 00:19:57 -0600 Subject: [PATCH 159/168] Use little endian for everything in Sapling. --- src/circuit/multipack.rs | 7 +++++++ src/circuit/sapling/mod.rs | 11 +++-------- src/group_hash.rs | 25 ++++++------------------- src/jubjub/edwards.rs | 25 ++----------------------- src/jubjub/mod.rs | 13 ++++++++++++- src/primitives/mod.rs | 24 +++++++----------------- src/redjubjub.rs | 21 +++------------------ src/util.rs | 28 ---------------------------- 8 files changed, 40 insertions(+), 114 deletions(-) diff --git a/src/circuit/multipack.rs b/src/circuit/multipack.rs index 04f6260..54d4138 100644 --- a/src/circuit/multipack.rs +++ b/src/circuit/multipack.rs @@ -45,6 +45,13 @@ pub fn bytes_to_bits(bytes: &[u8]) -> Vec .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 diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index dbfc4ce..31bab6b 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -226,11 +226,6 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { constants::CRH_IVK_PERSONALIZATION )?; - // Swap bit-endianness in each byte - for ivk_byte in ivk.chunks_mut(8) { - ivk_byte.reverse(); - } - // drop_5 to ensure it's in the field ivk.truncate(E::Fs::CAPACITY as usize); @@ -697,7 +692,7 @@ fn test_input_circuit_with_bls12_381() { } let expected_nf = note.nf(&viewing_key, position, params); - let expected_nf = multipack::bytes_to_bits(&expected_nf); + let expected_nf = multipack::bytes_to_bits_le(&expected_nf); let expected_nf = multipack::compute_multipacking::(&expected_nf); assert_eq!(expected_nf.len(), 2); @@ -718,7 +713,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 98777); - assert_eq!(cs.hash(), "499305e409599a3e4fe0a885f6adf674e9f49ba4a21e47362356d2a89f15dc1f"); + assert_eq!(cs.hash(), "d37c738e83df5d9b0bb6495ac96abf21bcb2697477e2c15c2c7916ff7a3b6a89"); assert_eq!(cs.get("randomization of note commitment/x3/num"), cm); @@ -795,7 +790,7 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "d18e83255220328a688134038ba4f82d5ce67ffe9f97b2ae2678042da0efad43"); + assert_eq!(cs.hash(), "c26d5cdfe6ccd65c03390902c02e11393ea6bb96aae32a7f2ecb12eb9103faee"); let expected_cm = payment_address.create_note( value_commitment.value, diff --git a/src/group_hash.rs b/src/group_hash.rs index 9e6b2f8..25e65f9 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -5,8 +5,7 @@ use jubjub::{ }; use pairing::{ - PrimeField, - PrimeFieldRepr + PrimeField }; use blake2_rfc::blake2s::Blake2s; @@ -29,20 +28,11 @@ pub fn group_hash( let mut h = Blake2s::with_params(32, &[], &[], personalization); h.update(constants::GH_FIRST_BLOCK); h.update(tag); - let mut h = h.finalize().as_ref().to_vec(); + let h = h.finalize().as_ref().to_vec(); assert!(h.len() == 32); - // Take first/unset first bit of hash - let s = h[0] >> 7 == 1; // get s - h[0] &= 0b0111_1111; // unset s from h - - // cast to prime field representation - let mut y0 = ::Repr::default(); - y0.read_be(&h[..]).expect("hash is sufficiently large"); - - if let Ok(y0) = E::Fr::from_repr(y0) { - if let Some(p) = edwards::Point::::get_for_y(y0, s, params) { - // Enter into the prime order subgroup + match edwards::Point::::read(&h[..], params) { + Ok(p) => { let p = p.mul_by_cofactor(params); if p != edwards::Point::zero() { @@ -50,10 +40,7 @@ pub fn group_hash( } else { None } - } else { - None - } - } else { - None + }, + Err(_) => None } } diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index d1d2772..2137d99 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -26,8 +26,6 @@ use std::io::{ Read }; -use util::swap_bits_u64; - // Represents the affine point (X/Z, Y/Z) via the extended // twisted Edwards coordinates. // @@ -97,21 +95,8 @@ impl Point { params: &E::Params ) -> io::Result { - // Jubjub points are encoded least significant bit first. - // The most significant bit (bit 254) encodes the parity - // of the x-coordinate. - let mut y_repr = ::Repr::default(); - - // This reads in big-endian, so we perform a swap of the - // limbs in the representation and swap the bit order. - y_repr.read_be(reader)?; - - y_repr.as_mut().reverse(); - - for b in y_repr.as_mut() { - *b = swap_bits_u64(*b); - } + y_repr.read_le(reader)?; let x_sign = (y_repr.as_ref()[3] >> 63) == 1; y_repr.as_mut()[3] &= 0x7fffffffffffffff; @@ -216,13 +201,7 @@ impl Point { y_repr.as_mut()[3] |= 0x8000000000000000u64; } - y_repr.as_mut().reverse(); - - for b in y_repr.as_mut() { - *b = swap_bits_u64(*b); - } - - y_repr.write_be(writer) + y_repr.write_le(writer) } /// Convert from a Montgomery point diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 1c21192..80a7986 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -363,7 +363,7 @@ fn test_jubjub_bls12() { tests::test_suite::(¶ms); - let test_repr = hex!("b9481dd1103b7d1f8578078eb429d3c476472f53e88c0eaefdf51334c7c8b98c"); + 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(), @@ -372,4 +372,15 @@ fn test_jubjub_bls12() { ).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/src/primitives/mod.rs b/src/primitives/mod.rs index 825aed5..01e042d 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -14,7 +14,7 @@ use pedersen_hash::{ }; use byteorder::{ - BigEndian, + LittleEndian, WriteBytesExt }; @@ -28,8 +28,6 @@ use jubjub::{ use blake2_rfc::blake2s::Blake2s; -use util::swap_bits_u64; - #[derive(Clone)] pub struct ValueCommitment { pub value: u64, @@ -96,14 +94,11 @@ impl ViewingKey { h.update(&preimage); let mut h = h.finalize().as_ref().to_vec(); - // Reverse the bytes to interpret it in little-endian byte order - h.reverse(); - - // Drop the first five bits, so it can be interpreted as a scalar. - h[0] &= 0b0000_0111; + // 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_be(&h[..]).unwrap(); + e.read_le(&h[..]).unwrap(); E::Fs::from_repr(e).expect("should be a valid scalar") } @@ -198,13 +193,8 @@ impl Note { // Calculate the note contents, as bytes let mut note_contents = vec![]; - // Write the value in little-endian bit order - // swapping the bits to ensure the order is - // correct (LittleEndian byte order would - // be incorrect here.) - (&mut note_contents).write_u64::( - swap_bits_u64(self.value) - ).unwrap(); + // 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(); @@ -219,7 +209,7 @@ impl Note { Personalization::NoteCommitment, note_contents.into_iter() .flat_map(|byte| { - (0..8).rev().map(move |i| ((byte >> i) & 1) == 1) + (0..8).map(move |i| ((byte >> i) & 1) == 1) }), params ); diff --git a/src/redjubjub.rs b/src/redjubjub.rs index 5b3d8a5..0283508 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -6,18 +6,11 @@ use rand::Rng; use std::io::{self, Read, Write}; use jubjub::{FixedGenerators, JubjubEngine, JubjubParams, Unknown, edwards::Point}; -use util::{hash_to_scalar, swap_bits_u64}; +use util::{hash_to_scalar}; fn read_scalar(reader: R) -> io::Result { let mut s_repr = ::Repr::default(); - - // This reads in big-endian, so we perform a swap of the - // limbs in the representation and swap the bit order. - s_repr.read_be(reader)?; - s_repr.as_mut().reverse(); - for b in s_repr.as_mut() { - *b = swap_bits_u64(*b); - } + s_repr.read_le(reader)?; match E::Fs::from_repr(s_repr) { Ok(s) => Ok(s), @@ -29,15 +22,7 @@ fn read_scalar(reader: R) -> io::Result { } fn write_scalar(s: &E::Fs, writer: W) -> io::Result<()> { - let mut s_repr = s.into_repr(); - - // This writes in big-endian, so we perform a swap of the - // limbs in the representation and swap the bit order. - s_repr.as_mut().reverse(); - for b in s_repr.as_mut() { - *b = swap_bits_u64(*b); - } - s_repr.write_be(writer) + s.into_repr().write_le(writer) } fn h_star(a: &[u8], b: &[u8]) -> E::Fs { diff --git a/src/util.rs b/src/util.rs index b5cc4e8..e67e660 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,15 +2,6 @@ use blake2_rfc::blake2b::Blake2b; use jubjub::{JubjubEngine, ToUniform}; -pub fn swap_bits_u64(x: u64) -> u64 -{ - let mut tmp = 0; - for i in 0..64 { - tmp |= ((x >> i) & 1) << (63 - i); - } - tmp -} - pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> E::Fs { let mut hasher = Blake2b::with_params(64, &[], &[], persona); hasher.update(a); @@ -18,22 +9,3 @@ pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> E: let ret = hasher.finalize(); E::Fs::to_uniform(ret.as_ref()) } - -#[test] -fn test_swap_bits_u64() { - assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111); - assert_eq!(swap_bits_u64(15135675916470734665), 0b1001001011110010001101010010001110110000100111010011000001001011); - assert_eq!(swap_bits_u64(6724233301461108393), 0b1001010101100000100011100001010111110001011000101000101010111010); - assert_eq!(swap_bits_u64(206708183275952289), 0b1000010100011010001010100011101011111111111110100111101101000000); - assert_eq!(swap_bits_u64(12712751566144824320), 0b0000000000100110010110111000001110001100001000110011011000001101); - - let mut a = 15863238721320035327u64; - for _ in 0..1000 { - a = a.wrapping_mul(a); - - let swapped = swap_bits_u64(a); - let unswapped = swap_bits_u64(swapped); - - assert_eq!(a, unswapped); - } -} From 89f47ef5c20449be2586e202869b6af72d2efc04 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 18 May 2018 12:41:55 -0600 Subject: [PATCH 160/168] Make ivk pub --- src/primitives/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 01e042d..26dafab 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -84,7 +84,7 @@ impl ViewingKey { ) } - fn ivk(&self) -> E::Fs { + pub fn ivk(&self) -> E::Fs { let mut preimage = [0; 64]; self.ak.write(&mut preimage[0..32]).unwrap(); From df7bfce0beb06bf776d41bb1a69febf12c4723d7 Mon Sep 17 00:00:00 2001 From: George Tankersley Date: Thu, 12 Jul 2018 00:00:00 +0000 Subject: [PATCH 161/168] redjubjub: make PrivateKey internal scalar public --- src/redjubjub.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index 0283508..994ca52 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -34,7 +34,7 @@ pub struct Signature { sbar: [u8; 32], } -pub struct PrivateKey(E::Fs); +pub struct PrivateKey(pub E::Fs); pub struct PublicKey(pub Point); From 3e43cae526feeedc125b2865b6ab2f7848063760 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 27 Jul 2018 10:28:08 -0600 Subject: [PATCH 162/168] Add test that torsion doesn't affect signature verification. --- src/redjubjub.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index 994ca52..d6604e9 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -155,10 +155,39 @@ mod tests { use pairing::bls12_381::Bls12; use rand::thread_rng; - use jubjub::JubjubBls12; + use jubjub::{JubjubBls12, fs::Fs, edwards}; use super::*; + #[test] + fn cofactor_check() { + let rng = &mut thread_rng(); + let params = &JubjubBls12::new(); + let inf = edwards::Point::zero(); + let p_g = FixedGenerators::SpendingKeyGenerator; + + 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 != inf && r4 != inf && r8 == inf { + break r; + } + }; + + 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); + 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(); From cae9715a8e1986269023501d0a638f1014500275 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 27 Jul 2018 10:37:26 -0600 Subject: [PATCH 163/168] Change signature verification equation to permit batch verification. --- src/redjubjub.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index d6604e9..82eeee5 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -145,8 +145,11 @@ impl PublicKey { Ok(s) => s, Err(_) => return false, }; - // S . P_G = R + c . vk - self.0.mul(c, params).add(&r, params) == params.generator(p_g).mul(s, params).into() + // 0 = 8(-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()) } } From a57d2773ab7a4fac5cf9bd4cc9f951c9f5b095b9 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 27 Jul 2018 10:54:21 -0600 Subject: [PATCH 164/168] Add demonstration implementation of batch verification. --- src/redjubjub.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index 82eeee5..7fc83b8 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -2,7 +2,7 @@ //! See section 5.4.6 of the Sapling protocol specification. use pairing::{Field, PrimeField, PrimeFieldRepr}; -use rand::Rng; +use rand::{Rng, Rand}; use std::io::{self, Read, Write}; use jubjub::{FixedGenerators, JubjubEngine, JubjubParams, Unknown, edwards::Point}; @@ -29,6 +29,7 @@ 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], @@ -153,6 +154,52 @@ impl PublicKey { } } +pub struct BatchEntry<'a, E: JubjubEngine> { + vk: PublicKey, + msg: &'a [u8], + sig: Signature, +} + +pub fn batch_verify<'a, E: JubjubEngine, R: Rng>( + rng: &mut R, + batch: &[BatchEntry<'a, E>], + params: &E::Params, + p_g: FixedGenerators +) -> 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; @@ -162,6 +209,36 @@ mod tests { 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, params, p_g)); + + batch[0].sig = sig2; + + assert!(!batch_verify(rng, &batch, params, p_g)); + } + #[test] fn cofactor_check() { let rng = &mut thread_rng(); From 9cb8accf099714413488d055b9df8582417e505c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 30 Jul 2018 07:29:23 -0600 Subject: [PATCH 165/168] Fix nits --- src/redjubjub.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/redjubjub.rs b/src/redjubjub.rs index 7fc83b8..dfae28c 100644 --- a/src/redjubjub.rs +++ b/src/redjubjub.rs @@ -146,7 +146,7 @@ impl PublicKey { Ok(s) => s, Err(_) => return false, }; - // 0 = 8(-S . P_G + R + c . vk) + // 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 @@ -160,11 +160,13 @@ pub struct BatchEntry<'a, E: JubjubEngine> { 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, - p_g: FixedGenerators ) -> bool { let mut acc = Point::::zero(); @@ -232,20 +234,21 @@ mod tests { BatchEntry { vk: vk2, msg: msg2, sig: sig2 } ]; - assert!(batch_verify(rng, &batch, params, p_g)); + assert!(batch_verify(rng, &batch, p_g, params)); batch[0].sig = sig2; - assert!(!batch_verify(rng, &batch, params, p_g)); + assert!(!batch_verify(rng, &batch, p_g, params)); } #[test] fn cofactor_check() { let rng = &mut thread_rng(); let params = &JubjubBls12::new(); - let inf = edwards::Point::zero(); + 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); @@ -253,13 +256,15 @@ mod tests { let r4 = r2.double(params); let r8 = r4.double(params); - if r2 != inf && r4 != inf && r8 == inf { + 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)); From 821810cd8248df1ede0865e4abc8f280dd6ce8fc Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 25 Jul 2018 14:46:00 -0600 Subject: [PATCH 166/168] Change pedersen hash outside the circuit to use window table lookups. --- benches/pedersen_hash.rs | 23 +++++++++++++++++++ src/jubjub/mod.rs | 49 ++++++++++++++++++++++++++++++++++++++++ src/pedersen_hash.rs | 22 +++++++++++++++--- 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 benches/pedersen_hash.rs diff --git a/benches/pedersen_hash.rs b/benches/pedersen_hash.rs new file mode 100644 index 0000000..c5968de --- /dev/null +++ b/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/src/jubjub/mod.rs b/src/jubjub/mod.rs index 80a7986..51a000a 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -112,6 +112,8 @@ pub trait JubjubParams: Sized { 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 @@ -126,6 +128,9 @@ pub trait JubjubParams: Sized { /// 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 { @@ -140,6 +145,7 @@ pub struct JubjubBls12 { scale: Fr, pedersen_hash_generators: Vec>, + pedersen_hash_exp: Vec>>>, pedersen_circuit_generators: Vec>>, fixed_base_generators: Vec>, @@ -154,6 +160,9 @@ impl JubjubParams for JubjubBls12 { 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 } @@ -171,6 +180,9 @@ impl JubjubParams for JubjubBls12 { { &self.fixed_base_circuit_generators[base as usize][..] } + fn pedersen_hash_exp_window_size() -> u32 { + 8 + } } impl JubjubBls12 { @@ -191,6 +203,7 @@ impl JubjubBls12 { // 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![], @@ -258,6 +271,42 @@ impl JubjubBls12 { 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]; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 5c3bb90..0590a5c 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -1,6 +1,7 @@ use jubjub::*; use pairing::*; +#[derive(Copy, Clone)] pub enum Personalization { NoteCommitment, MerkleTree(usize) @@ -31,7 +32,7 @@ pub fn pedersen_hash( let mut bits = personalization.get_bits().into_iter().chain(bits.into_iter()); let mut result = edwards::Point::zero(); - let mut generators = params.pedersen_hash_generators().iter(); + let mut generators = params.pedersen_hash_exp_table().iter(); loop { let mut acc = E::Fs::zero(); @@ -78,8 +79,23 @@ pub fn pedersen_hash( break; } - let mut tmp = generators.next().expect("we don't have enough generators").clone(); - tmp = tmp.mul(acc, params); + 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); } From 95b498af3394410c2d6bd7a96be7f41462c391b7 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 25 Jul 2018 15:02:02 -0600 Subject: [PATCH 167/168] Implement specialized doubling formula for extended twisted edwards coordinates. --- src/jubjub/edwards.rs | 72 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index 2137d99..73f0dfc 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -355,12 +355,72 @@ impl Point { p } - pub fn double(&self, params: &E::Params) -> Self { - // Point addition is unified and complete. - // There are dedicated formulae, but we do - // not implement these now. - - self.add(self, params) + 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 + } } pub fn add(&self, other: &Self, params: &E::Params) -> Self From b4d41b689c6c1710ce083c24860ad54ebd3ba82f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 27 Jul 2018 17:36:17 -0600 Subject: [PATCH 168/168] Add must_use to operations over Edwards/Montgomery points. --- src/jubjub/edwards.rs | 5 +++++ src/jubjub/montgomery.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index 73f0dfc..e91455c 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -164,6 +164,7 @@ impl Point { } /// 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) @@ -346,6 +347,7 @@ impl Point { (x, y) } + #[must_use] pub fn negate(&self) -> Self { let mut p = self.clone(); @@ -355,6 +357,7 @@ impl Point { 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 @@ -423,6 +426,7 @@ impl Point { } } + #[must_use] pub fn add(&self, other: &Self, params: &E::Params) -> Self { // See "Twisted Edwards Curves Revisited" @@ -495,6 +499,7 @@ impl Point { } } + #[must_use] pub fn mul::Repr>>( &self, scalar: S, diff --git a/src/jubjub/montgomery.rs b/src/jubjub/montgomery.rs index 2243877..18d0fcb 100644 --- a/src/jubjub/montgomery.rs +++ b/src/jubjub/montgomery.rs @@ -97,6 +97,7 @@ impl Point { } /// 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) @@ -216,6 +217,7 @@ impl Point { } } + #[must_use] pub fn negate(&self) -> Self { let mut p = self.clone(); @@ -224,6 +226,7 @@ impl Point { p } + #[must_use] pub fn double(&self, params: &E::Params) -> Self { if self.infinity { return Point::zero(); @@ -280,6 +283,7 @@ impl Point { } } + #[must_use] pub fn add(&self, other: &Self, params: &E::Params) -> Self { // This is a standard affine point addition formula @@ -330,6 +334,7 @@ impl Point { } } + #[must_use] pub fn mul::Repr>>( &self, scalar: S,