前言

上一章我们介绍了 ethereum运行开启console 的过程,那么在这一章我们将会介绍如何在以太坊中创建一个新的账号。以下的理解可能存在错误,如果各位大虾发现错误,还望指正。

指令分析

指令: personal.newAccount(password)

介绍:上面的指令的主要的作用是生成一个account

分析: password 是一个字符串,即你为账户设置的密码(ps:这个密码跟身份验证有关)

例子:

  • 首先我们先需要开启console,开启之后会在控制台出现下图的信息:

  • 然后我们输入 personal.newAccount("123") 指令,之后我们就可以看到下面信息:

"0x58f722f1aebff6ad0334d49786bfe448bfafef33" 就是我们的钱包地址,那么下面就让我们来看看账号的生成过程吧。

相关知识

从文献中我们可以知道,secp256k1这条曲线有几个参数 T =(p,a,b,G,n,h) ,这几个参数在eth的源代码中已经指定。它们分别是:

func init() {
// See SEC 2 section 2.7.1
// curve parameters taken from:
// http://www.secg.org/collateral/sec2_final.pdf
theCurve.P = math.MustParseBig256("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F")
theCurve.N = math.MustParseBig256("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141")
theCurve.B = math.MustParseBig256("0x0000000000000000000000000000000000000000000000000000000000000007")
theCurve.Gx = math.MustParseBig256("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")
theCurve.Gy = math.MustParseBig256("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8")
theCurve.BitSize = 256
}

在生成账号时我们需要用到上面的几个参数。

再eth中 scrypt 的初始化参数为:

const (
keyHeaderKDF = "scrypt" // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB
// memory and taking approximately 1s CPU time on a modern processor.
StandardScryptN = 1 << 18 // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB
// memory and taking approximately 1s CPU time on a modern processor.
StandardScryptP = 1 // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB
// memory and taking approximately 100ms CPU time on a modern processor.
LightScryptN = 1 << 12 // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB
// memory and taking approximately 100ms CPU time on a modern processor.
LightScryptP = 6 scryptR = 8
scryptDKLen = 32
)

账号生成过程

账号的生成主要分为以下几个过程:

  • 1. 生成 privateKey
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)//生成一个40byte的随机值
if err != nil {
return
} k = new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, one)//将椭圆曲线中的参数N减1,为了防止秘钥不等于0且秘钥不能超过参数N
k.Mod(k, n)//用生成的40byte的随机值去mod N得到一个32byte的值
k.Add(k, one)//再对得到了32位的值进行加1处理,这里我认为是为了防止秘钥等于0
return
}
  • 2.通过秘钥生成 publicKey
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
}

对于公钥的生成你只需要知道它是通过椭圆曲线生成出来的就可以了。

  • 3.通过 publicKey 生成 address

首先我们需要拼接公钥对,生成一个未压缩的公钥:

// Marshal converts a point into the uncompressed form specified in section 4.3.6 of ANSI X9.62.
func Marshal(curve Curve, x, y *big.Int) []byte {
byteLen := (curve.Params().BitSize + 7) >> 3 ret := make([]byte, 1+2*byteLen)//创建一个65size的byte数组
ret[0] = 4 // uncompressed point,将ret[0]赋值4,表明它是未压缩的公钥 //下面的代码是为了将公钥对拼接到一起即x+""+y
xBytes := x.Bytes()
copy(ret[1+byteLen-len(xBytes):], xBytes)
yBytes := y.Bytes()
copy(ret[1+2*byteLen-len(yBytes):], yBytes)
return ret
}

然后我们使用刚刚生成的未压缩的公钥生成地址:

func PubkeyToAddress(p ecdsa.PublicKey) common.Address {
pubBytes := FromECDSAPub(&p)
return common.BytesToAddress(Keccak256(pubBytes[1:])[12:])//使用压缩的公钥生成地址,首先公钥进行hash,生成一个32byte的值,在这里我们使用的hash算法是Keccak256,感兴趣的同学请自行百度。然后我们取后20位做我们的地址
}
  • 4.通过输入的 password 生成 mac ,为了做身份验证
