在区块链时代,以太坊钱包作为管理数字资产、与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(应用二进制接口)。 - 发送交易:
- 构建交易对象(
Transaction):包含目标地址、金额、GasLimit、GasPrice等。 - 签名交易:使用私钥对交易数据进行ECDSA签名(需处理RLP编码)。
- 发送交易:通过
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_requestAccounts、eth_sendTransaction)注入网页JS环境。 - 安全注意:需验证DApp域名(防钓鱼),并明确提示用户交易风险。
安全设计:钱包的生命线
安卓钱包的核心是安全,需从以下层面防范风险:
私钥存储:本地加密与隔离
- 禁止明文存储:私钥/助记词必须加密后存储,使用Android Keystore系统管理加密密钥(避免密钥被内存dump窃取)。
- 加密方案:AES-256加密私钥,密钥由用户设置密码派生(通过PBKDF2,迭代次数建议≥10000)。
