如何实现以太坊支付

在这篇文章中,我将实现一个简单但完整的以太坊支付通道。支付通道使用密码签名,以安全、即时、无交易费用重复地传送Ether。

什么是支付通道?

以太坊交易提供了一种安全的方式来转账,但每个交易需要被包括在一个区块中和并被挖掘。这意味着交易需要一些时间,并要求支付一些费用来补偿矿工的工作。特别是,这个交易费用使得其产生的这种小额支付,成为了以太坊和其他类似于它的区块链的使用,变得有点儿费劲一个原因。

支付通道允许参与者在不使用交易的情况下重复发送Ether。这意味着可以避免与交易相关的延迟和因此产生费用。在这篇文章中,我们将探讨一个简单的单向支付通道。这包括三个步骤:

  • 1.发送者用Ether支付一个智能合约。这会打开支付通道。
  • 2.发送者签署消息,指明该ether中应向接收者支付多少。对于每个支付,都重复这一步骤。
  • 3.接收者关闭支付通道,收取他们的那部分ether,并将其余部分返回发送者。

重要的是,只有步骤1和步骤3需要空缺交易。步骤2通过密码签名和两方之间的通信(如电子邮件)完成。这意味着只需要两个交易来支持任何数量的发送。

收件人保证收到他们的资金,因为智能合约托管了ether并认可有效签署的消息。智能合约还强制执行直到截止时间,而且发送方有权收回资金,即使接收方拒绝关闭支付通道。

这取决于支付通道的参与者决定多长时间保持开放。对于短时间的交互,例如对于提供网络服务按每分钟支付的网吧,使用只持续一个小时左右的支付通道就足够了。对于一个较长期的支付关系,比如给员工支付按小时计的工资,支付通道可以持续数月或数年。

打开支付通道

为了打开支付通道,发送方部署智能合约,ether也将被托管,并指定接收方和通道存在的最晚截止时间。

contract SimplePaymentChannel {
    address public sender;     // The account sending payments.
    address public recipient;  // The account receiving the payments.
    uint256 public expiration; // Timeout in case the recipient never closes.

    function SimplePaymentChannel(address _recipient, uint256 duration)
        public
        payable
    {
        sender = msg.sender;
        recipient = _recipient;
        expiration = now + duration;
    }

支付款项

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

每个消息包括以下信息:

  • 智能合约的地址,用来防止跨合约replay攻击。
  • 迄今为止,接受者所消耗的ether总量。

在一系列转账结束时,支付通道只关闭一次。正因为如此,只有一个发送的消息将被赎回。这就是为什么每个消息都指定了累积的Ether消耗总量,而不是单个微支付的量。接收者自然会选择赎回最近的消息,因为这是一个总拥有最高ether的消息。

请注意,因为智能合约仅对单个消息进行维护,所以不需要每个临时消息。智能合约的地址仍然用于防止用于一个支付通道的消息被用于不同的通道。

可以用支持加密的hash和签名操作的任何语言构建和签名支付相应的消息。下面的代码是用JavaScript编写的,并且使用ethereumjs-abi

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

function signMessage(message, callback) {
  web3.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);
}

核实付款

与签名不同,支付通道中的消息不会立即被赎回。接收方跟踪最新消息并在关闭支付通道时赎回。这意味着接收方对每个消息进行自己的验证是至关重要的。否则,不能保证收件人最终能得到报酬。

接收方应使用以下过程验证每个消息:

  • 1.验证消息中的合约地址与支付通道相匹配。
  • 2.验证新合计是否为预期金额。
  • 3.验证新的总量不超过ether的量。
  • 4.验证签名是否有效,并来自支付通道发送者。

前三个步骤很简单。最后一步可以通过多种方式执行,但是如果它在JavaScript中完成,我推荐ethereumjs-util库。下面的代码从上面的签名代码中借用constructMessage函数:

// This mimics the prefixing behavior of the eth_sign JSON-RPC method.
function prefixed(hash) {
  return ethereumjs.ABI.soliditySHA3(
    ["string", "bytes32"],
    ["\x19Ethereum Signed Message:\n32", hash]
  );
}

