以太坊钱包

### android钱包笔记 ### 1、创建钱包之助记词生成

//12个助记词 ArrayList words = new ArrayList<>(); MnemonicCode mnemonicCode = new MnemonicCode(context.getAssets().open("english.txt"), null); SecureRandom secureRandom = SecureRandomUtils.secureRandom(); byte[] initialEntropy = new byte[16]; //算法需要,必须是被4整除 secureRandom.nextBytes(initialEntropy); List wd = mnemonicCode.toMnemonic(initialEntropy); if (wd == null || wd.size() != 12) throw new RuntimeException("generate word error"); else { words.clear(); words.addAll(wd); StringBuilder save = new StringBuilder(); for (String word : words) { save.append(word).append(" "); } //真正的助记词钱包地址创建流程 DeterministicSeed deterministicSeed = new DeterministicSeed(save.toString().substring(0, save.toString().length() - 1), null, "", 0); List ls = deterministicSeed.getMnemonicCode(); DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build(); //DERIVATION_PATH = "m/44'/60'/0'/0/0"此值使用BIP44协议固定,导入钱包可用到,其他的PATH未尝试 List keyPath = HDUtils.parsePath("M/44H/60H/0H/0/0"); //私钥 BigInteger privateKey = deterministicKeyChain.getKeyByPath(keyPath, true).getPrivKey(); ECKeyPair ecKeyPair = ECKeyPair.create(privateKey); //钱包地址,不带0x头 String address = Keys.getAddress(ecKeyPair); }

