Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Noir development branch (serialization changes) #3858

Merged
merged 4 commits into from
Jan 8, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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 <guipublic@gmail.com>
Co-authored-by: guipublic <47281315+guipublic@users.noreply.github.com>
3 people authored Jan 5, 2024

Verified

This commit was created on github.com and signed with GitHub’s verified signature. The key has expired.
commit c98b807a843278b8a15f08e9e521b26382867977
2 changes: 1 addition & 1 deletion barretenberg/cpp/src/barretenberg/bb/main.cpp
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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) {
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ struct acir_format {
std::vector<Blake2sConstraint> blake2s_constraints;
std::vector<KeccakConstraint> keccak_constraints;
std::vector<KeccakVarConstraint> keccak_var_constraints;
std::vector<Keccakf1600> keccak_permutations;
std::vector<PedersenConstraint> pedersen_constraints;
std::vector<PedersenHashConstraint> pedersen_hash_constraints;
std::vector<FixedBaseScalarMul> 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,
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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<T, Circuit::BlackBoxFuncCall::Keccakf1600>) {
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<T, Circuit::BlackBoxFuncCall::RecursiveAggregation>) {
auto c = RecursionConstraint{
.key = map(arg.verification_key, [](auto& e) { return e.witness.value; }),
Original file line number Diff line number Diff line change
@@ -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 = {},
Original file line number Diff line number Diff line change
@@ -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 = {},
Original file line number Diff line number Diff line change
@@ -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 = {},
Original file line number Diff line number Diff line change
@@ -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 <typename Builder> void create_keccak_var_constraints(Builder& builder,
}
}

template <typename Builder> void create_keccak_permutations(Builder& builder, const Keccakf1600& constraint)
{
using field_ct = proof_system::plonk::stdlib::field_t<Builder>;

// Create the array containing the permuted state
std::array<field_ct, proof_system::plonk::stdlib::keccak<Builder>::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<field_ct, 25> output_state =
proof_system::plonk::stdlib::keccak<Builder>::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>(UltraCircuitBuilder& builder,
const KeccakConstraint& constraint);
template void create_keccak_var_constraints<UltraCircuitBuilder>(UltraCircuitBuilder& builder,
const KeccakVarConstraint& constraint);
template void create_keccak_permutations<UltraCircuitBuilder>(UltraCircuitBuilder& builder,
const Keccakf1600& constraint);

template void create_keccak_constraints<GoblinUltraCircuitBuilder>(GoblinUltraCircuitBuilder& builder,
const KeccakConstraint& constraint);
template void create_keccak_var_constraints<GoblinUltraCircuitBuilder>(GoblinUltraCircuitBuilder& builder,
const KeccakVarConstraint& constraint);

template void create_keccak_permutations<GoblinUltraCircuitBuilder>(GoblinUltraCircuitBuilder& builder,
const Keccakf1600& constraint);

} // namespace acir_format
Original file line number Diff line number Diff line change
@@ -15,6 +15,15 @@ struct HashInput {
friend bool operator==(HashInput const& lhs, HashInput const& rhs) = default;
};

struct Keccakf1600 {
std::vector<uint32_t> state;
std::vector<uint32_t> 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<HashInput> inputs;
std::vector<uint32_t> result;
@@ -36,5 +45,6 @@ struct KeccakVarConstraint {

template <typename Builder> void create_keccak_constraints(Builder& builder, const KeccakConstraint& constraint);
template <typename Builder> void create_keccak_var_constraints(Builder& builder, const KeccakVarConstraint& constraint);
template <typename Builder> void create_keccak_permutations(Builder& builder, const Keccakf1600& constraint);

} // namespace acir_format
Original file line number Diff line number Diff line change
@@ -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<Builder>& inner_circuits)
.blake2s_constraints = {},
.keccak_constraints = {},
.keccak_var_constraints = {},
.keccak_permutations = {},
.pedersen_constraints = {},
.pedersen_hash_constraints = {},
.fixed_base_scalar_mul_constraints = {},
62 changes: 62 additions & 0 deletions barretenberg/cpp/src/barretenberg/dsl/acir_format/serde/acir.hpp
Original file line number Diff line number Diff line change
@@ -155,6 +155,15 @@ struct BlackBoxFuncCall {
static Keccak256VariableLength bincodeDeserialize(std::vector<uint8_t>);
};

struct Keccakf1600 {
std::vector<Circuit::FunctionInput> inputs;
std::vector<Circuit::Witness> outputs;

friend bool operator==(const Keccakf1600&, const Keccakf1600&);
std::vector<uint8_t> bincodeSerialize() const;
static Keccakf1600 bincodeDeserialize(std::vector<uint8_t>);
};

struct RecursiveAggregation {
std::vector<Circuit::FunctionInput> verification_key;
std::vector<Circuit::FunctionInput> 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<uint8_t> BlackBoxFuncCall::Keccakf1600::bincodeSerialize() const
{
auto serializer = serde::BincodeSerializer();
serde::Serializable<BlackBoxFuncCall::Keccakf1600>::serialize(*this, serializer);
return std::move(serializer).bytes();
}

inline BlackBoxFuncCall::Keccakf1600 BlackBoxFuncCall::Keccakf1600::bincodeDeserialize(std::vector<uint8_t> input)
{
auto deserializer = serde::BincodeDeserializer(input);
auto value = serde::Deserializable<BlackBoxFuncCall::Keccakf1600>::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 <typename Serializer>
void serde::Serializable<Circuit::BlackBoxFuncCall::Keccakf1600>::serialize(
const Circuit::BlackBoxFuncCall::Keccakf1600& obj, Serializer& serializer)
{
serde::Serializable<decltype(obj.inputs)>::serialize(obj.inputs, serializer);
serde::Serializable<decltype(obj.outputs)>::serialize(obj.outputs, serializer);
}

template <>
template <typename Deserializer>
Circuit::BlackBoxFuncCall::Keccakf1600 serde::Deserializable<Circuit::BlackBoxFuncCall::Keccakf1600>::deserialize(
Deserializer& deserializer)
{
Circuit::BlackBoxFuncCall::Keccakf1600 obj;
obj.inputs = serde::Deserializable<decltype(obj.inputs)>::deserialize(deserializer);
obj.outputs = serde::Deserializable<decltype(obj.outputs)>::deserialize(deserializer);
return obj;
}

namespace Circuit {

inline bool operator==(const BlackBoxFuncCall::RecursiveAggregation& lhs,
const BlackBoxFuncCall::RecursiveAggregation& rhs)
{
127 changes: 127 additions & 0 deletions barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.cpp
Original file line number Diff line number Diff line change
@@ -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<field_t<Builder>> keccak<Builder>::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 <typename Builder>
std::array<field_t<Builder>, keccak<Builder>::NUM_KECCAK_LANES> keccak<Builder>::permutation_opcode(
std::array<field_t<Builder>, NUM_KECCAK_LANES> state, Builder* ctx)
{
std::vector<field_t<Builder>> converted_buffer(NUM_KECCAK_LANES);
std::vector<field_t<Builder>> 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<Builder>::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 <typename Builder>
void keccak<Builder>::sponge_absorb_with_permutation_opcode(keccak_state& internal,
std::vector<field_t<Builder>>& 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<Builder>::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 <typename Builder>
stdlib::byte_array<Builder> keccak<Builder>::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<uint8_t> 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 <typename Builder>
stdlib::byte_array<Builder> keccak<Builder>::hash(byte_array_ct& input, const uint32_ct& num_bytes)
{
@@ -762,6 +849,46 @@ stdlib::byte_array<Builder> keccak<Builder>::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 <typename Builder>
std::array<field_t<Builder>, keccak<Builder>::NUM_KECCAK_LANES> keccak<Builder>::extended_2_normal(
keccak_state& internal)
{
std::array<field_t<Builder>, 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<Builder>::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 <typename Builder>
stdlib::byte_array<Builder> keccak<Builder>::sponge_squeeze_for_permutation_opcode(
std::array<field_t<Builder>, 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
11 changes: 11 additions & 0 deletions barretenberg/cpp/src/barretenberg/stdlib/hash/keccak/keccak.hpp
Original file line number Diff line number Diff line change
@@ -189,6 +189,17 @@ template <typename Builder> 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<field_ct, NUM_KECCAK_LANES> permutation_opcode(std::array<field_ct, NUM_KECCAK_LANES> state,
Builder* context);
static void sponge_absorb_with_permutation_opcode(keccak_state& internal,
std::vector<field_ct>& input_buffer,
const size_t input_size);
static std::array<field_ct, NUM_KECCAK_LANES> extended_2_normal(keccak_state& internal);
static byte_array_ct sponge_squeeze_for_permutation_opcode(std::array<field_ct, NUM_KECCAK_LANES> lanes,
Builder* context);
};

EXTERN_STDLIB_ULTRA_TYPE(keccak)
Original file line number Diff line number Diff line change
@@ -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<uint8_t> input_v(input.begin(), input.end());

byte_array input_arr(&builder, input_v);
byte_array output =
stdlib::keccak<Builder>::hash_using_permutation_opcode(input_arr, static_cast<uint32_t>(input.size()));

std::vector<uint8_t> expected = stdlib::keccak<Builder>::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<uint8_t> input_v(input.begin(), input.end());

byte_array input_arr(&builder, input_v);
byte_array output =
stdlib::keccak<Builder>::hash_using_permutation_opcode(input_arr, static_cast<uint32_t>(input.size()));

std::vector<uint8_t> expected = stdlib::keccak<Builder>::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);
}
52 changes: 51 additions & 1 deletion noir/acvm-repo/acir/codegen/acir.cpp
Original file line number Diff line number Diff line change
@@ -155,6 +155,15 @@ namespace Circuit {
static Keccak256VariableLength bincodeDeserialize(std::vector<uint8_t>);
};

struct Keccakf1600 {
std::vector<Circuit::FunctionInput> inputs;
std::vector<Circuit::Witness> outputs;

friend bool operator==(const Keccakf1600&, const Keccakf1600&);
std::vector<uint8_t> bincodeSerialize() const;
static Keccakf1600 bincodeDeserialize(std::vector<uint8_t>);
};

struct RecursiveAggregation {
std::vector<Circuit::FunctionInput> verification_key;
std::vector<Circuit::FunctionInput> proof;
@@ -168,7 +177,7 @@ namespace Circuit {
static RecursiveAggregation bincodeDeserialize(std::vector<uint8_t>);
};

std::variant<AND, XOR, RANGE, SHA256, Blake2s, SchnorrVerify, PedersenCommitment, PedersenHash, EcdsaSecp256k1, EcdsaSecp256r1, FixedBaseScalarMul, Keccak256, Keccak256VariableLength, RecursiveAggregation> value;
std::variant<AND, XOR, RANGE, SHA256, Blake2s, SchnorrVerify, PedersenCommitment, PedersenHash, EcdsaSecp256k1, EcdsaSecp256r1, FixedBaseScalarMul, Keccak256, Keccak256VariableLength, Keccakf1600, RecursiveAggregation> value;

friend bool operator==(const BlackBoxFuncCall&, const BlackBoxFuncCall&);
std::vector<uint8_t> bincodeSerialize() const;
@@ -2188,6 +2197,47 @@ Circuit::BlackBoxFuncCall::Keccak256VariableLength serde::Deserializable<Circuit
return obj;
}

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<uint8_t> BlackBoxFuncCall::Keccakf1600::bincodeSerialize() const {
auto serializer = serde::BincodeSerializer();
serde::Serializable<BlackBoxFuncCall::Keccakf1600>::serialize(*this, serializer);
return std::move(serializer).bytes();
}

inline BlackBoxFuncCall::Keccakf1600 BlackBoxFuncCall::Keccakf1600::bincodeDeserialize(std::vector<uint8_t> input) {
auto deserializer = serde::BincodeDeserializer(input);
auto value = serde::Deserializable<BlackBoxFuncCall::Keccakf1600>::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 <typename Serializer>
void serde::Serializable<Circuit::BlackBoxFuncCall::Keccakf1600>::serialize(const Circuit::BlackBoxFuncCall::Keccakf1600 &obj, Serializer &serializer) {
serde::Serializable<decltype(obj.inputs)>::serialize(obj.inputs, serializer);
serde::Serializable<decltype(obj.outputs)>::serialize(obj.outputs, serializer);
}

template <>
template <typename Deserializer>
Circuit::BlackBoxFuncCall::Keccakf1600 serde::Deserializable<Circuit::BlackBoxFuncCall::Keccakf1600>::deserialize(Deserializer &deserializer) {
Circuit::BlackBoxFuncCall::Keccakf1600 obj;
obj.inputs = serde::Deserializable<decltype(obj.inputs)>::deserialize(deserializer);
obj.outputs = serde::Deserializable<decltype(obj.outputs)>::deserialize(deserializer);
return obj;
}

namespace Circuit {

inline bool operator==(const BlackBoxFuncCall::RecursiveAggregation &lhs, const BlackBoxFuncCall::RecursiveAggregation &rhs) {
4 changes: 4 additions & 0 deletions noir/acvm-repo/acir/src/circuit/black_box_functions.rs
Original file line number Diff line number Diff line change
@@ -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,
}
59 changes: 59 additions & 0 deletions noir/acvm-repo/acir/src/circuit/mod.rs
Original file line number Diff line number Diff line change
@@ -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)])),
Original file line number Diff line number Diff line change
@@ -88,6 +88,10 @@ pub enum BlackBoxFuncCall {
var_message_size: FunctionInput,
outputs: Vec<Witness>,
},
Keccakf1600 {
inputs: Vec<FunctionInput>,
outputs: Vec<Witness>,
},
RecursiveAggregation {
verification_key: Vec<FunctionInput>,
proof: Vec<FunctionInput>,
@@ -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(),
1 change: 1 addition & 0 deletions noir/acvm-repo/acvm/src/compiler/transformers/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
..
79 changes: 79 additions & 0 deletions noir/acvm-repo/acvm/src/pwg/blackbox/hash.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
20 changes: 18 additions & 2 deletions noir/acvm-repo/acvm/src/pwg/blackbox/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
Original file line number Diff line number Diff line change
@@ -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<usize> {
| 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<usize> {
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
Original file line number Diff line number Diff line change
@@ -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) => {