本篇主要說(shuō)明如何離線生成ETH地址和進(jìn)行離線交易
通過(guò)助記詞離線生成錢(qián)包地址
關(guān)于助記詞和HD錢(qián)包原理的原理可以參考以下鏈接的內(nèi)容
http://www.itdecent.cn/p/e6a4150eb729
大致過(guò)程如下圖:

derivation.png
由于在web3j庫(kù)中沒(méi)有加入助記詞派生地址的解決方法,所以我們離線生成地址時(shí)需要引入bitcoinj庫(kù)提供方法。
以下為需要的錢(qián)包相關(guān)依賴(lài):
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.14.7</version>
</dependency>
進(jìn)行離線交易
原理:將交易的原始信息包括nonce(交易次數(shù))、gasPrice、gasLimit、from、to、amount等構(gòu)造完成后進(jìn)行編碼,簽名,最后廣播至區(qū)塊鏈上。
以下是eth方法工具類(lèi):
import com.google.common.collect.ImmutableList;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicHierarchy;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.*;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
public class EthereumManager {
private final static ImmutableList<ChildNumber> BIP44_ETH_ACCOUNT_ZERO_PATH =
ImmutableList.of(new ChildNumber(44, true), new ChildNumber(60, true),
ChildNumber.ZERO_HARDENED, ChildNumber.ZERO);
private final static Web3j web3j = Web3j.build(new HttpService("localhost:8545"));
/**
* 通過(guò)助記詞和id生成對(duì)應(yīng)的子賬戶
* @param mnemonic 助記詞
* @param id 派生子id
* @return 子賬戶key
*/
private static DeterministicKey generateKeyFromMnemonicAndUid(String mnemonic, int id) {
byte[] seed = MnemonicUtils.generateSeed(mnemonic, "");
DeterministicKey rootKey = HDKeyDerivation.createMasterPrivateKey(seed);
DeterministicHierarchy hierarchy = new DeterministicHierarchy(rootKey);
return hierarchy.deriveChild(BIP44_ETH_ACCOUNT_ZERO_PATH, false, true, new ChildNumber(id, false));
}
/**
* 生成地址
* @param id 用戶id
* @return 地址
* @throws CipherException
*/
public static String getEthAddress(String mnemonic, int id) {
DeterministicKey deterministicKey = generateKeyFromMnemonicAndUid(mnemonic, id);
ECKeyPair ecKeyPair = ECKeyPair.create(deterministicKey.getPrivKey());
return Keys.getAddress(ecKeyPair);
}
/**
* 生成私鑰
* @param id 用戶id
* @return 私鑰
*/
public static BigInteger getPrivateKey(String mnemonic, int id) {
return generateKeyFromMnemonicAndUid(mnemonic, id).getPrivKey();
}
/**
* 通過(guò)private key生成credentials
*/
public static Credentials generateCredentials(String privateKey) {
return Credentials.create(privateKey);
}
/**
* 發(fā)送eth離線交易
* @param from eth持有地址
* @param to 發(fā)送目標(biāo)地址
* @param amount 金額(單位:eth)
* @param credentials 秘鑰對(duì)象
* @return 交易hash
* @throws IOException
* @throws ExecutionException
* @throws InterruptedException
*/
public static String sendEthRawTransaction(String from, String to, BigDecimal amount, Credentials credentials) throws IOException, ExecutionException, InterruptedException {
BigInteger nonce = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.PENDING).send().getTransactionCount();
BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
BigInteger gasLimit = BigInteger.valueOf(21000L);
BigInteger amountWei = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, amountWei, "");
byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
return web3j.ethSendRawTransaction(Numeric.toHexString(signMessage)).sendAsync().get().getTransactionHash();
}
/**
* 發(fā)送代幣離線交易
* @param from 代幣持有地址
* @param to 代幣目標(biāo)地址
* @param amount 金額(單位:代幣最小單位)
* @param coinAddress 代幣合約地址
* @param credentials 秘鑰對(duì)象
* @return 交易hash
* @throws IOException
* @throws ExecutionException
* @throws InterruptedException
*/
public static String sendContractTransaction(String from, String to, BigInteger gasLimit, BigInteger amount, String coinAddress, Credentials credentials) throws IOException, ExecutionException, InterruptedException {
BigInteger nonce = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.PENDING).send().getTransactionCount();
BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
Function function = new Function(
"transfer",
Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(to),
new org.web3j.abi.datatypes.generated.Uint256(amount)),
Collections.<TypeReference<?>>emptyList());
String data = FunctionEncoder.encode(function);
RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, coinAddress, data);
byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
return web3j.ethSendRawTransaction(Numeric.toHexString(signMessage)).sendAsync().get().getTransactionHash();
}
/**
* 發(fā)送賬戶內(nèi)所有eth
* @param from 持有地址
* @param to 目標(biāo)地址
* @param credentials 秘鑰對(duì)象
* @return 交易hash
* @throws IOException
*/
public static String sendAllEth(String from, String to, Credentials credentials) throws IOException {
BigInteger nonce = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.PENDING).send().getTransactionCount();
BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
BigInteger gasLimit = BigInteger.valueOf(21000L);
BigInteger balance = web3j.ethGetBalance(from, DefaultBlockParameterName.PENDING).send().getBalance();
if (balance.compareTo(gasPrice.multiply(gasLimit)) <= 0) {
return null;
}
BigInteger amount = balance.subtract(gasPrice.multiply(gasLimit));
RawTransaction transaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, amount, "");
byte[] signMessage = TransactionEncoder.signMessage(transaction, credentials);
return web3j.ethSendRawTransaction(Numeric.toHexString(signMessage)).send().getTransactionHash();
}
/**
* 發(fā)送賬戶內(nèi)所有某代幣
* @param from 代幣擁有地址
* @param to 代幣目標(biāo)地址
* @param coinAddress 代幣合約地址
* @param gasLimit gas值
* @param gasPrice gas price
* @param credentials 秘鑰對(duì)象
* @return 交易hash
* @throws IOException
*/
public static String sendAllCoin(String from, String to, String coinAddress, BigInteger gasLimit, BigInteger gasPrice, Credentials credentials) throws IOException {
BigInteger nonce = web3j.ethGetTransactionCount(from, DefaultBlockParameterName.PENDING).send().getTransactionCount();
BigInteger value = getBalanceOfCoin(from, coinAddress);
System.out.println(value);
Function transfer = new Function(
"transfer",
Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(to),
new org.web3j.abi.datatypes.generated.Uint256(value)),
Collections.<TypeReference<?>>emptyList());
String data = FunctionEncoder.encode(transfer);
RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, coinAddress, data);
byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
return web3j.ethSendRawTransaction(Numeric.toHexString(signMessage)).send().getTransactionHash();
}
/**
* 獲取賬戶代幣余額
* @param account 賬戶地址
* @param coinAddress 代幣地址
* @return 代幣余額 (單位:代幣最小單位)
* @throws IOException
*/
public static BigInteger getBalanceOfCoin(String account, String coinAddress) throws IOException {
Function balanceOf = new Function("balanceOf",
Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(account)),
Arrays.<TypeReference<?>>asList(new TypeReference<Uint256>() {
}));
if (coinAddress == null) {
return null;
}
String value = web3j.ethCall(Transaction.createEthCallTransaction(account, coinAddress, FunctionEncoder.encode(balanceOf)), DefaultBlockParameterName.PENDING).send().getValue();
return new BigInteger(value.substring(2), 16);
}
/**
* 獲取合約交易估算gas值
* @param from 發(fā)送者
* @param to 發(fā)送目標(biāo)地址
* @param coinAddress 代幣地址
* @param value 發(fā)送金額(單位:代幣最小單位)
* @return 估算的gas limit
* @throws IOException
*/
public static BigInteger getTransactionGasLimit(String from, String to, String coinAddress, BigInteger value) throws IOException {
Function transfer = new Function(
"transfer",
Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(to),
new org.web3j.abi.datatypes.generated.Uint256(value)),
Collections.<TypeReference<?>>emptyList());
String data = FunctionEncoder.encode(transfer);
return web3j.ethEstimateGas(new Transaction(from, null, null, null, coinAddress, BigInteger.ZERO, data)).send().getAmountUsed();
}
}