1. 程式人生 > >是誰控制了比特幣,是你?還是錢包?——BTC地址與交易原理大剖析

是誰控制了比特幣,是你?還是錢包?——BTC地址與交易原理大剖析

*- [ ] List item 比特幣地址有 1 打頭的地址 ,也有 3 打頭的地址,這兩者有什麼區別嗎?

  • - [ ] List item 在哪種情況下,地址上的比特幣會被鎖死?
  • - [ ] List item 到底是誰擁有比特幣的控制權,是你?還是你的錢包?**

如果你在使用比特幣錢包,但卻無法回答上面三個問題,那麼這篇文章是為你而寫。

安比(SECBIT)實驗室在對數字錢包原始碼審計時,發現一個名為 pywallet 的比特幣錢包開源庫包含了一個嚴重缺陷。如果向 pywallet 生成的 OmniLayer 收款地址轉賬,將導致資產永久丟失。 據安比(SECBIT)實驗室區塊鏈技術專家 zer0to0ne 解釋,OmniLayer 協議允許在比特幣區塊鏈上發行自定義資產(比如 USDT)。OmniLayer 資產交易的本質是比特幣交易。比特幣交易的程式碼庫有很多,pywallet 便是其中一種。它可以方便的構造符合 OmniLayer 格式的比特幣交易。目前 pywallet 已經被應用在一些數字錢包軟體中。 但是,開源庫 pywallet 在生成 OmniLayer 錢包地址的時候,誤將地址的字首寫反了,若干資產被鎖死在無效的地址內!

下面是 pywallet 相關錯誤程式碼截圖: 在這裡插入圖片描述

**檔案地址:**https://github.com/ranaroussi/pywallet/commit/eb784ea4dd62fe2a50e1352e7d24438fc66a4ac0#diff-ca3a8be6f2ab4be3bfd69a49f5f4122a 插隊科普一下:比特幣網路上最常見的地址型別有三種:普通公鑰地址(1-地址),指令碼雜湊地址(3-地址)和隔離見證地址(bc1-地址),地址型別通過地址的字首來區分。其中1-地址的字首為 0x00,3-地址 的字首為 0x05。 • 1-地址:這是最常見的比特幣地址,通常用於普通轉賬收款。1-地址 實際上為公鑰Hash的編碼。驗證 1-地址的簽名後便可解鎖收款。 • 3-地址:這個地址為指令碼(Script)雜湊地址。這類地址實際對應為一段比特幣指令碼Hash的編碼。 • bc1-地址:bech32編碼地址,用於隔離見證交易。 開源庫 pywallet 顛倒了地址字首,將 1-地址 錯誤地設定為 3-地址。因此原本要轉給 1-地址 的資產會誤轉入 3-地址。當賬戶持有者以 1-地址 的驗證方式,也就是私鑰簽名去取出資產的時候,區塊鏈網路卻以 3-地址 執行指令碼的方式去執行驗證,導致使用者無法正常取出資產!

請慎重使用 pywallet 開源庫!! **

真相:比特幣從未真正實現過轉賬功能

