Skip to content

Commit 55c0717

Browse files
feat(validium): stealth deposit key rotation (#142)
Co-authored-by: Rohit Narurkar <[email protected]>
1 parent 04f228b commit 55c0717

9 files changed

+170
-32
lines changed

src/test/validium/FastWithdrawVault.t.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ contract FastWithdrawVaultTest is ValidiumTestBase {
267267
address(counterpartGateway),
268268
address(messenger),
269269
address(template),
270-
address(factory)
270+
address(factory),
271+
address(rollup)
271272
)
272273
)
273274
);

src/test/validium/L1ERC20GatewayValidium.t.sol

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {AddressAliasHelper} from "../../libraries/common/AddressAliasHelper.sol"
1414
import {IL1ERC20GatewayValidium} from "../../validium/IL1ERC20GatewayValidium.sol";
1515
import {IL2ERC20GatewayValidium} from "../../validium/IL2ERC20GatewayValidium.sol";
1616
import {L1ERC20GatewayValidium} from "../../validium/L1ERC20GatewayValidium.sol";
17+
import {ScrollChainValidium} from "../../validium/ScrollChainValidium.sol";
1718

1819
import {TransferReentrantToken} from "../mocks/tokens/TransferReentrantToken.sol";
1920
import {FeeOnTransferToken} from "../mocks/tokens/FeeOnTransferToken.sol";
@@ -128,47 +129,63 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
128129
_deposit(sender, amount, recipient, gasLimit);
129130
}
130131

132+
function testDepositERC20WrongKey(
133+
uint256 amount,
134+
bytes memory recipient,
135+
uint256 gasLimit
136+
) public {
137+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
138+
hevm.expectRevert(ScrollChainValidium.ErrorUnknownEncryptionKey.selector);
139+
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId + 1);
140+
}
141+
131142
function testDepositReentrantToken(uint256 amount) public {
143+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
144+
132145
// should revert, reentrant before transfer
133146
reentrantToken.setReentrantCall(
134147
address(gateway),
135148
0,
136149
abi.encodeWithSignature(
137-
"depositERC20(address,bytes,uint256,uint256)",
150+
"depositERC20(address,bytes,uint256,uint256,uint256)",
138151
address(reentrantToken),
139152
new bytes(0),
140153
amount,
141-
defaultGasLimit
154+
defaultGasLimit,
155+
keyId
142156
),
143157
true
144158
);
145159
amount = bound(amount, 1, reentrantToken.balanceOf(address(this)));
146160
hevm.expectRevert("ReentrancyGuard: reentrant call");
147-
gateway.depositERC20(address(reentrantToken), new bytes(0), amount, defaultGasLimit);
161+
162+
gateway.depositERC20(address(reentrantToken), new bytes(0), amount, defaultGasLimit, keyId);
148163

149164
// should revert, reentrant after transfer
150165
reentrantToken.setReentrantCall(
151166
address(gateway),
152167
0,
153168
abi.encodeWithSignature(
154-
"depositERC20(address,bytes,uint256,uint256)",
169+
"depositERC20(address,bytes,uint256,uint256,uint256)",
155170
address(reentrantToken),
156171
new bytes(0),
157172
amount,
158-
defaultGasLimit
173+
defaultGasLimit,
174+
keyId
159175
),
160176
false
161177
);
162178
amount = bound(amount, 1, reentrantToken.balanceOf(address(this)));
163179
hevm.expectRevert("ReentrancyGuard: reentrant call");
164-
gateway.depositERC20(address(reentrantToken), new bytes(0), amount, defaultGasLimit);
180+
gateway.depositERC20(address(reentrantToken), new bytes(0), amount, defaultGasLimit, keyId);
165181
}
166182

167183
function testFeeOnTransferTokenFailed(uint256 amount) public {
168184
feeToken.setFeeRate(1e9);
169185
amount = bound(amount, 1, feeToken.balanceOf(address(this)));
186+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
170187
hevm.expectRevert(L1ERC20GatewayValidium.ErrorAmountIsZero.selector);
171-
gateway.depositERC20(address(feeToken), new bytes(0), amount, defaultGasLimit);
188+
gateway.depositERC20(address(feeToken), new bytes(0), amount, defaultGasLimit, keyId);
172189
}
173190

