Java EIP721 链下签名

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");
    }
}
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
没那么简单
没那么简单
江湖只有他的大名,没有他的介绍。