permit-712签名
来源于EIP2612提案,先看文档再做操作 https://eips.ethereum.org/EIPS/eip-2612
合约代码
支持permit-712的代币,DAI,它的核心签名方法代码如下
function permit(address holder, address spender, uint256 nonce, uint256 expiry,
bool allowed, uint8 v, bytes32 r, bytes32 s) external
{
bytes32 digest =
keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH,
holder,
spender,
nonce,
expiry,
allowed))
));
require(holder != address(0), "Dai/invalid-address-0");
require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");
require(expiry == 0 || now <= expiry, "Dai/permit-expired");
require(nonce == nonces[holder]++, "Dai/invalid-nonce");
uint wad = allowed ? uint(-1) : 0;
allowance[holder][spender] = wad;
emit Approval(holder, spender, wad);
}
查看全部代码, https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f#code
钱包签名实现
主要讲解web3实现 看metaMask钱包官方文档 https://docs.metamask.io/guide/signing-data.html#sign-typed-data-v4
web3签名实现
web3签名实现
步骤1、获取 PERMIT_TYPEHASH、DOMAIN_SEPARATOR
先看合约代码,发现PERMIT_TYPEHASH、DOMAIN_SEPARATOR定义都有public修饰符,且PERMIT_TYPEHASH有默认值,DOMAIN_SEPARATOR在初始化时已经赋值
// --- EIP712 niceties ---
bytes32 public DOMAIN_SEPARATOR;
// bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
constructor(uint256 chainId_) public {
wards[msg.sender] = 1;
DOMAIN_SEPARATOR = keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256(bytes(version)),
chainId_,
address(this)
));
}
获取方式1,通过rpc请求取合约数据,PERMIT_TYPEHASH、DOMAIN_SEPARATOR(建议)
const contract = new ClientContract(abi, '0x6b175474e89094c44da98b954eedeac495271d0f', 1)
const calls = [
contract.PERMIT_TYPEHASH(),
contract.DOMAIN_SEPARATOR(),
]
const [PERMIT_TYPEHASH, DOMAIN_SEPARATOR] = await multicallClient(calls)
获取方式2,通过web3Utils进行计算(特殊情况可以使用此方式获取,顺便了解web3获取的方式)
const PERMIT_TYPEHASH = Web3.utils.keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)")
const web3 = new Web3(window.ethereum)
const DOMAIN_SEPARATOR = Web3.utils.keccak256(
web3.eth.abi.encodeParameters(['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'], [
Web3.utils.keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
Web3.utils.keccak256('Dai Stablecoin'),
Web3.utils.keccak256('1'),
1,
'0x6B175474E89094C44Da98b954EedeAC495271d0F'
]))
对于合于名称,version,建议还是直接取合约数据(反正都要取合约数据,还是直接取计算好的hash更方便)
步骤2、获取structHash
先看合约代码获取部分
keccak256(abi.encode(PERMIT_TYPEHASH,holder,spender,nonce,expiry,allowed))
const digestHash = web3.utils.keccak256(web3.eth.abi.encodeParameters(['bytes32', 'address', 'address', 'uint256', 'uint256', 'bool'], [
PERMIT_TYPEHASH,
account,
TWTRDao.address,
nonce,
deadline,
buyTokenData.allowed]
))
web3代码
组合无限授权hash(如果你需要无限授权的话)
const tokenContract = new ClientContract(buyTokenData.abi, buyTokenData.address, ChainId.MAINNET)
const nonce = await multicallClient([
tokenContract.nonces(account)
]).then(data => data[0])
const digestHash = web3.utils.keccak256(web3.eth.abi.encodeParameters(['bytes32', 'address', 'address', 'uint256', 'uint256', 'bool'], [
PERMIT_TYPEHASH,
holder,
spender,
nonce,
expiry,
allowed,
]
))
组合指定量授权的hash(如果你需要有限授权的话)
const tokenContract = new ClientContract(buyTokenData.abi, buyTokenData.address, ChainId.MAINNET)
const nonce = await multicallClient([
tokenContract.nonces(account)
]).then(data => data[0])
const digestHash = web3.utils.keccak256(web3.eth.abi.encodeParameters(['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'], [
PERMIT_TYPEHASH,
holder,
spender,
amount,
nonce,
expiry,
]
))
步骤3、获取签名哈希 structHash
合约代码
bytes32 structHash =
keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH,
holder,
spender,
nonce,
expiry,
allowed))
));
web3代码
const digest = web3.utils.soliditySha3(
{
type: 'string',
value: '\x19\x01'
},
{
type: 'bytes32',
value: DOMAIN_SEPARATOR
},
{
type: 'bytes32',
value: structHash
}
)
步骤4、钱包签名
const signatureHash = await web3.eth.sign(digest, account);
此步骤会弹出钱包签名,用户同意签名之后得出一大串加密哈希,我们需要的是 v, r, s 所以要解析签名
解析签名
import {fromRpcSig} from 'ethereumjs-util'
const signature = fromRpcSig(signature)
node签名
web3签名第四步替换为以下方法即可
const SigningKey = new ethers.utils.SigningKey(私钥)
const signature = SigningKey.signDigest(digest);
|