Skip to content

Commit

Permalink
feat: add inclusion check l1->l2 (AztecProtocol#4141)
Browse files Browse the repository at this point in the history
Fixes AztecProtocol#1383
- Adding more tests
- Adding assertion strings 
- Remove `root` from oracle response as it is not needed
  • Loading branch information
LHerskind authored Jan 24, 2024
1 parent 9342048 commit bef65c3
Show file tree
Hide file tree
Showing 16 changed files with 502 additions and 151 deletions.
2 changes: 1 addition & 1 deletion l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ library Constants {
uint256 internal constant ARGS_HASH_CHUNK_LENGTH = 32;
uint256 internal constant ARGS_HASH_CHUNK_COUNT = 16;
uint256 internal constant L1_TO_L2_MESSAGE_LENGTH = 8;
uint256 internal constant L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 26;
uint256 internal constant L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 25;
uint256 internal constant MAX_NOTE_FIELDS_LENGTH = 20;
uint256 internal constant GET_NOTE_ORACLE_RETURN_LENGTH = 23;
uint256 internal constant MAX_NOTES_PER_PAGE = 10;
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/acir-simulator/src/acvm/oracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ export class Oracle {
}

async getL1ToL2Message([msgKey]: ACVMField[]): Promise<ACVMField[]> {
const { root, ...message } = await this.typedOracle.getL1ToL2Message(fromACVMField(msgKey));
return toAcvmL1ToL2MessageLoadOracleInputs(message, root);
const { ...message } = await this.typedOracle.getL1ToL2Message(fromACVMField(msgKey));
return toAcvmL1ToL2MessageLoadOracleInputs(message);
}

async getPortalContractAddress([aztecAddress]: ACVMField[]): Promise<ACVMField> {
Expand Down
14 changes: 2 additions & 12 deletions yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface NoteData {
}

/**
* The partial data for L1 to L2 Messages provided by other data sources.
* The data for L1 to L2 Messages provided by other data sources.
*/
export interface MessageLoadOracleInputs {
/**
Expand All @@ -66,16 +66,6 @@ export interface MessageLoadOracleInputs {
index: bigint;
}

/**
* The data required by Aztec.nr to validate L1 to L2 Messages.
*/
export interface L1ToL2MessageOracleReturnData extends MessageLoadOracleInputs {
/**
* The current root of the l1 to l2 message tree.
*/
root: Fr;
}

/**
* Oracle with typed parameters and typed return values.
* Methods that require read and/or write will have to be implemented based on the context (public, private, or view)
Expand Down Expand Up @@ -167,7 +157,7 @@ export abstract class TypedOracle {
throw new Error('Not available.');
}

getL1ToL2Message(_msgKey: Fr): Promise<L1ToL2MessageOracleReturnData> {
getL1ToL2Message(_msgKey: Fr): Promise<MessageLoadOracleInputs> {
throw new Error('Not available.');
}

Expand Down
7 changes: 1 addition & 6 deletions yarn-project/acir-simulator/src/acvm/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,18 +199,13 @@ export function toAcvmEnqueuePublicFunctionResult(item: PublicCallRequest): ACVM
/**
* Converts the result of loading messages to ACVM fields.
* @param messageLoadOracleInputs - The result of loading messages to convert.
* @param l1ToL2MessageTreeRoot - The L1 to L2 message tree root
* @returns The Message Oracle Fields.
*/
export function toAcvmL1ToL2MessageLoadOracleInputs(
messageLoadOracleInputs: MessageLoadOracleInputs,
l1ToL2MessageTreeRoot: Fr,
): ACVMField[] {
export function toAcvmL1ToL2MessageLoadOracleInputs(messageLoadOracleInputs: MessageLoadOracleInputs): ACVMField[] {
return [
...messageLoadOracleInputs.message.map(f => toACVMField(f)),
toACVMField(messageLoadOracleInputs.index),
...messageLoadOracleInputs.siblingPath.map(f => toACVMField(f)),
toACVMField(l1ToL2MessageTreeRoot),
];
}

Expand Down
274 changes: 235 additions & 39 deletions yarn-project/acir-simulator/src/client/private_execution.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Note, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types';
import { L1ToL2Message, Note, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types';
import {
BlockHeader,
CallContext,
Expand Down Expand Up @@ -86,7 +86,7 @@ describe('Private Execution test suite', () => {
l1ToL2Messages: L1_TO_L2_MSG_TREE_HEIGHT,
};

const trees: { [name: keyof typeof treeHeights]: AppendOnlyTree } = {};
let trees: { [name: keyof typeof treeHeights]: AppendOnlyTree } = {};
const txContextFields: FieldsOf<TxContext> = {
isContractDeploymentTx: false,
isFeePaymentTx: false,
Expand Down Expand Up @@ -143,7 +143,7 @@ describe('Private Execution test suite', () => {
await trees[name].appendLeaves(leaves.map(l => l.toBuffer()));

// Update root.
const newRoot = trees[name].getRoot(false);
const newRoot = trees[name].getRoot(true);
const prevRoots = blockHeader.toBuffer();
const rootIndex = name === 'noteHash' ? 0 : 32 * 3;
const newRoots = Buffer.concat([prevRoots.subarray(0, rootIndex), newRoot, prevRoots.subarray(rootIndex + 32)]);
Expand Down Expand Up @@ -177,6 +177,7 @@ describe('Private Execution test suite', () => {
});

beforeEach(() => {
trees = {};
oracle = mock<DBOracle>();
oracle.getNullifierKeyPair.mockImplementation((accountAddress: AztecAddress, contractAddress: AztecAddress) => {
if (accountAddress.equals(ownerCompleteAddress.address)) {
Expand Down Expand Up @@ -476,53 +477,248 @@ describe('Private Execution test suite', () => {
});
});

it('Should be able to consume a dummy cross chain message', async () => {
const bridgedAmount = 100n;
describe('L1 to L2', () => {
const artifact = getFunctionArtifact(TestContractArtifact, 'consume_mint_private_message');
const canceller = EthAddress.random();
let bridgedAmount = 100n;

const secretForL1ToL2MessageConsumption = new Fr(1n);
const secretHashForRedeemingNotes = new Fr(2n);
const canceller = EthAddress.random();
const preimage = buildL1ToL2Message(
getFunctionSelector('mint_private(bytes32,uint256,address)').substring(2),
[secretHashForRedeemingNotes, new Fr(bridgedAmount), canceller.toField()],
contractAddress,
secretForL1ToL2MessageConsumption,
);
let secretForL1ToL2MessageConsumption = new Fr(1n);

// stub message key
const messageKey = Fr.random();
const tree = await insertLeaves([messageKey], 'l1ToL2Messages');
let crossChainMsgRecipient: AztecAddress | undefined;
let crossChainMsgSender: EthAddress | undefined;
let messageKey: Fr | undefined;

oracle.getL1ToL2Message.mockImplementation(async () => {
return Promise.resolve({
message: preimage.toFieldArray(),
index: 0n,
siblingPath: (await tree.getSiblingPath(0n, false)).toFieldArray(),
let preimage: L1ToL2Message;

let args: Fr[];

beforeEach(() => {
bridgedAmount = 100n;
secretForL1ToL2MessageConsumption = new Fr(2n);

crossChainMsgRecipient = undefined;
crossChainMsgSender = undefined;
messageKey = undefined;
});

const computePreimage = () =>
buildL1ToL2Message(
getFunctionSelector('mint_private(bytes32,uint256,address)').substring(2),
[secretHashForRedeemingNotes, new Fr(bridgedAmount), canceller.toField()],
crossChainMsgRecipient ?? contractAddress,
secretForL1ToL2MessageConsumption,
);

const computeArgs = () =>
encodeArguments(artifact, [
secretHashForRedeemingNotes,
bridgedAmount,
canceller.toField(),
messageKey ?? preimage.hash(),
secretForL1ToL2MessageConsumption,
]);

const mockOracles = async () => {
const tree = await insertLeaves([messageKey ?? preimage.hash()], 'l1ToL2Messages');
oracle.getL1ToL2Message.mockImplementation(async () => {
return Promise.resolve({
message: preimage.toFieldArray(),
index: 0n,
siblingPath: (await tree.getSiblingPath(0n, false)).toFieldArray(),
});
});
};

it('Should be able to consume a dummy cross chain message', async () => {
preimage = computePreimage();

args = computeArgs();

await mockOracles();
// Update state
oracle.getBlockHeader.mockResolvedValue(blockHeader);

const result = await runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(1n) },
});

// Check a nullifier has been inserted
const newNullifiers = sideEffectArrayToValueArray(
nonEmptySideEffects(result.callStackItem.publicInputs.newNullifiers),
);

expect(newNullifiers).toHaveLength(1);
});

const args = [
secretHashForRedeemingNotes,
bridgedAmount,
canceller.toField(),
messageKey,
secretForL1ToL2MessageConsumption,
];
const result = await runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(1n) },
it('Message not matching requested key', async () => {
messageKey = Fr.random();

preimage = computePreimage();

args = computeArgs();

await mockOracles();
// Update state
oracle.getBlockHeader.mockResolvedValue(blockHeader);

await expect(
runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(1n) },
}),
).rejects.toThrowError('Message not matching requested key');
});

// Check a nullifier has been inserted
const newNullifiers = sideEffectArrayToValueArray(
nonEmptySideEffects(result.callStackItem.publicInputs.newNullifiers),
);
it('Invalid membership proof', async () => {
preimage = computePreimage();

expect(newNullifiers).toHaveLength(1);
args = computeArgs();

await mockOracles();

await expect(
runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(1n) },
}),
).rejects.toThrowError('Message not in state');
});

it('Invalid recipient', async () => {
crossChainMsgRecipient = AztecAddress.random();

preimage = computePreimage();

args = computeArgs();

await mockOracles();
// Update state
oracle.getBlockHeader.mockResolvedValue(blockHeader);

await expect(
runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(1n) },
}),
).rejects.toThrowError('Invalid recipient');
});

it('Invalid sender', async () => {
crossChainMsgSender = EthAddress.random();
preimage = computePreimage();

args = computeArgs();

await mockOracles();
// Update state
oracle.getBlockHeader.mockResolvedValue(blockHeader);

await expect(
runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(1n) },
}),
).rejects.toThrowError('Invalid sender');
});

it('Invalid chainid', async () => {
preimage = computePreimage();

args = computeArgs();

await mockOracles();
// Update state
oracle.getBlockHeader.mockResolvedValue(blockHeader);

await expect(
runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(2n) },
}),
).rejects.toThrowError('Invalid Chainid');
});

it('Invalid version', async () => {
preimage = computePreimage();

args = computeArgs();

await mockOracles();
// Update state
oracle.getBlockHeader.mockResolvedValue(blockHeader);

await expect(
runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(2n), chainId: new Fr(1n) },
}),
).rejects.toThrowError('Invalid Version');
});

it('Invalid content', async () => {
preimage = computePreimage();

bridgedAmount = bridgedAmount + 1n; // Invalid amount
args = computeArgs();

await mockOracles();
// Update state
oracle.getBlockHeader.mockResolvedValue(blockHeader);

await expect(
runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(1n) },
}),
).rejects.toThrowError('Invalid Content');
});

it('Invalid Secret', async () => {
preimage = computePreimage();

secretForL1ToL2MessageConsumption = Fr.random();
args = computeArgs();

await mockOracles();
// Update state
oracle.getBlockHeader.mockResolvedValue(blockHeader);

await expect(
runSimulator({
contractAddress,
artifact,
args,
portalContractAddress: crossChainMsgSender ?? preimage.sender.sender,
txContext: { version: new Fr(1n), chainId: new Fr(1n) },
}),
).rejects.toThrowError('Invalid message secret');
});
});

it('Should be able to consume a dummy public to private message', async () => {
Expand Down
Loading

0 comments on commit bef65c3

Please sign in to comment.