在区块链时代,以太坊钱包作为管理数字资产、与DApp交互的核心工具,其需求持续增长,开发一个安卓以太坊钱包不仅需要掌握传统移动应用开发技能,还需深入理解区块链原理、加密算法及安全实践,本文将从技术选型、核心功能实现、安全设计到测试发布,系统介绍开发安卓以太坊钱包的完整流程。

明确钱包类型与技术选型

钱包类型:托管式 vs 非托管式

  • 托管式钱包:私钥由服务器保管,用户依赖平台安全性(如交易所钱包),开发简单但中心化风险高。
  • 非托管式钱包:私钥本地存储,用户完全掌控资产(如MetaMask Mobile),安全性更高但需用户妥善保管助记词。

建议:优先开发非托管式钱包,符合区块链“去中心化”核心价值,也是主流钱包的选择。

技术栈选择

  • 开发语言:Kotlin(安卓官方推荐,语法简洁,生态完善)或 Java(传统选择,兼容性好)。
  • 钱包SDK
    • web3j:Java/Kotlin以太坊交互库,支持节点连接、合约调用、签名交易等核心功能。
    • ethers4j(轻量级替代):基于Ethers.js的Java实现,适合轻量化需求。
  • 加密库
    • Bouncy Castle:提供标准加密算法(如AES、SHA-256、ECDSA)。
    • Tink(Google):现代加密库,支持安全密钥管理,适合移动端。
  • UI框架:Jetpack Compose(声明式UI,开发效率高)或 XML + ViewModel(传统稳定方案)。
  • 依赖注入:Hilt(简化依赖管理,提升代码可维护性)。

核心功能实现

创建钱包:生成助记词与私钥

以太坊钱包遵循 BIP39(助记词生成)、BIP32(分层确定性钱包)、BIP44(多币种路径标准)规范。

步骤:

  • 生成随机熵:通过安全随机数生成器(如 SecureRandom)产生128位熵。
  • 生成助记词:使用 BIP39 的单词列表(如英语2048词),将熵转换为12/24个单词的助记词(例:witch collapse practice feed shame open despair creek road again ice lease)。
  • 生成种子:通过 PBKDF2 算法,用助记词 + 密钥(可选)生成512位种子(seed)。
  • 派生私钥:基于 BIP32/BIP44 路径(以太坊标准路径:m/44'/60'/0'/0/0),从种子生成主私钥,再逐层派生账户私钥。

代码示例(Kotlin + web3j)

import org.web3j.crypto.Bip32ECKeyPair
import org.web3j.crypto.MnemonicUtils
fun createWallet(): Pair<String, String> {
    // 1. 生成12位助记词
    val mnemonic = MnemonicUtils.generateMnemonic()
    // 2. 从助记词生成种子
    val seed = MnemonicUtils.generateSeed(mnemonic, "")
    // 3. 派生主私钥(BIP32)
    val masterKeyPair = Bip32ECKeyPair.generateKeyPair(seed)
    // 4. 派生以太坊账户私钥(路径:m/44'/60'/0'/0/0)
    val accountKeyPair = Bip32ECKeyPair.deriveKeyPair(masterKeyPair, intArrayOf(44, 60, 0, 0, 0))
    // 5. 从私钥生成地址
    val address = Numeric.prependHexPrefix(Keys.getAddress(accountKeyPair))
    return mnemonic to address
}

导入钱包:通过助记词/私钥恢复

用户需支持通过助记词、私钥或Keystore文件导入钱包,核心逻辑与创建钱包相反:

  • 助记词导入:通过助记词生成种子,再按BIP44路径派生私钥和地址。
  • 私钥导入:直接使用 ECKeyPair 从私钥字符串生成,再计算地址(需验证私钥格式合法性)。
  • Keystore导入:Keystore是加密后的私钥(JSON格式),需通过用户密码解密(使用Scrypt算法,需处理内存与计算开销)。

代码示例(助记词导入)

fun importWalletFromMnemonic(mnemonic: String): String {
    val seed = MnemonicUtils.generateSeed(mnemonic, "")
    val masterKeyPair = Bip32ECKeyPair.generateKeyPair(seed)
    val accountKeyPair = Bip32ECKeyPair.deriveKeyPair(masterKeyPair, intArrayOf(44, 60, 0, 0, 0))
    return Numeric.prependHexPrefix(Keys.getAddress(accountKeyPair))
}

连接以太坊节点:同步数据

钱包需与以太坊网络交互(查询余额、发送交易等),可通过两种方式连接节点:

  • 全节点:本地运行Geth/Parity节点,同步完整链数据,资源占用高但自主性强。
  • 轻节点/Infura:通过第三方服务(如Infura、Alchemy)连接API,适合移动端(无需同步全链,但依赖第三方服务)。

推荐:开发阶段使用Infura免费节点,生产阶段可支持用户自定义节点(如自建节点或第三方RPC)。

代码示例(web3j连接Infura)

val web3j = Web3j.build(HttpService("https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID"))
// 查询余额
val balance = web3j.ethGetBalance("0x...", DefaultBlockParameterName.LATEST).send().balance

资产管理:余额查询与交易

  • 余额查询:通过 eth_getBalance 接口获取地址的ETH余额(单位:Wei),转换为可读单位(如ETH)。
  • 代币余额:通过ERC-20合约的 balanceOf 方法查询代币余额,需解析ABI(应用二进制接口)。
  • 发送交易
    1. 构建交易对象(Transaction):包含目标地址、金额、GasLimit、GasPrice等。
    2. 签名交易:使用私钥对交易数据进行ECDSA签名(需处理RLP编码)。
    3. 发送交易:通过 eth_sendRawTransaction 广签名字节串到网络。

代码示例(发送ETH交易)

fun sendTransaction(
    privateKey: String,
    toAddress: String,
    amountInWei: BigInteger,
    gasPrice: BigInteger,
    gasLimit: BigInteger
): String {
    val credentials = Credentials.create(privateKey)
    val transaction = EthGetTransactionCount.createTransactionRequest()
        .from(credentials.address)
        .nonce(web3j.ethGetTransactionCount(credentials.address, DefaultBlockParameterName.LATEST).send().transactionCount)
        .to(toAddress)
        .value(amountInWei)
        .gasPrice(gasPrice)
        .gasLimit(gasLimit)
        .build()
    val signedTransaction = TransactionEncoder.signMessage(transaction, credentials)
    val hexValue = Numeric.toHexString(signedTransaction)
    return web3j.ethSendRawTransaction(hexValue).send().transactionHash
}

DApp浏览器:与去中心化应用交互

钱包需支持与网页DApp(如Uniswap、OpenSea)交互,核心功能是 注入Web3Provider(将钱包的签名、账户管理能力暴露给网页)。

  • 技术实现:通过安卓的 WebView 加载DApp页面,通过 JavaScriptInterface 将钱包功能(如 eth_requestAccountseth_sendTransaction)注入网页JS环境。
  • 安全注意:需验证DApp域名(防钓鱼),并明确提示用户交易风险。

安全设计:钱包的生命线

安卓钱包的核心是安全,需从以下层面防范风险:

私钥存储:本地加密与隔离

  • 禁止明文存储:私钥/助记词必须加密后存储,使用Android Keystore系统管理加密密钥(避免密钥被内存dump窃取)。
  • 加密方案:AES-256加密私钥,密钥由用户设置密码派生(通过PBKDF2,迭代次数建议≥10000)。
  • 随机配图