Skip to content

Commit

Permalink
feat: check existence of vk in contract tree (#167)
Browse files Browse the repository at this point in the history
* feat: partial logic to check existence of vk in contract tree (missing merkle tree membership calls)

* fix private call data

* perform sibling-path traversal/hashing to get from VKhash/function-leaf all the way to function root and then to contract root

---------

Co-authored-by: dbanks12 <[email protected]>
  • Loading branch information
2 people authored and ludamad committed Apr 17, 2023
1 parent 1368918 commit 71e515c
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,6 @@ template <typename NCT> std::ostream& operator<<(std::ostream& os, NewContractDa
<< "function_tree_root: " << new_contract_data.function_tree_root << "\n";
}

template <typename NCT> using ContractLeafPreimage = NewContractData<NCT>;

} // namespace aztec3::circuits::abis::private_kernel
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ template <typename NCT> struct PrivateCallData {
MembershipWitness<NCT, CONTRACT_TREE_HEIGHT> contract_leaf_membership_witness;

fr portal_contract_address; // an ETH address
fr acir_hash;

boolean operator==(PrivateCallData<NCT> const& other) const
{
Expand All @@ -45,7 +46,7 @@ template <typename NCT> struct PrivateCallData {
private_call_stack_preimages == other.private_call_stack_preimages && vk == other.vk &&
function_leaf_membership_witness == other.function_leaf_membership_witness &&
contract_leaf_membership_witness == other.contract_leaf_membership_witness &&
portal_contract_address == other.portal_contract_address;
portal_contract_address == other.portal_contract_address && acir_hash == other.acir_hash;
};

// WARNING: the `proof` does NOT get converted! (because the current implementation of `verify_proof` takes a proof
Expand All @@ -72,6 +73,7 @@ template <typename NCT> struct PrivateCallData {
to_circuit_type(contract_leaf_membership_witness),

to_ct(portal_contract_address),
to_ct(acir_hash),
};

return data;
Expand All @@ -89,6 +91,7 @@ template <typename NCT> void read(uint8_t const*& it, PrivateCallData<NCT>& obj)
read(it, obj.function_leaf_membership_witness);
read(it, obj.contract_leaf_membership_witness);
read(it, obj.portal_contract_address);
read(it, obj.acir_hash);
};

template <typename NCT> void write(std::vector<uint8_t>& buf, PrivateCallData<NCT> const& obj)
Expand All @@ -102,6 +105,7 @@ template <typename NCT> void write(std::vector<uint8_t>& buf, PrivateCallData<NC
write(buf, obj.function_leaf_membership_witness);
write(buf, obj.contract_leaf_membership_witness);
write(buf, obj.portal_contract_address);
write(buf, obj.acir_hash);
};

template <typename NCT> std::ostream& operator<<(std::ostream& os, PrivateCallData<NCT> const& obj)
Expand All @@ -118,7 +122,8 @@ template <typename NCT> std::ostream& operator<<(std::ostream& os, PrivateCallDa
<< obj.function_leaf_membership_witness << "\n"
<< "contract_leaf_membership_witness:\n"
<< obj.contract_leaf_membership_witness << "\n"
<< "portal_contract_address: " << obj.portal_contract_address << "\n";
<< "portal_contract_address: " << obj.portal_contract_address << "\n"
<< "acir_hash: " << obj.acir_hash << "\n";
}

} // namespace aztec3::circuits::abis::private_kernel
29 changes: 29 additions & 0 deletions circuits/cpp/src/aztec3/circuits/hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,33 @@ typename NCT::fr add_contract_address_to_nullifier(typename NCT::address contrac
return NCT::compress(inputs, aztec3::GeneratorIndex::OUTER_NULLIFIER);
}

/**
* @brief Calculate the Merkle tree root from the sibling path and leaf.
*
* @details The leaf is hashed with its sibling, and then the result is hashed
* with the next sibling etc in the path. The last hash is the root.
*
* @tparam NCT Operate on NativeTypes or CircuitTypes
* @tparam N The number of elements in the sibling path
* @param leaf The leaf element of the Merkle tree
* @param leafIndex The index of the leaf element in the Merkle tree
* @param siblingPath The nodes representing the merkle siblings of the leaf, its parent,
* the next parent, etc up to the sibling below the root
* @return The computed Merkle tree root.
*/
template <typename NCT, size_t N>
typename NCT::fr root_from_sibling_path(typename NCT::fr leaf,
typename NCT::uint32 leafIndex,
std::array<typename NCT::fr, N> siblingPath)
{
for (size_t i = 0; i < siblingPath.size(); i++) {
if (leafIndex & (1 << i)) {
leaf = NCT::merkle_hash(siblingPath[i], leaf);
} else {
leaf = NCT::merkle_hash(leaf, siblingPath[i]);
}
}
return leaf; // root
}

} // namespace aztec3::circuits
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
#include "aztec3/constants.hpp"
#include "init.hpp"