2、导入钱包之助记词导入
/** * 通用的以太坊基于bip44协议的助记词路径 (imtoken jaxx Metamask myetherwallet) */ public static String ETH_JAXX_TYPE = "m/44'/60'/0'/0/0"; /** * import mnemonic wallet * * @param mnemonic 格式为单词加空格的字符串"a b c d e f g h i j k l" */ private void importWallet(String mnemonic) { String[] pathArray = ETH_JAXX_TYPE.split("/"); if (pathArray.length <= 1) { //内容不对 return; } String passphrase = ""; long creationTimeSeconds = System.currentTimeMillis() / 1000; //助记词种子 DeterministicSeed ds = new DeterministicSeed(Arrays.asList(mnemonic.split(" ")), null, passphrase, creationTimeSeconds); createWallet(ds, pathArray); }/** * 导入钱包 */ private void createWallet(DeterministicSeed ds, String[] pathArray) { byte[] seedBytes = ds.getSeedBytes(); List mnemonicCode = ds.getMnemonicCode(); DeterministicKey masterPrivateKey = HDKeyDerivation.createMasterPrivateKey(seedBytes); for (int i = 1; i < pathArray.length; i++) { ChildNumber childNumber; 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); }masterPrivateKey = HDKeyDerivation.deriveChildKey(masterPrivateKey, childNumber); } Credentials credentials = Credentials.create(masterPrivateKey.getPrivKey().toString(16)); ECKeyPair ecKeyPair = ECKeyPair.create(masterPrivateKey.getPrivKeyBytes()); //钱包地址导入成功 String walletAddress = OwnWalletUtils.generateWalletFile(mDataHandler.pwd.get(), ecKeyPair, getFilesDir(), false); }//导入钱包生成地址 public String generateWalletFile( String password, ECKeyPair ecKeyPair, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException {WalletFile walletFile; if (useFullScrypt) { walletFile = Wallet.createStandard(password, ecKeyPair); } else { walletFile = Wallet.createLight(password, ecKeyPair); } String fileName = walletFile.getAddress(); File destination = new File(destinationDirectory, fileName); ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(); objectMapper.writeValue(destination, walletFile); return fileName; }

3、由于ETH节点获取的nonce值转账失败
【以太坊钱包】ETH节点要求每笔交易必须有一个nonce数值,同一个节点发起交易时,nonce会递增;
请求nonce接口文档
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactioncount
参数
"latest"获取最后一次交易的nonce
"earliest"没试过
"pending"获取最后一次交易中状态的nonce
nonce太小会被拒绝
nonce太大区块确认时间会很长
nonce会引起的问题:
1、转账时间变很慢
2、转账提交不成功
解决方法
1、取latest值,然后设置合适的gaslimit会降低交易单发起失败的概率
2、设置合适的默认gasprice会加快交易单的确认时间
3、本地对nonce的维护(不建议,多人同时操作一个钱包的时候会出现拥堵,此时没有补齐节点nonce和本地nonce的差值时交易将不被确认)
4、ETH转账签名
/** * 离线签名eth * @parame thWalletInfo.wordsw为助记词字符串 * @param to//转账的钱包地址 * @param nonce//获取到的交易次数 * @param gasPrice //gasPrice * @param gasLimit //gasLimit * @param value//转账的值 * @return */ public String signedEthTransactionData(String coinName, String to, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String value) throws Exception { if (TextUtils.isEmpty(ethWalletInfo.words)) { closeProgressDialog(); throw new RuntimeException("please generateMnemonic first"); } //为ETH的时候直接填入 //把十进制的转换成ETH的Wei, 1ETH = 10^18 Wei BigDecimal realValue = https://www.it610.com/article/Convert.toWei(value, Convert.Unit.ETHER); RawTransaction rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, to, realValue.toBigIntegerExact()); //获取个人钱包信息 此处用到助记词签名,实际可通过其他的签名 DeterministicSeed seed = new DeterministicSeed(ethWalletInfo.words, null,"", System.currentTimeMillis()); DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).build(); List keyPath = HDUtils.parsePath("M/44H/60H/0H/0/0"); DeterministicKey key = chain.getKeyByPath(keyPath, true); BigInteger privKey = key.getPrivKey(); Credentials credentials = Credentials.create(privKey.toString(16)); //使用TransactionEncoder对RawTransaction进行签名操作 byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); 转换成0x开头的字符串 return Numeric.toHexString(signedMessage); }

5、ETH代币转账签名
/** * 离线签名eth * @parame thWalletInfo.wordsw为助记词字符串 * @param to//转账的钱包地址 * @param nonce//获取到的交易次数 * @param gasPrice //建议提交前获取最新的gasPrice * @param gasLimit //建议提交前获取最新的gasLimit * @param value//转账的值 * @return */ public String signedEthTransactionData(String coinName, String to, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String value) throws Exception { if (TextUtils.isEmpty(ethWalletInfo.words)) { closeProgressDialog(); throw new RuntimeException("please generateMnemonic first"); }//为代币的时候要乘以100,此处规则需看代币的规定 * new BigInteger("100").doubleValue() //因为每个代币可以规定自己的小数位, 所以实际的转账值=数值 * 10^小数位 BigDecimal realValue = https://www.it610.com/article/BigDecimal.valueOf(new BigDecimal(value).doubleValue() * new BigInteger("100").doubleValue() ); //0xa9059cbb代表某个代币的转账方法hex(transfer) + 对方的转账地址hex + 转账的值的hex String data = "https://www.it610.com/article/0xa9059cbb" + Numeric.toHexStringNoPrefixZeroPadded(Numeric.toBigInt(to), 64) + Numeric.toHexStringNoPrefixZeroPadded(realValue.toBigInteger(), 64); RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit.add(new BigInteger("0")), coinInfo.contractAddress, data); //获取个人钱包信息此处用到助记词签名,实际可通过其他的签名 DeterministicSeed seed = new DeterministicSeed(ethWalletInfo.words, null, "", System.currentTimeMillis()); DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).build(); List keyPath = HDUtils.parsePath("M/44H/60H/0H/0/0"); DeterministicKey key = chain.getKeyByPath(keyPath, true); BigInteger privKey = key.getPrivKey(); Credentials credentials = Credentials.create(privKey.toString(16)); //使用TransactionEncoder对RawTransaction进行签名操作 byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); //转换成0x开头的字符串 return Numeric.toHexString(signedMessage); }

    推荐阅读