Skip to content

Commit

Permalink
feat: enforce that 0th nullifier is non-zero in private kernel circuit (
Browse files Browse the repository at this point in the history
#2576)

We enforce the constraint in inner and ordering native private kernel
circuit that the 0th nullifier is non-zero. This nullifier is actually
set in the init private kernel circuit and therefore the check is not
needed here.

Resolves #1329
  • Loading branch information
jeanmon authored Sep 28, 2023
1 parent a9e5d05 commit 458a4fe
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 9 deletions.
7 changes: 7 additions & 0 deletions circuits/cpp/src/aztec3/circuits/kernel/private/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ void common_validate_read_requests(DummyBuilder& builder,
}
}

void common_validate_0th_nullifier(DummyBuilder& builder, CombinedAccumulatedData<NT> const& end)
{
builder.do_assert(end.new_nullifiers[0] != 0,
"The 0th nullifier in the accumulated nullifier array is zero",
CircuitErrorCode::PRIVATE_KERNEL__0TH_NULLLIFIER_IS_ZERO);
}

void common_update_end_values(DummyBuilder& builder,
PrivateCallData<NT> const& private_call,
KernelCircuitPublicInputs<NT>& public_inputs)
Expand Down
4 changes: 4 additions & 0 deletions circuits/cpp/src/aztec3/circuits/kernel/private/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "init.hpp"

#include "aztec3/circuits/abis/combined_accumulated_data.hpp"
#include "aztec3/circuits/abis/contract_deployment_data.hpp"
#include "aztec3/circuits/abis/function_data.hpp"
#include "aztec3/circuits/abis/kernel_circuit_public_inputs.hpp"
Expand All @@ -13,6 +14,7 @@

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

using aztec3::circuits::abis::CombinedAccumulatedData;
using aztec3::circuits::abis::ContractDeploymentData;
using aztec3::circuits::abis::FunctionData;
using aztec3::circuits::abis::KernelCircuitPublicInputs;
Expand All @@ -32,6 +34,8 @@ void common_validate_read_requests(DummyBuilder& builder,
std::array<ReadRequestMembershipWitness<NT, PRIVATE_DATA_TREE_HEIGHT>,
MAX_READ_REQUESTS_PER_CALL> const& read_request_membership_witnesses);

void common_validate_0th_nullifier(DummyBuilder& builder, CombinedAccumulatedData<NT> const& end);

void common_update_end_values(DummyBuilder& builder,
PrivateCallData<NT> const& private_call,
KernelCircuitPublicInputs<NT>& public_inputs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ TEST_F(native_private_kernel_tests, native_transient_read_requests_no_match)
// Testing that the special value EMPTY_NULLIFIED_COMMITMENT keeps new_nullifiers aligned with nullified_commitments.
TEST_F(native_private_kernel_tests, native_empty_nullified_commitment_respected)
{
// The 0th nullifier (and corresponding nullified_commitment EMPTY_NULLIFIED_COMMITMENT) which is
// added by the init iteration is set into private_inputs_inner.
auto private_inputs_inner = do_private_call_get_kernel_inputs_inner(false, deposit, standard_test_args());

private_inputs_inner.private_call.call_stack_item.public_inputs.new_commitments[0] = fr(23);
Expand All @@ -189,30 +191,31 @@ TEST_F(native_private_kernel_tests, native_empty_nullified_commitment_respected)
<< " with code: " << builder.get_first_failure().code;

// EMPTY nullified commitment should keep new_nullifiers aligned with nullified_commitments
ASSERT_TRUE(public_inputs.end.nullified_commitments[0] == fr(EMPTY_NULLIFIED_COMMITMENT));
ASSERT_TRUE(public_inputs.end.nullified_commitments[1] != fr(0) &&
public_inputs.end.nullified_commitments[1] != fr(EMPTY_NULLIFIED_COMMITMENT));
// We have to shift the offset by 1 due to the 0th nullifier/nullified_commitment pair.
ASSERT_TRUE(public_inputs.end.nullified_commitments[1] == fr(EMPTY_NULLIFIED_COMMITMENT));
ASSERT_TRUE(public_inputs.end.nullified_commitments[2] != fr(0) &&
public_inputs.end.nullified_commitments[2] != fr(EMPTY_NULLIFIED_COMMITMENT));

// Nothing squashed yet (until ordering circuit)
ASSERT_TRUE(array_length(public_inputs.end.new_nullifiers) == 2);
ASSERT_TRUE(array_length(public_inputs.end.new_nullifiers) == 3); // 0th nullifier to be taking into account
ASSERT_TRUE(array_length(public_inputs.end.new_commitments) == 2);
// explicitly EMPTY commitment respected in array
ASSERT_TRUE(array_length(public_inputs.end.nullified_commitments) == 2);
ASSERT_TRUE(array_length(public_inputs.end.nullified_commitments) == 3);

auto& previous_kernel = private_inputs_inner.previous_kernel;
previous_kernel.public_inputs = public_inputs;

PrivateKernelInputsOrdering<NT> private_inputs{ .previous_kernel = previous_kernel,
.nullifier_commitment_hints =
std::array<fr, MAX_NEW_NULLIFIERS_PER_TX>{ 0, 1 } };
std::array<fr, MAX_NEW_NULLIFIERS_PER_TX>{ 0, 0, 1 } };

auto final_public_inputs = native_private_kernel_circuit_ordering(builder, private_inputs);

ASSERT_FALSE(builder.failed()) << "failure: " << builder.get_first_failure()
<< " with code: " << builder.get_first_failure().code;

ASSERT_TRUE(array_length(final_public_inputs.end.new_commitments) == 1); // 1/2 commitment squashed
ASSERT_TRUE(array_length(final_public_inputs.end.new_nullifiers) == 1); // 1/2 nullifier squashed
ASSERT_TRUE(array_length(final_public_inputs.end.new_nullifiers) == 2); // 1/2 nullifier squashed (+ 0th nullifier)
}

} // namespace aztec3::circuits::kernel::private_kernel
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ void validate_inputs(DummyCircuitBuilder& builder, PrivateKernelInputsInner<NT>
"Cannot execute private kernel circuit with an empty private call stack",
CircuitErrorCode::PRIVATE_KERNEL__PRIVATE_CALL_STACK_EMPTY);

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1329): validate that 0th nullifier is nonzero
common_validate_0th_nullifier(builder, private_inputs.previous_kernel.public_inputs.end);
}