** 這一點出乎很多人的意料,由於比特幣的實現基於 UTXO 模型,與我們直觀理解的賬戶模型不一樣。zer0to0ne 解釋說,實際上比特幣從未真正實現過通常意義上的轉賬功能。中本聰只給比特幣設計了一系列比特幣指令碼操作符和比特幣指令碼執行器,而所謂的轉賬過程實際是由一段比特幣指令碼鎖定、解鎖過程來模擬。這與日常生活中的賬本概念(或稱之為賬戶模型)不一樣。 為了便於理解,我們可以把比特幣區塊鏈上的資產交易比喻成 將資產鎖進保險箱,只有持有保險箱鑰匙的人(即收款人)才能拿出保險箱中的資產進行交易。舉個例子,如果 Alice 要向 Bob 支付一筆資產,Alice 將這筆資產鎖進一個保險箱中,只有 Bob 才有這個保險箱的鑰匙,即只有Bob才能取出這筆資產。如果 Bob 要取出資產,那麼要求 Bob 必須同時花掉這筆資產(即鎖入另一個保險箱)。在 Bob 沒有取出資產前,資產並不真正屬於 Bob。設想如果 Bob 丟了鑰匙,那麼將無法再取出資產。 換句話說,這筆資產還在保險箱中儲存的時候,既不屬於Alice,也不完全屬於Bob。當然,Alice 也可以把資產放入任何人都可以開啟的保險箱中,這也被稱之為 Anyone-Can-Spend 交易。 由於比特幣區塊鏈上的收款地址不同,保險箱的型別也有所不同。不同型別的保險箱需要不同型別的鑰匙來開啟。付款人為收款人定製一個保險箱,將資產放入保險箱中並上鎖,再將保險箱丟到公共場所。而保險箱有兩種開啟方法: • 若收款人為 1-地址,我們稱保險箱為1-類保險箱 。而相應的鑰匙必須是指定收款地址對應的私鑰。解鎖保險箱的過程也就是驗證 1-地址公鑰以及公鑰對應的數字簽名,這也是我們通常所理解的向普通賬戶地址轉賬的驗證過程。 • 若收款人為 3-地址,我們稱為3-類保險箱 。開啟鑰匙必須為一段可以執行的比特幣指令碼。解鎖保險箱的過程是:比特幣指令碼的Hash值對應到 3-地址 ,同時比特幣指令碼執行器執行該指令碼後成功返回。也就是說只有擁有指令碼原文並可以成功執行的人才可以提取這個保險箱裡的資產。 回到這一節的問題:為什麼說比特幣從未實現真正意義上的轉賬功能。答案很簡單,因為比特幣系統中根本就不存在賬戶的概念,賬戶之間的轉賬也無從談起。一個人能在未來開啟多少個保險箱,也是未知數。 通過上面的解釋,我們可知:當 pywallet 開源庫誤將 1-地址 識別為 3-地址 時,就好像將原本的1-類保險箱 改造成了3-類保險箱,而賬戶持有者還是拿著 1-類保險箱 的鑰匙去解鎖,那麼自然無法開啟保險箱。那麼之前 zer0to0ne 發現的被誤鎖住的 OmniLayer 數字資產是否能恢復?

是否存在一種可能性,採用 1-地址 的鑰匙去開啟 3-保險箱 ? zer0to0ne 接著向我們詳細解釋了兩個重要概念 P2PKH(Pay to Public Key Hash) 與 P2SH (Pay to Script Hash)的來龍去脈。這兩個名詞分別代表了兩種不同的比特幣交易型別。 下面是 zer0to0ne 的精彩技術細節分析

**

P2PKH——中本聰的偉大發明

** Pay to Public Key Hash 顧名思義,是將比特幣放入一個保險箱,鑰匙孔為公鑰 Hash(Public Key Hash)。我們最常見到的 1-地址 本質上就是 Public Key Hash 的一種編碼。1-地址 的生成過程也很簡單,將公鑰經過Hash160運算得到 Public Key Hash,在 Public Key Hash 頭部補上字首 0x00,Hash 尾部補上校驗和,經過Base58便得到了1開頭的比特幣地址。 Base58(0x00 + + Checksum) 我們來看看P2PKH交易型別的保險箱構造過程,Alice傳送比特幣給Bob為例: 付款方 Alice 在構造保險箱的時候需要設定一個鎖定指令碼: OP_DUP

OP_HASH160

(Bob收款地址蘊含的Public Key Hash)

OP_EQUALVERIFY

