Skip to content

Commit

Permalink
feat: block header block number oracle (AztecProtocol#3648)
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan authored Dec 12, 2023
1 parent 7c85750 commit ac1edc1
Show file tree
Hide file tree
Showing 15 changed files with 134 additions and 66 deletions.
11 changes: 11 additions & 0 deletions yarn-project/acir-simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ export class Oracle {
return blockHeader.toArray().map(toACVMField);
}

// TODO(#3564) - Nuke this oracle and inject the number directly to context
async getNullifierRootBlockNumber([nullifierTreeRoot]: ACVMField[]): Promise<ACVMField> {
const parsedRoot = fromACVMField(nullifierTreeRoot);

const blockNumber = await this.typedOracle.getNullifierRootBlockNumber(parsedRoot);
if (!blockNumber) {
throw new Error(`Block header not found for block ${parsedRoot}.`);
}
return toACVMField(blockNumber);
}

async getAuthWitness([messageHash]: ACVMField[]): Promise<ACVMField[]> {
const messageHashField = fromACVMField(messageHash);
const witness = await this.typedOracle.getAuthWitness(messageHashField);
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ export abstract class TypedOracle {
throw new Error('Not available.');
}

// TODO(#3564) - Nuke this oracle and inject the number directly to context
getNullifierRootBlockNumber(_nullifierTreeRoot: Fr): Promise<number | undefined> {
throw new Error('Not available.');
}

getCompleteAddress(_address: AztecAddress): Promise<CompleteAddress> {
throw new Error('Not available.');
}
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/acir-simulator/src/client/db_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,10 @@ export interface DBOracle extends CommitmentsDB {
* @returns - The block corresponding to the given block number. Undefined if it does not exist.
*/
getBlock(blockNumber: number): Promise<L2Block | undefined>;

/**
* Fetches the current block number.
* @returns The block number.
*/
getBlockNumber(): Promise<number>;
}
33 changes: 32 additions & 1 deletion yarn-project/acir-simulator/src/client/view_data_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { computeGlobalsHash, siloNullifier } from '@aztec/circuits.js/abis';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
import { AuthWitness, AztecNode, CompleteAddress, MerkleTreeId, NullifierMembershipWitness } from '@aztec/types';
import {
AuthWitness,
AztecNode,
CompleteAddress,
INITIAL_L2_BLOCK_NUM,
MerkleTreeId,
NullifierMembershipWitness,
} from '@aztec/types';

import { NoteData, TypedOracle } from '../acvm/index.js';
import { DBOracle } from './db_oracle.js';
Expand Down Expand Up @@ -113,6 +120,30 @@ export class ViewDataOracle extends TypedOracle {
);
}

/**
* Gets number of a block in which a given nullifier tree root was included.
* @param nullifierTreeRoot - The nullifier tree root to get the block number for.
* @returns The block number.
*
* TODO(#3564) - Nuke this oracle and inject the number directly to context
*/
public async getNullifierRootBlockNumber(nullifierTreeRoot: Fr): Promise<number | undefined> {
const currentBlockNumber = await this.db.getBlockNumber();
for (let i = currentBlockNumber; i >= INITIAL_L2_BLOCK_NUM; i -= 2) {
const block = await this.db.getBlock(i);
if (!block) {
throw new Error(`Block ${i} not found`);
}
if (block.endNullifierTreeSnapshot.root.equals(nullifierTreeRoot)) {
return i;
}
if (block.startNullifierTreeSnapshot.root.equals(nullifierTreeRoot)) {
return i - 1;
}
}
throw new Error(`Failed to find block containing nullifier tree root ${nullifierTreeRoot}`);
}

/**
* Retrieve the complete address associated to a given address.
* @param address - Address to fetch the complete address for.
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec-nr/aztec/src/context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl PrivateContext {
self.inputs.call_context.function_selector
}

pub fn get_block_header(self, block_number: Field) -> BlockHeader {
pub fn get_block_header(self, block_number: u32) -> BlockHeader {
get_block_header(block_number, self)
}

Expand Down
42 changes: 26 additions & 16 deletions yarn-project/aztec-nr/aztec/src/oracle/get_block_header.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,44 @@ use crate::{
},
};

// TODO(#3564) - Nuke this oracle and Inject the number directly to context
#[oracle(getNullifierRootBlockNumber)]
fn get_nullifier_root_block_number_oracle(_nullifier_tree_root: Field) -> Field {}

unconstrained pub fn get_nullifier_root_block_number(nullifier_tree_root: Field) -> u32 {
get_nullifier_root_block_number_oracle(nullifier_tree_root) as u32
}

#[oracle(getBlockHeader)]
fn get_block_header_oracle(_block_number: Field) -> [Field; BLOCK_HEADER_LENGTH] {}
fn get_block_header_oracle(_block_number: u32) -> [Field; BLOCK_HEADER_LENGTH] {}

unconstrained pub fn get_block_header_internal(block_number: Field) -> BlockHeader {
unconstrained pub fn get_block_header_internal(block_number: u32) -> BlockHeader {
let block_header = get_block_header_oracle(block_number);
BlockHeader::deserialize(block_header)
}

pub fn get_block_header(block_number: Field, context: PrivateContext) -> BlockHeader {
// 1) Get block header of a given block from oracle
pub fn get_block_header(block_number: u32, context: PrivateContext) -> BlockHeader {
// 1) Get block number corresponding to block header inside context
// Using nullifier tree root to get the block header block number because that changes in every block (every tx emits a nullifier).
let block_header_block_number = get_nullifier_root_block_number(context.block_header.nullifier_tree_root);

// 2) Check that the block header block number is more than or equal to the block number we want to prove against
// We could not perform the proof otherwise because the archive root from the header would not "contain" the block we want to prove against
assert(block_header_block_number >= block_number, "Block header block number is smaller than the block number we want to prove against");

// 3) Get block header of a given block from oracle
let block_header = get_block_header_internal(block_number);

// 2) Compute the block hash from the block header
// 4) Compute the block hash from the block header
let block_hash = block_header.block_hash();

// 3) Get the membership witness of the block in the archive
// 5) Get the membership witness of the block in the archive
let archive_id = 5; // TODO(#3443)

// Using `block_number` here for path is incorrect and it will break if we pass in an incorrect block number on input.
// Instead here should be the block number corresponding to `context.block_header.blocks_tree_root`
// This is not currently available in private context. See issue #3564
let path_block_number = block_number;

let witness: MembershipWitness<ARCHIVE_HEIGHT, ARCHIVE_HEIGHT + 1> = get_membership_witness(path_block_number, archive_id, block_hash);

// 4) Check that the block is in the archive (i.e. the witness is valid)
let witness: MembershipWitness<ARCHIVE_HEIGHT, ARCHIVE_HEIGHT + 1> = get_membership_witness(block_header_block_number, archive_id, block_hash);

// 6) Check that the block is in the archive (i.e. the witness is valid)
assert(context.block_header.archive_root == compute_merkle_root(block_hash, witness.index, witness.path), "Proving membership of a block in archive failed");

// 5) Return the block header
// 7) Return the block header
block_header
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ struct MembershipWitness<N, M> {
}

#[oracle(getMembershipWitness)]
fn get_membership_witness_oracle<M>(_block_number: Field, _tree_id: Field, _leaf_value: Field) -> [Field; M] {}
fn get_membership_witness_oracle<M>(_block_number: u32, _tree_id: Field, _leaf_value: Field) -> [Field; M] {}

unconstrained pub fn get_membership_witness<N, M>(block_number: Field, tree_id: Field, leaf_value: Field) -> MembershipWitness<N, M> {
unconstrained pub fn get_membership_witness<N, M>(block_number: u32, tree_id: Field, leaf_value: Field) -> MembershipWitness<N, M> {
let fields: [Field; M] = get_membership_witness_oracle(block_number, tree_id, leaf_value);
MembershipWitness { index: fields[0], path: arr_copy_slice(fields, [0; N], 1) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ struct NullifierMembershipWitness {
}

#[oracle(getLowNullifierMembershipWitness)]
fn get_low_nullifier_membership_witness_oracle(_block_number: Field, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {}
fn get_low_nullifier_membership_witness_oracle(_block_number: u32, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {}

// Nullifier here refers to the nullifier we are looking to get non-inclusion proof for (by proving that a lower
// nullifier's next_value is bigger than the nullifier)
unconstrained pub fn get_low_nullifier_membership_witness(block_number: Field, nullifier: Field) -> NullifierMembershipWitness {
unconstrained pub fn get_low_nullifier_membership_witness(block_number: u32, nullifier: Field) -> NullifierMembershipWitness {
let fields = get_low_nullifier_membership_witness_oracle(block_number, nullifier);
NullifierMembershipWitness {
index: fields[0],
Expand All @@ -46,11 +46,11 @@ unconstrained pub fn get_low_nullifier_membership_witness(block_number: Field, n
}

#[oracle(getNullifierMembershipWitness)]
fn get_nullifier_membership_witness_oracle(_block_number: Field, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {}
fn get_nullifier_membership_witness_oracle(_block_number: u32, _nullifier: Field) -> [Field; NULLIFIER_MEMBERSHIP_WITNESS] {}

// Nullifier here refers to the nullifier we are looking to get non-inclusion proof for (by proving that a lower
// nullifier's next_value is bigger than the nullifier)
unconstrained pub fn get_nullifier_membership_witness(block_number: Field, nullifier: Field) -> NullifierMembershipWitness {
unconstrained pub fn get_nullifier_membership_witness(block_number: u32, nullifier: Field) -> NullifierMembershipWitness {
let fields = get_nullifier_membership_witness_oracle(block_number, nullifier);
NullifierMembershipWitness {
index: fields[0],
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use dep::protocol_types::constants::NOTE_HASH_TREE_HEIGHT;
use crate::utils::arr_copy_slice;

#[oracle(getSiblingPath)]
fn get_sibling_path_oracle<N>(_block_number: Field, _tree_id: Field, _leaf_index: Field) -> [Field; N] {}
fn get_sibling_path_oracle<N>(_block_number: u32, _tree_id: Field, _leaf_index: Field) -> [Field; N] {}

unconstrained pub fn get_sibling_path<N>(block_number: Field, tree_id: Field, leaf_index: Field) -> [Field; N] {
unconstrained pub fn get_sibling_path<N>(block_number: u32, tree_id: Field, leaf_index: Field) -> [Field; N] {
let value: [Field; N] = get_sibling_path_oracle(block_number, tree_id, leaf_index);
value
}
35 changes: 26 additions & 9 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 @@ -19,24 +19,31 @@ describe('e2e_inclusion_proofs_contract', () => {
let accounts: CompleteAddress[];

let contract: InclusionProofsContract;
let deploymentBlockNumber: number;
const publicValue = 236n;

beforeAll(async () => {
({ pxe, teardown, wallets, accounts } = await setup(1));

contract = await InclusionProofsContract.deploy(wallets[0], publicValue).send().deployed();
const receipt = await InclusionProofsContract.deploy(wallets[0], publicValue).send().wait();
contract = receipt.contract;
deploymentBlockNumber = receipt.blockNumber!;
}, 100_000);

afterAll(() => teardown());

it('proves note existence and its nullifier non-existence and nullifier non-existence failure case', 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;
Expand All @@ -46,17 +53,14 @@ describe('e2e_inclusion_proofs_contract', () => {

{
// Prove note inclusion in a given block.
// TODO: Use here note block number from the creation note tx to test archival node. This is currently not
// possible because of issue #3564
const blockNumber = await pxe.getBlockNumber();
const ignoredCommitment = 0; // Not ignored only when the note doesn't exist
await contract.methods.proveNoteInclusion(owner, blockNumber, ignoredCommitment).send().wait();
await contract.methods.proveNoteInclusion(owner, noteCreationBlockNumber, ignoredCommitment).send().wait();
}

{
// Prove that the note has not been nullified
// TODO: Use here note block number from the creation note tx to test archival node. This is currently not
// possible because of issue #3564
// TODO(#3535): Prove the nullifier non-inclusion at older block to test archival node. This is currently not
// 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();
Expand Down Expand Up @@ -93,19 +97,25 @@ describe('e2e_inclusion_proofs_contract', () => {
});

it('proves an existence of a public value in private context', async () => {
const blockNumber = await pxe.getBlockNumber();
// 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();
});

it('public value existence failure case', async () => {
const blockNumber = await pxe.getBlockNumber();
// Chose random block number between deployment and current block number to test archival node
const blockNumber = await getRandomBlockNumberSinceDeployment();

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

it('proves existence of a nullifier in private context', async () => {
// TODO(#3535): Test this at "random" block to test archival node. This is currently not possible because of
// issue https://github.com/AztecProtocol/aztec-packages/issues/3535
const blockNumber = await pxe.getBlockNumber();
const block = await pxe.getBlock(blockNumber);
const nullifier = block?.newNullifiers[0];
Expand All @@ -114,11 +124,18 @@ describe('e2e_inclusion_proofs_contract', () => {
});

it('nullifier existence failure case', async () => {
// TODO(#3535): Test this at "random" block to test archival node. This is currently not possible because of
// issue https://github.com/AztecProtocol/aztec-packages/issues/3535
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/,
);
});

const getRandomBlockNumberSinceDeployment = async () => {
const currentBlockNumber = await pxe.getBlockNumber();
return deploymentBlockNumber + Math.floor(Math.random() * (currentBlockNumber - deploymentBlockNumber));
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,9 @@ contract InclusionProofs {
#[aztec(private)]
fn proveNoteInclusion(
owner: AztecAddress,
block_number: Field, // The block at which we'll prove that the note exists
block_number: u32, // The block at which we'll prove that the note exists
spare_commitment: Field, // This is only used when the note is not found --> used to test the failure case
) {
// TODO: assert that block number is less than the block number of context.block_header
// --> This will either require a new oracle method that returns block_header.global_variables_hash preimage
// or modifying the private context so that we somehow expose it.
// Blocked by #3564

// 1) Get block header from oracle and ensure that the block hash is included in the current blocks tree
// root.
let block_header = context.get_block_header(block_number);
Expand Down Expand Up @@ -142,14 +137,9 @@ contract InclusionProofs {
#[aztec(private)]
fn proveNullifierNonInclusion(
owner: AztecAddress,
block_number: Field, // The block at which we'll prove that the nullifier does not exists
block_number: u32, // The block at which we'll prove that the nullifier does not exists
spare_nullifier: Field, // This is only used when the note is not found --> used to test the failure case
) {
// TODO: assert that block number is less than the block number of context.block_header
// --> This will either require a new oracle method that returns block_header.global_variables_hash preimage
// or modifying the private context so that we somehow expose it.
// Blocked by #3564

// 1) Get block header from oracle and ensure that the block hash is included in the current blocks tree
// root.
let block_header = context.get_block_header(block_number);
Expand Down Expand Up @@ -215,13 +205,8 @@ contract InclusionProofs {
#[aztec(private)]
fn proveNullifierInclusion(
nullifier: Field,
block_number: Field, // The block at which we'll prove that the nullifier not exists in the tree
block_number: u32, // The block at which we'll prove that the nullifier not exists in the tree
) {
// TODO: assert that block number is less than the block number of context.block_header
// --> This will either require a new oracle method that returns block_header.global_variables_hash preimage
// or modifying the private context so that we somehow expose it.
// Blocked by #3564

// 1) Get block header from oracle and ensure that the block hash is included in the current blocks tree
// root.
let block_header = context.get_block_header(block_number);
Expand All @@ -248,13 +233,8 @@ contract InclusionProofs {
#[aztec(private)]
fn provePublicValueInclusion(
public_value: Field,
block_number: Field, // The block at which we'll prove that the public value exists
block_number: u32, // The block at which we'll prove that the public value exists
) {
// TODO: assert that block number is less than the block number of context.block_header
// --> This will either require a new oracle method that returns block_header.global_variables_hash preimage
// or modifying the private context so that we somehow expose it.
// Blocked by #3564

// 1) Get block header from oracle and ensure that the block hash is included in the current blocks tree
// root.
let block_header = context.get_block_header(block_number);
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/pxe/src/simulator_oracle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,12 @@ export class SimulatorOracle implements DBOracle {
getBlockHeader(): Promise<BlockHeader> {
return Promise.resolve(this.db.getBlockHeader());
}

/**
* Fetches the current block number.
* @returns The block number.
*/
public async getBlockNumber(): Promise<number> {
return await this.stateInfoProvider.getBlockNumber();
}
}
6 changes: 0 additions & 6 deletions yarn-project/types/src/interfaces/aztec-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@ export interface AztecNode extends StateInfoProvider {
*/
getBlocks(from: number, limit: number): Promise<L2Block[]>;

/**
* Fetches the current block number.
* @returns The block number.
*/
getBlockNumber(): Promise<number>;

/**
* Method to fetch the version of the rollup the node is connected to.
* @returns The rollup version.
Expand Down
Loading

0 comments on commit ac1edc1

Please sign in to comment.