solidity編寫簡單支付通道智慧合約程式碼
編寫一個簡單的支付通道:
Alice是發件人,Bob是收件人,若Alice和Bob之間含有多筆交易,每次都通過以太坊錢包轉賬,礦工不僅會扣取手續費,且以太坊的出塊速度約為10-15s,因此存在交易延遲。
在存在多筆交易時,構建一個簡單但完整的支付通道,使用橢圓曲線加密簽名以實現安全、即時且無交易費用地重複傳輸以太幣。
該支付通道需要實現發件人在呼叫合約初期把以太幣質押在合約中,根據簽名驗證判斷髮件人總共欠收件人多少以太幣,在合約有效期內由收件人結束合約並轉賬給收件人應得以太幣,這樣整個流程就只需要2次以太坊交易,節省了交易費和時間延遲。
什麼是支付通道:
支付通道允許參與者在不發起以太坊
(1)Alice用以太幣為智慧合約提供資金。這“開啟”了支付通道。
(2)Alice簽署訊息,指定欠Bob多少以太幣。每次付款都重複此步驟。
(3)Bob“關閉”支付通道,提取他的部分以太幣並將剩餘部分發還給Alice。
注意:只有第(1)步和第(3)步需要以太坊交易,第(2)步意味著Alice通過鏈下方法(如電子郵件)向Bob傳送加密簽名的訊息。這意味著只需要兩筆交易即可支援任意數量的轉賬。
Bob可以保證收到應得資金,因為智慧合約託管了
開通支付通道:
為開啟支付通道,Alice需部署智慧合約,附加要託管的以太幣並指定以太幣接收者和合約有效期。這是下面合約程式碼中函式SimplePaymentChannel的功能。
付款:
Alice通過向Bob傳送簽名訊息來付款,此步驟完全在以太坊網路之外執行。訊息由發件人加密簽名,然後直接傳輸給收件人。
每條訊息都包含以下資訊:
智慧合約的地址(用於防止跨合約重放攻擊),到目前為止欠收款人的以太幣總量。
關閉支付
支付通道僅關閉一次,每條訊息都指定了累積的以太幣欠款總額,只有最新傳送的訊息被兌換。當Bob準備好接收以太幣時,需通過呼叫智慧合約上的close函式來關閉支付通道,且只有Bob可以呼叫close函式,他們自然會傳遞最新的支付訊息,因為該訊息攜帶最高的總欠款,該函式向Bob支付應得以太幣、向Alice返還剩餘以太幣,並銷燬合約。
支付通道到期:
Bob可以隨時關閉支付通道,但如果他們不這樣做,Alice需要一種方法來收回她的託管資金。在合約部署時設定了合約有效期。一旦到達有效期,愛麗絲就可以呼叫函式claimTimeout()來收回資金。呼叫此函式後,Bob將無法再收到任何以太幣,因此Bob需要在到期之前關閉通道。
solidity程式碼如下:
pragma solidity >=0.7.0 <0.9.0; //用Remix編寫,需手動呼叫function以實現相關功能 contract SimplePaymentChannel{ address payable public sender;//發件人地址 address payable public recipient;//收件人地址 uint public expiration;//儲存合約到期時間,防止收件人一直不關閉合約,佔用發件人以太幣資源 //建構函式,部署合約時呼叫,僅呼叫一次 //初始化發件人地址,收件人地址,合約有效時間 constructor(address payable recipientAddress, uint256 duration) payable{ sender = payable(msg.sender);//msg.sender是address型別,需強制型別轉換為payable address型別 recipient = recipientAddress; expiration = block.timestamp + duration; } //銷燬合約,只有收件人能銷燬合約 function close(uint256 amount, bytes memory signature) external{ //require()中判斷條件為true則繼續,為false則退出該function,回退該function內所有更改 require(msg.sender == recipient);//判斷呼叫該function地址是否為收件人 require(isValidSignature(amount, signature));//判斷收件人是否掌握有正確的的發件人訊息簽名 recipient.transfer(amount);//把應得的以太幣傳送給收件人,誰呼叫transfer(),就給誰轉賬 selfdestruct(sender);//銷燬當前合約,將合約剩餘資金髮送到給定地址sender //由於合約內容已被記錄在舊的區塊上,仍可以被查詢,但不能被再次呼叫,除非重新部署該合約 } //合約有效期續期,僅有發件人可以呼叫 function extend(uint256 newExpiration) external{ require(msg.sender == sender);//判斷呼叫者是否為發件人 require(newExpiration > expiration);//判斷新的有效期是否大於當前有效期 expiration = newExpiration;//重置合約有效期 } //判斷當前合約是否在有效期內 function claimTimeout() external{ require(block.timestamp >= expiration);//判斷當前合約是否過期,若過期,則銷燬合約 selfdestruct(sender);//銷燬合約 } //函式isValidSignature(),splitSignature(),recoverSigner(),prefixed()涉及到 橢圓曲線加密 訊息的驗證過程, //詳見我的另一篇部落格https://www.cnblogs.com/forkroad/p/16121333.html,有詳細介紹 function isValidSignature(uint256 amount, bytes memory signature) internal view returns(bool){ bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount)));//根據當前地址this和轉賬金額amount雙重加密為訊息message return recoverSigner(message, signature) == sender;// } function splitSignature(bytes memory sig) internal pure returns(uint8 v, bytes32 r, bytes32 s){ require(sig.length == 65); assembly{ r := mload(add(sig, 32)) s := mload(add(sig, 64)) 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); } function prefixed(bytes32 hash) internal pure returns(bytes32){ return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); } }
來源(solidity官方英文文件0.8.13):https://docs.soliditylang.org/en/v0.8.13/solidity-by-example.html#writing-a-simple-payment-channel
solidity官方中文文件0.8.0:https://learnblockchain.cn/docs/solidity/solidity-by-example.html#id15
橢圓曲線簽名驗證程式碼實現:https://www.cnblogs.com/forkroad/p/16121333.html