[ethereum源码分析](5) 创建新账号
前言
在上一章我们介绍了 ethereum运行开启console 的过程,那么在这一章我们将会介绍如何在以太坊中创建一个新的账号。以下的理解可能存在错误,如果各位大虾发现错误,还望指正。
指令分析
指令: personal.newAccount(password)
介绍:上面的指令的主要的作用是生成一个account
分析: password 是一个字符串,即你为账户设置的密码(ps:这个密码跟身份验证有关)
例子:
- 首先我们先需要开启console,开启之后会在控制台出现下图的信息:
- 然后我们输入 personal.newAccount("123") 指令,之后我们就可以看到下面信息:
"0x58f722f1aebff6ad0334d49786bfe448bfafef33" 就是我们的钱包地址,那么下面就让我们来看看账号的生成过程吧。
相关知识
- 椭圆曲线secp256k1
从文献中我们可以知道,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生成账号的过程为:
- 生成一个随机的32 byte的 privateKey
- 通过 privateKey 生成一个公钥对,将公钥对进行拼接就得到了一个64 byte未压缩形式的 publicKey
- 对这个 publicKey 进行 Keccak256 ,然后取后20 byte,就生成了我们的20 byte的 address
- 将我们输入的 password 和随机生成的32 byte的 salt 进行 scrypt 加密,会得到一个32 byte的 derivedKey
- 用 derivedKey 的前16 byte与随机生成的16 byte的iv对 privateKey 进行aes加密,得到32 byte的 cipherText
- 将 derivedKey 的后16 byte与 cipherText 进行 Keccak256 ,得到一个32 byte的mac
- 将数据写入文件