GO-HPB源码解读--创建帐号

HPB在创建帐号时使用的命令是 ./ghpb --datadir node/data account init
通过代码看下创建流程

go-hpb源码命令架构使用的是urfave/cli,不知道是不是这样读这个名称:you are 废物,这里有个不错的帖子介绍,可以学习下。主要对命令进行了封装,我们主要关心命令所对应的业务实现逻辑即可,使用起来非常方便。阅读也从这个地方开始。

account命令定义在accountcmd.go文件中,通过源码可以看到account命令有list、new、update、import四个子命令。每一个命令都有flags参数。

github.com\hpb-project\go-hpb\cmd\accountcmd.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
accountCommand = cli.Command{
Name: "account",
Usage: "Manage accounts",
Category: "ACCOUNT COMMANDS",
Description: ``,
Subcommands: []cli.Command{
{
Name: "list",
Usage: "Print summary of existing accounts",
Action: utils.MigrateFlags(accountList),
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
},
Description: `Print a short summary of all accounts`,
},
{
Name: "new",
Usage: "Create a new account",
Action: utils.MigrateFlags(accountCreate),
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.PasswordFileFlag,
utils.LightKDFFlag,
},
Description: ``,
},
{
Name: "update",
Usage: "Update an existing account",
Action: utils.MigrateFlags(accountUpdate),
ArgsUsage: "<address>",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.LightKDFFlag,
},
Description: ``,
},
{
Name: "import",
Usage: "Import a private key into a new account",
Action: utils.MigrateFlags(accountImport),
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.PasswordFileFlag,
utils.LightKDFFlag,
},
ArgsUsage: "<keyFile>",
Description: `,
},
},
}
)

现在主要看一下new这个子命令,Action属性主要用来指定new命令的所需要执行的动作。这里可以看到是utils.MigrateFlags(accountCreate), MigrateFlags方法是把account命令的flag参数设置到全局参数里。比如命令hpb account new –keystore /tmp/mykeystore –lightkdf是等价于hpb –keystore /tmp/mykeystore –lightkdf account new。主要的业务实现需要查看accountCreate

1
2
3
4
5
6
7
8
9
10
11
12
{
Name: "new",
Usage: "Create a new account",
Action: utils.MigrateFlags(accountCreate),
Flags: []cli.Flag{
utils.DataDirFlag,
utils.KeyStoreDirFlag,
utils.PasswordFileFlag,
utils.LightKDFFlag,
},
Description: ``,
},

accountCreate方法首先初始化节点配置对象,并实例化一下节点对象,getPassPhrase接收一个用户输入的口令。接下来通过口令参数生成用户帐户信息.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// accountCreate creates a new account into the keystore defined by the CLI flags.
func accountCreate(ctx *cli.Context) error {
//进行节点参数配置
cfg := MakeConfigNode(ctx)
//创建节点实例
stack, err := createNode(cfg)
if err != nil {
utils.Fatalf("Failed to create node")
return 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))
//创建一个keyStore
ks := stack.AccountManager().KeyStore().(*keystore.KeyStore)
//使用口令对keyStore信息进行加密,并返回一个帐户地址
account, err := ks.NewAccount(password)
if err != nil {
utils.Fatalf("Failed to create account: %v", err)
}
fmt.Printf("Address: {%x}\n", account.Address)
return nil
}

NewAccount方法用来生成一个密钥,并把他保存在文件中,也就是命令执行后生成的文件,比如node/data/keystore/UTC–2018-11-25T14-05-44.446434210Z–a0603b3443c89a6e2eff7614acec5a59f9f70ebb。生成之后返回的account需要加载到当前cache里,并刷新wallet信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// NewAccount generates a new key and stores it into the key directory,
// encrypting it with the passphrase.
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
}

关键方法storeNewKey,第一步newKey会随机生成非对称密钥,使用的是大名顶顶的ECDSA算法。然后创建 一个account对象,并进行保存。保存完成需要把当前内存信息进行清除,以防安全隐患。

1
2
3
4
5
6
7
8
9
10
11
12
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
}

newKey方法生成非对称密钥,并转成一个用户帐户信息,主要是需要通过公钥生成一个用户帐户地址,在newKeyFromECDSA方法里,可以看到的是ECDSA.PublicKey生成Address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
}

StoreKey主要完成非对称密钥通过用户输入口令进行加密保存到文件

1
2
3
4
5
6
7
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
}
return writeKeyFile(filename, keyjson)
}

文件保存后,业务就基本完成,程序退出执行。