关系图
sequenceDiagram
随机数-->>随机数:生成128~256位随机数
随机数->>助记词: BIP-39规范
助记词->>种子: 助记词+口令(PBKDF2算法)
种子->>私钥: 获得
私钥-->>私钥: 创建公钥
私钥-->>私钥: 创建地址
步骤说明
要创建一个HD钱包(私钥),必须先有一个确定的512bit(64字节)的随机数种子。
1、随机数
- 随机数安全性决定了种子的安全性,所以这里要密码学的安全随机数;
- 随机数的总位数必须是32的倍数,常用128或256位;
- 256位的随机数,又叫256的熵;
2、随机数生成助记词
- BIP-39规范提出了通过助记词生成种子的算法;
- 总共2048个单词,也可以是其他语言,请参考助记词表;
- 根据第1步生成的安全随机数计算种子:
- 用SHA-256计算随机数的校验码,并取若干位追加到随机数末尾,使得随机数+校验码的总位数是11的倍数;
- 按每11位合成一个数字,得到范围是0~2047的24个整数,并作为索引从2048个助记词表里面取得24个助记词;
3、生成种子(Seed)
- 有了助记词之后,使用PBKDF2算法和Hmac-SHA512函数产生种子;
- 下面是一段开源的Dart代码供参考:
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
|
Uint8List mnemonicToSeed(List<String> mnemonic, {String passphrase = ''}) {
return pbkdf2(mnemonic, passphrase: passphrase);
}
const _saltPrefix = 'mnemonic';
/// Password-Based Key Derivation Function 2.
Uint8List pbkdf2(
List<String> mnemonic, {
String passphrase = '',
int blockLength = 128,
int iterationCount = 2048,
int desiredKeyLength = 64,
}) {
final derivator = PBKDF2KeyDerivator(HMac(SHA512Digest(), blockLength));
final salt = Uint8List.fromList(utf8.encode(_saltPrefix + passphrase));
derivator.init(Pbkdf2Parameters(salt, iterationCount, desiredKeyLength));
List<int> codeUnits = [];
for (final word in mnemonic) {
codeUnits.addAll(word.codeUnits);
codeUnits.add(32); //Space.
}
codeUnits.removeLast();
return derivator.process(Uint8List.fromList(codeUnits));
}
|
- 输入助记词列表,转换为UTF-8编码的列表,并设置salt为mnemonic+用户口令,循环2048次,最终生成64字节种子。
- 所以,如果助记词相同并且口令相同,那么就能产生相同的种子。随机数越随机,产生的助记词也就越随机,那么种子也就越随机。
4、公钥私钥
- 种子数据其实就是私钥;
- 用椭圆曲线算法就能产生公钥和地址;
- 下面是Dart的以太坊代码:
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
|
class Ethereum extends Coin {
const Ethereum();
@override
PrivateKey createPrivateKey(Uint8List seed) {
final bn = decodeBigIntWithSign(seed);
return PrivateKey(bn);
}
@override
PublicKey createPublicKey(PrivateKey privateKey) =>
PublicKey(EC.secp256k1.createPublicKey(privateKey.value, true));
@override
String createAddress(PublicKey publicKey) {
var compressed = EC.secp256k1.uncompressPublicKey(publicKey.value);
final input = compressed.sublist(1);
final address = KeccakDigest(256).process(input);
final w = address.skip(address.length - 20).toList();
final h = hex.encode(w);
final f = toChecksumAddress(h);
return '0x$f';
}
@override
Uint8List generateSignature(PrivateKey privateKey, Uint8List message) {
final signature = EC.secp256k1.generateSignature(privateKey.value, message);
final sgn = toDER(signature);
return sgn;
}
@override
bool verifySignature(
PublicKey publicKey,
Uint8List message,
Uint8List signature,
) {
final sgn = fromDER(signature);
final result = EC.secp256k1.verifySignature(publicKey.value, message, sgn);
return result;
}
}
|
引用或参考
廖雪峰-助记词
密钥、地址、钱包
dart_wallet 开源