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: Restore contract inclusion proofs #5141

Merged
merged 1 commit into from
Mar 12, 2024
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
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
Loading