OP_CHECKSIG 注:我們可以把這一步理解為 Alice 為 Bob 定製了一個保險箱,把比特幣放入保險箱並用 Bob 的公鑰 PubKey Hash上鎖。現在這把鎖除了持有私鑰的 Bob,誰都無法開啟。 當 Bob 需要花費 Alice 給他的比特幣時,需要提供必要的引數:交易簽名 + 公鑰(技術黑話:scriptSig)來開啟保險箱,使得鎖定指令碼執行後返回 True,這一步通常由錢包自動完成。 我們來看看比特幣節點是如何校驗 scriptSig 合法性的。 在這裡插入圖片描述 (圖片來自Mastering Bitcoin) 指令碼執行過程如圖所示,Bob將交易簽名後得到的資料(實際上還要包含資料長度資訊),真正的scriptSig 應該為 ,比特幣指令碼執行器從 PUSH 資料開始,PUSH 操作會讀取第一個位元組獲取將要入棧的資料長度資訊,然後持續執行比特幣指令碼,直到最後執行完畢檢查執行結果。 首先入棧的是 ,然後將 入棧,一次DUP操作將在棧頂複製一份 ,HASH160彈出棧頂的 並計算Hash,將結果壓回棧中,之後使用 EQUALVERIFY 彈出Hash對比是否合相等,如果相等則返回True,不相等便標記交易為無效。執行到這一步,暴露了公鑰,確保了簽名者的身份的正確性,但是黑客或礦工可以通過暴露的公鑰構造出一個新的交易替換原始交易,無法保證安全,那麼便需要下一步來保證交易無法偽造。此時棧上還有 和 ,執行 CHECKSIG,將校驗數字簽名的正確性,確保了簽名者擁有地址對應的私鑰。 數字簽名除了持有私鑰的人,誰也無法偽造,執行至此,一筆比特幣P2PKH交易已經安全地完成了。 再解釋一遍:當 Bob 要花費 Alice 給他的比特幣時,Bob 只有用正確的鑰匙才能開啟 Alice 留給他的保險箱,把錢放入 Bob 新構造的一個保險箱裡。 這時候一些聰明的讀者會注意到一個細節:如果 Bob 取出鑰匙,在還未開啟保險箱的時刻,區塊鏈上的任何礦工都能看得見這把鑰匙的形狀,理論上他們是可以立即複製一把鑰匙,把 Alice 留給 Bob 的保險箱開啟並花掉(俗稱 Front-running 攻擊)。真的可以這樣做嗎?顯然中本聰考慮了這個問題,這把鑰匙中的交易簽名是 Bob 發起的交易的完整簽名。假設 Bob 要將 Alice 構造的保險箱中的比特幣 裝入一個新的保險箱(留給Charlie),這時候 Bob 出示的鑰匙包含了 Charlie 的公鑰Hash,礦工雖然可以複製 Bob 的鑰匙,但是這把鑰匙已經隱藏了下一個新保險箱的關鍵資訊,因此礦工無法使用這個複製鑰匙來完成別的動作(無法挪用數字簽名)。

**

P2SH——後中本聰時代的重大創新

** 中本聰設計了一個這麼強大的腳本系統,只用來構造轉賬交易似乎太浪費了,我們試試用其他指令構造一些特別的鎖定指令碼,並使用其他方式來解鎖。 例如我們可以構造一個用 Hash 原象(Pre-image)來解鎖交易的指令碼: OP_HASH160 OP_EQUAL 這個指令碼的含義是:當滿足 Hash160(Pre-image)==這個條件時,便可成功將指令碼解鎖。 我們繼續通過保險箱的例子來解釋,並給這類保險箱起名為 3-類保險箱。現在 Alice 給 Bob 的比特幣鎖定在一個由上述 Hash160保護的保險箱裡,我們姑且稱之為雜湊鎖吧。 這把鎖依然需要正確的形狀才能開啟,但是安全性卻弱很多,缺少數字簽名機制導致鑰匙隱藏的關鍵資訊不會隨著Bob 新建的保險箱而變化。任何礦工都能在 Bob 亮出鑰匙的一瞬間複製出一摸一樣的鑰匙,搶著去開Alice留給Bob的保險箱(Front-running),將幣轉給另一個人 Eve,於是原本屬於 Bob 的比特幣會被洗劫一空。 雖然這個指令碼非常不安全,但是它卻有兩個非常神奇的功能:

  1. 交易構造的輸出足夠短,意味著比特幣節點維護的 UTXO 快取佔用空間將會大大減小
  2. Pre-image 總是在交易被花費時作為 input 來引用,不會在交易的 output 側出現,UTXO依然保持精簡,同時可以把手續費負擔轉嫁給接收方。 既然所述的輸出指令碼好處很多,那我們是否有辦法讓這種交易方式變得安全呢?這就需要講講什麼是 P2SH了。 比特幣核心開發者 Gavin Adresen 提出了一種叫做 Pay to Script Hash (P2SH) 的技術。 P2SH 的交易輸出依然是判斷 Hash160(Script)==

**

被鎖死的幣是否有挽回可能

