diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 5a1b9884606e..ee0e7fb0b793 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -339,7 +339,7 @@ export class AztecNodeService implements AztecNode { * @param leafIndex - The index of the leaf for which the sibling path is required. * @returns The sibling path for the leaf index. */ - public async getNullifierTreeSiblingPath( + public async getNullifierSiblingPath( blockNumber: number | 'latest', leafIndex: bigint, ): Promise> { @@ -408,7 +408,7 @@ export class AztecNodeService implements AztecNode { * @param leafIndex - Index of the leaf in the tree. * @returns The sibling path. */ - public async getPublicDataTreeSiblingPath( + public async getPublicDataSiblingPath( blockNumber: number | 'latest', leafIndex: bigint, ): Promise> { diff --git a/yarn-project/aztec-nr/aztec/src/history.nr b/yarn-project/aztec-nr/aztec/src/history.nr index c36332d365ab..597196eba970 100644 --- a/yarn-project/aztec-nr/aztec/src/history.nr +++ b/yarn-project/aztec-nr/aztec/src/history.nr @@ -1,3 +1,4 @@ +mod contract_inclusion; mod note_inclusion; mod note_validity; mod nullifier_inclusion; diff --git a/yarn-project/aztec-nr/aztec/src/history/contract_inclusion.nr b/yarn-project/aztec-nr/aztec/src/history/contract_inclusion.nr new file mode 100644 index 000000000000..8e71cc5729f9 --- /dev/null +++ b/yarn-project/aztec-nr/aztec/src/history/contract_inclusion.nr @@ -0,0 +1,62 @@ +use dep::protocol_types::{ + abis::{ + complete_address::CompleteAddress, + new_contract_data::NewContractData as ContractLeafPreimage, + }, + address::{AztecAddress, EthAddress}, + point::Point, +}; +use dep::std::merkle::compute_merkle_root; + +use crate::{ + context::PrivateContext, + oracle::get_membership_witness::get_contract_membership_witness, +}; + +// Proves that a contract exists at block `block_number` and returns its address. +// Note: This can be used to approximate a factory pattern --> a factory contract could perform this proof and that +// way verify that a contract at a given address is what it expects. Then it could store it in an internal +// map of contracts (like what Uniswap Factory does with pool contracts - it stores them in a mapping). +// By passing in the construct hash the factory can also verify that the contract was constructed with the +// correct constructor arguments. Typically the factory would store the expected construct hash and assert that +// it is what it expects. The constructor param check is the reason of why we pass in the preimage of contract's +// aztec address instead of just the address. +pub fn prove_contract_inclusion( + deployer_public_key: Point, + contract_address_salt: Field, + function_tree_root: Field, + constructor_hash: Field, + portal_contract_address: EthAddress, + block_number: u32, // The block at which we'll prove that the public value exists + context: PrivateContext +) -> AztecAddress { + // 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) Compute the contract address + let contract_address = CompleteAddress::compute( + deployer_public_key, + contract_address_salt, + function_tree_root, + constructor_hash + ).address; + + // 3) Form the contract tree leaf preimage + let preimage = ContractLeafPreimage { contract_address, portal_contract_address, function_tree_root }; + + // 4) Get the contract tree leaf by hashing the preimage + let contract_leaf = preimage.hash(); + + // 5) Get the membership witness of the leaf in the contract tree + let witness = get_contract_membership_witness(block_number, contract_leaf); + + // 6) Prove that the leaf is in the contract tree + assert( + block_header.contract_tree_root + == compute_merkle_root(contract_leaf, witness.index, witness.path), "Proving contract inclusion failed" + ); + + // --> Now we have traversed the trees all the way up to archive root. + + contract_address +} diff --git a/yarn-project/aztec.js/src/contract_deployer/deploy_method.ts b/yarn-project/aztec.js/src/contract_deployer/deploy_method.ts index e8a9ad18f28c..a6dc144d347a 100644 --- a/yarn-project/aztec.js/src/contract_deployer/deploy_method.ts +++ b/yarn-project/aztec.js/src/contract_deployer/deploy_method.ts @@ -69,7 +69,7 @@ export class DeployMethod extends Bas const { chainId, protocolVersion } = await this.pxe.getNodeInfo(); - const { completeAddress, constructorHash, functionTreeRoot } = getContractDeploymentInfo( + const { completeAddress, constructorVkHash, functionTreeRoot } = getContractDeploymentInfo( this.artifact, this.args, contractAddressSalt, @@ -78,7 +78,7 @@ export class DeployMethod extends Bas const contractDeploymentData = new ContractDeploymentData( this.publicKey, - constructorHash, + constructorVkHash, functionTreeRoot, contractAddressSalt, portalContract, diff --git a/yarn-project/circuits.js/src/contract/contract_deployment_info.ts b/yarn-project/circuits.js/src/contract/contract_deployment_info.ts index 38a15d1cfcc2..39bf1cbb13fb 100644 --- a/yarn-project/circuits.js/src/contract/contract_deployment_info.ts +++ b/yarn-project/circuits.js/src/contract/contract_deployment_info.ts @@ -48,7 +48,8 @@ export function getContractDeploymentInfo( return { completeAddress, - constructorHash: constructorVkHash, + constructorHash, + constructorVkHash, functionTreeRoot, }; } diff --git a/yarn-project/circuits.js/src/types/deployment_info.ts b/yarn-project/circuits.js/src/types/deployment_info.ts index a8abba471262..2cc3b98ddc3a 100644 --- a/yarn-project/circuits.js/src/types/deployment_info.ts +++ b/yarn-project/circuits.js/src/types/deployment_info.ts @@ -8,6 +8,10 @@ export type DeploymentInfo = { * The complete address of the deployed contract. */ completeAddress: CompleteAddress; + /** + * The contract's constructor verification key hash. + */ + constructorVkHash: Fr; /** * The contract's constructor hash. */ diff --git a/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts b/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts index da4ab9fbb085..9d5f750dc9a4 100644 --- a/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts @@ -1,4 +1,16 @@ -import { AccountWallet, AztecAddress, CompleteAddress, Fr, INITIAL_L2_BLOCK_NUM, PXE } from '@aztec/aztec.js'; +import { + AccountWallet, + AztecAddress, + CompleteAddress, + EthAddress, + Fr, + INITIAL_L2_BLOCK_NUM, + PXE, + Point, + getContractDeploymentInfo, +} from '@aztec/aztec.js'; +import { NewContractData } from '@aztec/circuits.js'; +import { computeContractLeaf } from '@aztec/circuits.js/abis'; import { InclusionProofsContract } from '@aztec/noir-contracts/types'; import { jest } from '@jest/globals'; @@ -21,142 +33,221 @@ describe('e2e_inclusion_proofs_contract', () => { let contract: InclusionProofsContract; let deploymentBlockNumber: number; const publicValue = 236n; + const contractAddressSalt = Fr.random(); beforeAll(async () => { ({ pxe, teardown, wallets, accounts } = await setup(1)); - const receipt = await InclusionProofsContract.deploy(wallets[0], publicValue).send().wait(); + const receipt = await InclusionProofsContract.deploy(wallets[0], publicValue).send({ contractAddressSalt }).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; - expect(receivedValue.toBigInt()).toBe(value); - expect(receivedOwner).toEqual(owner.toField()); - } - - { - // Prove note inclusion in a given block. - const ignoredCommitment = 0; // Not ignored only when the note doesn't exist - await contract.methods.test_note_inclusion_proof(owner, noteCreationBlockNumber, ignoredCommitment).send().wait(); - } - - { - // Prove that the note has not been nullified - // 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.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.nullify_note(owner).send().wait({ debug: true }); - const { newNullifiers } = receipt.debugInfo!; - expect(newNullifiers.length).toBe(2); - - const blockNumber = await pxe.getBlockNumber(); - const nullifier = newNullifiers[1]; - // Note: getLowNullifierMembershipWitness returns the membership witness of the nullifier itself and not - // 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. + describe('note inclusion and nullifier non-inclusion', () => { + let owner: AztecAddress; + + beforeAll(() => { + owner = accounts[0].address; + }); + + it('proves note existence and its nullifier non-existence and nullifier non-existence failure case', async () => { + // Owner of a note + 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 inclusion in a given block. + const ignoredCommitment = 0; // Not ignored only when the note doesn't exist + await contract.methods + .test_note_inclusion_proof(owner, noteCreationBlockNumber, ignoredCommitment) + .send() + .wait(); + } + + { + // Prove that the note has not been nullified + // 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.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.nullify_note(owner).send().wait({ debug: true }); + const { newNullifiers } = receipt.debugInfo!; + expect(newNullifiers.length).toBe(2); + + const blockNumber = await pxe.getBlockNumber(); + const nullifier = newNullifiers[1]; + // Note: getLowNullifierMembershipWitness returns the membership witness of the nullifier itself and not + // 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.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 - ignored in the contract since the note won't be found and the spare random note commitment + // will be used instead + const owner = AztecAddress.random(); + + // Choose random block number between deployment and current block number to test archival node + const blockNumber = await getRandomBlockNumberSinceDeployment(); + const randomNoteCommitment = Fr.random(); await expect( - 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/, - ); - } + contract.methods.test_note_inclusion_proof(owner, blockNumber, randomNoteCommitment).send().wait(), + ).rejects.toThrow(`Leaf value: ${randomNoteCommitment.toString()} not found in NOTE_HASH_TREE`); + }); }); - 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(); - } - }); + describe('public value existence at a slot', () => { + it('proves an existence of a public value in private context', async () => { + // Choose random block number between deployment and current block number to test archival node + const blockNumber = await getRandomBlockNumberSinceDeployment(); - it('note existence failure case', async () => { - // Owner of a note - ignored in the contract since the note won't be found and the spare random note commitment - // will be used instead - const owner = AztecAddress.random(); - - // Choose random block number between deployment and current block number to test archival node - const blockNumber = await getRandomBlockNumberSinceDeployment(); - const randomNoteCommitment = Fr.random(); - await expect( - 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/); - }); + await contract.methods.test_public_value_inclusion_proof(publicValue, blockNumber).send().wait(); + }); - it('proves an existence of a public value in private context', async () => { - // Choose random block number between deployment and current block number to test archival node - const blockNumber = await getRandomBlockNumberSinceDeployment(); + it('public value existence failure case', async () => { + // Choose random block number between first block and current block number to test archival node + const blockNumber = await getRandomBlockNumber(); - await contract.methods.test_public_value_inclusion_proof(publicValue, blockNumber).send().wait(); + const randomPublicValue = Fr.random(); + await expect( + contract.methods.test_public_value_inclusion_proof(randomPublicValue, blockNumber).send().wait(), + ).rejects.toThrow(/Public value does not match value in witness/); + }); }); - it('public value existence failure case', async () => { - // Choose random block number between first block and current block number to test archival node - const blockNumber = await getRandomBlockNumber(); + describe('nullifier inclusion', () => { + it('proves existence of a nullifier in private context', async () => { + // Choose random block number between deployment and current block number to test archival node + const blockNumber = await getRandomBlockNumberSinceDeployment(); + const block = await pxe.getBlock(blockNumber); + const nullifier = block?.newNullifiers[0]; - const randomPublicValue = Fr.random(); - await expect( - contract.methods.test_public_value_inclusion_proof(randomPublicValue, blockNumber).send().wait(), - ).rejects.toThrow(/Public value does not match value in witness/); - }); + await contract.methods.test_nullifier_inclusion_proof(nullifier!, blockNumber).send().wait(); + }); - it('proves existence of a nullifier in private context', async () => { - // Choose random block number between deployment and current block number to test archival node - const blockNumber = await getRandomBlockNumberSinceDeployment(); - const block = await pxe.getBlock(blockNumber); - const nullifier = block?.newNullifiers[0]; + it('nullifier existence failure case', async () => { + // Choose random block number between first block and current block number to test archival node + const blockNumber = await getRandomBlockNumber(); + const randomNullifier = Fr.random(); - await contract.methods.test_nullifier_inclusion_proof(nullifier!, blockNumber).send().wait(); + await expect( + contract.methods.test_nullifier_inclusion_proof(randomNullifier, blockNumber).send().wait(), + ).rejects.toThrow(`Low nullifier witness not found for nullifier ${randomNullifier.toString()} at block`); + }); }); - it('nullifier existence failure case', async () => { - // Choose random block number between first block and current block number to test archival node - const blockNumber = await getRandomBlockNumber(); - const randomNullifier = Fr.random(); + describe('contract inclusion', () => { + // InclusionProofs contract doesn't have associated public key because it's not an account contract + const publicKey = Point.ZERO; + let functionTreeRoot: Fr; + let constructorHash: Fr; + let portalContractAddress: EthAddress; + + beforeAll(() => { + const contractArtifact = contract.artifact; + + const constructorArgs = [publicValue]; + + ({ constructorHash, functionTreeRoot } = getContractDeploymentInfo( + contractArtifact, + constructorArgs, + contractAddressSalt, + publicKey, + )); + + portalContractAddress = contract.portalContract; + }); + + it('proves existence of a contract', async () => { + // Choose random block number between first block and current block number to test archival node + const blockNumber = await getRandomBlockNumberSinceDeployment(); + + // Note: We pass in preimage of AztecAddress instead of just AztecAddress in order for the contract to be able to + // test that the contract was deployed with correct constructor parameters. + await contract.methods + .test_contract_inclusion_proof( + publicKey, + contractAddressSalt, + functionTreeRoot, + constructorHash, + portalContractAddress, + blockNumber, + ) + .send() + .wait(); + }); + + it('contract existence failure case', async () => { + // This should fail because we choose a block number before the contract was deployed + const blockNumber = deploymentBlockNumber - 1; + + const contractData = new NewContractData(contract.address, contract.portalContract, functionTreeRoot); + const leaf = computeContractLeaf(contractData); - 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/); + await expect( + contract.methods + .test_contract_inclusion_proof( + publicKey, + contractAddressSalt, + functionTreeRoot, + constructorHash, + portalContractAddress, + blockNumber, + ) + .send() + .wait(), + ).rejects.toThrow(`Leaf value: ${leaf.toString()} not found in CONTRACT_TREE`); + }); }); const getRandomBlockNumberSinceDeployment = async () => { diff --git a/yarn-project/noir-contracts/Nargo.toml b/yarn-project/noir-contracts/Nargo.toml index bfed9ce41365..609d4c379592 100644 --- a/yarn-project/noir-contracts/Nargo.toml +++ b/yarn-project/noir-contracts/Nargo.toml @@ -10,6 +10,7 @@ members = [ "src/contracts/ecdsa_account_contract", "src/contracts/escrow_contract", "src/contracts/import_test_contract", + "src/contracts/inclusion_proofs_contract", "src/contracts/lending_contract", "src/contracts/parent_contract", "src/contracts/pending_commitments_contract", diff --git a/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/main.nr index c0516b904682..0f42396fe77b 100644 --- a/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/inclusion_proofs_contract/src/main.nr @@ -1,6 +1,12 @@ // A demonstration of inclusion and non-inclusion proofs. contract InclusionProofs { - use dep::protocol_types::address::AztecAddress; + use dep::protocol_types::{ + address::{ + AztecAddress, + EthAddress, + }, + point::Point, + }; use dep::aztec::{ state_vars::{ map::Map, @@ -19,6 +25,9 @@ contract InclusionProofs { }, history::{ + contract_inclusion::{ + prove_contract_inclusion, + }, note_inclusion::{ prove_note_commitment_inclusion, prove_note_inclusion, @@ -77,22 +86,21 @@ contract InclusionProofs { // Creates a value note owned by `owner`. #[aztec(private)] - fn create_note( - owner: AztecAddress, - value: Field, - ) { + fn create_note(owner: AztecAddress, value: Field) { let owner_private_values = storage.private_values.at(owner.to_field()); let mut note = ValueNote::new(value, owner); owner_private_values.insert(&mut note, true); } - // Proves that the owner owned a ValueNote at block `block_number`. #[aztec(private)] fn test_note_inclusion_proof( owner: AztecAddress, 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 + // Value bellow is only used when the note is not found --> used to test the note inclusion failure case (it + // allows me to pass in random value of note nullifier - I cannot add and fetch a random note from PXE because + // PXE performs note commitment inclusion check when you add a new note). + spare_commitment: Field ) { // 1) Get the note from PXE. let private_values = storage.private_values.at(owner.to_field()); @@ -102,7 +110,12 @@ contract InclusionProofs { // 2) Prove the note inclusion if maybe_note.is_some() { - prove_note_inclusion(ValueNoteMethods, maybe_note.unwrap_unchecked(), block_number, context); + prove_note_inclusion( + ValueNoteMethods, + maybe_note.unwrap_unchecked(), + block_number, + context + ); } else { // Note was not found so we will prove inclusion of the spare commitment prove_note_commitment_inclusion(spare_commitment, block_number, context); @@ -114,7 +127,10 @@ contract InclusionProofs { fn test_nullifier_non_inclusion_proof( owner: AztecAddress, 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 + // Value bellow is only used when the note is not found --> used to test the nullifier non-inclusion failure + // case (it allows me to pass in random value of note nullifier - I cannot add and fetch a random note from PXE + // because PXE performs note commitment inclusion check when you add a new note). + spare_nullifier: Field ) { // 2) Get the note from PXE let private_values = storage.private_values.at(owner.to_field()); @@ -124,7 +140,12 @@ contract InclusionProofs { // 3) Compute the nullifier from the note if maybe_note.is_some() { - prove_note_not_nullified(ValueNoteMethods, maybe_note.unwrap_unchecked(), block_number, context); + prove_note_not_nullified( + ValueNoteMethods, + maybe_note.unwrap_unchecked(), + block_number, + context + ); } else { // Note was not found so we will use the spare nullifier prove_nullifier_non_inclusion(spare_nullifier, block_number, context); @@ -134,7 +155,7 @@ contract InclusionProofs { #[aztec(private)] fn test_note_validity_proof( owner: AztecAddress, - block_number: u32, // The block at which we'll prove that the note exists and is not nullified + block_number: u32 // The block at which we'll prove that the note exists and is not nullified ) { // 1) Get the note from PXE. let private_values = storage.private_values.at(owner.to_field()); @@ -147,9 +168,7 @@ contract InclusionProofs { } #[aztec(private)] - fn nullify_note( - owner: AztecAddress, - ) { + fn nullify_note(owner: AztecAddress) { let private_values = storage.private_values.at(owner.to_field()); let options = NoteGetterOptions::new().select(1, owner.to_field()).set_limit(1); let notes = private_values.get_notes(options); @@ -164,7 +183,7 @@ contract InclusionProofs { #[aztec(private)] fn test_nullifier_inclusion_proof( nullifier: Field, - block_number: u32, // 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 ) { prove_nullifier_inclusion(nullifier, block_number, context); } @@ -172,17 +191,56 @@ contract InclusionProofs { #[aztec(private)] fn test_public_value_inclusion_proof( public_value: Field, - block_number: u32, // 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 + ) { + prove_public_value_inclusion( + public_value, + storage.public_value.storage_slot, + block_number, + context + ); + } + + // Proves that a contract exists at block `block_number`. + // Note: This can be used to approximate a factory pattern --> a factory contract could perform this proof and that + // way verify that a contract at a given address is what it expects. Then it could store it in an internal + // map of contracts (like what Uniswap Factory does with pool contracts - it stores them in a mapping). + // By passing in the construct hash the factory can also verify that the contract was constructed with the + // correct constructor arguments. Typically the factory would store the expected construct hash and assert + // that it is what it expects. The constructor param check is the reason of why we pass in the preimage of + // contract's aztec address instead of just the address. + #[aztec(private)] + fn test_contract_inclusion_proof( + deployer_public_key: Point, + contract_address_salt: Field, + function_tree_root: Field, + constructor_hash: Field, + portal_contract_address: EthAddress, + block_number: u32 // The block at which we'll prove that the public value exists ) { - prove_public_value_inclusion(public_value, storage.public_value.storage_slot, block_number, context); + let proven_contract_address = prove_contract_inclusion( + deployer_public_key, + contract_address_salt, + function_tree_root, + constructor_hash, + portal_contract_address, + block_number, + context + ); + // Here typically the factory would add the contract address to its internal map of deployed contracts. } // Computes note hash and nullifier. // Note 1: Needs to be defined by every contract producing logs. // Note 2: Having it in all the contracts gives us the ability to compute the note hash and nullifier differently for different kind of notes. - unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, serialized_note: [Field; VALUE_NOTE_LEN]) -> [Field; 4] { + unconstrained fn compute_note_hash_and_nullifier( + contract_address: Field, + nonce: Field, + storage_slot: Field, + serialized_note: [Field; VALUE_NOTE_LEN] + ) -> [Field; 4] { let _address = AztecAddress::from_field(contract_address); // TODO(benesjan) https://github.com/AztecProtocol/aztec-packages/issues/3669 let note_header = NoteHeader::new(_address, nonce, storage_slot); note_utils::compute_note_hash_and_nullifier(ValueNoteMethods, note_header, serialized_note) } -} \ No newline at end of file +} diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/point.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/point.nr index 6482295491e1..1f6b5da720e1 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/point.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/point.nr @@ -4,6 +4,8 @@ struct Point { y: Field, } +global POINT_SERIALIZED_LEN: Field = 2; + impl Point { pub fn zero() -> Self { Point { @@ -12,6 +14,10 @@ impl Point { } } + fn serialize(self) -> [Field; POINT_SERIALIZED_LEN] { + [self.x, self.y] + } + // TODO(David): Would be quite careful here as (0,0) is not a point // on the curve. A boolean flag may be the better approach here, // would also cost less constraints. It seems like we don't need to diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 537d60b654ff..f228aa8fe8cd 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -150,14 +150,16 @@ export class SimulatorOracle implements DBOracle { public async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: bigint): Promise { // @todo Doing a nasty workaround here because of https://github.com/AztecProtocol/aztec-packages/issues/3414 switch (treeId) { + case MerkleTreeId.CONTRACT_TREE: + return (await this.stateInfoProvider.getContractSiblingPath(blockNumber, leafIndex)).toFieldArray(); case MerkleTreeId.NULLIFIER_TREE: - return (await this.stateInfoProvider.getNullifierTreeSiblingPath(blockNumber, leafIndex)).toFieldArray(); + return (await this.stateInfoProvider.getNullifierSiblingPath(blockNumber, leafIndex)).toFieldArray(); case MerkleTreeId.NOTE_HASH_TREE: return (await this.stateInfoProvider.getNoteHashSiblingPath(blockNumber, leafIndex)).toFieldArray(); + case MerkleTreeId.PUBLIC_DATA_TREE: + return (await this.stateInfoProvider.getPublicDataSiblingPath(blockNumber, leafIndex)).toFieldArray(); case MerkleTreeId.ARCHIVE: return (await this.stateInfoProvider.getArchiveSiblingPath(blockNumber, leafIndex)).toFieldArray(); - case MerkleTreeId.PUBLIC_DATA_TREE: - return (await this.stateInfoProvider.getPublicDataTreeSiblingPath(blockNumber, leafIndex)).toFieldArray(); default: throw new Error('Not implemented'); } diff --git a/yarn-project/types/src/interfaces/state_info_provider.ts b/yarn-project/types/src/interfaces/state_info_provider.ts index d643ba10b6a9..9384212bda48 100644 --- a/yarn-project/types/src/interfaces/state_info_provider.ts +++ b/yarn-project/types/src/interfaces/state_info_provider.ts @@ -50,7 +50,7 @@ export interface StateInfoProvider { * @returns The sibling path for the leaf index. * TODO: https://github.com/AztecProtocol/aztec-packages/issues/3414 */ - getNullifierTreeSiblingPath( + getNullifierSiblingPath( blockNumber: BlockNumber, leafIndex: bigint, ): Promise>; @@ -103,7 +103,7 @@ export interface StateInfoProvider { * @returns The sibling path. * TODO: https://github.com/AztecProtocol/aztec-packages/issues/3414 */ - getPublicDataTreeSiblingPath( + getPublicDataSiblingPath( blockNumber: BlockNumber, leafIndex: bigint, ): Promise>;