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: Proving the private kernels and app circuits #6112

Merged
merged 19 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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
5 changes: 4 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"bbmalloc",
"benesjan",
"Bincode",
"bincoded",
"bleurgh",
"bodyparser",
"bootnode",
Expand Down Expand Up @@ -98,6 +99,7 @@
"gitrepo",
"grumpkin",
"gtest",
"gzipped",
"hackmd",
"hardfork",
"hardlinks",
Expand Down Expand Up @@ -181,6 +183,7 @@
"productionify",
"protobuf",
"protogalaxy",
"proverless",
"proxied",
"proxified",
"proxify",
Expand Down Expand Up @@ -287,4 +290,4 @@
"flagWords": [
"anonymous"
]
}
}
5 changes: 4 additions & 1 deletion yarn-project/end-to-end/src/benchmarks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ export async function waitNewPXESynced(
contract: BenchmarkingContract,
startingBlock: number = INITIAL_L2_BLOCK_NUM,
): Promise<PXEService> {
const pxe = await createPXEService(node, { l2BlockPollingIntervalMS: 100, l2StartingBlock: startingBlock });
const pxe = await createPXEService(node, {
l2BlockPollingIntervalMS: 100,
l2StartingBlock: startingBlock,
});
await pxe.registerContract(contract);
await retryUntil(() => pxe.isGlobalStateSynchronized(), 'pxe-global-sync');
return pxe;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { type Tx } from '@aztec/aztec.js';
import { type ClientProtocolArtifact } from '@aztec/noir-protocol-circuits-types';
import { type BBNativeProofCreator } from '@aztec/pxe';

import { ClientProverTest } from './client_prover_test.js';

const TIMEOUT = 300_000;

async function verifyProof(_1: ClientProtocolArtifact, _2: Tx, _3: BBNativeProofCreator) {
// TODO(@PhilWindle): Will verify proof once the circuits are fixed
await Promise.resolve();
//const result = await proofCreator.verifyProof(circuitType, tx.proof);
expect(true).toBeTruthy();
}

describe('client_prover_integration', () => {
const t = new ClientProverTest('transfer_private');
let { provenAsset, accounts, tokenSim, logger, proofCreator } = t;

beforeAll(async () => {
await t.applyBaseSnapshots();
await t.applyMintSnapshot();
await t.setup();
({ provenAsset, accounts, tokenSim, logger, proofCreator } = t);
});

afterAll(async () => {
await t.teardown();
});

afterEach(async () => {
await t.tokenSim.check();
});

it(
'private transfer less than balance',
async () => {
logger.info(
`Starting test using function: ${provenAsset.address}:${provenAsset.methods.balance_of_private.selector}`,
);
const balance0 = await provenAsset.methods.balance_of_private(accounts[0].address).simulate();
const amount = balance0 / 2n;
expect(amount).toBeGreaterThan(0n);
const interaction = provenAsset.methods.transfer(accounts[0].address, accounts[1].address, amount, 0);
const provenTx = await interaction.prove();

// This will recursively verify all app and kernel circuits involved in the private stage of this transaction!
logger.info(`Verifying kernel tail proof`);
await verifyProof('PrivateKernelTailArtifact', provenTx, proofCreator!);

await interaction.send().wait();
tokenSim.transferPrivate(accounts[0].address, accounts[1].address, amount);
},
TIMEOUT,
);

it(
'public transfer less than balance',
async () => {
logger.info(
`Starting test using function: ${provenAsset.address}:${provenAsset.methods.balance_of_public.selector}`,
);
const balance0 = await provenAsset.methods.balance_of_public(accounts[0].address).simulate();
const amount = balance0 / 2n;
expect(amount).toBeGreaterThan(0n);
const interaction = provenAsset.methods.transfer(accounts[0].address, accounts[1].address, amount, 0);
const provenTx = await interaction.prove();

// This will recursively verify all app and kernel circuits involved in the private stage of this transaction!
logger.info(`Verifying kernel tail to public proof`);
await verifyProof('PrivateKernelTailToPublicArtifact', provenTx, proofCreator!);

await interaction.send().wait();
tokenSim.transferPublic(accounts[0].address, accounts[1].address, amount);
},
TIMEOUT,
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { SchnorrAccountContractArtifact, getSchnorrAccount } from '@aztec/accounts/schnorr';
import {
type AccountWalletWithSecretKey,
type AztecNode,
type CompleteAddress,
type DebugLogger,
ExtendedNote,
type Fq,
Fr,
Note,
type TxHash,
computeSecretHash,
createDebugLogger,
} from '@aztec/aztec.js';
import { TokenContract } from '@aztec/noir-contracts.js';
import { BBNativeProofCreator, type PXEService } from '@aztec/pxe';

import * as fs from 'fs/promises';

import { waitRegisteredAccountSynced } from '../benchmarks/utils.js';
import {
SnapshotManager,
type SubsystemsContext,
addAccounts,
publicDeployAccounts,
} from '../fixtures/snapshot_manager.js';
import { getBBConfig, setupPXEService } from '../fixtures/utils.js';
import { TokenSimulator } from '../simulators/token_simulator.js';

const { E2E_DATA_PATH: dataPath } = process.env;

const SALT = 1;

/**
* Largely taken from the e2e_token_contract test file. We deploy 2 accounts and a token contract.
* However, we then setup a second PXE with a full prover instance.
* We configure this instance with all of the accounts and contracts.
* We then prove and verify transactions created via this full prover PXE.
*/

export class ClientProverTest {
static TOKEN_NAME = 'Aztec Token';
static TOKEN_SYMBOL = 'AZT';
static TOKEN_DECIMALS = 18n;
private snapshotManager: SnapshotManager;
logger: DebugLogger;
keys: Array<[Fr, Fq]> = [];
wallets: AccountWalletWithSecretKey[] = [];
accounts: CompleteAddress[] = [];
asset!: TokenContract;
tokenSim!: TokenSimulator;
aztecNode!: AztecNode;
pxe!: PXEService;
fullProverPXE!: PXEService;
provenAsset!: TokenContract;
provenPXETeardown?: () => Promise<void>;
private directoryToCleanup?: string;
proofCreator?: BBNativeProofCreator;

constructor(testName: string) {
this.logger = createDebugLogger(`aztec:client_prover_test:${testName}`);
this.snapshotManager = new SnapshotManager(`client_prover_integration/${testName}`, dataPath);
}

/**
* Adds two state shifts to snapshot manager.
* 1. Add 2 accounts.
* 2. Publicly deploy accounts, deploy token contract
*/
async applyBaseSnapshots() {
await this.snapshotManager.snapshot('2_accounts', addAccounts(2, this.logger), async ({ accountKeys }, { pxe }) => {
this.keys = accountKeys;
const accountManagers = accountKeys.map(ak => getSchnorrAccount(pxe, ak[0], ak[1], SALT));
this.wallets = await Promise.all(accountManagers.map(a => a.getWallet()));
this.accounts = await pxe.getRegisteredAccounts();
this.wallets.forEach((w, i) => this.logger.verbose(`Wallet ${i} address: ${w.getAddress()}`));
});

await this.snapshotManager.snapshot(
'client_prover_integration',
async () => {
// Create the token contract state.
// Move this account thing to addAccounts above?
this.logger.verbose(`Public deploy accounts...`);
await publicDeployAccounts(this.wallets[0], this.accounts.slice(0, 2));

this.logger.verbose(`Deploying TokenContract...`);
const asset = await TokenContract.deploy(
this.wallets[0],
this.accounts[0],
ClientProverTest.TOKEN_NAME,
ClientProverTest.TOKEN_SYMBOL,
ClientProverTest.TOKEN_DECIMALS,
)
.send()
.deployed();
this.logger.verbose(`Token deployed to ${asset.address}`);

return { tokenContractAddress: asset.address };
},
async ({ tokenContractAddress }) => {
// Restore the token contract state.
this.asset = await TokenContract.at(tokenContractAddress, this.wallets[0]);
this.logger.verbose(`Token contract address: ${this.asset.address}`);

this.tokenSim = new TokenSimulator(
this.asset,
this.logger,
this.accounts.map(a => a.address),
);

expect(await this.asset.methods.admin().simulate()).toBe(this.accounts[0].address.toBigInt());
},
);
}

async setup() {
const context = await this.snapshotManager.setup();
({ pxe: this.pxe, aztecNode: this.aztecNode } = context);

// Configure a full prover PXE
const bbConfig = await getBBConfig(this.logger);
this.directoryToCleanup = bbConfig?.directoryToCleanup;

if (!bbConfig?.bbWorkingDirectory || !bbConfig?.expectedBBPath) {
throw new Error(`Test must be run with BB native configuration`);
}

this.proofCreator = new BBNativeProofCreator(bbConfig?.expectedBBPath, bbConfig?.bbWorkingDirectory);

this.logger.debug(`Main setup completed, initializing full prover PXE...`);
({ pxe: this.fullProverPXE, teardown: this.provenPXETeardown } = await setupPXEService(
0,
this.aztecNode,
{
proverEnabled: false,
bbBinaryPath: bbConfig?.expectedBBPath,
bbWorkingDirectory: bbConfig?.bbWorkingDirectory,
},
undefined,
true,
this.proofCreator,
));
this.logger.debug(`Contract address ${this.asset.address}`);
await this.fullProverPXE.registerContract(this.asset);

for (let i = 0; i < 2; i++) {
await waitRegisteredAccountSynced(
this.fullProverPXE,
this.keys[i][0],
this.wallets[i].getCompleteAddress().partialAddress,
);

await waitRegisteredAccountSynced(this.pxe, this.keys[i][0], this.wallets[i].getCompleteAddress().partialAddress);
}

const account = getSchnorrAccount(this.fullProverPXE, this.keys[0][0], this.keys[0][1], SALT);

await this.fullProverPXE.registerContract({
instance: account.getInstance(),
artifact: SchnorrAccountContractArtifact,
});

const provenWallet = await account.getWallet();
this.provenAsset = await TokenContract.at(this.asset.address, provenWallet);
this.logger.debug(`Full prover PXE started!!`);
return this;
}

snapshot = <T>(
name: string,
apply: (context: SubsystemsContext) => Promise<T>,
restore: (snapshotData: T, context: SubsystemsContext) => Promise<void> = () => Promise.resolve(),
): Promise<void> => this.snapshotManager.snapshot(name, apply, restore);

async teardown() {
await this.snapshotManager.teardown();

// Cleanup related to the second 'full prover' PXE
await this.provenPXETeardown?.();

if (this.directoryToCleanup) {
await fs.rm(this.directoryToCleanup, { recursive: true, force: true });
}
}

async addPendingShieldNoteToPXE(accountIndex: number, amount: bigint, secretHash: Fr, txHash: TxHash) {
const note = new Note([new Fr(amount), secretHash]);
const extendedNote = new ExtendedNote(
note,
this.accounts[accountIndex].address,
this.asset.address,
TokenContract.storage.pending_shields.slot,
TokenContract.notes.TransparentNote.id,
txHash,
);
await this.wallets[accountIndex].addNote(extendedNote);
}

async applyMintSnapshot() {
await this.snapshotManager.snapshot(
'mint',
async () => {
const { asset, accounts } = this;
const amount = 10000n;

this.logger.verbose(`Minting ${amount} publicly...`);
await asset.methods.mint_public(accounts[0].address, amount).send().wait();

this.logger.verbose(`Minting ${amount} privately...`);
const secret = Fr.random();
const secretHash = computeSecretHash(secret);
const receipt = await asset.methods.mint_private(amount, secretHash).send().wait();

await this.addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash);
const txClaim = asset.methods.redeem_shield(accounts[0].address, amount, secret).send();
await txClaim.wait({ debug: true });
this.logger.verbose(`Minting complete.`);

return { amount };
},
async ({ amount }) => {
const {
asset,
accounts: [{ address }],
tokenSim,
} = this;
tokenSim.mintPublic(address, amount);

const publicBalance = await asset.methods.balance_of_public(address).simulate();
this.logger.verbose(`Public balance of wallet 0: ${publicBalance}`);
expect(publicBalance).toEqual(this.tokenSim.balanceOfPublic(address));

tokenSim.mintPrivate(amount);
tokenSim.redeemShield(address, amount);
const privateBalance = await asset.methods.balance_of_private(address).simulate();
this.logger.verbose(`Private balance of wallet 0: ${privateBalance}`);
expect(privateBalance).toEqual(tokenSim.balanceOfPrivate(address));

const totalSupply = await asset.methods.total_supply().simulate();
this.logger.verbose(`Total supply: ${totalSupply}`);
expect(totalSupply).toEqual(tokenSim.totalSupply);

return Promise.resolve();
},
);
}
}
Loading
Loading