Skip to content

Commit

Permalink
Move calldatahash to components (#462)
Browse files Browse the repository at this point in the history
* feat: add calldatahash from kernels component

* fix: tidy

* fix: address nit
  • Loading branch information
LHerskind authored May 4, 2023
1 parent 3e022e2 commit fd2604f
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 110 deletions.
38 changes: 5 additions & 33 deletions circuits/cpp/src/aztec3/circuits/rollup/base/.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "aztec3/circuits/abis/public_data_read.hpp"
#include "aztec3/circuits/abis/rollup/nullifier_leaf_preimage.hpp"
#include "aztec3/circuits/kernel/private/utils.hpp"
#include "aztec3/circuits/rollup/components/components.hpp"
#include "aztec3/circuits/rollup/test_utils/utils.hpp"
#include "aztec3/constants.hpp"
#include <aztec3/circuits/abis/call_context.hpp>
Expand Down Expand Up @@ -581,64 +582,35 @@ TEST_F(base_rollup_tests, native_calldata_hash)
// Execute the base rollup circuit with nullifiers, commitments and a contract deployment. Then check the calldata
// hash against the expected value.
std::array<PreviousKernelData<NT>, 2> kernel_data = { get_empty_kernel(), get_empty_kernel() };
std::vector<uint8_t> input_data = test_utils::utils::get_empty_calldata_leaf();

// Update commitment and nullifierss in kernels and testing byte array.
// Commitments and nullifiers are 32 bytes long, so we can update them in the byte array by offsetting by 32 bytes
// for every insertion. As there are two kernels in every leaf, nullifiers are offset by 8 elements (8*32). To
// insert correctly, the insertions of values from the second kernel must be offset by 4*32 bytes (kernel_offset).
// Further offset by 32 per prior insertion (j*32), and then only update the last byte (31) with the new value.
// Commitments inserted are [1,2,3,4,5,6,7,8]. Nullifiers inserted are [8,9,10,11,12,13,14,15]
for (size_t i = 0; i < 2; ++i) {
auto kernel_offset = i * 4 * 32;
for (size_t j = 0; j < 4; j++) {
auto const offset = static_cast<size_t>(kernel_offset + j * 32 + 31);
input_data[offset] = static_cast<uint8_t>(i * 4 + j + 1);
kernel_data[i].public_inputs.end.new_commitments[j] = fr(i * 4 + j + 1);
input_data[static_cast<unsigned long>(8 * 32 + offset)] = static_cast<uint8_t>(i * 4 + j + 8); // NOLINT
kernel_data[i].public_inputs.end.new_nullifiers[j] = fr(i * 4 + j + 8);
}
}

// Add a contract deployment
NewContractData<NT> new_contract = {
NewContractData<NT> const new_contract = {
.contract_address = fr(1),
.portal_contract_address = fr(3),
.function_tree_root = fr(2),
};
auto contract_leaf = crypto::pedersen_commitment::compress_native(
{ new_contract.contract_address, new_contract.portal_contract_address, new_contract.function_tree_root },
GeneratorIndex::CONTRACT_LEAF);
kernel_data[0].public_inputs.end.new_contracts[0] = new_contract;
auto contract_leaf_buffer = contract_leaf.to_buffer();
auto contract_address_buffer = new_contract.contract_address.to_field().to_buffer();
auto portal_address_buffer = new_contract.portal_contract_address.to_field().to_buffer();
// Insert the contract leaf and contract address into the byte array
for (uint8_t i = 0; i < 32; ++i) {
input_data[32 * 32 + i] = contract_leaf_buffer[i];
input_data[34 * 32 + i] = contract_address_buffer[i];
input_data[35 * 32 + i] = portal_address_buffer[i];
}

auto hash = sha256::sha256(input_data);
std::array<fr, 2> const expected_hash = components::compute_kernels_calldata_hash(kernel_data);

DummyComposer composer = DummyComposer();
BaseRollupInputs inputs = base_rollup_inputs_from_kernels(kernel_data);
BaseOrMergeRollupPublicInputs outputs =
aztec3::circuits::rollup::native_base_rollup::base_rollup_circuit(composer, inputs);

// Take the two fields and stich them together to get the calldata hash.
std::array<fr, 2> calldata_hash_fr = outputs.calldata_hash;
auto high_buffer = calldata_hash_fr[0].to_buffer();
auto low_buffer = calldata_hash_fr[1].to_buffer();
std::array<fr, 2> const calldata_hash_fr = outputs.calldata_hash;

std::array<uint8_t, 32> calldata_hash;
for (uint8_t i = 0; i < 16; ++i) {
calldata_hash[i] = high_buffer[16 + i];
calldata_hash[16 + i] = low_buffer[16 + i];
}
ASSERT_EQ(expected_hash, calldata_hash_fr);

ASSERT_EQ(hash, calldata_hash);
ASSERT_FALSE(composer.failed());
run_cbind(inputs, outputs);
}
Expand Down
1 change: 1 addition & 0 deletions circuits/cpp/src/aztec3/circuits/rollup/base/init.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#pragma once

#include "aztec3/circuits/abis/append_only_tree_snapshot.hpp"
#include "aztec3/circuits/abis/previous_kernel_data.hpp"
#include "aztec3/circuits/abis/rollup/base/base_or_merge_rollup_public_inputs.hpp"
#include "aztec3/circuits/abis/rollup/base/base_rollup_inputs.hpp"
#include "aztec3/circuits/abis/rollup/constant_rollup_data.hpp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,82 +126,6 @@ NT::fr calculate_commitments_subtree(DummyComposer& composer, BaseRollupInputs c
return commitments_tree.root();
}

