引言

以太坊私有链搭建及基本操作中,使用了下面的命令来新建账号。接下来我就来看下以太坊账号的原理

1
./geth --datadir data account new

另外,我们还关心一个命令 list

1
./geth --datadir data account list
  • 账号文件目录
    • data/keystore
    • 打开其中一个账号文件,可以看到(部分字符被…代替)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "address": "...c2504e15b05572df94f0df385cf3c4b130879",
  "crypto": {
    "cipher": "aes-128-ctr",
    "ciphertext": "d4...37",
    "cipherparams": {
      "iv": "94...a2"
    },
    "kdf": "scrypt",
    "kdfparams": {
      "dklen": 32,
      "n": 262144,
      "p": 1,
      "r": 8,
      "salt": "cf...0f"
    },
    "mac": "26...7c"
  },
  "id": "75...11",
  "version": 3
}
  • 地址就是其中的address字段,所以address是直接存储的
  • 密钥逻辑
    • 关于 非对称加密
    • 几个概念:公钥pubKey,私钥privKey,密码pass,签名sign,任意内容content
    • 每笔交易发起会去校验由私钥+密码是否能够得到账号地址:privKey+pass -> address,由此需要密码和私钥才能发起交易
    • 交易被确认前校验由公钥+内容能否解析出私钥+内容加密得到的签名:pubKey + content -> sign=[privKey+content]
    • 密码+keyStore对象获得私钥:pass+keyObject->privKey,所以务必确保自己保存好keystore和密码,丢失其中一个都不能发起交易。这点和比特币可能不太一样
    • 以太坊使用椭圆曲线算法,具体逻辑暂时不做理解

从main函数开始

当运行如下命令时

1
./geth --datadir data account list

首先会进入cmd/geth/main.go的主函数

1
2
3
4
5
6
func main() {
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

随后,解析参数放入上下文context,如果发现命令,则进入命令。命令行框架由cli提供

1
2
3
4
5
6
7
8
	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}

具体地,account list 命令进入cmd/geth/accountcmd.go的accountList

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func accountList(ctx *cli.Context) error {
	stack, _ := makeConfigNode(ctx)
	var index int
	for _, wallet := range stack.AccountManager().Wallets() {
		for _, account := range wallet.Accounts() {
			fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL)
			index++
		}
	}
	return nil
}

accountList会去keystore目录找出所有账号文件,解析json,拿到所有address字段

 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
//accounts/keystore/account_cache.go

// scanAccounts checks if any changes have occurred on the filesystem, and
// updates the account cache accordingly
func (ac *accountCache) scanAccounts() error {
	// Scan the entire folder metadata for file changes
    ...
	// Create a helper method to scan the contents of the key files
	var (
		buf = new(bufio.Reader)
		key struct {
			Address string `json:"address"`
		}
	)
	readAccount := func(path string) *accounts.Account {
		fd, err := os.Open(path)
		if err != nil {
			log.Trace("Failed to open keystore file", "path", path, "err", err)
			return nil
		}
		defer fd.Close()
		buf.Reset(fd)
		// Parse the address.
		key.Address = ""
		err = json.NewDecoder(buf).Decode(&key)
		addr := common.HexToAddress(key.Address)
        ...

对于account new原理类似,使用加密算法创建账户文件

对于unlock,正如引言所说,验证privKey+pass->address

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// cmd/geth/accountcmd.go

// tries unlocking the specified account a few times.
func unlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) {
	account, err := utils.MakeAddress(ks, address)
	if err != nil {
		utils.Fatalf("Could not list accounts: %v", err)
	}
	for trials := 0; trials < 3; trials++ {
		prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
		password := getPassPhrase(prompt, false, i, passwords)
		err = ks.Unlock(account, password)
        ...

对于交易,需要将pubKey和签名和交易数据放在一起,让其他peer验证,如V R S字段所示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//core/types/transaction.go

type txdata struct {
	AccountNonce uint64          `json:"nonce"    gencodec:"required"`
	Price        *big.Int        `json:"gasPrice" gencodec:"required"`
	GasLimit     uint64          `json:"gas"      gencodec:"required"`
	Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
	Amount       *big.Int        `json:"value"    gencodec:"required"`
	Payload      []byte          `json:"input"    gencodec:"required"`

	// Signature values
	V *big.Int `json:"v" gencodec:"required"`
	R *big.Int `json:"r" gencodec:"required"`
	S *big.Int `json:"s" gencodec:"required"`

	// This is only used when marshaling to JSON.
	Hash *common.Hash `json:"hash" rlp:"-"`
}

account list 命令执行路径

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
graph TD
A(cmd/geth/main.go main) -->B(urfave/cli.v1 Run)
B --> C(cmd/geth/accountcmd.go accountList)
C -->|Node配置初始化| D(cmd/geth/config.go makeConfigNode)
C -->|查询账户|E(accounts/manage.go Wallets)
D --> F(node/node.go New)
F --> G(node/config.go makeAccountManager)
G --> |配置keystore目录|H(accounts/keystore/keystore.go NewKeyStore)
H --> |初始化账户cache|J(accounts/keystore/account_cache.go newAccountCache)
H --> |扫描keystore目录加载账户|K(accounts/keystore/account_cache.go accounts )

代码调用路径


持续学习中,理解可能有误,敬请谅解

版权所有,转载请注明来源