Solidity:编写一个简单的支付通道

目录

 什么是支付通道?

注解

打开支付通道

进行支付

关闭状态通道

通道有效期

代码


 什么是支付通道?

支付通道允许在无需发生交易的情况下多次转移以太。这意味着可以避免与交易相关的延迟和费用。 我们将探讨两方(Alice和Bob)之间的简单单向支付通道。 它涉及三个步骤:

  1. Alice 附加一些以太创建智能合约,可以称为“打开”了支付通道
  2. Alice会签署一些消息指明给接收者付款金额。 每次付款都会重复此步骤。
  3. Bob“关闭”支付通道,取回以太币,并将剩余部分发送回发送者。

注解

只有步骤1和3需要以太坊交易,步骤2意味着发送者通过离线方法(例如电子消息)将加密签名的消息发送给接收者。 这意味着只需要两个交易就可以支持任意数量(次数)的以太币转账。

Bob 保证会收到资金,因为智能合约托管以太并根据合法的签名消息来执行。 合约还可以强制超时执行,即使收款人拒绝关闭通道,Alice也能保证最终收回资金。 付款通道的参与者可以决定支付通道打开的持续时间。 对于短期交易,例如为网络访问的每一分钟支付一次网费,或者是长期的,例如向员工支付小时工资,支付可能持续数月或数年。

打开支付通道

要打开支付通道,Alice 需要部署智能合约,附加要托管的以太币并指定预期的收款人,以及通道存在有效时间。 合约的 SimplePaymentChannel 函数就是来做这个事情,代码在本节末尾。

进行支付

Alice 通过向 Bob 发送签名消息来付款。该步骤完全在以太坊网络之外执行。 消息由发送者以加密方式签名,然后直接传输给收款人。

每条消息都包含以下信息:

  • 智能合约的地址,用于防止交叉合约重放攻击。
  • 到目前为止所发送的以太总量。

在一系列转账结束时,付款通道仅需关闭一次。因此,只有一条消息被兑换。 这就是为什么每条消息都指定了以太的累计总量,而不是每次的微支付金额。 收款人自然而然的会选择兑换最新消息,因为这是以太总数最高的消息。 每条信息包含的nonce 将不再需要,因为智能合约仅执行一条信息。

包含合约地址用于防止一个支付通道的消息被用于不同的通道。

以下是修改后的JavaScript代码,用于对上一节中的消息进行加密签名:

function constructPaymentMessage(contractAddress, amount) {
    return abi.soliditySHA3(
        ["address", "uint256"],
        [contractAddress, amount]
    );
}

function signMessage(message, callback) {
    web3.eth.personal.sign(
        "0x" + message.toString("hex"),
        web3.eth.defaultAccount,
        callback
    );
}

// contractAddress is used to prevent cross-contract replay attacks.
// amount, in wei, specifies how much Ether should be sent.

function signPayment(contractAddress, amount, callback) {
    var message = constructPaymentMessage(contractAddress, amount);
    signMessage(message, callback);
}

关闭状态通道

当Bob准备好收到他们的资金时,就可以通过调用智能合约上的 关闭 功能来关闭支付通道。 关闭通道会向接收方支付所欠的以太币并销毁合约,剩余的以太币返回Alice。为了关闭通道,Bob需要提供 Alice 签名过的消息。

智能合约必须验证信息是否包含发送者的有效签名。执行此验证的过程与上面收款人使用的方法相同。 Solidity函数 isValidSignature 和 recoverSigner 就是完成这个工作。

只有付款通道收款人可以调用 close 函数,其会选择最近的付款消息,因为该消息有最高的付款总额。 如果允许发送者调用此函数,他们可以提供较低金额的消息,来欺骗收款人。

函数会验证签名的消息是否与给定的参数匹配,如果匹配,收款人将收到应得的部分,余下的部分通过 selfdestruct 返还给发送者。 可以在完整的合约代码中看到 close 函数。

通道有效期

Bob可以随时关闭支付通道,但如果他没有这样做,Alice 需要一种方法来收回他们托管的资金。 一个方法是在合约部署时设置 到期时间 ,一旦达到那个时间,Alice 就可以调用 claimTimeout 收回他们的资金。 可以在完整的合约代码中查看 claimTimeout 函数。

调用此功能后,Bob无法再接收任何以太币,因此,Bob必须在到期前关闭频道。

代码

pragma solidity >=0.7.0 <0.9.0;

contract SimplePaymentChannel {
    address payable public sender;      // 发送者
    address payable public recipient;   // 接收者
    uint256 public expiration;  // 到期时间

    constructor (address payable recipientAddress, uint256 duration)
        public
        payable
    {
        sender = payable(msg.sender);
        recipient = recipientAddress;
        expiration = block.timestamp + duration;
    }

    //判断是否是有效的签名
    function isValidSignature(uint256 amount, bytes memory signature)
        internal
        view
        returns (bool)
    {
        bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount)));

        // 检查签名是否来自支付发送方 
        return recoverSigner(message, signature) == sender;
    }

    //关闭支付通道
    /// 接收方可以在任何时候关闭通道  
    /// 发送人指定签名金额, 接收人会收到这笔钱,  
    /// 剩下的将返回给发送者 
    function close(uint256 amount, bytes memory signature) external {
        require(msg.sender == recipient);//只有接收人可以关闭支付通道
        require(isValidSignature(amount, signature));//验证签名和金额

        recipient.transfer(amount);//接收人获得金额
        selfdestruct(sender);//合约自毁
    }

    /// 发送者可以在任何时候延长过期时间 
    function extend(uint256 newExpiration) external {
        require(msg.sender == sender);//只有发送者可以操作
        require(newExpiration > expiration);//新的时间要大于原来的时间才叫延长

        expiration = newExpiration;
    }

    /// 如果过期过期时间已到,而收款人没有关闭通道,可执行此函数,销毁合约并返还余额
    function claimTimeout() external {
        require(block.timestamp >= expiration);
        selfdestruct(sender);
    }

    //将被处理过的签名字段重新分割恢复原来的三部分
    function splitSignature(bytes memory sig)
        internal
        pure
        returns (uint8 v, bytes32 r, bytes32 s)
    {
        require(sig.length == 65);
        //内联汇编
        assembly {
            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
            // final byte (first byte of the next 32 bytes)
            v := byte(0, mload(add(sig, 96)))
        }

        return (v, r, s);
    }

    //根据消息和签名恢复发送者的地址
    function recoverSigner(bytes32 message, bytes memory sig)
        internal
        pure
        returns (address)
    {
        (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);

        return ecrecover(message, v, r, s);
    }

    /// duration构建一个带前缀的散列来模拟eth_sign的行为。
    function prefixed(bytes32 hash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值