本文是bip32、bip39、bip44的源码实现,主要描述了HD钱包的生成过程。
HD钱包生成流程:
下面是创建一个HD钱包的方法.
我们跟下代码来看看生成助记词的具体实现。
NetworkParameters params = TestNet3Params.get();
DeterministicSeed seed = new DeterministicSeed(new SecureRandom(),128,"password",Utils.currentTimeSeconds());
Wallet wallet = Wallet.fromSeed(params,seed);
DeterministicSeed的构造方法:
public DeterministicSeed(SecureRandom random, int bits, String passphrase, long creationTimeSeconds) {
this(getEntropy(random, bits), checkNotNull(passphrase), creationTimeSeconds);
}
先来看看getEntropy函数
private static byte[] getEntropy(SecureRandom random, int bits) {
checkArgument(bits <= MAX_SEED_ENTROPY_BITS, "requested entropy size too large");
byte[] seed = new byte[bits / 8];
random.nextBytes(seed);
return seed;
}
可以看出通过getEntropy函数得到一个byte数组,然后作为参数传给构造方法2
DeterministicSeed的构造方法2:
public DeterministicSeed(byte[] entropy, String passphrase, long creationTimeSeconds) {
//检查参数的正确性
checkArgument(entropy.length % 4 == 0, "entropy size in bits not divisible by 32");
checkArgument(entropy.length * 8 >= DEFAULT_SEED_ENTROPY_BITS, "entropy size too small");
checkNotNull(passphrase);
try {
//生成助记词
this.mnemonicCode = MnemonicCode.INSTANCE.toMnemonic(entropy);
} catch (MnemonicException.MnemonicLengthException e) {
// cannot happen
throw new RuntimeException(e);
}
//通过助记词生成种子,详情看“通过助记词生成种子”
this.seed = MnemonicCode.toSeed(mnemonicCode, passphrase);
this.encryptedMnemonicCode = null;
this.creationTimeSeconds = creationTimeSeconds;
}
我们来看看MnemonicCode.INSTANCE.toMnemonic方法是如何从随机生成的byte数组得出助记词的
代码位置MnemonicCode.java
/**
* entropy为上面通过SecureRandom生成的随机数组
**/
public List toMnemonic(byte[] entropy) throws MnemonicException.MnemonicLengthException {
//为了减少字数删来检查参数的代码
//计算entropyhash作为后面的checksum
byte[] hash = Sha256Hash.hash(entropy);
//将hash转换成二进制,true为1,false为0。详情请看bytesToBits函数的解析
boolean[] hashBits = bytesToBits(hash);
//将随机数组转换成二进制
boolean[] entropyBits = bytesToBits(entropy);
//checksum长度
int checksumLengthBits = entropyBits.length / 32;
// 将entropyBits和checksum加起来,相当于BIP39中的ENT+CS
boolean[] concatBits = new boolean[entropyBits.length + checksumLengthBits];
System.arraycopy(entropyBits, 0, concatBits, 0, entropyBits.length);
System.arraycopy(hashBits, 0, concatBits, entropyBits.length, checksumLengthBits);
/**
*this.wordList是助记词列表。
*
**/
ArrayList words = new ArrayList<>();
//助记词个数
int nwords = concatBits.length / 11;
for (int i = 0; i < nwords; ++i) {
int index = 0;
for (int j = 0; j < 11; ++j) {
//java中int是由32位二进制组成,index左移1位,如果concatBits对应的位为true则将index对应的位设置位1
index <<= 1;
if (concatBits[(i * 11) + j])
index |= 0x1;
}
//根据索引从助记词列表中获取单词并添加到words
words.add(this.wordList.get(index));
}
//得到的助记词
return words;
}
bytesToBits函数,代码位置MnemonicCode.java
private static boolean[] bytesToBits(byte[] data) {
//java中byte使用8位二进制表示
boolean[] bits = new boolean[data.length * 8];
for (int i = 0; i < data.length; ++i)
//循环data[i]中的没一位,如果data[i]的j位为1则bits[(i * 8) + j]=true否则为false
for (int j = 0; j < 8; ++j)
bits[(i * 8) + j] = (data[i] & (1 << (7 - j))) != 0;
return bits;
}
上一节通过随机数获得助记词后,使用MnemonicCode.toSeed可以推导出种子。
我们来看看这个方法的具体实现
public DeterministicSeed(byte[] entropy, String passphrase, long creationTimeSeconds) {
...
this.seed = MnemonicCode.toSeed(mnemonicCode, passphrase);
...
}
MnemonicCode.toSeed函数,位置MnemonicCode.java
public static byte[] toSeed(List words, String passphrase) {
checkNotNull(passphrase, "A null passphrase is not allowed.");
// To create binary seed from mnemonic, we use PBKDF2 function
// with mnemonic sentence (in UTF-8) used as a password and
// string "mnemonic" + passphrase (again in UTF-8) used as a
// salt. Iteration count is set to 4096 and HMAC-SHA512 is
// used as a pseudo-random function. Desired length of the
// derived key is 512 bits (= 64 bytes).
//将助记词连接起来,以空格作为分隔符。pass格式:"aa bb cc dd ..."
String pass = Utils.SPACE_JOINER.join(words);
String salt = "mnemonic" + passphrase;
final Stopwatch watch = Stopwatch.createStarted();
//使用PBKDF2SHA512生成64位的种子
byte[] seed = PBKDF2SHA512.derive(pass, salt, PBKDF2_ROUNDS, 64);
watch.stop();
log.info("PBKDF2 took {}", watch);
return seed;
}
通过上面两个步骤我们得到了种子seed,现在通过这个seed生成钱包。我们来看看Wallet.fromSeed这个函数到底做了那些工作。
NetworkParameters params = TestNet3Params.get();
DeterministicSeed seed = new DeterministicSeed(new SecureRandom(),128,"password",Utils.currentTimeSeconds());
Wallet wallet = Wallet.fromSeed(params,seed);
public static Wallet fromSeed(NetworkParameters params, DeterministicSeed seed) {
//创建一个KeyChainGroup实例
return new Wallet(params, new KeyChainGroup(params, seed));
}
public Wallet(NetworkParameters params, KeyChainGroup keyChainGroup) {
this(Context.getOrCreate(params), keyChainGroup);
}
private Wallet(Context context, KeyChainGroup keyChainGroup) {
创建wallet的具体方法,稍后分析
}
从上面代码我们知道。程序先创建了一个KeyChainGroup对象然后再创建wallet对象。我们先看看KeyChainGroup对象是怎么创建的,然后再分析wallet对象的创建过程。
KeyChainGroup创建代码,位置KeyChainGroup.java
public KeyChainGroup(NetworkParameters params, DeterministicSeed seed) {
this(params, null, ImmutableList.of(new DeterministicKeyChain(seed)), null, null);
}
从上面的关键类介绍中我们知道KeyChainGroup只是包装了DeterministicKeyChain和BasicKeyChain。实际干活的还是它们两个。
我们来看下DeterministicKeyChain的创建代码,
private DeterministicHierarchy hierarchy;
@Nullable private DeterministicKey rootKey;
@Nullable private DeterministicSeed seed;
private final BasicKeyChain basicKeyChain;
protected DeterministicKeyChain(DeterministicSeed seed) {
this(seed, null);
}
protected DeterministicKeyChain(DeterministicSeed seed, @Nullable KeyCrypter crypter) {
this.seed = seed;
//这里使用BasicKeyChain是为了方便按照hash查找DeterministicKey
//所有产生的DeterministicKey都会添加到这个basicKeyChain中
basicKeyChain = new BasicKeyChain(crypter);
//因为没有对seed加密,所以会进入这段代码
if (!seed.isEncrypted()) {
//根据bip32的公式生成
rootKey = HDKeyDerivation.createMasterPrivateKey(checkNotNull(seed.getSeedBytes()));
//设置创建实际
rootKey.setCreationTimeSeconds(seed.getCreationTimeSeconds());
//将rootKey添加到basicKeyChain
addToBasicChain(rootKey);
//创建树结构
hierarchy = new DeterministicHierarchy(rootKey);
//将hierarchy中第一层子节点都添加到basicKeyChain
for (int i = 1; i <= getAccountPath().size(); i++) {
addToBasicChain(hierarchy.get(getAccountPath().subList(0, i), false, true));
}
//初始化这个树结构
initializeHierarchyUnencrypted(rootKey);
}
}
//初始化这个树结构
private void initializeHierarchyUnencrypted(DeterministicKey baseKey) {
//创建外部链
externalParentKey = hierarchy.deriveChild(getAccountPath(), false, false, ChildNumber.ZERO);
//创建内部链
internalParentKey = hierarchy.deriveChild(getAccountPath(), false, false, ChildNumber.ONE);
//将内部链和外部链添加到basicKeyChain
addToBasicChain(externalParentKey);
addToBasicChain(internalParentKey);
}
总结一下创建DeterministicKeyChain做的工作:
通过创建KeyChainGroup1代码我们知道。
创建DeterministicKeyChain成功之后,会把这个DeterministicKeyChain作为参数继续传教KeyChainGroup
下面是KeyChainGroup的另外一个构造函数。
//创建KeyChainGroup1代码
public KeyChainGroup(NetworkParameters params, DeterministicSeed seed) {
this(params, null, ImmutableList.of(new DeterministicKeyChain(seed)), null, null);
}
private KeyChainGroup(NetworkParameters params, @Nullable BasicKeyChain basicKeyChain, List chains,
@Nullable EnumMap currentKeys, @Nullable KeyCrypter crypter) {
this.params = params;
//新建一个BasicKeyChain
this.basic = basicKeyChain == null ? new BasicKeyChain() : basicKeyChain;
//新建一个list并把上一步生成的DeterministicKeyChain添加进去
this.chains = new LinkedList(checkNotNull(chains));
//this.keyCrypter = null;
this.keyCrypter = crypter;
//新建当前节点
this.currentKeys = currentKeys == null
? new EnumMap(KeyChain.KeyPurpose.class)
: currentKeys;
//创建当前地址
this.currentAddresses = new EnumMap(KeyChain.KeyPurpose.class);
//循环执行chains里面元素的maybeLookAheadScripts方法,目前看DeterministicKeyChain的maybeLookAheadScripts方法是空的
//也就是说这里什么都没做
maybeLookaheadScripts();
//如果是多重签名,按照多重签名的方式生成地址
if (isMarried()) {
for (Map.Entry entry : this.currentKeys.entrySet()) {
Address address = makeP2SHOutputScript(entry.getValue(), getActiveKeyChain()).getToAddress(params);
currentAddresses.put(entry.getKey(), address);
}
}
}
总结一下创建KeyChainGroup做的工作:
KeyChainGroup创建好后继续创建wallet
private Wallet(Context context, KeyChainGroup keyChainGroup) {
this.context = context;
this.params = context.getParams();
this.keyChainGroup = checkNotNull(keyChainGroup);
//单元测试,可以忽略
if (params.getId().equals(NetworkParameters.ID_UNITTESTNET))
this.keyChainGroup.setLookaheadSize(5); // Cut down excess computation for unit tests.
//查看keyChainGroup是否有ECKey,如果没有创建一个。按照我们的流程,是不会进入if里面的代码段
if (this.keyChainGroup.numKeys() == 0)
this.keyChainGroup.createAndActivateNewHDChain();
//观察列表,查看交易、余额等
watchedScripts = Sets.newHashSet();
//未花费交易
unspent = new HashMap();
//已花费交易
spent = new HashMap();
//进行中的交易
pending = new HashMap();
//失败的交易
dead = new HashMap();
//交易列表
transactions = new HashMap();
//钱包扩展
extensions = new HashMap();
// Use a linked hash map to ensure ordering of event listeners is correct.
confidenceChanged = new LinkedHashMap();
//签名列表,发送交易时用到
signers = new ArrayList();
//添加一个本地签名
addTransactionSigner(new LocalTransactionSigner());
//创建块确认监听
createTransientState();
}
//创建块确认监听,收到交易或发送交易后用到
private void createTransientState() {
ignoreNextNewBlock = new HashSet();
txConfidenceListener = new TransactionConfidence.Listener() {
@Override
public void onConfidenceChanged(TransactionConfidence confidence, TransactionConfidence.Listener.ChangeReason reason) {
略
}
}
};
acceptRiskyTransactions = false;
}
总结一下创建Wallet做的工作。