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: libraryfying historic access #3658

Merged
merged 7 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions yarn-project/aztec-nr/aztec/src/history.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod note_inclusion;
mod note_validity;
mod nullifier_inclusion;
mod nullifier_non_inclusion;
mod public_value_inclusion;
48 changes: 48 additions & 0 deletions yarn-project/aztec-nr/aztec/src/history/note_inclusion.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use dep::protocol_types::constants::NOTE_HASH_TREE_HEIGHT;
use dep::std::merkle::compute_merkle_root;

use crate::{
context::PrivateContext,
note::{
utils::compute_unique_siloed_note_hash,
note_header::NoteHeader,
note_interface::NoteInterface,
},
oracle::get_membership_witness::{
get_membership_witness,
MembershipWitness,
},
};

pub fn prove_note_commitment_inclusion(
note_commitment: Field,
block_number: u32, // The block at which we'll prove that the note exists
context: PrivateContext
) {
// 1) Get block header from oracle and ensure that the block is included in the archive.
let block_header = context.get_block_header(block_number);

// 2) Get the membership witness of the note in the note hash tree
let note_hash_tree_id = 2; // TODO(#3443)
let witness: MembershipWitness<NOTE_HASH_TREE_HEIGHT, NOTE_HASH_TREE_HEIGHT + 1> =
get_membership_witness(block_number, note_hash_tree_id, note_commitment);

// 3) Prove that the commitment is in the note hash tree
assert(
block_header.note_hash_tree_root == compute_merkle_root(note_commitment, witness.index, witness.path),
"Proving note inclusion failed"
);

// --> Now we have traversed the trees all the way up to archive root.
}

pub fn prove_note_inclusion<Note, N>(
note_interface: NoteInterface<Note, N>,
note_with_header: Note,
block_number: u32, // The block at which we'll prove that the note exists
context: PrivateContext
) {
let note_commitment = compute_unique_siloed_note_hash(note_interface, note_with_header);

prove_note_commitment_inclusion(note_commitment, block_number, context);
}
19 changes: 19 additions & 0 deletions yarn-project/aztec-nr/aztec/src/history/note_validity.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::{
context::PrivateContext,
history::{
note_inclusion::prove_note_inclusion,
nullifier_non_inclusion::prove_note_not_nullified,
},
note::note_interface::NoteInterface,
};

// A helper function that proves that a note is valid at the given block number
pub fn prove_note_validity<Note, N>(
note_interface: NoteInterface<Note, N>,
note_with_header: Note,
block_number: u32, // The block at which we'll prove that the note exists
context: PrivateContext
) {
prove_note_inclusion(note_interface, note_with_header, block_number, context);
prove_note_not_nullified(note_interface, note_with_header, block_number, context);
}
33 changes: 33 additions & 0 deletions yarn-project/aztec-nr/aztec/src/history/nullifier_inclusion.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use dep::std::merkle::compute_merkle_root;

use crate::{
context::PrivateContext,
oracle::get_nullifier_membership_witness::get_nullifier_membership_witness,
};

pub fn prove_nullifier_inclusion(
nullifier: Field,
block_number: u32, // The block at which we'll prove that the note exists
context: PrivateContext
) {
// 1) Get block header from oracle and ensure that the block hash is included in the archive.
let block_header = context.get_block_header(block_number);

// 2) Get the membership witness of the nullifier
let witness = get_nullifier_membership_witness(block_number, nullifier);

// 3) Check that the witness we obtained matches the nullifier
assert(witness.leaf_data.value == nullifier, "Nullifier does not match value in witness");

// 4) Compute the nullifier tree leaf
let nullifier_leaf = witness.leaf_data.hash();

// 5) Prove that the nullifier is in the nullifier tree
assert(
block_header.nullifier_tree_root == compute_merkle_root(nullifier_leaf, witness.index, witness.path),
"Proving nullifier inclusion failed"
);

// --> Now we have traversed the trees all the way up to archive root and verified that the nullifier
// was not yet included in the nullifier tree.
}
63 changes: 63 additions & 0 deletions yarn-project/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use dep::std::merkle::compute_merkle_root;

use crate::{
context::PrivateContext,
note::{
utils::compute_siloed_nullifier,
note_header::NoteHeader,
note_interface::NoteInterface,
},
oracle::get_nullifier_membership_witness::get_low_nullifier_membership_witness,
utils::{
full_field_less_than,
full_field_greater_than,
},
};

