在這篇文章中,我將實現一個簡單但完整的以太坊支付通道。支付通道使用密碼簽名,以安全、即時、無交易費用重複地傳送Ether。php
以太坊交易提供了一種安全的方式來轉帳,但每一個交易須要被包括在一個區塊中和並被挖掘。這意味着交易須要一些時間,並要求支付一些費用來補償礦工的工做。特別是,這個交易費用使得其產生的這種小額支付,成爲了以太坊和其餘相似於它的區塊鏈的使用,變得有點兒費勁一個緣由。java
支付通道容許參與者在不使用交易的狀況下重複發送Ether。這意味着能夠避免與交易相關的延遲和所以產生費用。在這篇文章中,咱們將探討一個簡單的單向支付通道。這包括三個步驟:node
重要的是,只有步驟1和步驟3須要空缺交易。步驟2經過密碼簽名和兩方之間的通訊(如電子郵件)完成。這意味着只須要兩個交易來支持任何數量的發送。python
收件人保證收到他們的資金,由於智能合約託管了ether並承認有效簽署的消息。智能合約還強制執行直到截止時間,並且發送方有權收回資金,即便接收方拒絕關閉支付通道。android
這取決於支付通道的參與者決定多長時間保持開放。對於短期的交互,例如對於提供網絡服務按每分鐘支付的網吧,使用只持續一個小時左右的支付通道就足夠了。對於一個較長期的支付關係,好比給員工支付按小時計的工資,支付通道能夠持續數月或數年。git
爲了打開支付通道,發送方部署智能合約,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; }
發送者經過向接收者發送消息來進行支付。該步驟徹底在以太坊網絡以外執行。消息由發送方進行加密簽名,而後直接發送給接收方。github
每一個消息包括如下信息:web
在一系列轉帳結束時,支付通道只關閉一次。正由於如此,只有一個發送的消息將被贖回。這就是爲何每一個消息都指定了累積的Ether消耗總量,而不是單個微支付的量。接收者天然會選擇贖回最近的消息,由於這是一個總擁有最高ether的消息。mongodb
請注意,由於智能合約僅對單個消息進行維護,因此不須要每一個臨時消息。智能合約的地址仍然用於防止用於一個支付通道的消息被用於不一樣的通道。
能夠用支持加密的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); }
與簽名不一樣,支付通道中的消息不會當即被贖回。接收方跟蹤最新消息並在關閉支付通道時贖回。這意味着接收方對每一個消息進行本身的驗證是相當重要的。不然,不能保證收件人最終能獲得報酬。
接收方應使用如下過程驗證每一個消息:
前三個步驟很簡單。最後一步能夠經過多種方式執行,可是若是它在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回發送者。要關閉通道,接收方須要共享由發送方簽名的消息。
智能合約必須驗證消息包含來自發送者的有效簽名。進行此驗證的過程與接收方使用的過程相同。isValidSignature
和recoverSigner
函數與前一部分中的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#開發以太坊應用,那這個咱們進行打造的課程會頗有幫助:
若是是其餘語言開發以太坊應用的也能夠參考如下教程:
這裏是原文