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(messaging): public cross chain message e2e #841

Merged
merged 14 commits into from
Jun 16, 2023
13 changes: 13 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,17 @@ jobs:
name: "Test"
command: cond_spot_run_tests end-to-end e2e_cross_chain_messaging.test.ts

e2e-public-cross-chain-messaging:
docker:
- image: aztecprotocol/alpine-build-image
resource_class: small
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_spot_run_tests end-to-end e2e_public_cross_chain_messaging.test.ts

e2e-public-to-private-messaging:
docker:
- image: aztecprotocol/alpine-build-image
Expand Down Expand Up @@ -621,6 +632,7 @@ workflows:
- e2e-nested-contract: *e2e_test
- e2e-public-token-contract: *e2e_test
- e2e-cross-chain-messaging: *e2e_test
- e2e-public-cross-chain-messaging: *e2e_test
- e2e-public-to-private-messaging: *e2e_test
- e2e-account-contract: *e2e_test
- integration-l1-publisher: *e2e_test
Expand All @@ -635,6 +647,7 @@ workflows:
- e2e-nested-contract
- e2e-public-token-contract
- e2e-cross-chain-messaging
- e2e-public-cross-chain-messaging
- e2e-public-to-private-messaging
- e2e-account-contract
- integration-l1-publisher
Expand Down
1 change: 1 addition & 0 deletions yarn-project/acir-simulator/src/acvm/acvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ACIRCallback {
storageWrite(params: ACVMField[]): Promise<[ACVMField]>;
createCommitment(params: ACVMField[]): Promise<[ACVMField]>;
createL2ToL1Message(params: ACVMField[]): Promise<[ACVMField]>;
createNullifier(params: ACVMField[]): Promise<[ACVMField]>;
viewNotesPage(params: ACVMField[]): Promise<ACVMField[]>;
getCommitment(params: ACVMField[]): Promise<ACVMField[]>;
getL1ToL2Message(params: ACVMField[]): Promise<ACVMField[]>;
Expand Down
73 changes: 26 additions & 47 deletions yarn-project/acir-simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ import {
import { computeSecretMessageHash, siloCommitment } from '@aztec/circuits.js/abis';
import { Grumpkin, pedersenCompressInputs } from '@aztec/circuits.js/barretenberg';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer';
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
import { padArrayEnd } from '@aztec/foundation/collection';
import { sha256 } from '@aztec/foundation/crypto';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr, Point } from '@aztec/foundation/fields';
import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
Expand All @@ -29,25 +28,26 @@ import {
TestContractAbi,
ZkTokenContractAbi,
} from '@aztec/noir-contracts/examples';
import { L1Actor, L1ToL2Message, L2Actor, TxExecutionRequest } from '@aztec/types';
import { TxExecutionRequest } from '@aztec/types';
import { mock } from 'jest-mock-extended';
import { default as levelup } from 'levelup';
import { default as memdown, type MemDown } from 'memdown';
import { encodeArguments } from '../abi_coder/index.js';
import { NoirPoint, computeSlotForMapping, toPublicKey } from '../utils.js';
import { DBOracle } from './db_oracle.js';
import { AcirSimulator } from './simulator.js';
import { buildL1ToL2Message } from '../test/utils.js';

const createMemDown = () => (memdown as any)() as MemDown<any, any>;

describe('Private Execution test suite', () => {
let bbWasm: CircuitsWasm;
let circuitsWasm: CircuitsWasm;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed this as it put me off when bbWasm was referring to CircuitsWasm

let oracle: ReturnType<typeof mock<DBOracle>>;
let acirSimulator: AcirSimulator;
let logger: DebugLogger;

beforeAll(async () => {
bbWasm = await CircuitsWasm.get();
circuitsWasm = await CircuitsWasm.get();
logger = createDebugLogger('aztec:test:private_execution');
});

Expand Down Expand Up @@ -102,7 +102,7 @@ describe('Private Execution test suite', () => {
ownerPk = Buffer.from('5e30a2f886b4b6a11aea03bf4910fbd5b24e61aa27ea4d05c393b3ab592a8d33', 'hex');
recipientPk = Buffer.from('0c9ed344548e8f9ba8aa3c9f8651eaa2853130f6c1e9c050ccf198f7ea18a7ec', 'hex');

const grumpkin = new Grumpkin(bbWasm);
const grumpkin = new Grumpkin(circuitsWasm);
owner = toPublicKey(ownerPk, grumpkin);
recipient = toPublicKey(recipientPk, grumpkin);
});
Expand All @@ -125,13 +125,13 @@ describe('Private Execution test suite', () => {

expect(result.preimages.newNotes).toHaveLength(1);
const newNote = result.preimages.newNotes[0];
expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner, bbWasm));
expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner, circuitsWasm));

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

const [commitment] = newCommitments;
expect(commitment).toEqual(Fr.fromBuffer(acirSimulator.computeNoteHash(newNote.preimage, bbWasm)));
expect(commitment).toEqual(Fr.fromBuffer(acirSimulator.computeNoteHash(newNote.preimage, circuitsWasm)));
}, 30_000);

