2022年1月27日 星期四

密碼貨幣:從助憶詞到種子、主鑰、私鑰、公鑰、位址

為密碼貨幣創立 (不管哪一牌的) 非託管錢包 (例如 metamask) 的過程當中, 錢包會用亂數幫你產生12個英文助憶詞。 這12個助憶詞非常重要, 既不能遺失, 也不能被別人看到, 必須很小心地秘密保存且備份, 因為你的私鑰公鑰都是從這12個字產生出來的。 但實際的產生過程到底有哪些步驟? 我可以自己手動產生嗎? 這一篇沒有太大現實用處的好奇文筆記了我的心得。

Maarten Zuidhoorn 的 "The Journey from Mnemonic Phrase to Address" 大致回答了我的問題。 不過請找並點擊到 「留言」 的 icon, 會發現有一位 (比特幣開發者?) Gregory Gualtieri 對文章稍有一些指正。 我又找到 bip_utils 這個函式庫, 拿它把文章解釋的觀念實作一次, 得到 bip_demo.py 這個測試程式碼。 下圖是我的心得精華。

助憶詞/seed/主鑰/私鑰/公鑰/位址轉換

這12個位置每個位置有 2048=2^11 個不同的英文字 可以填入, 也就是總共帶有11*12=132 bits 的資訊。 但當中其實只有 128 個 bits 可以任意選取組合, 這部分稱為 entropy; 另外四個 bits 其實是 entropy 的檢查碼。 也就是說其實你可以從上述 2048 英文字清單當中任意自選11個英文字 (可重複、 順序有關係) 然後這清單當中約有 16 分之 1 (128 個) 可以作為你的第12個助憶詞。

其實不止英文, 另外還有西班牙文、 正體中文、 簡體中文... 等等 各種語言的2048字詞清單。 但是因為接下來的運算會拿 12 個字詞的字串而不是拿那 132 bits 下去運算, 所以 對應到相同一組 entropy 的 不同語言 字詞組會算出 不同的私鑰公鑰與位址

12 個字詞的字串會被拿來用 pbkdf2 這個單向函數 (它會重複呼叫某個 單向雜湊函數) 算出種子 (seed)。 這個種子再被套到某條橢圓曲線上面, 算出 master key。 Master key 又可以拿來算出 public key 跟 private key。 其實 private key 也可以倒推回 master key。 另外 private key 可以求出 public key, 而 public key 又可以求出位址; 但反之不然。

有了第一組金鑰, 接下來可以呼叫 DerivePath() 函數, 產生很多組衍生金鑰。 這些衍生金鑰彼此之間有著類似子目錄 (資料夾) 之間的關係, 每組公鑰私鑰有著自己的 「路徑」 (path)。 而且上層的公鑰可以產生下層的直系子孫公鑰; 上層的私鑰可以產生下層的直系子孫私鑰。 只有最開始的助憶詞是用亂數產生的; 其他後續產生種子、主鑰到這一家子所有金鑰產生的過程當中, 一路上用的都是固定的函數, 沒有用到亂數。 也就是說只要有助憶詞和某組公鑰私鑰的路徑 (例如 m/44'/60'/0'/0/0), 就一定可以算出這一組公鑰私鑰位址。 所以這一套產生一大家族金鑰與位址的運作方式稱為 hierarchical deterministic wallets。 (請圖片搜尋) HDWallet 原理分析 這篇中文文章有更詳細的解說。

全世界那麼多人在產生金鑰, 甚至還有程式碼可以自動無限產生金鑰, 那會不會有相撞的機會呢? 萬一相撞的話, 就是共用同一個位址, 兩人都可以花那個位址的錢, 不是很可怕嗎? 如果你很認真地跑程式, 可能要花宇宙一輩子的生命還不止 才有機會找到相撞的位址, 而且那個位址裡面還不一定有錢。 所以就安心吧。

我的 bip_demo.py 的第一部分照著 Zuidhoorn 的文章前半部的例子實作, 並且用 exodus 錢包實際測試。 你當然不要拿這組已經被公開的金鑰來當你的錢包。 文章的第二部分換了另一個例子, 我的 bip_demo.py 也跟著用新的 seed 重算一次。 至於 bip_demo.py 的第三部分則在說明: 任給一個私鑰, 你都可以把它變成主鑰, 再用它來產生一家子的公鑰/私鑰/位址。 你必須先把已知的私鑰放在一個名為 priv_key_file.txt 的文字檔裡, 第一部分才會執行。 這應該有一些實用價值。

寫程式的過程, 除了參考 bip_utils 的範例和搜尋, 也需要閱讀 bip_utils 的程式碼, 特別是各種類別物件之間的轉換 (可以用 pip3 show bip_utils 查詢原始碼存放路徑)。 我在圖中把一些關鍵的函數名稱畫出來, 方便你進原始碼去搜尋相關的片段。

沒有留言:

張貼留言

因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。