我们常见的区块链钱包有非确定性钱包和确定性钱包两种,而我们通常创建的脱离节点的轻钱包都是使用的就是确定性的钱包,它是由种子(随机数生成器生成的随机数)进行单向哈希运算生成的。我们备份了种子就可以保存钱包;但是由于种子是一串随机数,不便于记忆,所以我们用算法将种子转化为一串助记词(Mnemonic),方便保存记录,我们只要保存了助记词,就可以保存自己的资产,非常方便。
如何创建这样的数字货币钱包呢?
Tokenview github 有全面的开源项目供开发者使用:https://github.com/Tokenview/api-demo
该开源项目包含了各种主流币的地址生成规则,以及离线签名的过程,还提供了免费的api调用。下载项目代码后,新建了一个项目,导入下载的module:项目依赖了maven库:
<dependency>
<groupId>cash.bitcoinj</groupId>
<artifactId>bitcoinj-tools</artifactId>
<version>0.14.5.2</version>
</dependency>
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>3.0.1</version>
</dependency>
项目中提供了地址生成、交易发送、查询等一系列例子,首先看看种子的生成过程:
-
long creationTimeSeconds = System.currentTimeMillis() /1000;
-
DeterministicSeed ds =new DeterministicSeed(secureRandom, 128, “”, creationTimeSeconds);
可以选择种子的长度,分别有128,160,192,224,256位的生成方式;种子长度不同对应的助记词的个数也不同;生成的种子可以转换成助记词:
List mnemonicList = ds.getMnemonicCode();
通过HD钱包我们可以根据不同的币种和地址索引派生出相对应的公私钥对:
private static ECKeyPairgetEcKeyPairByDeterministicSeed(String path, DeterministicSeed
ds) {
String [] pathArray = path.split("/");
byte[] seedBytes = ds.getSeedBytes();
if (seedBytes ==null)
return null;
DeterministicKey dkKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);
ChildNumber childNumber;
for (int i =1; i < pathArray.length; i++) {
if (pathArray[i].endsWith("'")) {
int number = Integer.parseInt(pathArray[i].substring(0, pathArray[i].length() -1));
childNumber =new ChildNumber(number, true);
}else {
int number = Integer.parseInt(pathArray[i]);
childNumber =new ChildNumber(number, false);
}
dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
}
return ECKeyPair.create(dkKey.getPrivKeyBytes());
}
最后根据地址生成的规则,得到不同链对应的地址:
public static WalletBeangenerateAddress(String mnemonic, Coin coin)throws UnreadableWalletException {
ECKeyPair keyPair = ECKeyUtil.generateEcKey(mnemonic, coin);
ECKey ecKey = ECKey.fromPrivate(keyPair.getPrivateKey());
WalletBean wallet=new WalletBean();
wallet.setCoin_type(coin.getCoin());
NetworkParameters parameters=null;
switch (coin){
case BTC:
case BCH:
case BSV:
parameters= MainNetParams.get();
wallet.setAddress(ecKey.toAddress(parameters).toBase58());
wallet.setPrivateKey(ecKey.getPrivateKeyAsWiF(parameters));
break;
case LTC:
parameters= LitecoinParams.get();
wallet.setAddress(ecKey.toAddress(parameters).toBase58());
wallet.setPrivateKey(ecKey.getPrivateKeyAsWiF(parameters));
break;
case DOGE:
parameters= DogeParams.get();
wallet.setAddress(ecKey.toAddress(parameters).toBase58());
wallet.setPrivateKey(ecKey.getPrivateKeyAsWiF(parameters));
break;
case DASH:
parameters= DashParams.get();
wallet.setAddress(ecKey.toAddress(parameters).toBase58());
wallet.setPrivateKey(ecKey.getPrivateKeyAsWiF(parameters));
break;
case ETH:
case HT:
case ETC:
case PI:
case WAN:
case EM:
String address = Keys.getAddress(keyPair);
if(coin==Coin.EM){
address="EM"+address;
}else{
address="0x"+address;
}
wallet.setAddress(address);
String privateKey = Numeric.toHexStringNoPrefixZeroPadded(keyPair.getPrivateKey(), Keys.PRIVATE_KEY_LENGTH_IN_HEX);
wallet.setPrivateKey(privateKey);
break;
case NEO:
io.neow3j.crypto.ECKeyPair ecKeyPair= io.neow3j.crypto.ECKeyPair.create(keyPair.getPrivateKey());
wallet.setAddress(ecKeyPair.getAddress());
wallet.setPrivateKey(ecKeyPair.exportAsWIF());
break;
default:
break;
}
wallet.setMnemonic(mnemonic);
return wallet;
}
如何验证地址正确性呢?
这里给大家提供一个验证地址正确性的网址:https://iancoleman.io/bip39/
地址生成后,可以通过私钥进行离线签名并发送交易;例如ETH的交易的离线签名:首先获取的某个ETH地址的交易Nonce和当前的最佳手续费,推荐使用 Tokenview API 服务(https://services.tokenview.com/ )。除了获取当前最佳手续费,还可以查询100多个区块链的链上数据,交易,地址,余额等等。
通过Tokenview API 获取地址的 Nonce
public void getACCOUNTAddressNonce(){
String nonce =baseService.getACCOUNTAddressInfo("eth","0xda9cacf6c13450bea275c33e83503fb705d27bbb")
.getJSONObject("data")
.getString("nonce");
toResultString("GetACCOUNTAddressNonce", nonce);
}
调用SDK提供的签名方法签名:
public void testETHSign() {
String sinature =new ETHSign().signETHTransaction(
"0xb71a7616b42110d8345ddc6826ec42c2f1ce24d5f4d8efeb616168d5c1ef4a1f",
"0x9Ae75431335d2e70f8DB0b35F6C179a43756f78e",
"1",
78500000000L,
21000L,
80000000000L);
log.info(sinature);
}
我们获取到对应的签完名的交易数据,最后调用发送交易的 Tokenview api, 广播交易到链上:
public void testSendETHRawTransaction() {
JSONObject jo =new JSONObject();
jo.put("jsonrpc","2.0");
jo.put("id","viewtoken");
jo.put("method","eth_sendRawTransaction");
jo.put("params", Arrays.asList("0xf86801851246f6f100825208949ae75431335d2e70f8db0b35f6c179a43756f78e8405f5e100801ca058b2130954d3b84918db5c0fc309dcc942b8656d88964a5f981a4a954bbb1221a0788b6b75afaea28d2e9178a6cea3d432983c10db1c778a1dcb6af009f5457701"));
JSONObject onchainwallet =walletservice.sendRawTransaction("eth",jo);
toResultString("SendRawTransaction", onchainwallet);
}
这样我们就完成的整个钱包的开发。Tokenview API 不仅由基础的节点数据查询功能,还提供了各种数据分析和监听地址的功能。