Java EIP721 链下签名
java 部分的逻辑
package com.swapbot.swapbot;
import org.bouncycastle.util.encoders.Hex;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Sign;
import org.web3j.utils.Numeric;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class PermitHashExample {
public static void main(String[] args) {
String typeString = "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)";
byte[] typeHash = Hash.sha3(typeString.getBytes(StandardCharsets.UTF_8));
//代币名称
String name = "YHB Token";
//版本号
String version = "1";
//链 ID
BigInteger chainId = BigInteger.valueOf(31337);
//代币地址
String verifyingContract = "0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f";
//我的地址
String ownerAddress = "0xb24F05c7Fa8334999e4F15B06e63F0FA93D66138";
//私钥
String privateKeyHex = "bdded67b63f8b619540ece39c147ce9e7feb267f3af2122ea045c3cb3e3f4a7e";
//授权对方的地址
String spenderAddress = "0x2e234dae75c793f67a35089c9d99245e1c58470b";
//授权数量
BigInteger amount = new BigInteger("1000000000000000000");
//nonce
BigInteger nonce = BigInteger.ZERO;
//deadline
BigInteger deadline = BigInteger.valueOf(86401);
String domainSeparator = getDomainSeparator(name, version, verifyingContract, chainId);
String permitHashHex = calculatePermitHash(domainSeparator, typeHash, ownerAddress, spenderAddress, amount, nonce, deadline);
System.out.println("Permit Hash: " + permitHashHex);
sign( permitHashHex, privateKeyHex);
}
//计算PermitHash
public static String calculatePermitHash(String domainSeparator, byte[] typeHash, String owner, String spender, BigInteger value, BigInteger nonce, BigInteger deadline) {
byte[] ownerBytes = Numeric.toBytesPadded(Numeric.toBigInt(owner), 32);
byte[] spenderBytes = Numeric.toBytesPadded(Numeric.toBigInt(spender), 32);
byte[] valueBytes = Numeric.toBytesPadded(value, 32);
byte[] nonceBytes = Numeric.toBytesPadded(nonce, 32);
byte[] deadlineBytes = Numeric.toBytesPadded(deadline, 32);
ByteBuffer buffer = ByteBuffer.allocate(typeHash.length + ownerBytes.length + spenderBytes.length + valueBytes.length + nonceBytes.length + deadlineBytes.length);
buffer.put(typeHash);
buffer.put(ownerBytes);
buffer.put(spenderBytes);
buffer.put(valueBytes);
buffer.put(nonceBytes);
buffer.put(deadlineBytes);
byte[] innerHash = Hash.sha3(buffer.array());
byte[] domainSeparatorBytes = Numeric.hexStringToByteArray(domainSeparator);
ByteBuffer finalBuffer = ByteBuffer.allocate(2 + domainSeparatorBytes.length + innerHash.length);
finalBuffer.put((byte) 0x19);
finalBuffer.put((byte) 0x01);
finalBuffer.put(domainSeparatorBytes);
finalBuffer.put(innerHash);
byte[] permitHash = Hash.sha3(finalBuffer.array());
return Numeric.toHexString(permitHash);
}
/**
* @param permitHash
* @param privateKeyHex 私钥
*/
public static void sign(String permitHash, String privateKeyHex) {
// 替换为你的私钥和哈希值
// String privateKeyHex = "bdded67b63f8b619540ece39c147ce9e7feb267f3af2122ea045c3cb3e3f4a7e";
//你的私钥(十六进制)
// String hashHex = "0x5c27f2310bce9338a20792c7d53b4951c095434a8fe45c0f183b287dabb70e2a";//你的哈希值(十六进制)
BigInteger privateKey = new BigInteger(privateKeyHex, 16);
byte[] hash = Hex.decode(Numeric.cleanHexPrefix(permitHash));
ECKeyPair keyPair = ECKeyPair.create(privateKey);
Sign.SignatureData signature = Sign.signMessage(hash, keyPair, false);
int v = signature.getV()[0] & 0xFF;
byte[] r = signature.getR();
byte[] s = signature.getS();
System.out.println("v: " + v);
System.out.println("r: " + Numeric.toHexString(r));
System.out.println("s: " + Numeric.toHexString(s));
}
/**
* 计算domainSeparator
*
* @param name 合约名称
* @param version 版本号,固定
* @param verifyingContract 代币合约地址
* @param chainId 链 ID
* @return
*/
public static String getDomainSeparator(String name, String version, String verifyingContract, BigInteger chainId) {
byte[] domainTypeHash = Hash.sha3("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)".getBytes());
byte[] nameHash = Hash.sha3(name.getBytes());
byte[] versionHash = Hash.sha3(version.getBytes());
byte[] contractAddress = Numeric.hexStringToByteArray(verifyingContract);
return Numeric.toHexString(Hash.sha3(Numeric.hexStringToByteArray(
Numeric.toHexString(domainTypeHash)
+ Numeric.cleanHexPrefix(Numeric.toHexString(nameHash))
+ Numeric.cleanHexPrefix(Numeric.toHexString(versionHash))
+ Numeric.toHexStringNoPrefixZeroPadded(chainId, 64)
+ Numeric.toHexStringNoPrefixZeroPadded(new BigInteger(1, contractAddress), 64)
)));
}
}
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract TokenBank {
IERC20 public token;
mapping(address => uint256) public balances;
// 定义事件
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
constructor(address _tokenAddress) {
token = IERC20(_tokenAddress);
}
function deposit(uint256 amount) public {
require(amount > 0, "Amount must be greater than 0");
require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
balances[msg.sender] += amount;
// 触发存款事件
emit Deposit(msg.sender, amount);
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
require(token.transfer(msg.sender, amount), "Transfer failed");
// 触发取款事件
emit Withdraw(msg.sender, amount);
}
// 新增 permitDeposit 函数
function permitDeposit(
address owner,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// 调用ERC20Permit的permit方法进行授权
ERC20Permit(address(token)).permit(owner, address(this), value, deadline, v, r, s);
// 存款逻辑
require(token.transferFrom(owner, address(this), value), "Transfer failed");
balances[owner] += value;
// 触发存款事件
emit Deposit(owner, value);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract YHBToken is ERC20Permit, Ownable {
constructor() ERC20("YHB Token", "YHB") ERC20Permit("YHB Token") Ownable(msg.sender) {
_mint(msg.sender, 1000000 * 10 ** decimals());
}
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
function getPermitTypehash() public pure returns (bytes32) {
return keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
}
function getDomainSeparator() public view returns (bytes32) {
return _domainSeparatorV4();
}
}
测试用例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../src/TokenBank.sol";
import "../src/YHBToken.sol";
import {Test, console} from "forge-std/Test.sol";
contract TokenBankTest is Test {
TokenBank public bank;
YHBToken public token;
address public owner;
address public user1;
uint256 private ownerPrivateKey;
function setUp() public {
bytes32 privateKeyBytes = 0xbdded67b63f8b619540ece39c147ce9e7feb267f3af2122ea045c3cb3e3f4a7e;
ownerPrivateKey = uint256(privateKeyBytes);
owner = vm.addr(ownerPrivateKey);
user1 = address(0x1);
// 部署ERC20合约
token = new YHBToken();
// 部署TokenBank合约
bank = new TokenBank(address(token));
// 给测试合约的地址分配一些token
token.mint(owner, 1000 * 10 ** token.decimals());
}
// function testDeposit() public {
// uint256 amount = 100 * 10 ** token.decimals();
//
// // 先授权给TokenBank合约
// vm.prank(owner);
// token.approve(address(bank), amount);
//
// // 调用TokenBank的deposit函数
// vm.prank(owner);
// bank.deposit(amount);
//
// uint256 bankBalance = token.balanceOf(address(bank));
// console.log(unicode"银行的金额为:", bankBalance);
// // 检查TokenBank合约的余额和用户的存款
// assertEq(bankBalance, amount);
// assertEq(bank.balances(owner), amount);
// console.log(unicode"执行完成:", "1");
// }
function testPermitDeposit() public {
uint256 amount = 1e18;
uint256 nonce = token.nonces(owner);
console.logString("nonce:");
console.logUint(nonce);
uint256 deadline = block.timestamp + 1 days;
console.logString("deadline:");
console.logUint(deadline);
bytes32 typeHash = token.getPermitTypehash();
console.logString("typeHash:");
console.logBytes32(typeHash);
bytes32 separator = token.getDomainSeparator();
console.logString("separator:");
console.logBytes32(separator);
console.logString(unicode"链ID:");
console.logUint(block.chainid);
console.logString(unicode"owner:");
console.logAddress(owner);
console.logString(unicode"spender:");
console.logAddress(address(bank));
bytes32 permitHash = keccak256(
abi.encodePacked(
"\x19\x01",
separator,
keccak256(abi.encode(
typeHash,
owner,
address(bank),
amount,
nonce,
deadline
))
)
);
console.logString(unicode"permitHash:");
console.logBytes32(permitHash);
console.logString(unicode"ownerPrivateKey:");
console.logUint(ownerPrivateKey);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, permitHash);
console.log("v: %d", v);
console.logBytes32(r);
console.logBytes32(s);
// 调用TokenBank的permitDeposit函数
vm.prank(owner);
bank.permitDeposit(owner, amount, deadline, v, r, s);
// 检查TokenBank合约的余额和用户的存款
assertEq(token.balanceOf(address(bank)), amount);
assertEq(bank.balances(owner), amount);
console.log(unicode"执行完成:", "2");
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!