From 54fab2217f437bb04a5e9fb71b271cf91b90c6e5 Mon Sep 17 00:00:00 2001 From: Blaine Bublitz Date: Wed, 8 Mar 2023 11:45:32 -0700 Subject: [PATCH] feat: Add Noir DSL with acir_format and turbo_proofs namespaces (#198) * feat: Add Noir DSL with acir_format and turbo_proofs namespaces * chore: Upstream changes to fix proving key serialization in turbo_proofs (#204) --------- Co-authored-by: kevaundray Co-authored-by: Maxim Vezenov --- cpp/src/aztec/CMakeLists.txt | 7 + cpp/src/aztec/dsl/CMakeLists.txt | 2 + cpp/src/aztec/dsl/acir_format/CMakeLists.txt | 11 + cpp/src/aztec/dsl/acir_format/acir_format.cpp | 410 ++++++++++++++++++ cpp/src/aztec/dsl/acir_format/acir_format.hpp | 92 ++++ .../dsl/acir_format/blake2s_constraint.cpp | 40 ++ .../dsl/acir_format/blake2s_constraint.hpp | 52 +++ .../aztec/dsl/acir_format/ecdsa_secp256k1.cpp | 120 +++++ .../aztec/dsl/acir_format/ecdsa_secp256k1.hpp | 53 +++ .../dsl/acir_format/fixed_base_scalar_mul.cpp | 18 + .../dsl/acir_format/fixed_base_scalar_mul.hpp | 33 ++ .../aztec/dsl/acir_format/hash_to_field.cpp | 42 ++ .../aztec/dsl/acir_format/hash_to_field.hpp | 52 +++ .../dsl/acir_format/logic_constraint.cpp | 24 + .../dsl/acir_format/logic_constraint.hpp | 43 ++ .../merkle_membership_constraint.cpp | 47 ++ .../merkle_membership_constraint.hpp | 39 ++ cpp/src/aztec/dsl/acir_format/pedersen.cpp | 23 + cpp/src/aztec/dsl/acir_format/pedersen.hpp | 34 ++ .../dsl/acir_format/range_constraint.hpp | 28 ++ cpp/src/aztec/dsl/acir_format/round.cpp | 22 + cpp/src/aztec/dsl/acir_format/round.hpp | 11 + .../aztec/dsl/acir_format/schnorr_verify.cpp | 95 ++++ .../aztec/dsl/acir_format/schnorr_verify.hpp | 50 +++ .../dsl/acir_format/sha256_constraint.cpp | 44 ++ .../dsl/acir_format/sha256_constraint.hpp | 54 +++ cpp/src/aztec/dsl/turbo_proofs/CMakeLists.txt | 5 + cpp/src/aztec/dsl/turbo_proofs/c_bind.cpp | 43 ++ cpp/src/aztec/dsl/turbo_proofs/c_bind.hpp | 24 + .../aztec/dsl/turbo_proofs/turbo_proofs.cpp | 128 ++++++ .../aztec/dsl/turbo_proofs/turbo_proofs.hpp | 18 + 31 files changed, 1664 insertions(+) create mode 100644 cpp/src/aztec/dsl/CMakeLists.txt create mode 100644 cpp/src/aztec/dsl/acir_format/CMakeLists.txt create mode 100644 cpp/src/aztec/dsl/acir_format/acir_format.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/acir_format.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/blake2s_constraint.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/blake2s_constraint.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/ecdsa_secp256k1.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/ecdsa_secp256k1.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/fixed_base_scalar_mul.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/fixed_base_scalar_mul.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/hash_to_field.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/hash_to_field.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/logic_constraint.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/logic_constraint.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/merkle_membership_constraint.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/merkle_membership_constraint.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/pedersen.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/pedersen.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/range_constraint.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/round.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/round.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/schnorr_verify.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/schnorr_verify.hpp create mode 100644 cpp/src/aztec/dsl/acir_format/sha256_constraint.cpp create mode 100644 cpp/src/aztec/dsl/acir_format/sha256_constraint.hpp create mode 100644 cpp/src/aztec/dsl/turbo_proofs/CMakeLists.txt create mode 100644 cpp/src/aztec/dsl/turbo_proofs/c_bind.cpp create mode 100644 cpp/src/aztec/dsl/turbo_proofs/c_bind.hpp create mode 100644 cpp/src/aztec/dsl/turbo_proofs/turbo_proofs.cpp create mode 100644 cpp/src/aztec/dsl/turbo_proofs/turbo_proofs.hpp diff --git a/cpp/src/aztec/CMakeLists.txt b/cpp/src/aztec/CMakeLists.txt index 6041a87e9fd..09d17e8bcc7 100644 --- a/cpp/src/aztec/CMakeLists.txt +++ b/cpp/src/aztec/CMakeLists.txt @@ -51,6 +51,7 @@ add_subdirectory(honk) add_subdirectory(plonk) add_subdirectory(stdlib) add_subdirectory(join_split_example) +add_subdirectory(dsl) if(BENCHMARKS) add_subdirectory(benchmark) @@ -83,6 +84,8 @@ if(WASM) $ $ $ + $ + $ ) # With binaryen installed, it seems its wasm backend optimiser gets invoked automatically. @@ -138,6 +141,8 @@ if(WASM) $ $ $ + $ + $ ) else() # For use when compiling dependent cpp projects @@ -165,6 +170,8 @@ else() $ $ $ + $ + $ $ ) diff --git a/cpp/src/aztec/dsl/CMakeLists.txt b/cpp/src/aztec/dsl/CMakeLists.txt new file mode 100644 index 00000000000..9e37be54e7d --- /dev/null +++ b/cpp/src/aztec/dsl/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(acir_format) +add_subdirectory(turbo_proofs) diff --git a/cpp/src/aztec/dsl/acir_format/CMakeLists.txt b/cpp/src/aztec/dsl/acir_format/CMakeLists.txt new file mode 100644 index 00000000000..1b0d0c22638 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/CMakeLists.txt @@ -0,0 +1,11 @@ +barretenberg_module( + acir_format + plonk + stdlib_primitives + stdlib_sha256 + stdlib_blake2s + stdlib_pedersen + stdlib_merkle_tree + stdlib_schnorr + crypto_sha256 +) diff --git a/cpp/src/aztec/dsl/acir_format/acir_format.cpp b/cpp/src/aztec/dsl/acir_format/acir_format.cpp new file mode 100644 index 00000000000..4a411a1e324 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/acir_format.cpp @@ -0,0 +1,410 @@ +#include "acir_format.hpp" + +namespace acir_format { + +void read_witness(TurboComposer& composer, std::vector witness) +{ + composer.variables[0] = 0; + for (size_t i = 0; i < witness.size(); ++i) { + composer.variables[i + 1] = witness[i]; + } +} + +void create_circuit(TurboComposer& composer, const acir_format& constraint_system) +{ + if (constraint_system.public_inputs.size() > constraint_system.varnum) { + std::cout << "too many public inputs!" << std::endl; + } + + for (size_t i = 1; i < constraint_system.varnum; ++i) { + // If the index is in the public inputs vector, then we add it as a public input + + if (std::find(constraint_system.public_inputs.begin(), constraint_system.public_inputs.end(), i) != + constraint_system.public_inputs.end()) { + composer.add_public_variable(0); + + } else { + composer.add_variable(0); + } + } + + // Add arithmetic gates + for (const auto& constraint : constraint_system.constraints) { + composer.create_poly_gate(constraint); + } + + // Add and constraint + for (const auto& constraint : constraint_system.logic_constraints) { + create_logic_gate( + composer, constraint.a, constraint.b, constraint.result, constraint.num_bits, constraint.is_xor_gate); + } + + // Add range constraint + for (const auto& constraint : constraint_system.range_constraints) { + composer.decompose_into_base4_accumulators(constraint.witness, constraint.num_bits, ""); + } + + // Add sha256 constraints + for (const auto& constraint : constraint_system.sha256_constraints) { + create_sha256_constraints(composer, constraint); + } + + // Add merkle membership constraints + for (const auto& constraint : constraint_system.merkle_membership_constraints) { + create_merkle_check_membership_constraint(composer, constraint); + } + + // Add schnorr constraints + for (const auto& constraint : constraint_system.schnorr_constraints) { + create_schnorr_verify_constraints(composer, constraint); + } + + // Add ECDSA constraints + for (const auto& constraint : constraint_system.ecdsa_constraints) { + create_ecdsa_verify_constraints(composer, constraint); + } + + // Add blake2s constraints + for (const auto& constraint : constraint_system.blake2s_constraints) { + create_blake2s_constraints(composer, constraint); + } + + // Add pedersen constraints + for (const auto& constraint : constraint_system.pedersen_constraints) { + create_pedersen_constraint(composer, constraint); + } + + // Add fixed base scalar mul constraints + for (const auto& constraint : constraint_system.fixed_base_scalar_mul_constraints) { + create_fixed_base_constraint(composer, constraint); + } + + // Add hash to field constraints + for (const auto& constraint : constraint_system.hash_to_field_constraints) { + create_hash_to_field_constraints(composer, constraint); + } +} + +TurboComposer create_circuit(const acir_format& constraint_system, + std::unique_ptr&& crs_factory) +{ + if (constraint_system.public_inputs.size() > constraint_system.varnum) { + std::cout << "too many public inputs!" << std::endl; + } + + TurboComposer composer(std::move(crs_factory)); + + for (size_t i = 1; i < constraint_system.varnum; ++i) { + // If the index is in the public inputs vector, then we add it as a public input + + if (std::find(constraint_system.public_inputs.begin(), constraint_system.public_inputs.end(), i) != + constraint_system.public_inputs.end()) { + + composer.add_public_variable(0); + + } else { + composer.add_variable(0); + } + } + // Add arithmetic gates + for (const auto& constraint : constraint_system.constraints) { + composer.create_poly_gate(constraint); + } + + // Add logic constraint + for (const auto& constraint : constraint_system.logic_constraints) { + create_logic_gate( + composer, constraint.a, constraint.b, constraint.result, constraint.num_bits, constraint.is_xor_gate); + } + + // Add range constraint + for (const auto& constraint : constraint_system.range_constraints) { + composer.decompose_into_base4_accumulators(constraint.witness, constraint.num_bits, ""); + } + + // Add sha256 constraints + for (const auto& constraint : constraint_system.sha256_constraints) { + create_sha256_constraints(composer, constraint); + } + + // Add merkle membership constraints + for (const auto& constraint : constraint_system.merkle_membership_constraints) { + create_merkle_check_membership_constraint(composer, constraint); + } + + // Add schnorr constraints + for (const auto& constraint : constraint_system.schnorr_constraints) { + create_schnorr_verify_constraints(composer, constraint); + } + + // Add ECDSA constraints + for (const auto& constraint : constraint_system.ecdsa_constraints) { + create_ecdsa_verify_constraints(composer, constraint); + } + + // Add blake2s constraints + for (const auto& constraint : constraint_system.blake2s_constraints) { + create_blake2s_constraints(composer, constraint); + } + + // Add pedersen constraints + for (const auto& constraint : constraint_system.pedersen_constraints) { + create_pedersen_constraint(composer, constraint); + } + + // Add fixed base scalar mul constraints + for (const auto& constraint : constraint_system.fixed_base_scalar_mul_constraints) { + create_fixed_base_constraint(composer, constraint); + } + + // Add hash to field constraints + for (const auto& constraint : constraint_system.hash_to_field_constraints) { + create_hash_to_field_constraints(composer, constraint); + } + + return composer; +} + +TurboComposer create_circuit_with_witness(const acir_format& constraint_system, + std::vector witness, + std::unique_ptr&& crs_factory) +{ + if (constraint_system.public_inputs.size() > constraint_system.varnum) { + std::cout << "too many public inputs!" << std::endl; + } + + TurboComposer composer(std::move(crs_factory)); + + for (size_t i = 1; i < constraint_system.varnum; ++i) { + // If the index is in the public inputs vector, then we add it as a public input + + if (std::find(constraint_system.public_inputs.begin(), constraint_system.public_inputs.end(), i) != + constraint_system.public_inputs.end()) { + + composer.add_public_variable(0); + + } else { + composer.add_variable(0); + } + } + + read_witness(composer, witness); + + // Add arithmetic gates + for (const auto& constraint : constraint_system.constraints) { + composer.create_poly_gate(constraint); + } + + // Add logic constraint + for (const auto& constraint : constraint_system.logic_constraints) { + create_logic_gate( + composer, constraint.a, constraint.b, constraint.result, constraint.num_bits, constraint.is_xor_gate); + } + + // Add range constraint + for (const auto& constraint : constraint_system.range_constraints) { + composer.decompose_into_base4_accumulators(constraint.witness, constraint.num_bits, ""); + } + + // Add sha256 constraints + for (const auto& constraint : constraint_system.sha256_constraints) { + create_sha256_constraints(composer, constraint); + } + + // Add merkle membership constraints + for (const auto& constraint : constraint_system.merkle_membership_constraints) { + create_merkle_check_membership_constraint(composer, constraint); + } + + // Add schnorr constraints + for (const auto& constraint : constraint_system.schnorr_constraints) { + create_schnorr_verify_constraints(composer, constraint); + } + + // Add ECDSA constraints + for (const auto& constraint : constraint_system.ecdsa_constraints) { + create_ecdsa_verify_constraints(composer, constraint); + } + + // Add blake2s constraints + for (const auto& constraint : constraint_system.blake2s_constraints) { + create_blake2s_constraints(composer, constraint); + } + + // Add pedersen constraints + for (const auto& constraint : constraint_system.pedersen_constraints) { + create_pedersen_constraint(composer, constraint); + } + + // Add fixed base scalar mul constraints + for (const auto& constraint : constraint_system.fixed_base_scalar_mul_constraints) { + create_fixed_base_constraint(composer, constraint); + } + + // Add hash to field constraints + for (const auto& constraint : constraint_system.hash_to_field_constraints) { + create_hash_to_field_constraints(composer, constraint); + } + + return composer; +} +TurboComposer create_circuit_with_witness(const acir_format& constraint_system, std::vector witness) +{ + if (constraint_system.public_inputs.size() > constraint_system.varnum) { + std::cout << "too many public inputs!" << std::endl; + } + + auto composer = TurboComposer(); + + for (size_t i = 1; i < constraint_system.varnum; ++i) { + // If the index is in the public inputs vector, then we add it as a public input + + if (std::find(constraint_system.public_inputs.begin(), constraint_system.public_inputs.end(), i) != + constraint_system.public_inputs.end()) { + + composer.add_public_variable(0); + + } else { + composer.add_variable(0); + } + } + + read_witness(composer, witness); + + // Add arithmetic gates + for (const auto& constraint : constraint_system.constraints) { + composer.create_poly_gate(constraint); + } + + // Add logic constraint + for (const auto& constraint : constraint_system.logic_constraints) { + create_logic_gate( + composer, constraint.a, constraint.b, constraint.result, constraint.num_bits, constraint.is_xor_gate); + } + + // Add range constraint + for (const auto& constraint : constraint_system.range_constraints) { + composer.decompose_into_base4_accumulators(constraint.witness, constraint.num_bits, ""); + } + + // Add sha256 constraints + for (const auto& constraint : constraint_system.sha256_constraints) { + create_sha256_constraints(composer, constraint); + } + + // Add merkle membership constraints + for (const auto& constraint : constraint_system.merkle_membership_constraints) { + create_merkle_check_membership_constraint(composer, constraint); + } + + // Add schnorr constraints + for (const auto& constraint : constraint_system.schnorr_constraints) { + create_schnorr_verify_constraints(composer, constraint); + } + + // Add ECDSA constraints + for (const auto& constraint : constraint_system.ecdsa_constraints) { + create_ecdsa_verify_constraints(composer, constraint); + } + + // Add blake2s constraints + for (const auto& constraint : constraint_system.blake2s_constraints) { + create_blake2s_constraints(composer, constraint); + } + + // Add pedersen constraints + for (const auto& constraint : constraint_system.pedersen_constraints) { + create_pedersen_constraint(composer, constraint); + } + + // Add fixed base scalar mul constraints + for (const auto& constraint : constraint_system.fixed_base_scalar_mul_constraints) { + create_fixed_base_constraint(composer, constraint); + } + + // Add hash to field constraints + for (const auto& constraint : constraint_system.hash_to_field_constraints) { + create_hash_to_field_constraints(composer, constraint); + } + + return composer; +} +void create_circuit_with_witness(TurboComposer& composer, const acir_format& constraint_system, std::vector witness) +{ + if (constraint_system.public_inputs.size() > constraint_system.varnum) { + std::cout << "too many public inputs!" << std::endl; + } + + for (size_t i = 1; i < constraint_system.varnum; ++i) { + // If the index is in the public inputs vector, then we add it as a public input + + if (std::find(constraint_system.public_inputs.begin(), constraint_system.public_inputs.end(), i) != + constraint_system.public_inputs.end()) { + + composer.add_public_variable(0); + + } else { + composer.add_variable(0); + } + } + + read_witness(composer, witness); + + // Add arithmetic gates + for (const auto& constraint : constraint_system.constraints) { + composer.create_poly_gate(constraint); + } + + // Add logic constraint + for (const auto& constraint : constraint_system.logic_constraints) { + create_logic_gate( + composer, constraint.a, constraint.b, constraint.result, constraint.num_bits, constraint.is_xor_gate); + } + + // Add range constraint + for (const auto& constraint : constraint_system.range_constraints) { + composer.decompose_into_base4_accumulators(constraint.witness, constraint.num_bits, ""); + } + + // Add sha256 constraints + for (const auto& constraint : constraint_system.sha256_constraints) { + create_sha256_constraints(composer, constraint); + } + + // Add merkle membership constraints + for (const auto& constraint : constraint_system.merkle_membership_constraints) { + create_merkle_check_membership_constraint(composer, constraint); + } + + // Add schnorr constraints + for (const auto& constraint : constraint_system.schnorr_constraints) { + create_schnorr_verify_constraints(composer, constraint); + } + + // Add ECDSA constraints + for (const auto& constraint : constraint_system.ecdsa_constraints) { + create_ecdsa_verify_constraints(composer, constraint); + } + + // Add blake2s constraints + for (const auto& constraint : constraint_system.blake2s_constraints) { + create_blake2s_constraints(composer, constraint); + } + + // Add pedersen constraints + for (const auto& constraint : constraint_system.pedersen_constraints) { + create_pedersen_constraint(composer, constraint); + } + + // Add fixed base scalar mul constraints + for (const auto& constraint : constraint_system.fixed_base_scalar_mul_constraints) { + create_fixed_base_constraint(composer, constraint); + } + + // Add hash to field constraints + for (const auto& constraint : constraint_system.hash_to_field_constraints) { + create_hash_to_field_constraints(composer, constraint); + } +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/acir_format.hpp b/cpp/src/aztec/dsl/acir_format/acir_format.hpp new file mode 100644 index 00000000000..17c40441d8c --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/acir_format.hpp @@ -0,0 +1,92 @@ +#pragma once +#include "logic_constraint.hpp" +#include "range_constraint.hpp" +#include "sha256_constraint.hpp" +#include "blake2s_constraint.hpp" +#include "fixed_base_scalar_mul.hpp" +#include "schnorr_verify.hpp" +#include "ecdsa_secp256k1.hpp" +#include "merkle_membership_constraint.hpp" +#include "pedersen.hpp" +#include "hash_to_field.hpp" + +namespace acir_format { + +struct acir_format { + // The number of witnesses in the circuit + uint32_t varnum; + + std::vector public_inputs; + + std::vector fixed_base_scalar_mul_constraints; + std::vector logic_constraints; + std::vector range_constraints; + std::vector schnorr_constraints; + std::vector ecdsa_constraints; + std::vector sha256_constraints; + std::vector blake2s_constraints; + std::vector hash_to_field_constraints; + std::vector pedersen_constraints; + std::vector merkle_membership_constraints; + // A standard plonk arithmetic constraint, as defined in the poly_triple struct, consists of selector values + // for q_M,q_L,q_R,q_O,q_C and indices of three variables taking the role of left, right and output wire + std::vector constraints; + + friend bool operator==(acir_format const& lhs, acir_format const& rhs) = default; +}; + +void read_witness(TurboComposer& composer, std::vector witness); + +void create_circuit(TurboComposer& composer, const acir_format& constraint_system); + +TurboComposer create_circuit(const acir_format& constraint_system, + std::unique_ptr&& crs_factory); + +TurboComposer create_circuit_with_witness(const acir_format& constraint_system, + std::vector witness, + std::unique_ptr&& crs_factory); + +TurboComposer create_circuit_with_witness(const acir_format& constraint_system, std::vector witness); + +void create_circuit_with_witness(TurboComposer& composer, + const acir_format& constraint_system, + std::vector witness); + +// Serialisation +template inline void read(B& buf, acir_format& data) +{ + using serialize::read; + read(buf, data.varnum); + read(buf, data.public_inputs); + read(buf, data.logic_constraints); + read(buf, data.range_constraints); + read(buf, data.sha256_constraints); + read(buf, data.merkle_membership_constraints); + read(buf, data.schnorr_constraints); + read(buf, data.ecdsa_constraints); + read(buf, data.blake2s_constraints); + read(buf, data.pedersen_constraints); + read(buf, data.hash_to_field_constraints); + read(buf, data.fixed_base_scalar_mul_constraints); + read(buf, data.constraints); +} + +template inline void write(B& buf, acir_format const& data) +{ + using serialize::write; + write(buf, data.varnum); + write(buf, data.public_inputs); + write(buf, data.logic_constraints); + write(buf, data.range_constraints); + write(buf, data.sha256_constraints); + write(buf, data.merkle_membership_constraints); + write(buf, data.schnorr_constraints); + write(buf, data.ecdsa_constraints); + write(buf, data.blake2s_constraints); + write(buf, data.pedersen_constraints); + write(buf, data.hash_to_field_constraints); + write(buf, data.fixed_base_scalar_mul_constraints); + write(buf, data.constraints); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/blake2s_constraint.cpp b/cpp/src/aztec/dsl/acir_format/blake2s_constraint.cpp new file mode 100644 index 00000000000..3cf5540b40b --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/blake2s_constraint.cpp @@ -0,0 +1,40 @@ +#include "blake2s_constraint.hpp" +#include "round.hpp" +#include "stdlib/types/types.hpp" + +using namespace plonk::stdlib::types; + +namespace acir_format { + +void create_blake2s_constraints(plonk::TurboComposer& composer, const Blake2sConstraint& constraint) +{ + + // Create byte array struct + byte_array_ct arr(&composer); + + // Get the witness assignment for each witness index + // Write the witness assignment to the byte_array + for (const auto& witness_index_num_bits : constraint.inputs) { + auto witness_index = witness_index_num_bits.witness; + auto num_bits = witness_index_num_bits.num_bits; + + // XXX: The implementation requires us to truncate the element to the nearest byte and not bit + auto num_bytes = round_to_nearest_byte(num_bits); + + field_ct element = field_ct::from_witness_index(&composer, witness_index); + byte_array_ct element_bytes(element, num_bytes); + + arr.write(element_bytes); + } + + byte_array_ct output_bytes = plonk::stdlib::blake2s(arr); + + // Convert byte array to vector of field_t + auto bytes = output_bytes.bytes(); + + for (size_t i = 0; i < bytes.size(); ++i) { + composer.assert_equal(bytes[i].normalize().witness_index, constraint.result[i]); + } +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/blake2s_constraint.hpp b/cpp/src/aztec/dsl/acir_format/blake2s_constraint.hpp new file mode 100644 index 00000000000..e6bcf11da09 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/blake2s_constraint.hpp @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +struct Blake2sInput { + uint32_t witness; + uint32_t num_bits; + + friend bool operator==(Blake2sInput const& lhs, Blake2sInput const& rhs) = default; +}; + +struct Blake2sConstraint { + std::vector inputs; + std::vector result; + + friend bool operator==(Blake2sConstraint const& lhs, Blake2sConstraint const& rhs) = default; +}; + +void create_blake2s_constraints(plonk::TurboComposer& composer, const Blake2sConstraint& constraint); + +template inline void read(B& buf, Blake2sInput& constraint) +{ + using serialize::read; + read(buf, constraint.witness); + read(buf, constraint.num_bits); +} + +template inline void write(B& buf, Blake2sInput const& constraint) +{ + using serialize::write; + write(buf, constraint.witness); + write(buf, constraint.num_bits); +} + +template inline void read(B& buf, Blake2sConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.inputs); + read(buf, constraint.result); +} + +template inline void write(B& buf, Blake2sConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.inputs); + write(buf, constraint.result); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/ecdsa_secp256k1.cpp b/cpp/src/aztec/dsl/acir_format/ecdsa_secp256k1.cpp new file mode 100644 index 00000000000..94b9b0478ad --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/ecdsa_secp256k1.cpp @@ -0,0 +1,120 @@ +#include "ecdsa_secp256k1.hpp" +#include "crypto/ecdsa/ecdsa.hpp" +#include "stdlib/encryption/ecdsa/ecdsa.hpp" +#include "stdlib/types/types.hpp" + +using namespace plonk::stdlib::types; + +namespace acir_format { + +crypto::ecdsa::signature ecdsa_convert_signature(plonk::TurboComposer& composer, std::vector signature) +{ + + crypto::ecdsa::signature signature_cr; + + // Get the witness assignment for each witness index + // Write the witness assignment to the byte_array + + for (unsigned int i = 0; i < 32; i++) { + auto witness_index = signature[i]; + + std::vector fr_bytes(sizeof(fr)); + + fr value = composer.get_variable(witness_index); + + fr::serialize_to_buffer(value, &fr_bytes[0]); + + signature_cr.r[i] = fr_bytes.back(); + } + + for (unsigned int i = 32; i < 64; i++) { + auto witness_index = signature[i]; + + std::vector fr_bytes(sizeof(fr)); + + fr value = composer.get_variable(witness_index); + + fr::serialize_to_buffer(value, &fr_bytes[0]); + + signature_cr.s[i - 32] = fr_bytes.back(); + } + + return signature_cr; +} + +secp256k1_ct::g1_ct ecdsa_convert_inputs(plonk::TurboComposer* ctx, const secp256k1::g1::affine_element& input) +{ + uint256_t x_u256(input.x); + uint256_t y_u256(input.y); + secp256k1_ct::fq_ct x(witness_ct(ctx, barretenberg::fr(x_u256.slice(0, secp256k1_ct::fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(ctx, + barretenberg::fr(x_u256.slice(secp256k1_ct::fq_ct::NUM_LIMB_BITS * 2, + secp256k1_ct::fq_ct::NUM_LIMB_BITS * 4)))); + secp256k1_ct::fq_ct y(witness_ct(ctx, barretenberg::fr(y_u256.slice(0, secp256k1_ct::fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(ctx, + barretenberg::fr(y_u256.slice(secp256k1_ct::fq_ct::NUM_LIMB_BITS * 2, + secp256k1_ct::fq_ct::NUM_LIMB_BITS * 4)))); + + return { x, y }; +} + +// vector of bytes here, assumes that the witness indices point to a field element which can be represented +// with just a byte. +// notice that this function truncates each field_element to a byte +byte_array_ct ecdsa_vector_of_bytes_to_byte_array(plonk::TurboComposer& composer, std::vector vector_of_bytes) +{ + byte_array_ct arr(&composer); + + // Get the witness assignment for each witness index + // Write the witness assignment to the byte_array + for (const auto& witness_index : vector_of_bytes) { + + field_ct element = field_ct::from_witness_index(&composer, witness_index); + size_t num_bytes = 1; + + byte_array_ct element_bytes(element, num_bytes); + arr.write(element_bytes); + } + return arr; +} +witness_ct ecdsa_index_to_witness(plonk::TurboComposer& composer, uint32_t index) +{ + fr value = composer.get_variable(index); + return { &composer, value }; +} + +void create_ecdsa_verify_constraints(plonk::TurboComposer& composer, const EcdsaSecp256k1Constraint& input) +{ + + auto new_sig = ecdsa_convert_signature(composer, input.signature); + + auto message = ecdsa_vector_of_bytes_to_byte_array(composer, input.message); + auto pub_key_x_byte_arr = ecdsa_vector_of_bytes_to_byte_array(composer, input.pub_x_indices); + auto pub_key_y_byte_arr = ecdsa_vector_of_bytes_to_byte_array(composer, input.pub_y_indices); + + auto pub_key_x_fq = secp256k1_ct::fq_ct(pub_key_x_byte_arr); + auto pub_key_y_fq = secp256k1_ct::fq_ct(pub_key_y_byte_arr); + + std::vector rr(new_sig.r.begin(), new_sig.r.end()); + std::vector ss(new_sig.s.begin(), new_sig.s.end()); + + stdlib::ecdsa::signature sig{ stdlib::byte_array(&composer, rr), + stdlib::byte_array(&composer, ss) }; + + pub_key_x_fq.assert_is_in_field(); + pub_key_y_fq.assert_is_in_field(); + + secp256k1_ct::g1_bigfr_ct public_key = secp256k1_ct::g1_bigfr_ct(pub_key_x_fq, pub_key_y_fq); + + bool_ct signature_result = stdlib::ecdsa::verify_signature(message, public_key, sig); + + bool_ct signature_result_normalized = signature_result.normalize(); + + composer.assert_equal(signature_result_normalized.witness_index, input.result); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/ecdsa_secp256k1.hpp b/cpp/src/aztec/dsl/acir_format/ecdsa_secp256k1.hpp new file mode 100644 index 00000000000..20b053697b4 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/ecdsa_secp256k1.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +struct EcdsaSecp256k1Constraint { + // This is just a bunch of bytes + // which need to be interpreted as a string + // Note this must be a bunch of bytes + std::vector message; + + // This is the supposed public key which signed the + // message, giving rise to the signature. + // Since Fr does not have enough bits to represent + // the prime field in secp256k1, a byte array is used. + // Can also use low and hi where lo=128 bits + std::vector pub_x_indices; + std::vector pub_y_indices; + + // This is the result of verifying the signature + uint32_t result; + + // This is the computed signature + // + std::vector signature; + + friend bool operator==(EcdsaSecp256k1Constraint const& lhs, EcdsaSecp256k1Constraint const& rhs) = default; +}; + +void create_ecdsa_verify_constraints(plonk::TurboComposer& composer, const EcdsaSecp256k1Constraint& input); + +template inline void read(B& buf, EcdsaSecp256k1Constraint& constraint) +{ + using serialize::read; + read(buf, constraint.message); + read(buf, constraint.signature); + read(buf, constraint.pub_x_indices); + read(buf, constraint.pub_y_indices); + read(buf, constraint.result); +} + +template inline void write(B& buf, EcdsaSecp256k1Constraint const& constraint) +{ + using serialize::write; + write(buf, constraint.message); + write(buf, constraint.signature); + write(buf, constraint.pub_x_indices); + write(buf, constraint.pub_y_indices); + write(buf, constraint.result); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/fixed_base_scalar_mul.cpp b/cpp/src/aztec/dsl/acir_format/fixed_base_scalar_mul.cpp new file mode 100644 index 00000000000..033fea2f639 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/fixed_base_scalar_mul.cpp @@ -0,0 +1,18 @@ +#include "fixed_base_scalar_mul.hpp" +#include "stdlib/types/types.hpp" + +using namespace plonk::stdlib::types; + +namespace acir_format { + +void create_fixed_base_constraint(plonk::TurboComposer& composer, const FixedBaseScalarMul& input) +{ + + field_ct scalar_as_field = field_ct::from_witness_index(&composer, input.scalar); + auto public_key = group_ct::fixed_base_scalar_mul_g1<254>(scalar_as_field); + + composer.assert_equal(public_key.x.witness_index, input.pub_key_x); + composer.assert_equal(public_key.y.witness_index, input.pub_key_y); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/fixed_base_scalar_mul.hpp b/cpp/src/aztec/dsl/acir_format/fixed_base_scalar_mul.hpp new file mode 100644 index 00000000000..33e9021541f --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/fixed_base_scalar_mul.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +struct FixedBaseScalarMul { + uint32_t scalar; + uint32_t pub_key_x; + uint32_t pub_key_y; + + friend bool operator==(FixedBaseScalarMul const& lhs, FixedBaseScalarMul const& rhs) = default; +}; + +void create_fixed_base_constraint(plonk::TurboComposer& composer, const FixedBaseScalarMul& input); + +template inline void read(B& buf, FixedBaseScalarMul& constraint) +{ + using serialize::read; + read(buf, constraint.scalar); + read(buf, constraint.pub_key_x); + read(buf, constraint.pub_key_y); +} + +template inline void write(B& buf, FixedBaseScalarMul const& constraint) +{ + using serialize::write; + write(buf, constraint.scalar); + write(buf, constraint.pub_key_x); + write(buf, constraint.pub_key_y); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/hash_to_field.cpp b/cpp/src/aztec/dsl/acir_format/hash_to_field.cpp new file mode 100644 index 00000000000..11fc6568358 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/hash_to_field.cpp @@ -0,0 +1,42 @@ +#include "hash_to_field.hpp" +#include "round.hpp" +#include "stdlib/types/types.hpp" + +using namespace plonk::stdlib::types; + +namespace acir_format { + +void create_hash_to_field_constraints(plonk::TurboComposer& composer, const HashToFieldConstraint constraint) +{ + + // Create byte array struct + byte_array_ct arr(&composer); + + // Get the witness assignment for each witness index + // Write the witness assignment to the byte_array + for (const auto& witness_index_num_bits : constraint.inputs) { + auto witness_index = witness_index_num_bits.witness; + auto num_bits = witness_index_num_bits.num_bits; + + // XXX: The implementation requires us to truncate the element to the nearest byte and not bit + auto num_bytes = round_to_nearest_byte(num_bits); + + field_ct element = field_ct::from_witness_index(&composer, witness_index); + byte_array_ct element_bytes(element, num_bytes); + byte_array_ct reversed_bytes = element_bytes.reverse(); + + arr.write(reversed_bytes); + } + + // Hash To Field using blake2s. + // Note: It does not need to be blake2s in the future + + byte_array_ct out_bytes = plonk::stdlib::blake2s(arr); + + field_ct out(out_bytes); + field_ct normalised_out = out.normalize(); + + composer.assert_equal(normalised_out.witness_index, constraint.result); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/hash_to_field.hpp b/cpp/src/aztec/dsl/acir_format/hash_to_field.hpp new file mode 100644 index 00000000000..1953ba90ef6 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/hash_to_field.hpp @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +struct HashToFieldInput { + uint32_t witness; + uint32_t num_bits; + + friend bool operator==(HashToFieldInput const& lhs, HashToFieldInput const& rhs) = default; +}; + +struct HashToFieldConstraint { + std::vector inputs; + uint32_t result; + + friend bool operator==(HashToFieldConstraint const& lhs, HashToFieldConstraint const& rhs) = default; +}; + +void create_hash_to_field_constraints(plonk::TurboComposer& composer, HashToFieldConstraint constraint); + +template inline void read(B& buf, HashToFieldInput& constraint) +{ + using serialize::read; + read(buf, constraint.witness); + read(buf, constraint.num_bits); +} + +template inline void write(B& buf, HashToFieldInput const& constraint) +{ + using serialize::write; + write(buf, constraint.witness); + write(buf, constraint.num_bits); +} + +template inline void read(B& buf, HashToFieldConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.inputs); + read(buf, constraint.result); +} + +template inline void write(B& buf, HashToFieldConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.inputs); + write(buf, constraint.result); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/logic_constraint.cpp b/cpp/src/aztec/dsl/acir_format/logic_constraint.cpp new file mode 100644 index 00000000000..27d938fbf6e --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/logic_constraint.cpp @@ -0,0 +1,24 @@ +#include "logic_constraint.hpp" + +namespace acir_format { + +void create_logic_gate(TurboComposer& composer, + const uint32_t a, + const uint32_t b, + const uint32_t result, + const size_t num_bits, + const bool is_xor_gate) +{ + auto accumulators = composer.create_logic_constraint(a, b, num_bits, is_xor_gate); + composer.assert_equal(accumulators.out.back(), result); +} +void xor_gate(TurboComposer& composer, const uint32_t a, const uint32_t b, const uint32_t result, const size_t num_bits) +{ + create_logic_gate(composer, a, b, result, num_bits, true); +} +void and_gate(TurboComposer& composer, const uint32_t a, const uint32_t b, const uint32_t result, const size_t num_bits) +{ + create_logic_gate(composer, a, b, result, num_bits, false); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/logic_constraint.hpp b/cpp/src/aztec/dsl/acir_format/logic_constraint.hpp new file mode 100644 index 00000000000..bb71507df66 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/logic_constraint.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +struct LogicConstraint { + uint32_t a; + uint32_t b; + uint32_t result; + uint32_t num_bits; + uint32_t is_xor_gate; + + friend bool operator==(LogicConstraint const& lhs, LogicConstraint const& rhs) = default; +}; + +void create_logic_gate( + TurboComposer& composer, uint32_t a, uint32_t b, uint32_t result, size_t num_bits, bool is_xor_gate); + +void xor_gate(TurboComposer& composer, uint32_t a, uint32_t b, uint32_t result, size_t num_bits); + +void and_gate(TurboComposer& composer, uint32_t a, uint32_t b, uint32_t result, size_t num_bits); + +template inline void read(B& buf, LogicConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.a); + read(buf, constraint.b); + read(buf, constraint.result); + read(buf, constraint.num_bits); + read(buf, constraint.is_xor_gate); +} + +template inline void write(B& buf, LogicConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.a); + write(buf, constraint.b); + write(buf, constraint.result); + write(buf, constraint.num_bits); + write(buf, constraint.is_xor_gate); +} +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/merkle_membership_constraint.cpp b/cpp/src/aztec/dsl/acir_format/merkle_membership_constraint.cpp new file mode 100644 index 00000000000..a2caaaf4ea7 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/merkle_membership_constraint.cpp @@ -0,0 +1,47 @@ +#include "merkle_membership_constraint.hpp" +#include "stdlib/merkle_tree/membership.hpp" +#include "stdlib/types/types.hpp" + +using namespace plonk::stdlib::types; +using namespace plonk::stdlib::merkle_tree; + +namespace acir_format { + +void create_merkle_check_membership_constraint(plonk::TurboComposer& composer, const MerkleMembershipConstraint& input) +{ + // Convert value from a witness index into a field element. + // This is the hash of the message. In Barretenberg, this would be input.value = hash_value(message) + field_ct leaf = field_ct::from_witness_index(&composer, input.leaf); + + // Convert index from a witness index into a byte array + field_ct index_field = field_ct::from_witness_index(&composer, input.index); + auto index_bits = index_field.decompose_into_bits(); + + // Convert root into a field_ct + field_ct root = field_ct::from_witness_index(&composer, input.root); + + // We are given the HashPath as a Vec + // We want to first convert it into a Vec<(fr, fr)> then cast this to hash_path + // struct which requires the method create_witness_hashpath + hash_path hash_path; + + // In Noir we accept a hash path that only contains one hash per tree level + // It is ok to reuse the leaf as it will be overridden in check_subtree_membership when computing the current root + // at each tree level + for (size_t i = 0; i < input.hash_path.size(); i++) { + if (!index_bits[i].get_value()) { + field_ct left = leaf; + field_ct right = field_ct::from_witness_index(&composer, input.hash_path[i]); + hash_path.push_back(std::make_pair(left, right)); + } else { + field_ct left = field_ct::from_witness_index(&composer, input.hash_path[i]); + field_ct right = leaf; + hash_path.push_back(std::make_pair(left, right)); + } + } + + auto exists = check_subtree_membership(root, hash_path, leaf, index_bits, 0); + composer.assert_equal_constant(exists.witness_index, fr::one()); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/merkle_membership_constraint.hpp b/cpp/src/aztec/dsl/acir_format/merkle_membership_constraint.hpp new file mode 100644 index 00000000000..f54ce979fcf --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/merkle_membership_constraint.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +struct MerkleMembershipConstraint { + std::vector hash_path; // Vector of pairs of hashpaths. eg indices 0,1 denotes the pair (0,1) + uint32_t root; // Single field element -- field_t + uint32_t leaf; // Single field element -- field_t + uint32_t result; // Single field element -- bool_t + uint32_t index; + + friend bool operator==(MerkleMembershipConstraint const& lhs, MerkleMembershipConstraint const& rhs) = default; +}; + +void create_merkle_check_membership_constraint(plonk::TurboComposer& composer, const MerkleMembershipConstraint& input); + +template inline void read(B& buf, MerkleMembershipConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.hash_path); + read(buf, constraint.root); + read(buf, constraint.leaf); + read(buf, constraint.result); + read(buf, constraint.index); +} + +template inline void write(B& buf, MerkleMembershipConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.hash_path); + write(buf, constraint.root); + write(buf, constraint.leaf); + write(buf, constraint.result); + write(buf, constraint.index); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/pedersen.cpp b/cpp/src/aztec/dsl/acir_format/pedersen.cpp new file mode 100644 index 00000000000..b8243ccd12e --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/pedersen.cpp @@ -0,0 +1,23 @@ +#include "pedersen.hpp" +#include "stdlib/types/types.hpp" + +using namespace plonk::stdlib::types; + +namespace acir_format { + +void create_pedersen_constraint(plonk::TurboComposer& composer, const PedersenConstraint& input) +{ + std::vector scalars; + + for (const auto& scalar : input.scalars) { + // convert input indices to field_ct + field_ct scalar_as_field = field_ct::from_witness_index(&composer, scalar); + scalars.push_back(scalar_as_field); + } + auto point = pedersen::commit(scalars); + + composer.assert_equal(point.x.witness_index, input.result_x); + composer.assert_equal(point.y.witness_index, input.result_y); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/pedersen.hpp b/cpp/src/aztec/dsl/acir_format/pedersen.hpp new file mode 100644 index 00000000000..142bdce861d --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/pedersen.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +// P = xG + bH +struct PedersenConstraint { + std::vector scalars; + uint32_t result_x; + uint32_t result_y; + + friend bool operator==(PedersenConstraint const& lhs, PedersenConstraint const& rhs) = default; +}; + +void create_pedersen_constraint(plonk::TurboComposer& composer, const PedersenConstraint& input); + +template inline void read(B& buf, PedersenConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.scalars); + read(buf, constraint.result_x); + read(buf, constraint.result_y); +} + +template inline void write(B& buf, PedersenConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.scalars); + read(buf, constraint.result_x); + read(buf, constraint.result_y); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/range_constraint.hpp b/cpp/src/aztec/dsl/acir_format/range_constraint.hpp new file mode 100644 index 00000000000..636653f2191 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/range_constraint.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include "common/serialize.hpp" + +namespace acir_format { + +struct RangeConstraint { + uint32_t witness; + uint32_t num_bits; + + friend bool operator==(RangeConstraint const& lhs, RangeConstraint const& rhs) = default; +}; + +template inline void read(B& buf, RangeConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.witness); + read(buf, constraint.num_bits); +} + +template inline void write(B& buf, RangeConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.witness); + write(buf, constraint.num_bits); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/round.cpp b/cpp/src/aztec/dsl/acir_format/round.cpp new file mode 100644 index 00000000000..a3cbbaad7f0 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/round.cpp @@ -0,0 +1,22 @@ +#include "round.hpp" + +namespace acir_format { + +// Rounds a number to the nearest multiple of 8 +uint32_t round_to_nearest_mul_8(uint32_t num_bits) +{ + uint32_t remainder = num_bits % 8; + if (remainder == 0) { + return num_bits; + } + + return num_bits + 8 - remainder; +} + +// Rounds the number of bits to the nearest byte +uint32_t round_to_nearest_byte(uint32_t num_bits) +{ + return round_to_nearest_mul_8(num_bits) / 8; +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/round.hpp b/cpp/src/aztec/dsl/acir_format/round.hpp new file mode 100644 index 00000000000..4928bc3cdc6 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/round.hpp @@ -0,0 +1,11 @@ +#include + +namespace acir_format { + +// Rounds a number to the nearest multiple of 8 +uint32_t round_to_nearest_mul_8(uint32_t num_bits); + +// Rounds the number of bits to the nearest byte +uint32_t round_to_nearest_byte(uint32_t num_bits); + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/schnorr_verify.cpp b/cpp/src/aztec/dsl/acir_format/schnorr_verify.cpp new file mode 100644 index 00000000000..edbc041fee8 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/schnorr_verify.cpp @@ -0,0 +1,95 @@ +#include "schnorr_verify.hpp" +#include "crypto/schnorr/schnorr.hpp" +#include "stdlib/types/types.hpp" + +using namespace plonk::stdlib::types; + +namespace acir_format { + +crypto::schnorr::signature convert_signature(plonk::TurboComposer& composer, std::vector signature) +{ + + crypto::schnorr::signature signature_cr; + + // Get the witness assignment for each witness index + // Write the witness assignment to the byte_array + + for (unsigned int i = 0; i < 32; i++) { + auto witness_index = signature[i]; + + std::vector fr_bytes(sizeof(fr)); + + fr value = composer.get_variable(witness_index); + + fr::serialize_to_buffer(value, &fr_bytes[0]); + + signature_cr.s[i] = fr_bytes.back(); + } + + for (unsigned int i = 32; i < 64; i++) { + auto witness_index = signature[i]; + + std::vector fr_bytes(sizeof(fr)); + + fr value = composer.get_variable(witness_index); + + fr::serialize_to_buffer(value, &fr_bytes[0]); + + signature_cr.e[i - 32] = fr_bytes.back(); + } + + return signature_cr; +} +// vector of bytes here, assumes that the witness indices point to a field element which can be represented +// with just a byte. +// notice that this function truncates each field_element to a byte +byte_array_ct vector_of_bytes_to_byte_array(plonk::TurboComposer& composer, std::vector vector_of_bytes) +{ + byte_array_ct arr(&composer); + + // Get the witness assignment for each witness index + // Write the witness assignment to the byte_array + for (const auto& witness_index : vector_of_bytes) { + + field_ct element = field_ct::from_witness_index(&composer, witness_index); + size_t num_bytes = 1; + + byte_array_ct element_bytes(element, num_bytes); + arr.write(element_bytes); + } + return arr; +} +witness_ct index_to_witness(plonk::TurboComposer& composer, uint32_t index) +{ + fr value = composer.get_variable(index); + return { &composer, value }; +} + +void create_schnorr_verify_constraints(plonk::TurboComposer& composer, const SchnorrConstraint& input) +{ + + auto new_sig = convert_signature(composer, input.signature); + // From ignorance, you will see me convert a bunch of witnesses from ByteArray -> BitArray + // This may not be the most efficient way to do it. It is being used as it is known to work, + // optimisations are welcome! + + // First convert the message of u8 witnesses into a byte_array + // Do this by taking each element as a u8 and writing it to the byte array + + auto message = vector_of_bytes_to_byte_array(composer, input.message); + + fr pubkey_value_x = composer.get_variable(input.public_key_x); + fr pubkey_value_y = composer.get_variable(input.public_key_y); + + point_ct pub_key{ witness_ct(&composer, pubkey_value_x), witness_ct(&composer, pubkey_value_y) }; + + schnorr::signature_bits sig = stdlib::schnorr::convert_signature(&composer, new_sig); + + bool_ct signature_result = stdlib::schnorr::signature_verification_result(message, pub_key, sig); + + bool_ct signature_result_normalized = signature_result.normalize(); + + composer.assert_equal(signature_result_normalized.witness_index, input.result); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/schnorr_verify.hpp b/cpp/src/aztec/dsl/acir_format/schnorr_verify.hpp new file mode 100644 index 00000000000..3f208954d1d --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/schnorr_verify.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +struct SchnorrConstraint { + // This is just a bunch of bytes + // which need to be interpreted as a string + // Note this must be a bunch of bytes + std::vector message; + + // This is the supposed public key which signed the + // message, giving rise to the signature + uint32_t public_key_x; + uint32_t public_key_y; + + // This is the result of verifying the signature + uint32_t result; + + // This is the computed signature + // + std::vector signature; + + friend bool operator==(SchnorrConstraint const& lhs, SchnorrConstraint const& rhs) = default; +}; + +void create_schnorr_verify_constraints(plonk::TurboComposer& composer, const SchnorrConstraint& input); + +template inline void read(B& buf, SchnorrConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.message); + read(buf, constraint.signature); + read(buf, constraint.public_key_x); + read(buf, constraint.public_key_y); + read(buf, constraint.result); +} + +template inline void write(B& buf, SchnorrConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.message); + write(buf, constraint.signature); + write(buf, constraint.public_key_x); + write(buf, constraint.public_key_y); + write(buf, constraint.result); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/sha256_constraint.cpp b/cpp/src/aztec/dsl/acir_format/sha256_constraint.cpp new file mode 100644 index 00000000000..da4fc98a3e2 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/sha256_constraint.cpp @@ -0,0 +1,44 @@ +#include "sha256_constraint.hpp" +#include "round.hpp" +#include "stdlib/hash/sha256/sha256.hpp" +#include "stdlib/types/types.hpp" + +using namespace plonk::stdlib::types; + +namespace acir_format { + +// This function does not work (properly) because the stdlib:sha256 function is not working correctly for 512 bits +// pair +void create_sha256_constraints(plonk::TurboComposer& composer, const Sha256Constraint& constraint) +{ + + // Create byte array struct + byte_array_ct arr(&composer); + + // Get the witness assignment for each witness index + // Write the witness assignment to the byte_array + for (const auto& witness_index_num_bits : constraint.inputs) { + auto witness_index = witness_index_num_bits.witness; + auto num_bits = witness_index_num_bits.num_bits; + + // XXX: The implementation requires us to truncate the element to the nearest byte and not bit + auto num_bytes = round_to_nearest_byte(num_bits); + + field_ct element = field_ct::from_witness_index(&composer, witness_index); + byte_array_ct element_bytes(element, num_bytes); + + arr.write(element_bytes); + } + + // Compute sha256 + byte_array_ct output_bytes = plonk::stdlib::sha256(arr); + + // Convert byte array to vector of field_t + auto bytes = output_bytes.bytes(); + + for (size_t i = 0; i < bytes.size(); ++i) { + composer.assert_equal(bytes[i].normalize().witness_index, constraint.result[i]); + } +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/acir_format/sha256_constraint.hpp b/cpp/src/aztec/dsl/acir_format/sha256_constraint.hpp new file mode 100644 index 00000000000..7f83c4c9e17 --- /dev/null +++ b/cpp/src/aztec/dsl/acir_format/sha256_constraint.hpp @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include "plonk/composer/turbo_composer.hpp" + +namespace acir_format { + +struct Sha256Input { + uint32_t witness; + uint32_t num_bits; + + friend bool operator==(Sha256Input const& lhs, Sha256Input const& rhs) = default; +}; + +struct Sha256Constraint { + std::vector inputs; + std::vector result; + + friend bool operator==(Sha256Constraint const& lhs, Sha256Constraint const& rhs) = default; +}; + +// This function does not work (properly) because the stdlib:sha256 function is not working correctly for 512 bits +// pair +void create_sha256_constraints(plonk::TurboComposer& composer, const Sha256Constraint& constraint); + +template inline void read(B& buf, Sha256Input& constraint) +{ + using serialize::read; + read(buf, constraint.witness); + read(buf, constraint.num_bits); +} + +template inline void write(B& buf, Sha256Input const& constraint) +{ + using serialize::write; + write(buf, constraint.witness); + write(buf, constraint.num_bits); +} + +template inline void read(B& buf, Sha256Constraint& constraint) +{ + using serialize::read; + read(buf, constraint.inputs); + read(buf, constraint.result); +} + +template inline void write(B& buf, Sha256Constraint const& constraint) +{ + using serialize::write; + write(buf, constraint.inputs); + write(buf, constraint.result); +} + +} // namespace acir_format diff --git a/cpp/src/aztec/dsl/turbo_proofs/CMakeLists.txt b/cpp/src/aztec/dsl/turbo_proofs/CMakeLists.txt new file mode 100644 index 00000000000..066449db4aa --- /dev/null +++ b/cpp/src/aztec/dsl/turbo_proofs/CMakeLists.txt @@ -0,0 +1,5 @@ +barretenberg_module( + turbo_proofs + acir_format + plonk +) diff --git a/cpp/src/aztec/dsl/turbo_proofs/c_bind.cpp b/cpp/src/aztec/dsl/turbo_proofs/c_bind.cpp new file mode 100644 index 00000000000..1b4cd27d847 --- /dev/null +++ b/cpp/src/aztec/dsl/turbo_proofs/c_bind.cpp @@ -0,0 +1,43 @@ +#include "c_bind.hpp" +#include "turbo_proofs.hpp" +#include + +#define WASM_EXPORT __attribute__((visibility("default"))) + +extern "C" { + +// Get the exact circuit size for the constraint system. +WASM_EXPORT uint32_t turbo_get_exact_circuit_size(uint8_t const* constraint_system_buf) +{ + return turbo_proofs::turbo_get_exact_circuit_size(constraint_system_buf); +} + +WASM_EXPORT size_t turbo_init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk_buf) +{ + return turbo_proofs::turbo_init_proving_key(constraint_system_buf, pk_buf); +} + +WASM_EXPORT size_t turbo_init_verification_key(void* pippenger, + uint8_t const* g2x, + uint8_t const* pk_buf, + uint8_t const** vk_buf) +{ + return turbo_proofs::turbo_init_verification_key(pippenger, g2x, pk_buf, vk_buf); +} + +WASM_EXPORT size_t turbo_new_proof(void* pippenger, + uint8_t const* g2x, + uint8_t const* pk_buf, + uint8_t const* constraint_system_buf, + uint8_t const* witness_buf, + uint8_t** proof_data_buf) +{ + return turbo_proofs::turbo_new_proof(pippenger, g2x, pk_buf, constraint_system_buf, witness_buf, proof_data_buf); +} + +WASM_EXPORT bool turbo_verify_proof( + uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length) +{ + return turbo_proofs::turbo_verify_proof(g2x, vk_buf, constraint_system_buf, proof, length); +} +} diff --git a/cpp/src/aztec/dsl/turbo_proofs/c_bind.hpp b/cpp/src/aztec/dsl/turbo_proofs/c_bind.hpp new file mode 100644 index 00000000000..e9ba3c5402a --- /dev/null +++ b/cpp/src/aztec/dsl/turbo_proofs/c_bind.hpp @@ -0,0 +1,24 @@ +#include +#include + +#define WASM_EXPORT __attribute__((visibility("default"))) + +extern "C" { + +WASM_EXPORT uint32_t turbo_get_exact_circuit_size(uint8_t const* constraint_system_buf); + +// Construct composer using prover and verifier key buffers +WASM_EXPORT size_t turbo_init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk_buf); +WASM_EXPORT size_t turbo_init_verification_key(void* pippenger, + uint8_t const* g2x, + uint8_t const* pk_buf, + uint8_t const** vk_buf); +WASM_EXPORT size_t turbo_new_proof(void* pippenger, + uint8_t const* g2x, + uint8_t const* pk_buf, + uint8_t const* constraint_system_buf, + uint8_t const* witness_buf, + uint8_t** proof_data_buf); +WASM_EXPORT bool turbo_verify_proof( + uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length); +} diff --git a/cpp/src/aztec/dsl/turbo_proofs/turbo_proofs.cpp b/cpp/src/aztec/dsl/turbo_proofs/turbo_proofs.cpp new file mode 100644 index 00000000000..fefecbc4c28 --- /dev/null +++ b/cpp/src/aztec/dsl/turbo_proofs/turbo_proofs.cpp @@ -0,0 +1,128 @@ + +#include "turbo_proofs.hpp" +#include "proof_system/proving_key/serialize.hpp" +#include "dsl/acir_format/acir_format.hpp" +#include "stdlib/types/types.hpp" +#include "srs/reference_string/pippenger_reference_string.hpp" + +using namespace plonk::stdlib::types; + +namespace turbo_proofs { + +uint32_t turbo_get_exact_circuit_size(uint8_t const* constraint_system_buf) +{ + auto constraint_system = from_buffer(constraint_system_buf); + auto crs_factory = std::make_unique(); + auto composer = create_circuit(constraint_system, std::move(crs_factory)); + + auto num_gates = composer.get_num_gates(); + return static_cast(num_gates); +} + +size_t turbo_init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk_buf) +{ + auto constraint_system = from_buffer(constraint_system_buf); + // We know that we don't actually need any CRS to create a proving key, so just feed in a nothing. + // Hacky, but, right now it needs *something*. + auto crs_factory = std::make_unique(); + auto composer = create_circuit(constraint_system, std::move(crs_factory)); + auto proving_key = composer.compute_proving_key(); + + auto buffer = to_buffer(*proving_key); + auto raw_buf = (uint8_t*)malloc(buffer.size()); + memcpy(raw_buf, (void*)buffer.data(), buffer.size()); + *pk_buf = raw_buf; + + return buffer.size(); +} + +size_t turbo_init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const** vk_buf) +{ + std::shared_ptr crs; + bonk::proving_key_data pk_data; + read(pk_buf, pk_data); + auto proving_key = std::make_shared(std::move(pk_data), crs); + + auto crs_factory = std::make_unique( + reinterpret_cast(pippenger), g2x); + proving_key->reference_string = crs_factory->get_prover_crs(proving_key->circuit_size); + + TurboComposer composer(proving_key, nullptr); + auto verification_key = + plonk::stdlib::types::Composer::compute_verification_key_base(proving_key, crs_factory->get_verifier_crs()); + + // The composer_type has not yet been set. We need to set the composer_type for when we later read in and + // construct the verification key so that we have the correct polynomial manifest + verification_key->composer_type = ComposerType::TURBO; + + auto buffer = to_buffer(*verification_key); + auto raw_buf = (uint8_t*)malloc(buffer.size()); + memcpy(raw_buf, (void*)buffer.data(), buffer.size()); + *vk_buf = raw_buf; + + return buffer.size(); +} + +size_t turbo_new_proof(void* pippenger, + uint8_t const* g2x, + uint8_t const* pk_buf, + uint8_t const* constraint_system_buf, + uint8_t const* witness_buf, + uint8_t** proof_data_buf) +{ + auto constraint_system = from_buffer(constraint_system_buf); + + std::shared_ptr crs; + bonk::proving_key_data pk_data; + read(pk_buf, pk_data); + auto proving_key = std::make_shared(std::move(pk_data), crs); + + auto witness = from_buffer>(witness_buf); + + auto crs_factory = std::make_unique( + reinterpret_cast(pippenger), g2x); + proving_key->reference_string = crs_factory->get_prover_crs(proving_key->circuit_size); + + TurboComposer composer(proving_key, nullptr); + create_circuit_with_witness(composer, constraint_system, witness); + + auto prover = composer.create_prover(); + auto heapProver = new TurboProver(std::move(prover)); + auto& proof_data = heapProver->construct_proof().proof_data; + *proof_data_buf = proof_data.data(); + + return proof_data.size(); +} + +bool turbo_verify_proof( + uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length) +{ + bool verified = false; + +#ifndef __wasm__ + try { +#endif + auto constraint_system = from_buffer(constraint_system_buf); + + auto crs = std::make_shared(g2x); + bonk::verification_key_data vk_data; + read(vk_buf, vk_data); + auto verification_key = std::make_shared(std::move(vk_data), crs); + + TurboComposer composer(nullptr, verification_key); + create_circuit(composer, constraint_system); + plonk::proof pp = { std::vector(proof, proof + length) }; + + auto verifier = composer.create_verifier(); + + verified = verifier.verify_proof(pp); +#ifndef __wasm__ + } catch (const std::exception& e) { + verified = false; + info(e.what()); + } +#endif + return verified; +} + +} // namespace turbo_proofs diff --git a/cpp/src/aztec/dsl/turbo_proofs/turbo_proofs.hpp b/cpp/src/aztec/dsl/turbo_proofs/turbo_proofs.hpp new file mode 100644 index 00000000000..486973e4187 --- /dev/null +++ b/cpp/src/aztec/dsl/turbo_proofs/turbo_proofs.hpp @@ -0,0 +1,18 @@ +#include +#include + +namespace turbo_proofs { + +uint32_t turbo_get_exact_circuit_size(uint8_t const* constraint_system_buf); +size_t turbo_init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk_buf); +size_t turbo_init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const** vk_buf); +size_t turbo_new_proof(void* pippenger, + uint8_t const* g2x, + uint8_t const* pk_buf, + uint8_t const* constraint_system_buf, + uint8_t const* witness_buf, + uint8_t** proof_data_buf); +bool turbo_verify_proof( + uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length); + +} // namespace turbo_proofs