TrueChain-Beta主網賬戶地址生成解析
初鏈主網Beta版於新加坡時間2018年09月28日08:00正式上線,在此之前,07:56分PBFT委員會第一次共識出塊和TrueChain fPOW創世區塊被挖出,而作為true 社群的一員,我也參與了這次初鏈主網Beta版的測試挖礦。目前true 主網的beta 版在2018年10月10日第一期的測試已經停止,第二期的創世區塊預計在10月15日啟動,並且恢復Beta版主網的全網執行。由於主網beta版已經停止,因此我將基於true 主網beta版本的程式碼搭建私鏈,並且進行解釋,true 私鏈的搭建大家可以參考true 社群成員寫的True鏈開發實戰 進行自行搭建。
True 建立創世區塊:命令:getrue --datadir Test init path/to/cmd/getrue/genesis.json
此命令主要作用為生成創世區塊,生成帳號的一些資訊。Test資料夾記憶體放區塊資訊和賬號資訊,日誌資訊,genesis.json則為初始化的一些配置引數。
True -Beta 私鏈啟動
True 啟動測試鏈命令:getrue --datadir Test --nodiscover console
啟動命令執行,我們要找到整個true 專案的入口函式,即main函式,位於cmd/getrue/main.go內
func main() { if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
main函式是getrue命令的入口函式,main.go檔案中有一個**main() 和init()**函式,懂go語言的知道會先執行init()函式,init函式初始化配置一個解析命令的庫。其中app.Action = getrue 則說明如果使用者在沒有輸入其他的子命令的情況下會呼叫這個欄位指向的函式app.Action = getrue,即main.go中的func getrue(ctx *cli.Context) 函式.然後執行main函式,呼叫 app.Run(os.Args) , os.Args 為系統引數(例: --datadir Test --nodiscover console)。我們來看看這個 app 到底是啥
func init() { // 初始化CLI APP庫 並且執行getrue app.Action = getrue app.HideVersion = true // we have a command to print the version app.Copyright = "Copyright 2013-2018 The getrue Authors" app.Commands = []cli.Command{ // See chaincmd.go: initCommand, importCommand, exportCommand, importPreimagesCommand, exportPreimagesCommand, ...
根據main.go的一段程式碼:var app = utils.NewApp(gitCommit, "the go-ethereum command line interface"),我們可以看到此app 是由 app = utils.newApp() 方法建立的,我們進入到 ‘cmd/utils’ 資料夾下面,可以看到newApp() 方法位於flag.go 檔案內
// NewApp creates an app with sane defaults. func NewApp(gitCommit, usage string) *cli.App { app := cli.NewApp()// 建立app app.Name = filepath.Base(os.Args[0]) app.Author = "" //app.Authors = nil app.Email = "" app.Version = params.Version if len(gitCommit) >= 8 { app.Version += "-" + gitCommit[:8] } app.Usage = usage return app }
我們追溯上面的cli.NewApp(),會發現NewApp方法在是vender/gopkg.in/urfave/cli .v1/app.go這個第三方的包內
// NewApp creates a new cli Application with some reasonable defaults for Name, // Usage, Version and Action. func NewApp() *App { return &App{ Name:filepath.Base(os.Args[0]), HelpName:filepath.Base(os.Args[0]), Usage:"A new cli application", UsageText:"", Version:"0.0.0", BashComplete: DefaultAppComplete, Action:helpCommand.Action, Compiled:compileTime(), Writer:os.Stdout, } }
第三方包的大用法大致就是首先構造這個app物件,通過程式碼配置app物件的行為,提供一些回撥函式。然後執行的時候直接在main函式裡執行app.Run(os.Args)
賬戶地址生成
當我們使用personal.newAccount(password) 建立賬戶的時候如下:
上圖我們可以看到 true-beta的地址,每個地址代表一個賬戶,每個true地址在true-beta網路中都是唯一存在的。 當我們輸入新建賬戶的命令後,系統會給我們返回這個賬戶的地址,那true 是如何生成這串在全網唯一的字串?那就得從入口檔案cmd/getrue/main.go分析
main.go的init()方法中有個命令初始化,accountCommand則是解析賬戶操作的命令,原始碼註釋為賬戶命令詳情在accountcmd.go檔案內
func init() { // Initialize the CLI app and start Getrue app.Action = getrue app.HideVersion = true // we have a command to print the version app.Copyright = "Copyright 2013-2018 The getrue Authors" app.Commands = []cli.Command{ // See chaincmd.go: initCommand, importCommand, exportCommand, importPreimagesCommand, exportPreimagesCommand, copydbCommand, removedbCommand, dumpCommand, // See monitorcmd.go: monitorCommand, // See accountcmd.go: // 賬戶命令詳情在accountcmd.go 檔案內 accountCommand,
新建賬戶的命令為new,會呼叫accountCreate 方法,原始碼位於cmd/getrue/accountcmd.go檔案內
{ Name:"new", Usage:"Create a new account", Action: utils.MigrateFlags(accountCreate), Flags: []cli.Flag{ utils.DataDirFlag, utils.KeyStoreDirFlag, utils.PasswordFileFlag, utils.LightKDFFlag, }, Description: ` getrue account new Creates a new account and prints the address. The account is saved in encrypted format, you are prompted for a passphrase. You must remember this passphrase to unlock your account in the future. For non-interactive use the passphrase can be specified with the --password flag: Note, this is meant to be used for testing only, it is a bad idea to save your password to file or expose in any other way. `, }, ...... // accountCreate creates a new account into the keystore defined by the CLI flags. func accountCreate(ctx *cli.Context) error { cfg := gethConfig{Node: defaultNodeConfig()} // Load config file. if file := ctx.GlobalString(configFileFlag.Name); file != "" { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } } utils.SetNodeConfig(ctx, &cfg.Node) scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() if err != nil { utils.Fatalf("Failed to read configuration: %v", err) } password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) if err != nil { utils.Fatalf("Failed to create account: %v", err) } fmt.Printf("Address: {%x}\n", address) return nil }
我們可以看到上面的程式碼中生成地址的程式碼是address,err := keystore.StoreKey(keydir, password, scryptN, scryptP),呼叫keystore.StoreKey()方法,傳入4個引數,這4個引數的意思分別為:
-
keydir: 生成的key 儲存的位置
-
password: 解鎖賬戶的密碼,是personal.newAccount(password)中的password.
-
scryptN,scryptP :兩個整型數scryptN,scryptP,這兩個整型引數在keyStorePassphrase物件生命週期內部是固定不變的,只能在建立時賦值。
通過scryptN, scryptP, keydir, err := cfg.Node.AccountConfig()這段程式碼可知scryptN, scryptP, keydir 這3個值是通過賬戶配置檔案配置而來,
我們再進入到keystore.StoreKey()方法中,原始碼位於accounts/keystore/keystore_passphrase.go
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory // 通過傳入的變數,返回一個地址,和一個錯誤,若賬戶地址成功生成,那麼 error = nil func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) { _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, crand.Reader, auth) return a.Address, err }
StoreKey呼叫了一個storeNewKey()方法,storeNewKey方法中呼叫了newKey()方法。其原始碼位於accounts/keystore/key.go
func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) {
key, err := newKey(rand)
if err != nil {
return nil, accounts.Account{}, err
}
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}}
if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
zeroKey(key.PrivateKey)
return nil, a, err
}
return key, a, err
}
.......
func newKey(rand io.Reader) (*Key, error) {
privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
if err != nil {
return nil, err
}
return newKeyFromECDSA(privateKeyECDSA), nil
}
......
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
id := uuid.NewRandom()
key := &Key{
Id:id,
Address:crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
PrivateKey: privateKeyECDSA,
}
return key
}
在newKey方法中可以看到privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)true採了橢圓曲線數字簽名演算法(ECDSA)生成私鑰, 從newKeyFromECDSA方法中看出傳入的私鑰,通過crypto.PubkeyToAddress()生成地址,那麼我們就要看看PubkeyToAddress()這個方法,此方法的原始碼位於crypto/crypto.go
func PubkeyToAddress(p ecdsa.PublicKey) common.Address { pubBytes := FromECDSAPub(&p) return common.BytesToAddress(Keccak256(pubBytes[1:])[12:]) } ...... // Keccak256 calculates and returns the Keccak256 hash of the input data. func Keccak256(data ...[]byte) []byte { d := sha3.NewKeccak256() for _, b := range data { d.Write(b) } return d.Sum(nil) }
從上面的原始碼我們可以看出,通過傳入的私鑰,**pubBytes := FromECDSAPub(&p)**使用ECDSA演算法推匯出公鑰,再將推匯出的公鑰經過Keccak-256單線雜湊函式推匯出地址。最後一步步返回。
總結True-beta主網生成賬戶地址的過程:
-
通過橢圓曲線數字簽名演算法(ECDSA)建立隨機私鑰
-
從生成的隨機私鑰推匯出公鑰
-
從公鑰推匯出地址
有想了解非對稱加密之橢圓曲線加密過程的可以自行百度。
當拿到了地址之後並且列印了地址。然後就是儲存keystore 檔案。通過解析啟動節點時的–datadir命令引數,即會配置config,將生成的keystore 儲存至 --datadir 指定資料夾下的keystore 資料夾下。寫入的原始碼位置為accounts/keystore/keystore_passphrase.go:
func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error { keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) if err != nil { return err } // 寫入keystore 檔案 return writeKeyFile(filename, keyjson) } ... // 這是將生成的賬戶地址資訊儲存為keystore.json func writeKeyFile(file string, content []byte) error { // Create the keystore directory with appropriate permissions // in case it is not present yet. //定義生成的檔案是否可讀可寫,可執行。0700代表當前使用者可讀可寫可執行 const dirPerm = 0700 if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil { return err } // Atomic write: create a temporary hidden file first // then move it into place. TempFile assigns mode 0600. f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp") if err != nil { return err } if _, err := f.Write(content); err != nil { f.Close() os.Remove(f.Name()) return err } f.Close() return os.Rename(f.Name(), file) }
這就是我主要解析的true-beta主網的生成賬戶地址並且儲存生成的賬戶地址的keystore.json檔案的原始碼。
true 的主要創新點在於 TrueChain採用PBFT+PoW混合共識,形成雙鏈結構,將PBFT作為快鏈,而將PoW作為慢鏈,快鏈用於儲存賬本和達成交易,慢鏈用於挖礦和委員會選舉,從而實現了公平和效率的有效融合,並使得資料結構變得更加清晰,使兩種共識之間的通訊變得更加容易。
其次原創性的推出TrueHash,實現不依賴手動調整,從而根本上抗ASIC的挖礦演算法
再是初鏈“交易處理擔當”主要為PBFT委員會,為保證公平和安全性,初鏈PBFT委員會將定期換屆trueChain實現了隨機演算法在換屆時公平地選舉PBFT委員,基於VRF(verify random function,可驗證隨機函式)可以實現PBFT節點等概率的公平選舉。而這裡面也會有一些過濾,過濾掉一些挖水果特別少的礦工,因為他們挖的特別少,可能他們的硬體裝置或者網路情況不太好。從剩下滿足處理交易條件的節點中,隨機的公平的選舉出來PBFT委員會成員。
作者:一步之遙hh
來源:CSDN