// EncryptKey encrypts a key using the specified scrypt parameters into a json
// blob that can be decrypted later on.
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
authArray := []byte(auth)
salt := randentropy.GetEntropyCSPRNG(32)//生成一个随机的32byte的salt
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)//使用scrypt算法将输入的password加密,生成一个32位的derivedKey
if err != nil {
return nil, err
}
encryptKey := derivedKey[:16]//取derivedKey的前16byte
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 生成一个随机的16byte的iv
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)//对进行provateKey进行aes加密,生成一个32byte的cipherText
//fmt.Printf("cipherText:%s\n", hex.EncodeToString(cipherText))
if err != nil {
return nil, err
}
mac := crypto.Keccak256(derivedKey[16:32], cipherText)//将derivedKey的后16byte与cipherText进行Keccak256,生成32byte的mac,我猜这里是对这两个byte进行了拼接处理 //后面的代码是构造要写入硬盘的json
scryptParamsJSON := make(map[string]interface{}, 5)
scryptParamsJSON["n"] = scryptN
scryptParamsJSON["r"] = scryptR
scryptParamsJSON["p"] = scryptP
scryptParamsJSON["dklen"] = scryptDKLen
scryptParamsJSON["salt"] = hex.EncodeToString(salt) cipherParamsJSON := cipherparamsJSON{
IV: hex.EncodeToString(iv),
} cryptoStruct := cryptoJSON{
Cipher: "aes-128-ctr",
CipherText: hex.EncodeToString(cipherText),
CipherParams: cipherParamsJSON,
KDF: keyHeaderKDF,
KDFParams: scryptParamsJSON,
MAC: hex.EncodeToString(mac),
}
encryptedKeyJSONV3 := encryptedKeyJSONV3{
hex.EncodeToString(key.Address[:]),
cryptoStruct,
key.Id.String(),
version,
}
return json.Marshal(encryptedKeyJSONV3)
}
  • 5.存储生成的信息
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)//写入文件,文件的名包含了address,并且存储位置是我们启动eth时,--datadir所指定的位置,--datadir下面有个文件夹keystore,里面存储了你的账号信息
}

那么让我们来看一看我们生成的文件:

{
"address": "30384e0690af2e71710cc0cce26227a4d9418177",
"crypto": {
"cipher": "aes-128-ctr",//使用的加密算法
"ciphertext": "77f85a3664573f1bb719acda4990af9814ddc3e2e6acaa2a9b88daa8b8979028",//加密后的私钥
"cipherparams": {
"iv": "1c72678046f622b9bf63cfa4a6615304"//之前代码中生成的iv,用于加密私钥
},
"kdf": "scrypt",//使用的hash算法
"kdfparams": {//hash算法的参数信息
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "749a66fea9065b07c71519e60da65cad58a1a0b9c38f7fbcb36d5fe6f32ee493"//之前代码中生成的salt,用于生成dk
},
"mac": "53fc76f41231ed418a91ddf3758f2ee78b1261f08896aa18a1a0cef6bfbccfd6"//之前代码中生成的mac,用于身份验证
},
"id": "9c760694-7026-4d2e-9172-56a44e35d10e",//随机生成的uuid
"version": 3
}

至此我们的账号就生成完成了,生成账号的入口函数在 go-ethereum/accounts/keystore/keystore.go ,大家可以运行 go-ethereum/accounts/keystore/keystore_test.go 中的代码来debug。

小结

总结来说eth生成账号的过程为:

  1. 生成一个随机的32 byte的 privateKey
  2. 通过 privateKey 生成一个公钥对,将公钥对进行拼接就得到了一个64 byte未压缩形式的 publicKey
  3. 对这个 publicKey 进行 Keccak256 ,然后取后20 byte,就生成了我们的20 byte的 address
  4. 将我们输入的 password 和随机生成的32 byte的 salt 进行 scrypt 加密,会得到一个32 byte的 derivedKey
  5. 用 derivedKey 的前16 byte与随机生成的16 byte的iv对 privateKey 进行aes加密,得到32 byte的 cipherText
  6. 将 derivedKey 的后16 byte与 cipherText 进行 Keccak256 ,得到一个32 byte的mac
  7. 将数据写入文件