function recoverSigner(message, signature) {
  var split = ethereumjs.Util.fromRpcSig(signature);
  var publicKey = ethereumjs.Util.ecrecover(message, split.v, split.r, split.s);
  var signer = ethereumjs.Util.pubToAddress(publicKey).toString("hex");
  return signer;
}

function isValidSignature(contractAddress, amount, signature, expectedSigner) {
  var message = prefixed(constructPaymentMessage(contractAddress, amount));
  var signer = recoverSigner(message, signature);
  return signer.toLowerCase() ==
    ethereumjs.Util.stripHexPrefix(expectedSigner).toLowerCase();
}

关闭支付通道

当接受者准备好接收他们的资金时,是时候通过在智能合约上调用close功能来关闭支付通道。关闭通道给接收者,他们获得自己的ether并销毁合约,发送剩余的Ether回发送者。要关闭通道,接收方需要共享由发送方签名的消息。

智能合约必须验证消息包含来自发送者的有效签名。进行此验证的过程与接收方使用的过程相同。isValidSignaturerecoverSigner函数与前一部分中的JavaScript代码对应。后者是在Signing and Verifying Messages in Ethereum中从ReceiverPays合约中copy来的。

function isValidSignature(uint256 amount, bytes signature)
    internal
    view
    returns (bool)
{
    bytes32 message = prefixed(keccak256(this, amount));

    // Check that the signature is from the payment sender.
    return recoverSigner(message, signature) == sender;
}

// The recipient can close the channel at any time by presenting a signed
// amount from the sender. The recipient will be sent that amount, and the
// remainder will go back to the sender.
function close(uint256 amount, bytes signature) public {
    require(msg.sender == recipient);
    require(isValidSignature(amount, signature));

    recipient.transfer(amount);
    selfdestruct(sender);
}

关闭功能只能由支付通道接收者来调用,而接收者自然会传递最新的支付消息,因为该消息具有最高的总费用。如果发送者被允许调用这个函数,他们可以提供一个较低费用的消息,并欺骗接收者。

函数验证签名的消息与给定的参数匹配。如果一切都被检测出来,收件人就发送了他们的部分ether,发送者通过selfdestruct发送其余部分。

关闭支付通道

接收方可以在任何时候关闭支付通道,但是如果他们不这样做,发送者需要一种方法来收回他们的托管资金。在合约部署时设置了expiration时间。一旦到达该时间,发送方可以调用claimTimeout来恢复其资金。

// If the timeout is reached without the recipient closing the channel, then
// the ether is released back to the sender.
function claimTimeout() public {
    require(now >= expiration);
    selfdestruct(sender);
}

在这个函数被调用之后,接收者再也不能接收任何ether,所以接收者在到达期满之前关闭通道是很重要的。

总结

  • 支付通道支持安全的、区块链外的资金转移,同时避免每次转账产生交易费用。
  • 付款是累积的,只有一个是在关闭频道时赎回的。
  • 转账是通过托管资金和密码签名来保证的。
  • 超时保护发送者的资金免受不合作的接收者的影响。

完整源代码,simplePaymentChannel.sol

pragma solidity ^0.4.20;

