乐趣区

如何开发一款以太坊(安卓)钱包系列2 – 导入账号及账号管理

这是如何开发一款以太坊(安卓)钱包系列第 2 篇,如何导入账号。有时用户可能已经有一个账号,这篇文章接来介绍下,如何实现导入用户已经存在的账号。
导入账号预备知识
从用户需求上来讲,导入用户已经存在的账号是有必要的。不过从安全性考虑,当你之前使用的是一个非官方、非开源的钱包产品时(尤其是小众钱包),或者之前没有对私钥、助记词、Keysotre 文件小心保存时。正确的做法是提示用户:

在新的钱包重新创建一个钱包账号、并安全备份(因为之前的可能已经不安全);
然后在老钱包里把所有的币转移到新账号。

导入账号有 3 种方式:

通过私钥导入
通过 KeyStore 导入
通过助记词导入

通过私钥导入账号
关键是用用户输入的私钥创建一个椭圆曲线秘钥对,然后用这个秘钥对创建钱包,代码如下:(代码在代码库中的 app/src/pro/upchain/wallet/utils/ETHWalletUtils.java 文件中)
public static ETHWallet loadWalletByPrivateKey(String privateKey, String pwd) {
Credentials credentials = null;
ECKeyPair ecKeyPair = ECKeyPair.create(Numeric.toBigInt(privateKey));
return generateWallet(generateNewWalletName(), pwd, ecKeyPair);
}
返回语句中的 generateWallet(),在系列 1 - 通过助记词创建账号 已经介绍过,通过椭圆曲线秘钥对创建钱包。
loadWalletByPrivateKey() 中第 2 个参数密码 pwd,在私钥生成账号这个过程并不需要 pwd,它是用来加密保存私钥,即为了生成 keystore 文件。
通过 KeyStore 文件导入账号
关于 KeyStore 文件,不了解的可以阅读下账号 Keystore 文件导入导出。
关键步骤:

KeyStore 文本内容解析 WalletFile 实例;
使用密码 解码 WalletFile 生成椭圆曲线秘钥对创建钱包。

/**

* @param keystore 原 json 文件内容
* @param pwd keystore 解密密码
* @return
*/
public static ETHWallet loadWalletByKeystore(String keystore, String pwd) {
try {
WalletFile walletFile = null;
walletFile = objectMapper.readValue(keystore, WalletFile.class);
return generateWallet(generateNewWalletName(), pwd, Wallet.decrypt(pwd, walletFile));
} catch (IOException e) {
} catch (CipherException e) {
}
return null;
}
通过助记词导入账号
导入和上一篇中,创建非常相似,不同的是,种子由用户提供的助记词生成。
使用助记词导入账号时,还需要用户选择(或输入)一个推倒路径 (参考 BIP44),关键步骤是:

通过助记词创建随机数种子;
通过 种子 + 路径 派生生成私钥 创建钱包;

/**
* 通过导入助记词,导入钱包
*
* @param path bip44 路径
* @param list 助记词
* @param pwd 密码
* @return
*/
public static ETHWallet importMnemonic(String path, String mnemonic, String pwd) {
List<String> list = Arrays.asList(mnemonic.split(” “));
if (!path.startsWith(“m”) && !path.startsWith(“M”)) {
// 参数非法
return null;
}
String[] pathArray = path.split(“/”);
if (pathArray.length <= 1) {
// 内容不对
return null;
}
String passphrase = “”;
long creationTimeSeconds = System.currentTimeMillis() / 1000;
DeterministicSeed ds = new DeterministicSeed(list, null, passphrase, creationTimeSeconds);
return generateWalletByMnemonic(generateNewWalletName(), ds, pathArray, pwd);
}

generateWalletByMnemonic 在上一篇中已经介绍过,
账号存储(保存到数据库)
很多同学肯定已经注意到,不管通过什么方式构造的账号,都会最终构造为一个 ETHWallet 钱包对象,他的定义如下:
@Entity
public class ETHWallet {

@Id(autoincrement = true)
private Long id;

public String address;
private String name;
private String password; // 经过加密后的 pwd
private String keystorePath;
private String mnemonic;
private boolean isCurrent; // 是否是当前选中的钱包
private boolean isBackup; // 是否备份过
}
前面构造的 ETHWallet 是只存在于内容之中,在应用程序退出之后,这个数据将丢失,因此我们需要把它序列化到序列化数据库中存储起来,在下一次进入应用的时候加载数据库还原出账号。
greenDAO
greenDAO 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案,以下是一个 greenDAO 的作用示意图:

这里我们也使用了 greenDAO 来把 ETHWallet 对象映射到 SQLite 数据库,greenDAO 的用法这里只简单说明,不详细阐述,大家可以跟随官方提供的 introduction 和 how-to-get-started。
对象映射保存
把 ETHWallet 映射的到数据库,需要给类加上 @Entity 注解,这样 greenDAO 会生成几个类:DaoMaster、DaoSession 及 ETHWalletDao 帮我们完成构建数据库表等操作。
在使用 ETHWalletDao 插入到数据库之前需要先进行一个初始化,通常初始化放在应用程序入口中进行,如:pro.upchain.wallet.UpChainWalletApp 的 onCreate() 中执行,初始化代码如下:
protected void init() {
DaoMaster.DevOpenHelper mHelper = new DaoMaster.DevOpenHelper(this, “wallet”, null);
SQLiteDatabase db = mHelper.getWritableDatabase();
DaoSession daoSession = new DaoMaster(db).newSession();
ETHWalletDao ethWalletDao = daoSession.getETHWalletDao();
}
有了 greenDAO 为我们生成的辅助类,插入到数据库就很简单了,一行代码:
ethWalletDao.insert(ethWallet); //
ethWallet 为 ETHWallet 实例,前面不管是新创建还是导入的账号都会构造这样一个实例。
多账号管理
考虑到用户可能会创建多个账号,因此需要确定一个当前选定的账号,一般情况下,用户新创建的账号应该作为当前选中的的账号,同时其他账号应该取消选中,我们完善下账号存储逻辑,如下:(代码在代码库中的 app/src/pro/upchain/wallet/utils/WalletDaoUtils.java 文件中)

/**
* 插入新创建钱包
*
* @param ethWallet 钱
*/
public static void insertNewWallet(ETHWallet ethWallet) {
updateCurrent(-1); // 取消其他站账号选中状态
ethWallet.setCurrent(true);
ethWalletDao.insert(ethWallet);
}

/**
* 更新选中钱包
*
* @param id 钱包 ID
*/
public static ETHWallet updateCurrent(long id) {
// 加载所有钱包账号
List<ETHWallet> ethWallets = ethWalletDao.loadAll();
ETHWallet currentWallet = null;
for (ETHWallet ethwallet : ethWallets) {
if (id != -1 && ethwallet.getId() == id) {
ethwallet.setCurrent(true);
currentWallet = ethwallet;
} else {
ethwallet.setCurrent(false);
}
ethWalletDao.update(ethwallet);
}
return currentWallet;
}
打通账号创建与保存
以通过私钥导入账号进行保存为例,把创建账号和保存账号打通,这里我们使用响应式编程 ReactiveX,这部分作为订阅者福利,发表在我的小专栏,趁还未涨价,赶紧订阅吧,超值的!
学习资料

RxAndroid 了解更多响应式编程

introduction 和 how-to-get-started 了解 greenDAO。

我创建了一个专门讨论钱包开发的微信群,加微信:xlbxiong 备注:钱包。
加入知识星球,和一群优秀的区块链从业者一起学习。深入浅出区块链 – 系统学习区块链,打造最好的区块链技术博客。
<!–
(代码在代码库中的 app/src/pro/upchain/wallet/interact/CreateWalletInteract.java 中)
java
public Single<ETHWallet> loadWalletByPrivateKey(final String privateKey, final String pwd) {
return Single.fromCallable(() -> {
ETHWallet ethWallet = ETHWalletUtils.loadWalletByPrivateKey(privateKey, pwd);
if (ethWallet != null) {
WalletDaoUtils.insertNewWallet(ethWallet);
}
return ethWallet;
}
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());

}

这个方法使用响应式编程方法,返回了一个可订阅的 Single<ETHWallet> 对象。
响应式编程 ReactiveX 提供了一个清晰、准确处理异步问题和事件的方法。RxJava 是一个 ReactiveX 在 JVM 上的实现,在 Android 应用开发中使用 RxJava 为 RxAndroid。
补充下为什么要用响应式编程方法,因为加解密及数据库(磁盘 IO)操作都是耗时操作,不能放在主线程中执行(会造成 UI 卡顿),传统的做法是用 AsyncTask 在 doInBackground 执行耗时操作,而使用 ReactiveX 代码将简洁很多。
代码中使用 subscribeOn 指定被观察者在单独 的 io 子线程运行(Schedulers.io()),observeOn 指定观察者运行在 AndroidSchedulers.mainThread() 主线程(UI)线程,这样观察者在订阅收到 ETHWallet 对象后,就可以 UI 展示等操作。
提示大家阅读本文时,最好把代码库克隆到本地对照阅读。–>

退出移动版