174191
function testFeeOnTransferTokenSucceed(uint256 amount, uint256 feeRate) public {
@@ -179,7 +196,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
179196
// should succeed, for valid amount
180197
uint256 balanceBefore = feeToken.balanceOf(address(gateway));
181198
uint256 fee = (amount * feeRate) / 1e9;
182-
gateway.depositERC20(address(feeToken), new bytes(0), amount, defaultGasLimit);
199+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
200+
gateway.depositERC20(address(feeToken), new bytes(0), amount, defaultGasLimit, keyId);
183201
uint256 balanceAfter = feeToken.balanceOf(address(gateway));
184202
assertEq(balanceBefore + amount - fee, balanceAfter);
185203
}
@@ -245,7 +263,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
245263
amount = bound(amount, 1, l1Token.balanceOf(address(this)));
246264

247265
// deposit some token to L1ERC20GatewayValidium
248-
gateway.depositERC20(address(l1Token), new bytes(0), amount, defaultGasLimit);
266+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
267+
gateway.depositERC20(address(l1Token), new bytes(0), amount, defaultGasLimit, keyId);
249268

250269
// do finalize withdraw token
251270
bytes memory message = abi.encodeWithSelector(
@@ -302,7 +321,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
302321
amount = bound(amount, 1, l1Token.balanceOf(address(this)));
303322

304323
// deposit some token to L1ERC20GatewayValidium
305-
gateway.depositERC20(address(l1Token), new bytes(0), amount, defaultGasLimit);
324+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
325+
gateway.depositERC20(address(l1Token), new bytes(0), amount, defaultGasLimit, keyId);
306326

307327
// do finalize withdraw token
308328
bytes memory message = abi.encodeWithSelector(
@@ -385,11 +405,12 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
385405
);
386406

387407
if (amount == 0) {
408+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
388409
hevm.expectRevert(L1ERC20GatewayValidium.ErrorAmountIsZero.selector);
389410
if (from == address(this)) {
390-
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit);
411+
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId);
391412
} else {
392-
gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit);
413+
gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit, keyId);
393414
}
394415
} else {
395416
// emit QueueTransaction from L1MessageQueueV2
@@ -412,10 +433,11 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
412433
uint256 gatewayBalance = l1Token.balanceOf(address(gateway));
413434
uint256 feeVaultBalance = address(feeVault).balance;
414435
assertEq(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), 0);
436+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
415437
if (from == address(this)) {
416-
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit);
438+
gateway.depositERC20(address(l1Token), recipient, amount, gasLimit, keyId);
417439
} else {
418-
gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit);
440+
gateway.depositERC20(address(l1Token), from, recipient, amount, gasLimit, keyId);
419441
}
420442
assertEq(amount + gatewayBalance, l1Token.balanceOf(address(gateway)));
421443
assertEq(feeVaultBalance, address(feeVault).balance);
@@ -433,7 +455,8 @@ contract L1ERC20GatewayValidiumTest is ValidiumTestBase {
433455
address(counterpartGateway),
434456
address(messenger),
435457
address(template),
436-
address(factory)
458+
address(factory),
459+
address(rollup)
437460
)
438461
)
439462
);

src/test/validium/L1WETHGatewayValidium.t.sol

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,15 @@ contract L1WETHGatewayValidiumTest is ValidiumTestBase {
108108
message
109109
);
110110