std::array<NT::fr, 2> calculate_calldata_hash(BaseRollupInputs const& baseRollupInputs,
std::vector<NT::fr> const& contract_leaves)
{
// Compute calldata hashes
// Consist of 2 kernels
// 8 commitments (4 per kernel) -> 8 fields
// 8 nullifiers (4 per kernel) -> 8 fields
// 8 public state transitions (4 per kernel) -> 16 fields
// 2 contract deployments (1 per kernel) -> 6 fields
auto const number_of_inputs = (KERNEL_NEW_COMMITMENTS_LENGTH + KERNEL_NEW_NULLIFIERS_LENGTH +
STATE_TRANSITIONS_LENGTH * 2 + KERNEL_NEW_CONTRACTS_LENGTH * 3) *
2;
std::array<NT::fr, number_of_inputs> calldata_hash_inputs;

for (size_t i = 0; i < 2; i++) {
auto new_commitments = baseRollupInputs.kernel_data[i].public_inputs.end.new_commitments;
auto new_nullifiers = baseRollupInputs.kernel_data[i].public_inputs.end.new_nullifiers;
auto state_transitions = baseRollupInputs.kernel_data[i].public_inputs.end.state_transitions;

size_t offset = 0;

for (size_t j = 0; j < KERNEL_NEW_COMMITMENTS_LENGTH; j++) {
calldata_hash_inputs[offset + i * KERNEL_NEW_COMMITMENTS_LENGTH + j] = new_commitments[j];
}
offset += KERNEL_NEW_COMMITMENTS_LENGTH * 2;

for (size_t j = 0; j < KERNEL_NEW_NULLIFIERS_LENGTH; j++) {
calldata_hash_inputs[offset + i * KERNEL_NEW_NULLIFIERS_LENGTH + j] = new_nullifiers[j];
}
offset += KERNEL_NEW_NULLIFIERS_LENGTH * 2;

for (size_t j = 0; j < STATE_TRANSITIONS_LENGTH; j++) {
calldata_hash_inputs[offset + i * STATE_TRANSITIONS_LENGTH * 2 + j * 2] = state_transitions[j].leaf_index;
calldata_hash_inputs[offset + i * STATE_TRANSITIONS_LENGTH * 2 + j * 2 + 1] =
state_transitions[j].new_value;
}
offset += STATE_TRANSITIONS_LENGTH * 2 * 2;

calldata_hash_inputs[offset + i] = contract_leaves[i];
offset += KERNEL_NEW_CONTRACTS_LENGTH * 2;

auto new_contracts = baseRollupInputs.kernel_data[i].public_inputs.end.new_contracts;
calldata_hash_inputs[offset + i * 2] = new_contracts[0].contract_address;
calldata_hash_inputs[offset + i * 2 + 1] = new_contracts[0].portal_contract_address;
}

constexpr auto num_bytes = calldata_hash_inputs.size() * 32;
std::array<uint8_t, num_bytes> calldata_hash_inputs_bytes;
// Convert all into a buffer, then copy into the array, then hash
for (size_t i = 0; i < calldata_hash_inputs.size(); i++) {
auto as_bytes = calldata_hash_inputs[i].to_buffer();

auto offset = i * 32;
std::copy(as_bytes.begin(), as_bytes.end(), calldata_hash_inputs_bytes.begin() + offset);
}

std::vector<uint8_t> const calldata_hash_inputs_bytes_vec(calldata_hash_inputs_bytes.begin(),
calldata_hash_inputs_bytes.end());

auto h = sha256::sha256(calldata_hash_inputs_bytes_vec);

// Split the hash into two fields, a high and a low
std::array<uint8_t, 32> buf_1;
std::array<uint8_t, 32> buf_2;
for (uint8_t i = 0; i < 16; i++) {
buf_1[i] = 0;
buf_1[16 + i] = h[i];
buf_2[i] = 0;
buf_2[16 + i] = h[i + 16];
}
auto high = fr::serialize_from_buffer(buf_1.data());
auto low = fr::serialize_from_buffer(buf_2.data());

return std::array<NT::fr, 2>{ high, low };
}

