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: add inclusion check l1->l2 #4141

Merged
merged 6 commits into from
Jan 24, 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
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 @@ -228,8 +228,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 @@ -34,7 +34,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 @@ -52,16 +52,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 @@ -153,7 +143,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 @@ -81,7 +81,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 @@ -138,7 +138,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 All @@ -160,6 +160,7 @@ describe('Private Execution test suite', () => {
});

beforeEach(() => {
trees = {};
oracle = mock<DBOracle>();
oracle.getSecretKey.mockImplementation((contractAddress: AztecAddress, pubKey: PublicKey) => {
if (pubKey.equals(ownerCompleteAddress.publicKey)) {
Expand Down Expand Up @@ -453,53 +454,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(),
Copy link
Contributor

Choose a reason for hiding this comment

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

nice! Think previously this was just Fr.random() right? becaue we didn't do any checks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ye, there were no real check seeing that what you were requesting was also what it responded with. So you could ask for key a and it give you b and it did not check it.

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