From 7582717e28bba42e2eeec22684226bae978330c4 Mon Sep 17 00:00:00 2001 From: kevaundray Date: Fri, 5 Jan 2024 16:39:00 +0000 Subject: [PATCH 1/3] empty commit From c98b807a843278b8a15f08e9e521b26382867977 Mon Sep 17 00:00:00 2001 From: kevaundray Date: Fri, 5 Jan 2024 16:47:05 +0000 Subject: [PATCH 2/3] feat!: keccak in noir using a permutation opcode (rebased from #3726) (#3854) This is #3726 but without the keccak changes in Noir which are not currently working # Checklist: Remove the checklist to signal you've completed it. Enable auto-merge if the PR is ready to merge. - [ ] If the pull request requires a cryptography review (e.g. cryptographic algorithm implementations) I have added the 'crypto' tag. - [ ] I have reviewed my diff in github, line by line and removed unexpected formatting changes, testing logs, or commented-out code. - [ ] Every change is related to the PR description. - [ ] I have [linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this pull request to relevant issues (if any exist). --------- Co-authored-by: guipublic Co-authored-by: guipublic <47281315+guipublic@users.noreply.github.com> --- barretenberg/cpp/src/barretenberg/bb/main.cpp | 2 +- .../dsl/acir_format/acir_format.cpp | 3 + .../dsl/acir_format/acir_format.hpp | 2 + .../dsl/acir_format/acir_format.test.cpp | 48 +++++++ .../acir_format/acir_to_constraint_buf.hpp | 5 + .../dsl/acir_format/block_constraint.test.cpp | 1 + .../dsl/acir_format/ecdsa_secp256k1.test.cpp | 3 + .../dsl/acir_format/ecdsa_secp256r1.test.cpp | 4 + .../dsl/acir_format/keccak_constraint.cpp | 28 ++++ .../dsl/acir_format/keccak_constraint.hpp | 10 ++ .../acir_format/recursion_constraint.test.cpp | 2 + .../dsl/acir_format/serde/acir.hpp | 62 +++++++++ .../stdlib/hash/keccak/keccak.cpp | 127 ++++++++++++++++++ .../stdlib/hash/keccak/keccak.hpp | 11 ++ .../stdlib/hash/keccak/keccak.test.cpp | 43 ++++++ noir/acvm-repo/acir/codegen/acir.cpp | 52 ++++++- .../acir/src/circuit/black_box_functions.rs | 4 + noir/acvm-repo/acir/src/circuit/mod.rs | 59 ++++++++ .../opcodes/black_box_function_call.rs | 7 + .../acvm/src/compiler/transformers/mod.rs | 1 + noir/acvm-repo/acvm/src/pwg/blackbox/hash.rs | 79 +++++++++++ noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs | 20 ++- .../ssa/acir_gen/acir_ir/generated_acir.rs | 6 + .../src/ssa/ir/instruction/call.rs | 1 + 24 files changed, 576 insertions(+), 4 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/bb/main.cpp b/barretenberg/cpp/src/barretenberg/bb/main.cpp index f61d1913a06..9d562afdf14 100644 --- a/barretenberg/cpp/src/barretenberg/bb/main.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/main.cpp @@ -400,7 +400,7 @@ void acvm_info(const std::string& output_path) "width" : 3 }, "opcodes_supported" : ["arithmetic", "directive", "brillig", "memory_init", "memory_op"], - "black_box_functions_supported" : ["and", "xor", "range", "sha256", "blake2s", "keccak256", "schnorr_verify", "pedersen", "pedersen_hash", "ecdsa_secp256k1", "ecdsa_secp256r1", "fixed_base_scalar_mul", "recursive_aggregation"] + "black_box_functions_supported" : ["and", "xor", "range", "sha256", "blake2s", "keccak256", "keccak_f1600", "schnorr_verify", "pedersen", "pedersen_hash", "ecdsa_secp256k1", "ecdsa_secp256r1", "fixed_base_scalar_mul", "recursive_aggregation"] })"; size_t length = strlen(jsonData); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 516fa1aaa89..23c936b6a27 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -126,6 +126,9 @@ void build_constraints(Builder& builder, acir_format const& constraint_system, b for (const auto& constraint : constraint_system.keccak_var_constraints) { create_keccak_var_constraints(builder, constraint); } + for (const auto& constraint : constraint_system.keccak_permutations) { + create_keccak_permutations(builder, constraint); + } // Add pedersen constraints for (const auto& constraint : constraint_system.pedersen_constraints) { diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 1f00b202b9f..6dadfa7bd6e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -32,6 +32,7 @@ struct acir_format { std::vector blake2s_constraints; std::vector keccak_constraints; std::vector keccak_var_constraints; + std::vector keccak_permutations; std::vector pedersen_constraints; std::vector pedersen_hash_constraints; std::vector fixed_base_scalar_mul_constraints; @@ -57,6 +58,7 @@ struct acir_format { blake2s_constraints, keccak_constraints, keccak_var_constraints, + keccak_permutations, pedersen_constraints, pedersen_hash_constraints, fixed_base_scalar_mul_constraints, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index a4f64e0791c..a3ffaedd720 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -39,6 +39,7 @@ TEST_F(AcirFormatTests, TestASingleConstraintNoPubInputs) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -145,6 +146,7 @@ TEST_F(AcirFormatTests, TestLogicGateFromNoirCircuit) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -209,6 +211,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifyPass) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -296,6 +299,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -402,6 +406,7 @@ TEST_F(AcirFormatTests, TestVarKeccak) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = { keccak }, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -419,4 +424,47 @@ TEST_F(AcirFormatTests, TestVarKeccak) EXPECT_EQ(verifier.verify_proof(proof), true); } +TEST_F(AcirFormatTests, TestKeccakPermutation) +{ + Keccakf1600 + keccak_permutation{ + .state = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, + .result = { 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 }, + }; + + acir_format constraint_system{ .varnum = 51, + .public_inputs = {}, + .logic_constraints = {}, + .range_constraints = {}, + .sha256_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_k1_constraints = {}, + .ecdsa_r1_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .keccak_var_constraints = {}, + .keccak_permutations = { keccak_permutation }, + .pedersen_constraints = {}, + .pedersen_hash_constraints = {}, + .fixed_base_scalar_mul_constraints = {}, + .recursion_constraints = {}, + .constraints = {}, + .block_constraints = {} }; + + WitnessVector witness{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 }; + + auto builder = create_circuit_with_witness(constraint_system, witness); + + auto composer = Composer(); + auto prover = composer.create_ultra_with_keccak_prover(builder); + auto proof = prover.construct_proof(); + + auto verifier = composer.create_ultra_with_keccak_verifier(builder); + + EXPECT_EQ(verifier.verify_proof(proof), true); +} + } // namespace acir_format::tests diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp index 123c52a27a3..20da55ce6c3 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp @@ -180,6 +180,11 @@ void handle_blackbox_func_call(Circuit::Opcode::BlackBoxFuncCall const& arg, aci .result = map(arg.outputs, [](auto& e) { return e.value; }), .var_message_size = arg.var_message_size.witness.value, }); + } else if constexpr (std::is_same_v) { + af.keccak_permutations.push_back(Keccakf1600{ + .state = map(arg.inputs, [](auto& e) { return e.witness.value; }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + }); } else if constexpr (std::is_same_v) { auto c = RecursionConstraint{ .key = map(arg.verification_key, [](auto& e) { return e.witness.value; }), diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp index a20de2c75db..649c751edc5 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp @@ -120,6 +120,7 @@ TEST_F(UltraPlonkRAM, TestBlockConstraint) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index 9bcbc89ec87..fb8f99288ee 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -99,6 +99,7 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintSucceed) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -139,6 +140,7 @@ TEST_F(ECDSASecp256k1, TestECDSACompilesForVerifier) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -174,6 +176,7 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintFail) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp index 5d26a4b7a37..0e6fe54509a 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp @@ -133,6 +133,7 @@ TEST(ECDSASecp256r1, test_hardcoded) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -174,6 +175,7 @@ TEST(ECDSASecp256r1, TestECDSAConstraintSucceed) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -213,6 +215,7 @@ TEST(ECDSASecp256r1, TestECDSACompilesForVerifier) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -247,6 +250,7 @@ TEST(ECDSASecp256r1, TestECDSAConstraintFail) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/keccak_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/keccak_constraint.cpp index 46745de023c..bd26897dbad 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/keccak_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/keccak_constraint.cpp @@ -1,5 +1,6 @@ #include "keccak_constraint.hpp" #include "barretenberg/stdlib/hash/keccak/keccak.hpp" +#include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp" #include "round.hpp" namespace acir_format { @@ -73,13 +74,40 @@ template void create_keccak_var_constraints(Builder& builder, } } +template void create_keccak_permutations(Builder& builder, const Keccakf1600& constraint) +{ + using field_ct = proof_system::plonk::stdlib::field_t; + + // Create the array containing the permuted state + std::array::NUM_KECCAK_LANES> state; + + // Get the witness assignment for each witness index + // Write the witness assignment to the byte_array + for (size_t i = 0; i < constraint.state.size(); ++i) { + info(constraint.state[i]); + state[i] = field_ct::from_witness_index(&builder, constraint.state[i]); + } + + std::array output_state = + proof_system::plonk::stdlib::keccak::permutation_opcode(state, &builder); + + for (size_t i = 0; i < output_state.size(); ++i) { + builder.assert_equal(output_state[i].normalize().witness_index, constraint.result[i]); + } +} template void create_keccak_constraints(UltraCircuitBuilder& builder, const KeccakConstraint& constraint); template void create_keccak_var_constraints(UltraCircuitBuilder& builder, const KeccakVarConstraint& constraint); +template void create_keccak_permutations(UltraCircuitBuilder& builder, + const Keccakf1600& constraint); + template void create_keccak_constraints(GoblinUltraCircuitBuilder& builder, const KeccakConstraint& constraint); template void create_keccak_var_constraints(GoblinUltraCircuitBuilder& builder, const KeccakVarConstraint& constraint); +template void create_keccak_permutations(GoblinUltraCircuitBuilder& builder, + const Keccakf1600& constraint); + } // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/keccak_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/keccak_constraint.hpp index 25259e1e941..4524e35862b 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/keccak_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/keccak_constraint.hpp @@ -15,6 +15,15 @@ struct HashInput { friend bool operator==(HashInput const& lhs, HashInput const& rhs) = default; }; +struct Keccakf1600 { + std::vector state; + std::vector result; + + // For serialization, update with any new fields + MSGPACK_FIELDS(state, result); + friend bool operator==(Keccakf1600 const& lhs, Keccakf1600 const& rhs) = default; +}; + struct KeccakConstraint { std::vector inputs; std::vector result; @@ -36,5 +45,6 @@ struct KeccakVarConstraint { template void create_keccak_constraints(Builder& builder, const KeccakConstraint& constraint); template void create_keccak_var_constraints(Builder& builder, const KeccakVarConstraint& constraint); +template void create_keccak_permutations(Builder& builder, const Keccakf1600& constraint); } // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index c4decf04a3a..700ea0b70b0 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -92,6 +92,7 @@ Builder create_inner_circuit() .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -249,6 +250,7 @@ Builder create_outer_circuit(std::vector& inner_circuits) .blake2s_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp index 2d124e2a7b2..b0c8baf17d1 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -155,6 +155,15 @@ struct BlackBoxFuncCall { static Keccak256VariableLength bincodeDeserialize(std::vector); }; + struct Keccakf1600 { + std::vector inputs; + std::vector outputs; + + friend bool operator==(const Keccakf1600&, const Keccakf1600&); + std::vector bincodeSerialize() const; + static Keccakf1600 bincodeDeserialize(std::vector); + }; + struct RecursiveAggregation { std::vector verification_key; std::vector proof; @@ -181,6 +190,7 @@ struct BlackBoxFuncCall { FixedBaseScalarMul, Keccak256, Keccak256VariableLength, + Keccakf1600, RecursiveAggregation> value; @@ -2520,6 +2530,58 @@ Circuit::BlackBoxFuncCall::Keccak256VariableLength serde::Deserializable< namespace Circuit { +inline bool operator==(const BlackBoxFuncCall::Keccakf1600& lhs, const BlackBoxFuncCall::Keccakf1600& rhs) +{ + if (!(lhs.inputs == rhs.inputs)) { + return false; + } + if (!(lhs.outputs == rhs.outputs)) { + return false; + } + return true; +} + +inline std::vector BlackBoxFuncCall::Keccakf1600::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline BlackBoxFuncCall::Keccakf1600 BlackBoxFuncCall::Keccakf1600::bincodeDeserialize(std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize( + const Circuit::BlackBoxFuncCall::Keccakf1600& obj, Serializer& serializer) +{ + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.outputs, serializer); +} + +template <> +template +Circuit::BlackBoxFuncCall::Keccakf1600 serde::Deserializable::deserialize( + Deserializer& deserializer) +{ + Circuit::BlackBoxFuncCall::Keccakf1600 obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + inline bool operator==(const BlackBoxFuncCall::RecursiveAggregation& lhs, const BlackBoxFuncCall::RecursiveAggregation& rhs) { diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp index 2ff044e13fe..bbc2159c756 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp @@ -1,6 +1,7 @@ #include "keccak.hpp" #include "barretenberg/common/constexpr_utils.hpp" #include "barretenberg/numeric/bitop/sparse_form.hpp" +#include "barretenberg/stdlib/primitives/logic/logic.hpp" #include "barretenberg/stdlib/primitives/uint/uint.hpp" namespace proof_system::plonk { namespace stdlib { @@ -721,6 +722,92 @@ std::vector> keccak::format_input_lanes(byte_array_ct& return lanes; } +// Returns the keccak f1600 permutation of the input state +// We first convert the state into 'extended' representation, along with the 'twisted' state +// and then we call keccakf1600() with this keccak 'internal state' +// Finally, we convert back the state from the extented representation +template +std::array, keccak::NUM_KECCAK_LANES> keccak::permutation_opcode( + std::array, NUM_KECCAK_LANES> state, Builder* ctx) +{ + std::vector> converted_buffer(NUM_KECCAK_LANES); + std::vector> msb_buffer(NUM_KECCAK_LANES); + // populate keccak_state, convert our 64-bit lanes into an extended base-11 representation + keccak_state internal; + internal.context = ctx; + for (size_t i = 0; i < state.size(); ++i) { + const auto accumulators = plookup_read::get_lookup_accumulators(KECCAK_FORMAT_INPUT, state[i]); + internal.state[i] = accumulators[ColumnIdx::C2][0]; + internal.state_msb[i] = accumulators[ColumnIdx::C3][accumulators[ColumnIdx::C3].size() - 1]; + } + compute_twisted_state(internal); + keccakf1600(internal); + // we convert back to the normal lanes + return extended_2_normal(internal); +} + +// This function is similar to sponge_absorb() +// but it uses permutation_opcode() instead of calling directly keccakf1600(). +// As a result, this function is less efficient and should only be used to test permutation_opcode() +template +void keccak::sponge_absorb_with_permutation_opcode(keccak_state& internal, + std::vector>& input_buffer, + const size_t input_size) +{ + // populate keccak_state + const size_t num_blocks = input_size / (BLOCK_SIZE / 8); + for (size_t i = 0; i < num_blocks; ++i) { + if (i == 0) { + for (size_t j = 0; j < LIMBS_PER_BLOCK; ++j) { + internal.state[j] = input_buffer[j]; + } + for (size_t j = LIMBS_PER_BLOCK; j < NUM_KECCAK_LANES; ++j) { + internal.state[j] = witness_ct::create_constant_witness(internal.context, 0); + } + } else { + for (size_t j = 0; j < LIMBS_PER_BLOCK; ++j) { + internal.state[j] = stdlib::logic::create_logic_constraint( + internal.state[j], input_buffer[i * LIMBS_PER_BLOCK + j], 64, true); + } + } + internal.state = permutation_opcode(internal.state, internal.context); + } +} + +// This function computes the keccak hash, like the hash() function +// but it uses permutation_opcode() instead of calling directly keccakf1600(). +// As a result, this function is less efficient and should only be used to test permutation_opcode() +template +stdlib::byte_array keccak::hash_using_permutation_opcode(byte_array_ct& input, + const uint32_ct& num_bytes) +{ + auto ctx = input.get_context(); + + ASSERT(uint256_t(num_bytes.get_value()) == input.size()); + + if (ctx == nullptr) { + // if buffer is constant compute hash and return w/o creating constraints + byte_array_ct output(nullptr, 32); + const std::vector result = hash_native(input.get_value()); + for (size_t i = 0; i < 32; ++i) { + output.set_byte(i, result[i]); + } + return output; + } + + // convert the input byte array into 64-bit keccak lanes (+ apply padding) + auto formatted_slices = format_input_lanes(input, num_bytes); + + keccak_state internal; + internal.context = ctx; + uint32_ct num_blocks_with_data = (num_bytes + BLOCK_SIZE) / BLOCK_SIZE; + sponge_absorb_with_permutation_opcode(internal, formatted_slices, formatted_slices.size()); + + auto result = sponge_squeeze_for_permutation_opcode(internal.state, ctx); + + return result; +} + template stdlib::byte_array keccak::hash(byte_array_ct& input, const uint32_ct& num_bytes) { @@ -762,6 +849,46 @@ stdlib::byte_array keccak::hash(byte_array_ct& input, const ui return result; } +// Convert the 'extended' representation of the internal Keccak state into the usual array of 64 bits lanes +template +std::array, keccak::NUM_KECCAK_LANES> keccak::extended_2_normal( + keccak_state& internal) +{ + std::array, NUM_KECCAK_LANES> conversion; + + // Each hash limb represents a little-endian integer. Need to reverse bytes before we write into the output array + for (size_t i = 0; i < internal.state.size(); ++i) { + field_ct output_limb = plookup_read::read_from_1_to_2_table(KECCAK_FORMAT_OUTPUT, internal.state[i]); + conversion[i] = output_limb; + } + + return conversion; +} + +// This function is the same as sponge_squeeze, except that it does not convert +// from extended representation and assumes the input has already being converted +template +stdlib::byte_array keccak::sponge_squeeze_for_permutation_opcode( + std::array, NUM_KECCAK_LANES> lanes, Builder* context) +{ + byte_array_ct result(context); + + // Each hash limb represents a little-endian integer. Need to reverse bytes before we write into the output array + for (size_t i = 0; i < 4; ++i) { + byte_array_ct limb_bytes(lanes[i], 8); + byte_array_ct little_endian_limb_bytes(context, 8); + little_endian_limb_bytes.set_byte(0, limb_bytes[7]); + little_endian_limb_bytes.set_byte(1, limb_bytes[6]); + little_endian_limb_bytes.set_byte(2, limb_bytes[5]); + little_endian_limb_bytes.set_byte(3, limb_bytes[4]); + little_endian_limb_bytes.set_byte(4, limb_bytes[3]); + little_endian_limb_bytes.set_byte(5, limb_bytes[2]); + little_endian_limb_bytes.set_byte(6, limb_bytes[1]); + little_endian_limb_bytes.set_byte(7, limb_bytes[0]); + result.write(little_endian_limb_bytes); + } + return result; +} INSTANTIATE_STDLIB_ULTRA_TYPE(keccak) } // namespace stdlib } // namespace proof_system::plonk diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.hpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.hpp index 1790040731e..5719938ebad 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.hpp @@ -189,6 +189,17 @@ template class keccak { memcpy((void*)&output[0], (void*)&hash_result.word64s[0], 32); return output; } + + // exposing keccak f1600 permutation + static byte_array_ct hash_using_permutation_opcode(byte_array_ct& input, const uint32_ct& num_bytes); + static std::array permutation_opcode(std::array state, + Builder* context); + static void sponge_absorb_with_permutation_opcode(keccak_state& internal, + std::vector& input_buffer, + const size_t input_size); + static std::array extended_2_normal(keccak_state& internal); + static byte_array_ct sponge_squeeze_for_permutation_opcode(std::array lanes, + Builder* context); }; EXTERN_STDLIB_ULTRA_TYPE(keccak) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.test.cpp index dd5ab49884e..852488f5a6c 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.test.cpp @@ -269,3 +269,46 @@ TEST(stdlib_keccak, test_variable_length_nonzero_input_greater_than_byte_array_s bool proof_result = builder.check_circuit(); EXPECT_EQ(proof_result, true); } + +TEST(stdlib_keccak, test_permutation_opcode_single_block) +{ + Builder builder = Builder(); + std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"; + std::vector input_v(input.begin(), input.end()); + + byte_array input_arr(&builder, input_v); + byte_array output = + stdlib::keccak::hash_using_permutation_opcode(input_arr, static_cast(input.size())); + + std::vector expected = stdlib::keccak::hash_native(input_v); + + EXPECT_EQ(output.get_value(), expected); + + builder.print_num_gates(); + + bool proof_result = builder.check_circuit(); + EXPECT_EQ(proof_result, true); +} + +TEST(stdlib_keccak, test_permutation_opcode_double_block) +{ + Builder builder = Builder(); + std::string input = ""; + for (size_t i = 0; i < 200; ++i) { + input += "a"; + } + std::vector input_v(input.begin(), input.end()); + + byte_array input_arr(&builder, input_v); + byte_array output = + stdlib::keccak::hash_using_permutation_opcode(input_arr, static_cast(input.size())); + + std::vector expected = stdlib::keccak::hash_native(input_v); + + EXPECT_EQ(output.get_value(), expected); + + builder.print_num_gates(); + + bool proof_result = builder.check_circuit(); + EXPECT_EQ(proof_result, true); +} diff --git a/noir/acvm-repo/acir/codegen/acir.cpp b/noir/acvm-repo/acir/codegen/acir.cpp index 18532940752..29981e66b35 100644 --- a/noir/acvm-repo/acir/codegen/acir.cpp +++ b/noir/acvm-repo/acir/codegen/acir.cpp @@ -155,6 +155,15 @@ namespace Circuit { static Keccak256VariableLength bincodeDeserialize(std::vector); }; + struct Keccakf1600 { + std::vector inputs; + std::vector outputs; + + friend bool operator==(const Keccakf1600&, const Keccakf1600&); + std::vector bincodeSerialize() const; + static Keccakf1600 bincodeDeserialize(std::vector); + }; + struct RecursiveAggregation { std::vector verification_key; std::vector proof; @@ -168,7 +177,7 @@ namespace Circuit { static RecursiveAggregation bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const BlackBoxFuncCall&, const BlackBoxFuncCall&); std::vector bincodeSerialize() const; @@ -2188,6 +2197,47 @@ Circuit::BlackBoxFuncCall::Keccak256VariableLength serde::Deserializable BlackBoxFuncCall::Keccakf1600::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxFuncCall::Keccakf1600 BlackBoxFuncCall::Keccakf1600::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::Keccakf1600 &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.outputs, serializer); +} + +template <> +template +Circuit::BlackBoxFuncCall::Keccakf1600 serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::Keccakf1600 obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Circuit { inline bool operator==(const BlackBoxFuncCall::RecursiveAggregation &lhs, const BlackBoxFuncCall::RecursiveAggregation &rhs) { diff --git a/noir/acvm-repo/acir/src/circuit/black_box_functions.rs b/noir/acvm-repo/acir/src/circuit/black_box_functions.rs index b2dcbd73ef6..47de9eaa9c2 100644 --- a/noir/acvm-repo/acir/src/circuit/black_box_functions.rs +++ b/noir/acvm-repo/acir/src/circuit/black_box_functions.rs @@ -38,6 +38,8 @@ pub enum BlackBoxFunc { FixedBaseScalarMul, /// Calculates the Keccak256 hash of the inputs. Keccak256, + /// Keccak Permutation function of 1600 width + Keccakf1600, /// Compute a recursive aggregation object when verifying a proof inside another circuit. /// This outputted aggregation object will then be either checked in a top-level verifier or aggregated upon again. RecursiveAggregation, @@ -63,6 +65,7 @@ impl BlackBoxFunc { BlackBoxFunc::XOR => "xor", BlackBoxFunc::RANGE => "range", BlackBoxFunc::Keccak256 => "keccak256", + BlackBoxFunc::Keccakf1600 => "keccak_f1600", BlackBoxFunc::RecursiveAggregation => "recursive_aggregation", BlackBoxFunc::EcdsaSecp256r1 => "ecdsa_secp256r1", } @@ -81,6 +84,7 @@ impl BlackBoxFunc { "xor" => Some(BlackBoxFunc::XOR), "range" => Some(BlackBoxFunc::RANGE), "keccak256" => Some(BlackBoxFunc::Keccak256), + "keccakf1600" => Some(BlackBoxFunc::Keccakf1600), "recursive_aggregation" => Some(BlackBoxFunc::RecursiveAggregation), _ => None, } diff --git a/noir/acvm-repo/acir/src/circuit/mod.rs b/noir/acvm-repo/acir/src/circuit/mod.rs index 99ab389e31e..e42bebc52ac 100644 --- a/noir/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/acvm-repo/acir/src/circuit/mod.rs @@ -250,6 +250,64 @@ mod tests { input: FunctionInput { witness: Witness(1), num_bits: 8 }, }) } + fn keccakf1600_opcode() -> Opcode { + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { + inputs: vec![ + FunctionInput { witness: Witness(1), num_bits: 64 }, + FunctionInput { witness: Witness(2), num_bits: 64 }, + FunctionInput { witness: Witness(3), num_bits: 64 }, + FunctionInput { witness: Witness(4), num_bits: 64 }, + FunctionInput { witness: Witness(5), num_bits: 64 }, + FunctionInput { witness: Witness(6), num_bits: 64 }, + FunctionInput { witness: Witness(7), num_bits: 64 }, + FunctionInput { witness: Witness(8), num_bits: 64 }, + FunctionInput { witness: Witness(9), num_bits: 64 }, + FunctionInput { witness: Witness(10), num_bits: 64 }, + FunctionInput { witness: Witness(11), num_bits: 64 }, + FunctionInput { witness: Witness(12), num_bits: 64 }, + FunctionInput { witness: Witness(13), num_bits: 64 }, + FunctionInput { witness: Witness(14), num_bits: 64 }, + FunctionInput { witness: Witness(15), num_bits: 64 }, + FunctionInput { witness: Witness(16), num_bits: 64 }, + FunctionInput { witness: Witness(17), num_bits: 64 }, + FunctionInput { witness: Witness(18), num_bits: 64 }, + FunctionInput { witness: Witness(19), num_bits: 64 }, + FunctionInput { witness: Witness(20), num_bits: 64 }, + FunctionInput { witness: Witness(21), num_bits: 64 }, + FunctionInput { witness: Witness(22), num_bits: 64 }, + FunctionInput { witness: Witness(23), num_bits: 64 }, + FunctionInput { witness: Witness(24), num_bits: 64 }, + FunctionInput { witness: Witness(25), num_bits: 64 }, + ], + outputs: vec![ + Witness(26), + Witness(27), + Witness(28), + Witness(29), + Witness(30), + Witness(31), + Witness(32), + Witness(33), + Witness(34), + Witness(35), + Witness(36), + Witness(37), + Witness(38), + Witness(39), + Witness(40), + Witness(41), + Witness(42), + Witness(43), + Witness(44), + Witness(45), + Witness(46), + Witness(47), + Witness(48), + Witness(49), + Witness(50), + ], + }) + } #[test] fn serialization_roundtrip() { @@ -284,6 +342,7 @@ mod tests { }), range_opcode(), and_opcode(), + keccakf1600_opcode(), ], private_parameters: BTreeSet::new(), public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), diff --git a/noir/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index ded3b0898a0..3775310ec3b 100644 --- a/noir/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -88,6 +88,10 @@ pub enum BlackBoxFuncCall { var_message_size: FunctionInput, outputs: Vec, }, + Keccakf1600 { + inputs: Vec, + outputs: Vec, + }, RecursiveAggregation { verification_key: Vec, proof: Vec, @@ -129,6 +133,7 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::FixedBaseScalarMul { .. } => BlackBoxFunc::FixedBaseScalarMul, BlackBoxFuncCall::Keccak256 { .. } => BlackBoxFunc::Keccak256, BlackBoxFuncCall::Keccak256VariableLength { .. } => BlackBoxFunc::Keccak256, + BlackBoxFuncCall::Keccakf1600 { .. } => BlackBoxFunc::Keccakf1600, BlackBoxFuncCall::RecursiveAggregation { .. } => BlackBoxFunc::RecursiveAggregation, } } @@ -142,6 +147,7 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::SHA256 { inputs, .. } | BlackBoxFuncCall::Blake2s { inputs, .. } | BlackBoxFuncCall::Keccak256 { inputs, .. } + | BlackBoxFuncCall::Keccakf1600 { inputs, .. } | BlackBoxFuncCall::PedersenCommitment { inputs, .. } | BlackBoxFuncCall::PedersenHash { inputs, .. } => inputs.to_vec(), BlackBoxFuncCall::AND { lhs, rhs, .. } | BlackBoxFuncCall::XOR { lhs, rhs, .. } => { @@ -231,6 +237,7 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::SHA256 { outputs, .. } | BlackBoxFuncCall::Blake2s { outputs, .. } | BlackBoxFuncCall::Keccak256 { outputs, .. } + | BlackBoxFuncCall::Keccakf1600 { outputs, .. } | BlackBoxFuncCall::RecursiveAggregation { output_aggregation_object: outputs, .. } => outputs.to_vec(), diff --git a/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs b/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs index 664e0f9ac9a..4d2a49c330b 100644 --- a/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -112,6 +112,7 @@ pub(super) fn transform_internal( outputs, .. } + | acir::circuit::opcodes::BlackBoxFuncCall::Keccakf1600 { outputs, .. } | acir::circuit::opcodes::BlackBoxFuncCall::RecursiveAggregation { output_aggregation_object: outputs, .. diff --git a/noir/acvm-repo/acvm/src/pwg/blackbox/hash.rs b/noir/acvm-repo/acvm/src/pwg/blackbox/hash.rs index 1ada397fc59..bbf7dd43bd9 100644 --- a/noir/acvm-repo/acvm/src/pwg/blackbox/hash.rs +++ b/noir/acvm-repo/acvm/src/pwg/blackbox/hash.rs @@ -86,3 +86,82 @@ fn write_digest_to_outputs( Ok(()) } + +const ROUNDS: usize = 24; + +const RC: [u64; ROUNDS] = [ + 1u64, + 0x8082u64, + 0x800000000000808au64, + 0x8000000080008000u64, + 0x808bu64, + 0x80000001u64, + 0x8000000080008081u64, + 0x8000000000008009u64, + 0x8au64, + 0x88u64, + 0x80008009u64, + 0x8000000au64, + 0x8000808bu64, + 0x800000000000008bu64, + 0x8000000000008089u64, + 0x8000000000008003u64, + 0x8000000000008002u64, + 0x8000000000000080u64, + 0x800au64, + 0x800000008000000au64, + 0x8000000080008081u64, + 0x8000000000008080u64, + 0x80000001u64, + 0x8000000080008008u64, +]; + +const RHO: [u32; 24] = + [1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44]; + +const PI: [usize; 24] = + [10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1]; + +const KECCAK_LANES: usize = 25; + +pub(crate) fn keccakf1600(state: &mut [u64; KECCAK_LANES]) { + for rc in RC { + let mut array: [u64; 5] = [0; 5]; + + // Theta + for x in 0..5 { + for y_count in 0..5 { + let y = y_count * 5; + array[x] ^= state[x + y]; + } + } + + for x in 0..5 { + for y_count in 0..5 { + let y = y_count * 5; + state[y + x] ^= array[(x + 4) % 5] ^ array[(x + 1) % 5].rotate_left(1); + } + } + + // Rho and pi + let mut last = state[1]; + for x in 0..24 { + array[0] = state[PI[x]]; + state[PI[x]] = last.rotate_left(RHO[x]); + last = array[0]; + } + + // Chi + for y_step in 0..5 { + let y = y_step * 5; + array[..5].copy_from_slice(&state[y..(5 + y)]); + + for x in 0..5 { + state[y + x] = array[x] ^ ((!array[(x + 1) % 5]) & (array[(x + 2) % 5])); + } + } + + // Iota + state[0] ^= rc; + } +} diff --git a/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs index ad15b121383..0ae68bf0eb2 100644 --- a/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -5,10 +5,10 @@ use acir::{ }; use acvm_blackbox_solver::{blake2s, keccak256, sha256}; -use self::pedersen::pedersen_hash; +use self::{hash::keccakf1600, pedersen::pedersen_hash}; use super::{insert_value, OpcodeNotSolvable, OpcodeResolutionError}; -use crate::BlackBoxFunctionSolver; +use crate::{pwg::witness_to_value, BlackBoxFunctionSolver}; mod fixed_base_scalar_mul; mod hash; @@ -101,6 +101,22 @@ pub(crate) fn solve( bb_func.get_black_box_func(), ) } + BlackBoxFuncCall::Keccakf1600 { inputs, outputs } => { + let mut state = [0; 25]; + for (i, input) in inputs.iter().enumerate() { + let witness = input.witness; + let num_bits = input.num_bits as usize; + assert_eq!(num_bits, 64); + let witness_assignment = witness_to_value(initial_witness, witness)?; + let lane = witness_assignment.try_to_u64(); + state[i] = lane.unwrap(); + } + keccakf1600(&mut state); + for (output_witness, value) in outputs.iter().zip(state.into_iter()) { + insert_value(output_witness, FieldElement::from(value as u128), initial_witness)?; + } + Ok(()) + } BlackBoxFuncCall::SchnorrVerify { public_key_x, public_key_y, diff --git a/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index bd8271260af..076039efbbf 100644 --- a/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -222,6 +222,9 @@ impl GeneratedAcir { outputs, } } + BlackBoxFunc::Keccakf1600 => { + BlackBoxFuncCall::Keccakf1600 { inputs: inputs[0].clone(), outputs } + } BlackBoxFunc::RecursiveAggregation => { let has_previous_aggregation = self.opcodes.iter().any(|op| { matches!( @@ -572,6 +575,8 @@ fn black_box_func_expected_input_size(name: BlackBoxFunc) -> Option { | BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash => None, + BlackBoxFunc::Keccakf1600 => Some(25), + // Can only apply a range constraint to one // witness at a time. BlackBoxFunc::RANGE => Some(1), @@ -598,6 +603,7 @@ fn black_box_expected_output_size(name: BlackBoxFunc) -> Option { BlackBoxFunc::AND | BlackBoxFunc::XOR => Some(1), // 32 byte hash algorithms BlackBoxFunc::Keccak256 | BlackBoxFunc::SHA256 | BlackBoxFunc::Blake2s => Some(32), + BlackBoxFunc::Keccakf1600 => Some(25), // Pedersen commitment returns a point BlackBoxFunc::PedersenCommitment => Some(2), // Pedersen hash returns a field diff --git a/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index cada0b97f93..96028709a0e 100644 --- a/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -374,6 +374,7 @@ fn simplify_black_box_func( match bb_func { BlackBoxFunc::SHA256 => simplify_hash(dfg, arguments, acvm::blackbox_solver::sha256), BlackBoxFunc::Blake2s => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake2s), + BlackBoxFunc::Keccakf1600 => SimplifyResult::None, //TODO(Guillaume) BlackBoxFunc::Keccak256 => { match (dfg.get_array_constant(arguments[0]), dfg.get_numeric_constant(arguments[1])) { (Some((input, _)), Some(num_bytes)) if array_is_constant(dfg, &input) => { From 8c96d4ca35b777d1b5c9ddf796664c02c1835496 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Fri, 5 Jan 2024 18:36:12 +0000 Subject: [PATCH 3/3] feat!: add `blake3` blackbox function to acir format (#3856) Resolves https://github.com/noir-lang/noir/issues/3171 We've started adding breaking changes to the serialisation so I thought I'd chuck this in. # Checklist: Remove the checklist to signal you've completed it. Enable auto-merge if the PR is ready to merge. - [ ] If the pull request requires a cryptography review (e.g. cryptographic algorithm implementations) I have added the 'crypto' tag. - [x] I have reviewed my diff in github, line by line and removed unexpected formatting changes, testing logs, or commented-out code. - [x] Every change is related to the PR description. - [x] I have [linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this pull request to relevant issues (if any exist). --- .../dsl/acir_format/acir_format.cpp | 5 ++ .../dsl/acir_format/acir_format.hpp | 3 + .../dsl/acir_format/acir_format.test.cpp | 6 ++ .../acir_format/acir_to_constraint_buf.hpp | 12 ++++ .../dsl/acir_format/blake3_constraint.cpp | 44 +++++++++++++ .../dsl/acir_format/blake3_constraint.hpp | 29 +++++++++ .../dsl/acir_format/block_constraint.test.cpp | 1 + .../dsl/acir_format/ecdsa_secp256k1.test.cpp | 3 + .../dsl/acir_format/ecdsa_secp256r1.test.cpp | 4 ++ .../acir_format/recursion_constraint.test.cpp | 2 + .../dsl/acir_format/serde/acir.hpp | 62 +++++++++++++++++++ noir/Cargo.lock | 26 ++++++++ noir/acvm-repo/acir/codegen/acir.cpp | 52 +++++++++++++++- .../acir/src/circuit/black_box_functions.rs | 4 ++ .../opcodes/black_box_function_call.rs | 7 +++ .../acvm/src/compiler/transformers/mod.rs | 3 +- noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs | 10 ++- noir/acvm-repo/blackbox_solver/Cargo.toml | 1 + noir/acvm-repo/blackbox_solver/src/lib.rs | 4 ++ .../ssa/acir_gen/acir_ir/generated_acir.rs | 7 ++- .../src/ssa/ir/instruction/call.rs | 1 + noir/noir_stdlib/src/hash.nr | 3 + .../execution_success/blake3/Nargo.toml | 7 +++ .../execution_success/blake3/Prover.toml | 37 +++++++++++ .../execution_success/blake3/src/main.nr | 6 ++ .../mock_backend/src/info_cmd.rs | 1 + .../src/__snapshots__/index.test.ts.snap | 4 +- 27 files changed, 338 insertions(+), 6 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.cpp create mode 100644 barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.hpp create mode 100644 noir/test_programs/execution_success/blake3/Nargo.toml create mode 100644 noir/test_programs/execution_success/blake3/Prover.toml create mode 100644 noir/test_programs/execution_success/blake3/src/main.nr diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 23c936b6a27..912c23e77df 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -119,6 +119,11 @@ void build_constraints(Builder& builder, acir_format const& constraint_system, b create_blake2s_constraints(builder, constraint); } + // Add blake3 constraints + for (const auto& constraint : constraint_system.blake3_constraints) { + create_blake3_constraints(builder, constraint); + } + // Add keccak constraints for (const auto& constraint : constraint_system.keccak_constraints) { create_keccak_constraints(builder, constraint); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 6dadfa7bd6e..a2f198e2f7f 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -3,6 +3,7 @@ #include "barretenberg/dsl/types.hpp" #include "barretenberg/serialize/msgpack.hpp" #include "blake2s_constraint.hpp" +#include "blake3_constraint.hpp" #include "block_constraint.hpp" #include "ecdsa_secp256k1.hpp" #include "ecdsa_secp256r1.hpp" @@ -30,6 +31,7 @@ struct acir_format { std::vector ecdsa_k1_constraints; std::vector ecdsa_r1_constraints; std::vector blake2s_constraints; + std::vector blake3_constraints; std::vector keccak_constraints; std::vector keccak_var_constraints; std::vector keccak_permutations; @@ -56,6 +58,7 @@ struct acir_format { ecdsa_k1_constraints, ecdsa_r1_constraints, blake2s_constraints, + blake3_constraints, keccak_constraints, keccak_var_constraints, keccak_permutations, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index a3ffaedd720..4b6fdcd52a5 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -37,6 +37,7 @@ TEST_F(AcirFormatTests, TestASingleConstraintNoPubInputs) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -144,6 +145,7 @@ TEST_F(AcirFormatTests, TestLogicGateFromNoirCircuit) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -209,6 +211,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifyPass) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -297,6 +300,7 @@ TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -404,6 +408,7 @@ TEST_F(AcirFormatTests, TestVarKeccak) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = { keccak }, .keccak_permutations = {}, @@ -442,6 +447,7 @@ TEST_F(AcirFormatTests, TestKeccakPermutation) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = { keccak_permutation }, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp index 20da55ce6c3..9b6b36d7e06 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp @@ -3,6 +3,7 @@ #include "barretenberg/common/container.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include "barretenberg/dsl/acir_format/blake2s_constraint.hpp" +#include "barretenberg/dsl/acir_format/blake3_constraint.hpp" #include "barretenberg/dsl/acir_format/block_constraint.hpp" #include "barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp" #include "barretenberg/dsl/acir_format/keccak_constraint.hpp" @@ -113,6 +114,17 @@ void handle_blackbox_func_call(Circuit::Opcode::BlackBoxFuncCall const& arg, aci }), .result = map(arg.outputs, [](auto& e) { return e.value; }), }); + } else if constexpr (std::is_same_v) { + af.blake3_constraints.push_back(Blake3Constraint{ + .inputs = map(arg.inputs, + [](auto& e) { + return Blake3Input{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + }); } else if constexpr (std::is_same_v) { af.schnorr_constraints.push_back(SchnorrConstraint{ .message = map(arg.message, [](auto& e) { return e.witness.value; }), diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.cpp new file mode 100644 index 00000000000..7348ee25d0d --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.cpp @@ -0,0 +1,44 @@ +#include "blake3_constraint.hpp" +#include "round.hpp" + +namespace acir_format { + +template void create_blake3_constraints(Builder& builder, const Blake3Constraint& constraint) +{ + using byte_array_ct = proof_system::plonk::stdlib::byte_array; + using field_ct = proof_system::plonk::stdlib::field_t; + + // Create byte array struct + byte_array_ct arr(&builder); + + // 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(&builder, witness_index); + byte_array_ct element_bytes(element, num_bytes); + + arr.write(element_bytes); + } + + byte_array_ct output_bytes = proof_system::plonk::stdlib::blake3s(arr); + + // Convert byte array to vector of field_t + auto bytes = output_bytes.bytes(); + + for (size_t i = 0; i < bytes.size(); ++i) { + builder.assert_equal(bytes[i].normalize().witness_index, constraint.result[i]); + } +} + +template void create_blake3_constraints(UltraCircuitBuilder& builder, + const Blake3Constraint& constraint); +template void create_blake3_constraints(GoblinUltraCircuitBuilder& builder, + const Blake3Constraint& constraint); + +} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.hpp new file mode 100644 index 00000000000..2fe421fb16c --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/blake3_constraint.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "barretenberg/dsl/types.hpp" +#include "barretenberg/serialize/msgpack.hpp" +#include +#include + +namespace acir_format { + +struct Blake3Input { + uint32_t witness; + uint32_t num_bits; + + // For serialization, update with any new fields + MSGPACK_FIELDS(witness, num_bits); + friend bool operator==(Blake3Input const& lhs, Blake3Input const& rhs) = default; +}; + +struct Blake3Constraint { + std::vector inputs; + std::vector result; + + // For serialization, update with any new fields + MSGPACK_FIELDS(inputs, result); + friend bool operator==(Blake3Constraint const& lhs, Blake3Constraint const& rhs) = default; +}; + +template void create_blake3_constraints(Builder& builder, const Blake3Constraint& constraint); + +} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp index 649c751edc5..0473907838a 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp @@ -118,6 +118,7 @@ TEST_F(UltraPlonkRAM, TestBlockConstraint) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index fb8f99288ee..4dfdff06fb2 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -97,6 +97,7 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintSucceed) .ecdsa_k1_constraints = { ecdsa_k1_constraint }, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -138,6 +139,7 @@ TEST_F(ECDSASecp256k1, TestECDSACompilesForVerifier) .ecdsa_k1_constraints = { ecdsa_k1_constraint }, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -174,6 +176,7 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintFail) .ecdsa_k1_constraints = { ecdsa_k1_constraint }, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp index 0e6fe54509a..deb95e656b5 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp @@ -131,6 +131,7 @@ TEST(ECDSASecp256r1, test_hardcoded) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = { ecdsa_r1_constraint }, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -173,6 +174,7 @@ TEST(ECDSASecp256r1, TestECDSAConstraintSucceed) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = { ecdsa_r1_constraint }, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -213,6 +215,7 @@ TEST(ECDSASecp256r1, TestECDSACompilesForVerifier) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = { ecdsa_r1_constraint }, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -248,6 +251,7 @@ TEST(ECDSASecp256r1, TestECDSAConstraintFail) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = { ecdsa_r1_constraint }, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 700ea0b70b0..63a60cda21a 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -90,6 +90,7 @@ Builder create_inner_circuit() .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, @@ -248,6 +249,7 @@ Builder create_outer_circuit(std::vector& inner_circuits) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, .keccak_permutations = {}, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp index b0c8baf17d1..4aa912073c8 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -70,6 +70,15 @@ struct BlackBoxFuncCall { static Blake2s bincodeDeserialize(std::vector); }; + struct Blake3 { + std::vector inputs; + std::vector outputs; + + friend bool operator==(const Blake3&, const Blake3&); + std::vector bincodeSerialize() const; + static Blake3 bincodeDeserialize(std::vector); + }; + struct SchnorrVerify { Circuit::FunctionInput public_key_x; Circuit::FunctionInput public_key_y; @@ -182,6 +191,7 @@ struct BlackBoxFuncCall { RANGE, SHA256, Blake2s, + Blake3, SchnorrVerify, PedersenCommitment, PedersenHash, @@ -2045,6 +2055,58 @@ Circuit::BlackBoxFuncCall::Blake2s serde::Deserializable BlackBoxFuncCall::Blake3::bincodeSerialize() const +{ + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); +} + +inline BlackBoxFuncCall::Blake3 BlackBoxFuncCall::Blake3::bincodeDeserialize(std::vector input) +{ + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw_or_abort("Some input bytes were not read"); + } + return value; +} + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::Blake3& obj, + Serializer& serializer) +{ + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.outputs, serializer); +} + +template <> +template +Circuit::BlackBoxFuncCall::Blake3 serde::Deserializable::deserialize( + Deserializer& deserializer) +{ + Circuit::BlackBoxFuncCall::Blake3 obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + inline bool operator==(const BlackBoxFuncCall::SchnorrVerify& lhs, const BlackBoxFuncCall::SchnorrVerify& rhs) { if (!(lhs.public_key_x == rhs.public_key_x)) { diff --git a/noir/Cargo.lock b/noir/Cargo.lock index f1fb11aea6a..82d4030f100 100644 --- a/noir/Cargo.lock +++ b/noir/Cargo.lock @@ -58,6 +58,7 @@ version = "0.38.0" dependencies = [ "acir", "blake2", + "blake3", "k256", "p256", "sha2", @@ -344,6 +345,12 @@ dependencies = [ "rand", ] +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + [[package]] name = "arrayvec" version = "0.7.4" @@ -575,6 +582,19 @@ dependencies = [ "digest", ] +[[package]] +name = "blake3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -963,6 +983,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "core-foundation-sys" version = "0.8.4" diff --git a/noir/acvm-repo/acir/codegen/acir.cpp b/noir/acvm-repo/acir/codegen/acir.cpp index 29981e66b35..27dc427227f 100644 --- a/noir/acvm-repo/acir/codegen/acir.cpp +++ b/noir/acvm-repo/acir/codegen/acir.cpp @@ -70,6 +70,15 @@ namespace Circuit { static Blake2s bincodeDeserialize(std::vector); }; + struct Blake3 { + std::vector inputs; + std::vector outputs; + + friend bool operator==(const Blake3&, const Blake3&); + std::vector bincodeSerialize() const; + static Blake3 bincodeDeserialize(std::vector); + }; + struct SchnorrVerify { Circuit::FunctionInput public_key_x; Circuit::FunctionInput public_key_y; @@ -177,7 +186,7 @@ namespace Circuit { static RecursiveAggregation bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const BlackBoxFuncCall&, const BlackBoxFuncCall&); std::vector bincodeSerialize() const; @@ -1830,6 +1839,47 @@ Circuit::BlackBoxFuncCall::Blake2s serde::Deserializable BlackBoxFuncCall::Blake3::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxFuncCall::Blake3 BlackBoxFuncCall::Blake3::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::Blake3 &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.outputs, serializer); +} + +template <> +template +Circuit::BlackBoxFuncCall::Blake3 serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::Blake3 obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Circuit { inline bool operator==(const BlackBoxFuncCall::SchnorrVerify &lhs, const BlackBoxFuncCall::SchnorrVerify &rhs) { diff --git a/noir/acvm-repo/acir/src/circuit/black_box_functions.rs b/noir/acvm-repo/acir/src/circuit/black_box_functions.rs index 47de9eaa9c2..6e45a9a2c21 100644 --- a/noir/acvm-repo/acir/src/circuit/black_box_functions.rs +++ b/noir/acvm-repo/acir/src/circuit/black_box_functions.rs @@ -19,6 +19,8 @@ pub enum BlackBoxFunc { SHA256, /// Calculates the Blake2s hash of the inputs. Blake2s, + /// Calculates the Blake3 hash of the inputs. + Blake3, /// Verifies a Schnorr signature over a curve which is "pairing friendly" with the curve on which the ACIR circuit is defined. /// /// The exact curve which this signature uses will vary based on the curve being used by ACIR. @@ -57,6 +59,7 @@ impl BlackBoxFunc { BlackBoxFunc::SHA256 => "sha256", BlackBoxFunc::SchnorrVerify => "schnorr_verify", BlackBoxFunc::Blake2s => "blake2s", + BlackBoxFunc::Blake3 => "blake3", BlackBoxFunc::PedersenCommitment => "pedersen_commitment", BlackBoxFunc::PedersenHash => "pedersen_hash", BlackBoxFunc::EcdsaSecp256k1 => "ecdsa_secp256k1", @@ -75,6 +78,7 @@ impl BlackBoxFunc { "sha256" => Some(BlackBoxFunc::SHA256), "schnorr_verify" => Some(BlackBoxFunc::SchnorrVerify), "blake2s" => Some(BlackBoxFunc::Blake2s), + "blake3" => Some(BlackBoxFunc::Blake3), "pedersen_commitment" => Some(BlackBoxFunc::PedersenCommitment), "pedersen_hash" => Some(BlackBoxFunc::PedersenHash), "ecdsa_secp256k1" => Some(BlackBoxFunc::EcdsaSecp256k1), diff --git a/noir/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 3775310ec3b..fea12f9c08a 100644 --- a/noir/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -39,6 +39,10 @@ pub enum BlackBoxFuncCall { inputs: Vec, outputs: Vec, }, + Blake3 { + inputs: Vec, + outputs: Vec, + }, SchnorrVerify { public_key_x: FunctionInput, public_key_y: FunctionInput, @@ -125,6 +129,7 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::RANGE { .. } => BlackBoxFunc::RANGE, BlackBoxFuncCall::SHA256 { .. } => BlackBoxFunc::SHA256, BlackBoxFuncCall::Blake2s { .. } => BlackBoxFunc::Blake2s, + BlackBoxFuncCall::Blake3 { .. } => BlackBoxFunc::Blake3, BlackBoxFuncCall::SchnorrVerify { .. } => BlackBoxFunc::SchnorrVerify, BlackBoxFuncCall::PedersenCommitment { .. } => BlackBoxFunc::PedersenCommitment, BlackBoxFuncCall::PedersenHash { .. } => BlackBoxFunc::PedersenHash, @@ -146,6 +151,7 @@ impl BlackBoxFuncCall { match self { BlackBoxFuncCall::SHA256 { inputs, .. } | BlackBoxFuncCall::Blake2s { inputs, .. } + | BlackBoxFuncCall::Blake3 { inputs, .. } | BlackBoxFuncCall::Keccak256 { inputs, .. } | BlackBoxFuncCall::Keccakf1600 { inputs, .. } | BlackBoxFuncCall::PedersenCommitment { inputs, .. } @@ -236,6 +242,7 @@ impl BlackBoxFuncCall { match self { BlackBoxFuncCall::SHA256 { outputs, .. } | BlackBoxFuncCall::Blake2s { outputs, .. } + | BlackBoxFuncCall::Blake3 { outputs, .. } | BlackBoxFuncCall::Keccak256 { outputs, .. } | BlackBoxFuncCall::Keccakf1600 { outputs, .. } | BlackBoxFuncCall::RecursiveAggregation { diff --git a/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs b/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs index 4d2a49c330b..003124f8b63 100644 --- a/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -117,7 +117,8 @@ pub(super) fn transform_internal( output_aggregation_object: outputs, .. } - | acir::circuit::opcodes::BlackBoxFuncCall::Blake2s { outputs, .. } => { + | acir::circuit::opcodes::BlackBoxFuncCall::Blake2s { outputs, .. } + | acir::circuit::opcodes::BlackBoxFuncCall::Blake3 { outputs, .. } => { for witness in outputs { transformer.mark_solvable(*witness); } diff --git a/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs index 0ae68bf0eb2..c36596235a2 100644 --- a/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -3,7 +3,7 @@ use acir::{ native_types::{Witness, WitnessMap}, FieldElement, }; -use acvm_blackbox_solver::{blake2s, keccak256, sha256}; +use acvm_blackbox_solver::{blake2s, blake3, keccak256, sha256}; use self::{hash::keccakf1600, pedersen::pedersen_hash}; @@ -83,6 +83,14 @@ pub(crate) fn solve( blake2s, bb_func.get_black_box_func(), ), + BlackBoxFuncCall::Blake3 { inputs, outputs } => solve_generic_256_hash_opcode( + initial_witness, + inputs, + None, + outputs, + blake3, + bb_func.get_black_box_func(), + ), BlackBoxFuncCall::Keccak256 { inputs, outputs } => solve_generic_256_hash_opcode( initial_witness, inputs, diff --git a/noir/acvm-repo/blackbox_solver/Cargo.toml b/noir/acvm-repo/blackbox_solver/Cargo.toml index be2a58417f4..258321d8ef4 100644 --- a/noir/acvm-repo/blackbox_solver/Cargo.toml +++ b/noir/acvm-repo/blackbox_solver/Cargo.toml @@ -17,6 +17,7 @@ acir.workspace = true thiserror.workspace = true blake2 = "0.10.6" +blake3 = "1.5.0" sha2 = "0.10.6" sha3 = "0.10.6" k256 = { version = "0.11.0", features = [ diff --git a/noir/acvm-repo/blackbox_solver/src/lib.rs b/noir/acvm-repo/blackbox_solver/src/lib.rs index cf2cf295f7a..ede648ef75e 100644 --- a/noir/acvm-repo/blackbox_solver/src/lib.rs +++ b/noir/acvm-repo/blackbox_solver/src/lib.rs @@ -59,6 +59,10 @@ pub fn blake2s(inputs: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError> { .map_err(|err| BlackBoxResolutionError::Failed(BlackBoxFunc::Blake2s, err)) } +pub fn blake3(inputs: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError> { + Ok(blake3::hash(inputs).into()) +} + pub fn keccak256(inputs: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError> { generic_hash_256::(inputs) .map_err(|err| BlackBoxResolutionError::Failed(BlackBoxFunc::Keccak256, err)) diff --git a/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 076039efbbf..c9f7ee51e97 100644 --- a/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -155,6 +155,7 @@ impl GeneratedAcir { BlackBoxFunc::Blake2s => { BlackBoxFuncCall::Blake2s { inputs: inputs[0].clone(), outputs } } + BlackBoxFunc::Blake3 => BlackBoxFuncCall::Blake3 { inputs: inputs[0].clone(), outputs }, BlackBoxFunc::SchnorrVerify => { BlackBoxFuncCall::SchnorrVerify { public_key_x: inputs[0][0], @@ -572,6 +573,7 @@ fn black_box_func_expected_input_size(name: BlackBoxFunc) -> Option { BlackBoxFunc::Keccak256 | BlackBoxFunc::SHA256 | BlackBoxFunc::Blake2s + | BlackBoxFunc::Blake3 | BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash => None, @@ -602,7 +604,10 @@ fn black_box_expected_output_size(name: BlackBoxFunc) -> Option { // or the operation. BlackBoxFunc::AND | BlackBoxFunc::XOR => Some(1), // 32 byte hash algorithms - BlackBoxFunc::Keccak256 | BlackBoxFunc::SHA256 | BlackBoxFunc::Blake2s => Some(32), + BlackBoxFunc::Keccak256 + | BlackBoxFunc::SHA256 + | BlackBoxFunc::Blake2s + | BlackBoxFunc::Blake3 => Some(32), BlackBoxFunc::Keccakf1600 => Some(25), // Pedersen commitment returns a point BlackBoxFunc::PedersenCommitment => Some(2), diff --git a/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 96028709a0e..f77e84d99e0 100644 --- a/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -374,6 +374,7 @@ fn simplify_black_box_func( match bb_func { BlackBoxFunc::SHA256 => simplify_hash(dfg, arguments, acvm::blackbox_solver::sha256), BlackBoxFunc::Blake2s => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake2s), + BlackBoxFunc::Blake3 => simplify_hash(dfg, arguments, acvm::blackbox_solver::blake3), BlackBoxFunc::Keccakf1600 => SimplifyResult::None, //TODO(Guillaume) BlackBoxFunc::Keccak256 => { match (dfg.get_array_constant(arguments[0]), dfg.get_numeric_constant(arguments[1])) { diff --git a/noir/noir_stdlib/src/hash.nr b/noir/noir_stdlib/src/hash.nr index ad7e4f2e28f..5933209d9bc 100644 --- a/noir/noir_stdlib/src/hash.nr +++ b/noir/noir_stdlib/src/hash.nr @@ -7,6 +7,9 @@ pub fn sha256(_input: [u8; N]) -> [u8; 32] {} #[foreign(blake2s)] pub fn blake2s(_input: [u8; N]) -> [u8; 32] {} +#[foreign(blake3)] +pub fn blake3(_input: [u8; N]) -> [u8; 32] {} + struct PedersenPoint { x : Field, y : Field, diff --git a/noir/test_programs/execution_success/blake3/Nargo.toml b/noir/test_programs/execution_success/blake3/Nargo.toml new file mode 100644 index 00000000000..29f6ad5f11c --- /dev/null +++ b/noir/test_programs/execution_success/blake3/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "blake3" +type = "bin" +authors = [""] +compiler_version = ">=0.22.0" + +[dependencies] \ No newline at end of file diff --git a/noir/test_programs/execution_success/blake3/Prover.toml b/noir/test_programs/execution_success/blake3/Prover.toml new file mode 100644 index 00000000000..c807701479b --- /dev/null +++ b/noir/test_programs/execution_success/blake3/Prover.toml @@ -0,0 +1,37 @@ +# hello as bytes +# https://connor4312.github.io/blake3/index.html +x = [104, 101, 108, 108, 111] +result = [ + 0xea, + 0x8f, + 0x16, + 0x3d, + 0xb3, + 0x86, + 0x82, + 0x92, + 0x5e, + 0x44, + 0x91, + 0xc5, + 0xe5, + 0x8d, + 0x4b, + 0xb3, + 0x50, + 0x6e, + 0xf8, + 0xc1, + 0x4e, + 0xb7, + 0x8a, + 0x86, + 0xe9, + 0x08, + 0xc5, + 0x62, + 0x4a, + 0x67, + 0x20, + 0x0f, +] diff --git a/noir/test_programs/execution_success/blake3/src/main.nr b/noir/test_programs/execution_success/blake3/src/main.nr new file mode 100644 index 00000000000..3bfea6c5f95 --- /dev/null +++ b/noir/test_programs/execution_success/blake3/src/main.nr @@ -0,0 +1,6 @@ +use dep::std; + +fn main(x: [u8; 5], result: [u8; 32]) { + let digest = std::hash::blake3(x); + assert(digest == result); +} diff --git a/noir/tooling/backend_interface/test-binaries/mock_backend/src/info_cmd.rs b/noir/tooling/backend_interface/test-binaries/mock_backend/src/info_cmd.rs index 09c9596fb5a..fd8cf602125 100644 --- a/noir/tooling/backend_interface/test-binaries/mock_backend/src/info_cmd.rs +++ b/noir/tooling/backend_interface/test-binaries/mock_backend/src/info_cmd.rs @@ -14,6 +14,7 @@ const INFO_RESPONSE: &str = r#"{ "range", "sha256", "blake2s", + "blake3", "keccak256", "schnorr_verify", "pedersen", diff --git a/yarn-project/noir-compiler/src/__snapshots__/index.test.ts.snap b/yarn-project/noir-compiler/src/__snapshots__/index.test.ts.snap index 1a99b10fe69..a68f82ec125 100644 --- a/yarn-project/noir-compiler/src/__snapshots__/index.test.ts.snap +++ b/yarn-project/noir-compiler/src/__snapshots__/index.test.ts.snap @@ -7,7 +7,7 @@ exports[`noir-compiler using nargo compiles the test contract 1`] = ` "events": [], "functions": [ { - "bytecode": "H4sIAAAAAAAA/62QUQqEMAxEY7ew10maxCZ/e5Ut1vufQFRsoX7rQJiZnyG8CAABLk3HfZv3vrX8gbt6/zXHZ6Lpxa0wbDHOIjWnSkx/TF5MUbTMRkZquiRjriaWvXhGJ+FKqzqvbSy+9xeOXHsOA/+TaRwY7+UbOCacAQAA", + "bytecode": "H4sIAAAAAAAA/62QQQ6EMAwDQ7eHfU7SJDS57Ve2ovz/BQgQrVTOYCmyfbGiiQAQ4NJ03Ld571vLH7ir919zfCaaXtwKwxbjLFJzqsT0x+TFFEXLbGSkpksy5mpi2YtndBKutKrz2sbie3/hyLXnMPA/mcaB8Q4eX+1anAEAAA==", "functionType": "secret", "isInternal": false, "name": "constructor", @@ -205,7 +205,7 @@ exports[`noir-compiler using wasm binary compiles the test contract 1`] = ` "events": [], "functions": [ { - "bytecode": "H4sIAAAAAAAA/62QUQqEMAxEY7ew10maxCZ/e5Ut1vufQFRsoX7rQJiZnyG8CAABLk3HfZv3vrX8gbt6/zXHZ6Lpxa0wbDHOIjWnSkx/TF5MUbTMRkZquiRjriaWvXhGJ+FKqzqvbSy+9xeOXHsOA/+TaRwY7+UbOCacAQAA", + "bytecode": "H4sIAAAAAAAA/62QQQ6EMAwDQ7eHfU7SJDS57Ve2ovz/BQgQrVTOYCmyfbGiiQAQ4NJ03Ld571vLH7ir919zfCaaXtwKwxbjLFJzqsT0x+TFFEXLbGSkpksy5mpi2YtndBKutKrz2sbie3/hyLXnMPA/mcaB8Q4eX+1anAEAAA==", "functionType": "secret", "isInternal": false, "name": "constructor",