用測試幣搶先體驗Bitcoin Core的冷錢包離線簽名新功能

買賣虛擬貨幣
錢包冷熱分離,其實早就不是什麼新鮮的概念了。 **簡單說,冷端和熱端兩臺裝置是分工的。** 熱端聯網,完全不含有私鑰,負責同步區塊鏈賬本、監視錢包裡的餘額和交易記錄,還負責生成收款地址、生成未簽名交易,然後在冷端簽名交易後,把交易廣播出去。 冷端自始至終都不聯網,負責生成、使用私鑰(包括助記詞);熱端傳遞過來的未簽名交易,冷端負責對其進行檢查、簽名。 **這樣私鑰自始至終都可以“不觸網”,冷熱兩端可以用二維碼等不直接聯網的方式來傳遞交易資料。** **不過bitcoin core作為最“官方”的比特幣全節點錢包,卻一直沒有在圖形介面上支援這個功能**——其實bitcoin core並不是做不到這一點,只不過,必須折騰,一般來說就是折騰命令列,要跟listunspent、createrawtransaction、decoderawtransaction、signrawtransactionwithkey、sendrawtransaction等等等等json-rpc命令打交道,很顯然這很麻煩,而且容易出現人為錯誤。 也有壇友 素履以往 發過一個[教程](https://www.chainnode.com/post/191966),裡面折騰命令列的成分相對很少,但是如果我沒理解錯,他的方法還是非常麻煩:需要用行動硬碟把數百gb的區塊檔案從熱端電腦複製到冷端電腦,然後這麼多區塊檔案在冷端電腦是“一次性”使用的,用完了就要被低格刪掉。 除了麻煩之外,我還經常對這個方法用其他姿勢挑刺:地址是重複使用的,所以理論上對隱私不利;除此之外,更重要的是——如果忘記改找零地址,一次轉出操作中沒花完的幣,就會預設找零到新生成的地址上(很顯然這個新地址的私鑰之前沒有備份過),如果沒有注意這一點,很容易就會釀成經典的“找零丟幣”杯具…… 我也建議過壇友,可以用比太、electrum等已經完善支援冷熱分離的錢包(而且electrum還是可以用electrum personal server對接bitcoin core全節點的),但是看上去很多人無論怎樣就是不願意信任這些“不是官方的”錢包軟體。 現在呢,bitcoin core已經在主線裡合併[輸出描述符(output descriptors)](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md)、[psbt](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)(它是最近幾年由開發者提出的,未簽名交易的標準格式)這些新特性的支援,雖然還沒有正式釋出新版,但是我們仍然可以從開原始碼直接編譯,搶先體驗一下。 **編譯出來的exe呢,不好意思,我就不貼出來了。** 一方面是發附件有點麻煩;另一方面,我覺得既然還沒正式釋出,那最好還是再等等,等到程式碼審查、測試地差不多了,足夠穩定了,才可以支撐起日常使用。 所以說,這裡我使用的也是測試網的測試幣,即便玩脫了(並沒有),也沒有什麼損失。 如果你沒有自己編譯bitcoin-qt.exe,可以直接跳到**第三部分**開始看。 ### 第一部分:生成助記詞,以及助記詞對應的hd主公鑰/主私鑰 第一部分這三步,本來應該在冷端的bitcoin core裡面就能完成的。 第1、2步,按理說,錢包應該是可以直接匯入(或者生成)bip39助記詞的,然後錢包自己就能從助記詞推導得到主私鑰、主公鑰、地址等等;但是bitcoin core目前還不支援bip39助記詞,所以只能用外部工具完成了。 第3步我好像沒找到哪裡能做到,除了直接用sqlite資料庫瀏覽器開啟新版sqlite格式的wallet.dat。 **總之,bitcoin core因為暫不支援bip39助記詞,在這個方面還不夠傻瓜。** 1. 使用ian coleman的bip39工具生成一個bip39助記詞: `infant trumpet upset kid globe domain adapt tool keen turtle rebuild twin` 2. 在bip32 root key這裡,可以看到這個助記詞對應的bip32根節點主私鑰: `tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4` 這裡用的是測試網,所以“coin”這裡應該選擇`btc - bitcoin testnet`,然後才能顯示tprv開頭的測試網主私鑰(否則預設是xprv開頭的主網主私鑰,不能匯入到測試網錢包裡)。 原則上,拿到**主私鑰**,就可以匯入**冷端**錢包了。 3. 在“derivation path”下面選擇bip32,client選擇`custom derivation path`,然後在“bip32 derivation path”裡面輸入派生出收款地址主公鑰的路徑: `m/84'/0'/0'/0` 得到收款地址的主公鑰: `tpubdf1ptmbkbf1ddzgg6vy5ajymag4nunytiv12g26hwkhr7zufrsj93szmrgbwitc1a3vmr8mka7kwr8fccpnb6xg1kfjkpqhudsuhbkxum6c` 然後,再在bip32 derivation path裡面輸入派生出找零地址主公鑰的路徑: `m/84'/0'/0'/1` 得到找零地址的主公鑰: `tpubdf1ptmbkbf1derrsx3dsrx1s8aqmhs2wro9qvpp49o7mqucscies9z58i5gvyzwuedtvfqhijnrzkwlytkidduxo1ucbprkb6fonsxzigjf` (這裡用的bip84規定的標準派生路徑,用來生成bech32原生隔離見證地址) 原則上,拿到**主公鑰**,就可以匯入**熱端**錢包了。 ### 第二部分:建立並設定錢包 4. 以測試網模式啟動最新編譯的bitcoin core,作為熱端: `bitcoin-qt.exe -testnet -datadir=d:\bitcoin\data` 5. 因為這只是一次測試,為了方便,我就不在另一臺真正離線的機器上啟動冷端bitcoin core了,還是在同一臺機器啟動,不過這次換一個資料目錄,而且要讓它不連線比特幣p2p網路(所以當然就不會同步區塊了),這樣就模擬了離線的情況: `bitcoin-qt.exe -testnet -datadir=d:\bitcoin\data-cold -networkactive=0` 可以看到,熱端bitcoin core聯網同步了區塊(右下角是對勾,因為我已經同步完成了); ![01hot.png](https://appserversrc.8btc.com/post/e4a1177765faacd31c6ca8735010772f.png) 而冷端就不會同步,顯示了從零開始的進度條,也不會走: ![02cold.png](https://appserversrc.8btc.com/post/be75b720f05ce6c5df1a115f28a5181e.png) 而且還可以看到,新版bitcoin core已經不會自動建立預設錢包了。 6. 在冷端,點“檔案”選單,選“建立錢包”。 加密不加密無所謂,我這裡選的是不加密(實際上啟用加密也不會加密地址和交易資訊,只加密私鑰); **“禁用私鑰”留空不勾選;** **“建立空白錢包”要勾選;** **“descriptor wallet”要勾選。** ![03coldcreatewallet.png](https://appserversrc.8btc.com/post/c4ad5a36e5e6c2ab95c304fdbc07763b.png) 在熱端,類似地,也需要建立錢包。區別就是,熱端這邊**需要勾選“禁用私鑰”**。 ![04hotcreatewallet.png](https://appserversrc.8btc.com/post/fd886ba558385a14b22cacb89ce782d3.png) **第7、8、10、11、12步這些折騰命令列的麻煩步驟,在比太、electrum等其他支援冷熱分離的錢包裡,就是一個簡單傻瓜的掃描二維碼操作。** bitcoin core目前在這方面還不傻瓜,有點折騰,但是應該也可以看出來,基本的功能已經實現了,要做成掃碼傻瓜化完成,已經是“最後一公里”了。 如果說這裡還有困難,那可能就是錢包資訊匯入/匯出的格式也許還需要標準化——psbt已經把未簽名交易給標準化了,所以錢包其實已經可以“混搭”使用了,如果錢包資訊的匯入/匯出也能標準化,那混搭使用就更傻瓜容易了。 7. 在冷端建立錢包後,點冷端的“視窗”選單,選“控制檯”。因為在bitcoin core裡面用輸出描述符(output descriptors)匯入hd主金鑰的時候,必須提供校驗碼(checksum),所以這裡先用getdescriptorinfo命令計算校驗碼。 首先要把hd根節點主私鑰拿來, `tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4` 改寫成輸出描述符格式。先在tprv開頭的主私鑰後面加上收款地址的hd派生路徑`/84'/0'/0'/0/*`,這樣就變成了: `tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4/84'/0'/0'/0/*` (星號\*表示一個範圍內的多個子私鑰,這裡預設就是從0號子私鑰,也就是第一個收款地址開始) 然後用`wpkh()`把它括起來,表示這些子私鑰要生成bech32原生隔離見證地址(而不是其他型別的地址),變成: `wpkh(tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4/84'/0'/0'/0/*)` 這就是收款地址的輸出描述符。 最後用getdescriptorinfo命令計算它的校驗碼: `getdescriptorinfo "wpkh(tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4/84'/0'/0'/0/*)"` (這裡就不貼圖了,帖子已經很長了)命令執行後,可以看到結果裡有一行`"checksum": "fy95gzqr"`,然後我們就可以把校驗碼加上井號\#,加在後面,變成: `wpkh(tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4/84'/0'/0'/0/*)#fy95gzqr` 現在我們在冷端只搞定了收款地址,還需要如法炮製,搞定找零地址。找零地址的hd派生路徑是`/84'/0'/0'/1/*`,區別就是最後一個0改成1。加上校驗碼後,就是: `wpkh(tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4/84'/0'/0'/1/*)#csq44hsm` 8. 上一步,我們把hd根節點主私鑰改寫成輸出描述符的格式了(而且還加了校驗碼),具體指定了地址型別是bech32,而且還指定了兩個hd派生路徑,分別用來生成收款地址和找零地址。 現在,用importdescriptors命令,就可以把收款、找零這兩個輸出描述符(本質上就是私鑰)匯入進冷端錢包。 匯入收款地址的輸出描述符: `importdescriptors "[{\"desc\":\"wpkh(tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4/84'/0'/0'/0/*)#fy95gzqr\",\"active\":true,\"timestamp\":\"now\",\"internal\":false,\"label\":\"bip39-84-account0-receiving\"}]"` 匯入找零地址的輸出描述符: `importdescriptors "[{\"desc\":\"wpkh(tprv8zgxmbicqkspebuxb4sxhkkmxwrsqvqrtcvaqz2csexkwxhiacbtezrnv8mb6dus61lo64o6ktrp2sszgmk2cp17gr8pcb8nep5dcklevg4/84'/0'/0'/1/*)#csq44hsm\",\"active\":true,\"timestamp\":\"now\",\"internal\":true}]"` 9. 現在冷端錢包已經可以生成收款地址了。回到錢包主介面,切換到“接收”標籤頁,點“新建收款地址”按鈕,就會生成一個測試網收款地址`tb1qtu0ualg6jk9l7h7fkzsjcjfxajlyzmlckx9gfa`。 ![05coldcreateaddress.png](https://appserversrc.8btc.com/post/cbaf818928a3a621b4af8f51c46d8fbf.png) 網上有很多測試網水龍頭,可以索取測試幣,樓主就用這個地址索取了測試幣。 10. 回到冷端的控制檯,輸入這個命令: `getaddressinfo tb1qtu0ualg6jk9l7h7fkzsjcjfxajlyzmlckx9gfa` 可以看到返回結果裡也有輸出描述符資訊: `"desc": "wpkh([702ee02c/84'/0'/0'/0/0]0269343479a77090536f7425b6c079ab799584c8178b27b3d90418007504fcab38)#0rewm4x6"` 但是這裡就只顯示了這個地址的公鑰,並沒有顯示hd主公鑰。我們只取方括號內的部分`[702ee02c/84'/0'/0'/0/0]`,它描述了這個地址的派生來源,我們把它複製出來。其中,`702ee02c`是hd根節點主私鑰的指紋(fingerprint)。 冷端錢包現在已經設定好了。下面就可以設定熱端了。 11. 和冷端類似,我們也需要先用getdescriptorinfo命令計算校驗碼。 這裡要拿來的就不是tprv開頭的hd主私鑰了,而是兩個tpub開頭的hd主公鑰。 先以收款地址的hd主公鑰為例: `tpubdf1ptmbkbf1ddzgg6vy5ajymag4nunytiv12g26hwkhr7zufrsj93szmrgbwitc1a3vmr8mka7kwr8fccpnb6xg1kfjkpqhudsuhbkxum6c` 在它前面加上`[702ee02c/84'/0'/0'/0]`(就是之前在冷端看到的地址派生來源資訊,把最後一個`/0`刪去,因為這裡我們描述的是主公鑰,主公鑰很顯然在地址的上一個層級),再在後面加上派生路徑`/*`,最後用`wpkh()`括起來: `wpkh([702ee02c/84'/0'/0'/0]tpubdf1ptmbkbf1ddzgg6vy5ajymag4nunytiv12g26hwkhr7zufrsj93szmrgbwitc1a3vmr8mka7kwr8fccpnb6xg1kfjkpqhudsuhbkxum6c/*)` 把這個輸出描述符用getdescriptorinfo命令計算校驗碼: `getdescriptorinfo "wpkh([702ee02c/84'/0'/0'/0]tpubdf1ptmbkbf1ddzgg6vy5ajymag4nunytiv12g26hwkhr7zufrsj93szmrgbwitc1a3vmr8mka7kwr8fccpnb6xg1kfjkpqhudsuhbkxum6c/*)"` 得到校驗碼`c8zgl83l`。有了校驗碼,我們就可以把它匯入進去了。 12. 在熱端bitcoin core的“視窗”選單選擇“控制檯”,開啟命令列控制檯,輸入: `importdescriptors "[{\"desc\":\"wpkh([702ee02c/84'/0'/0'/0]tpubdf1ptmbkbf1ddzgg6vy5ajymag4nunytiv12g26hwkhr7zufrsj93szmrgbwitc1a3vmr8mka7kwr8fccpnb6xg1kfjkpqhudsuhbkxum6c/*)#c8zgl83l\",\"active\":true,\"timestamp\":\"now\",\"internal\":false,\"label\":\"bip39-84-account0-receiving\"}]"` 這樣就給熱端匯入了收款地址的hd主公鑰。 同理,如法炮製,可以把找零地址的hd主公鑰也匯入進去: `importdescriptors "[{\"desc\":\"wpkh([702ee02c/84'/0'/0'/1]tpubdf1ptmbkbf1derrsx3dsrx1s8aqmhs2wro9qvpp49o7mqucscies9z58i5gvyzwuedtvfqhijnrzkwlytkidduxo1ucbprkb6fonsxzigjf/*)#f79wwrk7\",\"active\":true,\"timestamp\":\"now\",\"internal\":true}]"` 好了,現在冷端、熱端都設定好了。下面就是激動人心的—— ### 第三部分:(模擬)離線冷簽名 這裡我是為了操作方便,才在同一臺機器上同時執行冷端和熱端,所以只能叫模擬離線冷簽名。不過很顯然,整個操作在兩臺真正冷熱分離的不同機器上也是可以完成的。 13. **可以看到,熱端同步區塊後就看到了交易記錄;冷端不聯網同步,當然暫時就看不到收款,也不需要冷端看到,這不是冷端的分工職責。** ![06hottransactionreceived.png](https://appserversrc.8btc.com/post/a0b6fbcf7e1728299eff39a03e3e32e6.png) 而且,**沒有私鑰的熱端,也是可以新建收款地址的**(這個地址也是從hd生成的,歸根到底是從bip39助記詞推匯出來的): ![07hotcreateaddress.png](https://appserversrc.8btc.com/post/dd518d2a1e4cd82a095686cd84ad2918.png) 只不過之前我們是用冷端生成了收款地址,用來接收測試網水龍頭髮來的測試幣。 bitcoin core貌似是不允許花掉零確認收款的,需要等至少1個區塊確認才能花掉。**現在水龍頭髮來的測試幣已經收到,在熱端的“傳送”標籤頁,我們輸入一個地址(這裡就是測試網水龍頭的測試幣回收地址),試試看怎麼用離線冷簽名把剛剛收到的測試幣花出去:** ![08hotcomposetransaction.png](https://appserversrc.8btc.com/post/cef058b7b423b720c38684d2bd5ddbfe.png) 點“建立未簽名交易”,出現了3秒確認操作視窗: ![09hotcomposetransactionclickthrough.png](https://appserversrc.8btc.com/post/b28e53985a8ed71b1caa7bedaee14bba.png) 3秒等待結束後,點“create unsigned”,彈窗提示未簽名交易(是標準的psbt格式)已經建立,並且複製到了剪貼簿(用記事本貼上,可以看到是base64編碼的),還詢問我們是否儲存到檔案: ![10hotpsbtcreated.png](https://appserversrc.8btc.com/post/4e2f3c228ff43cf6bfe5cfab4f6a201d.png) 可以想見,這個base64編碼過的psbt未簽名交易,是**可以用二維碼等手段在冷端和熱端之間傳遞的**。只是出於測試方便,這裡我選擇了“discard”,不儲存到檔案,只用剪貼簿。(我試過儲存到檔案,得到的psbt檔案是二進位制格式,但是不知道為什麼,不能被冷端載入,載入時會報錯。如果要儲存的話,可以用記事本貼上,把base64編碼的psbt資料儲存起來) 不過有一說一,二維碼的資料容量也有限,單張二維碼只能容下不到4kb的內容,如果交易內容太大,單張二維碼就容不下了,所以具體要怎麼解決貌似還沒有定論(比太對這個問題的對策就是拆分成多張二維碼)。 14. 現在回到冷端,在“檔案”選單中選擇“load psbt from clipboard”,即可從剪貼簿中讀取剛剛生成的未簽名psbt交易資料。 對話方塊裡綠色高亮文字提示使用者,這個未簽名psbt交易,在這裡(冷端)是可以進行簽名的。下面還顯示了目標地址、金額等重要資訊: ![11coldreadytosign.png](https://appserversrc.8btc.com/post/70c775176147d58fc5251a925b4494a2.png) 一定要核對好這些資料,確認無誤後,再點“sign tx”按鈕,簽名這個交易。 然後對話方塊裡,綠色高亮文字提示使用者:簽名完成了: ![12coldsigned.png](https://appserversrc.8btc.com/post/0b83af221e1f9ab63684123c68e6f354.png) 但是很顯然冷端不聯網,不能廣播交易,所以點“copy to clipboard”按鈕,把已經簽名的psbt交易複製到剪貼簿,這樣就可以傳遞給熱端。 15. 交易簽名好了。現在回到熱端,在“檔案”選單中選擇“load psbt from clipboard”,這次從剪貼簿里載入的,就是已經簽名的psbt交易資料了。 對話方塊裡,綠色高亮文字提示使用者:交易已經完全簽名,已經可以廣播了: ![13hotreadytobroadcast.png](https://appserversrc.8btc.com/post/597653965141d7770bd0e05cdcedc37c.png) 所以還等什麼呢?點“broadcast tx”按鈕,交易就廣播到比特幣p2p網路裡、等待礦工打包確認了。 綠色高亮文字提示使用者:廣播成功,並且顯示了交易的txid: ![14hotsent.png](https://appserversrc.8btc.com/post/816802442e0f9faa1ff7e1e61239ee91.png) ### 啊,好不容易,這次測試終於完成了。 可以看出來,雖然目前來看,bitcoin core想要把(使用bip39助記詞的hd錢包)錢包設定好還比較麻煩,需要折騰命令列; 但是,你應該也能看出來,其實這次測試之所以看上去麻煩,主要還是為了使用hd錢包,可以每次轉入/轉出幣都更換新地址,而不是重複使用單個私鑰。 **拋開錢包設定這個環節不談,就離線簽名這個環節,已經是非常輕鬆傻瓜了**——之前想要達成一樣的目的,可是要折騰listunspent、createrawtransaction、decoderawtransaction、signrawtransactionwithkey、sendrawtransaction等等等等很多命令的,而且想要用hd錢包還會更麻煩——在支援“輸出描述符”之前,bitcoin core的hd預設只能是hardened derivation,換句話說,沒辦法匯出hd主公鑰給熱端來生成地址、監控錢包金額和交易記錄。 據我所知,不少core開發者非常不喜歡bip39……不過,至少其中一個本來不喜歡bip39的開發者,也提議擴充輸出描述符的功能、加入bip39支援——雖然這只是個提議,但應該也是很積極的一個訊號。所以,大家就拭目以待吧。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;