Skip to content

Commit 2d2348d

Browse files
authored
Merge pull request #360 from rsksmart/GBI-2875/integrate-openzeppelin-ecdsa-verify
Gbi 2875/integrate openzeppelin ecdsa verify
2 parents 10b14d1 + 7ff9f86 commit 2d2348d

File tree

3 files changed

+137
-1
lines changed

3 files changed

+137
-1
lines changed

contracts/libraries/SignatureValidator.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.25;
33

4+
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
5+
46
library SignatureValidator {
7+
8+
using ECDSA for bytes32;
9+
510
error IncorrectSignature(address expectedAddress, bytes32 usedHash, bytes signature);
611
error ZeroAddress();
712
/**
@@ -34,6 +39,6 @@ library SignatureValidator {
3439
// TODO use EIP712 compatible format instead
3540
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
3641
bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, quoteHash));
37-
return ecrecover(prefixedHash, v, r, s) == addr;
42+
return prefixedHash.recover(v, r, s) == addr;
3843
}
3944
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.25;
3+
4+
// solhint-disable comprehensive-interface
5+
contract ECDSAError {
6+
error ECDSAInvalidSignatureS(bytes32 s);
7+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import hre, { ethers, upgrades } from "hardhat";
2+
import { expect } from "chai";
3+
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
4+
import { getTestPegoutQuote, totalValue } from "./utils/quotes";
5+
import { LP_COLLATERAL } from "./utils/constants";
6+
import { deployContract } from "../scripts/deployment-utils/utils";
7+
8+
describe("LBC signature malleability defense", () => {
9+
async function deployRealLbc() {
10+
// Deploy libraries with real SignatureValidator
11+
const network = hre.network.name;
12+
const quotesDeployment = await deployContract(
13+
"contracts/legacy/Quotes.sol:Quotes",
14+
network
15+
);
16+
const btcUtilsDeployment = await deployContract("BtcUtils", network);
17+
const realSignatureValidatorDeployment = await deployContract(
18+
"SignatureValidator",
19+
network
20+
);
21+
const bridgeMockDeployment = await deployContract("BridgeMock", network);
22+
23+
// Deploy LBC with real SignatureValidator
24+
const LiquidityBridgeContract = await ethers.getContractFactory(
25+
"LiquidityBridgeContract",
26+
{
27+
libraries: {
28+
Quotes: quotesDeployment.address,
29+
BtcUtils: btcUtilsDeployment.address,
30+
SignatureValidator: realSignatureValidatorDeployment.address,
31+
},
32+
}
33+
);
34+
35+
const lbcProxy = await upgrades.deployProxy(
36+
LiquidityBridgeContract,
37+
[
38+
bridgeMockDeployment.address, // bridgeAddress
39+
ethers.parseEther("0.03"), // minimumCollateral
40+
ethers.parseEther("0.5"), // minimumPegIn
41+
10, // rewardPercentage
42+
60, // resignDelayBlocks
43+
2300n * 65164000n, // dustThreshold
44+
900, // btcBlockTime
45+
false, // mainnet
46+
],
47+
{
48+
unsafeAllow: ["external-library-linking"],
49+
}
50+
);
51+
await lbcProxy.waitForDeployment();
52+
53+
// Upgrade to V2 with real SignatureValidator
54+
const quotesV2Deployment = await deployContract("QuotesV2", network);
55+
const LiquidityBridgeContractV2 = await ethers.getContractFactory(
56+
"LiquidityBridgeContractV2",
57+
{
58+
libraries: {
59+
QuotesV2: quotesV2Deployment.address,
60+
BtcUtils: btcUtilsDeployment.address,
61+
SignatureValidator: realSignatureValidatorDeployment.address,
62+
},
63+
}
64+
);
65+
66+
const lbc = await upgrades.upgradeProxy(
67+
lbcProxy,
68+
LiquidityBridgeContractV2,
69+
{
70+
unsafeAllow: ["external-library-linking"],
71+
}
72+
);
73+
await lbc.waitForDeployment();
74+
75+
const accounts = await ethers.getSigners();
76+
const lp = accounts[1];
77+
const user = accounts[2];
78+
79+
// Register one LP with pegout support
80+
await lbc.connect(lp).register("LP", "http://lp.local", true, "both", {
81+
value: LP_COLLATERAL,
82+
});
83+
84+
return { lbc, lp, user };
85+
}
86+
87+
it("reverts with ECDSAInvalidSignatureS when depositPegout gets high-s signature", async () => {
88+
const { lbc, lp, user } = await loadFixture(deployRealLbc);
89+
90+
const quote = getTestPegoutQuote({
91+
lbcAddress: await lbc.getAddress(),
92+
liquidityProvider: lp,
93+
refundAddress: user.address,
94+
value: ethers.parseEther("1"),
95+
});
96+
97+
const quoteHash = await lbc.hashPegoutQuote(quote);
98+
99+
const lowS = await lp.signMessage(ethers.getBytes(quoteHash));
100+
const parsed = ethers.Signature.from(lowS);
101+
102+
const secp256k1n =
103+
0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
104+
const s = BigInt(parsed.s);
105+
const sPrime = secp256k1n - s;
106+
const vPrime = parsed.v === 27 ? 28 : 27;
107+
const malleableSig = ethers.hexlify(
108+
ethers.concat([
109+
ethers.getBytes(parsed.r),
110+
ethers.getBytes(ethers.toBeHex(sPrime, 32)),
111+
ethers.getBytes(ethers.toBeHex(vPrime, 1)),
112+
])
113+
);
114+
115+
const errorHelper = await ethers.deployContract("ECDSAError");
116+
await errorHelper.waitForDeployment();
117+
118+
await expect(
119+
lbc
120+
.connect(user)
121+
.depositPegout(quote, malleableSig, { value: totalValue(quote) })
122+
).to.be.revertedWithCustomError(errorHelper, "ECDSAInvalidSignatureS");
123+
});
124+
});

0 commit comments

Comments
 (0)