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: Add root rollup circuit #3217

Merged
merged 11 commits into from
Nov 3, 2023
Merged
29 changes: 29 additions & 0 deletions circuits/cpp/src/aztec3/circuits/rollup/root/.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,33 @@ TEST_F(root_rollup_tests, native_root_missing_nullifier_logic)
// run_cbind(rootRollupInputs, outputs, true);
}

TEST_F(root_rollup_tests, noir_interop_test)
{
// This is an annoying hack to convert the field into a hex string
// We should add a to_hex and from_hex method to field class
auto to_hex = [](const NT::fr& value) -> std::string {
std::stringstream field_as_hex_stream;
field_as_hex_stream << value;
return field_as_hex_stream.str();
};

MemoryStore merkle_tree_store;
MerkleTree merkle_tree(merkle_tree_store, L1_TO_L2_MSG_SUBTREE_HEIGHT);

std::array<fr, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP> leaves = { 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 };
for (size_t i = 0; i < NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP; i++) {
merkle_tree.update_element(i, leaves[i]);
}
auto root = merkle_tree.root();
auto expected = "0x17e8bb70a11d0c946345950879484d2f4f9fef397ff6adbfdec3baab2d41faab";
ASSERT_EQ(to_hex(root), expected);

// Empty subtree is the same as zeroes
MemoryStore empty_tree_store;
MerkleTree const empty_tree = MerkleTree(empty_tree_store, L1_TO_L2_MSG_SUBTREE_HEIGHT);
auto empty_root = empty_tree.root();
auto expected_empty_root = "0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d";
ASSERT_EQ(to_hex(empty_root), expected_empty_root);
}

} // namespace aztec3::circuits::rollup::root::native_root_rollup_circuit
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::abis::base_or_merge_rollup_public_inputs::BaseOrMergeRollupPublicInputs;
use dep::types::mocked::AggregationObject;
use dep::types::hash::accumulate_sha256;
use dep::types::hash::{accumulate_sha256, assert_check_membership, root_from_sibling_path};
use dep::types::utils::uint128::U128;
use dep::aztec::constants_gen::NUM_FIELDS_PER_SHA256;
use crate::abis::previous_rollup_data::PreviousRollupData;
use crate::abis::append_only_tree_snapshot::AppendOnlyTreeSnapshot;

/**
* Create an aggregation object for the proofs that are provided
Expand All @@ -30,7 +31,7 @@ pub fn assert_both_input_proofs_of_same_rollup_type(left : BaseOrMergeRollupPubl
* Asserts that the rollup subtree heights are the same and returns the height
* Returns the height of the rollup subtrees
*/
pub fn assert_both_input_proofs_of_same_height_and_return(left : BaseOrMergeRollupPublicInputs, right : BaseOrMergeRollupPublicInputs) -> Field{
pub fn assert_both_input_proofs_of_same_height_and_return(left : BaseOrMergeRollupPublicInputs, right : BaseOrMergeRollupPublicInputs) -> Field {
assert(left.rollup_subtree_height == right.rollup_subtree_height, "input proofs are of different rollup heights");
left.rollup_subtree_height
}
Expand Down Expand Up @@ -67,3 +68,26 @@ pub fn compute_calldata_hash(previous_rollup_data : [PreviousRollupData ; 2]) ->
U128::from_field(previous_rollup_data[1].base_or_merge_rollup_public_inputs.calldata_hash[1])
])
}