** 再回到開頭的那個問題,zer0to0ne 遇到的錢包錯誤鎖死資產的事故,能否採用 1-地址 的鑰匙去開啟 3-保險箱 ,挽救保險箱中的資產呢? 這個問題可以解釋為:Alice 按照 Bob 的要求製作了一個 3-類保險箱,但是這個保險箱是被 pywallet 錯誤修改的,實際上 Bob 的本意是需要一個 1-類保險箱 ,因為 Bob 手裡只有一個1-類保險箱 的鑰匙。 當 Bob 請求 Alice 把比特幣鎖定到 3-類保險箱 時,這個保險箱就需要同時校驗鑰匙形狀和鑰匙晶片內容了,但是 Bob 的1-類保險箱 的鑰匙形狀是 Public Key 決定的,在保險箱誤變為 3-類保險箱 後,鑰匙形狀校驗卻沒有改變,也沒有相應的鑰匙晶片內容。 Bob 嘗試在鑰匙晶片中寫入Public Key,使得鑰匙形狀和鎖匹配,但是晶片中的Public Key 卻無法被保險箱正確執行,所以 Bob 可能再也無法將他的比特幣從保險箱中解鎖了。

**

SegWit——全新時代來臨

** 比特幣開核心發者 Eric Lombrozo, Johnson Lau, Pieter Wuille 提出來了一種全新的概念,隔離見證(Segregated Witness,簡稱SegWit)。這是一種有效緩解比特幣區塊擁堵的技術,並且徹底解決了交易延展性問題(Transaction Malleability)。 在SegWit升級之前,每一個用完的鑰匙都插在保險箱上,鑰匙佔據了一定的體積導致倉庫無法密集堆積這些用完的保險箱,總是要為了露在外面一大截的鑰匙騰空間。那我們想想能不能把鑰匙體積減小,並且用足夠廉價的材料來製作,節約成本。 於是便有了隔離見證(Segregated Witness簡稱SegWit),它乾脆直接把鎖從保險箱上移走,變成一個遠端無線校驗的保險箱,使用者可以把鑰匙統一插在遠離保險箱的地方來遠端開啟對應的保險箱。 2017年08月24日,SegWit軟分叉被正式啟用,結束了曠日持久的礦工開發者對峙 為了相容3-類保險箱,比特幣開發者使用了一種叫做P2SH-P2WPKH的技術,即通過P2SH來包裹P2WPKH交易,讓P2WPKH交易可以騙過不支援SegWit的老舊節點。還有一種類似P2SH包裹的P2WSH技術(P2SH-P2WSH),在此不多做介紹。 我們先來解釋一下P2WPKH是什麼: P2WPKH全稱Pay to Witness Public Key Hash,相較於P2PKH,P2WPKH把scriptSig移動到交易外部,節省了佔用的區塊空間。 為了向前相容未及時升級的比特幣節點,這個P2SH需要如何構造呢? Bob首先產生一個P2PKH地址,從地址中解析出PubKey Hash,然後構造一個這樣的指令碼: Script = 0x0014 + 然後計算Script的Hash160得到Script Hash,構造出一個3地址: Base58(0x05 +

**

對三個問題的回覆

** 比特幣地址有 1 打頭地址 ,也有 3 打頭的地址,你知道這兩者有什麼區別嗎? 1-地址 是用做 P2PKH 交易的目標地址,而 3-地址 是用作 P2SH,SegWit交易的目標地址。 在哪種情況下,地址上的比特幣會被鎖死? 假設一個比特幣地址為 3-地址 ,如果世界上沒有人能夠提供一個可以通過Hash160校驗並有效可執行的指令碼,那麼這個地址上的比特幣會被鎖死。 到底是誰擁有比特幣的控制權,是你?還是你在使用的錢包? 如果一個比特幣地址為 1-地址 ,那麼該地址上的比特幣被鎖在一個 1-類保險箱 中,擁有私鑰的使用者擁有該地址上的比特幣。這裡請注意“擁有私鑰”有兩層含義:(1)自己牢記私鑰,(2)其他人無從知曉。 如果一個比特幣地址為 3-地址 ,那麼在某個使用者披露一個“解鎖指令碼”之前,沒人知道該地址上的比特幣歸屬。因為指令碼由錢包來生成的,而使用者只“擁有私鑰”,如果你不知道地址對應的指令碼,就相當於交出了地址控制權。指令碼的內容才真正決定了比特幣的歸屬。