contract SimplePaymentChannel {
    address public sender;     // The account sending payments.
    address public recipient;  // The account receiving the payments.
    uint256 public expiration; // Timeout in case the recipient never closes.

    function SimplePaymentChannel(address _recipient, uint256 duration)
        public
        payable
    {
        sender = msg.sender;
        recipient = _recipient;
        expiration = now + duration;
    }

    function isValidSignature(uint256 amount, bytes signature)
        internal
        view
        returns (bool)
    {
        bytes32 message = prefixed(keccak256(this, amount));

        // Check that the signature is from the payment sender.
        return recoverSigner(message, signature) == sender;
    }

    // The recipient can close the channel at any time by presenting a signed
    // amount from the sender. The recipient will be sent that amount, and the
    // remainder will go back to the sender.
    function close(uint256 amount, bytes signature) public {
        require(msg.sender == recipient);
        require(isValidSignature(amount, signature));

        recipient.transfer(amount);
        selfdestruct(sender);
    }

    // The sender can extend the expiration at any time.
    function extend(uint256 newExpiration) public {
        require(msg.sender == sender);
        require(newExpiration > expiration);

        expiration = newExpiration;
    }

    // If the timeout is reached without the recipient closing the channel, then
    // the ether is released back to the sender.
    function claimTimeout() public {
        require(now >= expiration);
        selfdestruct(sender);
    }

    function splitSignature(bytes sig)
        internal
        pure
        returns (uint8, bytes32, bytes32)
    {
        require(sig.length == 65);

        bytes32 r;
        bytes32 s;
        uint8 v;

        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 sig)
        internal
        pure
        returns (address)
    {
        uint8 v;
        bytes32 r;
        bytes32 s;

        (v, r, s) = splitSignature(sig);

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

    // Builds a prefixed hash to mimic the behavior of eth_sign.
    function prefixed(bytes32 hash) internal pure returns (bytes32) {
        return keccak256("\x19Ethereum Signed Message:\n32", hash);
    }
}

=========================================================================

如果你希望快速的开始使用.net和C#开发以太坊应用,那这个我们进行打造的课程会很有帮助:

C#以太坊

如果是其他语言开发以太坊应用的也可以参考以下教程:

  • java以太坊教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。

这里是原文

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏liuchengxu

用 Go 构建一个区块链 -- Part 7: 网络

翻译的系列文章我已经放到了 GitHub 上:blockchain-tutorial,后续如有更新都会在 GitHub 上,可能就不在这里同步了。如果想直接运行...

1093
来自专栏区块链入门

第十三课 如何在DAPP应用实现自带钱包转账功能?

区块链是一个伟大的发明,它改变了生产关系。很多生态,有了区块链技术,可以由全公司员工的"全员合伙人"变成了全平台的”全体合伙人”了,是真正的共享经济模式。

1656
来自专栏Seebug漏洞平台

金钱难寐,大盗独行——以太坊 JSON-RPC 接口多种盗币手法大揭秘

2010年,Laszlo 使用 10000 个比特币购买了两张价值25美元的披萨被认为是比特币在现实世界中的第一笔交易。

1052
来自专栏区块链入门

第七课 技术小白如何在45分钟内发行通证(TOKEN)并上线交易

通过逐步的指导和截图举证,一步步带领一个技术小白完成一个数字货币(通证,代币,TOKEN)的发布演示和上线交易。

1352
来自专栏区块链领域

以太坊潜伏多年令全球黑客为之疯狂的“偷渡”漏洞引发偷币狂潮

世界上有一群人,互联网对于他们来说就是提款机。 是的,过去是,现在更是,因为电子货币的出现,他们提款的速度变得更疯狂。 在2017年,我们的蜜罐监测到一起针对以...

3659
来自专栏FreeBuf

SSH僵尸主机挖矿木马预警

XMR(门罗币)是目前比特币等电子货币的一种,以其匿名性,支持CPU挖矿,以及不菲的价格等特点,得到了“黑产”的青睐。瀚思科技挖掘出黑产利用互联网服务器进行挖矿...

3069
来自专栏CDA数据分析师

你的第一个智能合约「Hello World」,好像也不是很智能

在看过 我花了 99 个以太坊(Ethereum)来学智能合约开发(http://davidfnck.com/blockchain/ethereum-smart...

1422
来自专栏FreeBuf

利用树莓派探索以太坊第一部分:环境搭建

在本系列文章的第一部分中,我们将在一台树莓派Pi 3 Model B上安装并运行一个以太坊区块链客户端。 ? 毫无疑问,区块链绝对是当前的热点。之所以会这样,...

4576
来自专栏区块链大本营

千万别惹牛人!小哥被盗22元后,整出了这篇以太坊钱包安全攻略,黑客看完得哭了...

对于区块链动辄几十万行的代码量,安全漏洞时不时就冒出来。敏锐的黑客们,虎视眈眈地盯着漏洞的闸门,一旦看见开闸,便以迅雷不及掩耳的速度展开偷袭。

1222
来自专栏极客编程

如何开发创建ERC20以太坊代币

可以把ERC20简单理解成以太坊上的代币协议,所有基于以太坊开发的代币合约都遵守这个协议。遵守这些协议的代币我们可以认为是标准化的代币,而标准化带来的好处是兼容...

1231

扫码关注云+社区

领取腾讯云代金券