上週我開源了一款錢包,反映很好,一週時間不到已經快到100 Star。接下來我會幾篇系列文章把開發以太坊錢包的核心要點寫出來,也算是對代碼的一個解讀。java
錢包是使用Android安卓平臺編寫,使用的是原生代碼Java 語言編寫, 是基於Java 1.8 版本,也使用了Java 1.8 中一些較新的語言特性,如 Lambda表達式等;另外還較多使用了ReactiveX/RxAndroid響應式編程用法。android
在本系列文章中,重點是介紹以太坊錢包帳號、交易等邏輯,有時可能會假定讀者已經瞭解Android開發等相關知識,由於這些內容不是文章的重點,所以不會過多介紹,請海涵。git
一般一個錢包會包含如下功能:github
咱們先來介紹第一個功能:經過生成助記詞、Keystore文件、私鑰建立錢包帳號。 本系列中,錢包都是指分層肯定性錢包,(HD錢包 Hierarchical Deterministic Wallets), 以前博客有一篇文章分層錢包進行了詳細的介紹,還不熟悉的能夠讀一下。 爲了保持本文的完整,這裏作一個總結性回顧:以太坊及比特幣的地址是由隨機生成的私鑰通過橢圓曲線等算法單向推倒而來 ,BIP32及BIP44是爲方便管理私鑰提出的分層推倒方案,BIP39 定義助記詞讓分層種子的備份更方便。 而KeyStore文件是用來解密以太坊保存私鑰的一種方式,你們能夠閱讀下這篇文章: 帳號Keystore文件導入導出瞭解更多。web
實現完成的,界面以下圖:算法
這是一張導入錢包帳號的截圖(導入和建立,其實原理同樣),界面仿照ImToken,不過本文將不會介紹UI部分的編寫。編程
爲了完成建立帳號功能,咱們須要使用到兩個庫:Web3j 和 bitcoinjjson
Web3是一套和以太坊通訊的封裝庫,Web3j是Java版本的實現,例如發起交易和智能合約進行交互,下圖很好的表達了其做用。 bash
不過本文中的功能,主要是使用了web3j中橢圓曲線加密及KeyStore文件的生成與解密。app
bitcoinj 的功能和web3相似,它是比特幣協議的Java實現,他實現了 BIP3二、BIP44及BIP39 相關協議。
Android使用Gradle來構建,直接在app/build.gradle
文件中加入:
implementation 'org.web3j:core:4.1.0-android'
implementation 'org.bitcoinj:bitcoinj-core:0.14.7'
複製代碼
提示: 實踐中遇到的一個問題,因爲bitcoinj 中引入了
com.lambdaworks:scrypt
加密庫, 它包含的lib/x86_64/darwin/libscrypt.dylib
文件,會致使在進行Android App Bundle 編譯時會出現錯誤(好像也會致使某些機型無法安裝),解決辦法是在 build.gradle 加入一下語句,把這個文件在打包時排除掉。 packagingOptions { exclude 'lib/x86_64/darwin/libscrypt.dylib' }
這是目前錢包客戶端,最多見的一種爲用戶常見帳號的方式,這裏會包含一下幾個核心步驟:
你們能夠在再次閱讀分層錢包,理解爲什麼這麼作的緣由。
理解了上面幾點,那麼代碼就容易明白了,代碼在代碼庫中的app/src/pro/upchain/wallet/utils/ETHWalletUtils.java
中,關鍵代碼邏輯以下:
// 建立錢包對象入口函數
public static ETHWallet generateMnemonic(String walletName, String pwd) {
String[] pathArray = "m/44'/60'/0'/0/0".split("/");
long creationTimeSeconds = System.currentTimeMillis() / 1000;
SecureRandom secureRandom = SecureRandomUtils.secureRandom();
DeterministicSeed ds = new DeterministicSeed(secureRandom, 128, "", creationTimeSeconds);
return generateWalletByMnemonic(walletName, ds, pathArray, pwd);
}
/** * @param walletName 錢包名稱 * @param ds 助記詞加密種子 * @param pathArray 助記詞標準 * @param pwd 密碼 * @return */
@Nullable
public static ETHWallet generateWalletByMnemonic(String walletName, DeterministicSeed ds, String[] pathArray, String pwd) {
//種子
byte[] seedBytes = ds.getSeedBytes();
//助記詞
List<String> mnemonic = ds.getMnemonicCode();
if (seedBytes == null)
return null;
// 衍生推倒key
DeterministicKey dkKey = 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);
}
dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
}
ECKeyPair keyPair = ECKeyPair.create(dkKey.getPrivKeyBytes());
ETHWallet ethWallet = generateWallet(walletName, pwd, keyPair);
if (ethWallet != null) {
ethWallet.setMnemonic(convertMnemonicList(mnemonic));
}
return ethWallet;
}
@Nullable
private static ETHWallet generateWallet(String walletName, String pwd, ECKeyPair ecKeyPair) {
WalletFile keyStoreFile;
try {
keyStoreFile = Wallet.create(pwd, ecKeyPair, 1024, 1); // WalletUtils. .generateNewWalletFile();
} catch (Exception e) {
e.printStackTrace();
return null;
}
BigInteger publicKey = ecKeyPair.getPublicKey();
String s = publicKey.toString();
String wallet_dir = AppFilePath.Wallet_DIR;
String keystorePath = "keystore_" + walletName + ".json";
File destination = new File(wallet_dir, "keystore_" + walletName + ".json");
//目錄不存在則建立目錄,建立不了則報錯
if (!createParentDir(destination)) {
return null;
}
try {
objectMapper.writeValue(destination, keyStoreFile);
} catch (IOException e) {
e.printStackTrace();
return null;
}
ETHWallet ethWallet = new ETHWallet();
ethWallet.setName(walletName);
ethWallet.setAddress(Keys.toChecksumAddress(keyStoreFile.getAddress()));
ethWallet.setKeystorePath(destination.getAbsolutePath());
ethWallet.setPassword(Md5Utils.md5(pwd));
return ethWallet;
}
複製代碼
上述代碼中,generateMnemonic()
是入口函數,最終返回的是一個ETHWallet 自定義的錢包實體類,一個實例就對應一個錢包,ETHWallet保存了錢包相關的屬性,後面會詳細介紹,若是對它序列化保存錢包帳號及多個錢包帳號管理。
關於助記詞及私鑰的保存,有幾點要特別注意,不然有可能和其餘錢包沒法兼容或致使私鑰泄漏。
這部分做爲訂閱者福利,發表在個人小專欄,趁還未漲價,趕忙訂閱吧,超值的!