Skip to content

Commit 3af64db

Browse files
authored
Merge pull request #358 from rsksmart/zero-address-vulnerability-fix
GBI-2873/zero-address-vulnerability-fix
2 parents 96d6855 + c7cc50d commit 3af64db

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

contracts/libraries/SignatureValidator.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity 0.8.25;
33

44
library SignatureValidator {
55
error IncorrectSignature(address expectedAddress, bytes32 usedHash, bytes signature);
6+
error ZeroAddress();
67
/**
78
@dev Verfies signature against address
89
@param addr The signing address
@@ -11,6 +12,10 @@ library SignatureValidator {
1112
@return True if the signature is valid, false otherwise.
1213
*/
1314
function verify(address addr, bytes32 quoteHash, bytes memory signature) public pure returns (bool) {
15+
if (addr == address(0)) {
16+
revert ZeroAddress();
17+
}
18+
1419
bytes32 r;
1520
bytes32 s;
1621
uint8 v;

test/signature-validator.test.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { expect } from "chai";
2+
import { ethers } from "hardhat";
3+
import { SignatureValidator } from "../typechain-types";
4+
import { deployContract } from "../scripts/deployment-utils/utils";
5+
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
6+
import hre from "hardhat";
7+
8+
describe("SignatureValidator", () => {
9+
let signatureValidator: SignatureValidator;
10+
let signer: HardhatEthersSigner;
11+
let otherSigner: HardhatEthersSigner;
12+
13+
beforeEach(async () => {
14+
const signers = await ethers.getSigners();
15+
signer = signers[0];
16+
otherSigner = signers[1];
17+
18+
const deploymentInfo = await deployContract(
19+
"SignatureValidator",
20+
hre.network.name
21+
);
22+
signatureValidator = await ethers.getContractAt(
23+
"SignatureValidator",
24+
deploymentInfo.address
25+
);
26+
});
27+
28+
describe("Zero Address Protection", () => {
29+
it("should revert with ZeroAddress error when addr parameter is address(0)", async () => {
30+
const testMessage = "test message";
31+
const messageHash = ethers.keccak256(ethers.toUtf8Bytes(testMessage));
32+
const signature = await signer.signMessage(ethers.getBytes(messageHash));
33+
34+
// Attempt to verify with zero address should revert
35+
await expect(
36+
signatureValidator.verify(ethers.ZeroAddress, messageHash, signature)
37+
).to.be.revertedWithCustomError(signatureValidator, "ZeroAddress");
38+
});
39+
40+
it("should prevent zero address bypass attack vector", async () => {
41+
// This test demonstrates that the attack vector described is now prevented
42+
// An attacker cannot pass address(0) and arbitrary invalid signature data
43+
// to bypass authentication
44+
45+
const arbitraryHash = ethers.keccak256(
46+
ethers.toUtf8Bytes("malicious data")
47+
);
48+
const invalidSignature =
49+
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12";
50+
51+
// Before the fix, this would have returned true, allowing the attack
52+
// Now it should revert with ZeroAddress error, preventing the attack
53+
await expect(
54+
signatureValidator.verify(
55+
ethers.ZeroAddress,
56+
arbitraryHash,
57+
invalidSignature
58+
)
59+
).to.be.revertedWithCustomError(signatureValidator, "ZeroAddress");
60+
});
61+
62+
it("should prevent zero address bypass with empty signature", async () => {
63+
// Another variation of the attack with empty signature
64+
const arbitraryHash = ethers.keccak256(
65+
ethers.toUtf8Bytes("malicious data")
66+
);
67+
const emptySignature = "0x";
68+
69+
await expect(
70+
signatureValidator.verify(
71+
ethers.ZeroAddress,
72+
arbitraryHash,
73+
emptySignature
74+
)
75+
).to.be.revertedWithCustomError(signatureValidator, "ZeroAddress");
76+
});
77+
78+
it("should prevent zero address bypass with malformed signature", async () => {
79+
// Test with malformed signature data that could cause ecrecover to return zero address
80+
const arbitraryHash = ethers.keccak256(
81+
ethers.toUtf8Bytes("malicious data")
82+
);
83+
const malformedSignature =
84+
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c";
85+
86+
await expect(
87+
signatureValidator.verify(
88+
ethers.ZeroAddress,
89+
arbitraryHash,
90+
malformedSignature
91+
)
92+
).to.be.revertedWithCustomError(signatureValidator, "ZeroAddress");
93+
});
94+
});
95+
96+
describe("Valid Signature Verification", () => {
97+
it("should correctly verify valid signatures for non-zero addresses", async () => {
98+
const testMessage = "test message for signature";
99+
const messageHash = ethers.keccak256(ethers.toUtf8Bytes(testMessage));
100+
const messageBytes = ethers.getBytes(messageHash);
101+
102+
// Sign the message hash with the signer
103+
const signature = await signer.signMessage(messageBytes);
104+
105+
// Verify the signature should return true for the correct signer address
106+
const isValid = await signatureValidator.verify(
107+
signer.address,
108+
messageHash,
109+
signature
110+
);
111+
expect(isValid).to.equal(true);
112+
});
113+
114+
it("should reject invalid signatures for non-zero addresses", async () => {
115+
const testMessage = "test message for signature";
116+
const messageHash = ethers.keccak256(ethers.toUtf8Bytes(testMessage));
117+
const messageBytes = ethers.getBytes(messageHash);
118+
119+
// Sign with one signer
120+
const signature = await signer.signMessage(messageBytes);
121+
122+
// Try to verify with different signer's address should return false
123+
const isValid = await signatureValidator.verify(
124+
otherSigner.address,
125+
messageHash,
126+
signature
127+
);
128+
expect(isValid).to.equal(false);
129+
});
130+
131+
it("should handle signature verification with different message hashes", async () => {
132+
const originalMessage = "original message";
133+
const differentMessage = "different message";
134+
135+
const originalHash = ethers.keccak256(
136+
ethers.toUtf8Bytes(originalMessage)
137+
);
138+
const differentHash = ethers.keccak256(
139+
ethers.toUtf8Bytes(differentMessage)
140+
);
141+
142+
const originalBytes = ethers.getBytes(originalHash);
143+
const signature = await signer.signMessage(originalBytes);
144+
145+
// Should return true for original hash
146+
const validOriginal = await signatureValidator.verify(
147+
signer.address,
148+
originalHash,
149+
signature
150+
);
151+
expect(validOriginal).to.equal(true);
152+
153+
// Should return false for different hash
154+
const validDifferent = await signatureValidator.verify(
155+
signer.address,
156+
differentHash,
157+
signature
158+
);
159+
expect(validDifferent).to.equal(false);
160+
});
161+
});
162+
163+
describe("Edge Cases", () => {
164+
it("should handle very long signature data", async () => {
165+
const testMessage = "test message";
166+
const messageHash = ethers.keccak256(ethers.toUtf8Bytes(testMessage));
167+
168+
// Create an overly long signature (should still work if first 65 bytes are valid)
169+
const messageBytes = ethers.getBytes(messageHash);
170+
const validSignature = await signer.signMessage(messageBytes);
171+
const longSignature = validSignature + "deadbeef"; // Add extra data
172+
173+
const isValid = await signatureValidator.verify(
174+
signer.address,
175+
messageHash,
176+
longSignature
177+
);
178+
expect(isValid).to.equal(true);
179+
});
180+
181+
it("should handle short signature data gracefully", async () => {
182+
const testMessage = "test message";
183+
const messageHash = ethers.keccak256(ethers.toUtf8Bytes(testMessage));
184+
const shortSignature = "0x1234"; // Too short to be a valid signature
185+
186+
// Should not revert but return false (ecrecover will return zero address, but we check for non-zero signer)
187+
const isValid = await signatureValidator.verify(
188+
signer.address,
189+
messageHash,
190+
shortSignature
191+
);
192+
expect(isValid).to.equal(false);
193+
});
194+
});
195+
});

0 commit comments

Comments
 (0)