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 257bcf4a2e23..75be3c9d18e1 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 @@ -192,10 +192,6 @@ TEST(ECDSASecp256r1, TestECDSAConstraintSucceed) .blake2s_constraints = {}, .blake3_constraints = {}, .keccak_constraints = {}, - .keccak_permutations = {}, - .pedersen_constraints = {}, - .pedersen_hash_constraints = {}, - .poseidon2_constraints = {}, .multi_scalar_mul_constraints = {}, .ec_add_constraints = {}, .recursion_constraints = {}, 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 14139cba4191..2285298599e8 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 Keccakf1600 { std::array state; std::array result; 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 2c8ad3cc5f8a..95be16509da7 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp @@ -185,6 +185,15 @@ struct BlackBoxFuncCall { static Keccakf1600 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; @@ -3359,6 +3368,58 @@ Program::BlackBoxFuncCall::Keccakf1600 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_or_abort("Some input bytes were not read"); + } + return value; +} + +} // namespace Program + +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) { @@ -3395,7 +3456,7 @@ inline BlackBoxFuncCall::RecursiveAggregation BlackBoxFuncCall::RecursiveAggrega return value; } -} // end of namespace Program +} // namespace Circuit template <> template diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp index a9ce1ab4eb07..8871cf5c2c61 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp @@ -807,6 +807,92 @@ stdlib::byte_array keccak::hash_using_permutation_opcode(byte_ return result; } +// 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) { 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 9efb1679b66a..5e2a3e38493b 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.test.cpp @@ -322,3 +322,46 @@ TEST(stdlib_keccak, test_permutation_opcode_double_block) bool proof_result = CircuitChecker::check(builder); 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/noir-repo/acvm-repo/acir/codegen/acir.cpp b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp index 47e184a63328..5254635364fc 100644 --- a/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp +++ b/noir/noir-repo/acvm-repo/acir/codegen/acir.cpp @@ -185,6 +185,15 @@ namespace Program { static Keccakf1600 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; @@ -2928,6 +2937,47 @@ Program::BlackBoxFuncCall::Keccakf1600 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) { if (!(lhs.verification_key == rhs.verification_key)) { return false; } if (!(lhs.proof == rhs.proof)) { return false; } diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index b0e77b15c2c3..7c8156ec056b 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -109,6 +109,10 @@ pub enum BlackBoxFuncCall { inputs: Box<[FunctionInput; 25]>, outputs: Box<[Witness; 25]>, }, + Keccakf1600 { + inputs: Vec, + outputs: Vec, + }, RecursiveAggregation { verification_key: Vec, proof: Vec, diff --git a/noir/noir-repo/noir_stdlib/src/hash/keccak.nr b/noir/noir-repo/noir_stdlib/src/hash/keccak.nr new file mode 100644 index 000000000000..1a8801ac9fd1 --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/hash/keccak.nr @@ -0,0 +1,89 @@ +global LIMBS_PER_BLOCK = 17; //BLOCK_SIZE / 8; +global NUM_KECCAK_LANES = 25; + +fn hash(input: [u8; N], input_length: u64) -> [u8; 32] +{ + // no var keccak for now + assert(N == input_length); + + //1. format_input_lanes + let max_blocks = (N as u64 + BLOCK_SIZE) / BLOCK_SIZE; + let max_blocks_length = (BLOCK_SIZE * (max_blocks)); //maximum number of bytes to hash + assert(max_blocks_length < 1000); + let mut block_bytes = [0; 1000]; //should be max_blocks_length + for i in 0..N { + block_bytes[i] = input[i]; + } + let byte_difference = max_blocks_length - N; + block_bytes[N] = 1; + block_bytes[max_blocks_length - 1] = 0x80; + + + // keccak lanes interpret memory as little-endian integers, + // means we need to swap our byte ordering... + let num_limbs = max_blocks * LIMBS_PER_BLOCK; //max_blocks_length / WORD_SIZE; + for i in 0..num_limbs{ + let mut temp = [0;8]; + + for j in 0..8 { + temp[j] = block_bytes[8*i+j]; + } + for j in 0..8 { + block_bytes[8*i+j] = temp[7-j]; + } + } + let byte_size = max_blocks_length; + assert(num_limbs < 1000); + let mut sliced_buffer = [0;1000]; + // populate a vector of 64-bit limbs from our byte array + for i in 0..num_limbs { + let mut sliced = 0; + if ((i as u64) * WORD_SIZE + WORD_SIZE > byte_size) { + let slice_size = byte_size - (i * WORD_SIZE); + let byte_shift = (WORD_SIZE - slice_size) * 8; + let mut v = 1; + for k in 0..slice_size { + sliced += v*(block_bytes[i * WORD_SIZE+7-k] as Field); + v = v*256; + } + let w = 1 << byte_shift; + sliced *= w as Field; + }else { + let mut v = 1; + for k in 0..WORD_SIZE { + sliced += v*(block_bytes[i * WORD_SIZE+7-k] as Field); + v = v*256; + } + } + sliced_buffer[i] =sliced as u64; + } + + //2. sponge_absorb + let num_blocks = max_blocks; + let mut state : [u64;NUM_KECCAK_LANES]= [0; NUM_KECCAK_LANES]; + for i in 0..num_blocks { + if (i == 0) { + for j in 0..LIMBS_PER_BLOCK { + state[j] = sliced_buffer[j]; + } + } else { + for j in 0..LIMBS_PER_BLOCK { + state[j] = state[j] ^ sliced_buffer[i * LIMBS_PER_BLOCK + j]; + } + } + state = std::hash::keccak_f1600(state); + } + + //3. sponge_squeeze + let mut result = [0; 32]; + + // Each hash limb represents a little-endian integer. Need to reverse bytes before we write into the output array + for i in 0..4 { + let lane = state[i] as Field; + let lane_le = lane.to_le_bytes(8); + for j in 0..8 { + result[8*i+j] = lane_le[7-j]; + } + } + result +} diff --git a/noir/noir-repo/test_programs/execution_success/keccak256/src/main.nr b/noir/noir-repo/test_programs/execution_success/keccak256/src/main.nr index eb401fe614cc..8eb9211069e8 100644 --- a/noir/noir-repo/test_programs/execution_success/keccak256/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/keccak256/src/main.nr @@ -6,8 +6,10 @@ fn main(x: Field, result: [u8; 32]) { // The padding is taken care of by the program let digest = std::hash::keccak256([x as u8], 1); assert(digest == result); - - //#1399: variable message size + let digest_with_permutation = std::hash::keccak256::hash([x as u8], 1); + assert(digest_with_permutation == result); + + //#1399: variable meesage size let message_size = 4; let hash_a = std::hash::keccak256([1, 2, 3, 4], message_size); let hash_b = std::hash::keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size);