用測試幣搶先體驗Bitcoin Core的冷錢包離線簽名新功能
By ChainNode·
錢包冷熱分離,其實早就不是什麼新鮮的概念了。
**簡單說,冷端和熱端兩臺裝置是分工的。**
熱端聯網,完全不含有私鑰,負責同步區塊鏈賬本、監視錢包裡的餘額和交易記錄,還負責生成收款地址、生成未簽名交易,然後在冷端簽名交易後,把交易廣播出去。
冷端自始至終都不聯網,負責生成、使用私鑰(包括助記詞);熱端傳遞過來的未簽名交易,冷端負責對其進行檢查、簽名。
**這樣私鑰自始至終都可以“不觸網”,冷熱兩端可以用二維碼等不直接聯網的方式來傳遞交易資料。**
**不過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支援——雖然這只是個提議,但應該也是很積極的一個訊號。所以,大家就拭目以待吧。#資訊
免責聲明:
- 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
- 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
- 鏈報僅提供相關項目信息,不構成任何投資建議。
推荐阅读