/**
* @brief Check all of the provided commitments against the historical tree roots
*
Expand Down Expand Up @@ -576,7 +500,7 @@ BaseOrMergeRollupPublicInputs base_rollup_circuit(DummyComposer& composer, BaseR
fr const end_public_data_tree_root = validate_and_process_public_state(composer, baseRollupInputs);

// Calculate the overall calldata hash
std::array<NT::fr, 2> const calldata_hash = calculate_calldata_hash(baseRollupInputs, contract_leaves);
std::array<NT::fr, 2> const calldata_hash = components::compute_kernels_calldata_hash(baseRollupInputs.kernel_data);

// Perform membership checks that the notes provided exist within the historic trees data
perform_historical_private_data_tree_membership_checks(composer, baseRollupInputs);
Expand Down
83 changes: 83 additions & 0 deletions circuits/cpp/src/aztec3/circuits/rollup/components/components.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,89 @@ void assert_equal_constants(DummyComposer& composer,
utils::CircuitErrorCode::CONSTANTS_MISMATCH);
}

/**
* @brief Computes the calldata hash for a base rollup
*
* @param kernel_data - 2 kernels
* @return std::array<fr, 2>
*/
std::array<fr, 2> compute_kernels_calldata_hash(std::array<abis::PreviousKernelData<NT>, 2> kernel_data)
{
// Compute calldata hashes
// Consist of 2 kernels
// 8 commitments (4 per kernel) -> 8 fields
// 8 nullifiers (4 per kernel) -> 8 fields
// 8 public state transitions (4 per kernel) -> 16 fields
// 2 contract deployments (1 per kernel) -> 6 fields
auto const number_of_inputs = (KERNEL_NEW_COMMITMENTS_LENGTH + KERNEL_NEW_NULLIFIERS_LENGTH +
STATE_TRANSITIONS_LENGTH * 2 + KERNEL_NEW_CONTRACTS_LENGTH * 3) *
2;
std::array<NT::fr, number_of_inputs> calldata_hash_inputs;

for (size_t i = 0; i < 2; i++) {
auto new_commitments = kernel_data[i].public_inputs.end.new_commitments;
auto new_nullifiers = kernel_data[i].public_inputs.end.new_nullifiers;
auto state_transitions = kernel_data[i].public_inputs.end.state_transitions;

size_t offset = 0;

for (size_t j = 0; j < KERNEL_NEW_COMMITMENTS_LENGTH; j++) {
calldata_hash_inputs[offset + i * KERNEL_NEW_COMMITMENTS_LENGTH + j] = new_commitments[j];
}
offset += KERNEL_NEW_COMMITMENTS_LENGTH * 2;

for (size_t j = 0; j < KERNEL_NEW_NULLIFIERS_LENGTH; j++) {
calldata_hash_inputs[offset + i * KERNEL_NEW_NULLIFIERS_LENGTH + j] = new_nullifiers[j];
}
offset += KERNEL_NEW_NULLIFIERS_LENGTH * 2;

for (size_t j = 0; j < STATE_TRANSITIONS_LENGTH; j++) {
calldata_hash_inputs[offset + i * STATE_TRANSITIONS_LENGTH * 2 + j * 2] = state_transitions[j].leaf_index;
calldata_hash_inputs[offset + i * STATE_TRANSITIONS_LENGTH * 2 + j * 2 + 1] =
state_transitions[j].new_value;
}
offset += STATE_TRANSITIONS_LENGTH * 2 * 2;

auto const contract_leaf = kernel_data[i].public_inputs.end.new_contracts[0];
calldata_hash_inputs[offset + i] = contract_leaf.is_empty() ? NT::fr::zero() : contract_leaf.hash();

offset += KERNEL_NEW_CONTRACTS_LENGTH * 2;

auto new_contracts = kernel_data[i].public_inputs.end.new_contracts;
calldata_hash_inputs[offset + i * 2] = new_contracts[0].contract_address;
calldata_hash_inputs[offset + i * 2 + 1] = new_contracts[0].portal_contract_address;
}

constexpr auto num_bytes = calldata_hash_inputs.size() * 32;
std::array<uint8_t, num_bytes> calldata_hash_inputs_bytes;
// Convert all into a buffer, then copy into the array, then hash
for (size_t i = 0; i < calldata_hash_inputs.size(); i++) {
auto as_bytes = calldata_hash_inputs[i].to_buffer();

auto offset = i * 32;
std::copy(as_bytes.begin(), as_bytes.end(), calldata_hash_inputs_bytes.begin() + offset);
}

std::vector<uint8_t> const calldata_hash_inputs_bytes_vec(calldata_hash_inputs_bytes.begin(),
calldata_hash_inputs_bytes.end());

auto h = sha256::sha256(calldata_hash_inputs_bytes_vec);

// Split the hash into two fields, a high and a low
std::array<uint8_t, 32> buf_1;
std::array<uint8_t, 32> buf_2;
for (uint8_t i = 0; i < 16; i++) {
buf_1[i] = 0;
buf_1[16 + i] = h[i];
buf_2[i] = 0;
buf_2[16 + i] = h[i + 16];
}
auto high = fr::serialize_from_buffer(buf_1.data());
auto low = fr::serialize_from_buffer(buf_2.data());

return std::array<NT::fr, 2>{ high, low };
}

