Skip to content

Commit

Permalink
feat: Restore contract inclusion proofs (#5141)
Browse files Browse the repository at this point in the history
Restore contract inclusion proofs, which just delegate to nullifier
inclusion proofs now, because we don't have a contract tree anymore.
  • Loading branch information
spalladino authored Mar 12, 2024
1 parent 69bd7dd commit a39cd61
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 151 deletions.
114 changes: 47 additions & 67 deletions noir-projects/aztec-nr/aztec/src/history/contract_inclusion.nr
Original file line number Diff line number Diff line change
@@ -1,84 +1,64 @@
use dep::protocol_types::{
address::{AztecAddress, EthAddress}, contract_class_id::ContractClassId,
grumpkin_point::GrumpkinPoint
address::{AztecAddress, EthAddress},
contract_class_id::ContractClassId,
grumpkin_point::GrumpkinPoint,
hash::silo_nullifier,
constants::DEPLOYER_CONTRACT_ADDRESS
};
use dep::std::merkle::compute_merkle_root;

use crate::{context::PrivateContext};
use crate::{
context::PrivateContext,
history::{
nullifier_inclusion::prove_nullifier_inclusion_at,
nullifier_non_inclusion::prove_nullifier_not_included_at,
}
};

// 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(
public_key: GrumpkinPoint,
contract_address_salt: Field,
contract_class_id: ContractClassId,
initialization_hash: Field,
portal_contract_address: EthAddress,
pub fn prove_contract_deployment_at(
contract_address: AztecAddress,
block_number: u32,
context: PrivateContext
) -> AztecAddress {
// 1) Get block header from context
// let block_header = context.historical_header;

// 2) Compute the contract address
let contract_address = AztecAddress::compute_from_public_key(
public_key,
contract_class_id,
contract_address_salt,
initialization_hash,
portal_contract_address
);
) {
// Compute deployment nullifier
let nullifier = silo_nullifier(AztecAddress::from_field(DEPLOYER_CONTRACT_ADDRESS), contract_address.to_field());

// TODO(@spalladino): Use initialization and/or deployment nullifier for this proof.
// Consider splitting this into 2 methods, one for initialization and one for public deployment.
// Prove its inclusion
prove_nullifier_inclusion_at(nullifier, block_number, context);
}

// 3) Form the contract tree leaf preimage
// let preimage = ContractLeafPreimage { contract_address, portal_contract_address, contract_class_id };
//
// 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.partial.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.
pub fn prove_contract_non_deployment_at(
contract_address: AztecAddress,
block_number: u32,
context: PrivateContext
) {
// Compute deployment nullifier
let nullifier = silo_nullifier(AztecAddress::from_field(DEPLOYER_CONTRACT_ADDRESS), contract_address.to_field());

contract_address
// Prove its non-inclusion
prove_nullifier_not_included_at(nullifier, block_number, context);
}

pub fn prove_contract_inclusion_at(
public_key: GrumpkinPoint,
contract_address_salt: Field,
contract_class_id: ContractClassId,
initialization_hash: Field,
portal_contract_address: EthAddress,
pub fn prove_contract_initialization_at(
contract_address: AztecAddress,
block_number: u32,
context: PrivateContext
) -> AztecAddress {
// 1) Get block header from oracle and ensure that the block is included in the archive.
let header = context.get_header_at(block_number);
) {
// Compute initialization nullifier
let nullifier = silo_nullifier(contract_address, contract_address.to_field());

// 2) Compute the contract address
let contract_address = AztecAddress::compute_from_public_key(
public_key,
contract_class_id,
contract_address_salt,
initialization_hash,
portal_contract_address
);
// Prove its inclusion
prove_nullifier_inclusion_at(nullifier, block_number, context);
}

// TODO(@spalladino): See above func to impl
pub fn prove_contract_non_initialization_at(
contract_address: AztecAddress,
block_number: u32,
context: PrivateContext
) {
// Compute initialization nullifier
let nullifier = silo_nullifier(contract_address, contract_address.to_field());

contract_address
// Prove its non-inclusion
prove_nullifier_not_included_at(nullifier, block_number, context);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ contract InclusionProofs {
use dep::aztec::{context::Context, note::note_getter_options::NoteStatus};
// docs:start:imports
use dep::aztec::history::{
contract_inclusion::{prove_contract_inclusion, prove_contract_inclusion_at},
contract_inclusion::{
prove_contract_deployment_at, prove_contract_non_deployment_at,
prove_contract_initialization_at, prove_contract_non_initialization_at
},
note_inclusion::{prove_note_inclusion, prove_note_inclusion_at},
note_validity::{prove_note_validity, prove_note_validity_at},
nullifier_inclusion::{
Expand Down Expand Up @@ -216,32 +219,35 @@ contract InclusionProofs {
}
}

// 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.
// Proves that a contract was publicly deployed and/or initialized at block `block_number`.
#[aztec(private)]
fn test_contract_inclusion(
public_key: GrumpkinPoint,
contract_address_salt: Field,
contract_class_id: ContractClassId,
initialization_hash: Field,
portal_contract_address: EthAddress,
block_number: u32 // The block at which we'll prove that the public value exists
contract_address: AztecAddress,
block_number: u32,
test_deployment: bool,
test_initialization: bool
) {
let proven_contract_address = prove_contract_inclusion_at(
public_key,
contract_address_salt,
contract_class_id,
initialization_hash,
portal_contract_address,
block_number,
context
);
// Here typically the factory would add the contract address to its internal map of deployed contracts.
if test_deployment {
prove_contract_deployment_at(contract_address, block_number, context);
}
if test_initialization {
prove_contract_initialization_at(contract_address, block_number, context);
}
}

// Proves that a contract was NOT publicly deployed and/or initialized at block `block_number`.
#[aztec(private)]
fn test_contract_non_inclusion(
contract_address: AztecAddress,
block_number: u32,
test_deployment: bool,
test_initialization: bool
) {
if test_deployment {
prove_contract_non_deployment_at(contract_address, block_number, context);
}
if test_initialization {
prove_contract_non_initialization_at(contract_address, block_number, context);
}
}
}
93 changes: 34 additions & 59 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 @@ -2,13 +2,13 @@ import {
AccountWallet,
AztecAddress,
CompleteAddress,
EthAddress,
Fr,
INITIAL_L2_BLOCK_NUM,
PXE,
Point,
getContractInstanceFromDeployParams,
} from '@aztec/aztec.js';
import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment';
import { StatefulTestContract, StatefulTestContractArtifact } from '@aztec/noir-contracts.js';
import { InclusionProofsContract } from '@aztec/noir-contracts.js/InclusionProofs';

import { jest } from '@jest/globals';
Expand Down Expand Up @@ -250,67 +250,42 @@ describe('e2e_inclusion_proofs_contract', () => {
});

describe('contract inclusion', () => {
// InclusionProofs contract doesn't have associated public key because it's not an account contract
const publicKey = Point.ZERO;
let contractClassId: Fr;
let initializationHash: Fr;
let portalContractAddress: EthAddress;

beforeAll(() => {
const contractArtifact = contract.artifact;
const constructorArgs = [publicValue];
portalContractAddress = EthAddress.random();

const instance = getContractInstanceFromDeployParams(
contractArtifact,
constructorArgs,
contractAddressSalt,
publicKey,
portalContractAddress,
);

contractClassId = instance.contractClassId;
initializationHash = instance.initializationHash;
const assertInclusion = async (
address: AztecAddress,
blockNumber: number,
opts: { testDeploy: boolean; testInit: boolean },
) => {
const { testDeploy, testInit } = opts;
// Assert contract was publicly deployed or initialized in the block in which it was deployed
await contract.methods.test_contract_inclusion(address, blockNumber, testDeploy, testInit).send().wait();

// And prove that it was not before that
const olderBlock = blockNumber - 2;
await contract.methods.test_contract_non_inclusion(address, olderBlock, testDeploy, testInit).send().wait();

// Or that the positive call fails when trying to prove in the older block
await expect(
contract.methods.test_contract_inclusion(address, olderBlock, testDeploy, testInit).simulate(),
).rejects.toThrow(/not found/);
};

it('proves public deployment of a contract', async () => {
// Publicly deploy another contract (so we don't test on the same contract)
const initArgs = [accounts[0], 42n];
const instance = getContractInstanceFromDeployParams(StatefulTestContractArtifact, initArgs);
await (await registerContractClass(wallets[0], StatefulTestContractArtifact)).send().wait();
const receipt = await deployInstance(wallets[0], instance).send().wait();

await assertInclusion(instance.address, receipt.blockNumber!, { testDeploy: true, testInit: false });
});

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(
publicKey,
contractAddressSalt,
contractClassId,
initializationHash,
portalContractAddress,
blockNumber,
)
.send()
it('proves initialization of a contract', async () => {
// Initialize (but not deploy) a test contract
const receipt = await StatefulTestContract.deploy(wallets[0], accounts[0], 42n)
.send({ skipClassRegistration: true, skipPublicDeployment: true })
.wait();
});

// TODO(@spalladino): Re-enable once we add check for non-inclusion based on nullifier
it.skip('contract existence failure case', async () => {
// This should fail because we choose a block number before the contract was deployed
const blockNumber = deploymentBlockNumber - 1;
const leaf = Fr.ZERO; // TODO: Calculate proper leaf value

await expect(
contract.methods
.test_contract_inclusion(
publicKey,
contractAddressSalt,
contractClassId,
initializationHash,
portalContractAddress,
blockNumber,
)
.send()
.wait(),
).rejects.toThrow(`Leaf value: ${leaf.toString()} not found in CONTRACT_TREE`);
await assertInclusion(receipt.contract.address, receipt.blockNumber!, { testDeploy: false, testInit: true });
});
});

Expand Down

0 comments on commit a39cd61

Please sign in to comment.