创建一个以太坊钱包有多种方式,一般情况下可以通过geth、EtherumWallet等客户端。对于前端,可以使用插件MetaMask进行创建。这几种方式技术实现虽然不同,但底层原理是一致的。本文主要介绍如何通过web3j架构创建一个以太坊的冷钱包,从而实现将这一过程部署在服务端或者android端。
文中涉及到的技术栈有:
Web3j :轻量级java库,用于连接以太坊客户端或节点
Infura :以太坊基础设施,用于访问以太坊主网络或测试网络
Java:编程语言
1.Web3j的安装
无论是java工程还是android工程,web3j都提供了maven和grade 两种依赖方式:
- java工程
- manen依赖
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>3.3.1</version>
</dependency>
复制代码
- gradle依赖
compile ('org.web3j:core:3.3.1')
复制代码
- android工程
- maven依赖
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>3.3.1-android</version>
</dependency>
复制代码
- gradle依赖
compile ('org.web3j:core:3.3.1-android')
复制代码
值得注意的是,目前的web3j对于高版本JDK存在不兼容的问题,如果出现如下类似的问题,直接更换JDK为version 8即可。
Could not determine Java version using executable /Library/Java/JavaVirtualMachines/jdk-10.jdk/Contents/Home/bin/java.
2.关于Infura
以太坊的客户端实现有多种,但很多都需要在本地同步所有的节点数据而占用大量硬盘存储空间,并且需要消耗同步的时间。Infura就是提供一种中心化的服务,通过web3.js或者web3j使前端或服务端能便捷的访问以太坊所有节点。可以理解为一种以太坊客户端的云端版本。使用过程需要注册,一个专属的访问token。本文中使用的客户端都是Infura提供的Rinkeby测试网络。
3.新建钱包文件keyfile
在以太坊中,钱包(wallet)和账户(account)是两个不同的概念。账户是以太坊的核心,由一对秘钥组成-公钥和私钥。账户可以分为两种,外部账户和合约账户。而钱包是指保存 地址、公钥、私钥的文件或其他机构,每个钱包文件至少包含一个账户。创建钱包的同时也是创建一个以太坊账户的过程不同的客户端创建钱包的方式不一致但原理相同,有关钱包是具体是如何生成的可以查看另外这篇文章。
- 新建一个java工程,初始化gradle或者maven
- 依赖web3j
- 新建Application.java文件,设置程序入口main函数
- 调用钱包工具类生成钱包文件
/*************创建一个钱包文件**************/
private void creatAccount() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CipherException, IOException {
String walletFileName0="";//文件名
String walletFilePath0="/Users/yepeng/MyGitHub/z_wallet_temp";
//钱包文件保持路径,请替换位自己的某文件夹路径
walletFileName0 = WalletUtils.generateNewWalletFile("123456", new File(walletFilePath0), false);
//WalletUtils.generateFullNewWalletFile("password1",new File(walleFilePath1));
//WalletUtils.generateLightNewWalletFile("password2",new File(walleFilePath2));
log.info("walletName: "+walletFileName0);
}
复制代码
钱包构建的过程中需要输入的三个参数,分别设置钱包的密码、保存路径、以及是否轻量级钱包。
执行创建函数后,会自动在指定路径生成一个json 文件,即钱包keyfiles。
钱包文件结构:
- cipher:加密算法,AES算法,用于加密以太坊私钥
- cipherparams:cipher算法需要的参数,参数iv,是aes-128-ctr加密算法需要的初始化向量
- ciphertext:加密后的密文,aes-128-ctr函数的加密输入密文;
- kdf:秘钥生成函数,用于使用密码加密keystore文件
- kdfparams:kdf算法所需要的参数
- mac:验证密码的编码
生成钱包的逆向过程 为加载钱包。
4.加载钱包文件
加载钱包的过程需要提供钱包文件和密码
/********加载钱包文件**********/
private void loadWallet() throws IOException, CipherException {
String walleFilePath="/Users/yepeng/MyGitHub/z_wallet_temp/UTC--2018-04-10T02-51-24.815000000Z--12571f46ec3f81f7ebe79112be5883194d683787.json";
String passWord="123456";
credentials = WalletUtils.loadCredentials(passWord, walleFilePath);
String address = credentials.getAddress();
BigInteger publicKey = credentials.getEcKeyPair().getPublicKey();
BigInteger privateKey = credentials.getEcKeyPair().getPrivateKey();
log.info("address="+address);
log.info("public key="+publicKey);
log.info("private key="+privateKey);
}
复制代码
函数运行的结果:
通过工具类 WalletUtols的函数 loadCredentials(),会返回一个对象Credentials,这个对象即包含了钱包文件的所有信息,包括地址、秘钥对。
至此,钱包的创建和加载已经完成,但这一过程全部发生在本地,并未同步到以太坊区块链。查询地址余额前,需要连接以太坊结点。
5.构建Web3j实体,连接以太坊结点
web3j是连接java端与以太坊的桥梁,广播交易,查询账户都需要通过web3j实体。web3j支持通过http进行构建,而且兼容了infura。在本文中,使用的是infura的测试网络Rinkeby。
/*******连接以太坊客户端**************/
private void conectETHclient() throws IOException {
//连接方式1:使用infura 提供的客户端
web3j = Web3j.build(new HttpService("https://rinkeby.infura.io/zmd7VgRt9go0x6qlJ2Mk"));// TODO: 2018/4/10 token更改为自己的
//连接方式2:使用本地客户端
//web3j = Web3j.build(new HttpService("127.0.0.1:7545"));
//测试是否连接成功
String web3ClientVersion = web3j.web3ClientVersion().send().getWeb3ClientVersion();
log.info("version=" + web3ClientVersion);
}
复制代码
web3j实体构建完成后,可以打印出版本号以测试是否连接成功。如果成功,就可以做其他的事情了。值得注意的是,web3j采用的是RxJava的设计,所以许多函数的返回值是 Request,这个对象有两种执行方式,异步和同步,即send()和sendAsyn()。
6.查询账户余额
查询账户的余额的方式:
/***********查询指定地址的余额***********/
private void getBlanceOf() throws IOException {
if (web3j == null) return;
String address = "0x41F1dcbC0794BAD5e94c6881E7c04e4F98908a87";//等待查询余额的地址
//第二个参数:区块的参数,建议选最新区块
EthGetBalance balance = web3j.ethGetBalance(address, DefaultBlockParameter.valueOf("latest")).send();
//格式转化 wei-ether
String blanceETH = Convert.fromWei(balance.getBalance().toString(), Convert.Unit.ETHER).toPlainString().concat(" ether");
log.info(blanceETH);
}
复制代码
其中核心方法 web3j.ethGetBalance(address, defaultBlockParameter) 中的第二个参数比较特殊,指默认的区块参数。当请求余额的方法作用与以太坊的区块网络时,这个参数决定了查询区块的高度。
HEX String
- 一个整数块号String "earliest"
为最早/起源块String "latest"
- 为最新的采矿块String "pending"
- 待处理状态/交易
一般情况下,选择“latest”即可。
以太坊中,如果没有特殊标示,数字的单位都是小数点后18位,因此查询账户余额有必要将wei转化成ether。
6.使用钱包进行转账
作为一个钱包,除了保存账户资产外,最重要的就是转账或交易了,利用web3j可以便捷的实现eth的转移。
/ /****************交易*****************/
private void transto() throws Exception {
if (web3j == null) return;
if (credentials == null) return;
//开始发送0.01 =eth到指定地址
String address_to = "0x41F1dcbC0794BAD5e94c6881E7c04e4F98908a87";
TransactionReceipt send = Transfer.sendFunds(web3j, credentials, address_to, BigDecimal.ONE, Convert.Unit.FINNEY).send();
log.info("Transaction complete:");
log.info("trans hash=" + send.getTransactionHash());
log.info("from :" + send.getFrom());
log.info("to:" + send.getTo());
log.info("gas used=" + send.getGasUsed());
log.info("status: " + send.getStatus());
}
复制代码
核心方法需要提供4个参数:
- web3j实体
- Credentials 源账户
- address 转出地址
- value 数量
- uint 单位
等待片刻后,会返回转账结果
可以看到交易hash、转入转出地址、gas消耗等信息。同时可以在etherscan-rinkeby上进行查看本次交易详情
7.总结
上面的代码已经完成了一个以太坊钱包所需的所有基本功能,包括创建、加载、转账、查询。本文中采用的网络是infura提供的Rinkeby测试网络,创建的钱包地址为 0x12571F46Ec3f81F7EbE79112Be5883194d683787。
在具体的业务场景中,只要将测试网络更换为以太坊主网络即可。