pub fn prove_nullifier_non_inclusion(
nullifier: Field,
block_number: u32, // The block at which we'll prove that the nullifier does not exists
context: PrivateContext
) {
// 1) Get block header from oracle and ensure that the block is included in the archive.
let block_header = context.get_block_header(block_number);

// 2) Get the membership witness of a low nullifier of the nullifier
let witness = get_low_nullifier_membership_witness(block_number, nullifier);

// 3) Prove that the nullifier is not included in the nullifier tree

// 3.a) Compute the low nullifier leaf and prove that it is in the nullifier tree
let low_nullifier_leaf = witness.leaf_data.hash();
assert(
block_header.nullifier_tree_root == compute_merkle_root(low_nullifier_leaf, witness.index, witness.path),
"Proving nullifier non-inclusion failed: Could not prove low nullifier inclusion"
);

// 3.b) Prove that the low nullifier is smaller than the nullifier
assert(
full_field_less_than(witness.leaf_data.value, nullifier),
"Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed"
);

// 3.c) Prove that the low nullifier is pointing "over" the nullifier to prove that the nullifier is not
// included in the nullifier tree (or to 0 if the to-be-inserted nullifier is the largest of all)
assert(
full_field_greater_than(witness.leaf_data.next_value, nullifier) | (witness.leaf_data.next_index == 0),
"Proving nullifier non-inclusion failed: low_nullifier.next_value > nullifier.value check failed"
);

// --> Now we have traversed the trees all the way up to archive root and verified that the nullifier
// was not yet included in the nullifier tree.
}

pub fn prove_note_not_nullified<Note, N>(
note_interface: NoteInterface<Note, N>,
note_with_header: Note,
block_number: u32, // The block at which we'll prove that the note was not nullified
context: PrivateContext
) {
let nullifier = compute_siloed_nullifier(note_interface, note_with_header);

prove_nullifier_non_inclusion(nullifier, block_number, context);
}
44 changes: 44 additions & 0 deletions yarn-project/aztec-nr/aztec/src/history/public_value_inclusion.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use dep::protocol_types::constants::{
PUBLIC_DATA_TREE_HEIGHT,
GENERATOR_INDEX__PUBLIC_LEAF_INDEX,
};
use dep::std::merkle::compute_merkle_root;

use crate::{
context::PrivateContext,
hash::pedersen_hash,
oracle::get_sibling_path::get_sibling_path,
};

pub fn prove_public_value_inclusion(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should probably have an issue for fixing it when the underlying tree structure is changed from sparse trees in a few days.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will break when the tree is changed and it will be caught by the test so I feel like it's part of the tree structure change issue. Won't you agree?

value: Field, // The value that we want to prove is in the public data tree
storage_slot: Field, // The storage slot in which the value is stored
block_number: u32, // The block at which we'll prove that the note exists
context: PrivateContext
) {
// 1) Get block header from oracle and ensure that the block hash is included in the archive.
let block_header = context.get_block_header(block_number);

// 2) Compute the public value leaf index.
// We have to compute the leaf index here because unlike in the case of note commitments, public values are
// not siloed with contract address so an oracle could cheat and give us a membership witness for arbitrary
// value in the public data tree.
let value_leaf_index = pedersen_hash(
[context.this_address(), storage_slot],
GENERATOR_INDEX__PUBLIC_LEAF_INDEX
);

// 3) Get the sibling path of the value leaf index in the public data tree at block `block_number`.
let public_data_tree_id = 3; // TODO(#3443)
let path: [Field; PUBLIC_DATA_TREE_HEIGHT] =
get_sibling_path(block_number, public_data_tree_id, value_leaf_index);

// 4) Prove that the value provided on input is in the public data tree at the given storage slot.
assert(
block_header.public_data_tree_root == compute_merkle_root(value, value_leaf_index, path),
"Proving public value inclusion failed"
);

// --> Now we have traversed the trees all the way up to archive root and that way verified that a specific
// `value` was really set in a given contract storage slot at block `block_number` in public data tree.
}
1 change: 1 addition & 0 deletions yarn-project/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod abi;
mod address;
mod context;
mod hash;
mod history;
mod log;
mod messaging;
mod note;
Expand Down
12 changes: 12 additions & 0 deletions yarn-project/aztec-nr/aztec/src/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ pub fn field_from_bytes<N>(bytes: [u8; N], big_endian: bool) -> Field {

as_field
}

// TODO(#3470): Copied over from https://github.com/AztecProtocol/aztec-packages/blob/a07c4bd47313be6aa604a63f37857eb0136b41ba/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr#L599
// move to a shared place?

// TODO to radix returns u8, so we cannot use bigger radixes. It'd be ideal to use a radix of the maximum range-constrained integer noir supports
pub fn full_field_less_than(lhs: Field, rhs: Field) -> bool {
dep::std::eddsa::lt_bytes32(lhs, rhs)
}

