composition of snarks fails in outer prover with `what(): libff::get_root_of_unity: expected logn <= FieldT::s`
hammdist opened this issue · comments
Here I have put together a simple example that just checks a SHA256 preimage in the inner snark, but using primary inputs. The proof is supposed to be checked in an outer snark. It fails during proof generation for the outer snark (full error below):
I have tried with a different inner snark that allows scaling the complexity more smoothly than SHA256. Specifically, I looked at the knapsack hash function, where it starts to fail at 16-bits of preimage combined with 32-bits of output hash (I was trying only with 1:2 ratio).
Well here is the failing SHA-256 snark at any rate:
#include <cassert>
#include <sstream>
#include <type_traits>
#include <libff/common/utils.hpp>
#include <libff/common/default_types/ec_pp.hpp>
#include <libff/common/profiling.hpp>
#include <libsnark/relations/constraint_satisfaction_problems/r1cs/r1cs.hpp>
#include <libsnark/gadgetlib1/gadgets/basic_gadgets.hpp>
#include <libsnark/gadgetlib1/gadgets/hashes/sha256/sha256_gadget.hpp>
#include <libsnark/gadgetlib1/gadgets/verifiers/r1cs_ppzksnark_verifier_gadget.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp>
using namespace libsnark;
template<typename ppI, typename ppO, typename FieldI, typename FieldO>
void test()
{
protoboard<FieldI> pb;
digest_variable<FieldI> prior(pb, SHA256_digest_size, "prior");
digest_variable<FieldI> subseq(pb, SHA256_digest_size, "subseq");
pb.set_input_sizes(pb.num_variables());
digest_variable<FieldI> padding(pb, SHA256_digest_size, "padding");
sha256_two_to_one_hash_gadget<FieldI> cf(pb, prior, padding, subseq, "cf");
cf.generate_r1cs_constraints();
const libff::bit_vector prior_bv = libff::int_list_to_bits({0xe3b0c442, 0x98fc1c14, 0x9afbf4c8, 0x996fb924, 0x27ae41e4, 0x649b934c, 0xa495991b, 0x7852b855}, 32);
const libff::bit_vector subseq_bv = libff::int_list_to_bits({0x5df6e0e2, 0x761359d3, 0x0a827505, 0x8e299fcc, 0x03815345, 0x45f55cf4, 0x3e41983f, 0x5d4c9456}, 32);
const libff::bit_vector padding_bv = libff::int_list_to_bits({1, 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, 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, 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, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0}, 1);
prior.generate_r1cs_witness(prior_bv);
padding.generate_r1cs_witness(padding_bv);
cf.generate_r1cs_witness();
fprintf(stderr, "number of constraints for protoboard: %zu\n", pb.num_constraints());
if (subseq.get_digest() != subseq_bv) {
for (bool bit : subseq.get_digest()) {
std::cerr << "bit " << bit << std::endl;
}
std::cerr << "oops " << __LINE__ << std::endl;
exit(1);
}
if (!(pb.is_satisfied())) {
std::cerr << "oops " << __LINE__ << std::endl;
exit(1);
}
r1cs_ppzksnark_keypair<ppI> keypair = r1cs_ppzksnark_generator<ppI>(pb.get_constraint_system());
r1cs_ppzksnark_processed_verification_key<ppI> pvk = r1cs_ppzksnark_verifier_process_vk<ppI>(keypair.vk);
{
libff::enter_block("Test serialization of keys");
keypair.pk = libff::reserialize<r1cs_ppzksnark_proving_key<ppI> >(keypair.pk);
keypair.vk = libff::reserialize<r1cs_ppzksnark_verification_key<ppI> >(keypair.vk);
pvk = libff::reserialize<r1cs_ppzksnark_processed_verification_key<ppI> >(pvk);
libff::leave_block("Test serialization of keys");
}
r1cs_ppzksnark_proof<ppI> proof = r1cs_ppzksnark_prover<ppI>(keypair.pk, pb.primary_input(), pb.auxiliary_input());
{
libff::enter_block("Test serialization of proof");
proof = libff::reserialize<r1cs_ppzksnark_proof<ppI> >(proof);
libff::leave_block("Test serialization of proof");
}
libff::print_header("R1CS ppzkSNARK Verifier");
const bool ans = r1cs_ppzksnark_verifier_strong_IC<ppI>(keypair.vk, pb.primary_input(), proof);
printf("* The verification result is: %s\n", (ans ? "PASS" : "FAIL"));
const size_t elt_size = FieldI::size_in_bits();
const size_t primary_input_size_in_bits = (pb.num_inputs() * elt_size);
std::cerr << "========== STARTING ON NESTING SNARK ==========" << std::endl;
protoboard<FieldO> pb2;
pb_variable_array<FieldO> primary_input_bits;
primary_input_bits.allocate(pb2, primary_input_size_in_bits, "primary_input_bits");
r1cs_ppzksnark_proof_variable<ppO> proof_variable(pb2, "proof");
pb2.set_input_sizes(pb2.num_variables());
r1cs_ppzksnark_preprocessed_r1cs_ppzksnark_verification_key_variable<ppO> hardcoded_vk(pb2, keypair.vk, "hardcoded_vk");
pb_variable<FieldO> result;
result.allocate(pb2, "result");
r1cs_ppzksnark_online_verifier_gadget<ppO> online_verifier(pb2, hardcoded_vk, primary_input_bits, elt_size, proof_variable, result, "online_verifier");
proof_variable.generate_r1cs_constraints();
online_verifier.generate_r1cs_constraints();
libff::bit_vector input_as_bits;
for (const FieldI &e : pb.primary_input()) {
libff::bit_vector v = libff::convert_field_element_to_bit_vector<FieldI>(e, elt_size);
input_as_bits.insert(input_as_bits.end(), v.begin(), v.end());
}
primary_input_bits.fill_with_bits(pb2, input_as_bits);
proof_variable.generate_r1cs_witness(proof);
online_verifier.generate_r1cs_witness();
pb2.val(result) = FieldO::one();
if (!(pb2.is_satisfied())) {
std::cerr << "oops " << __LINE__ << std::endl;
exit(1);
}
r1cs_ppzksnark_keypair<ppO> keypair2 = r1cs_ppzksnark_generator<ppO>(pb2.get_constraint_system());
r1cs_ppzksnark_processed_verification_key<ppO> pvk2 = r1cs_ppzksnark_verifier_process_vk<ppO>(keypair2.vk);
#if 0
{
libff::enter_block("Test serialization of keys");
keypair2.pk = libff::reserialize<r1cs_ppzksnark_proving_key<ppO> >(keypair2.pk);
keypair2.vk = libff::reserialize<r1cs_ppzksnark_verification_key<ppO> >(keypair2.vk);
pvk2 = libff::reserialize<r1cs_ppzksnark_processed_verification_key<ppO> >(pvk2);
libff::leave_block("Test serialization of keys");
}
#endif
r1cs_ppzksnark_proof<ppO> proof2 = r1cs_ppzksnark_prover<ppO>(keypair2.pk, pb2.primary_input(), pb2.auxiliary_input());
{
libff::enter_block("Test serialization of proof");
proof2 = libff::reserialize<r1cs_ppzksnark_proof<ppO> >(proof2);
libff::leave_block("Test serialization of proof");
}
libff::print_header("R1CS ppzkSNARK Verifier");
const bool ans3 = r1cs_ppzksnark_verifier_strong_IC<ppO>(keypair2.vk, pb2.primary_input(), proof2);
printf("* The verification result is: %s\n", (ans3 ? "PASS" : "FAIL"));
}
int main(void)
{
libff::start_profiling();
libff::mnt4_pp::init_public_params();
libff::mnt6_pp::init_public_params();
test<libff::mnt4_pp, libff::mnt6_pp, libff::Fr<libff::mnt4_pp>, libff::Fr<libff::mnt6_pp> >();
}
Result:
* PK size in bits: 3527132826
* G1 elements in VK: 152615
* G2 elements in VK: 5
* VK size in bits: 55403891
(enter) Call to r1cs_ppzksnark_verifier_process_vk [ ] (441.5563s x1.00 from start)
(enter) Call to mnt6_ate_precompute_G2 [ ] (441.5563s x1.00 from start)
(leave) Call to mnt6_ate_precompute_G2 [0.1182s x1.00] (441.6745s x1.00 from start)
(enter) Call to mnt6_ate_precompute_G2 [ ] (441.6746s x1.00 from start)
(leave) Call to mnt6_ate_precompute_G2 [0.0025s x1.00] (441.6771s x1.00 from start)
(enter) Call to mnt6_ate_precompute_G1 [ ] (441.6771s x1.00 from start)
(leave) Call to mnt6_ate_precompute_G1 [0.0000s x1.04] (441.6771s x1.00 from start)
(enter) Call to mnt6_ate_precompute_G2 [ ] (441.6771s x1.00 from start)
(leave) Call to mnt6_ate_precompute_G2 [0.0025s x1.00] (441.6796s x1.00 from start)
(enter) Call to mnt6_ate_precompute_G2 [ ] (441.6796s x1.00 from start)
(leave) Call to mnt6_ate_precompute_G2 [0.0025s x1.00] (441.6821s x1.00 from start)
(enter) Call to mnt6_ate_precompute_G2 [ ] (441.6821s x1.00 from start)
(leave) Call to mnt6_ate_precompute_G2 [0.0025s x1.00] (441.6846s x1.00 from start)
(enter) Call to mnt6_ate_precompute_G1 [ ] (441.6846s x1.00 from start)
(leave) Call to mnt6_ate_precompute_G1 [0.0000s x1.03] (441.6846s x1.00 from start)
(enter) Call to mnt6_ate_precompute_G2 [ ] (441.6846s x1.00 from start)
(leave) Call to mnt6_ate_precompute_G2 [0.0032s x1.00] (441.6878s x1.00 from start)
(leave) Call to r1cs_ppzksnark_verifier_process_vk [0.1354s x1.00] (441.6917s x1.00 from start)
(enter) Call to r1cs_ppzksnark_prover [ ] (441.7043s x1.00 from start)
(enter) Compute the polynomial H [ ] (441.7044s x1.00 from start)
(enter) Call to r1cs_to_qap_witness_map [ ] (441.7044s x1.00 from start)
(enter) Compute evaluation of polynomials A, B on set S [ ] (441.7163s x1.00 from start)
(leave) Compute evaluation of polynomials A, B on set S [0.4858s x1.00] (442.2020s x1.00 from start)
(enter) Compute coefficients of polynomial A [ ] (442.2021s x1.00 from start)
terminate called after throwing an instance of 'std::invalid_argument'
what(): libff::get_root_of_unity: expected logn <= FieldT::s
line 32: 12 Aborted
Update: By playing with it some more, I discovered that it works with the curves flipped around - mnt6 works in mnt4 but not vice versa (the way I have them above). Isn't it supposed to work either way though, and that's how recursive proofs are done?