Accounts源碼分析與邏輯結(jié)構(gòu)1
總所周知以太坊在比特幣的基礎(chǔ)上加以引用與改進(jìn),比特幣使用UTXO來表示狀態(tài)的轉(zhuǎn)移,而以太坊使用賬來表示狀態(tài)的轉(zhuǎn)移。
accounts包實(shí)現(xiàn)了以太坊客戶端的錢包和賬戶管理
在以太坊網(wǎng)絡(luò)中存在兩種賬戶:
外部賬戶EOA:一般是屬于個(gè)人或者用戶的賬戶,被私鑰控制沒有任何代碼與之相關(guān)
內(nèi)部賬戶CA:給智能合約分配的賬戶,被合約代碼控制,且與合約關(guān)聯(lián)
在源碼core/state/state_object.go文件下,賬戶定義如下:
// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // merkle root of the storage trie
CodeHash []byte
}
Nonce:如果是EOA賬戶表示發(fā)送交易的序號(hào),如果為CA賬戶,則Nonce表示合約創(chuàng)建的序號(hào)
Balance:表示賬戶的余額,該賬戶地址對(duì)應(yīng)的賬戶余額
Root:存儲(chǔ)merkle樹的根,如果為EOA賬戶root為nil
CodeHash:賬戶綁定的EVM Code,如果為EOA則CodeHash為nil
錢包interface,是指包含了一個(gè)或多個(gè)賬戶的軟件錢包或者硬件錢包:
type Wallet interface {
// URL 用來獲取這個(gè)錢包可以訪問的規(guī)范路徑。它會(huì)被上層使用用來從所有的后端的錢包來排序。
URL() URL
// 用來返回一個(gè)文本值用來標(biāo)識(shí)當(dāng)前錢包的狀態(tài)。同時(shí)也會(huì)返回一個(gè)error用來標(biāo)識(shí)錢包遇到的任何錯(cuò)誤。
Status() (string, error)
//Open初始化對(duì)錢包實(shí)例的訪問。如果你open了一個(gè)錢包,你必須close它。
Open(passphrase string) error
// Close 釋放由Open方法占用的任何資源。
Close() error
// Accounts用來獲取錢包發(fā)現(xiàn)了賬戶列表。對(duì)于分層次的錢包,這個(gè)列表不會(huì)詳盡的列出所有的賬號(hào),而是只包 //含在帳戶派生期間明確固定的帳戶。
Accounts() []Account
//包含返回帳戶是否屬于此特定錢包的一部分。
Contains(account Account) bool
//Derive嘗試在指定的派生路徑上顯式派生出分層確定性帳戶。如果pin為true,派生帳戶將被添加到錢包的跟蹤 //帳戶列表中。
Derive(path DerivationPath, pin bool) (Account, error)
//SelfDerive設(shè)置一個(gè)基本帳戶導(dǎo)出路徑,從中錢包嘗試發(fā)現(xiàn)非零帳戶,并自動(dòng)將其添加到跟蹤帳戶列表中。
SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
// SignHash 請(qǐng)求錢包來給傳入的hash進(jìn)行簽名。
SignHash(account Account, hash []byte) ([]byte, error)
// SignTx 請(qǐng)求錢包對(duì)指定的交易進(jìn)行簽名。
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
//SignHashWithPassphrase請(qǐng)求錢包使用給定的passphrase來簽名給定的hash
SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
// SignHashWithPassphrase請(qǐng)求錢包使用給定的passphrase來簽名給定的
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
}
后端Backend,Backend是一個(gè)錢包提供器??梢园慌~號(hào)。他們可以根據(jù)請(qǐng)求簽署交易
type Backend interface {
// Wallets獲取當(dāng)前能夠查找到的錢包
Wallets() []Wallet
// 訂閱創(chuàng)建異步訂閱,以便在后端檢測到錢包的到達(dá)或離開時(shí)接收通知。
Subscribe(sink chan<- WalletEvent) event.Subscription
}
manager.go:Manager是一個(gè)包含所有東西的賬戶管理工具。可以和所有的Backends來通信來簽署交易
type Manager struct {
// 當(dāng)前注冊(cè)的后端索引
backends map[reflect.Type][]Backend
// 錢包更新訂閱所有后端
updaters []event.Subscription
// 錢包更新訂閱接收器
updates chan WalletEvent
// 緩存所有錢包從所有注冊(cè)后端
wallets []Wallet
// 通知到達(dá)/離開的錢包事件
feed event.Feed
//退出數(shù)據(jù)管道 錯(cuò)誤信息
quit chan chan error
//讀寫互斥鎖
lock sync.RWMutex
}
newAccount源碼解讀
了解了賬戶的結(jié)構(gòu)以后,我們來看看和賬戶有關(guān)的代碼,因?yàn)橐蕴辉创a的分離性,數(shù)據(jù)結(jié)構(gòu)的定義和工具方法邏輯實(shí)現(xiàn)比較分離,在整個(gè)流程的執(zhí)行中或調(diào)用多層。
首先當(dāng)用戶在console也就是控制臺(tái)輸入personal.newAccount()會(huì)創(chuàng)建一個(gè)新的賬戶這個(gè)命令的執(zhí)行流程如下:
1)執(zhí)行internal/ethapi/api.go文件中的NewAccount方法,返回賬戶地址
func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
acc, err := fetchKeystore(s.am).NewAccount(password)
if err == nil {
return acc.Address, nil
}
return common.Address{}, err
}
internal/ethapi/api.go文件中的NewAccount方法調(diào)用fetchKeystore方法從帳戶管理器檢索加密的密鑰存儲(chǔ)庫獲取keystore。
func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
}
internal/ethapi/api.go文件中的NewAccount方法獲取到keystore后通過keystore調(diào)用accounts/keystore/keystore.go中的NewAccoun方法獲取account,并將這個(gè)賬戶添加到keystore中,返回account
func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) {
_, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
if err != nil {
return accounts.Account{}, err
}
// Add the account to the cache immediately rather
// than waiting for file system notifications to pick it up.
ks.cache.add(account)
ks.refreshWallets()
return account, nil
}
調(diào)用storeNewKey方法創(chuàng)建一個(gè)新的賬戶,生成一對(duì)公私鑰,通過私鑰以及地址構(gòu)建一個(gè)賬戶
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
}
Key的生成函數(shù),通過橢圓曲線加密生成的私鑰,生成Key
func newKey(rand io.Reader) (*Key, error) {
privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
if err != nil {
return nil, err
}
return newKeyFromECDSA(privateKeyECDSA), nil
}
生成公鑰和私鑰對(duì),ecdsa.GenerateKey(crypto.S256(), rand) 以太坊采用了橢圓曲線數(shù)字簽名算法(ECDSA)生成一對(duì)公私鑰,并選擇的是secp256k1曲線
func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
k, err := randFieldElement(c, rand)
if err != nil {
return nil, err
}
priv := new(PrivateKey)
priv.PublicKey.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}
func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) {
params := c.Params()
b := make([]byte, params.BitSize/8+8)
_, err = io.ReadFull(rand, b)
if err != nil {
return
}
k = new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, one)
k.Mod(k, n)
k.Add(k, one)
return
}
以太坊使用私鑰通過 ECDSA算法推導(dǎo)出公鑰,繼而經(jīng)過 Keccak-256 單向散列函數(shù)推導(dǎo)出地址
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
id := uuid.NewRandom()
key := &Key{
Id: id,
Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
PrivateKey: privateKeyECDSA,
}
return key
}
地址代幣以太坊的20位地址hash
// Address represents the 20 byte address of an Ethereum account.
type Address [AddressLength]byte
整個(gè)過程可以總結(jié)為:
從前控制臺(tái)傳入創(chuàng)建賬戶命令
首先創(chuàng)建隨機(jī)私鑰
通過私鑰導(dǎo)出公鑰
通過公私鑰導(dǎo)出地址