一、钱包核心架构设计
分层实现方案:
- 安全层:密钥存储与加密模块
- 业务层:交易处理与区块链交互
- 网络层:节点通信与API管理
- UI层:用户界面与交互
二、完整代码实现
1. 安全模块:助记词与密钥管理
// MnemonicManager.kt
object MnemonicManager {
private const val ENTROPY_BITS = 128 // 对应12个助记词
fun generateMnemonic(): List<String> {
val entropy = ByteArray(ENTROPY_BITS / 8).apply {
SecureRandom().nextBytes(this)
}
return MnemonicCode().toMnemonic(entropy)
}
fun validateMnemonic(mnemonic: List<String>): Boolean {
return try {
MnemonicCode().check(mnemonic)
true
} catch (e: MnemonicException) {
false
}
}
}
// KeyStorage.kt
class KeyStorage(context: Context) {
private val androidKeyStore = AndroidKeyStore.getInstance()
private val sharedPrefs = context.getSharedPreferences("wallet_prefs", MODE_PRIVATE)
fun encryptAndSaveMnemonic(mnemonic: List<String>, password: String) {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val key = generateKey(password)
cipher.init(Cipher.ENCRYPT_MODE, key)
val encrypted = cipher.doFinal(mnemonic.joinToString(" ").toByteArray())
val iv = cipher.iv
sharedPrefs.edit {
putString("encrypted_mnemonic", Base64.encodeToString(encrypted, Base64.DEFAULT))
putString("iv", Base64.encodeToString(iv, Base64.DEFAULT))
}
}
private fun generateKey(password: String): SecretKey {
val keySpec = PBEKeySpec(
password.toCharArray(),
"salt_value".toByteArray(),
10000,
256
)
return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
.generateSecret(keySpec)
}
}
2. 钱包核心逻辑实现
// EthereumWallet.kt
class EthereumWallet(private val context: Context) {
private val web3j: Web3j by lazy {
Web3j.build(HttpService("https://mainnet.infura.io/v3/YOUR_API_KEY"))
}
suspend fun createWallet(password: String): WalletInfo {
return withContext(Dispatchers.IO) {
// 生成助记词
val mnemonic = MnemonicManager.generateMnemonic()
// 生成确定性钱包
val seed = DeterministicSeed(mnemonic, null, "", System.currentTimeMillis())
val masterKey = HDKeyDerivation.createMasterPrivateKey(seed.seedBytes)
// 根据BIP44路径派生
val path = "m/44'/60'/0'/0/0"
val derivedKey = derivePath(path, masterKey)
// 创建凭证
val credentials = Credentials.create(
Numeric.toHexStringNoPrefix(derivedKey.privKeyBytes)
)
// 加密存储
KeyStorage(context).encryptAndSaveMnemonic(mnemonic, password)
WalletInfo(
address = credentials.address,
publicKey = Numeric.toHexStringNoPrefix(credentials.ecKeyPair.publicKey.toByteArray()),
mnemonic = mnemonic
)
}
}
private fun derivePath(path: String, masterKey: DeterministicKey): DeterministicKey {
val parts = path.split("/").drop(1)
var key = masterKey
for (segment in parts) {
val hardened = segment.endsWith("'")
val index = segment.replace("'", "").toInt()
key = HDKeyDerivation.deriveChildKey(key,
if (hardened) ChildNumber(index, true)
else ChildNumber(index, false)
)
}
return key
}
data class WalletInfo(
val address: String,
val publicKey: String,
val mnemonic: List<String>
)
}
3. 交易处理模块
// TransactionManager.kt
class TransactionManager(
private val web3j: Web3j,
private val credentials: Credentials
) {
private val gasEstimator = GasEstimator(web3j)
suspend fun sendEther(
toAddress: String,
amountInEther: Double
): TransactionResult {
return withContext(Dispatchers.IO) {
// 参数验证
if (!WalletUtils.isValidAddress(toAddress)) {
throw IllegalArgumentException("Invalid recipient address")
}
// 单位转换
val value = Convert.toWei(amountInEther.toString(), Convert.Unit.ETHER).toBigInteger()
// 获取nonce
val nonce = web3j.ethGetTransactionCount(
credentials.address,
DefaultBlockParameterName.PENDING
).send().transactionCount
// 估算Gas
val gasParams = gasEstimator.estimateGas()
// 构造原始交易
val rawTx = RawTransaction.createEtherTransaction(
nonce,
gasParams.gasPrice,
gasParams.gasLimit,
toAddress,
value
)
// 签名交易
val signedMessage = TransactionEncoder.signMessage(rawTx, credentials)
val hexValue = Numeric.toHexString(signedMessage)
// 广播交易
val txHash = web3j.ethSendRawTransaction(hexValue).send().transactionHash
TransactionResult(
txHash = txHash,
status = TransactionStatus.PENDING
)
}
}
data class TransactionResult(
val txHash: String,
val status: TransactionStatus
)
enum class TransactionStatus { PENDING, CONFIRMED, FAILED }
}
// GasEstimator.kt
class GasEstimator(private val web3j: Web3j) {
suspend fun estimateGas(): GasParams {
return try {
val gasPrice = web3j.ethGasPrice().send().gasPrice
val gasLimit = BigInteger.valueOf(21_000) // 简单转账的基础Gas
GasParams(gasPrice, gasLimit)
} catch (e: Exception) {
// 异常处理逻辑
GasParams.default()
}
}
data class GasParams(
val gasPrice: BigInteger,
val gasLimit: BigInteger
) {
companion object {
fun default() = GasParams(
BigInteger.valueOf(20_000_000_000L), // 20 Gwei
BigInteger.valueOf(21_000)
)
}
}
}
三、Android UI集成示例
1. 创建钱包界面
// CreateWalletFragment.kt
class CreateWalletFragment : Fragment() {
private val viewModel: WalletViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnCreateWallet.setOnClickListener {
val password = etPassword.text.toString()
if (password.length < 8) {
showError("密码至少需要8个字符")
return@setOnClickListener
}
viewModel.createWallet(password).observe(viewLifecycleOwner) { state ->
when (state) {
is WalletState.Loading -> showProgress()
is WalletState.Success -> showWalletInfo(state.walletInfo)
is WalletState.Error -> showError(state.message)
}
}
}
}
private fun showWalletInfo(info: WalletInfo) {
// 显示地址和助记词警告信息
}
}
// WalletViewModel.kt
class WalletViewModel : ViewModel() {
private val _walletState = MutableLiveData<WalletState>()
val walletState: LiveData<WalletState> = _walletState
fun createWallet(password: String) {
viewModelScope.launch {
_walletState.value = WalletState.Loading
try {
val wallet = EthereumWallet(context).createWallet(password)
_walletState.value = WalletState.Success(wallet)
} catch (e: Exception) {
_walletState.value = WalletState.Error("创建钱包失败: ${e.message}")
}
}
}
sealed class WalletState {
object Loading : WalletState()
data class Success(val walletInfo: WalletInfo) : WalletState()
data class Error(val message: String) : WalletState()
}
}
2. 交易确认对话框
class TransactionDialog : DialogFragment() {
fun showTransactionDetails(txData: TxData) {
// 显示交易详细信息
binding.tvToAddress.text = txData.toAddress
binding.tvAmount.text = "${txData.amount} ETH"
binding.tvFee.text = "矿工费: ${txData.fee} ETH"
binding.btnConfirm.setOnClickListener {
viewModel.sendTransaction(txData).observe(this) { result ->
when (result) {
is TransactionResult.Success -> showTxHash(result.txHash)
is TransactionResult.Error -> showError(result.message)
}
}
}
}
}
四、安全增强实现
1. 生物特征认证集成
class BiometricAuthHelper(
private val context: Context,
private val cryptoObject: BiometricPrompt.CryptoObject
) {
fun authenticate(callback: (Boolean) -> Unit) {
val executor = ContextCompat.getMainExecutor(context)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("钱包认证")
.setSubtitle("使用生物特征访问钱包")
.setNegativeButtonText("取消")
.build()
val biometricPrompt = BiometricPrompt(context as FragmentActivity, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
callback(true)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
callback(false)
}
})
biometricPrompt.authenticate(promptInfo, cryptoObject)
}
}
2. 安全键盘实现
<!-- secure_keyboard.xml -->
<androidx.gridlayout.widget.GridLayout
android:id="@+id/keyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:columnCount="3">
<Button
android:id="@+id/btn1"
android:text="1"
style="@style/KeyboardButton"
android:inputType="none"/>
<!-- 其他数字按钮... -->
<Button
android:id="@+id/btnDelete"
android:text="←"
style="@style/KeyboardButton"
android:background="?selectableItemBackgroundBorderless"/>
</androidx.gridlayout.widget.GridLayout>
class SecureKeyboard(context: Context, attrs: AttributeSet) : GridLayout(context, attrs) {
fun setupWithPinEntry(pinEntry: EditText) {
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child is Button) {
child.setOnClickListener {
when (child.id) {
R.id.btnDelete -> {
val text = pinEntry.text
if (text.isNotEmpty()) {
pinEntry.setText(text.substring(0, text.length - 1))
}
}
else -> {
pinEntry.append(child.text)
}
}
}
}
}
}
}
五、测试方案
1. 单元测试示例
@RunWith(AndroidJUnit4::class)
class WalletUnitTest {
private val testContext = InstrumentationRegistry.getInstrumentation().targetContext
@Test
fun testMnemonicGeneration() {
val mnemonic = MnemonicManager.generateMnemonic()
assertEquals(12, mnemonic.size)
assertTrue(MnemonicManager.validateMnemonic(mnemonic))
}
@Test
fun testKeyDerivation() {
val testMnemonic = listOf(
"abandon", "ability", "able", "about", "above", "absent",
"absorb", "abstract", "absurd", "abuse", "access", "accident"
)
val wallet = EthereumWallet(testContext)
val info = runBlocking { wallet.createWallet("test1234") }
assertEquals("0x9858Ef...", info.address.substring(0, 10))
}
}
2. UI测试示例
@RunWith(AndroidJUnit4::class)
class WalletUiTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun testCreateWalletFlow() {
onView(withId(R.id.etPassword)).perform(typeText("StrongPass123!"))
onView(withId(R.id.btnCreate)).perform(click())
// 验证进度条显示
onView(withId(R.id.progressBar))
.check(matches(isDisplayed()))
// 验证结果展示
onView(withId(R.id.tvAddress))
.check(matches(withText(startsWith("0x"))))
}
}
六、部署与优化
1. ProGuard规则示例
# 保留web3j相关类
-keep class org.web3j.** { *; }
-keep class org.bouncycastle.** { *; }
# 保留钱包核心类
-keep class com.example.wallet.core.** { *; }
# 保留数据模型
-keep class com.example.wallet.model.** { *; }
2. 性能优化建议
- 使用缓存策略管理区块链数据
- 实现交易历史的分页加载
- 使用WorkManager处理后台同步
- 优化Gas Price预测算法
七、完整项目结构
app/
├── src/
│ ├── main/
│ │ ├── java/com/example/wallet/
│ │ │ ├── core/ # 核心钱包逻辑
│ │ │ ├── security/ # 安全模块
│ │ │ ├── network/ # 网络通信
│ │ │ ├── ui/ # 界面相关
│ │ │ └── di/ # 依赖注入
│ │ └── res/
│ │ ├── layout/
│ │ └── values/
│ └── test/ # 单元测试
└── build.gradle
八、后续开发路线
-
多链支持扩展
enum class BlockchainNetwork( val rpcUrl: String, val chainId: Int ) { ETH_MAINNET( "https://mainnet.infura.io/v3/", 1 ), BSC_MAINNET( "https://bsc-dataseed.binance.org/", 56 ) } -
智能合约交互
suspend fun callContract( contractAddress: String, function: Function, value: BigInteger ) { val contract = SmartContract.load( contractAddress, web3j, credentials, Contract.GAS_PRICE, Contract.GAS_LIMIT ) val result = contract.execute(function).sendAsync().get() }
通过以上完整实现,开发者可以构建一个具备基础功能的区块链钱包应用。在实际生产环境中,还需结合具体业务需求添加以下功能:
- 多语言国际化支持
- 交易加速/取消功能
- 市场行情集成
- 智能合约模板库
- 合规性检查模块 安全考虑,实际开发请勿直接使用示例密钥