3分钟Solidity: 9.8 单向支付通道

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

如需获取本内容的最新版本,请参见 Cyfrin.io 上的单向支付通道(代码示例)

支付通道允许参与者在链下反复转移以太币。

该合约的使用方式如下:

  • Alice部署合约,并存入一定数量的以太币。
  • Alice通过离线签署消息授权支付,并将签名发送给Bob
  • Bob向智能合约出示签名消息以申领付款。
  • 如果Bob未申领付款,合约到期后Alice可收回她的以太币。

这种机制被称为单向支付通道,因为资金只能从Alice单向流向Bob

scss 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "./ECDSA.sol";

contract ReentrancyGuard {
    bool private locked;

    modifier guard() {
        require(!locked);
        locked = true;
        _;
        locked = false;
    }
}

contract UniDirectionalPaymentChannel is ReentrancyGuard {
    using ECDSA for bytes32;

    address payable public sender;
    address payable public receiver;

    uint256 private constant DURATION = 7 * 24 * 60 * 60;
    uint256 public expiresAt;

    constructor(address payable _receiver) payable {
        require(_receiver != address(0), "receiver = zero address");
        sender = payable(msg.sender);
        receiver = _receiver;
        expiresAt = block.timestamp + DURATION;
    }

    function _getHash(uint256 _amount) private view returns (bytes32) {
        // NOTE: 签署包含本合约地址的签名以防止对其他合约的重放攻击
        return keccak256(abi.encodePacked(address(this), _amount));
    }

    function getHash(uint256 _amount) external view returns (bytes32) {
        return _getHash(_amount);
    }

    function _getEthSignedHash(uint256 _amount)
        private
        view
        returns (bytes32)
    {
        return _getHash(_amount).toEthSignedMessageHash();
    }

    function getEthSignedHash(uint256 _amount)
        external
        view
        returns (bytes32)
    {
        return _getEthSignedHash(_amount);
    }

    function _verify(uint256 _amount, bytes memory _sig)
        private
        view
        returns (bool)
    {
        return _getEthSignedHash(_amount).recover(_sig) == sender;
    }

    function verify(uint256 _amount, bytes memory _sig)
        external
        view
        returns (bool)
    {
        return _verify(_amount, _sig);
    }

    function close(uint256 _amount, bytes memory _sig) external guard {
        require(msg.sender == receiver, "!receiver");
        require(_verify(_amount, _sig), "invalid sig");

        (bool sent,) = receiver.call{value: _amount}("");
        require(sent, "Failed to send Ether");
        selfdestruct(sender);
    }

    function cancel() external {
        require(msg.sender == sender, "!sender");
        require(block.timestamp >= expiresAt, "!expired");
        selfdestruct(sender);
    }
}
scss 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)

library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        } else if (error == RecoverError.InvalidSignatureV) {
            revert("ECDSA: invalid signature 'v' value");
        }
    }

    function tryRecover(bytes32 hash, bytes memory signature)
        internal
        pure
        returns (address, RecoverError)
    {
        // 检查签名长度
        // - case 65: r,s,v signature (standard)
        // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover 接收签名参数,目前唯一获取这些参数的方式是通过汇编代码。
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else if (signature.length == 64) {
            bytes32 r;
            bytes32 vs;
            // ecrecover函数需要签名参数,目前获取这些参数的唯一方法是使用汇编代码。
            assembly {
                r := mload(add(signature, 0x20))
                vs := mload(add(signature, 0x40))
            }
            return tryRecover(hash, r, vs);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    function recover(bytes32 hash, bytes memory signature)
        internal
        pure
        returns (address)
    {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs)
        internal
        pure
        returns (address, RecoverError)
    {
        bytes32 s = vs
            & bytes32(
                0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
            );
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    function recover(bytes32 hash, bytes32 r, bytes32 vs)
        internal
        pure
        returns (address)
    {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        internal
        pure
        returns (address, RecoverError)
    {
        // EIP-2仍然允许ecrecover()存在签名可塑性。
        // 消除这种可能性,使签名具有唯一性。
        // 以太坊黄皮书(<https://ethereum.github.io/yellowpaper/paper.pdf>)附录F
        // 定义了s的有效范围(第301条):0 < s < secp256k1n ÷ 2 + 1;
        // 以及v的有效范围(第302条):v ∈ {27, 28}。
        // 当前大多数库生成的签名都具有s值位于低半序区间的唯一性特征。
        //
        // 如果你的库生成了可变的签名,例如s值在较高范围内,可以计算一个新的s值
    
        // 使用0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141减去s1,并将v从27翻转为28或
        // 反之亦然。如果你的库还生成了v值为0/1而非27/28的签名,则将v加27以同样接受
        // 这些可变签名。
        if (
            uint256(s)
                > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
        ) {
            return (address(0), RecoverError.InvalidSignatureS);
        }
        if (v != 27 && v != 28) {
            return (address(0), RecoverError.InvalidSignatureV);
        }

        // 如果签名有效(且不可篡改),则返回签名者地址
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
        internal
        pure
        returns (address)
    {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    function toEthSignedMessageHash(bytes32 hash)
        internal
        pure
        returns (bytes32)
    {
        // 32是哈希值的字节长度
        // 由上述类型签名强制执行
        return keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
        );
    }
}

Remix Lite 尝试一下

相关推荐
devmoon2 小时前
在 Polkadot Runtime 中添加多个 Pallet 实例实战指南
java·开发语言·数据库·web3·区块链·波卡
Web3VentureView3 小时前
SYNBO Protocol AMA回顾:下一个起点——什么将真正推动比特币重返10万美元?
大数据·人工智能·金融·web3·区块链
devmoon6 小时前
在 Polkadot 链上添加智能合约功能全指南
安全·区块链·智能合约·polkadot·erc-20·测试网·独立链
REDcker6 小时前
Web1 到 Web3 技术演进详解
web3
devmoon18 小时前
Polkadot SDK 平行链模板搭建全流程指南
web3·区块链·sdk·比特币·波卡
China_Yanhy19 小时前
入职 Web3 运维日记 · 第 8 日:黑暗森林 —— 对抗 MEV 机器人的“三明治攻击”
运维·机器人·web3
傻小胖1 天前
22.ETH-智能合约-北大肖臻老师客堂笔记
笔记·区块链·智能合约
devmoon2 天前
使用 Hardhat 在 Polkadot Hub 测试网部署基础 Solidity 合约(完整实战指南)
web3·区块链·智能合约·波卡·hardhat
devmoon2 天前
快速了解兼容 Ethereum 的 JSON-RPC 接口
开发语言·网络·rpc·json·区块链·智能合约·polkadot
devmoon2 天前
用Remix IDE在Polkadot Hub部署一个最基础的Solidity 合约(新手友好)
web3·区块链·智能合约·编译·remix·polkadot