Skip to content

Commit

Permalink
feat: reusable rollup components (#183)
Browse files Browse the repository at this point in the history
* add rollup subtree height

* just use one type of public inputs for base/merge (#168)

* fix: re-usable rollup components

* fix: hash_multiple -> hash_pair_native

---------

Co-authored-by: Rahul Kothari <[email protected]>
  • Loading branch information
LHerskind and rahul-kothari authored Apr 12, 2023
1 parent 9a0c038 commit bb87daf
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 303 deletions.
139 changes: 36 additions & 103 deletions cpp/src/aztec3/circuits/rollup/base/native_base_rollup_circuit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "barretenberg/stdlib/merkle_tree/memory_tree.hpp"
#include "barretenberg/stdlib/merkle_tree/merkle_tree.hpp"
#include "init.hpp"
#include "aztec3/circuits/rollup/components/components.hpp"

#include <algorithm>
#include <array>
Expand Down Expand Up @@ -100,54 +101,7 @@ std::vector<NT::fr> calculate_contract_leaves(BaseRollupInputs const& baseRollup
return contract_leaves;
}

template <size_t N>
NT::fr iterate_through_tree_via_sibling_path(NT::fr leaf,
NT::uint32 const& leafIndex,
std::array<NT::fr, N> const& siblingPath)
{
for (size_t i = 0; i < siblingPath.size(); i++) {
if (leafIndex & (1 << i)) {
leaf = proof_system::plonk::stdlib::merkle_tree::hash_pair_native(siblingPath[i], leaf);
} else {
leaf = proof_system::plonk::stdlib::merkle_tree::hash_pair_native(leaf, siblingPath[i]);
}
}
return leaf;
}

template <size_t N>
void check_membership(DummyComposer& composer,
NT::fr const& leaf,
NT::uint32 const& leafIndex,
std::array<NT::fr, N> const& siblingPath,
NT::fr const& root)
{
auto calculatedRoot = iterate_through_tree_via_sibling_path(leaf, leafIndex, siblingPath);
// TODO: update tests to build the correct trees
composer.do_assert(calculatedRoot == root, "Membership check failed");
}

template <size_t N>
AppendOnlySnapshot insert_subtree_to_snapshot_tree(std::array<NT::fr, N> const& siblingPath,
NT::uint32 const& nextAvailableLeafIndex,
NT::fr const& subtreeRootToInsert,
uint8_t const& subtreeDepth)
{
// TODO: Sanity check len of siblingPath > height of subtree
// TODO: Ensure height of subtree is correct (eg 3 for commitments, 1 for contracts)

// if index of leaf is x, index of its parent is x/2 or x >> 1. We need to find the parent `subtreeDepth` levels up.
auto leafIndexAtDepth = nextAvailableLeafIndex >> subtreeDepth;
auto new_root = iterate_through_tree_via_sibling_path(subtreeRootToInsert, leafIndexAtDepth, siblingPath);
// 2^subtreeDepth is the number of leaves added. 2^x = 1 << x
auto new_next_available_leaf_index = nextAvailableLeafIndex + (uint8_t(1) << subtreeDepth);

AppendOnlySnapshot newTreeSnapshot = { .root = new_root,
.next_available_leaf_index = new_next_available_leaf_index };
return newTreeSnapshot;
}

NT::fr calculate_contract_subtree(std::vector<NT::fr> const& contract_leaves)
NT::fr calculate_contract_subtree(std::vector<NT::fr> contract_leaves)
{
MerkleTree contracts_tree = MerkleTree(CONTRACT_SUBTREE_DEPTH);

Expand Down Expand Up @@ -260,7 +214,7 @@ void perform_historical_private_data_tree_membership_checks(DummyComposer& compo
abis::MembershipWitness<NT, PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT> historic_root_witness =
baseRollupInputs.historic_private_data_tree_root_membership_witnesses[i];

check_membership(
components::check_membership(
composer, leaf, historic_root_witness.leaf_index, historic_root_witness.sibling_path, historic_root);
}
}
Expand All @@ -275,7 +229,7 @@ void perform_historical_contract_data_tree_membership_checks(DummyComposer& comp
abis::MembershipWitness<NT, PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT> historic_root_witness =
baseRollupInputs.historic_contract_tree_root_membership_witnesses[i];

check_membership(
components::check_membership(
composer, leaf, historic_root_witness.leaf_index, historic_root_witness.sibling_path, historic_root);
}
}
Expand Down Expand Up @@ -389,19 +343,19 @@ AppendOnlySnapshot check_nullifier_tree_non_membership_and_insert_to_tree(DummyC
};

// perform membership check for the low nullifier against the original root
check_membership<NULLIFIER_TREE_HEIGHT>(composer,
original_low_nullifier.hash(),
witness.leaf_index,
witness.sibling_path,
current_nullifier_tree_root);
components::check_membership<NULLIFIER_TREE_HEIGHT>(composer,
original_low_nullifier.hash(),
witness.leaf_index,
witness.sibling_path,
current_nullifier_tree_root);

// Calculate the new value of the low_nullifier_leaf
NullifierLeaf updated_low_nullifier = NullifierLeaf{ .value = low_nullifier_preimage.leaf_value,
.nextIndex = new_index,
.nextValue = nullifier };

// We need another set of witness values for this
current_nullifier_tree_root = iterate_through_tree_via_sibling_path(
current_nullifier_tree_root = components::iterate_through_tree_via_sibling_path(
updated_low_nullifier.hash(), witness.leaf_index, witness.sibling_path);
}

Expand All @@ -428,8 +382,8 @@ AppendOnlySnapshot check_nullifier_tree_non_membership_and_insert_to_tree(DummyC
// Calculate the new root
// We are inserting a subtree rather than a full tree here
auto subtree_index = start_insertion_index >> (NULLIFIER_SUBTREE_DEPTH);
auto new_root =
iterate_through_tree_via_sibling_path(nullifier_subtree_root, subtree_index, nullifier_sibling_path);
auto new_root = components::iterate_through_tree_via_sibling_path(
nullifier_subtree_root, subtree_index, nullifier_sibling_path);

// Return the new state of the nullifier tree
return {
Expand All @@ -453,57 +407,36 @@ BaseOrMergeRollupPublicInputs base_rollup_circuit(DummyComposer& composer, BaseR
// First we compute the contract tree leaves
std::vector<NT::fr> contract_leaves = calculate_contract_leaves(baseRollupInputs);

// Perform merkle membership check with the provided sibling path up to the root
// Note - the subtree hasn't been created (i.e. it is empty) so you check that the sibling path corresponds to
// an empty tree

// check for commitments/private_data
// next_available_leaf_index is at the leaf level. We need at the subtree level (say height 3). So divide by 8.
// (if leaf is at index x, its parent is at index floor >> depth)
auto leafIndexAtSubtreeDepth =
baseRollupInputs.start_private_data_tree_snapshot.next_available_leaf_index >> (PRIVATE_DATA_SUBTREE_DEPTH);
check_membership(composer,
EMPTY_COMMITMENTS_SUBTREE_ROOT,
leafIndexAtSubtreeDepth,
baseRollupInputs.new_commitments_subtree_sibling_path,
baseRollupInputs.start_private_data_tree_snapshot.root);

// check for contracts
auto leafIndexContractsSubtreeDepth =
baseRollupInputs.start_contract_tree_snapshot.next_available_leaf_index >> CONTRACT_SUBTREE_DEPTH;
check_membership(composer,
EMPTY_CONTRACTS_SUBTREE_ROOT,
leafIndexContractsSubtreeDepth,
baseRollupInputs.new_contracts_subtree_sibling_path,
baseRollupInputs.start_contract_tree_snapshot.root);

// check for nullifiers
auto leafIndexNullifierSubtreeDepth =
baseRollupInputs.start_nullifier_tree_snapshot.next_available_leaf_index >> NULLIFIER_SUBTREE_DEPTH;
check_membership(composer,
EMPTY_NULLIFIER_SUBTREE_ROOT,
leafIndexNullifierSubtreeDepth,
baseRollupInputs.new_nullifiers_subtree_sibling_path,
baseRollupInputs.start_nullifier_tree_snapshot.root);

// Check contracts and commitments subtrees
NT::fr contracts_tree_subroot = calculate_contract_subtree(contract_leaves);
NT::fr commitments_tree_subroot = calculate_commitments_subtree(composer, baseRollupInputs);

// Insert subtrees to the tree:
// Insert commitment subtrees:
auto end_private_data_tree_snapshot =
insert_subtree_to_snapshot_tree(baseRollupInputs.new_commitments_subtree_sibling_path,
baseRollupInputs.start_private_data_tree_snapshot.next_available_leaf_index,
commitments_tree_subroot,
PRIVATE_DATA_SUBTREE_DEPTH);

components::insert_subtree_to_snapshot_tree(composer,
baseRollupInputs.start_private_data_tree_snapshot,
baseRollupInputs.new_commitments_subtree_sibling_path,
EMPTY_COMMITMENTS_SUBTREE_ROOT,
commitments_tree_subroot,
PRIVATE_DATA_SUBTREE_DEPTH);

// Insert contract subtrees:
auto end_contract_tree_snapshot =
insert_subtree_to_snapshot_tree(baseRollupInputs.new_contracts_subtree_sibling_path,
baseRollupInputs.start_contract_tree_snapshot.next_available_leaf_index,
contracts_tree_subroot,
CONTRACT_SUBTREE_DEPTH);

// Check nullifiers and check new subtree insertion
components::insert_subtree_to_snapshot_tree(composer,
baseRollupInputs.start_contract_tree_snapshot,
baseRollupInputs.new_contracts_subtree_sibling_path,
EMPTY_CONTRACTS_SUBTREE_ROOT,
contracts_tree_subroot,
CONTRACT_SUBTREE_DEPTH);

// Update nullifier tree and insert new subtree
auto leafIndexNullifierSubtreeDepth =
baseRollupInputs.start_nullifier_tree_snapshot.next_available_leaf_index >> NULLIFIER_SUBTREE_DEPTH;
components::check_membership(composer,
EMPTY_NULLIFIER_SUBTREE_ROOT,
leafIndexNullifierSubtreeDepth,
baseRollupInputs.new_nullifiers_subtree_sibling_path,
baseRollupInputs.start_nullifier_tree_snapshot.root);
AppendOnlySnapshot end_nullifier_tree_snapshot =
check_nullifier_tree_non_membership_and_insert_to_tree(composer, baseRollupInputs);

Expand Down
128 changes: 128 additions & 0 deletions cpp/src/aztec3/circuits/rollup/components/components.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include "aztec3/circuits/abis/rollup/base/base_or_merge_rollup_public_inputs.hpp"
#include "aztec3/constants.hpp"
#include "barretenberg/crypto/pedersen_hash/pedersen.hpp"
#include "barretenberg/crypto/sha256/sha256.hpp"
#include "barretenberg/ecc/curves/bn254/fr.hpp"
#include "barretenberg/stdlib/hash/pedersen/pedersen.hpp"
#include "init.hpp"

#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <tuple>
#include <vector>

namespace aztec3::circuits::rollup::components {

/**
* @brief Create an aggregation object for the proofs that are provided
* - We add points P0 for each of our proofs
* - We add points P1 for each of our proofs
* - We concat our public inputs
*
* @param mergeRollupInputs
* @return AggregationObject
*/
AggregationObject aggregate_proofs(BaseOrMergeRollupPublicInputs const& left,
BaseOrMergeRollupPublicInputs const& right)
{
// TODO: NOTE: for now we simply return the aggregation object from the first proof
(void)right;
return left.end_aggregation_object;
}

/**
* @brief Asserts that the rollup types are the same
*
* @param left - The public inputs of the left rollup (base or merge)
* @param right - The public inputs of the right rollup (base or merge)
*/
void assert_both_input_proofs_of_same_rollup_type(DummyComposer& composer,
BaseOrMergeRollupPublicInputs const& left,
BaseOrMergeRollupPublicInputs const& right)
{
composer.do_assert(left.rollup_type == right.rollup_type, "input proofs are of different rollup types");
}

/**
* @brief Asserts that the rollup subtree heights are the same and returns the height
*
* @param left - The public inputs of the left rollup (base or merge)
* @param right - The public inputs of the right rollup (base or merge)
* @return NT::fr - The height of the rollup subtrees
*/
NT::fr assert_both_input_proofs_of_same_height_and_return(DummyComposer& composer,
BaseOrMergeRollupPublicInputs const& left,
BaseOrMergeRollupPublicInputs const& right)
{
composer.do_assert(left.rollup_subtree_height == right.rollup_subtree_height,
"input proofs are of different rollup heights");
return left.rollup_subtree_height;
}

/**
* @brief Asserts that the constants used in the left and right child are identical
*
* @param left - The public inputs of the left rollup (base or merge)
* @param right - The public inputs of the right rollup (base or merge)
*/
void assert_equal_constants(DummyComposer& composer,
BaseOrMergeRollupPublicInputs const& left,
BaseOrMergeRollupPublicInputs const& right)
{
composer.do_assert(left.constants == right.constants, "input proofs have different constants");
}

// 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)
{
// Generate a 512 bit input from right and left 256 bit hashes
std::array<uint8_t, 2 * 32> calldata_hash_input_bytes;
for (uint8_t i = 0; i < 2; i++) {
std::array<fr, 2> calldata_hash_fr = previous_rollup_data[i].base_or_merge_rollup_public_inputs.calldata_hash;

auto high_buffer = calldata_hash_fr[0].to_buffer();
auto low_buffer = calldata_hash_fr[1].to_buffer();

for (uint8_t j = 0; j < 16; ++j) {
calldata_hash_input_bytes[i * 32 + j] = high_buffer[16 + j];
calldata_hash_input_bytes[i * 32 + 16 + j] = low_buffer[16 + j];
}
}

// Compute the sha256
std::vector<uint8_t> calldata_hash_input_bytes_vec(calldata_hash_input_bytes.begin(),
calldata_hash_input_bytes.end());
auto h = sha256::sha256(calldata_hash_input_bytes_vec);

// Split the hash into two fields, a high and a low
std::array<uint8_t, 32> buf_1, 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 { high, low };
}

// asserts that the end snapshot of previous_rollup 0 equals the start snapshot of previous_rollup 1 (i.e. ensure they
// follow on from one-another). Ensures that right uses the tres that was updated by left.
void assert_prev_rollups_follow_on_from_each_other(DummyComposer& composer,
BaseOrMergeRollupPublicInputs const& left,
BaseOrMergeRollupPublicInputs const& right)
{
composer.do_assert(left.end_private_data_tree_snapshot == right.start_private_data_tree_snapshot,
"input proofs have different private data tree snapshots");
composer.do_assert(left.end_nullifier_tree_snapshot == right.start_nullifier_tree_snapshot,
"input proofs have different nullifier tree snapshots");
composer.do_assert(left.end_contract_tree_snapshot == right.start_contract_tree_snapshot,
"input proofs have different contract tree snapshots");
}

} // namespace aztec3::circuits::rollup::components
Loading

0 comments on commit bb87daf

Please sign in to comment.