前言本文主要实现EIP712类型化数据签名的智能合约的开发、测试、部署、交互,测试过程:涉及到前端通过ethers库和合约以及钱包的交互;EIP712类型化数据签名定义:一种以太坊改进提案,旨在提供一种更高级、更安全的类型化数据签名方法;背景与重要性链下签名,链上验证:EIP712
本文主要实现EIP721类型化数据签名的智能合约的开发、测试、部署、交互,测试过程:涉及到前端通过ethers库和合约以及钱包的交互;
定义:一种以太坊改进提案,旨在提供一种更高级、更安全的类型化数据签名方法;
// SPDX-License-Identifier: MIT
// By 0xAA
pragma solidity ^0.8.0;
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract EIP712Storage {
using ECDSA for bytes32;
bytes32 private constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
bytes32 private constant STORAGE_TYPEHASH = keccak256("Storage(address spender,uint256 number)");
bytes32 private DOMAIN_SEPARATOR;
uint256 number;
address owner;
constructor(){
DOMAIN_SEPARATOR = keccak256(abi.encode(
EIP712DOMAIN_TYPEHASH, // type hash
keccak256(bytes("EIP712Storage")), // name
keccak256(bytes("1")), // version
block.chainid, // chain id
address(this) // contract address
));
owner = msg.sender;
}
/**
* @dev Store value in variable
*/
function permitStore(uint256 _num, bytes memory _signature) public {
// 检查签名长度,65是标准r,s,v签名的长度
require(_signature.length == 65, "invalid signature length");
bytes32 r;
bytes32 s;
uint8 v;
// 目前只能用assembly (内联汇编)来从签名中获得r,s,v的值
assembly {
/*
前32 bytes存储签名的长度 (动态数组存储规则)
add(sig, 32) = sig的指针 + 32
等效为略过signature的前32 bytes
mload(p) 载入从内存地址p起始的接下来32 bytes数据
*/
// 读取长度数据后的32 bytes
r := mload(add(_signature, 0x20))
// 读取之后的32 bytes
s := mload(add(_signature, 0x40))
// 读取最后一个byte
v := byte(0, mload(add(_signature, 0x60)))
}
// 获取签名消息hash
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(STORAGE_TYPEHASH, msg.sender, _num))
));
address signer = digest.recover(v, r, s); // 恢复签名者
require(signer == owner, "EIP712Storage: Invalid signature"); // 检查签名
// 修改状态变量
number = _num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}
# 编译指令
# npx hardhat compile
说明:部署成功后会生成一个部署合约的地址(例:EIP712Storage合约地址: 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853)也会生成一个json文件,测试时会使用到
module.exports = async function ({getNamedAccounts,deployments}) {
const firstAccount = (await getNamedAccounts()).firstAccount;
const {deploy,log} = deployments;
const EIP712Storage=await deploy("EIP712Storage",{
contract: "EIP712Storage",
from: firstAccount,
args: [],
log: true,
// waitConfirmations: 1,
})
console.log("EIP712Storage合约地址",EIP712Storage.address)
}
module.exports.tags = ["all", "EIP712Storage"]
# 部署指令
# npx hardhat deploy
合约部分
npx hardhat node
会获取20个包含10000 ETH的测试账户npx hardhat deploy
会返回一个合约地址和会在artifacts/contracts/下生成一个json文件前端部分
npm start
import React,{useState,useEffect}from "react";
import { ethers ,JsonRpcProvider,BrowserProvider} from "ethers";//
import EIP712Storage from "../json/EIP712Storage.json";//合约abi
let provider,signer,account,balance,chainid,signature,owner;
let contranAddress="0xa513E6E4b8f2a923D98304ec87F64353C4D5C853";//合约地址
const EIP712StorageApp= ()=>{
let [account,setAccount]=useState("0");//钱包地址
let [balance,setBalance]=useState(0);//钱包余额
let [chainid,setChainid]=useState("0");//链id
let [signature,setSignature]=useState();//签名信息
// 链接钱包
const connectWallet=async ()=>{
if(window.ethereum){
provider = new ethers.BrowserProvider(window.ethereum);
signer = await provider.getSigner();//签名者
account = await signer.getAddress();
setAccount(account)
balance = await provider.getBalance(account);//余额
setBalance(balance.toString())
chainid = await provider.getNetwork();//链id
setChainid(chainid.chainId.toString())
owner = await signer.getAddress();//所有者
console.log(signer,account,balance,chainid)
}else{
alert("请安装钱包")
}
}
//获取签名
const signPermit=async ()=>{
const contract = new ethers.Contract(contranAddress, EIP712Storage.abi, signer);
console.log("合约地址",await contract.getAddress())//合约
const domain = {
name: "EIP712Domain",
version: "1",
chainId: chainid,
verifyingContract: contranAddress,
};
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
};
const value = {
owner: account,
spender: account,
value: ethers.parseEther("22"),//账22个
nonce: 0,
deadline: Math.floor(Date.now() / 1000) + 60 * 60 * 24,
};
const signature = await signer.signTypedData(domain, types, value);//签名信息
console.log(signature)
setSignature(signature)
}
useEffect(()=>{
connectWallet();//自动链接钱包
},[])
return(
<div>
<h1 className={`text-center font-black text-4xl`}>EIP712前端测试</h1>
<button className={`w-28 h-10 p-2 rounded m-4 bg-blue-500 text-center text-white hover:bg-blue-700`} onClick={connectWallet}>链接钱包</button>
<button className={`w-28 h-10 p-2 rounded m-4 bg-blue-500 text-center text-white hover:bg-blue-700`} onClick={signPermit}>签名消息</button>
{/* <h1 onClick={massage}>钱包信息</h1> */}
<ul className="ml-4">
<li className={`mb-4`}><span className={`font-black`}>地址:</span>{account}</li>
<li className={`mb-4`}><span className={`font-black`}>余额:</span>{balance} USDT</li>
<li className={`mb-4`}><span className={`font-black`}>chainid:</span>{chainid}</li>
<li className={`mb-4`}><span className={`font-black`}>签名信息:</span>{signature}</li>
</ul>
</div>
)
}
export default EIP712StorageApp
功能说明:以上实现了一个链接钱包和获取数字签名hash功能的前端界面,打开界面自动链接钱包
npm start
启动项目自动链接MataMask钱包界面<br>
如何所示:以上就是EIP712 类型化数据签名合约的开发测试部署全部看流程,注意
:启动npx hardhat node 节点后,需要把账号的私钥导入到metamask插件中。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!