Skip to content

Commit dc756e9

Browse files
committed
Add eip1271 test
1 parent a0af8f2 commit dc756e9

File tree

5 files changed

+130
-2
lines changed

5 files changed

+130
-2
lines changed

contracts/ECRecovery.sol

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
pragma solidity 0.8.19;
2+
3+
// SPDX-License-Identifier: MIT
4+
5+
contract ECRecovery {
6+
/**
7+
* @dev Recover signer address from a message by using their signature
8+
* @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
9+
* @param sig bytes signature, the signature is generated using web3.eth.sign()
10+
*/
11+
function recover(bytes32 hash, bytes memory sig)
12+
internal
13+
pure
14+
returns (address)
15+
{
16+
bytes32 r;
17+
bytes32 s;
18+
uint8 v;
19+
20+
//Check the signature length
21+
if (sig.length != 65) {
22+
return (address(0));
23+
}
24+
25+
// Divide the signature in r, s and v variables
26+
assembly {
27+
r := mload(add(sig, 32))
28+
s := mload(add(sig, 64))
29+
v := byte(0, mload(add(sig, 96)))
30+
}
31+
// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
32+
if (v < 27) {
33+
v += 27;
34+
}
35+
36+
// If the version is correct return the signer address
37+
if (v != 27 && v != 28) {
38+
return (address(0));
39+
} else {
40+
return ecrecover(hash, v, r, s);
41+
}
42+
}
43+
}

contracts/EIP1271.sol

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
pragma solidity 0.8.19;
2+
import "./ECRecovery.sol";
3+
4+
//SPDX-License-Identifier: MIT
5+
6+
contract EIP1271 is ECRecovery {
7+
mapping(address => bool) isOwner;
8+
9+
constructor() {
10+
isOwner[msg.sender] = true;
11+
}
12+
13+
function addOwner(address _owner) public {
14+
isOwner[_owner] = true;
15+
}
16+
17+
/**
18+
* @notice Verifies that the signer is the owner of the signing contract.
19+
*/
20+
function isValidSignature(bytes32 _hash, bytes calldata _signature)
21+
external
22+
view
23+
returns (bytes4)
24+
{
25+
if (isOwner[recover(_hash, _signature)]) {
26+
return 0x1626ba7e;
27+
} else {
28+
return 0xffffffff;
29+
}
30+
}
31+
}

contracts/MockEIP712Decoder.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.13;
2+
pragma solidity ^0.8.19;
33

44
import "./EIP712Decoder.sol";
55

hardhat.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require("@nomiclabs/hardhat-ethers");
22

33
module.exports = {
4-
solidity: "0.8.13",
4+
solidity: "0.8.19",
55
};

test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,60 @@ describe('EIP712Decoder', function () {
139139
});
140140
});
141141

142+
describe('EIP-1271', () => {
143+
let contract, eip1271, owner, other, accounts, signer, typedData, eip712Decoder, privateKey, wallet;
144+
145+
beforeEach(async () => {
146+
// Compile the contract using Hardhat
147+
await hre.run('compile');
148+
149+
// Set up a ganache provider with the generated Solidity code
150+
const ganacheProvider = ganache.provider({})
151+
const provider = new ethers.providers.Web3Provider(ganacheProvider);
152+
const mnemonic = ganacheProvider.options.mnemonic;
153+
wallet = ethers.Wallet.fromMnemonic(mnemonic);
154+
privateKey = wallet.privateKey;
155+
accounts = await provider.listAccounts();
156+
signer = provider.getSigner(accounts[0]);
157+
158+
// Load up the compiled contract artifact
159+
const EIP712Decoder = await hre.artifacts.readArtifact('MockEIP712Decoder');
160+
161+
// Deploy the contract
162+
const EIP712DecoderFactory = new ethers.ContractFactory(EIP712Decoder.abi, EIP712Decoder.bytecode, signer);
163+
contract = await EIP712DecoderFactory.deploy([1]);
164+
message.domain.verifyingContract = contract.address;
165+
await contract.deployed();
166+
167+
// Create the typed data for testing
168+
typedData = JSON.parse(JSON.stringify(message));
169+
typedData.domain.verifyingContract = contract.address;
170+
171+
// Deploy the EIP-1271 contract
172+
const EIP1271 = await hre.artifacts.readArtifact('EIP1271');
173+
const EIP1271ContractFactory = new ethers.ContractFactory(EIP1271.abi, EIP1271.bytecode, signer);
174+
const EIP1271Contract = await EIP1271ContractFactory.deploy([]);
175+
eip1271 = await EIP1271Contract.deployed();
176+
await eip1271.deployed();
177+
178+
await eip1271.addOwner(wallet.address);
179+
});
180+
181+
describe('contract account signature recovery', () => {
182+
it('should recover the correct signer', async () => {
183+
// Sign the typed data using eth-sig-util
184+
const signedPerson = signStruct(privateKey);
185+
signedPerson.signer = eip1271.address;
186+
187+
// Call the verifySigned method with the EIP-1271 contract address as the signer
188+
const result = await contract.verifySignedPerson(signedPerson);
189+
190+
// Verify that the returned address is equal to the EIP-1271 contract address
191+
expect(result).to.equal(eip1271.address);
192+
});
193+
});
194+
});
195+
142196
function signStruct (privateKey) {
143197
const signature = sigUtil.signTypedData({
144198
privateKey: fromHexString(privateKey.indexOf('0x') === 0 ? privateKey.substring(2) : privateKey),

0 commit comments

Comments
 (0)