pub fn full_field_greater_than(lhs: Field, rhs: Field) -> bool {
dep::std::eddsa::lt_bytes32(rhs, lhs)
}
47 changes: 36 additions & 11 deletions yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('e2e_inclusion_proofs_contract', () => {
{
// Prove note inclusion in a given block.
const ignoredCommitment = 0; // Not ignored only when the note doesn't exist
await contract.methods.proveNoteInclusion(owner, noteCreationBlockNumber, ignoredCommitment).send().wait();
await contract.methods.test_note_inclusion_proof(owner, noteCreationBlockNumber, ignoredCommitment).send().wait();
}

{
Expand All @@ -63,12 +63,12 @@ describe('e2e_inclusion_proofs_contract', () => {
// possible because of issue https://github.com/AztecProtocol/aztec-packages/issues/3535
const blockNumber = await pxe.getBlockNumber();
const ignoredNullifier = 0; // Not ignored only when the note doesn't exist
await contract.methods.proveNullifierNonInclusion(owner, blockNumber, ignoredNullifier).send().wait();
await contract.methods.test_nullifier_non_inclusion_proof(owner, blockNumber, ignoredNullifier).send().wait();
}

{
// We test the failure case now --> The proof should fail when the nullifier already exists
const receipt = await contract.methods.nullifyNote(owner).send().wait({ debug: true });
const receipt = await contract.methods.nullify_note(owner).send().wait({ debug: true });
const { newNullifiers } = receipt.debugInfo!;
expect(newNullifiers.length).toBe(2);

Expand All @@ -78,29 +78,54 @@ describe('e2e_inclusion_proofs_contract', () => {
// the low nullifier when the nullifier already exists in the tree and for this reason the execution fails
// on low_nullifier.value < nullifier.value check.
await expect(
contract.methods.proveNullifierNonInclusion(owner, blockNumber, nullifier).send().wait(),
contract.methods.test_nullifier_non_inclusion_proof(owner, blockNumber, nullifier).send().wait(),
).rejects.toThrowError(
/Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed/,
);
}
});

it('proves note validity (note commitment inclusion and nullifier non-inclusion)', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty nice 👍

// Owner of a note
const owner = accounts[0].address;
let noteCreationBlockNumber: number;
{
// Create a note
const value = 100n;
const receipt = await contract.methods.create_note(owner, value).send().wait({ debug: true });

noteCreationBlockNumber = receipt.blockNumber!;
const { newCommitments, visibleNotes } = receipt.debugInfo!;

expect(newCommitments.length).toBe(1);
expect(visibleNotes.length).toBe(1);
const [receivedValue, receivedOwner, _randomness] = visibleNotes[0].note.items;
expect(receivedValue.toBigInt()).toBe(value);
expect(receivedOwner).toEqual(owner.toField());
}

{
// Prove note validity
await contract.methods.test_note_validity_proof(owner, noteCreationBlockNumber).send().wait();
}
});

it('note existence failure case', async () => {
// Owner of a note
const owner = AztecAddress.random();

const blockNumber = await pxe.getBlockNumber();
const randomNoteCommitment = Fr.random();
await expect(
contract.methods.proveNoteInclusion(owner, blockNumber, randomNoteCommitment).send().wait(),
contract.methods.test_note_inclusion_proof(owner, blockNumber, randomNoteCommitment).send().wait(),
).rejects.toThrow(/Leaf value: 0x[0-9a-fA-F]+ not found in NOTE_HASH_TREE/);
});

it('proves an existence of a public value in private context', async () => {
// Chose random block number between deployment and current block number to test archival node
const blockNumber = await getRandomBlockNumberSinceDeployment();

await contract.methods.provePublicValueInclusion(publicValue, blockNumber).send().wait();
await contract.methods.test_public_value_inclusion_proof(publicValue, blockNumber).send().wait();
});

it('public value existence failure case', async () => {
Expand All @@ -109,7 +134,7 @@ describe('e2e_inclusion_proofs_contract', () => {

const randomPublicValue = Fr.random();
await expect(
contract.methods.provePublicValueInclusion(randomPublicValue, blockNumber).send().wait(),
contract.methods.test_public_value_inclusion_proof(randomPublicValue, blockNumber).send().wait(),
).rejects.toThrow(/Proving public value inclusion failed/);
});

Expand All @@ -120,7 +145,7 @@ describe('e2e_inclusion_proofs_contract', () => {
const block = await pxe.getBlock(blockNumber);
const nullifier = block?.newNullifiers[0];

await contract.methods.proveNullifierInclusion(nullifier!, blockNumber).send().wait();
await contract.methods.test_nullifier_inclusion_proof(nullifier!, blockNumber).send().wait();
});

it('nullifier existence failure case', async () => {
Expand All @@ -129,9 +154,9 @@ describe('e2e_inclusion_proofs_contract', () => {
const blockNumber = await pxe.getBlockNumber();
const randomNullifier = Fr.random();

await expect(contract.methods.proveNullifierInclusion(randomNullifier, blockNumber).send().wait()).rejects.toThrow(
/Low nullifier witness not found for nullifier 0x[0-9a-fA-F]+ at block/,
);
await expect(
contract.methods.test_nullifier_inclusion_proof(randomNullifier, blockNumber).send().wait(),
).rejects.toThrow(/Low nullifier witness not found for nullifier 0x[0-9a-fA-F]+ at block/);
});

const getRandomBlockNumberSinceDeployment = async () => {
Expand Down
Loading