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..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); @@ -126,6 +131,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..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,8 +31,10 @@ 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; std::vector pedersen_constraints; std::vector pedersen_hash_constraints; std::vector fixed_base_scalar_mul_constraints; @@ -55,8 +58,10 @@ struct acir_format { ecdsa_k1_constraints, ecdsa_r1_constraints, blake2s_constraints, + blake3_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..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,8 +37,10 @@ TEST_F(AcirFormatTests, TestASingleConstraintNoPubInputs) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -143,8 +145,10 @@ TEST_F(AcirFormatTests, TestLogicGateFromNoirCircuit) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -207,8 +211,10 @@ TEST_F(AcirFormatTests, TestSchnorrVerifyPass) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -294,8 +300,10 @@ TEST_F(AcirFormatTests, TestSchnorrVerifySmallRange) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -400,8 +408,10 @@ TEST_F(AcirFormatTests, TestVarKeccak) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = { keccak }, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -419,4 +429,48 @@ 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 = {}, + .blake3_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..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; }), @@ -180,6 +192,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/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 a20de2c75db..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,8 +118,10 @@ TEST_F(UltraPlonkRAM, TestBlockConstraint) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_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..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,8 +97,10 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintSucceed) .ecdsa_k1_constraints = { ecdsa_k1_constraint }, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -137,8 +139,10 @@ TEST_F(ECDSASecp256k1, TestECDSACompilesForVerifier) .ecdsa_k1_constraints = { ecdsa_k1_constraint }, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -172,8 +176,10 @@ TEST_F(ECDSASecp256k1, TestECDSAConstraintFail) .ecdsa_k1_constraints = { ecdsa_k1_constraint }, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_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..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,8 +131,10 @@ TEST(ECDSASecp256r1, test_hardcoded) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = { ecdsa_r1_constraint }, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -172,8 +174,10 @@ TEST(ECDSASecp256r1, TestECDSAConstraintSucceed) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = { ecdsa_r1_constraint }, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -211,8 +215,10 @@ TEST(ECDSASecp256r1, TestECDSACompilesForVerifier) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = { ecdsa_r1_constraint }, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -245,8 +251,10 @@ TEST(ECDSASecp256r1, TestECDSAConstraintFail) .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = { ecdsa_r1_constraint }, .blake2s_constraints = {}, + .blake3_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..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,8 +90,10 @@ Builder create_inner_circuit() .ecdsa_k1_constraints = {}, .ecdsa_r1_constraints = {}, .blake2s_constraints = {}, + .blake3_constraints = {}, .keccak_constraints = {}, .keccak_var_constraints = {}, + .keccak_permutations = {}, .pedersen_constraints = {}, .pedersen_hash_constraints = {}, .fixed_base_scalar_mul_constraints = {}, @@ -247,8 +249,10 @@ 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 = {}, .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..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; @@ -155,6 +164,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; @@ -173,6 +191,7 @@ struct BlackBoxFuncCall { RANGE, SHA256, Blake2s, + Blake3, SchnorrVerify, PedersenCommitment, PedersenHash, @@ -181,6 +200,7 @@ struct BlackBoxFuncCall { FixedBaseScalarMul, Keccak256, Keccak256VariableLength, + Keccakf1600, RecursiveAggregation> value; @@ -2035,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)) { @@ -2520,6 +2592,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/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 18532940752..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; @@ -155,6 +164,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 +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; @@ -1821,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) { @@ -2188,6 +2247,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..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. @@ -38,6 +40,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, @@ -55,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", @@ -63,6 +68,7 @@ impl BlackBoxFunc { BlackBoxFunc::XOR => "xor", BlackBoxFunc::RANGE => "range", BlackBoxFunc::Keccak256 => "keccak256", + BlackBoxFunc::Keccakf1600 => "keccak_f1600", BlackBoxFunc::RecursiveAggregation => "recursive_aggregation", BlackBoxFunc::EcdsaSecp256r1 => "ecdsa_secp256r1", } @@ -72,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), @@ -81,6 +88,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..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, @@ -88,6 +92,10 @@ pub enum BlackBoxFuncCall { var_message_size: FunctionInput, outputs: Vec, }, + Keccakf1600 { + inputs: Vec, + outputs: Vec, + }, RecursiveAggregation { verification_key: Vec, proof: Vec, @@ -121,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, @@ -129,6 +138,7 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::FixedBaseScalarMul { .. } => BlackBoxFunc::FixedBaseScalarMul, BlackBoxFuncCall::Keccak256 { .. } => BlackBoxFunc::Keccak256, BlackBoxFuncCall::Keccak256VariableLength { .. } => BlackBoxFunc::Keccak256, + BlackBoxFuncCall::Keccakf1600 { .. } => BlackBoxFunc::Keccakf1600, BlackBoxFuncCall::RecursiveAggregation { .. } => BlackBoxFunc::RecursiveAggregation, } } @@ -141,7 +151,9 @@ impl BlackBoxFuncCall { match self { BlackBoxFuncCall::SHA256 { inputs, .. } | BlackBoxFuncCall::Blake2s { inputs, .. } + | BlackBoxFuncCall::Blake3 { inputs, .. } | BlackBoxFuncCall::Keccak256 { inputs, .. } + | BlackBoxFuncCall::Keccakf1600 { inputs, .. } | BlackBoxFuncCall::PedersenCommitment { inputs, .. } | BlackBoxFuncCall::PedersenHash { inputs, .. } => inputs.to_vec(), BlackBoxFuncCall::AND { lhs, rhs, .. } | BlackBoxFuncCall::XOR { lhs, rhs, .. } => { @@ -230,7 +242,9 @@ impl BlackBoxFuncCall { match self { BlackBoxFuncCall::SHA256 { outputs, .. } | BlackBoxFuncCall::Blake2s { outputs, .. } + | BlackBoxFuncCall::Blake3 { 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..003124f8b63 100644 --- a/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/noir/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -112,11 +112,13 @@ pub(super) fn transform_internal( outputs, .. } + | acir::circuit::opcodes::BlackBoxFuncCall::Keccakf1600 { outputs, .. } | acir::circuit::opcodes::BlackBoxFuncCall::RecursiveAggregation { 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/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..c36596235a2 100644 --- a/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -3,12 +3,12 @@ use acir::{ native_types::{Witness, WitnessMap}, FieldElement, }; -use acvm_blackbox_solver::{blake2s, keccak256, sha256}; +use acvm_blackbox_solver::{blake2s, blake3, 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; @@ -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, @@ -101,6 +109,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/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 bd8271260af..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], @@ -222,6 +223,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!( @@ -569,9 +573,12 @@ fn black_box_func_expected_input_size(name: BlackBoxFunc) -> Option { BlackBoxFunc::Keccak256 | BlackBoxFunc::SHA256 | BlackBoxFunc::Blake2s + | BlackBoxFunc::Blake3 | 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), @@ -597,7 +604,11 @@ 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), // 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..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,8 @@ 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])) { (Some((input, _)), Some(num_bytes)) if array_is_constant(dfg, &input) => { 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",