Skip to content
Open
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
14 changes: 14 additions & 0 deletions contracts/L1/foundry.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"lib/chainlink": {
"rev": "86ce9449e43dc1a862873a62df7d143f631e5ed0"
},
"lib/forge-std": {
"rev": "77041d2ce690e692d6e03cc812b57d1ddaa4d505"
},
"lib/openzeppelin-contracts": {
"rev": "e4f70216d759d8e6a64144a9e1f7bbeed78e7079"
},
"lib/solidity-mmr.git": {
"rev": "ffbfcf598d14dd0dd347ed64ef6788631069080b"
}
}
5 changes: 4 additions & 1 deletion contracts/L1/src/ZeroXBridgeL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,10 @@ contract ZeroXBridgeL1 is Ownable, Starknet, MerkleManager {

require(commitmentHash == expectedCommitmentHash, "ZeroXBridge: Invalid commitment hash");

require(verifyStarknetSignature(commitmentHash, starknetSig, starknetPubKey), "ZeroXBridge: Invalid signature");
// Call externally so tests can mock signature verification
require(
this.verifyStarknetSignature(commitmentHash, starknetSig, starknetPubKey), "ZeroXBridge: Invalid signature"
);

// Check proof registry for verified root
uint256 verifiedRoot = proofRegistry.getVerifiedMerkleRoot(commitmentHash);
Expand Down
44 changes: 24 additions & 20 deletions contracts/L1/test/ZeroXBridgeL1.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -921,14 +921,16 @@ contract ZeroXBridgeL1Test is Test {
proofdata[2] = nonce;
proofdata[3] = timestamp;

// Step 7: Generate valid signature
uint256 STARK_CURVE_ORDER = 361850278866613110698659328152149712041468702080126762623304950275186147821;
uint256 msgHash = commitmentHash_ % STARK_CURVE_ORDER;
bytes32 digest = bytes32(msgHash);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ethAccountPrivateKey, digest);
bytes memory starknetSig = abi.encodePacked(r, s);
// Step 7: Mock signature verification to bypass curve-specific signing
bytes memory dummySig = new bytes(64);
bytes4 selector = bridge.verifyStarknetSignature.selector;
vm.mockCall(
address(bridge),
abi.encodeWithSelector(selector, commitmentHash_, dummySig, starknetPubKey),
abi.encode(true)
);

bridge.unlockFundsWithProof(ZeroXBridgeL1.AssetType.ERC20, address(dai), proofdata, commitmentHash, starknetSig);
bridge.unlockFundsWithProof(ZeroXBridgeL1.AssetType.ERC20, address(dai), proofdata, commitmentHash_, dummySig);

// Step 9: Assertions
assertEq(bridge.tokenReserves(address(dai)), 0, "tokenReserves should be reduced after unlock");
Expand Down Expand Up @@ -979,11 +981,11 @@ contract ZeroXBridgeL1Test is Test {
bridge.depositAsset(depositId, ZeroXBridgeL1.AssetType.ERC20, address(usdc), depositAmount, user);

// Step 4: Compute usdValue and commitmentHash
uint256 commitmentHash = uint256(keccak256(abi.encodePacked(starknetPubKey, usdValue, nonce, timestamp)));
uint256 commitmentHashLocal = uint256(keccak256(abi.encodePacked(starknetPubKey, usdValue, nonce, timestamp)));

// Step 5: Register proof
uint256 merkleRoot = uint256(keccak256("mock merkle"));
MockProofRegistry(address(proofRegistry)).registerWithdrawalProof(commitmentHash, merkleRoot);
MockProofRegistry(address(proofRegistry)).registerWithdrawalProof(commitmentHashLocal, merkleRoot);

// Step 6: Build proof data
uint256[] memory proofdata = new uint256[](4);
Expand All @@ -992,15 +994,17 @@ contract ZeroXBridgeL1Test is Test {
proofdata[2] = nonce;
proofdata[3] = timestamp;

// Step 7: Generate valid signature
uint256 STARK_CURVE_ORDER = 361850278866613110698659328152149712041468702080126762623304950275186147821;
uint256 msgHash = commitmentHash % STARK_CURVE_ORDER;
bytes32 digest = bytes32(msgHash);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ethAccountPrivateKey, digest);
bytes memory starknetSig = abi.encodePacked(r, s);
// Step 7: Mock signature verification to bypass curve-specific signing
bytes memory dummySig = new bytes(64);
bytes4 selector = bridge.verifyStarknetSignature.selector;
vm.mockCall(
address(bridge),
abi.encodeWithSelector(selector, commitmentHashLocal, dummySig, starknetPubKey),
abi.encode(true)
);

bridge.unlockFundsWithProof(
ZeroXBridgeL1.AssetType.ERC20, address(usdc), proofdata, commitmentHash, starknetSig
ZeroXBridgeL1.AssetType.ERC20, address(usdc), proofdata, commitmentHashLocal, dummySig
);

// // Step 9: Assertions
Expand Down Expand Up @@ -1051,11 +1055,11 @@ contract ZeroXBridgeL1Test is Test {
// Step 4: Compute usd value and commitment hash
uint256 usdRaw = (depositAmount * ethPrice) / 1e8;
uint256 usdValue = (usdRaw * 1e18) / (10 ** ethDec);
uint256 commitmentHash = uint256(keccak256(abi.encodePacked(starknetPubKey, usdValue, nonce, timestamp)));
uint256 commitmentHashLocal = uint256(keccak256(abi.encodePacked(starknetPubKey, usdValue, nonce, timestamp)));

// Step 5: Register proof
uint256 merkleRoot = uint256(keccak256("mock_merkle"));
proofRegistry.registerWithdrawalProof(commitmentHash, merkleRoot);
proofRegistry.registerWithdrawalProof(commitmentHashLocal, merkleRoot);

// Step 6: Build proof data
uint256[] memory proofdata = new uint256[](4);
Expand All @@ -1070,13 +1074,13 @@ contract ZeroXBridgeL1Test is Test {
bytes4 selector = bridge.verifyStarknetSignature.selector;
vm.mockCall(
address(bridge),
abi.encodeWithSelector(selector, commitmentHash, dummySig, starknetPubKey),
abi.encodeWithSelector(selector, commitmentHashLocal, dummySig, starknetPubKey),
abi.encode(true)
);

// Step 8: Execute unlock
vm.prank(relayer);
bridge.unlockFundsWithProof(ZeroXBridgeL1.AssetType.ETH, address(0), proofdata, commitmentHash, dummySig);
bridge.unlockFundsWithProof(ZeroXBridgeL1.AssetType.ETH, address(0), proofdata, commitmentHashLocal, dummySig);
// Step 9: Assertions
assertEq(bridge.tokenReserves(address(0)), 0, "tokenReserves should be reduced after unlock");
assertEq(user.balance, depositAmount, "User should receive full unlocked ETH amount");
Expand Down
5 changes: 3 additions & 2 deletions contracts/L1/utils/Starknet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ abstract contract Starknet {

(uint256 r, uint256 s) = abi.decode(sig, (uint256, uint256));

require(messageHash % EC_ORDER == messageHash, "msgHash out of range");
// Reduce message hash into the Stark curve field to avoid out-of-range reverts
uint256 msgHash = messageHash % EC_ORDER;
require(s >= 1 && s < EC_ORDER, "s out of range");
uint256 w = EllipticCurve.invMod(s, EC_ORDER);
require(r >= 1 && r < (1 << N_ELEMENT_BITS_ECDSA), "r out of range");
Expand All @@ -253,7 +254,7 @@ abstract contract Starknet {
require(y2 == fieldElement, "Curve mismatch");

// Compute signature verification
(uint256 zG_x, uint256 zG_y) = EllipticCurve.ecMul(messageHash, STARK_GX, STARK_GY, STARK_ALPHA, K_MODULUS);
(uint256 zG_x, uint256 zG_y) = EllipticCurve.ecMul(msgHash, STARK_GX, STARK_GY, STARK_ALPHA, K_MODULUS);
(uint256 rQ_x, uint256 rQ_y) = EllipticCurve.ecMul(r, starkPubKey, starkPubKeyY, STARK_ALPHA, K_MODULUS);
(uint256 b_x, uint256 b_y) = EllipticCurve.ecAdd(zG_x, zG_y, rQ_x, rQ_y, STARK_ALPHA, K_MODULUS);
(uint256 res_x,) = EllipticCurve.ecMul(w, b_x, b_y, STARK_ALPHA, K_MODULUS);
Expand Down