创建数字钱包(一)账号生成
椭圆曲线数字签名算法生成私钥
Secp256k1
通过椭圆曲线数字签名算法生成私钥和公钥,其中SEC(Standards for Efficient Cryptography)是专门利用ECDSA或者其可选项Schnorr算法来产生高效的加密方法。
特点是生成密钥很快。
Scep256k1 基本特性
- secp256k1 ECDSA signing/verification and key generation.
- Adding/multiplying private/public keys.
- Serialization/parsing of private keys, public keys, signatures.
- Constant time, constant memory access signing and pubkey generation.
- Derandomized DSA (via RFC6979 or with a caller provided function.)
- Very efficient implementation.
讲解代码
步骤
- 生成私钥
- 加密私钥
- 生成 keyObject 对象
- 从keyObject对象中恢复私钥
生成私钥
下面利用 keythereum[^1] 产生符合以太坊的密钥,并产生keyObject文件
1 | const params = { keyBytes: 32, ivBytes: 16 }; |
keythereum可以产生私钥,以及后面加密私钥所用的PBKDF2算法需要的salt,和加密aes-128-ctr私钥的iv值。
得到私钥之后,我们可以通过私钥生成公钥。
1 | let privateKeyBuffer = Buffer.from(privateKey, "hex") // or "base64" |
加密私钥
利用KDF算法基于password派生出密钥,然后利用这个密钥加密我们的私钥。
1 | const password = "Hello,Ethereum" |
这就是产生keyObject基本思路。我们在看看dump函数到底做了什么
1 | this.marshal(this.deriveKey(password, salt, options), privateKey, salt, iv, options); |
deriveKey(…) 的源码如下:
1 | this.crypto.pbkdf2Sync( |
这里基于password生成的derivedKey,这个密钥并不是我们要用的私钥,而是用来加密先前生成的privateKey的,加密的过程在marshal函数中调用的encrypt函数里。
1 | let ciphertext = this.encrypt(privateKey, derivedKey.slice(0, 16), iv, algo).toString("hex"); |
encrypt函数,如下:
1 | var cipher, ciphertext; |
此处的ciphertext代表的是privateKey,而key则是derivedKey
生成 keyObject 对象
得到了加密后的ciphertext之后,开始组装keyObject对象并返回。
1 | keyObject = { |
privateKeyToAddress(…)方法里首先通过privateKey产生publicKey,然后使用keccak256哈希publicKey得到地址。
具体实现如下:
1 | let privateKeyBuffer = Buffer.from(privateKey); |
keccak256(publicKey) 产生了32bytes,截取尾部20bytes转换成十六进制之后就是40字符,加上前导0x之后,就是42个字符的以太坊地址,比如:0x0f645438395206b408e52be4fcf4bc21c330bfa2
从keyObject对象中恢复私钥
有了keyObject和密码就可以恢复原来的私钥
1 | let privateKey = keythereum.recover(password, keyObject) |
可以想到,recover方法中,首先会利用password和keyObject中的salt派生出当初的密钥derivedKey,然后把加密过的私钥ciphertext和derivedKey, iv作为原来加密算法aes-128-ctr的输入参数,成功解密后返回明文的私钥。
具体代码如下:
1 | verifyAndDecrypt(this.deriveKey(password, salt, keyObjectCrypto), salt, iv, ciphertext, algo) |
这里首先得到了derivedKey,然后验证并解密kyeObject中的ciphertext,如下:
1 | function verifyAndDecrypt(derivedKey, salt, iv, ciphertext, algo) { |
注意这里的mac值比较,确保了ciphertext没有被人篡改才有解密的必要。
参考实现
- NodeJS
- Bitcoin-core
[^1]: Keythereum