// Generates a 512 bit input from right and left 256 bit hashes. Then computes the sha256, and splits the hash into two
// field elements, a high and a low that is returned.
std::array<fr, 2> compute_calldata_hash(std::array<abis::PreviousRollupData<NT>, 2> previous_rollup_data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ using aztec3::circuits::root_from_sibling_path;

namespace aztec3::circuits::rollup::components {
NT::fr calculate_empty_tree_root(size_t depth);
std::array<fr, 2> compute_kernels_calldata_hash(std::array<abis::PreviousKernelData<NT>, 2> kernel_data);
std::array<fr, 2> compute_calldata_hash(std::array<abis::PreviousRollupData<NT>, 2> previous_rollup_data);
void assert_prev_rollups_follow_on_from_each_other(DummyComposer& composer,
BaseOrMergeRollupPublicInputs const& left,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#pragma once

#include "aztec3/circuits/abis/append_only_tree_snapshot.hpp"
#include "aztec3/circuits/abis/previous_kernel_data.hpp"
#include "aztec3/circuits/abis/rollup/base/base_or_merge_rollup_public_inputs.hpp"
#include "aztec3/circuits/abis/rollup/constant_rollup_data.hpp"
#include "aztec3/circuits/abis/rollup/merge/merge_rollup_inputs.hpp"
Expand Down

0 comments on commit fd2604f

Please sign in to comment.