目錄
- 1. 橢圓曲線是什麼?
- 2. 私鑰的簡單介紹
- 3. 助記詞
- 使用 Go 語言生成私鑰、公鑰、地址
- 總結
於是乎,我決定自己寫一個系列文章。記錄我的學習研究成果。
我認為,用代碼一步步生成一個私鑰、公鑰、地址,是更好的學習方式,接下來就使用 Go 語言來做到這一點。
寫代碼之前,先補充一些必備知識。
1. 橢圓曲線是什麼?
以太坊和比特幣使用完全相同的橢圓曲線:secp256k1,公鑰就是這個橢圓曲線上的(x, y)坐標,x,y 數值通過私鑰唯一確定。
x、y 各 32 字節,故公鑰為 64 字節。你可能會看到 65 字節表示的公鑰,這是由 SECG 發佈的行業標準的一種序列化編碼方式,在最前面加一個字節的前綴,04 表示公鑰為非壓縮格式,即完整存儲瞭 x 和 y 的坐標各 32 字節。
為什麼要分為 壓縮公鑰 和 非壓縮公鑰 呢?
因為在secp256k1的橢圓曲線方程裡,隻要知道其中一個坐標值,另一個坐標值可以通過解方程得出,因此可以隻存儲其中一個坐標,這樣就可以節約 32 個字節,從而引入瞭壓縮格式的公鑰,前綴為 02 或 03。
故非壓縮格式的公鑰 65 字節(前綴 04 + 64 字節的實際公鑰),壓縮格式的公鑰 33 字節(前綴 02 或 03 + 32 字節的一個坐標值)。
推薦閱讀:吳軍《數學之美》第 31 章 橢圓曲線加密原理
2. 私鑰的簡單介紹
以太坊私鑰隻是一個隨機生成的數字。這個數字是 256 位的,且必須是完全隨機的,你可以投擲硬幣 256 次,得到一個二進制數字作為私鑰。隻要不可預測且不可重復,具體怎麼得到這個 256 位的隨機數字並不重要。
大多數情況下,我們都是寫代碼生成私鑰,這時候最重要的就是找到密碼學安全的隨機源,千萬不要自己生成隨機數或使用編程語言自帶的簡單隨機數,你使用的隨機數一定要是密碼學安全的,這一點至關重要。
私鑰的重要性不言而喻。需要強調的是,私鑰絕對不可以丟失,和銀行卡密碼不一樣,私鑰如果丟失無法像找回密碼那樣還能恢復,這是去中心化的特點。
在中心化世界裡,我們用銀行卡不擔心密碼丟失,因為可以找回,但是消費記錄、數據隱私無法保證,好比在互聯網上裸奔。
在去中心化世界,數據倒是可以做到隱私(雖然目前來看,大傢並不關心隱私),但是很不方便,比如保存私鑰就是個很麻煩的問題。
我不是想談數據隱私、保存私鑰的話題,因為目前沒什麼好的解決方案。而是想引出我一直以來的想法,就是任何事物都具有兩面性。正所謂 "禍兮福所倚,福兮禍所伏",當你學習有負面情緒,堅持不下去的時候,想一想負面情緒的好處。我認為正是因為有瞭負面情緒的存在,才擋住瞭很多比我們更聰明的人的腳步,讓我們得以有機會超越他們。如果學習沒有負面情緒,那所有人都堅持學習瞭,那大傢隻能看誰最聰明瞭,這樣哪還有普通人的機會。從這個角度,我們需要感謝負面情緒。
3. 助記詞
助記詞涉及到錢包的行業標準瞭,我會在本系列第三篇進一步講解。現在,簡單瞭解一下,前面說瞭,私鑰是隨機生成的一個數字,那如果需要 10 個賬戶?100 個賬戶?甚至 1000000 個賬戶呢?就要隨機生成 1000000 個私鑰嗎?這麼多私鑰怎麼管理?怎麼保存?基於此,助記詞誕生瞭。
助記詞可以管理成千上萬個私鑰,這就需要瞭解以太坊的 2 種錢包瞭,從下一篇文章開始,我將連續介紹錢包及開發一個錢包。
使用 Go 語言生成私鑰、公鑰、地址
下圖是生成私鑰、公鑰、地址的基本原理,現在根據這個原理圖,寫出相應的代碼。

需要引入的包
import (
"crypto/elliptic"
"crypto/rand"
"encoding/hex"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
)
生成一個橢圓曲線。
curve := secp256k1.S256()
生成私鑰(32byte)
b := make([]byte, curve.Params().N.BitLen()/8)
io.ReadFull(rand.Reader, b)
key := new(big.Int).SetBytes(b) // key就是私鑰
// 打印私鑰
fmt.Println("key:", len(key.Bytes()))
fmt.Println("key:", hex.EncodeToString(key.Bytes()))
生成公鑰:對私鑰進行橢圓曲線加密,生成公鑰(64byte)
X, Y := curve.ScalarBaseMult(key.Bytes())
pubKey := elliptic.Marshal(curve, X, Y)
fmt.Println("pubKey:", pubKey)
生成地址:去掉公鑰第一個字節 04,再使用 keccak256 算法壓縮公鑰,最後的 20byte 就是地址
compressPubKey := crypto.Keccak256(pubKey[1:])
addr := common.BytesToAddress(compressPubKey[12:])
fmt.Println("addr:", addr.String())
驗證私鑰和地址匹配
將第 3 步產生的私鑰導入 metamask
查看 metamask 生成的地址,和第 5 步計算出的地址是否匹配
總結
記住上面的基本原理流程圖,就算是學會瞭本文最重要的內容。
原创文章,作者:fanbi,如若转载,请注明出处:https://www.fanbi.net/archives/3066