做者:freewind前端
比原項目倉庫:git
Github地址:https://github.com/Bytom/bytomgithub
Gitee地址:https://gitee.com/BytomBlockchain/bytomweb
在前一篇,咱們探討了從瀏覽器的dashboard中進行註冊的時候,密鑰、賬戶的別名以及密碼,是如何從前端傳到了後端。在這一篇,咱們就要看一下,當比原後臺收到了建立密鑰的請求以後,將會如何建立。算法
因爲本文的問題比較具體,因此就不須要再細分,咱們直接從代碼開始。json
還記得在前一篇中,對應建立密鑰的web api的功能點的配置是什麼樣的嗎?後端
在API.buildHandler
方法中:api
func (a *API) buildHandler() { // ... if a.wallet != nil { // ... m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey)) // ...
可見,其路徑爲/create-key
,而相應的handler是a.pseudohsmCreateKey
(外面套着的jsonHandler
在以前已經討論過,這裏不提):dom
func (a *API) pseudohsmCreateKey(ctx context.Context, in struct { Alias string `json:"alias"` Password string `json:"password"` }) Response { xpub, err := a.wallet.Hsm.XCreate(in.Alias, in.Password) if err != nil { return NewErrorResponse(err) } return NewSuccessResponse(xpub) }
它主要是調用了a.wallet.Hsm.XCreate
,讓咱們跟進去:
blockchain/pseudohsm/pseudohsm.go#L50-L66
// XCreate produces a new random xprv and stores it in the db. func (h *HSM) XCreate(alias string, auth string) (*XPub, error) { // ... // 1. normalizedAlias := strings.ToLower(strings.TrimSpace(alias)) // 2. if ok := h.cache.hasAlias(normalizedAlias); ok { return nil, ErrDuplicateKeyAlias } // 3. xpub, _, err := h.createChainKDKey(auth, normalizedAlias, false) if err != nil { return nil, err } // 4. h.cache.add(*xpub) return xpub, err }
其中出現了HSM
這個詞,它是指Hardware-Security-Module
,原來比原還預留了跟硬件相關的模塊(暫不討論)。
上面的代碼分紅了4部分,分別是:
alias
參數進行標準化操做,即去兩邊空白,而且轉換成小寫cache
中有沒有,有的話就直接返回並報個相應的錯,不會重複生成,由於私鑰和別名是一一對應的。在前端能夠根據這個錯誤提醒用戶檢查或者換一個新的別名。createChainKDKey
生成相應的密鑰,並拿到返回的公鑰xpub
因此咱們進入h.cache.hasAlias
看看:
blockchain/pseudohsm/keycache.go#L76-L84
func (kc *keyCache) hasAlias(alias string) bool { xpubs := kc.keys() for _, xpub := range xpubs { if xpub.Alias == alias { return true } } return false }
經過xpub.Alias
咱們能夠了解到,原來別名跟公鑰是綁定的,alias
能夠看做是公鑰的一個屬性(固然也屬於相應的私鑰)。因此前面把公鑰放進cache,以後就能夠查詢別名了。
那麼第3步中的createChainKDKey
又是如何生成密鑰的呢?
blockchain/pseudohsm/pseudohsm.go#L68-L86
func (h *HSM) createChainKDKey(auth string, alias string, get bool) (*XPub, bool, error) { // 1. xprv, xpub, err := chainkd.NewXKeys(nil) if err != nil { return nil, false, err } // 2. id := uuid.NewRandom() key := &XKey{ ID: id, KeyType: "bytom_kd", XPub: xpub, XPrv: xprv, Alias: alias, } // 3. file := h.keyStore.JoinPath(keyFileName(key.ID.String())) if err := h.keyStore.StoreKey(file, key, auth); err != nil { return nil, false, errors.Wrap(err, "storing keys") } // 4. return &XPub{XPub: xpub, Alias: alias, File: file}, true, nil }
這塊代碼內容比較清晰,咱們能夠把它分紅4步,分別是:
chainkd.NewXKeys
生成密鑰。其中chainkd
對應的是比原代碼庫中的另外一個包"crypto/ed25519/chainkd"
,從名稱上來看,使用的是ed25519
算法。若是對前面文章「如何連上一個比原節點」還有印象的話,會記得比原在有新節點連上的時候,就會使用該算法生成一對密鑰,用於當次鏈接進行加密通訊。不過須要注意的是,雖然二者都是ed25519
算法,可是上次使用的代碼倒是來自第三方庫"github.com/tendermint/go-crypto"
的。它跟此次的算法在細節上究竟有哪些不一樣,目前還不清楚,留待之後合適的機會研究。而後是傳入chainkd.NewXKeys(nil)
的參數nil
,對應的是「隨機數生成器」。若是傳的是nil
,NewXKeys
就會在內部使用默認的隨機數生成器生成隨機數並生成密鑰。關於密鑰算法相關的內容,在本文中並不探討。62bc9340-f6a7-4d16-86f0-4be61920a06e
這樣的全球惟一的隨機數咱們再詳細講一下第3步,把密鑰保存成文件。首先是生成文件名,keyFileName
函數對應的代碼以下:
blockchain/pseudohsm/key.go#L96-L101
// keyFileName implements the naming convention for keyfiles: // UTC--<created_at UTC ISO8601>-<address hex> func keyFileName(keyAlias string) string { ts := time.Now().UTC() return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), keyAlias) }
注意這裏的參數keyAlias
實際上應該是keyID
,就是前面生成的uuid。寫成alias
有點誤導,已經提交PR#922。最後生成的文件名,形如:UTC--2018-05-07T06-20-46.270917000Z--62bc9340-f6a7-4d16-86f0-4be61920a06e
生成文件名以後,會經過h.keyStore.JoinPath
把它放在合適的目錄下。一般來講,這個目錄是本機數據目錄下的keystore
,若是你是OSX系統,它應該在你的~/Library/Bytom/keystore
,若是是別的,你能夠經過下面的代碼來肯定DefaultDataDir()
關於上面的保存密鑰文件的目錄,究竟是怎麼肯定的,在代碼中實際上是有點繞的。不過若是你對這感興趣的話,我相信你應該能自行找到,這裏就不列出來了。若是找不到的話,能夠試試如下關鍵字:pseudohsm.New(config.KeysDir())
, os.ExpandEnv(config.DefaultDataDir())
, DefaultDataDir()
,DefaultBaseConfig()
在第3步的最後,會調用keyStore.StoreKey
方法,把它保存成文件。該方法代碼以下:
blockchain/pseudohsm/keystore_passphrase.go#L67-L73
func (ks keyStorePassphrase) StoreKey(filename string, key *XKey, auth string) error { keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) if err != nil { return err } return writeKeyFile(filename, keyjson) }
EncryptKey
裏作了不少事情,把傳進來的密鑰及其它信息利用起來生成了JSON格式的信息,而後經過writeKeyFile
把它保存硬盤上。因此在你的keystore
目錄下,會看到屬於你的密鑰文件。它們很重要,千萬別誤刪了。
a.wallet.Hsm.XCreate
看完了,讓咱們回到a.pseudohsmCreateKey
方法的最後一部分。能夠看到,當成功生成key以後,會返回一個NewSuccessResponse(xpub)
,把與公鑰相關的信息返回給前端。它會被jsonHandler
自動轉換成JSON格式,經過http返回過去。
在此次的問題中,咱們主要研究的是比原在經過web api接口/create-key
接收到請求後,在內部作了哪些事,以及把密鑰文件放在了哪裏。其中涉及到密鑰的算法(如ed25519
)會在之後的文章中,進行詳細的討論。