111+
(uint256 keyId, ) = rollup.getLatestEncryptionKey();
112+
111113
if (amount == 0) {
112114
hevm.expectRevert(L1ERC20GatewayValidium.ErrorAmountIsZero.selector);
113-
wethGateway.deposit(recipient, amount);
115+
wethGateway.deposit(recipient, amount, keyId);
114116
} else {
115117
// revert when ErrorInsufficientValue
116118
hevm.expectRevert(L1WETHGatewayValidium.ErrorInsufficientValue.selector);
117-
wethGateway.deposit{value: amount - 1}(recipient, amount);
119+
wethGateway.deposit{value: amount - 1}(recipient, amount, keyId);
118120

119121
// emit QueueTransaction from L1MessageQueueV2
120122
{
@@ -137,7 +139,7 @@ contract L1WETHGatewayValidiumTest is ValidiumTestBase {
137139
uint256 gatewayBalance = weth.balanceOf(address(gateway));
138140
uint256 feeVaultBalance = address(feeVault).balance;
139141
assertEq(l1Messenger.messageSendTimestamp(keccak256(xDomainCalldata)), 0);
140-
wethGateway.deposit{value: amount}(recipient, amount);
142+
wethGateway.deposit{value: amount}(recipient, amount, keyId);
141143
assertEq(ethBalance - amount, address(this).balance);
142144
assertEq(amount + gatewayBalance, weth.balanceOf(address(gateway)));
143145
assertEq(feeVaultBalance, address(feeVault).balance);
@@ -155,7 +157,8 @@ contract L1WETHGatewayValidiumTest is ValidiumTestBase {
155157
address(counterpartGateway),
156158
address(messenger),
157159
address(template),
158-
address(factory)
160+
address(factory),
161+
address(rollup)
159162
)
160163
)
161164
);

src/test/validium/ValidiumTestBase.t.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ abstract contract ValidiumTestBase is ScrollTestBase {
130130
address(new ScrollChainValidium(_chainId, address(messageQueueV2), address(verifier)))
131131
);
132132
rollup.initialize(address(this));
133+
rollup.grantRole(rollup.KEY_MANAGER_ROLE(), address(this));
134+
rollup.registerNewEncryptionKey(hex"123456789012345678901234567890123456789012345678901234567890123456");
133135

134136
// Make nonzero block.timestamp
135137
hevm.warp(1);

src/validium/IL1ERC20GatewayValidium.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ interface IL1ERC20GatewayValidium {
6161
address _token,
6262
bytes memory _to,
6363
uint256 _amount,
64-
uint256 _gasLimit
64+
uint256 _gasLimit,
65+
uint256 _keyId
6566
) external payable;
6667

6768
/// @notice Deposit some token to a recipient's account on L2.
@@ -76,7 +77,8 @@ interface IL1ERC20GatewayValidium {
7677
address _realSender,
7778
bytes memory _to,
7879
uint256 _amount,
79-
uint256 _gasLimit
80+
uint256 _gasLimit,
81+
uint256 _keyId
8082
) external payable;
8183

8284
/// @notice Complete ERC20 withdraw from L2 to L1 and send fund to recipient's account in L1.

src/validium/IScrollChainValidium.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ interface IScrollChainValidium {
2424
/// @param withdrawRoot The merkle root on layer2 after this batch.
2525
event FinalizeBatch(uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot);
2626

27+
/// @notice Emitted when a new encryption key is added.
28+
/// @param keyId The incremental index of the key.
29+
/// @param msgIndex The message queue index at the time of key rotation.
30+
/// @param key The encryption key.
31+
event NewEncryptionKey(uint256 indexed keyId, uint256 msgIndex, bytes key);
32+
2733
/*************************
2834
* Public View Functions *
2935
*************************/
@@ -50,6 +56,14 @@ interface IScrollChainValidium {
5056
/// @return Whether the batch is finalized by batch index.
5157
function isBatchFinalized(uint256 batchIndex) external view returns (bool);
5258

59+
/// @return The key-id of the latest encryption key.
60+
/// @return The latest encryption key.
61+
function getLatestEncryptionKey() external view returns (uint256, bytes memory);
62+
63+
/// @param keyId The incremental index for the encryption key.
64+
/// @return The encryption key with the given key-id.
65+
function getEncryptionKey(uint256 keyId) external view returns (bytes memory);
66+
5367
/*****************************
5468
* Public Mutating Functions *
5569
*****************************/

src/validium/L1ERC20GatewayValidium.sol

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ER
1010
import {IL1ScrollMessenger} from "../L1/IL1ScrollMessenger.sol";
1111
import {IL1ERC20GatewayValidium} from "./IL1ERC20GatewayValidium.sol";
1212
import {IL2ERC20GatewayValidium} from "./IL2ERC20GatewayValidium.sol";
13+
import {IScrollChainValidium} from "./IScrollChainValidium.sol";
1314

1415
import {ScrollGatewayBase} from "../libraries/gateway/ScrollGatewayBase.sol";
1516

@@ -43,6 +44,9 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
4344
/// @notice The address of ScrollStandardERC20Factory contract in L2.
4445
address public immutable l2TokenFactory;
4546

47+
/// @notice The address of ScrollChainValidium contract in L2.
48+
address public immutable scrollChainValidium;
49+
4650
/*************
4751
* Variables *
4852
*************/
@@ -67,12 +71,14 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
6771
address _counterpart,
6872
address _messenger,
6973
address _l2TokenImplementation,
70-
address _l2TokenFactory
74+
address _l2TokenFactory,
75+
address _scrollChainValidium
7176
) ScrollGatewayBase(_counterpart, address(0), _messenger) {
7277
_disableInitializers();
7378

7479
l2TokenImplementation = _l2TokenImplementation;
7580
l2TokenFactory = _l2TokenFactory;
81+
scrollChainValidium = _scrollChainValidium;
7682
}
7783

7884
/// @notice Initialize the storage of L1ERC20GatewayValidium.
@@ -102,9 +108,10 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
102108
address _token,
103109
bytes memory _to,
104110
uint256 _amount,
105-
uint256 _gasLimit
111+
uint256 _gasLimit,
112+
uint256 _keyId
106113
) external payable override {
107-
_deposit(_token, _msgSender(), _to, _amount, new bytes(0), _gasLimit);
114+
_deposit(_token, _msgSender(), _to, _amount, new bytes(0), _gasLimit, _keyId);
108115
}
109116

110117
/// @inheritdoc IL1ERC20GatewayValidium
@@ -113,9 +120,10 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
113120
address _realSender,
114121
bytes memory _to,
115122
uint256 _amount,
116-
uint256 _gasLimit
123+
uint256 _gasLimit,
124+
uint256 _keyId
117125
) external payable override {
118-
_deposit(_token, _realSender, _to, _amount, new bytes(0), _gasLimit);
126+
_deposit(_token, _realSender, _to, _amount, new bytes(0), _gasLimit, _keyId);
119127
}
120128