#include "aztec3/circuits/abis/function_leaf_preimage.hpp"
#include <aztec3/circuits/abis/private_kernel/private_inputs.hpp>
#include <aztec3/circuits/abis/private_kernel/public_inputs.hpp>
#include <aztec3/circuits/abis/private_kernel/new_contract_data.hpp>

#include <aztec3/utils/array.hpp>
#include <aztec3/circuits/hash.hpp>
#include "aztec3/constants.hpp"

#include <barretenberg/stdlib/merkle_tree/membership.hpp>

namespace aztec3::circuits::kernel::private_kernel {

using aztec3::circuits::abis::FunctionLeafPreimage;
using aztec3::circuits::abis::private_kernel::ContractLeafPreimage;
using aztec3::circuits::abis::private_kernel::NewContractData;
using aztec3::circuits::abis::private_kernel::PrivateInputs;
using aztec3::circuits::abis::private_kernel::PublicInputs;
Expand All @@ -22,6 +27,9 @@ using aztec3::utils::push_array_to_array;

using aztec3::circuits::compute_constructor_hash;
using aztec3::circuits::compute_contract_address;
using aztec3::circuits::root_from_sibling_path;

// using plonk::stdlib::merkle_tree::

// // TODO: NEED TO RECONCILE THE `proof`'s public inputs (which are uint8's) with the
// // private_call.call_stack_item.public_inputs!
Expand Down Expand Up @@ -62,73 +70,124 @@ void initialise_end_values(PrivateInputs<NT> const& private_inputs, PublicInputs
end.optionally_revealed_data = start.optionally_revealed_data;
}

void update_end_values(PrivateInputs<NT> const& private_inputs, PublicInputs<NT>& public_inputs)
void contract_logic(PrivateInputs<NT> const& private_inputs, PublicInputs<NT>& public_inputs)
{
const auto private_call_public_inputs = private_inputs.private_call.call_stack_item.public_inputs;

const auto& new_commitments = private_call_public_inputs.new_commitments;
const auto& new_nullifiers = private_call_public_inputs.new_nullifiers;

const auto& is_static_call = private_call_public_inputs.call_context.is_static_call;

if (is_static_call) {
// No state changes are allowed for static calls:
ASSERT(is_array_empty(new_commitments) == true);
ASSERT(is_array_empty(new_nullifiers) == true);
}

const auto& storage_contract_address = private_call_public_inputs.call_context.storage_contract_address;
const auto& portal_contract_address = private_inputs.private_call.portal_contract_address;
const auto& deployer_address = private_call_public_inputs.call_context.msg_sender;
const auto& contract_deployment_data =
private_inputs.signed_tx_request.tx_request.tx_context.contract_deployment_data;

{ // contract deployment
// input storage contract address must be 0 if its a constructor call and non-zero otherwise
auto is_contract_deployment = public_inputs.constants.tx_context.is_contract_deployment_tx;
// contract deployment

auto private_call_vk_hash = stdlib::recursion::verification_key<CT::bn254>::compress_native(
private_inputs.private_call.vk, GeneratorIndex::VK);
// input storage contract address must be 0 if its a constructor call and non-zero otherwise
auto is_contract_deployment = public_inputs.constants.tx_context.is_contract_deployment_tx;

auto constructor_hash = compute_constructor_hash(private_inputs.signed_tx_request.tx_request.function_data,
private_call_public_inputs.args,
private_call_vk_hash);
auto private_call_vk_hash = stdlib::recursion::verification_key<CT::bn254>::compress_native(
private_inputs.private_call.vk, GeneratorIndex::VK);

if (is_contract_deployment) {
ASSERT(contract_deployment_data.constructor_vk_hash == private_call_vk_hash);
}
auto constructor_hash = compute_constructor_hash(private_inputs.signed_tx_request.tx_request.function_data,
private_call_public_inputs.args,
private_call_vk_hash);

auto contract_address = compute_contract_address<NT>(deployer_address,
contract_deployment_data.contract_address_salt,
contract_deployment_data.function_tree_root,
constructor_hash);

if (is_contract_deployment) {
// must imply == derived address
ASSERT(storage_contract_address == contract_address);
} else {
// non-contract deployments must specify contract address being interacted with
ASSERT(storage_contract_address != 0);
}
if (is_contract_deployment) {
ASSERT(contract_deployment_data.constructor_vk_hash == private_call_vk_hash);
}

// compute contract address nullifier
auto blake_input = contract_address.to_field().to_buffer();
auto contract_address_nullifier = NT::fr::serialize_from_buffer(NT::blake3s(blake_input).data());
auto const new_contract_address = compute_contract_address<NT>(deployer_address,
contract_deployment_data.contract_address_salt,
contract_deployment_data.function_tree_root,
constructor_hash);

// push the contract address nullifier to nullifier vector
if (is_contract_deployment) {
array_push(public_inputs.end.new_nullifiers, contract_address_nullifier);
}
if (is_contract_deployment) {
// must imply == derived address
ASSERT(storage_contract_address == new_contract_address);
} else {
// non-contract deployments must specify contract address being interacted with
ASSERT(storage_contract_address != 0);
}

// compute contract address nullifier
auto const blake_input = new_contract_address.to_field().to_buffer();
auto const new_contract_address_nullifier = NT::fr::serialize_from_buffer(NT::blake3s(blake_input).data());

// push the contract address nullifier to nullifier vector
if (is_contract_deployment) {
array_push(public_inputs.end.new_nullifiers, new_contract_address_nullifier);
}

// Add new contract data if its a contract deployment function
NewContractData<NT> native_new_contract_data{ new_contract_address,
portal_contract_address,
contract_deployment_data.function_tree_root };

// Add new contract data if its a contract deployment function
auto native_new_contract_data = NewContractData<NT>{ contract_address,
portal_contract_address,
contract_deployment_data.function_tree_root };
array_push<NewContractData<NT>, KERNEL_NEW_CONTRACTS_LENGTH>(public_inputs.end.new_contracts,
native_new_contract_data);

array_push<NewContractData<NT>, KERNEL_NEW_CONTRACTS_LENGTH>(public_inputs.end.new_contracts,
native_new_contract_data);
/* We need to compute the root of the contract tree, starting from the function's VK:
* - Compute the vk_hash (done above)
* - Compute the function_leaf: hash(function_selector, is_private, vk_hash, acir_hash)
* - Hash the function_leaf with the function_leaf's sibling_path to get the function_tree_root
* - Compute the contract_leaf: hash(contract_address, portal_contract_address, function_tree_root)
* - Hash the contract_leaf with the contract_leaf's sibling_path to get the contract_tree_root
*/

const auto function_leaf_preimage = FunctionLeafPreimage<NT>{
.function_selector = private_inputs.private_call.call_stack_item.function_data.function_selector,
.is_private = true,
.vk_hash = private_call_vk_hash,
.acir_hash = private_inputs.private_call.acir_hash,
};

const auto function_leaf = function_leaf_preimage.hash();

const auto& function_leaf_index = private_inputs.private_call.function_leaf_membership_witness.leaf_index;
const auto& function_leaf_sibling_path = private_inputs.private_call.function_leaf_membership_witness.sibling_path;

const auto& function_tree_root =
root_from_sibling_path<NT>(function_leaf, function_leaf_index, function_leaf_sibling_path);

const ContractLeafPreimage<NT> contract_leaf_preimage{
storage_contract_address,
portal_contract_address,
function_tree_root,
};

const auto contract_leaf = contract_leaf_preimage.hash();

auto& contract_leaf_index = private_inputs.private_call.contract_leaf_membership_witness.leaf_index;
auto& contract_leaf_sibling_path = private_inputs.private_call.contract_leaf_membership_witness.sibling_path;

const auto& computed_contract_tree_root =
root_from_sibling_path<NT>(contract_leaf, contract_leaf_index, contract_leaf_sibling_path);

auto& purported_contract_tree_root =
private_inputs.private_call.call_stack_item.public_inputs.historic_contract_tree_root;
ASSERT(computed_contract_tree_root == purported_contract_tree_root);

auto& previous_kernel_contract_tree_root =
private_inputs.previous_kernel.public_inputs.constants.old_tree_roots.contract_tree_root;
ASSERT(purported_contract_tree_root == previous_kernel_contract_tree_root);
}

void update_end_values(PrivateInputs<NT> const& private_inputs, PublicInputs<NT>& public_inputs)
{
const auto private_call_public_inputs = private_inputs.private_call.call_stack_item.public_inputs;

const auto& new_commitments = private_call_public_inputs.new_commitments;
const auto& new_nullifiers = private_call_public_inputs.new_nullifiers;

const auto& is_static_call = private_call_public_inputs.call_context.is_static_call;

if (is_static_call) {
// No state changes are allowed for static calls:
ASSERT(is_array_empty(new_commitments) == true);
ASSERT(is_array_empty(new_nullifiers) == true);
}

const auto& storage_contract_address = private_call_public_inputs.call_context.storage_contract_address;

{
// Nonce nullifier
// DANGER: This is terrible. This should not be part of the protocol. This is an intentional bodge to reach a
Expand Down Expand Up @@ -183,11 +242,11 @@ void validate_this_private_call_hash(PrivateInputs<NT> const& private_inputs)
const auto& start = private_inputs.previous_kernel.public_inputs.end;
// TODO: this logic might need to change to accommodate the weird edge 3 initial txs (the 'main' tx, the 'fee' tx,
// and the 'gas rebate' tx).
const auto this_private_call_hash = array_pop(start.private_call_stack);
const auto popped_private_call_hash = array_pop(start.private_call_stack);
const auto calculated_this_private_call_hash = private_inputs.private_call.call_stack_item.hash();

ASSERT(this_private_call_hash ==
calculated_this_private_call_hash); // "this private_call_hash does not reconcile");
ASSERT(popped_private_call_hash ==
calculated_this_private_call_hash); // "this private_call_hash does not reconcile";
};

void validate_this_private_call_stack(PrivateInputs<NT> const& private_inputs)
Expand Down
17 changes: 17 additions & 0 deletions circuits/cpp/src/aztec3/utils/types/circuit_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <barretenberg/stdlib/recursion/verifier/verifier.hpp>
#include <barretenberg/stdlib/recursion/verification_key/verification_key.hpp>
#include <barretenberg/stdlib/commitment/pedersen/pedersen.hpp>
#include <barretenberg/stdlib/hash/pedersen/pedersen.hpp>
#include <barretenberg/stdlib/hash/blake2s/blake2s.hpp>
#include "native_types.hpp"

Expand Down Expand Up @@ -83,6 +84,22 @@ template <typename Composer> struct CircuitTypes {
return plonk::stdlib::pedersen_commitment<Composer>::compress(input_pairs);
};

/**
* @brief Compute the hash for a pair of left and right nodes in a merkle tree.
*
* @details Compress the two nodes using the default/0-generator which is reserved
* for internal merkle hashing.
*
* @param left The left child node
* @param right The right child node
* @return The computed Merkle tree hash for the given pair of nodes
*/
static fr merkle_hash(fr left, fr right)
{
// use 0-generator for internal merkle hashing
return plonk::stdlib::pedersen_hash<Composer>::hash_multiple({ left, right }, 0);
};

static grumpkin_point commit(const std::vector<fr>& inputs, const size_t hash_index = 0)
{
return plonk::stdlib::pedersen_commitment<Composer>::commit(inputs, hash_index);
Expand Down
16 changes: 16 additions & 0 deletions circuits/cpp/src/aztec3/utils/types/native_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ struct NativeTypes {
return crypto::pedersen_commitment::compress_native(input_pairs);
}

/**
* @brief Compute the hash for a pair of left and right nodes in a merkle tree.
*
* @details Compress the two nodes using the default/0-generator which is reserved
* for internal merkle hashing.
*
* @param left The left child node
* @param right The right child node
* @return The computed Merkle tree hash for the given pair of nodes
*/
static fr merkle_hash(fr left, fr right)
{
// use 0-generator for internal merkle hashing
return crypto::pedersen_hash::hash_multiple({ left, right }, 0);
}

static grumpkin_point commit(const std::vector<fr>& inputs, const size_t hash_index = 0)
{
return crypto::pedersen_commitment::commit_native(inputs, hash_index);
Expand Down

0 comments on commit 71e515c

Please sign in to comment.