it('should run the mint function', async () => {
Expand All @@ -152,18 +152,18 @@ describe('Private Execution test suite', () => {

expect(result.preimages.newNotes).toHaveLength(1);
const newNote = result.preimages.newNotes[0];
expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner, bbWasm));
expect(newNote.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner, circuitsWasm));

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

const [commitment] = newCommitments;
expect(commitment).toEqual(Fr.fromBuffer(acirSimulator.computeNoteHash(newNote.preimage, bbWasm)));
expect(commitment).toEqual(Fr.fromBuffer(acirSimulator.computeNoteHash(newNote.preimage, circuitsWasm)));
});

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

const contractAddress = AztecAddress.random();
const amountToTransfer = 100n;
Expand All @@ -172,7 +172,7 @@ describe('Private Execution test suite', () => {
const tree: AppendOnlyTree = await newTree(StandardTree, 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)));
await tree.appendLeaves(preimages.map(preimage => acirSimulator.computeNoteHash(preimage, circuitsWasm)));

const historicRoots = new PrivateHistoricTreeRoots(
Fr.fromBuffer(tree.getRoot(false)),
Expand Down Expand Up @@ -213,30 +213,32 @@ describe('Private Execution test suite', () => {
expect(newNullifiers).toHaveLength(2);

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

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

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)),
Fr.fromBuffer(acirSimulator.computeNoteHash(recipientNote.preimage, circuitsWasm)),
);
expect(changeNoteCommitment).toEqual(
Fr.fromBuffer(acirSimulator.computeNoteHash(changeNote.preimage, circuitsWasm)),
);
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 pedersen = new Pedersen(circuitsWasm);

const contractAddress = AztecAddress.random();
const amountToTransfer = 100n;
Expand All @@ -246,7 +248,7 @@ describe('Private Execution test suite', () => {
const tree: AppendOnlyTree = await newTree(StandardTree, 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)));
await tree.appendLeaves(preimages.map(preimage => acirSimulator.computeNoteHash(preimage, circuitsWasm)));

