Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a52c0e5
fix: wip noir 1.5
sirasistant Apr 11, 2023
bd329c0
rand_idx hack to prevent repreated rand oracle removal
joss-aztec Apr 11, 2023
c490e91
fix: various fixes for noir 1.5
sirasistant Apr 12, 2023
2ff1b4b
fix: readded change note
sirasistant Apr 12, 2023
6f4504d
wip trying to fix transfer
sirasistant Apr 12, 2023
500ae72
refactor(noir): refactored noir-aztec3
sirasistant Apr 12, 2023
c5b5cd5
chore: udpate bytecode of constructor and mint
sirasistant Apr 12, 2023
ab67b33
fix: work around bug by passing root as input
sirasistant Apr 12, 2023
9ece8a4
Merge branch 'master' into arv/noir_milestone_1_5
sirasistant Apr 12, 2023
4474525
Merge branch 'master' into arv/noir_milestone_1_5
sirasistant Apr 12, 2023
28351e4
refactor: remove debug stuffs
sirasistant Apr 12, 2023
9bfe70a
feat: wip support for dummy notes
sirasistant Apr 12, 2023
62c0edb
fix(noir): worked around a weird transfer bug
sirasistant Apr 13, 2023
566650e
Merge branch 'master' into arv/noir_milestone_1_5
sirasistant Apr 13, 2023
ea1d944
chore: switch to brillig oracles
joss-aztec Apr 13, 2023
922c348
refactor(sim): recurse by creating nested classes
sirasistant Apr 14, 2023
9d793ae
refactor(noir): is_dummy and dummy notes creation
sirasistant Apr 14, 2023
16e5e25
chore(noir): update bytecode
sirasistant Apr 14, 2023
c7de493
log: added a log in execution
sirasistant Apr 14, 2023
66697f8
Revert "chore: switch to brillig oracles"
sirasistant Apr 14, 2023
a627535
Merge branch 'master' into arv/noir_milestone_1_5
sirasistant Apr 14, 2023
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
1 change: 1 addition & 0 deletions yarn-project/acir-simulator/src/acvm/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export function writeInputs(

toACVMField(oldRoots.contractTreeRoot),
toACVMField(oldRoots.nullifierTreeRoot),
toACVMField(oldRoots.privateDataTreeRoot),
];

