Skip to content

Commit

Permalink
feat!: Unique L1 to L2 messages (#9492)
Browse files Browse the repository at this point in the history
Ensures uniqueness of L1 to L2 messages by mixing in the leaf index into
the content hash. This changes how message hashes are computed. And
since messages are now unique, we can also change how their nullifiers
are computed and _remove_ the message index from them.

**Breaking change:** The structure of the archiver db is changed, so any
existing archiver needs to be wiped out and re-synced.

**Breaking change:** Consuming an L1 to L2 message from private-land now
requires the message index. Good thing is this is how it worked some
time ago and the docs were never updated, so we don't need to change the
docs now!


![image](https://github.com/user-attachments/assets/cc2be4df-11e1-4743-88fb-085c33a50997)

Fixes #9450
  • Loading branch information
spalladino authored Oct 30, 2024
1 parent d9de430 commit 4e5ae95
Show file tree
Hide file tree
Showing 75 changed files with 1,142 additions and 1,077 deletions.
10 changes: 5 additions & 5 deletions l1-contracts/src/core/FeeJuicePortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ contract FeeJuicePortal is IFeeJuicePortal {
* @param _to - The aztec address of the recipient
* @param _amount - The amount to deposit
* @param _secretHash - The hash of the secret consumable message. The hash should be 254 bits (so it can fit in a Field element)
* @return - The key of the entry in the Inbox
* @return - The key of the entry in the Inbox and its leaf index
*/
function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash)
external
override(IFeeJuicePortal)
returns (bytes32)
returns (bytes32, uint256)
{
// Preamble
address rollup = canonicalRollup();
Expand All @@ -80,11 +80,11 @@ contract FeeJuicePortal is IFeeJuicePortal {
UNDERLYING.safeTransferFrom(msg.sender, address(this), _amount);

// Send message to rollup
bytes32 key = inbox.sendL2Message(actor, contentHash, _secretHash);
(bytes32 key, uint256 index) = inbox.sendL2Message(actor, contentHash, _secretHash);

emit DepositToAztecPublic(_to, _amount, _secretHash, key);
emit DepositToAztecPublic(_to, _amount, _secretHash, key, index);

return key;
return (key, index);
}

/**
Expand Down
6 changes: 4 additions & 2 deletions l1-contracts/src/core/interfaces/IFeeJuicePortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol";

interface IFeeJuicePortal {
event DepositToAztecPublic(bytes32 indexed to, uint256 amount, bytes32 secretHash, bytes32 key);
event DepositToAztecPublic(
bytes32 indexed to, uint256 amount, bytes32 secretHash, bytes32 key, uint256 index
);
event FeesDistributed(address indexed to, uint256 amount);

function initialize() external;
function distributeFees(address _to, uint256 _amount) external;
function depositToAztecPublic(bytes32 _to, uint256 _amount, bytes32 _secretHash)
external
returns (bytes32);
returns (bytes32, uint256);
function canonicalRollup() external view returns (address);

function UNDERLYING() external view returns (IERC20);
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/src/core/interfaces/messagebridge/IInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ interface IInbox {
* @param _recipient - The recipient of the message
* @param _content - The content of the message (application specific)
* @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2)
* @return The key of the message in the set
* @return The key of the message in the set and its leaf index in the tree
*/
function sendL2Message(
DataStructures.L2Actor memory _recipient,
bytes32 _content,
bytes32 _secretHash
) external returns (bytes32);
) external returns (bytes32, uint256);
// docs:end:send_l1_to_l2_message

// docs:start:consume
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/src/core/libraries/DataStructures.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ library DataStructures {
* @param recipient - The recipient of the message
* @param content - The content of the message (application specific) padded to bytes32 or hashed if larger.
* @param secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2).
* @param index - Global leaf index on the L1 to L2 messages tree.
*/
struct L1ToL2Msg {
L1Actor sender;
L2Actor recipient;
bytes32 content;
bytes32 secretHash;
uint256 index;
}
// docs:end:l1_to_l2_msg

Expand Down
4 changes: 3 additions & 1 deletion l1-contracts/src/core/libraries/crypto/Hash.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ library Hash {
*/
function sha256ToField(DataStructures.L1ToL2Msg memory _message) internal pure returns (bytes32) {
return sha256ToField(
abi.encode(_message.sender, _message.recipient, _message.content, _message.secretHash)
abi.encode(
_message.sender, _message.recipient, _message.content, _message.secretHash, _message.index
)
);
}

Expand Down
20 changes: 11 additions & 9 deletions l1-contracts/src/core/messagebridge/Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ contract Inbox is IInbox {
* @param _content - The content of the message (application specific)
* @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2)
*
* @return Hash of the sent message.
* @return Hash of the sent message and its leaf index in the tree.
*/
function sendL2Message(
DataStructures.L2Actor memory _recipient,
bytes32 _content,
bytes32 _secretHash
) external override(IInbox) returns (bytes32) {
) external override(IInbox) returns (bytes32, uint256) {
require(
uint256(_recipient.actor) <= Constants.MAX_FIELD_VALUE,
Errors.Inbox__ActorTooLarge(_recipient.actor)
Expand All @@ -81,23 +81,25 @@ contract Inbox is IInbox {
currentTree = trees[inProgress];
}

// this is the global leaf index and not index in the l2Block subtree
// such that users can simply use it and don't need access to a node if they are to consume it in public.
// trees are constant size so global index = tree number * size + subtree index
uint256 index = (inProgress - Constants.INITIAL_L2_BLOCK_NUM) * SIZE + currentTree.nextIndex;

DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({
sender: DataStructures.L1Actor(msg.sender, block.chainid),
recipient: _recipient,
content: _content,
secretHash: _secretHash
secretHash: _secretHash,
index: index
});

bytes32 leaf = message.sha256ToField();
// this is the global leaf index and not index in the l2Block subtree
// such that users can simply use it and don't need access to a node if they are to consume it in public.
// trees are constant size so global index = tree number * size + subtree index
uint256 index =
(inProgress - Constants.INITIAL_L2_BLOCK_NUM) * SIZE + currentTree.insertLeaf(leaf);
currentTree.insertLeaf(leaf);
totalMessagesInserted++;
emit MessageSent(inProgress, index, leaf);

return leaf;
return (leaf, index);
}

/**
Expand Down
9 changes: 7 additions & 2 deletions l1-contracts/src/mock/MockFeeJuicePortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ contract MockFeeJuicePortal is IFeeJuicePortal {

function distributeFees(address, uint256) external override {}

function depositToAztecPublic(bytes32, uint256, bytes32) external pure override returns (bytes32) {
return bytes32(0);
function depositToAztecPublic(bytes32, uint256, bytes32)
external
pure
override
returns (bytes32, uint256)
{
return (bytes32(0), 0);
}

function canonicalRollup() external pure override returns (address) {
Expand Down
36 changes: 24 additions & 12 deletions l1-contracts/test/Inbox.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ contract InboxTest is Test {
version: version
}),
content: 0x2000000000000000000000000000000000000000000000000000000000000000,
secretHash: 0x3000000000000000000000000000000000000000000000000000000000000000
secretHash: 0x3000000000000000000000000000000000000000000000000000000000000000,
index: 0x01
});
}

function _divideAndRoundUp(uint256 a, uint256 b) internal pure returns (uint256) {
return (a + b - 1) / b;
}

function _boundMessage(DataStructures.L1ToL2Msg memory _message)
function _boundMessage(DataStructures.L1ToL2Msg memory _message, uint256 _globalLeafIndex)
internal
view
returns (DataStructures.L1ToL2Msg memory)
Expand All @@ -61,6 +62,8 @@ contract InboxTest is Test {
_message.secretHash = bytes32(uint256(_message.secretHash) % Constants.P);
// update version
_message.recipient.version = version;
// set leaf index
_message.index = _globalLeafIndex;

return _message;
}
Expand All @@ -84,32 +87,40 @@ contract InboxTest is Test {
}

function testFuzzInsert(DataStructures.L1ToL2Msg memory _message) public checkInvariant {
DataStructures.L1ToL2Msg memory message = _boundMessage(_message);
uint256 globalLeafIndex = (FIRST_REAL_TREE_NUM - 1) * SIZE;
DataStructures.L1ToL2Msg memory message = _boundMessage(_message, globalLeafIndex);

bytes32 leaf = message.sha256ToField();
vm.expectEmit(true, true, true, true);
// event we expect
uint256 globalLeafIndex = (FIRST_REAL_TREE_NUM - 1) * SIZE;
emit IInbox.MessageSent(FIRST_REAL_TREE_NUM, globalLeafIndex, leaf);
// event we will get
bytes32 insertedLeaf =
(bytes32 insertedLeaf, uint256 insertedIndex) =
inbox.sendL2Message(message.recipient, message.content, message.secretHash);

assertEq(insertedLeaf, leaf);
assertEq(insertedIndex, globalLeafIndex);
}

function testSendDuplicateL2Messages() public checkInvariant {
DataStructures.L1ToL2Msg memory message = _fakeMessage();
bytes32 leaf1 = inbox.sendL2Message(message.recipient, message.content, message.secretHash);
bytes32 leaf2 = inbox.sendL2Message(message.recipient, message.content, message.secretHash);
bytes32 leaf3 = inbox.sendL2Message(message.recipient, message.content, message.secretHash);
(bytes32 leaf1, uint256 index1) =
inbox.sendL2Message(message.recipient, message.content, message.secretHash);
(bytes32 leaf2, uint256 index2) =
inbox.sendL2Message(message.recipient, message.content, message.secretHash);
(bytes32 leaf3, uint256 index3) =
inbox.sendL2Message(message.recipient, message.content, message.secretHash);

// Only 1 tree should be non-zero
assertEq(inbox.getNumTrees(), 1);

// All the leaves should be the same
assertEq(leaf1, leaf2);
assertEq(leaf2, leaf3);
// All the leaves should be different since the index gets mixed in
assertNotEq(leaf1, leaf2);
assertNotEq(leaf2, leaf3);

// Check indices
assertEq(index1 + 1, index2);
assertEq(index1 + 2, index3);
}

function testRevertIfActorTooLarge() public {
Expand Down Expand Up @@ -161,7 +172,8 @@ contract InboxTest is Test {

// We send the messages and then check that toConsume root did not change.
for (uint256 i = 0; i < _messages.length; i++) {
DataStructures.L1ToL2Msg memory message = _boundMessage(_messages[i]);
DataStructures.L1ToL2Msg memory message =
_boundMessage(_messages[i], inbox.getNextMessageIndex());

// We check whether a new tree is correctly initialized when the one in progress is full
uint256 numTrees = inbox.getNumTrees();
Expand Down
12 changes: 7 additions & 5 deletions l1-contracts/test/fee_portal/depositToAztecPublic.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,14 @@ contract DepositToAztecPublic is Test {
bytes32 to = bytes32(0x0);
bytes32 secretHash = bytes32(uint256(0x01));
uint256 amount = 100 ether;
uint256 expectedIndex = 2 ** Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT;

DataStructures.L1ToL2Msg memory message = DataStructures.L1ToL2Msg({
sender: DataStructures.L1Actor(address(feeJuicePortal), block.chainid),
recipient: DataStructures.L2Actor(feeJuicePortal.L2_TOKEN_ADDRESS(), 1 + numberOfRollups),
content: Hash.sha256ToField(abi.encodeWithSignature("claim(bytes32,uint256)", to, amount)),
secretHash: secretHash
secretHash: secretHash,
index: expectedIndex
});

bytes32 expectedKey = message.sha256ToField();
Expand All @@ -92,16 +94,16 @@ contract DepositToAztecPublic is Test {

Inbox inbox = Inbox(address(Rollup(address(registry.getRollup())).INBOX()));
assertEq(inbox.totalMessagesInserted(), 0);
uint256 index = 2 ** Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT;

vm.expectEmit(true, true, true, true, address(inbox));
emit IInbox.MessageSent(2, index, expectedKey);
emit IInbox.MessageSent(2, expectedIndex, expectedKey);
vm.expectEmit(true, true, true, true, address(feeJuicePortal));
emit IFeeJuicePortal.DepositToAztecPublic(to, amount, secretHash, expectedKey);
emit IFeeJuicePortal.DepositToAztecPublic(to, amount, secretHash, expectedKey, expectedIndex);

bytes32 key = feeJuicePortal.depositToAztecPublic(to, amount, secretHash);
(bytes32 key, uint256 index) = feeJuicePortal.depositToAztecPublic(to, amount, secretHash);

assertEq(inbox.totalMessagesInserted(), 1);
assertEq(key, expectedKey);
assertEq(index, expectedIndex);
}
}
16 changes: 8 additions & 8 deletions l1-contracts/test/fixtures/empty_block_1.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"l2ToL1Messages": []
},
"block": {
"archive": "0x18589e53843baf9415aa9a4943bd4d8fa19b318329604f51a4ac0b8e7eb8e155",
"blockHash": "0x2662dae080808aceafe3377c439d8bea968d5ea74c98643adf5c2ea805f72c0c",
"archive": "0x04f48997a6e0472d7919af16c50859f86c041ef9aefceba4be94657943618ac1",
"blockHash": "0x2f3394755802dfe8105c0e7a5a7733970b59d83f6b9bd6eb754317f4d6a73c0f",
"body": "0x00000000",
"txsEffectsHash": "0x00e994e16b3763fd5039413cf99c2b3c378e2bab939e7992a77bd201b28160d6",
"decodedHeader": {
Expand All @@ -21,12 +21,12 @@
},
"globalVariables": {
"blockNumber": 1,
"slotNumber": "0x0000000000000000000000000000000000000000000000000000000000000011",
"slotNumber": "0x0000000000000000000000000000000000000000000000000000000000000012",
"chainId": 31337,
"timestamp": 1728563130,
"timestamp": 1730234580,
"version": 1,
"coinbase": "0xd498444361455d5099a036e93f395a584cf085c6",
"feeRecipient": "0x07c66a9b410a7e26824d677a12d8af977c0db64e5fcdfc8ad704c71c0267d649",
"coinbase": "0x2590e3544a7e2e7d736649fefc72ea5ad1d6efc3",
"feeRecipient": "0x2146e005e28b76eedc61edeff431b412b5ece74a5818080051832d286795ce2e",
"gasFees": {
"feePerDaGas": 0,
"feePerL2Gas": 0
Expand Down Expand Up @@ -57,8 +57,8 @@
}
}
},
"header": "0x1200a06aae1368abe36530b585bd7a4d2ba4de5037b82076412691a187d7621e00000001000000000000000000000000000000000000000000000000000000000000000200e994e16b3763fd5039413cf99c2b3c378e2bab939e7992a77bd201b28160d600089a9d421a82c4a25f7acbebe69e638d5b064fa8a60e018793dcb0be53752c00f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb14f44d672eb357739e42463497f9fdac46623af863eea4d947ca00a497dcdeb3000000100b59baa35b9dc267744f0ccb4e3b0255c1fc512460d91130c6bc19fb2668568d0000008019a8c197c12bb33da6314c4ef4f8f6fcb9e25250c085df8672adf67c8f1e3dbc0000010023c08a6b1297210c5e24c76b9a936250a1ce2721576c26ea797c7ec35f9e46a9000001000000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000006707c7bad498444361455d5099a036e93f395a584cf085c607c66a9b410a7e26824d677a12d8af977c0db64e5fcdfc8ad704c71c0267d649000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"publicInputsHash": "0x00e081da850e07688f3b8eac898caaeb80fabf215e1c87bbecc180d88a19f34e",
"header": "0x1200a06aae1368abe36530b585bd7a4d2ba4de5037b82076412691a187d7621e00000001000000000000000000000000000000000000000000000000000000000000000200e994e16b3763fd5039413cf99c2b3c378e2bab939e7992a77bd201b28160d600089a9d421a82c4a25f7acbebe69e638d5b064fa8a60e018793dcb0be53752c00f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb14f44d672eb357739e42463497f9fdac46623af863eea4d947ca00a497dcdeb3000000100b59baa35b9dc267744f0ccb4e3b0255c1fc512460d91130c6bc19fb2668568d0000008019a8c197c12bb33da6314c4ef4f8f6fcb9e25250c085df8672adf67c8f1e3dbc0000010023c08a6b1297210c5e24c76b9a936250a1ce2721576c26ea797c7ec35f9e46a9000001000000000000000000000000000000000000000000000000000000000000007a6900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000672148d42590e3544a7e2e7d736649fefc72ea5ad1d6efc32146e005e28b76eedc61edeff431b412b5ece74a5818080051832d286795ce2e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"publicInputsHash": "0x0086289eca84479d5e34db9c4548bdd156af80fb725c51219650aa4460c80240",
"numTxs": 0
}
}
18 changes: 9 additions & 9 deletions l1-contracts/test/fixtures/empty_block_2.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"l2ToL1Messages": []
},
"block": {
"archive": "0x1d3dc82359e412945750080a10eede5c3ee3cb9360dc3ca35dda2c5f2e296c52",
"blockHash": "0x15fb8c7900432692f9f4e590e6df42d6fff4ce24f0b720514d6bc30ba234d548",
"archive": "0x07d7359b2b2922b8126019bef4f2d5698f25226f5f2270a79d1105503727dd6e",
"blockHash": "0x1836f56b16db44064b3231102c84c6eaf3327d0204a5171f65b3019fc79c2b5e",
"body": "0x00000000",
"txsEffectsHash": "0x00e994e16b3763fd5039413cf99c2b3c378e2bab939e7992a77bd201b28160d6",
"decodedHeader": {
Expand All @@ -21,20 +21,20 @@
},
"globalVariables": {
"blockNumber": 2,
"slotNumber": "0x0000000000000000000000000000000000000000000000000000000000000012",
"slotNumber": "0x0000000000000000000000000000000000000000000000000000000000000013",
"chainId": 31337,
"timestamp": 1728563154,
"timestamp": 1730234604,
"version": 1,
"coinbase": "0xd498444361455d5099a036e93f395a584cf085c6",
"feeRecipient": "0x07c66a9b410a7e26824d677a12d8af977c0db64e5fcdfc8ad704c71c0267d649",
"coinbase": "0x2590e3544a7e2e7d736649fefc72ea5ad1d6efc3",
"feeRecipient": "0x2146e005e28b76eedc61edeff431b412b5ece74a5818080051832d286795ce2e",
"gasFees": {
"feePerDaGas": 0,
"feePerL2Gas": 0
}
},
"lastArchive": {
"nextAvailableLeafIndex": 2,
"root": "0x18589e53843baf9415aa9a4943bd4d8fa19b318329604f51a4ac0b8e7eb8e155"
"root": "0x04f48997a6e0472d7919af16c50859f86c041ef9aefceba4be94657943618ac1"
},
"stateReference": {
"l1ToL2MessageTree": {
Expand All @@ -57,8 +57,8 @@
}
}
},
"header": "0x18589e53843baf9415aa9a4943bd4d8fa19b318329604f51a4ac0b8e7eb8e15500000002000000000000000000000000000000000000000000000000000000000000000200e994e16b3763fd5039413cf99c2b3c378e2bab939e7992a77bd201b28160d600089a9d421a82c4a25f7acbebe69e638d5b064fa8a60e018793dcb0be53752c00f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb14f44d672eb357739e42463497f9fdac46623af863eea4d947ca00a497dcdeb3000000200b59baa35b9dc267744f0ccb4e3b0255c1fc512460d91130c6bc19fb2668568d0000010019a8c197c12bb33da6314c4ef4f8f6fcb9e25250c085df8672adf67c8f1e3dbc0000018023c08a6b1297210c5e24c76b9a936250a1ce2721576c26ea797c7ec35f9e46a9000001800000000000000000000000000000000000000000000000000000000000007a69000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000006707c7d2d498444361455d5099a036e93f395a584cf085c607c66a9b410a7e26824d677a12d8af977c0db64e5fcdfc8ad704c71c0267d649000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"publicInputsHash": "0x00815a814be4b3e0ac1a671e1a0321c231ac90e23ef7b2b1e411d32a2fb1948d",
"header": "0x04f48997a6e0472d7919af16c50859f86c041ef9aefceba4be94657943618ac100000002000000000000000000000000000000000000000000000000000000000000000200e994e16b3763fd5039413cf99c2b3c378e2bab939e7992a77bd201b28160d600089a9d421a82c4a25f7acbebe69e638d5b064fa8a60e018793dcb0be53752c00f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb14f44d672eb357739e42463497f9fdac46623af863eea4d947ca00a497dcdeb3000000200b59baa35b9dc267744f0ccb4e3b0255c1fc512460d91130c6bc19fb2668568d0000010019a8c197c12bb33da6314c4ef4f8f6fcb9e25250c085df8672adf67c8f1e3dbc0000018023c08a6b1297210c5e24c76b9a936250a1ce2721576c26ea797c7ec35f9e46a9000001800000000000000000000000000000000000000000000000000000000000007a6900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000672148ec2590e3544a7e2e7d736649fefc72ea5ad1d6efc32146e005e28b76eedc61edeff431b412b5ece74a5818080051832d286795ce2e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"publicInputsHash": "0x009727b9d0f6c22ea711c2e31249776bd687ffa0eaf97b54120af68c96da0eda",
"numTxs": 0
}
}
Loading

0 comments on commit 4e5ae95

Please sign in to comment.