核心
- signMessage ( personal_sign 方式)
- verifyMessage ( recover )
合约核心代码
// 引入的库
import '@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol';
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
// ======================== Functions ========================
function claim(uint256 nonce, uint256 amount, uint256 deadline, bytes calldata signature) public {
// ...
require(recover(msg.sender, nonce, amount, deadline, signature) == serviceAddress, 'signer error');
// ...
}
// ======================== Internal ========================
function recover(address user_address, uint256 nonce, uint256 amount, uint256 deadline, bytes calldata signature) internal pure returns (address signer) {
bytes32 messageHash = MessageHashUtils.toEthSignedMessageHash(keccak256(abi.encode(user_address, nonce, amount, deadline, block.chainid, address(this))));
signer = ECDSA.recover(messageHash, signature);
}
前后端代码演示
可以先去掉 chainid 和 合约地址,进行签名,对比最终结果是否相同。
const ethers = require("ethers");
const AbiCoder = new ethers.AbiCoder()
const privatekey = ''
const wallet = new ethers.Wallet(privatekey);
const params = {
"user_address": "0xXXXXX",
"nonce": "1",
"amount": "10000000000000000000",
"deadline": "1724342400",
data_message: "", // encode
data_message_hash: "", // encode to keccak256(sha3)
data_signature_by: "",
data_signature: "", // use service address to sign encode_hash
};
(async () => {
const result = AbiCoder.encode(["address", "uint256", "uint256", "uint256"], [params.user_address, params.nonce, params.amount, params.deadline]);
params.data_message = result;
params.data_message_hash = ethers.keccak256(result);
// 签名:地址+结果
params.data_signature_by = wallet.address;
params.data_signature = await wallet.signMessage(ethers.getBytes(params.data_message_hash));
console.log('params', params);
const recoveredAddress = ethers.verifyMessage(ethers.getBytes(params.data_message_hash), params.data_signature)
console.log('恢复签名地址', recoveredAddress);
})()