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