Skip to content

Commit

Permalink
feat: libraryfying historic access (AztecProtocol#3658)
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan authored Dec 13, 2023
1 parent 53eb54f commit 6877ca1
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 153 deletions.
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(
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 () => {
// 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

0 comments on commit 6877ca1

Please sign in to comment.