return fields.reduce((witness, field, index) => {
Expand Down
109 changes: 63 additions & 46 deletions yarn-project/acir-simulator/src/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@ import {
writeInputs,
} from './acvm/index.js';
import { AztecAddress, EthAddress, Fr } from '@aztec/foundation';
import { CallContext, OldTreeRoots, TxRequest, PrivateCallStackItem, FunctionData } from '@aztec/circuits.js';
import {
CallContext,
OldTreeRoots,
TxRequest,
PrivateCallStackItem,
FunctionData,
PRIVATE_DATA_TREE_HEIGHT,
} from '@aztec/circuits.js';
import { DBOracle } from './db_oracle.js';
import { extractPublicInputs, frToAztecAddress, frToSelector } from './acvm/deserialize.js';
import { FunctionAbi } from '@aztec/noir-contracts';
import { DUMMY_NOTE_LENGTH } from './simulator.js';
import { createDebugLogger } from '@aztec/foundation/log';

interface NewNoteData {
preimage: Fr[];
Expand Down Expand Up @@ -46,53 +55,39 @@ export interface ExecutionResult {

export class Execution {
constructor(
// Global to the tx
private db: DBOracle,
private request: TxRequest,
private entryPointABI: FunctionAbi,
private contractAddress: AztecAddress,
private portalContractAddress: EthAddress,
private oldRoots: OldTreeRoots,
) {}
// Concrete to this execution
private abi: FunctionAbi,
private contractAddress: AztecAddress,
private functionData: FunctionData,
private args: Fr[],
private callContext: CallContext,

public run(): Promise<ExecutionResult> {
const callContext = new CallContext(
this.request.from,
this.contractAddress,
this.portalContractAddress,
false,
false,
this.request.functionData.isConstructor,
);
private log = createDebugLogger('aztec:simulator:execution'),
) {}

return this.runExternalFunction(
this.entryPointABI,
this.contractAddress,
this.request.functionData,
this.request.args,
callContext,
public async run(): Promise<ExecutionResult> {
this.log(
`Executing external function ${this.contractAddress.toShortString()}:${this.functionData.functionSelector.toString(
'hex',
)}`,
);
}

// Separate function so we can recurse in the future
private async runExternalFunction(
abi: FunctionAbi,
contractAddress: AztecAddress,
functionData: FunctionData,
args: Fr[],
callContext: CallContext,
): Promise<ExecutionResult> {
const acir = Buffer.from(abi.bytecode, 'hex');
const initialWitness = writeInputs(args, callContext, this.request.txContext, this.oldRoots);
const acir = Buffer.from(this.abi.bytecode, 'hex');
const initialWitness = writeInputs(this.args, this.callContext, this.request.txContext, this.oldRoots);
const newNotePreimages: NewNoteData[] = [];
const newNullifiers: NewNullifierData[] = [];
const nestedExecutionContexts: ExecutionResult[] = [];

const { partialWitness } = await acvm(acir, initialWitness, {
getSecretKey: ([address]: ACVMField[]) => {
return this.getSecretKey(contractAddress, address);
return this.getSecretKey(this.contractAddress, address);
},
getNotes2: async ([, storageSlot]: ACVMField[]) => {
return await this.getNotes(contractAddress, storageSlot, 2);
return await this.getNotes(this.contractAddress, storageSlot, 2);
},
getRandomField: () => Promise.resolve([toACVMField(Fr.random())]),
notifyCreatedNote: ([storageSlot, ownerX, ownerY, ...acvmPreimage]: ACVMField[]) => {
Expand All @@ -119,7 +114,7 @@ export class Execution {
frToAztecAddress(fromACVMField(acvmContractAddress)),
frToSelector(fromACVMField(acvmFunctionSelector)),
acvmArgs.map(f => fromACVMField(f)),
callContext,
this.callContext,
);

nestedExecutionContexts.push(childExecutionResult);
Expand All @@ -130,7 +125,7 @@ export class Execution {

const publicInputs = extractPublicInputs(partialWitness, acir);

const callStackItem = new PrivateCallStackItem(contractAddress, functionData, publicInputs);
const callStackItem = new PrivateCallStackItem(this.contractAddress, this.functionData, publicInputs);

return {
acir,
Expand All @@ -140,17 +135,28 @@ export class Execution {
newNotes: newNotePreimages,
nullifiedNotes: newNullifiers,
},
vk: Buffer.from(abi.verificationKey!, 'hex'),
vk: Buffer.from(this.abi.verificationKey!, 'hex'),
nestedExecutions: nestedExecutionContexts,
};
}

private async getNotes(contractAddress: AztecAddress, storageSlot: ACVMField, count: number) {
const notes = await this.db.getNotes(contractAddress, fromACVMField(storageSlot), count);
const mapped = notes.flatMap(noteGetData =>
toAcvmNoteLoadOracleInputs(noteGetData, this.oldRoots.privateDataTreeRoot),
);
return mapped;
const dummyCount = Math.max(0, count - notes.length);
const dummyNotes = Array.from({ length: dummyCount }, () => this.createDummyNote());

return notes
.concat(dummyNotes)
.flatMap(noteGetData => toAcvmNoteLoadOracleInputs(noteGetData, this.oldRoots.privateDataTreeRoot));
}

// TODO this should use an unconstrained fn in the future
private createDummyNote() {
return {
preimage: Array(DUMMY_NOTE_LENGTH).fill(new Fr(0n)),
siblingPath: new Array(PRIVATE_DATA_TREE_HEIGHT).fill(new Fr(0n)),
index: 0,
};
}

private async getSecretKey(contractAddress: AztecAddress, address: ACVMField) {
Expand All @@ -161,21 +167,32 @@ export class Execution {
private async privateFunctionCall(
targetContractAddress: AztecAddress,
targetFunctionSelector: Buffer,
args: Fr[],
targetArgs: Fr[],
callerContext: CallContext,
) {
const abi = await this.db.getFunctionABI(targetContractAddress, targetFunctionSelector);
const portalContractAddress = await this.db.getPortalContractAddress(targetContractAddress);
const functionData = new FunctionData(targetFunctionSelector, true, false);
const targetAbi = await this.db.getFunctionABI(targetContractAddress, targetFunctionSelector);
const targetPortalContractAddress = await this.db.getPortalContractAddress(targetContractAddress);
const targetFunctionData = new FunctionData(targetFunctionSelector, true, false);
const derivedCallContext = this.deriveCallContext(
callerContext,
targetContractAddress,
portalContractAddress,
targetPortalContractAddress,
false,
false,
);

return this.runExternalFunction(abi, targetContractAddress, functionData, args, derivedCallContext);
const nestedExecution = new Execution(
this.db,
this.request,
this.oldRoots,
targetAbi,
targetContractAddress,
targetFunctionData,
targetArgs,
derivedCallContext,
);

return nestedExecution.run();
}

private deriveCallContext(
Expand Down
98 changes: 84 additions & 14 deletions yarn-project/acir-simulator/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FunctionData,
NEW_COMMITMENTS_LENGTH,
OldTreeRoots,
PRIVATE_DATA_TREE_HEIGHT,
TxContext,
TxRequest,
} from '@aztec/circuits.js';
Expand All @@ -12,7 +13,7 @@ import { Grumpkin, pedersenCompressInputs } from '@aztec/barretenberg.js/crypto'
import { FunctionAbi } from '@aztec/noir-contracts';
import { TestContractAbi, ZkTokenContractAbi } from '@aztec/noir-contracts/examples';
import { DBOracle } from './db_oracle.js';
import { AcirSimulator, MAPPING_SLOT_PEDERSEN_CONSTANT } from './simulator.js';
import { AcirSimulator, DUMMY_NOTE_LENGTH, MAPPING_SLOT_PEDERSEN_CONSTANT } from './simulator.js';
import { jest } from '@jest/globals';
import { toBigIntBE } from '@aztec/foundation';
import { BarretenbergWasm } from '@aztec/barretenberg.js/wasm';
Expand Down Expand Up @@ -74,7 +75,6 @@ describe('ACIR simulator', () => {

describe('token contract', () => {
let currentNonce = 0n;
const SIBLING_PATH_SIZE = 5;

const contractDeploymentData = new ContractDeploymentData(Fr.ZERO, Fr.ZERO, Fr.ZERO, EthAddress.ZERO);
const txContext = new TxContext(false, false, false, contractDeploymentData);
Expand All @@ -94,14 +94,10 @@ describe('ACIR simulator', () => {
let recipient: NoirPoint;

function buildNote(amount: bigint, owner: NoirPoint, isDummy = false) {
return [
new Fr(amount),
new Fr(owner.x),
new Fr(owner.y),
new Fr(4n),
new Fr(currentNonce++),
new Fr(isDummy ? 1n : 0n),
];
if (isDummy) {
return Array(DUMMY_NOTE_LENGTH).fill(new Fr(0n));
}
return [new Fr(1n), new Fr(currentNonce++), new Fr(owner.x), new Fr(owner.y), new Fr(4n), new Fr(amount)];
}

function toPublicKey(privateKey: Buffer, grumpkin: Grumpkin): NoirPoint {
Expand Down Expand Up @@ -175,15 +171,15 @@ describe('ACIR simulator', () => {
expect(commitment).toEqual(Fr.fromBuffer(acirSimulator.computeNoteHash(newNote.preimage, bbWasm)));
});

it.skip('should run the transfer function', async () => {
it('should run the transfer function', async () => {
const db = levelup(createMemDown());
const pedersen = new Pedersen(bbWasm);

const contractAddress = AztecAddress.random();
const amountToTransfer = 100n;
const abi = ZkTokenContractAbi.functions.find(f => f.name === 'transfer') as unknown as FunctionAbi;

const tree = await StandardMerkleTree.new(db, pedersen, 'privateData', SIBLING_PATH_SIZE);
const tree = await StandardMerkleTree.new(db, pedersen, 'privateData', PRIVATE_DATA_TREE_HEIGHT);
const preimages = [buildNote(60n, owner), buildNote(80n, owner)];
// TODO for this we need that noir siloes the commitment the same way as the kernel does, to do merkle membership
await tree.appendLeaves(preimages.map(preimage => acirSimulator.computeNoteHash(preimage, bbWasm)));
Expand Down Expand Up @@ -214,7 +210,81 @@ describe('ACIR simulator', () => {

const result = await acirSimulator.run(txRequest, abi, AztecAddress.random(), EthAddress.ZERO, oldRoots);

console.log(result);
});
// The two notes were nullified
const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toHaveLength(2);

expect(newNullifiers).toEqual(
preimages.map(preimage => Fr.fromBuffer(acirSimulator.computeNullifier(preimage, ownerPk, bbWasm))),
);

expect(result.preimages.newNotes).toHaveLength(2);
const [recipientNote, changeNote] = result.preimages.newNotes;
expect(recipientNote.storageSlot).toEqual(computeSlot(new Fr(1n), recipient, bbWasm));

const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO));

expect(newCommitments).toHaveLength(2);

const [recipientNoteCommitment, changeNoteCommitment] = newCommitments;
expect(recipientNoteCommitment).toEqual(
Fr.fromBuffer(acirSimulator.computeNoteHash(recipientNote.preimage, bbWasm)),
);
expect(changeNoteCommitment).toEqual(Fr.fromBuffer(acirSimulator.computeNoteHash(changeNote.preimage, bbWasm)));

expect(recipientNote.preimage[5]).toEqual(new Fr(amountToTransfer));
expect(changeNote.preimage[5]).toEqual(new Fr(40n));
}, 30_000);

it('should be able to transfer with dummy notes', async () => {
const db = levelup(createMemDown());
const pedersen = new Pedersen(bbWasm);

const contractAddress = AztecAddress.random();
const amountToTransfer = 100n;
const balance = 160n;
const abi = ZkTokenContractAbi.functions.find(f => f.name === 'transfer') as unknown as FunctionAbi;

const tree = await StandardMerkleTree.new(db, pedersen, 'privateData', PRIVATE_DATA_TREE_HEIGHT);
const preimages = [buildNote(balance, owner)];
// TODO for this we need that noir siloes the commitment the same way as the kernel does, to do merkle membership
await tree.appendLeaves(preimages.map(preimage => acirSimulator.computeNoteHash(preimage, bbWasm)));

const oldRoots = new OldTreeRoots(Fr.fromBuffer(tree.getRoot()), new Fr(0n), new Fr(0n), new Fr(0n));

oracle.getNotes.mockImplementation(() => {
return Promise.all(
preimages.map(async (preimage, index) => ({
preimage,
siblingPath: (await tree.getSiblingPath(BigInt(index))).data.map(buf => Fr.fromBuffer(buf)),
index,
})),
);
});

oracle.getSecretKey.mockReturnValue(Promise.resolve(ownerPk));

const txRequest = new TxRequest(
AztecAddress.random(),
contractAddress,
new FunctionData(Buffer.alloc(4), true, true),
encodeArguments(abi, [amountToTransfer, owner, recipient]),
Fr.random(),
txContext,
new Fr(0n),
);

const result = await acirSimulator.run(txRequest, abi, AztecAddress.random(), EthAddress.ZERO, oldRoots);

const newNullifiers = result.callStackItem.publicInputs.newNullifiers.filter(field => !field.equals(Fr.ZERO));
expect(newNullifiers).toHaveLength(2);

expect(newNullifiers[0]).toEqual(Fr.fromBuffer(acirSimulator.computeNullifier(preimages[0], ownerPk, bbWasm)));

expect(result.preimages.newNotes).toHaveLength(2);
const [recipientNote, changeNote] = result.preimages.newNotes;
expect(recipientNote.preimage[5]).toEqual(new Fr(amountToTransfer));
expect(changeNote.preimage[5]).toEqual(new Fr(balance - amountToTransfer));
}, 30_000);
});
});
25 changes: 22 additions & 3 deletions yarn-project/acir-simulator/src/simulator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AztecAddress, EthAddress, Fr } from '@aztec/foundation';
import { OldTreeRoots, TxRequest } from '@aztec/circuits.js';
import { CallContext, OldTreeRoots, TxRequest } from '@aztec/circuits.js';
import { FunctionAbi } from '@aztec/noir-contracts';
import { DBOracle } from './db_oracle.js';
import { Execution, ExecutionResult } from './execution.js';
Expand All @@ -9,7 +9,8 @@ import { pedersenCompressInputs } from '@aztec/barretenberg.js/crypto';
export const NOTE_PEDERSEN_CONSTANT = new Fr(2n);
export const MAPPING_SLOT_PEDERSEN_CONSTANT = new Fr(4n);
export const NULLIFIER_PEDERSEN_CONSTANT = new Fr(5n);

// To be extracted from the specific contract
export const DUMMY_NOTE_LENGTH = 6;
export class AcirSimulator {
constructor(private db: DBOracle) {}

Expand All @@ -20,7 +21,25 @@ export class AcirSimulator {
portalContractAddress: EthAddress,
oldRoots: OldTreeRoots,
): Promise<ExecutionResult> {
const execution = new Execution(this.db, request, entryPointABI, contractAddress, portalContractAddress, oldRoots);
const callContext = new CallContext(
request.from,
contractAddress,
portalContractAddress,
false,
false,
request.functionData.isConstructor,
);

const execution = new Execution(
this.db,
request,
oldRoots,
entryPointABI,
contractAddress,
request.functionData,
request.args,
callContext,
);

return execution.run();
}
Expand Down
Loading