[ethereum源码分析](5) 创建新账号的更多相关文章

  1. 干货分享之spring框架源码分析02-(对象创建or生命周期)

    记录并分享一下本人学习spring源码的过程,有什么问题或者补充会持续更新.欢迎大家指正! 环境: spring5.X + idea 之前分析了Spring读取xml文件的所有信息封装成beanDef ...

  2. [ethereum源码分析](3) ethereum初始化指令

    前言 在上一章介绍了关于区块链的一些基础知识,这一章会分析指令 geth --datadir dev/data/02 init private-geth/genesis.json 的源码,若你的eth ...

  3. live555源码分析----RSTPServer创建过程分析

    最近五一回家,终于有机会能安静的看一下流媒体这方面相关的知识,准备分析live555的源码,接下来会把我读源码的过程记录成博客,以供其他的同路人参考. 因为再读源码的过程中,并不是一路顺着读下来,往往 ...

  4. Akka源码分析-Actor创建(续)

    在上一遍博客中,我们已经分析了actor创建的大致过程,但只是涉及到了Dipatcher/Mailbox/ActorCell/InternalActorRef等对象的创建,并没有介绍我们自定义的继承A ...

  5. 1.live555源码分析----RSTPServer创建过程分析

    最近五一回家,终于有机会能安静的看一下流媒体这方面相关的知识,准备分析live555的源码,接下来会把我读源码的过程记录成博客,以供其他的同路人参考. 因为再读源码的过程中,并不是一路顺着读下来,往往 ...

  6. Akka源码分析-Actor创建

    上一篇博客我们介绍了ActorSystem的创建过程,下面我们就研究一下actor的创建过程. val system = ActorSystem("firstActorSystem" ...

  7. 从源码分析 MGR 的新主选举算法

    MGR 的新主选举算法,在节点版本一致的情况下,其实也挺简单的. 首先比较权重,权重越高,选为新主的优先级越高. 如果权重一致,则会进一步比较节点的 server_uuid.server_uuid 越 ...

  8. Ethereum 源码分析之 accounts

    一.Account // Account represents an Ethereum account located at a specific location defined // by the ...

  9. [ethereum源码分析](1) dubug环境搭建

    前言 因为最近云小哥哥换了一份工作,新公司比较忙,所以一直没有更新新的博客.云小哥哥新的公司是做区块链的,最近在学习区块链相关的东西(也算是乘坐上了区块链这艘大船).本博客是记录我搭建ethereum ...

随机推荐

  1. 2019JAVA第一次編程总结

    2019第二周实验报告 Java实验报告 班级 计算机科学与技术二班 学号 20188442 姓名 吴怡君 完成时间 2019/9/7 评分等级 实验一 Java开发环境与简单Java程序 一. 实验 ...

  2. [转帖]是时候深入了解Linux的系统结构

    是时候深入了解Linux的系统结构   http://os.51cto.com/art/201905/596011.htm linux的体系结果 其实自己也知道 linus 做了一个 kernel 大 ...

  3. Java内存结构详解

    Java内存结构详解 Java把内存分成:栈内存,堆内存,方法区,本地方法区和寄存器等. 下面分别介绍栈内存,堆内存,方法区各自一些特性: 1.栈内存 (1)一些基本类型的变量和对象的引用变量都是在函 ...

  4. Luogu P5444 [APIO2019]奇怪装置

    题目 这种题目看上去就是有循环节的对吧. 在考场上,一个可行的方式是打表. 现在我们手推一下这个循环节. 记函数\(f(t)=(((t+\lfloor\frac tB\rfloor)\%A),(t\% ...

  5. some problem

    CF1257F Make Them Similar $solution:$ 折半搜索后考虑如何维护两个数组的和,可以将 $A$ 中每个数减 $A_1$ ,$B$ 中每个数被减 $B_1$ ,$map$ ...

  6. liunx驱动----信号量的实现

    使用信号量必须包含  <asm/semaphore.h>  头文件.其中相关结构体  struct semaphore 可以通过以下几种方式来声明或者初始化. 1.void sema_in ...

  7. SQL Server to MySQL

    使用 Navicat 导入向导迁移 会遇到以下问题 SQL Server 中的 GUID 类型字段会变成 {guid} 多个外层花括号, 导致程序问题. 部分字段类型长度不大一致, 需要手工调整. . ...

  8. k3 cloud的单据存储在业务对象表中

    k3 cloud的单据存储在业务对象表中,表名为T_META_OBJECTTYPE,查询表名和对应的表: select FNAME,FBASEOBJECTID from T_META_OBJECTTY ...

  9. OC(构造函数,分类等知识总结)

    文章来源:http://my.oschina.net/luoguankun/blog/219532 一.成员变量的作用域 ·        @public ·        在任何地方都能直接访问对象 ...

  10. ES6中class的实现原理

    一.在ES6以前实现类和继承 实现类的代码如下: function Person(name, age) { this.name = name; this.age = age; } Person.pro ...