const historicRoots = new PrivateHistoricTreeRoots(
Fr.fromBuffer(tree.getRoot(false)),
Expand Down Expand Up @@ -285,7 +287,9 @@ describe('Private Execution test suite', () => {
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(newNullifiers[0]).toEqual(
Fr.fromBuffer(acirSimulator.computeNullifier(preimages[0], ownerPk, circuitsWasm)),
);

expect(result.preimages.newNotes).toHaveLength(2);
const [recipientNote, changeNote] = result.preimages.newNotes;
Expand Down Expand Up @@ -361,41 +365,16 @@ describe('Private Execution test suite', () => {
let recipientPk: Buffer;
let recipient: NoirPoint;

const buildL1ToL2Message = async (contentPreimage: Fr[], targetContract: AztecAddress, secret: Fr) => {
const wasm = await CircuitsWasm.get();

// Function selector: 0xeeb73071 keccak256('mint(uint256,bytes32,address)')
const contentBuf = Buffer.concat([
Buffer.from([0xee, 0xb7, 0x30, 0x71]),
...contentPreimage.map(field => field.toBuffer()),
]);
const temp = toBigIntBE(sha256(contentBuf));
const content = Fr.fromBuffer(toBufferBE(temp % Fr.MODULUS, 32));

const secretHash = computeSecretMessageHash(wasm, secret);

// Eventually the kernel will need to prove the kernel portal pair exists within the contract tree,
// EthAddress.random() will need to be replaced when this happens
return new L1ToL2Message(
new L1Actor(EthAddress.random(), 1),
new L2Actor(targetContract, 1),
content,
secretHash,
0,
0,
);
};

beforeAll(() => {
recipientPk = Buffer.from('0c9ed344548e8f9ba8aa3c9f8651eaa2853130f6c1e9c050ccf198f7ea18a7ec', 'hex');

const grumpkin = new Grumpkin(bbWasm);
const grumpkin = new Grumpkin(circuitsWasm);
recipient = toPublicKey(recipientPk, grumpkin);
});

it('Should be able to consume a dummy cross chain message', async () => {
const db = levelup(createMemDown());
const pedersen = new Pedersen(bbWasm);
const pedersen = new Pedersen(circuitsWasm);

const contractAddress = AztecAddress.random();
const bridgedAmount = 100n;
Expand Down Expand Up @@ -452,7 +431,7 @@ describe('Private Execution test suite', () => {

it('Should be able to consume a dummy public to private message', async () => {
const db = levelup(createMemDown());
const pedersen = new Pedersen(bbWasm);
const pedersen = new Pedersen(circuitsWasm);

const contractAddress = AztecAddress.random();
const amount = 100n;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export class PrivateFunctionExecution {
storageWrite: notAvailable,
createCommitment: notAvailable,
createL2ToL1Message: notAvailable,
createNullifier: notAvailable,
callPublicFunction: notAvailable,
emitUnencryptedLog: notAvailable,
emitEncryptedLog: async ([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export class UnconstrainedFunctionExecution {
storageWrite: notAvailable,
createCommitment: notAvailable,
createL2ToL1Message: notAvailable,
createNullifier: notAvailable,
emitEncryptedLog: notAvailable,
emitUnencryptedLog: notAvailable,
});
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/acir-simulator/src/public/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface PublicExecutionResult {
newCommitments: Fr[];
/** The new l2 to l1 messages generated in this call. */
newL2ToL1Messages: Fr[];
/** The new nullifiers to be inserted into the nullifier tree. */
newNullifiers: Fr[];
/** The contract storage reads performed by the function. */
contractStorageReads: ContractStorageRead[];
/** The contract storage update requests performed by the function. */
Expand Down
13 changes: 12 additions & 1 deletion yarn-project/acir-simulator/src/public/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { CommitmentsDB, PublicContractsDB, PublicStateDB } from './db.js';
import { PublicExecution, PublicExecutionResult } from './execution.js';
import { ContractStorageActionsCollector } from './state_actions.js';
import { fieldsToFormattedStr } from '../client/debug.js';

// Copied from crate::abi at noir-contracts/src/contracts/noir-aztec3/src/abi.nr
const NOIR_MAX_RETURN_VALUES = 4;
Expand Down Expand Up @@ -56,6 +57,7 @@ export class PublicExecutor {
const storageActions = new ContractStorageActionsCollector(this.stateDb, execution.contractAddress);
const newCommitments: Fr[] = [];
const newL2ToL1Messages: Fr[] = [];
const newNullifiers: Fr[] = [];
const nestedExecutions: PublicExecutionResult[] = [];
const unencryptedLogs = new FunctionL2Logs([]);

Expand All @@ -71,8 +73,11 @@ export class PublicExecutor {
enqueuePublicFunctionCall: notAvailable,
emitEncryptedLog: notAvailable,
viewNotesPage: notAvailable,
debugLog: notAvailable,

debugLog: (fields: ACVMField[]) => {
this.log(fieldsToFormattedStr(fields));
return Promise.resolve([ZERO_ACVM_FIELD]);
},
getL1ToL2Message: async ([msgKey]: ACVMField[]) => {
const messageInputs = await this.commitmentsDb.getL1ToL2Message(fromACVMField(msgKey));
return toAcvmL1ToL2MessageLoadOracleInputs(messageInputs, this.treeRoots.l1ToL2MessagesTreeRoot);
Expand Down Expand Up @@ -108,6 +113,11 @@ export class PublicExecutor {
newL2ToL1Messages.push(fromACVMField(message));
return await Promise.resolve([ZERO_ACVM_FIELD]);
},
createNullifier: async ([nullifier]) => {
this.log('Creating nullifier: ' + nullifier.toString());
newNullifiers.push(fromACVMField(nullifier));
return await Promise.resolve([ZERO_ACVM_FIELD]);
},
callPublicFunction: async ([address, functionSelector, ...args]) => {
this.log(`Public function call: addr=${address} selector=${functionSelector} args=${args.join(',')}`);
const childExecutionResult = await this.callPublicFunction(
Expand All @@ -134,6 +144,7 @@ export class PublicExecutor {
execution,
newCommitments,
newL2ToL1Messages,
newNullifiers,
contractStorageReads,
contractStorageUpdateRequests,
returnValues,
Expand Down
Loading