乾貨 | 以太坊上傳送交易的九種辦法
本文的目的是為在以太坊生態中傳送交易使用的各類技術,模式和機制提供一個指南。由於新技術層出不窮,本文也會隨之更新,所以可以認為是未完待續的一個狀態。
針對這個公認的大課題,本文將包含以下內容:
- Brief introduction to Ethereum transactions 以太坊交易的簡明介紹
- Gas usage and gasToken Gas 的用途和 gas 代幣
- 元交易
- 潛艇交易
- Counterfactual instantiation 反事實合約例項化
- Zero confirmation transactions 零確認交易
- 批量轉賬
- 基於簡訊的付款
- Repeated subscription payments 重複訂閱付款
- Gas reduction via oracle contract for batched transactions 通過預言機合約為批量交易節省 Gas
- Multisends with one-time addresses 使用一次性地址進行多筆付款
背景知識
以太坊是一個 基於賬戶的系統 ,目前有兩種賬戶:普通賬戶和合約賬戶。這兩種賬戶都有自己的以太坊地址,交易計數器 Nonce 和餘額。合約賬戶還額外擁有不可變的程式碼以及相應的儲存空間。 這裡 有一篇介紹這些基本概念的好文章。
一個以太坊交易包含以下關鍵欄位:
- Nonce,或者說交易計數器,即該賬戶主動發起的交易數量,從0開始計數
- gas Price,決定了該筆交易需要支付的 Ether 數量
- gas Limit,即處理該交易所允許的最大 gas 數量
- 目標地址,即接受該筆交易的物件,如為空,則該交易會建立一個新的合約
- 交易金額,即傳送的 Ether 數量
- 資料,即可以是任意的一條文字訊息,也可以是某合約的一次呼叫或者建立新合約的一段程式碼
請注意,以上關鍵點在於沒有“交易發起”地址,因為該地址可以從生成該筆交易雜湊值簽名的公鑰-私鑰對推匯出來,其中交易欄位採用了適當的 RLP(遞迴長度字首)編碼。
Gas 用途和 Gas 代幣
站在一個很通俗的角度,區塊鏈可以看成是一個共享資料庫。每次從該資料庫讀取或者寫入資料都需要花費 gas 以防止類似垃圾郵件的惡意攻擊。具體來說,以太坊上執行的每個計算步驟都需要花費 gas,以避免可能導致以太坊停擺的惡意攻擊。每個操作碼的 gas 開銷都在 以太坊黃皮書 中有說明。但操作碼的 gas 開銷仍是一個熱烈討論的活躍話題,以太坊的社群成員們正在研究 引入儲存租金機制 的可能性,甚至是 gas 和操作碼的 動態定價方案 。
在以太坊區塊鏈中寫入資料很貴,比如建立一個非空的儲存單元需要花費 20000 gas,幾乎與一次簡單的 Ether 轉賬交易(即當交易結構中的資料欄位為空時)花費相當(21000 gas)。作為緩解區塊鏈資料儲存暴漲的一種激勵方案,以太坊協議會為清空不再使用的舊儲存單元退還 10000 個 gas。
這個 Ether 的退還機制最多可以返還合約交易花費的一半 gas(普通轉賬交易是無法獲得退款的,因為它們已經使用了 gas 的最低消費;但是針對合約的批量呼叫是可以享受這個退款機制的)。Gas Token 允許開發者簡單而高效地利用這個退款機制,即通過 gas 的代幣化,在 gas 價格低的時候囤貨,然後 在 gas 價格高的時候花掉之前儲存的 gas 代幣 。
沒有正確設定交易
gas上限的漏洞
。攻擊方法很簡單:在交易所申請提現,然後將提現交易目標地址設定成一個攻擊者部署的惡意合約,其 預設 fallback 函式 (傳送 Ether 到該合約會觸發該函式的呼叫)就會趁機 鑄造 新的 gas Token(囤貨 gas)。(校對注:詳情可見文末超連結《深處的蟻穴》)元交易
元交易是這樣一種傳送交易的模式:傳送方先對一個合法的以太坊交易簽名,然後把該交易和簽名通過鏈下傳遞的方式轉交給一箇中繼方,該中繼方願意承擔該筆交易的 gas 開銷並最終傳送交易到以太坊網路中。
這種元交易模式很有用,因為傳送方不再需要在傳送賬戶中存有 Ether,從使用者體驗角度這很有益處。我之前在 這篇文章 中提到過元交易及其在 UX 上的影響。
元交易最終的目標地址一般都是某個以太坊合約,且在某種程度上,該合約知道,交易的簽名方並不是交易實際的傳送者。以太坊交易 API 的 msg.sender 欄位會返回中繼方的地址,但其很可能並沒有代表簽名方操作的權利,所以在這個場景下(僅僅檢視 msg.sender 欄位)並沒有太大意義。因此,許多元交易依賴鏈上的簽名校驗(通過以太坊 API 的 ecRecover 函式)來保證簽名方賬戶的確是在一份合適的白名單裡面(有許可權操作該交易想要執行的指令)。
潛艇交易(Submarine Send)
(別跟這個潛艇交易網站 < https://submarineswaps.org > 混淆了)
礦工搶跑(frontruning)現象在基於區塊鏈經濟的交易市場中是一個很難杜絕的老問題,即礦工可以對交易重新排序,隨意裁剪或者讓他們自己的交易插隊來獲利。 潛艇交易試圖通過極強的保密特性來提供礦工搶跑問題的一個解決方案 。不僅僅是隱藏交易金額, 潛艇交易 會嘗試完全隱藏該筆交易的存在。當然,如果一筆交易永遠都被隱藏著,那也就沒啥意思了。潛艇交易允許傳送方在未來的某個時刻公開該筆交易,這也是稱其為“潛艇”交易的緣由。
使用者提交一個包含加密承諾的交易,其中包含了使用者希望傳送給目標合約的若干應用資料,並在潛艇地址中鎖定交易涉及的 Ether 或者代幣,其中潛艇地址與一個全新的地址無異(因此難以被礦工識別)。鎖定在該地址中的 Ether 或者代幣只有目標合約可以解鎖。通過在承諾交易中附加貨幣價值(除非使用者完成承諾的公開,否則附加貨幣價值其實是被燃燒掉了,無法找回),我們保證了有效的經濟約束來防止某些惡意使用者選擇性地公開承諾(即防止使用者可以無代價地提交任意承諾)。只要承諾交易被成功打包並經過足夠區塊確認,使用者就可以向目標合約公開其加密承諾,然後合約(成功校驗後)便會執行該筆交易中包含的應用邏輯。
反事實合約例項化
Counterfactual(反事實)一詞源於哲學和思辯中的一個概念。一條反事實陳述是一連串有理有據的推理以及相應的結論,但是該陳述的前提是有意與事實相反的。除開這個與事實不符的前提,整個推理鏈條是合理的,所以如果前提正確,最終結論也會是正確的。應用到區塊鏈交易場景,Conterfactual 的邏輯不光會考慮區塊鏈當前的狀態,還會考慮如果某合約部署完成後,區塊鏈的狀態。
更具體來說,在合約部署之前就獲取它的地址,這種模式被稱作反事實合約例項化,這個理論由 L4 發表在他們的“反事實狀態通道”論文中,並受到以太坊社群的廣泛歡迎。
目前,新的合約地址由以太坊操作碼 CREATE 生成,並可通過合約的建立者賬戶地址(sender欄位)以及建立者已經發出去的交易數量(nonce欄位)來明確決定,即 sender 和 nonce 欄位會通過 RLP 編碼然後經過 Keccak256 雜湊演算法生成新的合約地址。
EIP1014 引入的 Skinny CREATE2 操作碼更進一步,允許使用者與鏈上尚不存在的地址進行互動;雖然該地址上還沒有程式碼,但可以保證它最終只可能包含通過一段特定的初始程式碼生成的合約邏輯。與 CREATE 操作碼一般使用 sender-nonce 然後雜湊得到合約地址不同,CREATE2 操作碼使用的是如下地址生成公式: keccak256( 0xff ++ address ++ salt ++ keccak256(init_code)))[12:]
。
這種模式,對於涉及與尚不存在的合約進行互動的狀態通道場景,尤其重要。它讓以太坊主鏈可以成為爭議(解決)層,並且不需要考慮合約部署的真實開銷。類似地,這種模式在已知功能將建立新地址的場景也可以使用,比如這裡的 借貸還款地址 。
零確認交易
零確認交易 源於 Bitcoin Cash 社群 ,目前仍是一個有趣 但尚未經過證明 的研究領域,在這樣的一個區塊鏈網路中,出塊時間實質上可能更加不利於使用者體驗(UX-inhibiting)。零確認交易的傳送方需要提交一個保證金,如果有雙花行為,傳送方就會損失掉該保證金。在比特幣現金中,雙花行為可以通過 UTXO 的輸入項重用被檢測到。任何人(一般假設是礦工)都可以提交找到的兩筆雙花交易,然後得到保證金的獎勵。
在以太坊的賬戶網路中,不同於使用類似比特幣的UTXO,我們可以檢查同一傳送方是否重用了同一個 nonce。比如一個已部署合約提供一個 reportDoubleSpend 方法,該方法接受兩個待完成的已簽名交易,然後合約會檢查其傳送方和 nonce,如果相等,就會把保證金獎勵給方法呼叫者。原理很簡單:如果保證金數額足夠大,這對於交易傳送方而言,就是防止其作弊(雙花)的一個有力武器,因為他們有可能損失繳納的保證金。這種交易型別被認為最適合用於小額一次性的單筆支付場景,因為有一系列針對該場景的潛在攻擊模式存在。
批量轉賬
跟 ERC20 代幣互動的一個主要問題在於,一般需要兩次不同的交易:一次是呼叫代幣合約的 approve 方法,另一次才是真正呼叫目標合約(該目標合約內部會呼叫 transferFrom 方法)使用代幣完成特定邏輯(doSomethingForTokens 方法)。這種模式就會產生非原子性交易的一系列問題。最簡單的情況就是,如果 doSomethingForTokens 呼叫交易失敗了,之前的 approve 呼叫不會回滾,即 approve 方法允許合約支配的代幣額度(allowance)仍然成立。
Limechain 實現了一種特殊的批量轉賬方法。借鑑元交易中的鏈上簽名校驗原理,失敗的 doSomethingForTokens 呼叫交易會回滾相應的 approve 呼叫,從而改善了 ERC20 代幣原本 approve 和 transferFrom 方法的非原子性。
基於簡訊的付款
CoinText 可能是最有名的基於簡訊的密碼學貨幣支付服務商,目前專注提供比特幣現金的交易。這種付款機制對於發展中國家和地區的移動裝置尤其有用。 Eth2 也已經在以太坊上 部署 了類似的技術,它可以通過傳統的基於移動應用的以太坊錢包(比如 Trust)來工作。
這個特定方案採用了一個託管合約。交易傳送方生成一個 臨時的 公私鑰對,然後往 託管合約 存入 Ether ,該筆轉賬中附帶之前生成的臨時公鑰。私鑰則通過隨機生成的對稱金鑰加密,然後密文通過鏈下方式(郵件,簡訊,Whatsapp 等)傳送給某個中心化的校驗伺服器。提現時,如果接收方手機號碼校驗成功,校驗伺服器就會把密文發給接收方,接收方可以解密(即拿到臨時私鑰),然後對 提現交易訊息 簽名,託管合約隨後可以對該簽名進行校驗,確認是由臨時私鑰完成的簽名。
中心化伺服器用來對手機號做驗證並傳遞祕鑰,但是 Eth2 的伺服器無法控制鎖定在託管合約中的 Ether。如果中心化伺服器被攻擊了,付款交易會失敗,但是 Ether 仍在託管合約中。如果此時想拿回鎖定的 Ether,傳送方可以通過呼叫託管合約取消該筆付款。
訂閱付款
基於可選擇退出的訂閱服務付款是 Web2.0 時代網際網路服務的主流變現方式,比如 Spotify,Netflix,Headspace 和 Tinder 都是基於訂閱付款構建其商業收入模型。
密碼學貨幣中的訂閱付款概念也不是新東西,在比特幣中, nLocktime 欄位 就可以用來保證一筆已簽名的交易在指定的區塊高度之前不會被打包(即消費掉)。但在以太坊上,用於未來支付的預簽名交易意義不大,因為賬戶的 nonce 會隨著該賬戶不停發出交易而增長,會導致預簽名時所用 nonce 偏小,進而導致交易無效。
幸好,以太坊的圖靈完備性提供了一個解決辦法:有一些針對重複訂閱型別交易的 架構方案 。這些架構在保證金(流動性),使用者體驗複雜性,可選功能,gas 開銷和可延展性方面有不同的權衡取捨。
基於預言機的方法呼叫
另一種更加特殊的交易傳送方式是使用預言機服務,比如 Oraclize,以期通過適當的中心化來換取gas使用量的減少,可以參見 此文 。
這種型別適用於非交易型(返回常量)的方法呼叫。已經與以太坊主網同步的節點,可以通過以太坊 JSON-RPC 的 eth_call 介面來呼叫上述方法。只要繼承了 usingOraclize,在你的合約中就可以使用 Oraclize 的 oraclize_query 方法進行常量查詢。另外,你的合約裡面還必須定義一個 __callback(bytes32 queryId, string results) 的回撥函式,Oraclize 查詢會呼叫該函式並儲存查詢結果。與呼叫 Oraclize 相比,直接進行鏈上查詢來獲取和計算這些狀態常量可能更加昂貴。
使用一次性地址進行多筆付款
如背景知識中介紹的,交易欄位中並沒有“發起地址”。這個地址可以通過 ecRecover 函式計算得出。那麼問題來了:我們能不能在交易簽名中任意填入我們想要的資料?事實表明,有一半的簽名是正確的,即 ecRecover 仍然返回一個合法的公鑰(因此也對應著以太坊地址)。由於我們無法控制生成的地址,那麼我們通過設定欄位值,其實是在構建這樣一個交易:該交易可以花費看上去是一個隨機生成的地址中的餘額。
如果我們建立了這樣一筆交易(傳送方是我們想要生成的地址),並給生成的地址充值了若干Ether,那麼該筆交易就可以像一般交易一樣執行。這樣我們實質上建立了一個 一次性的地址 ,因為其中的餘額只能被一筆交易使用。如果我們以某種可預測的方式選擇交易簽名中的欄位值並公佈該筆交易,我們就可以向任何人證明,發給交易傳送方地址的金額,只能被該筆交易使用,而不能被其它任何交易使用。
如上圖所述,該場景嘗試 傳送交易至11140個目標地址 ,由一系列傳送 Ether 至多個地址的交易組成,每個交易傳送到 110 個地址,其中傳送方的地址通過上述方法生成。對於簽名欄位,我們填入‘0xDA0DA0DA0…’ ,這是一個可預測的值,這樣我們確定,沒有人能拿到這些簽名所對應地址的私鑰。
這就建立了一批擁有“一次性地址”的交易,這些地址可以用來給相應交易提供所需交易金額。但 104 個要簽名的交易對於受託自然人而言還是太多了,所以我們重複一次上述過程,形成一個級聯結構:我們先構造 104 筆交易,每筆交易都有其對應的唯一地址,然後再構造一筆傳送 Ether到對應的 104 個地址的轉賬。通過驗證,程式碼確實可以按照預期執行,那麼任何人就可以這些構建好的交易傳送到以太坊網路中,整個過程就像多米諾骨牌一樣自動進行了:名單上的 11400 個地址都會收到 Ether,但我們僅僅用了一次人工簽名。
以太坊上完成交易居然有這麼多不同的方式!!!
如果有什麼遺漏,請告訴我,我會時不時地更新本文。歡迎在推特上關注我: https://twitter.com/gawnieg
原文連結: https://medium.com/coinmonks/the-business-of-sending-transactions-on-ethereum-e79fd9a2b88
作者:Aodhgan Gleeson
翻譯&校對:Ray & 阿劍