121129
/// @inheritdoc IL1ERC20GatewayValidium
@@ -192,8 +200,12 @@ contract L1ERC20GatewayValidium is ScrollGatewayBase, IL1ERC20GatewayValidium {
192200
bytes memory _to,
193201
uint256 _amount,
194202
bytes memory _data,
195-
uint256 _gasLimit
203+
uint256 _gasLimit,
204+
uint256 _keyId
196205
) internal virtual nonReentrant {
206+
// Validate the encryption key with the given key-id.
207+
IScrollChainValidium(scrollChainValidium).getEncryptionKey(_keyId);
208+
197209
// 1. Transfer token into this contract.
198210
_amount = _transferERC20In(_msgSender(), _token, _amount);
199211
if (_amount == 0) revert ErrorAmountIsZero();

src/validium/L1WETHGatewayValidium.sol

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ contract L1WETHGatewayValidium {
5050

5151
/// @notice Deposit ETH to L2 through the `L1ERC20GatewayValidium` contract.
5252
/// @param _to The encrypted address of recipient in L2 to receive the token.
53-
function deposit(bytes memory _to, uint256 _amount) external payable {
53+
function deposit(
54+
bytes memory _to,
55+
uint256 _amount,
56+
uint256 _keyId
57+
) external payable {
5458
if (msg.value < _amount) revert ErrorInsufficientValue();
5559

5660
// WETH deposit is safe.
@@ -62,7 +66,8 @@ contract L1WETHGatewayValidium {
6266
msg.sender,
6367
_to,
6468
_amount,
65-
GAS_LIMIT
69+
GAS_LIMIT,
70+
_keyId
6671
);
6772
}
6873
}

0 commit comments

Comments
 (0)