使用 Solidity 通过智能合约实现单向支付功能

在很多业务场景中,我们不需要双向支付功能,只约定单向支付功能——即资产只能从账户A发送到账户B中,而不能从账户B向账户A支付。 我们称这种付款方式为单向支付通道。

本文我们将使用 Solidity 语言编码实现单向支付功能,并且鉴于安全性考虑我们在实现中引入智能合约

1. 实现过程

1.1 创建智能合约

处于安全性考虑,为防止重放攻击及重入攻击我们将首先创建一个简单的智能合约,添加一些依赖项来安全地签署交易。这些依赖项来自 OpenZeppelin 合约,本库主要用于安全智能合约的开发。

Solidity 实现智能合约源码:

pragma solidity >=0.7.0 <0.9.0;

import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/cryptography/ECDSA.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/utils/ReentrancyGuard.sol";

contract UniDirectionalPaymentChannel is ReentrancyGuard {
    using ECDSA for bytes32;

    address payable public sender;
    address payable public receiver;

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

1.2 签署支付

我们在本步骤将创建一些函数,用于从发送方签署付款。

我们已经创建了4个不同的函数用于哈希加密并完成哈希向 EthSignedMessageHash 的转换。为减少 gas 的使用量我们在这里将使用私有函数来提升代码的可复用性。

pragma solidity >=0.7.0 <0.9.0;

import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/cryptography/ECDSA.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/utils/ReentrancyGuard.sol";

contract UniDirectionalPaymentChannel is ReentrancyGuard {
    
    // ...

    function _getHash(uint _amount) private view returns (bytes32) {
        return keccak256(abi.encodePacked(address(this), _amount));
    }

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

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

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

    // ...
}

1.3 对哈希进行验证

本步骤我们将创建一个逆函数来“取消签名”交易用于验证接收方哈希。因此,当我们恢复签名时,我们应该获得发送方地址作为结果,用以验证付款是否已经从发送方地址发出。

pragma solidity >=0.7.0 <0.9.0;

import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/cryptography/ECDSA.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/utils/ReentrancyGuard.sol";

contract UniDirectionalPaymentChannel is ReentrancyGuard {
    
    // ...

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

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

}

1.4 交易处理(Transaction)

上个步骤中,我们已经能够从一边到另一边签署和恢复信息。因此,我们就可以安全的进行支付交易了。

pragma solidity >=0.7.0 <0.9.0;

import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/cryptography/ECDSA.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/utils/ReentrancyGuard.sol";

contract UniDirectionalPaymentChannel is ReentrancyGuard {

    // ...
    
    function send(uint _amount, bytes memory _sig) external nonReentrant {
        //  verifing signature and sender != receiver
        require(msg.sender == receiver, "sender must be different than receiver");
        require(_verify(_amount, _sig), "invalid signature");
        
        (bool sent, ) = receiver.call{value: _amount}("");
        require(sent, "Failed to send Ether");
        selfdestruct(sender);
    }
    
    // ...
 }

2. 完整源码

Solidity 通过智能合约实现单向支付功能完整代码如下。

pragma solidity >=0.7.0 <0.9.0;

import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/cryptography/ECDSA.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/utils/ReentrancyGuard.sol";

contract UniDirectionalPaymentChannel is ReentrancyGuard {
    using ECDSA for bytes32;

    address payable public sender;
    address payable public receiver;

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

    function _getHash(uint _amount) private view returns (bytes32) {
        return keccak256(abi.encodePacked(address(this), _amount));
    }

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

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

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

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

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

    function send(uint _amount, bytes memory _sig) external nonReentrant {
        //  verifing signature and sender != receiver
        require(msg.sender != receiver, "sender must be different than receiver");
        require(_verify(_amount, _sig), "invalid signature");
        
        (bool sent, ) = receiver.call{value: _amount}("");
        require(sent, "Failed to send Ether");
        selfdestruct(sender);
    }
}

您可以直接下载以上代码 contract.sol

区块链技术能够让我们同时获得金融领域的知识,您需要了解这些过程,因此非常具有挑战性。同时,它也具有十足的趣味性。