Web3j钱包 -- java版本
基于Web3j的钱包工具类,包括普通钱包的生成和加载,bip39钱包的生成和加载,bip39钱包签名和验证。
相关依赖
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.9</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.2</version>
</dependency>
安全随机数
import org.web3j.crypto.LinuxSecureRandom;
import java.security.SecureRandom;
/**
* @author ming
* @version 1.0.0
* @date 2020/9/23 16:05
**/
public class SecureRandomUtils {
private static final SecureRandom SECURE_RANDOM;
private static int isAndroid;
public static SecureRandom secureRandom() {
return SECURE_RANDOM;
}
private static boolean isAndroidRuntime() {
if (isAndroid == -1) {
String runtime = System.getProperty("java.runtime.name");
isAndroid = runtime != null && "Android Runtime".equals(runtime) ? 1 : 0;
}
return isAndroid == 1;
}
private SecureRandomUtils() {
}
static {
if (isAndroidRuntime()) {
new LinuxSecureRandom();
}
SECURE_RANDOM = new SecureRandom();
isAndroid = -1;
}
}
钱包工具
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.tld.admin.common.utils.SecureRandomUtils;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.web3j.crypto.*;
import org.web3j.utils.Numeric;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Random;
/**
* 基于web3j的钱包工具类
*
* @author ming
* @version 1.0.0
* @date 2020/9/21 10:30
**/
public class Web3jWalletUtils {
private Logger log = LoggerFactory.getLogger(this.getClass());
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final SecureRandom SECURE_RANDOM = SecureRandomUtils.secureRandom();
static {
// 转换为格式化的json
OBJECT_MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
// 如果json中有新增的字段并且是实体类类中不存在的,不报错
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public Web3jWalletUtils() {
}
/**
* 创建普通钱包
*
* @param password 钱包密码
* @param walletFilePath 钱包文件存储路径
* @return CommonWallet
* @throws Exception e
*/
public CommonWallet generateCommonWallet(String password, String walletFilePath) throws Exception {
try {
String walletFileName = WalletUtils.generateNewWalletFile(password, new File(walletFilePath), false);
String path = StringUtils.isNotBlank(walletFilePath) && File.separator.equals(walletFilePath.substring(walletFilePath.length() - 1)) ? walletFilePath + walletFileName : walletFilePath + File.separator + walletFileName;
Credentials credentials = WalletUtils.loadCredentials(password, walletFilePath);
String address = credentials.getAddress();
BigInteger publicKey = credentials.getEcKeyPair().getPublicKey();
BigInteger privateKey = credentials.getEcKeyPair().getPrivateKey();
return new CommonWallet(address, password, privateKey, publicKey, path);
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException | CipherException | IOException e) {
throw new Exception(e);
}
}
/**
* 创建普通钱包
*
* @param password 密码
* @return CommonWallet
* @throws Exception e
*/
public CommonWallet generateCommonWallet(String password) throws Exception {
try {
ECKeyPair ecKeyPair = Keys.createEcKeyPair();
WalletFile walletFile = generateWalletFile(password, ecKeyPair, false);
BigInteger publicKey = ecKeyPair.getPublicKey();
BigInteger privateKey = ecKeyPair.getPrivateKey();
return new CommonWallet(walletFile.getAddress(), JSON.toJSONString(walletFile), password, privateKey, publicKey);
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException | CipherException e) {
throw new Exception(e);
}
}
/**
* 从钱包json加载钱包
*
* @param password 钱包密码
* @param json 钱包json
* @return CommonWallet
* @throws JsonProcessingException jsonProcessingException
* @throws CipherException cipherException
*/
public CommonWallet loadCommonWalletFromJson(String password, String json) throws JsonProcessingException, CipherException {
WalletFile walletFile = OBJECT_MAPPER.readValue(json, WalletFile.class);
log.debug("获取到的钱包Json: " + JSON.toJSONString(walletFile));
Credentials credentials = loadCredentials(password, walletFile);
String address = credentials.getAddress();
ECKeyPair ecKeyPair = credentials.getEcKeyPair();
BigInteger privateKey = ecKeyPair.getPrivateKey();
BigInteger publicKey = ecKeyPair.getPublicKey();
return new CommonWallet(address, json, password, privateKey, publicKey);
}
/**
* 从钱包文件加载钱包
*
* @param password 密码
* @param walletFilePath 钱包文件路径
* @return CommonWallet
* @throws IOException e
* @throws CipherException e
*/
public CommonWallet loadCommonWalletFromFile(String password, String walletFilePath) throws IOException, CipherException {
Credentials credentials = WalletUtils.loadCredentials(password, walletFilePath);
String address = credentials.getAddress();
ECKeyPair ecKeyPair = credentials.getEcKeyPair();
BigInteger privateKey = ecKeyPair.getPrivateKey();
BigInteger publicKey = ecKeyPair.getPublicKey();
return new CommonWallet(address, password, privateKey, publicKey, walletFilePath);
}
/**
* 创建bip39钱包
*
* @param password 钱包密码
* @return CommonWallet
* @throws CipherException e
*/
public Bip39Wallet2 generateBip39Wallet(String password) throws CipherException {
byte[] initialEntropy = new byte[16];
SECURE_RANDOM.nextBytes(initialEntropy);
String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy);
byte[] seed = MnemonicUtils.generateSeed(mnemonic, password);
ECKeyPair ecKeyPair = ECKeyPair.create(Hash.sha256(seed));
WalletFile walletFile = generateWalletFile(password, ecKeyPair, false);
return new Bip39Wallet2(walletFile.getAddress(), mnemonic, password, JSON.toJSONString(walletFile), ecKeyPair.getPrivateKey(), ecKeyPair.getPublicKey());
}
/**
* 从json加载bip39钱包
*
* @param password 密码
* @param mnemonic 助记词
* @param json 钱包文件
* @return Bip39Wallet2
*/
public Bip39Wallet2 loadBip39WalletFromJson(String password, String mnemonic, String json) {
Credentials credentials = WalletUtils.loadBip39Credentials(password, mnemonic);
return new Bip39Wallet2(credentials.getAddress(), json, mnemonic, password, credentials.getEcKeyPair().getPrivateKey(), credentials.getEcKeyPair().getPublicKey());
}
/**
* 从文件加载bip39钱包
*
* @param password 密码
* @param mnemonic 助记词
* @param walletFilePath 钱包文件
* @return Bip39Wallet2
*/
public Bip39Wallet2 loadBip39WalletFromFile(String password, String mnemonic, String walletFilePath) {
Credentials credentials = WalletUtils.loadBip39Credentials(password, mnemonic);
return new Bip39Wallet2(credentials.getAddress(), mnemonic, password, credentials.getEcKeyPair().getPrivateKey(), credentials.getEcKeyPair().getPublicKey(), walletFilePath);
}
/**
* 创建bip39钱包
*
* @param password 钱包密码
* @param walletFilePath 钱包文件路径
* @return CommonWallet
* @throws CipherException e
*/
public Bip39Wallet2 generateBip39Wallet(String password, String walletFilePath) throws CipherException, IOException {
Bip39Wallet bip39Wallet = WalletUtils.generateBip39Wallet(password, new File(walletFilePath));
String mnemonic = bip39Wallet.getMnemonic();
// 返回钱包文件名
String filename = bip39Wallet.getFilename();
Credentials credentials = WalletUtils.loadBip39Credentials(password, mnemonic);
String address = credentials.getAddress();
BigInteger publicKey = credentials.getEcKeyPair().getPublicKey();
BigInteger privateKey = credentials.getEcKeyPair().getPrivateKey();
String path = StringUtils.isNotBlank(walletFilePath) && File.separator.equals(walletFilePath.substring(walletFilePath.length() - 1)) ? walletFilePath + filename : walletFilePath + File.separator + filename;
return new Bip39Wallet2(address, mnemonic, password, privateKey, publicKey, path);
}
public static Credentials loadCredentials(String password, WalletFile walletFile) throws CipherException {
return Credentials.create(Wallet.decrypt(password, walletFile));
}
public static WalletFile generateWalletFile(String password, ECKeyPair ecKeyPair, boolean useFullScrypt) throws CipherException {
WalletFile walletFile;
if (useFullScrypt) {
walletFile = Wallet.createStandard(password, ecKeyPair);
} else {
walletFile = Wallet.createLight(password, ecKeyPair);
}
return walletFile;
}
public String signTransaction(String json, ECKeyPair keyPair) {
Sign.SignatureData signatureData = Sign.signMessage(json.getBytes(), keyPair);
JSONObject signatureDataJson = new JSONObject();
signatureDataJson.put("v", Byte.toString(signatureData.getV()));
signatureDataJson.put("r", Numeric.toBigInt(signatureData.getR()));
signatureDataJson.put("s", Numeric.toBigInt(signatureData.getS()));
return signatureDataJson.toJSONString();
}
/**
* verify data
* get public key and get wallet address with sign
*
* @param data 明文数据
* @param walletAddress 钱包地址
* @param strSign 签名数据
* @return boolean
* @throws Exception e
*/
public boolean verifyTransaction(String data, String walletAddress, String strSign) throws Exception {
try {
if (StringUtils.isBlank(data)) {
return false;
}
JSONObject jsonSign = JSONObject.parseObject(strSign);
if (jsonSign == null) {
return false;
}
byte v = jsonSign.getByte("v");
byte[] r = Numeric.toBytesPadded(jsonSign.getBigInteger("r"), 32);
byte[] s = Numeric.toBytesPadded(jsonSign.getBigInteger("s"), 32);
Sign.SignatureData signatureData = new Sign.SignatureData(v, r, s);
BigInteger publicKey = Sign.signedMessageToKey(data.getBytes(), signatureData);
return StringUtils.equalsIgnoreCase("0x" + Keys.getAddress(publicKey), walletAddress);
} catch (Exception e) {
e.printStackTrace();
throw new Exception(e);
}
}
public String generateRandomPassword() {
return generateRandomPassword(8);
}
public String generateRandomPassword(Integer count) {
if (ObjectUtils.isEmpty(count)) {
throw new RuntimeException("count must setting");
}
StringBuilder codeNum = new StringBuilder();
int[] code = new int[3];
Random random = new Random();
for (int i = 0; i < count; i++) {
int num = random.nextInt(10) + 48;
int uppercase = random.nextInt(26) + 65;
int lowercase = random.nextInt(26) + 97;
code[0] = num;
code[1] = uppercase;
code[2] = lowercase;
codeNum.append((char) code[random.nextInt(3)]);
}
return codeNum.toString();
}
@Data
@Accessors(chain = true)
static class CommonWallet {
private String address;
private String json;
private String password;
private BigInteger privateKey;
private String privateKeyHexStr;
private BigInteger publicKey;
private String publicKeyHexStr;
private String path;
public CommonWallet(String address, String json, String password, BigInteger privateKey, BigInteger publicKey) {
this.address = address;
this.json = json;
this.password = password;
this.privateKey = privateKey;
this.publicKey = publicKey;
this.path = "";
this.setPrivateKeyHexStr(privateKey);
this.setPublicKeyHexStr(publicKey);
}
public CommonWallet(String address, String password, BigInteger privateKey, BigInteger publicKey, String path) {
this.address = address;
this.json = "";
this.password = password;
this.privateKey = privateKey;
this.publicKey = publicKey;
this.path = path;
this.setPrivateKeyHexStr(privateKey);
this.setPublicKeyHexStr(publicKey);
}
public void setPrivateKeyHexStr(BigInteger privateKey) {
this.privateKeyHexStr = Numeric.toHexStringWithPrefix(privateKey);
}
public void setPublicKeyHexStr(BigInteger publicKey) {
this.publicKeyHexStr = Numeric.toHexStringWithPrefix(publicKey);
}
}
@Data
@Accessors(chain = true)
static class Bip39Wallet2 {
private String address;
private String password;
private String json;
private BigInteger privateKey;
private String privateKeyHexStr;
private BigInteger publicKey;
private String publicKeyHexStr;
private String mnemonic;
private String path;
public Bip39Wallet2(String address, String mnemonic, String password, String json, BigInteger privateKey, BigInteger publicKey) {
this.address = address;
this.password = password;
this.json = json;
this.privateKey = privateKey;
this.publicKey = publicKey;
this.mnemonic = mnemonic;
this.path = "";
this.setPrivateKeyHexStr(privateKey);
this.setPublicKeyHexStr(publicKey);
}
public Bip39Wallet2(String address, String mnemonic, String password, BigInteger privateKey, BigInteger publicKey, String path) {
this.address = address;
this.password = password;
this.json = "";
this.privateKey = privateKey;
this.publicKey = publicKey;
this.mnemonic = mnemonic;
this.path = path;
this.setPrivateKeyHexStr(privateKey);
this.setPublicKeyHexStr(publicKey);
}
public void setPrivateKeyHexStr(BigInteger privateKey) {
this.privateKeyHexStr = Numeric.toHexStringWithPrefix(privateKey);
}
public void setPublicKeyHexStr(BigInteger publicKey) {
this.publicKeyHexStr = Numeric.toHexStringWithPrefix(publicKey);
}
}
}
测试方法
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.WalletUtils;
import java.io.IOException;
class Web3jWalletUtilsTest {
private Logger log = LoggerFactory.getLogger(this.getClass());
private static Web3jWalletUtils web3jWalletUtils;
private static final String COMMON_WALLET_PATH = "E:\\tmp\\wallet_home\\common_wallet";
private static final String BIP39_WALLET_PATH = "E:\\tmp\\wallet_home\\bip39_wallet";
@BeforeEach
void setUp() {
web3jWalletUtils = new Web3jWalletUtils();
}
/**
* 普通钱包生成和恢复 -- json
*
* @throws Exception e
*/
@Test
void generateCommonWalletTest() throws Exception {
Web3jWalletUtils.CommonWallet commonWallet = web3jWalletUtils.generateCommonWallet("");
log.debug(JSON.toJSONString(commonWallet));
// 从json加载
Web3jWalletUtils.CommonWallet commonWallet1 = web3jWalletUtils.loadCommonWalletFromJson("", commonWallet.getJson());
log.debug("加载的json: " + commonWallet1.getJson());
}
/**
* 普通钱包生成和恢复 -- file
*
* @throws Exception e
*/
@Test
void generateCommonWalletOnWalletPathTest() throws Exception {
Web3jWalletUtils.CommonWallet commonWallet = web3jWalletUtils.generateCommonWallet("", COMMON_WALLET_PATH);
log.debug("生成的普通钱包: " + JSON.toJSONString(commonWallet));
// 从文件加载
Web3jWalletUtils.CommonWallet commonWallet1 = web3jWalletUtils.loadCommonWalletFromFile("", commonWallet.getPath());
log.debug("从文件加载的json: " + commonWallet1.getJson());
}
/**
* bip39钱包生成和恢复 -- json
*
* @throws CipherException e
*/
@Test
void bip39Wallet2Test() throws CipherException {
Web3jWalletUtils.Bip39Wallet2 bip39Wallet2 = web3jWalletUtils.generateBip39Wallet("");
log.debug("生成的bip39钱包: " + JSON.toJSONString(bip39Wallet2));
// 从json加载钱包
Web3jWalletUtils.Bip39Wallet2 bip39Wallet21 = web3jWalletUtils.loadBip39WalletFromJson("", bip39Wallet2.getMnemonic(), bip39Wallet2.getJson());
log.debug("从json加载的bip39钱包: " + JSON.toJSONString(bip39Wallet21));
}
/**
* bip39钱包生成和恢复 -- file
*
* @throws CipherException e
* @throws IOException e
*/
@Test
void bip39Wallet2OnWalletPathTest() throws CipherException, IOException {
Web3jWalletUtils.Bip39Wallet2 bip39Wallet2 = web3jWalletUtils.generateBip39Wallet("", BIP39_WALLET_PATH);
log.debug("生成的bip39钱包" + JSON.toJSONString(bip39Wallet2));
Web3jWalletUtils.Bip39Wallet2 bip39Wallet21 = web3jWalletUtils.loadBip39WalletFromFile("", bip39Wallet2.getMnemonic(), bip39Wallet2.getPath());
log.debug("生成的bip39钱包" + JSON.toJSONString(bip39Wallet21));
}
/**
* 生成随机密码
*/
@Test
void generateRandomPasswordTest() {
log.debug(web3jWalletUtils.generateRandomPassword());
log.debug(web3jWalletUtils.generateRandomPassword(16));
}
/**
* bip39钱包签名和验证交易
*/
@Test
void bip39WalletSignAndVerifyTransaction() throws Exception {
// TODO: 2020/9/24 生成bip39钱包
Web3jWalletUtils.Bip39Wallet2 bip39Wallet2 = web3jWalletUtils.generateBip39Wallet("123456", BIP39_WALLET_PATH);
log.debug("生成的bip39钱包" + JSON.toJSONString(bip39Wallet2));
String password = bip39Wallet2.getPassword();
String mnemonic = bip39Wallet2.getMnemonic();
log.debug("钱包密码: " + password);
log.debug("钱包助记词: " + mnemonic);
// TODO: 2020/9/24 获取原始数据
JSONObject data = new JSONObject();
data.put("fromWalletAddress", bip39Wallet2.getAddress());
data.put("toWalletAddress", "0x565fe768c659259abn45cf4f1081a663d091bcb9");
data.put("value", "99.4");
data.put("chargeWalletAddress", "0xdd05e23c39eead942bcv63fd388ffa13a1a28307");
data.put("chargeValue", "0.6");
String rawData = data.toJSONString();
log.debug("原始数据 : " + rawData);
Credentials credentials = WalletUtils.loadBip39Credentials(password, mnemonic);
// TODO: 2020/9/24 对原始数据进行签名
String sign = web3jWalletUtils.signTransaction(rawData, credentials.getEcKeyPair());
// TODO: 2020/9/24 验证签名的数据
boolean flag = web3jWalletUtils.verifyTransaction(rawData, bip39Wallet2.getAddress(), sign);
log.debug("验签结果: " + flag);
}
}