From 6c00b2dbd09ec5b77232cb983b08fe961c1b02ed Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Mon, 11 Jan 2021 17:43:23 +0000 Subject: [PATCH 01/14] client: mimc-based client-side hashing of field elements --- client/tests/test_input_hasher.py | 44 +++++++++++++++++++++++++++++++ client/zeth/core/input_hasher.py | 35 ++++++++++++++++++++++++ client/zeth/core/mimc.py | 11 ++++++-- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 client/tests/test_input_hasher.py create mode 100644 client/zeth/core/input_hasher.py diff --git a/client/tests/test_input_hasher.py b/client/tests/test_input_hasher.py new file mode 100644 index 000000000..10043937e --- /dev/null +++ b/client/tests/test_input_hasher.py @@ -0,0 +1,44 @@ +# Copyright (c) 2015-2021 Clearmatics Technologies Ltd +# +# SPDX-License-Identifier: LGPL-3.0+ + +from zeth.core.mimc import MiMC7, MiMC31 +from zeth.core.input_hasher import InputHasher +from unittest import TestCase + +DUMMY_INPUT_VALUES = [-1, 0, 1] + + +class TestInputHasher(TestCase): + + def test_input_hasher_simple(self) -> None: + # Some very simple cases + mimc = MiMC7() + input_hasher = InputHasher(mimc, 7) + self.assertEqual(mimc.hash_int(7, 0), input_hasher.hash([])) + self.assertEqual( + mimc.hash_int(mimc.hash_int(7, 1), 1), input_hasher.hash([1])) + self.assertEqual( + mimc.hash_int( + mimc.hash_int( + mimc.hash_int(7, 1), 2), + 2), + input_hasher.hash([1, 2])) + + def test_input_hasher_mimc7(self) -> None: + mimc = MiMC7() + input_hasher = InputHasher(mimc) + values = [x % mimc.prime for x in DUMMY_INPUT_VALUES] + # pylint:disable=line-too-long + expect = 5568471640435576440988459485125198359192118312228711462978763973844457667180 # noqa + # pylint:enable=line-too-long + self.assertEqual(expect, input_hasher.hash(values)) + + def test_input_hasher_mimc31(self) -> None: + mimc = MiMC31() + input_hasher = InputHasher(mimc) + values = [x % mimc.prime for x in DUMMY_INPUT_VALUES] + # pylint: disable=line-too-long + expect = 1029772481427643815119825324071277815354972734622711297984795198139876181749 # noqa + # pylint: enable=line-too-long + self.assertEqual(expect, input_hasher.hash(values)) diff --git a/client/zeth/core/input_hasher.py b/client/zeth/core/input_hasher.py new file mode 100644 index 000000000..87dd395c6 --- /dev/null +++ b/client/zeth/core/input_hasher.py @@ -0,0 +1,35 @@ +# Copyright (c) 2015-2021 Clearmatics Technologies Ltd +# +# SPDX-License-Identifier: LGPL-3.0+ + +from zeth.core.mimc import MiMCBase +from typing import List + + +# Default seed, generated as: +# zeth.core.mimc._keccak_256( +# zeth.core.mimc._str_to_bytes("clearmatics_hash_seed")) +DEFAULT_IV_UINT256 = \ + 13196537064117388418196223856311987714388543839552400408340921397545324034315 + + +class InputHasher: + """ + Hash a series of field elements via the Merkle-Damgard construction on a + MiMC compression function. Note that since this function only accepts whole + numbers of scalar field elements, there is no ambiguity w.r.t to padding + and we could technically omit the finalization step. It has been kept for + now, to allow time for further consideration, and in case the form of the + hasher changes (e.g. in case we want to be able to hash arbitrary bit + strings in the future). + """ + def __init__(self, compression_fn: MiMCBase, iv: int = DEFAULT_IV_UINT256): + assert compression_fn.prime < (2 << 256) + self._compression_fn = compression_fn + self._iv = iv % compression_fn.prime + + def hash(self, values: List[int]) -> int: + current = self._iv + for m in values: + current = self._compression_fn.hash_int(current, m) + return self._compression_fn.hash_int(current, len(values)) diff --git a/client/zeth/core/mimc.py b/client/zeth/core/mimc.py index d94bc81c2..b05dc2648 100644 --- a/client/zeth/core/mimc.py +++ b/client/zeth/core/mimc.py @@ -61,8 +61,15 @@ def hash(self, left: bytes, right: bytes) -> bytes: """ x = int.from_bytes(left, byteorder='big') % self.prime y = int.from_bytes(right, byteorder='big') % self.prime - result = (self.encrypt(x, y) + x + y) % self.prime - return result.to_bytes(32, byteorder='big') + return self.hash_int(x, y).to_bytes(32, byteorder='big') + + def hash_int(self, x: int, y: int) -> int: + """ + Similar to hash, but use field elements directly. + """ + assert x < self.prime + assert y < self.prime + return (self.encrypt(x, y) + x + y) % self.prime @abstractmethod def mimc_round(self, message: int, key: int, rc: int) -> int: From 9ad8aeaafb916dfc544cd7bc1e28477dd04703cd Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Mon, 11 Jan 2021 18:31:43 +0000 Subject: [PATCH 02/14] libzeth: pb_linear_combinations instead of pb_variables in mimc round gadget --- libzeth/circuits/mimc/mimc_round.hpp | 8 ++++---- libzeth/circuits/mimc/mimc_round.tcc | 11 +++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/libzeth/circuits/mimc/mimc_round.hpp b/libzeth/circuits/mimc/mimc_round.hpp index be80016bd..87c7ad757 100644 --- a/libzeth/circuits/mimc/mimc_round.hpp +++ b/libzeth/circuits/mimc/mimc_round.hpp @@ -25,10 +25,10 @@ class MiMC_round_gadget : public libsnark::gadget bit_utils::hamming_weight() - 2; // Message of the current round - const libsnark::pb_variable msg; + const libsnark::pb_linear_combination msg; // Key of the current round - const libsnark::pb_variable key; + const libsnark::pb_linear_combination key; // Round constant of the current round const FieldT round_const; @@ -45,8 +45,8 @@ class MiMC_round_gadget : public libsnark::gadget public: MiMC_round_gadget( libsnark::protoboard &pb, - const libsnark::pb_variable &msg, - const libsnark::pb_variable &key, + const libsnark::pb_linear_combination &msg, + const libsnark::pb_linear_combination &key, const FieldT &round_const, libsnark::pb_variable &result, const bool add_k_to_result, diff --git a/libzeth/circuits/mimc/mimc_round.tcc b/libzeth/circuits/mimc/mimc_round.tcc index 33051b903..02186624d 100644 --- a/libzeth/circuits/mimc/mimc_round.tcc +++ b/libzeth/circuits/mimc/mimc_round.tcc @@ -13,8 +13,8 @@ namespace libzeth template MiMC_round_gadget::MiMC_round_gadget( libsnark::protoboard &pb, - const libsnark::pb_variable &msg, - const libsnark::pb_variable &key, + const libsnark::pb_linear_combination &msg, + const libsnark::pb_linear_combination &key, const FieldT &round_const, libsnark::pb_variable &result, const bool add_key_to_result, @@ -100,9 +100,12 @@ void MiMC_round_gadget::generate_r1cs_constraints() template void MiMC_round_gadget::generate_r1cs_witness() const { + key.evaluate(this->pb); + msg.evaluate(this->pb); + constexpr size_t mask = 1 << (EXPONENT_NUM_BITS - 1); - const FieldT k_val = this->pb.val(key); - const FieldT t = this->pb.val(msg) + k_val + round_const; + const FieldT k_val = this->pb.lc_val(key); + const FieldT t = this->pb.lc_val(msg) + k_val + round_const; // First intermediate variable has value t^2 size_t exp = Exponent << 1; From 46accdb123162bb5f6625035cd3aa34c98b6e769 Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Tue, 12 Jan 2021 10:39:16 +0000 Subject: [PATCH 03/14] libzeth: clean up mimc gadgets and make interface consistent --- .../merkle_tree/merkle_path_compute.hpp | 4 +- .../merkle_tree/merkle_path_compute.tcc | 9 ++-- libzeth/circuits/mimc/mimc.hpp | 8 +-- libzeth/circuits/mimc/mimc.tcc | 49 ++++++++++------- libzeth/circuits/mimc/mimc_mp.hpp | 16 +++--- libzeth/circuits/mimc/mimc_mp.tcc | 54 +++++++++---------- libzeth/tests/circuits/mimc_mp_test.cpp | 34 ++++++++---- 7 files changed, 101 insertions(+), 73 deletions(-) diff --git a/libzeth/circuits/merkle_tree/merkle_path_compute.hpp b/libzeth/circuits/merkle_tree/merkle_path_compute.hpp index abec3e2a0..32ab1f5b7 100644 --- a/libzeth/circuits/merkle_tree/merkle_path_compute.hpp +++ b/libzeth/circuits/merkle_tree/merkle_path_compute.hpp @@ -23,10 +23,12 @@ class merkle_path_compute : public libsnark::gadget // Merkle Authentication path const libsnark::pb_variable_array path; + // Digests + libsnark::pb_variable_array digests; // Gadget informing the position in the three of the computed // hash and authentication node std::vector> selectors; - // Vector of hash gadgets to compute the intermediary hashes + // Vector of hash gadgets to compute the intermediary digests std::vector hashers; merkle_path_compute( diff --git a/libzeth/circuits/merkle_tree/merkle_path_compute.tcc b/libzeth/circuits/merkle_tree/merkle_path_compute.tcc index e6cdf6dab..1f9f5dbde 100644 --- a/libzeth/circuits/merkle_tree/merkle_path_compute.tcc +++ b/libzeth/circuits/merkle_tree/merkle_path_compute.tcc @@ -29,7 +29,9 @@ merkle_path_compute::merkle_path_compute( assert(address_bits.size() == depth); // For each layer of the tree + digests.allocate(pb, depth, FMT(annotation_prefix, " digests")); for (size_t i = 0; i < depth; i++) { + // We first initialize the gadget to order the computed hash and the // authentication node to know which one is the first to be hashed and // which one is the second (as in mimc_hash(left, right)) We also append @@ -44,7 +46,7 @@ merkle_path_compute::merkle_path_compute( } else { selectors.push_back(merkle_path_selector( pb, - hashers[i - 1].result(), + digests[i - 1], path[i], address_bits[i], FMT(this->annotation_prefix, " selector[%zu]", i))); @@ -56,6 +58,7 @@ merkle_path_compute::merkle_path_compute( pb, {selectors[i].get_left()}, selectors[i].get_right(), + digests[i], FMT(this->annotation_prefix, " hasher[%zu]", i)); // We append the initialized hasher in the vector of hashers @@ -91,11 +94,11 @@ const libsnark::pb_variable merkle_path_compute:: result() { // We first check that we are not working with an empty tree - assert(hashers.size() > 0); + assert(digests.size() > 0); // We return the last hasher result, that is to say the computed root, // generated out of leaf, leaf address and merkle authentication path - return hashers.back().result(); + return digests[digests.size() - 1]; }; } // namespace libzeth diff --git a/libzeth/circuits/mimc/mimc.hpp b/libzeth/circuits/mimc/mimc.hpp index 9948d019f..aad68e26b 100644 --- a/libzeth/circuits/mimc/mimc.hpp +++ b/libzeth/circuits/mimc/mimc.hpp @@ -28,21 +28,21 @@ class MiMC_permutation_gadget : public libsnark::gadget // Vector of intermediate result values std::array, NumRounds> round_results; + // Vector of MiMC round_gadgets std::vector round_gadgets; public: MiMC_permutation_gadget( libsnark::protoboard &pb, - const libsnark::pb_variable &msg, - const libsnark::pb_variable &key, + const libsnark::pb_linear_combination &msg, + const libsnark::pb_linear_combination &key, + const libsnark::pb_variable &result, const std::string &annotation_prefix = "MiMCe7_permutation_gadget"); void generate_r1cs_constraints(); void generate_r1cs_witness() const; - const libsnark::pb_variable &result() const; - // Constants vector initialization void setup_sha3_constants(); }; diff --git a/libzeth/circuits/mimc/mimc.tcc b/libzeth/circuits/mimc/mimc.tcc index 6f6487646..ce18bad95 100644 --- a/libzeth/circuits/mimc/mimc.tcc +++ b/libzeth/circuits/mimc/mimc.tcc @@ -21,8 +21,9 @@ bool MiMC_permutation_gadget:: template MiMC_permutation_gadget::MiMC_permutation_gadget( libsnark::protoboard &pb, - const libsnark::pb_variable &msg, - const libsnark::pb_variable &key, + const libsnark::pb_linear_combination &msg, + const libsnark::pb_linear_combination &key, + const libsnark::pb_variable &result, const std::string &annotation_prefix) : libsnark::gadget(pb, annotation_prefix) { @@ -31,27 +32,45 @@ MiMC_permutation_gadget::MiMC_permutation_gadget( // Initialize the round gadgets round_gadgets.reserve(NumRounds); - const libsnark::pb_variable *round_msg = &msg; - for (size_t i = 0; i < NumRounds; i++) { - // Set the input of the next round with the output variable of the - // previous round (except for round 0) - round_results[i].allocate( - this->pb, FMT(this->annotation_prefix, " round_result[%zu]", i)); + // First round uses round_msg as an input. + round_results[0].allocate( + this->pb, FMT(this->annotation_prefix, " round_result[0]")); + round_gadgets.emplace_back( + this->pb, + msg, + key, + round_constants[0], + round_results[0], + false, + FMT(this->annotation_prefix, " round[0]")); + + // All other rounds use the output of the previous round and output to an + // intermediate variable, except the last round, which outputs to the + // result parameter. + for (size_t i = 1; i < NumRounds; i++) { const bool is_last = (i == (NumRounds - 1)); + // Allocate output variable (except for last round, which outputs to + // the result variable). + if (is_last) { + round_results[i] = result; + } else { + round_results[i].allocate( + this->pb, + FMT(this->annotation_prefix, " round_result[%zu]", i)); + } + // Initialize and add the current round gadget into the rounds gadget // vector, picking the relative constant round_gadgets.emplace_back( this->pb, - *round_msg, + round_results[i - 1], key, round_constants[i], round_results[i], is_last, FMT(this->annotation_prefix, " round[%zu]", i)); - - round_msg = &round_results[i]; } } @@ -76,14 +95,6 @@ void MiMC_permutation_gadget:: } } -template -const libsnark::pb_variable - &MiMC_permutation_gadget::result() const -{ - // Returns the result of the last encryption/permutation - return round_results.back(); -} - // The following constants correspond to the iterative computation of sha3_256 // hash function over the initial seed "clearmatics_mt_seed". See: // client/zethCodeConstantsGeneration.py for more details diff --git a/libzeth/circuits/mimc/mimc_mp.hpp b/libzeth/circuits/mimc/mimc_mp.hpp index 757ae5d22..c7ab684b3 100644 --- a/libzeth/circuits/mimc/mimc_mp.hpp +++ b/libzeth/circuits/mimc/mimc_mp.hpp @@ -24,24 +24,24 @@ class MiMC_mp_gadget : public libsnark::gadget libsnark::pb_variable x; // Second input libsnark::pb_variable y; - // Permutation gadget - PermutationT permutation_gadget; // Output variable - libsnark::pb_variable output; + libsnark::pb_variable result; + // Permutation output + libsnark::pb_variable perm_output; + // Permutation gadget + std::shared_ptr permutation_gadget; public: MiMC_mp_gadget( libsnark::protoboard &pb, - const libsnark::pb_variable x, - const libsnark::pb_variable y, + const libsnark::pb_variable &x, + const libsnark::pb_variable &y, + const libsnark::pb_variable &result, const std::string &annotation_prefix = "MiMC_mp_gadget"); void generate_r1cs_constraints(); void generate_r1cs_witness() const; - // Returns the hash computed - const libsnark::pb_variable &result() const; - // Returns the hash (field element) static FieldT get_hash(const FieldT x, FieldT y); }; diff --git a/libzeth/circuits/mimc/mimc_mp.tcc b/libzeth/circuits/mimc/mimc_mp.tcc index bb86ef0ec..f92dd042c 100644 --- a/libzeth/circuits/mimc/mimc_mp.tcc +++ b/libzeth/circuits/mimc/mimc_mp.tcc @@ -5,38 +5,41 @@ #ifndef __ZETH_CIRCUITS_MIMC_MP_TCC__ #define __ZETH_CIRCUITS_MIMC_MP_TCC__ +#include "mimc_mp.hpp" + namespace libzeth { template MiMC_mp_gadget::MiMC_mp_gadget( libsnark::protoboard &pb, - const libsnark::pb_variable x, - const libsnark::pb_variable y, + const libsnark::pb_variable &x, + const libsnark::pb_variable &y, + const libsnark::pb_variable &result, const std::string &annotation_prefix) : libsnark::gadget(pb, annotation_prefix) , x(x) , y(y) - , permutation_gadget( - pb, x, y, FMT(this->annotation_prefix, " permutation_gadget")) + , result(result) { - // Allocates output variable - output.allocate(pb, FMT(this->annotation_prefix, " output")); + perm_output.allocate(this->pb, FMT(annotation_prefix, " perm_output")); + permutation_gadget.reset(new PermutationT( + pb, + x, + y, + perm_output, + FMT(this->annotation_prefix, " permutation_gadget"))); } template void MiMC_mp_gadget::generate_r1cs_constraints() { // Setting constraints for the permutation gadget - permutation_gadget.generate_r1cs_constraints(); - - const libsnark::pb_variable &m = x; - const libsnark::pb_variable &key = y; + permutation_gadget->generate_r1cs_constraints(); // Adding constraint for the Miyaguchi-Preneel equation this->pb.add_r1cs_constraint( - libsnark::r1cs_constraint( - permutation_gadget.result() + m + key, 1, output), + libsnark::r1cs_constraint(perm_output + x + y, 1, result), FMT(this->annotation_prefix, " out=k+E_k(m_i)+m_i")); } @@ -44,20 +47,11 @@ template void MiMC_mp_gadget::generate_r1cs_witness() const { // Generating witness for the gadget - permutation_gadget.generate_r1cs_witness(); + permutation_gadget->generate_r1cs_witness(); // Filling output variables for Miyaguchi-Preenel equation - this->pb.val(output) = this->pb.val(y) + - this->pb.val(permutation_gadget.result()) + - this->pb.val(x); -} - -template -const libsnark::pb_variable - &MiMC_mp_gadget::result() const -{ - // Returns the output - return output; + this->pb.val(result) = + this->pb.val(y) + this->pb.val(perm_output) + this->pb.val(x); } // Returns the hash of two elements @@ -68,23 +62,27 @@ FieldT MiMC_mp_gadget::get_hash(const FieldT x, FieldT y) libsnark::pb_variable pb_x; libsnark::pb_variable pb_y; + libsnark::pb_variable result; // Allocates and fill with the x and y - pb_x.allocate(pb, " x"); + pb_x.allocate(pb, "x"); pb.val(pb_x) = x; - pb_y.allocate(pb, " y"); + pb_y.allocate(pb, "y"); pb.val(pb_y) = y; + result.allocate(pb, "result"); + // Initialize the Hash MiMC_mp_gadget mimc_hasher( - pb, pb_x, pb_y, " mimc_hash"); + pb, pb_x, pb_y, result, " mimc_hash"); // Computes the hash + mimc_hasher.generate_r1cs_constraints(); mimc_hasher.generate_r1cs_witness(); // Returns the hash - return pb.val(mimc_hasher.result()); + return pb.val(result); } } // namespace libzeth diff --git a/libzeth/tests/circuits/mimc_mp_test.cpp b/libzeth/tests/circuits/mimc_mp_test.cpp index e7e82e7fa..ca081c8e4 100644 --- a/libzeth/tests/circuits/mimc_mp_test.cpp +++ b/libzeth/tests/circuits/mimc_mp_test.cpp @@ -132,8 +132,10 @@ TEST(TestMiMC, MiMC7PermTrue) libsnark::pb_variable in_x; libsnark::pb_variable in_k; + libsnark::pb_variable result; in_x.allocate(pb, "x"); in_k.allocate(pb, "k"); + result.allocate(pb, "result"); pb.val(in_x) = Field("3703141493535563179657531719960160174296085208671919" "316200479060314459804651"); @@ -141,14 +143,14 @@ TEST(TestMiMC, MiMC7PermTrue) "2890706581988986633412003"); MiMC_permutation_gadget mimc_gadget( - pb, in_x, in_k, "mimc_gadget"); + pb, in_x, in_k, result, "mimc_gadget"); mimc_gadget.generate_r1cs_constraints(); mimc_gadget.generate_r1cs_witness(); Field expected_out = Field("192990723315478049773124691205698348115617480" "95378968014959488920239255590840"); ASSERT_TRUE(pb.is_satisfied()); - ASSERT_TRUE(expected_out == pb.val(mimc_gadget.result())); + ASSERT_TRUE(expected_out == pb.val(result)); } TEST(TestMiMC, MiMC7PermFalse) @@ -157,21 +159,24 @@ TEST(TestMiMC, MiMC7PermFalse) libsnark::pb_variable in_x; libsnark::pb_variable in_k; + libsnark::pb_variable result; in_x.allocate(pb, "x"); in_k.allocate(pb, "k"); + result.allocate(pb, "result"); pb.val(in_x) = Field("3703141493535563179657531719960160174296085208671919" "316200479060314459804651"); pb.val(in_k) = Field("13455131405143248756924738814405142"); - MiMCe7_permutation_gadget mimc_gadget(pb, in_x, in_k, "mimc_gadget"); + MiMCe7_permutation_gadget mimc_gadget( + pb, in_x, in_k, result, "mimc_gadget"); mimc_gadget.generate_r1cs_constraints(); mimc_gadget.generate_r1cs_witness(); Field unexpected_out = Field("1929907233154780497731246912056983481156174" "8095378968014959488920239255590840"); ASSERT_TRUE(pb.is_satisfied()); - ASSERT_FALSE(unexpected_out == pb.val(mimc_gadget.result())); + ASSERT_FALSE(unexpected_out == pb.val(result)); } TEST(TestMiMC, MiMC7MpTrue) @@ -193,15 +198,18 @@ TEST(TestMiMC, MiMC7MpTrue) pb.val(x) = Field("3703141493535563179657531719960160174296085208671919316" "200479060314459804651"); + libsnark::pb_variable result; + result.allocate(pb, "result"); + MiMC_mp_gadget> mimc_mp_gadget( - pb, x, y, "gadget"); + pb, x, y, result, "gadget"); mimc_mp_gadget.generate_r1cs_constraints(); mimc_mp_gadget.generate_r1cs_witness(); Field expected_out = Field("167979224495559946840631042142333962005996937" "15764605878168345782964540311877"); ASSERT_TRUE(pb.is_satisfied()); - ASSERT_TRUE(expected_out == pb.val(mimc_mp_gadget.result())); + ASSERT_TRUE(expected_out == pb.val(result)); } TEST(TestMiMC, MiMC7MpFalse) @@ -221,15 +229,18 @@ TEST(TestMiMC, MiMC7MpFalse) pb.val(x) = Field("3703141493535563179657531719960160174296085208671919316" "200479060314459804651"); + libsnark::pb_variable result; + result.allocate(pb, "result"); + MiMC_mp_gadget> mimc_mp_gadget( - pb, x, y, "gadget"); + pb, x, y, result, "gadget"); mimc_mp_gadget.generate_r1cs_constraints(); mimc_mp_gadget.generate_r1cs_witness(); Field unexpected_out = Field("1679792244955599468406310421423339620059969" "3715764605878168345782964540311877"); ASSERT_TRUE(pb.is_satisfied()); - ASSERT_FALSE(unexpected_out == pb.val(mimc_mp_gadget.result())); + ASSERT_FALSE(unexpected_out == pb.val(result)); } TEST(TestMiMC, TestMiMC31) @@ -260,15 +271,18 @@ TEST(TestMiMC, TestMiMC31) m.allocate(pb, "m"); pb.val(m) = m_val; + libsnark::pb_variable h; + h.allocate(pb, "h"); + MiMC_mp_gadget> mimc_mp_gadget( - pb, m, k, "mimc_mp"); + pb, m, k, h, "mimc_mp"); mimc_mp_gadget.generate_r1cs_constraints(); mimc_mp_gadget.generate_r1cs_witness(); // Check that the circuit is satisfied, and that the expected result is // generated. ASSERT_TRUE(pb.is_satisfied()); - ASSERT_EQ(h_val, pb.val(mimc_mp_gadget.result())); + ASSERT_EQ(h_val, pb.val(h)); } } // namespace From df2ba6df4fa78917b551ce247240bf0e5cdfeb1a Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Tue, 12 Jan 2021 10:48:35 +0000 Subject: [PATCH 04/14] libzeth: support pb_linear_combination as input to mimc_mp --- libzeth/circuits/mimc/mimc_mp.hpp | 8 ++++---- libzeth/circuits/mimc/mimc_mp.tcc | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libzeth/circuits/mimc/mimc_mp.hpp b/libzeth/circuits/mimc/mimc_mp.hpp index c7ab684b3..64747624e 100644 --- a/libzeth/circuits/mimc/mimc_mp.hpp +++ b/libzeth/circuits/mimc/mimc_mp.hpp @@ -21,9 +21,9 @@ class MiMC_mp_gadget : public libsnark::gadget { private: // First input - libsnark::pb_variable x; + libsnark::pb_linear_combination x; // Second input - libsnark::pb_variable y; + libsnark::pb_linear_combination y; // Output variable libsnark::pb_variable result; // Permutation output @@ -34,8 +34,8 @@ class MiMC_mp_gadget : public libsnark::gadget public: MiMC_mp_gadget( libsnark::protoboard &pb, - const libsnark::pb_variable &x, - const libsnark::pb_variable &y, + const libsnark::pb_linear_combination &x, + const libsnark::pb_linear_combination &y, const libsnark::pb_variable &result, const std::string &annotation_prefix = "MiMC_mp_gadget"); diff --git a/libzeth/circuits/mimc/mimc_mp.tcc b/libzeth/circuits/mimc/mimc_mp.tcc index f92dd042c..ff66204f2 100644 --- a/libzeth/circuits/mimc/mimc_mp.tcc +++ b/libzeth/circuits/mimc/mimc_mp.tcc @@ -13,8 +13,8 @@ namespace libzeth template MiMC_mp_gadget::MiMC_mp_gadget( libsnark::protoboard &pb, - const libsnark::pb_variable &x, - const libsnark::pb_variable &y, + const libsnark::pb_linear_combination &x, + const libsnark::pb_linear_combination &y, const libsnark::pb_variable &result, const std::string &annotation_prefix) : libsnark::gadget(pb, annotation_prefix) @@ -51,7 +51,7 @@ void MiMC_mp_gadget::generate_r1cs_witness() const // Filling output variables for Miyaguchi-Preenel equation this->pb.val(result) = - this->pb.val(y) + this->pb.val(perm_output) + this->pb.val(x); + this->pb.lc_val(y) + this->pb.val(perm_output) + this->pb.lc_val(x); } // Returns the hash of two elements From 23effc340976894b846d6f8c222398c7d0b41b0f Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Tue, 12 Jan 2021 15:57:13 +0000 Subject: [PATCH 05/14] libzeth: input hasher gadget and test --- libzeth/circuits/mimc/mimc_input_hasher.hpp | 43 ++++++++ libzeth/circuits/mimc/mimc_input_hasher.tcc | 97 +++++++++++++++++++ .../tests/circuits/mimc_input_hasher_test.cpp | 71 ++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 libzeth/circuits/mimc/mimc_input_hasher.hpp create mode 100644 libzeth/circuits/mimc/mimc_input_hasher.tcc create mode 100644 libzeth/tests/circuits/mimc_input_hasher_test.cpp diff --git a/libzeth/circuits/mimc/mimc_input_hasher.hpp b/libzeth/circuits/mimc/mimc_input_hasher.hpp new file mode 100644 index 000000000..f0b328ba9 --- /dev/null +++ b/libzeth/circuits/mimc/mimc_input_hasher.hpp @@ -0,0 +1,43 @@ +// Copyright (c) 2015-2021 Clearmatics Technologies Ltd +// +// SPDX-License-Identifier: LGPL-3.0+ + +#ifndef __ZETH_CIRCUITS_MIMC_MIMC_INPUT_HASHER_HPP__ +#define __ZETH_CIRCUITS_MIMC_MIMC_INPUT_HASHER_HPP__ + +#include + +namespace libzeth +{ + +/// Given a list of variables, hash the variables to a value which can be used +/// as a public input bound to the original variables. +template +class mimc_input_hasher : public libsnark::gadget +{ +private: + // Output variable + libsnark::pb_variable _result; + + // Compression function constraints + std::vector> _compression_functions; + + // Intermediate values + libsnark::pb_variable_array _intermediate_values; + +public: + mimc_input_hasher( + libsnark::protoboard &pb, + const libsnark::pb_linear_combination_array &inputs, + const libsnark::pb_variable hash_output, + const std::string &annotation_prefix); + + void generate_r1cs_constraints(); + void generate_r1cs_witness() const; +}; + +} // namespace libzeth + +#include "mimc_input_hasher.tcc" + +#endif // __ZETH_CIRCUITS_MIMC_MIMC_INPUT_HASHER_HPP__ diff --git a/libzeth/circuits/mimc/mimc_input_hasher.tcc b/libzeth/circuits/mimc/mimc_input_hasher.tcc new file mode 100644 index 000000000..adea1f359 --- /dev/null +++ b/libzeth/circuits/mimc/mimc_input_hasher.tcc @@ -0,0 +1,97 @@ +// Copyright (c) 2015-2021 Clearmatics Technologies Ltd +// +// SPDX-License-Identifier: LGPL-3.0+ + +#ifndef __ZETH_CIRCUITS_MIMC_MIMC_INPUT_HASHER_TCC__ +#define __ZETH_CIRCUITS_MIMC_MIMC_INPUT_HASHER_TCC__ + +#include "mimc_input_hasher.hpp" + +namespace libzeth +{ + +template +mimc_input_hasher::mimc_input_hasher( + libsnark::protoboard &pb, + const libsnark::pb_linear_combination_array &inputs, + const libsnark::pb_variable result, + const std::string &annotation_prefix) + : libsnark::gadget(pb, annotation_prefix), _result(result) +{ + const size_t num_inputs = inputs.size(); + if (num_inputs < 2) { + // Although it would be superfluous, we could support 1 entry. However + // it would add some complexity to the code below. For now, assume + // strictly more than 1 entry. + throw std::invalid_argument( + "inputs array must have at least 2 entries"); + } + + // Require one compression function invocation per element in the array, + // followed by the finalization step. Each invocation except the last + // requires an intermediate output variable. + _compression_functions.reserve(num_inputs + 1); + _intermediate_values.allocate( + pb, num_inputs, FMT(annotation_prefix, "intermediate_values")); + + // IV generated as: + // zeth.core.mimc._keccak_256( + // zeth.core.mimc._str_to_bytes("clearmatics_hash_seed")) + // See: client/zeth/core/mimc.py + const FieldT iv_val( + "1319653706411738841819622385631198771438854383955240040834092139" + "7545324034315"); + libsnark::pb_linear_combination iv; + iv.assign(pb, iv_val); + + // First step: hash_output[0] <- mimc_mp(iv, i[0]) + + _compression_functions.emplace_back(new comp_fnT( + pb, + iv, + inputs[0], + _intermediate_values[0], + FMT(annotation_prefix, " compression_functions[0]"))); + + // Intermediate invocations of the compression fucntion. + for (size_t i = 1; i < num_inputs; ++i) { + _compression_functions.emplace_back(new comp_fnT( + pb, + _intermediate_values[i - 1], + inputs[i], + _intermediate_values[i], + FMT(annotation_prefix, " compression_functions[%zu]", i))); + } + + // Last invocation of compression function to finalize. + libsnark::pb_linear_combination num_inputs_lc; + num_inputs_lc.assign(pb, FieldT(num_inputs)); + _compression_functions.emplace_back(new comp_fnT( + pb, + _intermediate_values[num_inputs - 1], + num_inputs_lc, + result, + FMT(annotation_prefix, " compression_functions[%zu]", num_inputs))); + + assert(_compression_functions.size() == num_inputs + 1); +} + +template +void mimc_input_hasher::generate_r1cs_constraints() +{ + for (const std::shared_ptr &cf : _compression_functions) { + cf->generate_r1cs_constraints(); + } +} + +template +void mimc_input_hasher::generate_r1cs_witness() const +{ + for (const std::shared_ptr &cf : _compression_functions) { + cf->generate_r1cs_witness(); + } +} + +} // namespace libzeth + +#endif // __ZETH_CIRCUITS_MIMC_MIMC_INPUT_HASHER_TCC__ diff --git a/libzeth/tests/circuits/mimc_input_hasher_test.cpp b/libzeth/tests/circuits/mimc_input_hasher_test.cpp new file mode 100644 index 000000000..bef917e50 --- /dev/null +++ b/libzeth/tests/circuits/mimc_input_hasher_test.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2015-2021 Clearmatics Technologies Ltd +// +// SPDX-License-Identifier: LGPL-3.0+ + +#include "libzeth/circuits/circuit_types.hpp" +#include "libzeth/circuits/mimc/mimc_input_hasher.hpp" +#include "libzeth/circuits/mimc/mimc_mp.hpp" + +#include +#include + +namespace +{ + +using pp = libff::bls12_377_pp; +using Field = libff::Fr; +using comp_fn = libzeth::tree_hash_selector::tree_hash; +using input_hasher = libzeth::mimc_input_hasher; + +TEST(MiMCInputHasherTest, SimpleInputValues) +{ + // Test data generated as follows: + // $ python + // >>> from zeth.core.mimc import MiMC31 + // >>> from zeth.core.input_hasher import InputHasher + // >>> InputHasher(MiMC31()).hash([0,1,-1,2,-2]) + const std::vector simple_values{{ + Field::zero(), + Field::one(), + -Field::one(), + Field("2"), + -Field("2"), + }}; + const Field expect_hash("47690627216444699952391427631776755329098419241452" + "8676152606007181154538262"); + + libsnark::protoboard pb; + + // Public input: hash of multiple values + libsnark::pb_variable hashed_inputs; + hashed_inputs.allocate(pb, "hashed_inputs"); + pb.set_input_sizes(1); + + // Values to hash + libsnark::pb_variable_array orig_inputs; + orig_inputs.allocate(pb, simple_values.size(), "orig_inputs"); + + // Input hasher + input_hasher hasher(pb, orig_inputs, hashed_inputs, "hasher"); + + // Constraints + hasher.generate_r1cs_constraints(); + + // Witness + for (size_t i = 0; i < simple_values.size(); ++i) { + pb.val(orig_inputs[i]) = simple_values[i]; + } + hasher.generate_r1cs_witness(); + + ASSERT_EQ(expect_hash, pb.val(hashed_inputs)); + ASSERT_TRUE(pb.is_satisfied()); +} + +} // namespace + +int main(int argc, char **argv) +{ + pp::init_public_params(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 7e9db5e916beca2588e8f0cfcef75a0b52d7e546 Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Wed, 13 Jan 2021 10:33:36 +0000 Subject: [PATCH 06/14] libzeth: immediate version of input hashing function --- libzeth/circuits/mimc/mimc_input_hasher.hpp | 3 ++ libzeth/circuits/mimc/mimc_input_hasher.tcc | 32 ++++++++++++++----- .../tests/circuits/mimc_input_hasher_test.cpp | 1 + 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/libzeth/circuits/mimc/mimc_input_hasher.hpp b/libzeth/circuits/mimc/mimc_input_hasher.hpp index f0b328ba9..b8fffda08 100644 --- a/libzeth/circuits/mimc/mimc_input_hasher.hpp +++ b/libzeth/circuits/mimc/mimc_input_hasher.hpp @@ -34,6 +34,9 @@ class mimc_input_hasher : public libsnark::gadget void generate_r1cs_constraints(); void generate_r1cs_witness() const; + + static FieldT get_iv(); + static FieldT compute_hash(const std::vector &values); }; } // namespace libzeth diff --git a/libzeth/circuits/mimc/mimc_input_hasher.tcc b/libzeth/circuits/mimc/mimc_input_hasher.tcc index adea1f359..792644b22 100644 --- a/libzeth/circuits/mimc/mimc_input_hasher.tcc +++ b/libzeth/circuits/mimc/mimc_input_hasher.tcc @@ -34,15 +34,8 @@ mimc_input_hasher::mimc_input_hasher( _intermediate_values.allocate( pb, num_inputs, FMT(annotation_prefix, "intermediate_values")); - // IV generated as: - // zeth.core.mimc._keccak_256( - // zeth.core.mimc._str_to_bytes("clearmatics_hash_seed")) - // See: client/zeth/core/mimc.py - const FieldT iv_val( - "1319653706411738841819622385631198771438854383955240040834092139" - "7545324034315"); libsnark::pb_linear_combination iv; - iv.assign(pb, iv_val); + iv.assign(pb, get_iv()); // First step: hash_output[0] <- mimc_mp(iv, i[0]) @@ -92,6 +85,29 @@ void mimc_input_hasher::generate_r1cs_witness() const } } +template +FieldT mimc_input_hasher::get_iv() +{ + // IV generated as: + // zeth.core.mimc._keccak_256( + // zeth.core.mimc._str_to_bytes("clearmatics_hash_seed")) + // See: client/zeth/core/mimc.py + return FieldT( + "1319653706411738841819622385631198771438854383955240040834092139" + "7545324034315"); +} + +template +FieldT mimc_input_hasher::compute_hash( + const std::vector &values) +{ + FieldT h = get_iv(); + for (const FieldT &v : values) { + h = comp_fnT::get_hash(h, v); + } + return comp_fnT::get_hash(h, FieldT(values.size())); +} + } // namespace libzeth #endif // __ZETH_CIRCUITS_MIMC_MIMC_INPUT_HASHER_TCC__ diff --git a/libzeth/tests/circuits/mimc_input_hasher_test.cpp b/libzeth/tests/circuits/mimc_input_hasher_test.cpp index bef917e50..0c8e706f4 100644 --- a/libzeth/tests/circuits/mimc_input_hasher_test.cpp +++ b/libzeth/tests/circuits/mimc_input_hasher_test.cpp @@ -58,6 +58,7 @@ TEST(MiMCInputHasherTest, SimpleInputValues) hasher.generate_r1cs_witness(); ASSERT_EQ(expect_hash, pb.val(hashed_inputs)); + ASSERT_EQ(expect_hash, input_hasher::compute_hash(simple_values)); ASSERT_TRUE(pb.is_satisfied()); } From 2d60a5003e977903e59bb580714c6442f5f24ec3 Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Wed, 13 Jan 2021 16:35:47 +0000 Subject: [PATCH 07/14] client: abstract proto types behind prover_client interface --- client/test_commands/scenario.py | 6 +---- client/zeth/cli/zeth_get_verification_key.py | 8 +------ client/zeth/core/mixer_client.py | 12 ++++------ client/zeth/core/prover_client.py | 24 ++++++++++++++------ 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/client/test_commands/scenario.py b/client/test_commands/scenario.py index 635a76aa0..463eee7b2 100644 --- a/client/test_commands/scenario.py +++ b/client/test_commands/scenario.py @@ -56,7 +56,6 @@ def wait_for_tx_update_mk_tree( def get_mix_parameters_components( zeth_client: MixerClient, prover_client: ProverClient, - zksnark: IZKSnarkProvider, mk_tree: MerkleTree, sender_ownership_keypair: OwnershipKeyPair, inputs: List[Tuple[int, ZethNote]], @@ -79,8 +78,7 @@ def get_mix_parameters_components( compute_h_sig_cb) prover_inputs, signing_keypair = zeth_client.create_prover_inputs( mix_call_desc) - ext_proof_proto = prover_client.get_proof(prover_inputs) - ext_proof = zksnark.extended_proof_from_proto(ext_proof_proto) + ext_proof = prover_client.get_proof(prover_inputs) return ( prover_inputs.js_outputs[0], prover_inputs.js_outputs[1], @@ -262,7 +260,6 @@ def compute_h_sig_attack_nf( get_mix_parameters_components( zeth_client, prover_client, - zksnark, mk_tree, keystore["Charlie"].ownership_keypair(), # sender [input1, input2], @@ -370,7 +367,6 @@ def charlie_corrupt_bob_deposit( get_mix_parameters_components( zeth_client, prover_client, - zksnark, mk_tree, keystore["Bob"].ownership_keypair(), [input1, input2], diff --git a/client/zeth/cli/zeth_get_verification_key.py b/client/zeth/cli/zeth_get_verification_key.py index 31a750adb..662728163 100644 --- a/client/zeth/cli/zeth_get_verification_key.py +++ b/client/zeth/cli/zeth_get_verification_key.py @@ -4,7 +4,6 @@ from click import command, option, Context, pass_context -from zeth.core.zksnark import get_zksnark_provider from zeth.cli.utils import create_prover_client import json from typing import Optional @@ -21,12 +20,7 @@ def get_verification_key(ctx: Context, vk_out: Optional[str]) -> None: # Get the VK (proto object) client_ctx = ctx.obj prover_client = create_prover_client(client_ctx) - vk_proto = prover_client.get_verification_key() - - # Get a zksnark provider and convert the VK to json - zksnark_name = prover_client.get_configuration().zksnark_name - zksnark = get_zksnark_provider(zksnark_name) - vk = zksnark.verification_key_from_proto(vk_proto) + vk = prover_client.get_verification_key() vk_json = vk.to_json_dict() # Write the json to stdout or a file diff --git a/client/zeth/core/mixer_client.py b/client/zeth/core/mixer_client.py index 07b18d057..0b7cbda5d 100644 --- a/client/zeth/core/mixer_client.py +++ b/client/zeth/core/mixer_client.py @@ -276,13 +276,12 @@ def deploy( Deploy Zeth contracts. """ prover_config = prover_client.get_configuration() - zksnark = get_zksnark_provider(prover_config.zksnark_name) - vk_proto = prover_client.get_verification_key() - pp = prover_config.pairing_parameters - vk = zksnark.verification_key_from_proto(vk_proto) + vk = prover_client.get_verification_key() deploy_gas = deploy_gas or constants.DEPLOYMENT_GAS_WEI contracts_dir = get_contracts_dir() + zksnark = get_zksnark_provider(prover_config.zksnark_name) + pp = prover_config.pairing_parameters mixer_name = zksnark.get_contract_name(pp) mixer_src = os.path.join(contracts_dir, mixer_name + ".sol") @@ -581,11 +580,8 @@ def create_mix_parameters_and_signing_key( prover_inputs, signing_keypair = MixerClient.create_prover_inputs( mix_call_desc) - zksnark = get_zksnark_provider(self.prover_config.zksnark_name) - # Query the prover_server for the related proof - ext_proof_proto = prover_client.get_proof(prover_inputs) - ext_proof = zksnark.extended_proof_from_proto(ext_proof_proto) + ext_proof = prover_client.get_proof(prover_inputs) # Create the final MixParameters object mix_params = self.create_mix_parameters_from_proof( diff --git a/client/zeth/core/prover_client.py b/client/zeth/core/prover_client.py index eb7126114..a9e387dea 100644 --- a/client/zeth/core/prover_client.py +++ b/client/zeth/core/prover_client.py @@ -5,9 +5,10 @@ # SPDX-License-Identifier: LGPL-3.0+ from __future__ import annotations -from .pairing import PairingParameters, pairing_parameters_from_proto +from zeth.core.zksnark import IZKSnarkProvider, get_zksnark_provider, \ + IVerificationKey, ExtendedProof +from zeth.core.pairing import PairingParameters, pairing_parameters_from_proto from zeth.api.zeth_messages_pb2 import ProofInputs -from zeth.api.snark_messages_pb2 import VerificationKey, ExtendedProof from zeth.api import prover_pb2 # type: ignore from zeth.api import prover_pb2_grpc # type: ignore import grpc # type: ignore @@ -93,14 +94,22 @@ def get_configuration(self) -> ProverConfiguration: return self.prover_config - def get_verification_key(self) -> VerificationKey: + def get_zksnark_provider(self) -> IZKSnarkProvider: + """ + Get the appropriate zksnark provider, based on the server configuration. + """ + config = self.get_configuration() + return get_zksnark_provider(config.zksnark_name) + + def get_verification_key(self) -> IVerificationKey: """ Fetch the verification key from the proving service """ with grpc.insecure_channel(self.endpoint) as channel: stub = prover_pb2_grpc.ProverStub(channel) # type: ignore - verificationkey = stub.GetVerificationKey(_make_empty_message()) - return verificationkey + vk_proto = stub.GetVerificationKey(_make_empty_message()) + zksnark = self.get_zksnark_provider() + return zksnark.verification_key_from_proto(vk_proto) def get_proof( self, @@ -111,8 +120,9 @@ def get_proof( with grpc.insecure_channel(self.endpoint) as channel: stub = prover_pb2_grpc.ProverStub(channel) # type: ignore print("-------------- Get the proof --------------") - proof = stub.Prove(proof_inputs) - return proof + extproof_proto = stub.Prove(proof_inputs) + zksnark = self.get_zksnark_provider() + return zksnark.extended_proof_from_proto(extproof_proto) def _make_empty_message() -> empty_pb2.Empty: From 66f53f1e8f72507467b106f85d183458492dbfef Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Thu, 14 Jan 2021 16:13:47 +0000 Subject: [PATCH 08/14] prover_server: fix memory error --- prover_server/prover_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prover_server/prover_server.cpp b/prover_server/prover_server.cpp index b4234dd64..8e9fda11c 100644 --- a/prover_server/prover_server.cpp +++ b/prover_server/prover_server.cpp @@ -88,7 +88,7 @@ static void write_ext_proof_to_file( class prover_server final : public zeth_proto::Prover::Service { private: - circuit_wrapper prover; + circuit_wrapper &prover; // The keypair is the result of the setup. Store a copy internally. snark::keypair keypair; From 8fec66aeb0a3dc8fb7d7d1da65a61893b7663e7f Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Thu, 14 Jan 2021 16:14:49 +0000 Subject: [PATCH 09/14] libzecale: clean up circuit_wrapper and avoid recreating the full circuit for each invocation --- libzeth/circuits/circuit_wrapper.hpp | 23 ++++++++++++++--------- libzeth/circuits/circuit_wrapper.tcc | 23 +++++++---------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/libzeth/circuits/circuit_wrapper.hpp b/libzeth/circuits/circuit_wrapper.hpp index a9a6240ac..0094c1c39 100644 --- a/libzeth/circuits/circuit_wrapper.hpp +++ b/libzeth/circuits/circuit_wrapper.hpp @@ -25,26 +25,27 @@ template< size_t TreeDepth> class circuit_wrapper { -private: - std::shared_ptr, +public: + using Field = libff::Fr; + // Both `joinsplit` and `joinsplit_gadget` are already used in the + // namespace. + using joinsplit_type = joinsplit_gadget< + Field, HashT, HashTreeT, NumInputs, NumOutputs, - TreeDepth>> - joinsplit_g; - -public: - using Field = libff::Fr; + TreeDepth>; circuit_wrapper(); + circuit_wrapper(const circuit_wrapper &) = delete; + circuit_wrapper &operator=(const circuit_wrapper &) = delete; // Generate the trusted setup typename snarkT::keypair generate_trusted_setup() const; // Retrieve the constraint system (intended for debugging purposes). - libsnark::protoboard get_constraint_system() const; + const libsnark::protoboard &get_constraint_system() const; // Generate a proof and returns an extended proof extended_proof prove( @@ -56,6 +57,10 @@ class circuit_wrapper const bits256 &h_sig_in, const bits256 &phi_in, const typename snarkT::proving_key &proving_key) const; + +private: + libsnark::protoboard pb; + std::shared_ptr joinsplit; }; } // namespace libzeth diff --git a/libzeth/circuits/circuit_wrapper.tcc b/libzeth/circuits/circuit_wrapper.tcc index 77adbb937..5d6379894 100644 --- a/libzeth/circuits/circuit_wrapper.tcc +++ b/libzeth/circuits/circuit_wrapper.tcc @@ -27,6 +27,11 @@ circuit_wrapper< NumOutputs, TreeDepth>::circuit_wrapper() { + // TODO: joinsplit_gadget should be refactored to be properly composable. + joinsplit = std::make_shared(pb); + + // Generate constraints + joinsplit->generate_r1cs_constraints(); } template< @@ -46,11 +51,6 @@ typename snarkT::keypair circuit_wrapper< NumOutputs, TreeDepth>::generate_trusted_setup() const { - libsnark::protoboard pb; - joinsplit_gadget - g(pb); - g.generate_r1cs_constraints(); - // Generate a verification and proving key (trusted setup) and write them // in a file return snarkT::generate_setup(pb); @@ -64,7 +64,7 @@ template< size_t NumInputs, size_t NumOutputs, size_t TreeDepth> -libsnark::protoboard> circuit_wrapper< +const libsnark::protoboard> &circuit_wrapper< HashT, HashTreeT, ppT, @@ -73,10 +73,6 @@ libsnark::protoboard> circuit_wrapper< NumOutputs, TreeDepth>::get_constraint_system() const { - libsnark::protoboard pb; - joinsplit_gadget - g(pb); - g.generate_r1cs_constraints(); return pb; } @@ -128,12 +124,7 @@ extended_proof circuit_wrapper< throw std::invalid_argument("invalid joinsplit balance"); } - libsnark::protoboard pb; - - joinsplit_gadget - g(pb); - g.generate_r1cs_constraints(); - g.generate_r1cs_witness( + joinsplit->generate_r1cs_witness( root, inputs, outputs, vpub_in, vpub_out, h_sig_in, phi_in); bool is_valid_witness = pb.is_satisfied(); From 896685e6cdab59194978c70e85f840686acc7419 Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Thu, 14 Jan 2021 17:25:04 +0000 Subject: [PATCH 10/14] submodule: update libsnark to get some better memory protection --- depends/libsnark | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/libsnark b/depends/libsnark index 39bb0b9f1..fa306c6fc 160000 --- a/depends/libsnark +++ b/depends/libsnark @@ -1 +1 @@ -Subproject commit 39bb0b9f193511a22a94d35bb3829afc9671b55d +Subproject commit fa306c6fc981c5459adcb368f3e42cb0122681fb From 026a12e5054458f6c03d1addee9a2320e30f4488 Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Thu, 14 Jan 2021 17:50:56 +0000 Subject: [PATCH 11/14] libzeth: make joinsplit a bit more composable --- libzeth/circuits/circuit_wrapper.tcc | 3 +++ libzeth/circuits/joinsplit.tcc | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/libzeth/circuits/circuit_wrapper.tcc b/libzeth/circuits/circuit_wrapper.tcc index 5d6379894..d30a93c8c 100644 --- a/libzeth/circuits/circuit_wrapper.tcc +++ b/libzeth/circuits/circuit_wrapper.tcc @@ -27,8 +27,11 @@ circuit_wrapper< NumOutputs, TreeDepth>::circuit_wrapper() { + // Joinsplit gadget internally allocates its public data first. // TODO: joinsplit_gadget should be refactored to be properly composable. joinsplit = std::make_shared(pb); + const size_t num_public_elements = joinsplit->get_num_public_elements(); + pb.set_input_sizes(num_public_elements); // Generate constraints joinsplit->generate_r1cs_constraints(); diff --git a/libzeth/circuits/joinsplit.tcc b/libzeth/circuits/joinsplit.tcc index edbd35ed4..b4c41f3fb 100644 --- a/libzeth/circuits/joinsplit.tcc +++ b/libzeth/circuits/joinsplit.tcc @@ -192,10 +192,10 @@ public: // represented. The aggregation of these bits plus of value_pub_in, // and value_pub_out take `nb_field_residual` field element(s) to be // represented - const size_t nb_packed_inputs = + const size_t num_packed_public_elements = 2 * NumInputs + 1 + nb_field_residual; - const size_t nb_inputs = 1 + NumOutputs + nb_packed_inputs; - pb.set_input_sizes(nb_inputs); + const size_t num_public_elements = + 1 + NumOutputs + num_packed_public_elements; // --------------------------------------------------------------- ZERO.allocate(pb, FMT(this->annotation_prefix, " ZERO")); @@ -277,14 +277,15 @@ public: // since we are packing all the inputs nullifiers + the h_is + // + the h_sig + the residual bits assert(packed_inputs.size() == NumInputs + 1 + NumInputs + 1); - assert(nb_packed_inputs == [this]() { + assert(num_packed_public_elements == [this]() { size_t sum = 0; for (const auto &i : packed_inputs) { sum = sum + i.size(); } return sum; }()); - assert(nb_inputs == get_inputs_field_element_size()); + assert(num_public_elements == get_num_public_elements()); + (void)num_public_elements; // [SANITY CHECK] Total size of unpacked inputs size_t total_size_unpacked_inputs = 0; @@ -599,8 +600,8 @@ public: return get_inputs_bit_size() - (1 + NumOutputs) * FieldT::capacity(); } - // Computes the number of field elements in the primary inputs - static size_t get_inputs_field_element_size() + // Computes the number of field elements in the public data + static size_t get_num_public_elements() { size_t nb_elements = 0; From 7090f4817d8472ffd2de17fa590445c16781d167 Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Wed, 20 Jan 2021 11:05:52 +0000 Subject: [PATCH 12/14] libzeth: comment about "experimental" code (review) --- client/zeth/core/input_hasher.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client/zeth/core/input_hasher.py b/client/zeth/core/input_hasher.py index 87dd395c6..09119064f 100644 --- a/client/zeth/core/input_hasher.py +++ b/client/zeth/core/input_hasher.py @@ -15,13 +15,14 @@ class InputHasher: """ - Hash a series of field elements via the Merkle-Damgard construction on a - MiMC compression function. Note that since this function only accepts whole - numbers of scalar field elements, there is no ambiguity w.r.t to padding - and we could technically omit the finalization step. It has been kept for - now, to allow time for further consideration, and in case the form of the - hasher changes (e.g. in case we want to be able to hash arbitrary bit - strings in the future). + Note that this is currently experimental code. Hash a series of field + elements via the Merkle-Damgard construction on a MiMC compression + function. Note that since this function only accepts whole numbers of + scalar field elements, there is no ambiguity w.r.t to padding and we could + technically omit the finalization step. It has been kept for now, to allow + time for further consideration, and in case the form of the hasher changes + (e.g. in case we want to be able to hash arbitrary bit strings in the + future). """ def __init__(self, compression_fn: MiMCBase, iv: int = DEFAULT_IV_UINT256): assert compression_fn.prime < (2 << 256) From b38aa679d70377fc967013bb2a7499aa3812e084 Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Wed, 20 Jan 2021 11:29:18 +0000 Subject: [PATCH 13/14] libzeth: rename type parameter (review) --- libzeth/circuits/mimc/mimc_input_hasher.hpp | 4 +-- libzeth/circuits/mimc/mimc_input_hasher.tcc | 36 ++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/libzeth/circuits/mimc/mimc_input_hasher.hpp b/libzeth/circuits/mimc/mimc_input_hasher.hpp index b8fffda08..a6395593a 100644 --- a/libzeth/circuits/mimc/mimc_input_hasher.hpp +++ b/libzeth/circuits/mimc/mimc_input_hasher.hpp @@ -12,7 +12,7 @@ namespace libzeth /// Given a list of variables, hash the variables to a value which can be used /// as a public input bound to the original variables. -template +template class mimc_input_hasher : public libsnark::gadget { private: @@ -20,7 +20,7 @@ class mimc_input_hasher : public libsnark::gadget libsnark::pb_variable _result; // Compression function constraints - std::vector> _compression_functions; + std::vector> _compression_functions; // Intermediate values libsnark::pb_variable_array _intermediate_values; diff --git a/libzeth/circuits/mimc/mimc_input_hasher.tcc b/libzeth/circuits/mimc/mimc_input_hasher.tcc index 792644b22..f00c0de8f 100644 --- a/libzeth/circuits/mimc/mimc_input_hasher.tcc +++ b/libzeth/circuits/mimc/mimc_input_hasher.tcc @@ -10,8 +10,8 @@ namespace libzeth { -template -mimc_input_hasher::mimc_input_hasher( +template +mimc_input_hasher::mimc_input_hasher( libsnark::protoboard &pb, const libsnark::pb_linear_combination_array &inputs, const libsnark::pb_variable result, @@ -39,16 +39,16 @@ mimc_input_hasher::mimc_input_hasher( // First step: hash_output[0] <- mimc_mp(iv, i[0]) - _compression_functions.emplace_back(new comp_fnT( + _compression_functions.emplace_back(new compFnT( pb, iv, inputs[0], _intermediate_values[0], FMT(annotation_prefix, " compression_functions[0]"))); - // Intermediate invocations of the compression fucntion. + // Intermediate invocations of the compression function. for (size_t i = 1; i < num_inputs; ++i) { - _compression_functions.emplace_back(new comp_fnT( + _compression_functions.emplace_back(new compFnT( pb, _intermediate_values[i - 1], inputs[i], @@ -59,7 +59,7 @@ mimc_input_hasher::mimc_input_hasher( // Last invocation of compression function to finalize. libsnark::pb_linear_combination num_inputs_lc; num_inputs_lc.assign(pb, FieldT(num_inputs)); - _compression_functions.emplace_back(new comp_fnT( + _compression_functions.emplace_back(new compFnT( pb, _intermediate_values[num_inputs - 1], num_inputs_lc, @@ -69,24 +69,24 @@ mimc_input_hasher::mimc_input_hasher( assert(_compression_functions.size() == num_inputs + 1); } -template -void mimc_input_hasher::generate_r1cs_constraints() +template +void mimc_input_hasher::generate_r1cs_constraints() { - for (const std::shared_ptr &cf : _compression_functions) { + for (const std::shared_ptr &cf : _compression_functions) { cf->generate_r1cs_constraints(); } } -template -void mimc_input_hasher::generate_r1cs_witness() const +template +void mimc_input_hasher::generate_r1cs_witness() const { - for (const std::shared_ptr &cf : _compression_functions) { + for (const std::shared_ptr &cf : _compression_functions) { cf->generate_r1cs_witness(); } } -template -FieldT mimc_input_hasher::get_iv() +template +FieldT mimc_input_hasher::get_iv() { // IV generated as: // zeth.core.mimc._keccak_256( @@ -97,15 +97,15 @@ FieldT mimc_input_hasher::get_iv() "7545324034315"); } -template -FieldT mimc_input_hasher::compute_hash( +template +FieldT mimc_input_hasher::compute_hash( const std::vector &values) { FieldT h = get_iv(); for (const FieldT &v : values) { - h = comp_fnT::get_hash(h, v); + h = compFnT::get_hash(h, v); } - return comp_fnT::get_hash(h, FieldT(values.size())); + return compFnT::get_hash(h, FieldT(values.size())); } } // namespace libzeth From d94a36a02570f600f61f60425ac2c82064c949f4 Mon Sep 17 00:00:00 2001 From: Duncan Tebbs Date: Thu, 21 Jan 2021 12:49:43 +0000 Subject: [PATCH 14/14] libzeth: update some comments about public vs primary inputs in joinsplit --- libzeth/circuits/joinsplit.tcc | 70 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/libzeth/circuits/joinsplit.tcc b/libzeth/circuits/joinsplit.tcc index b4c41f3fb..b6dcd183d 100644 --- a/libzeth/circuits/joinsplit.tcc +++ b/libzeth/circuits/joinsplit.tcc @@ -63,7 +63,8 @@ private: libsnark::pb_variable ZERO; - // ---- Primary inputs (public) ---- // + // PUBLIC DATA: to be made available to the mixer + // Merkle Root std::shared_ptr> merkle_root; // List of nullifiers of the notes to spend @@ -82,7 +83,11 @@ private: std::array>, NumInputs> h_is; - // ---- Auxiliary inputs (private) ---- // + // PRIVATE DATA: must be auxiliary (private) inputs to the statement. + // Protoboard owner is responsible for ensuring this. (Note that the PUBLIC + // inputs above are allocated first, so only the first + // get_num_public_elements allocated by this gadget are "public"). + // Total amount transfered in the transaction libsnark::pb_variable_array zk_total_uint64; // List of all spending keys @@ -125,29 +130,26 @@ public: { // Block dedicated to generate the verifier inputs { - // The verification inputs are, except for the root, all bit-strings - // of various lengths (256-bit digests and 64-bit integers) and so - // we pack them into as few field elements as possible. (The more - // verification inputs you have, the more expensive verification - // is.) - - // --------- ALLOCATION OF PRIMARY INPUTS -------- // - // We make sure to have the primary inputs ordered as follow: - // [Root, NullifierS, CommitmentS, h_sig, h_iS, Residual field - // element(S)] ie, below is the index mapping of the primary input - // elements on the protoboard: - // - Index of the "Root" field element: {0} - // - Index of the "NullifierS" field elements: [1, 1 + NumInputs[ - // - Index of the "CommitmentS" field elements: [1 + NumInputs, - // 1 + NumInputs + NumOutputs[ - // - Index of the "h_sig" field element: {1 + NumInputs + - // NumOutputs} - // - Index of the "h_iS" field elements: [1 + NumInputs + NumOutputs - // + 1, 1 + NumInputs + NumOutputs + NumInputs[ - // - Index of the "Residual field element(S)", ie "v_pub_in", - // "v_pub_out", and bits of previous variables not fitting within - // FieldT::capacity() [1 + NumInputs + NumOutputs + NumInputs, - // 1 + NumInputs + NumOutputs + NumInputs + nb_field_residual[ + // PUBLIC DATA: allocated first so that the protoboard has access. + // + // Allocation is currently performed here in the following order + // (with the protoboard owner determining whether these are primary + // or auxiliary inputs to the circuit): + // - Root + // - NullifierS + // - CommitmentS + // - h_sig + // - h_iS + // - Residual field element(S) + // + // This yields the following index mappings: + // 0 : "Root" + // 1, ... : Nullifiers (NumInputs) + // 1 + NumInputs, ... : Commitments (Num Outputs) + // 1 + NumInputs + NumOutputs : h_sig + // 2 + NumInputs + NumOutputs, ... : h_iS (NumInputs) + // 2 + 2xNumInputs + NumOutputs, ... : v_in, v_out, residual + // (nb_field_residual) // We first allocate the root merkle_root.reset(new libsnark::pb_variable); @@ -184,20 +186,20 @@ public: nb_field_residual, FMT(this->annotation_prefix, " residual_bits")); - // The primary inputs are: - // [Root, NullifierS, CommitmentS, h_sig, h_iS, Residual Field - // Element(S)]. The root is represented on a single field element. - // H_sig, as well as each nullifier, commitment and h_i are in - // {0,1}^256 and thus take 1 field element and a few bits to be - // represented. The aggregation of these bits plus of value_pub_in, - // and value_pub_out take `nb_field_residual` field element(s) to be - // represented + // Compute the number of packed public elements, and the total + // number of public elements (see table above). The "packed" inputs + // (those represented as a field element and some residual bits) + // are: + // H_sig, nullifier, commitments and h_iS const size_t num_packed_public_elements = 2 * NumInputs + 1 + nb_field_residual; const size_t num_public_elements = 1 + NumOutputs + num_packed_public_elements; - // --------------------------------------------------------------- + // PRIVATE DATA: + + // Allocate a ZERO variable + // TODO: check whether/why this is actually needed ZERO.allocate(pb, FMT(this->annotation_prefix, " ZERO")); // Initialize the digest_variables