pub fn insert_subtree_to_snapshot_tree<N>(
snapshot : AppendOnlyTreeSnapshot,
siblingPath : [Field; N],
emptySubtreeRoot : Field,
subtreeRootToInsert : Field,
subtreeDepth : u8,
) -> AppendOnlyTreeSnapshot {
// TODO(Lasse): Sanity check len of siblingPath > height of subtree
// TODO(Lasse): Ensure height of subtree is correct (eg 3 for commitments, 1 for contracts)
let leafIndexAtDepth = snapshot.next_available_leaf_index >> (subtreeDepth as u32);

// Check that the current root is correct and that there is an empty subtree at the insertion location
assert_check_membership(emptySubtreeRoot, leafIndexAtDepth as Field, siblingPath, snapshot.root);

// 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.
let new_root = root_from_sibling_path(subtreeRootToInsert, leafIndexAtDepth as Field, siblingPath);

// 2^subtreeDepth is the number of leaves added. 2^x = 1 << x
let new_next_available_leaf_index = (snapshot.next_available_leaf_index as u64) + (1 << (subtreeDepth as u64));

AppendOnlyTreeSnapshot{root: new_root, next_available_leaf_index: new_next_available_leaf_index as u32}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::abis::global_variables::GlobalVariables;
use dep::aztec::constants_gen;

pub fn compute_block_hash_with_globals(
globals : GlobalVariables,
note_hash_tree_root : Field,
nullifier_tree_root : Field,
contract_tree_root : Field,
l1_to_l2_data_tree_root : Field,
public_data_tree_root : Field) -> Field {

let inputs = [globals.hash(), note_hash_tree_root, nullifier_tree_root, contract_tree_root, l1_to_l2_data_tree_root, public_data_tree_root];

dep::std::hash::pedersen_hash_with_separator(inputs, constants_gen::GENERATOR_INDEX__BLOCK_HASH)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ mod merge;
mod root;

mod components;

mod hash;

mod merkle_tree;
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// This is the naive way to do the merkle tree hashing given we know the height.
// Its janky but has the least complexity given that for loop bounds need to be constant.
//
// TODO: Check that in all cases the compiler is able to see that num_of_leaves
// is a constant, else this will be pretty expensive.
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
pub fn calculate_subtree<N>(leaves : [Field; N]) -> Field {
let num_of_leaves = leaves.len();
if num_of_leaves == 128 {
calculate_subtree_128(leaves.as_slice())
} else if num_of_leaves == 64 {
calculate_subtree_64(leaves.as_slice())
} else if num_of_leaves == 32 {
calculate_subtree_32(leaves.as_slice())
} else if num_of_leaves == 16 {
calculate_subtree_16(leaves.as_slice())
} else if num_of_leaves == 8 {
calculate_subtree_8(leaves.as_slice())
} else if num_of_leaves == 4 {
calculate_subtree_4(leaves.as_slice())
} else if num_of_leaves == 2 {
calculate_subtree_2(leaves.as_slice())
} else {
assert(false, "number of leaves should be 2, 4, 8, or 16");
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
0
}
}

fn calculate_subtree_128(leaves : [Field]) -> Field {
assert(leaves.len() == 128, "number of leaves should be 128");

let mut layer = [0; 64];
for i in 0..layer.len() {
let combined = [leaves[2 * i], leaves[2 * i + 1]];
layer[i] = dep::std::hash::pedersen_hash(combined);
}

calculate_subtree_64(layer.as_slice())
}
fn calculate_subtree_64(leaves : [Field]) -> Field {
assert(leaves.len() == 64, "number of leaves should be 64");

let mut layer = [0; 32];
for i in 0..layer.len() {
let combined = [leaves[2 * i], leaves[2 * i + 1]];
layer[i] = dep::std::hash::pedersen_hash(combined);
}

calculate_subtree_32(layer.as_slice())
}
fn calculate_subtree_32(leaves : [Field]) -> Field {
assert(leaves.len() == 32, "number of leaves should be 32");

let mut layer = [0; 16];
for i in 0..layer.len() {
let combined = [leaves[2 * i], leaves[2 * i + 1]];
layer[i] = dep::std::hash::pedersen_hash(combined);
}

calculate_subtree_16(layer.as_slice())
}
fn calculate_subtree_16(leaves : [Field]) -> Field {
assert(leaves.len() == 16, "number of leaves should be 16");

let mut layer = [0; 8];
for i in 0..layer.len() {
let combined = [leaves[2 * i], leaves[2 * i + 1]];
layer[i] = dep::std::hash::pedersen_hash(combined);
}

calculate_subtree_8(layer.as_slice())
}
fn calculate_subtree_8(leaves : [Field]) -> Field {
assert(leaves.len() == 8, "number of leaves should be 8");

let mut layer = [0; 4];
for i in 0..layer.len() {
let combined = [leaves[2 * i], leaves[2 * i + 1]];
layer[i] = dep::std::hash::pedersen_hash(combined);
}

calculate_subtree_4(layer)
}
fn calculate_subtree_4(leaves : [Field]) -> Field {
assert(leaves.len() == 4, "number of leaves should be 4");

let mut layer = [0; 2];
for i in 0..layer.len() {
let combined = [leaves[2 * i], leaves[2 * i + 1]];
layer[i] = dep::std::hash::pedersen_hash(combined);
}

calculate_subtree_2(layer.as_slice())
}
fn calculate_subtree_2(leaves : [Field]) -> Field {
dep::std::hash::pedersen_hash(leaves)
}

// These values are precomputed and we run tests to ensure that they
// are correct. The values themselves were computed from the cpp code.
//
// Would be good if we could use width since the compute_subtree
// algorithm uses depth.
fn calculate_empty_tree_root(depth : Field) -> Field {
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
if depth == 1 {
0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed
} else if depth == 2 {
0x21dbfd1d029bf447152fcf89e355c334610d1632436ba170f738107266a71550
} else if depth == 3{
0x0bcd1f91cf7bdd471d0a30c58c4706f3fdab3807a954b8f5b5e3bfec87d001bb
} else if depth == 4 {
0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d
} else if depth == 5 {
0x03c9e2e67178ac638746f068907e6677b4cc7a9592ef234ab6ab518f17efffa0
} else if depth == 6 {
0x15d28cad4c0736decea8997cb324cf0a0e0602f4d74472cd977bce2c8dd9923f
} else if depth == 7 {
0x268ed1e1c94c3a45a14db4108bc306613a1c23fab68e0466a002dfb0a3f8d2ab
} else if depth == 8 {
0x0cd8d5695bc2dde99dd531671f76f1482f14ddba8eeca7cb9686d4a62359c257
} else if depth == 9 {
0x047fbb7eb974155702149e58ea6ad91f4c6e953e693db35e953e250d8ceac9a9
} else if depth == 10 {
0x00c5ae2526e665e2c7c698c11a06098b7159f720606d50e7660deb55758b0b02
} else {
assert(false, "depth should be between 1 and 10");
0
}
}

#[test]
fn test_merkle_root_interop_test() {
// This is a test to ensure that we match the cpp implementation.
// You can grep for `TEST_F(root_rollup_tests, noir_interop_test)`
// to find the test that matches this.
let root = calculate_subtree([1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4]);
assert(0x17e8bb70a11d0c946345950879484d2f4f9fef397ff6adbfdec3baab2d41faab == root);

let empty_root = calculate_subtree([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
assert(0x06e62084ee7b602fe9abc15632dda3269f56fb0c6e12519a2eb2ec897091919d == empty_root);
}

#[test]
fn test_empty_subroot() {
let expected_empty_root_2 = calculate_subtree([0,0]);
assert(calculate_empty_tree_root(1) == expected_empty_root_2);

let expected_empty_root_4 = calculate_subtree([0,0,0,0]);
assert(calculate_empty_tree_root(2) == expected_empty_root_4);

let expected_empty_root_8 = calculate_subtree([0,0,0,0,0,0,0,0]);
assert(calculate_empty_tree_root(3) == expected_empty_root_8);

let expected_empty_root_16 = calculate_subtree([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
assert(calculate_empty_tree_root(4) == expected_empty_root_16);

let expected_empty_root_32 = calculate_subtree([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
assert(calculate_empty_tree_root(5) == expected_empty_root_32);

let expected_empty_root_64 = calculate_subtree([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
assert(calculate_empty_tree_root(6) == expected_empty_root_64);

let expected_empty_root_128 = calculate_subtree([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
assert(calculate_empty_tree_root(7) == expected_empty_root_128);
}
Loading