前段时间做了一下eth钱包开发的事情,现在项目基本结束了。空下来就去梳理一下之前遇到的一些关键问题,以及踩过的一些坑。
下面就和大家分享一下我遇到过的一个问题。
创建钱包
在钱包开发中,创建钱包大致分为以下几个步骤。
1、随机生成一组助记词
2、根据助记词生成种子 seed
3、根据种子seed生成ECKeyPair
4、根据ECKeyPair和密码创建钱包
我发现网上很多博客在生成ECKeyPair的时候,姿势不对。
网上很多教程的生成方式
ECKeyPair ecKeyPair= ECKeyPair.create(sha256(seed));
这种方式是Web3j
提供的API,看上去是很简洁,用起来很方便,但实际上坑很大,根本不能在项目中使用,只能作为Demo。
在创建ETH钱包的过程,需要使用BIP32
,BIP39
,BIP44
,3个协议。它们的作用如下:
BIP32:
定义了目前被广泛使用的 HD Wallet,生成种子seed 和公钥、私钥的。
BIP39:
用于生成助记词
BIP44:
基于 BIP32 的系统,赋予树状结构中的各层特殊的意义。让同一个 seed 可以支援多币种、多帐户等。简单一点说,它就是指定币种规范的一个协议。它的格式如下:
m / purpose' / coin_type' / account' / change / address_index
其中的 purporse'
固定是 44'
,代表使用 BIP44。而coin_type'
用来表示不同币种,例如 Bitcoin 就是 0'
,Ethereum 是 60'
目前市面上的Ethereum 钱包均采用以上 Bitcoin HD Wallet 的架构,用的都是BIP44:
,具体是这样的m/44'/60'/0'/0/0
而Web3j那种创建方式,并没有采用BIP44
,在项目中是不能采用这种方式的,但是依然被很多人采用,并且在网上传播。
下面是我验证这种方式的一个例子。
private void test(String password, String mnemonics) {
String path = mContext.getCacheDir() + "/MyWallet";
//2种方式生成seed是一样的
// byte[] seed = MnemonicUtils.generateSeed(mnemonics, "");
// ECKeyPair ecKeyPair = ECKeyPair.create(sha256(seed));
byte[] seed2 = new SeedCalculator().calculateSeed(mnemonics, "");
ECKeyPair ecKeyPair2 = ECKeyPair.create(sha256(seed2));
Log.i(TAG, "test2 pubk : " + ecKeyPair2.getPublicKey());
Log.i(TAG, "test2 prek : " + ecKeyPair2.getPrivateKey());
Log.i(TAG, "test2 address : " + Keys.getAddress(ecKeyPair2));
}
通过seed获取ECKeyPair,然后通过ECKeyPair获取公钥、私钥、地址。
结果是这样的,可以明显看出是有问题的。
PublicKey:
7244922113023370429973158797216818288186973573283250245018732882291753635193658294263389808990758395488598426777669619093953194711324283066023497720888144
privateKey:
51826596792659989115758568077400059679039347198031504935670220417030436567903
address :
f81f6c5eb74f1b726e4bce7f4528c966e46dc2e3
注意:明文私钥是64位的。
正确的姿势
1、生成 seed
2、生成 master key
3、生成 child key
4、我们取第一组child key即m/44'/60'/0'/0/0 得到私钥,keystore及地址
完整代码如下,这段代码用到了BIP32
,BIP39
,BIP44
/**
* generate key pair to create eth wallet_normal
* 生成KeyPair , 用于创建钱包(助记词生成私钥)
*/
public ECKeyPair generateKeyPair(String mnemonics) {
// 1. we just need eth wallet_normal for now
AddressIndex addressIndex = BIP44
.m()
.purpose44()
.coinType(60)
.account(0)
.external()
.address(0);
// 2. calculate seed from mnemonics , then get master/root key ; Note that the bip39 passphrase we set "" for common
byte[] seed = new SeedCalculator().calculateSeed(mnemonics, "");
ExtendedPrivateKey rootKey = ExtendedPrivateKey.fromSeed(seed, Bitcoin.MAIN_NET);
Log.i(TAG, "mnemonics:" + mnemonics);
String extendedBase58 = rootKey.extendedBase58();
Log.i(TAG, "extendedBase58:" + extendedBase58);
// 3. get child private key deriving from master/root key
ExtendedPrivateKey childPrivateKey = rootKey.derive(addressIndex, AddressIndex.DERIVATION);
String childExtendedBase58 = childPrivateKey.extendedBase58();
Log.i(TAG, "childExtendedBase58:" + childExtendedBase58);
// 4. get key pair
byte[] privateKeyBytes = childPrivateKey.getKey();
ECKeyPair keyPair = ECKeyPair.create(privateKeyBytes);
// we 've gotten what we need
String privateKey = childPrivateKey.getPrivateKey();
String publicKey = childPrivateKey.neuter().getPublicKey();
String address = Keys.getAddress(keyPair);
Log.i(TAG, "privateKey:" + privateKey);
Log.i(TAG, "publicKey:" + publicKey);
Log.i(TAG, "address:" + Constant.PREFIX_16 + address);
return keyPair;
}