// NOTE: THIS IS A VERY UNFINISHED WORK IN PROGRESS.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -745,4 +745,20 @@ TEST_F(native_private_kernel_inner_tests, native_logs_are_hashed_as_expected)
ASSERT_EQ(public_inputs.end.unencrypted_logs_hash, expected_unencrypted_logs_hash);
}

TEST_F(native_private_kernel_inner_tests, 0th_nullifier_zero_fails)
{
auto private_inputs = do_private_call_get_kernel_inputs_inner(false, deposit, standard_test_args());

// Change the 0th nullifier to be zero.
private_inputs.previous_kernel.public_inputs.end.new_nullifiers[0] = 0;

// Invoke the native private kernel circuit
DummyBuilder builder = DummyBuilder("private_kernel_tests__0th_nullifier_zero_fails");
native_private_kernel_circuit_inner(builder, private_inputs);

// Assertion checks
EXPECT_TRUE(builder.failed());
EXPECT_EQ(builder.get_first_failure().code, CircuitErrorCode::PRIVATE_KERNEL__0TH_NULLLIFIER_IS_ZERO);
}

} // namespace aztec3::circuits::kernel::private_kernel
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ KernelCircuitPublicInputsFinal<NT> native_private_kernel_circuit_ordering(
// Do this before any functions can modify the inputs.
initialise_end_values(private_inputs.previous_kernel, public_inputs);

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1329): validate that 0th nullifier is nonzero
common_validate_0th_nullifier(builder, private_inputs.previous_kernel.public_inputs.end);

// TODO(https://github.com/AztecProtocol/aztec-packages/issues/1486): validate that `len(new_nullifiers) ==
// len(nullified_commitments)`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,4 +367,21 @@ TEST_F(native_private_kernel_ordering_tests, native_empty_nullified_commitment_m
ASSERT_TRUE(array_length(public_inputs.end.new_nullifiers) == 1);
}

TEST_F(native_private_kernel_ordering_tests, 0th_nullifier_zero_fails)
{
auto private_inputs_inner = do_private_call_get_kernel_inputs_inner(false, deposit, standard_test_args());

std::array<fr, MAX_NEW_NULLIFIERS_PER_TX> new_nullifiers{};
auto& previous_kernel = private_inputs_inner.previous_kernel;
previous_kernel.public_inputs.end.new_nullifiers = new_nullifiers;
PrivateKernelInputsOrdering<NT> private_inputs{ .previous_kernel = previous_kernel };

DummyBuilder builder = DummyBuilder("native_private_kernel_ordering_tests__0th_nullifier_zero_fails");
auto public_inputs = native_private_kernel_circuit_ordering(builder, private_inputs);

ASSERT_TRUE(builder.failed());
auto failure = builder.get_first_failure();
ASSERT_EQ(failure.code, CircuitErrorCode::PRIVATE_KERNEL__0TH_NULLLIFIER_IS_ZERO);
}

} // namespace aztec3::circuits::kernel::private_kernel
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,8 @@ PrivateKernelInputsInner<NT> do_private_call_get_kernel_inputs_inner(
public_inputs_encrypted_log_preimages_length;
mock_previous_kernel.public_inputs.end.unencrypted_log_preimages_length =
public_inputs_unencrypted_log_preimages_length;
mock_previous_kernel.public_inputs.end.new_nullifiers[0] = 321; // 0th nullifier must be non-zero.
mock_previous_kernel.public_inputs.end.nullified_commitments[0] = EMPTY_NULLIFIED_COMMITMENT;

//***************************************************************************
// Now we can construct the full private inputs to the kernel circuit
Expand Down
1 change: 1 addition & 0 deletions circuits/cpp/src/aztec3/utils/circuit_errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ enum CircuitErrorCode : uint16_t {
PRIVATE_KERNEL__UNRESOLVED_NON_TRANSIENT_READ_REQUEST = 2021,
PRIVATE_KERNEL__IS_INTERNAL_BUT_NOT_SELF_CALL = 2022,
PRIVATE_KERNEL__TRANSIENT_NEW_NULLIFIER_NO_MATCH = 2023,
PRIVATE_KERNEL__0TH_NULLLIFIER_IS_ZERO = 2024,

// Public kernel related errors
PUBLIC_KERNEL_CIRCUIT_FAILED = 3000,
Expand Down

0 comments on commit 458a4fe

Please sign in to comment.