0.00000001聪(satoshi)(基本单位)
1 bitcoin (BTC) = 1000 millibitcoins (mBTC) = 1 million microbitcoins (uBTC) = 100 million Satoshi
根据三个模块的组合,可以分为全服务钱包、只签名钱包(离线钱包和硬件钱包)、只分发钱包。
BIP协议
BIP32
BIP32:定义了层级确定性钱包(hierarchical deterministic wallets),是一个系统可以从单一个 seed 产生一树状结构储存多组 keypairs(私钥和公钥)。好处是可以方便的备份、转移到其他相容装置(因为都只需要 seed),以及分层的权限控制等。
作用:
-
1、备份更容易。按照比特币的原则,尽量不要使用同一个地址,一个地址只使用一次,这样会导致频繁备份钱包。HD钱包只需要在创建时保存主密钥,通过主密钥可以派生出所有的子密钥。
-
2、私钥离线更安全。主私钥离线存储,主公钥在线使用,通过主公钥可以派生出所有的子公钥。例如:给每个商品提供一个收款地址。
-
3、利于管理,权限控制。树状结构类似于公司的组织架构,可以给各个部门指定一个密钥分支。
-
4、记账。只使用公钥即可记账。
BIP39
BIP39:将seed 用方便记忆和书写的单字表示。一般由 12 个单字组成,称为 mnemonic code(phrase),中文称为助记词或助记码。例如: average green proud remember advance trick estate oblige trouble when cube person
BIP43
BIP43对BIP32树结构增加了子索引标识purpose的拓展m/purpose’/*
BIP32的索引:m/0’/*
BIP44的索引:m/44’/*。
BIP44
BIP44:基于BIP32和BIP43,赋予树状结构中的各层特殊的意义。让同一个 seed 可以支援多币种、多帐户等。各层定义如下:
m / purpose’ / coin_type’ / account’ / change / address_index
- purporse’: 固定值44’, 代表是BIP44
- coin_type’: 这个代表的是币种, 可以兼容很多种币, 比如BTC是0’, ETH是60’, 例如:btc一般是 m/44’/0’/0’/0, eth一般是 m/44’/60’/0’/0
- account’:账号
- change’: 0表示外部链(External Chain),用户接收比特币,1表示内部链(Internal Chain),用于接收找零
- address_index:钱包索引
钱包最佳实践
- 使用助记词(BIP39)
- 使用层级确定性钱包(HD Wallets)(BIP32)
- 使用多目的HD Wallets(BIP43)
- 使用多币种,多账号的HD Wallets (BIP44)
比特币钱包地址创建过程
1、生成128bit~256bit作为私钥
2、通过secp256k1椭圆曲线算法得到私钥对应的公钥
3、将公钥进行SHA-256,得到公钥Hash
4、将3的结果进行RIMEMD-160
5、将4中结果添加1个字节版本号
6、将5中结果进行两次SHA-256,取前4个字节作为checksum
7、将6中结果添加到5中结果的末尾
8、将7中结果进行Base58,结果为比特币地址
BitcoinJ创建钱包
Bitcoinj是比特币协议Java版本实现的库。
添加依赖:
dependencies {
implementation ‘org.bitcoinj:bitcoinj-core:0.14.7’
implementation ‘org.slf4j:slf4j-api:1.7.25’
implementation ‘com.squareup.okhttp3:okhttp:3.10.0’
implementation ‘com.squareup.okhttp3:logging-interceptor:3.10.0’
implementation ‘com.google.zxing:core:3.3.3’//二维码
}
Android最大方法数的限制,60K 开启multiDexEnabled
android {
compileSdkVersion 28
defaultConfig {
multiDexEnabled true
}
}
dependencies {
implementation ‘com.android.support:multidex:1.0.3’
}
创建新钱包
File walletFile = activity.getFileStreamPath(“wallet-protobuf”);
//创建钱包
wallet = new Wallet(Constants.NETWORK_PARAMETERS);
//创建WalletFiles,设置自动保存Wallet
WalletFiles walletFiles = wallet.autosaveToFile(walletFile, 3 * 1000, TimeUnit.MILLISECONDS, null);
//立即保存
walletFiles.saveNow();
钱包创建源码分析:
-
Wallet
-
KeyChainGroup
-
DeterministicKeyChain
-
DeterministicSeed
protected DeterministicKeyChain(DeterministicSeed seed, @Nullable KeyCrypter crypter) {
this.seed = seed;
basicKeyChain = new BasicKeyChain(crypter);
if (!seed.isEncrypted()) {
rootKey = HDKeyDerivation.createMasterPrivateKey(checkNotNull(seed.getSeedBytes()));
rootKey.setCreationTimeSeconds(seed.getCreationTimeSeconds());
addToBasicChain(rootKey);
hierarchy = new DeterministicHierarchy(rootKey);
for (int i = 1; i <= getAccountPath().size(); i++) {
addToBasicChain(hierarchy.get(getAccountPath().subList(0, i), false, true));
}
initializeHierarchyUnencrypted(rootKey);
}
// Else…
// We can’t initialize ourselves with just an encrypted seed, so we expected deserialization code to do the
// rest of the setup (loading the root key).
}
获取钱包地址
Address address = wallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
address.toString();
在获取地址的过程中会调用RIMEMD-160算法处理公钥hash:
//Utils.java
public static byte[] sha256hash160(byte[] input) {
byte[] sha256 = Sha256Hash.hash(input);
RIPEMD160Digest digest = new RIPEMD160Digest();
digest.update(sha256, 0, sha256.length);
byte[] out = new byte[20];
digest.doFinal(out, 0);
return out;
}
处理公钥hash后会进行Base58算法:
//VersionedChecksummedBytes.java
public final String toBase58() {
// A stringified buffer is:
// 1 byte version + data bytes + 4 bytes check code (a truncated hash)
byte[] addressBytes = new byte[1 + bytes.length + 4];
addressBytes[0] = (byte) version;
System.arraycopy(bytes, 0, addressBytes, 1, bytes.length);
byte[] checksum = Sha256Hash.hashTwice(addressBytes, 0, bytes.length + 1);
System.arraycopy(checksum, 0, addressBytes, bytes.length + 1, 4);
return Base58.encode(addressBytes);
}
从文件中加载钱包
//读取钱包文件
File walletFile = activity.getFileStreamPath(“wallet-protobuf”);
if (walletFile.exists()) {
InputStream inputStream = new FileInputStream(walletFile);
//反序列化
wallet = new WalletProtobufSerializer().readWallet(inputStream);
//设置自动保存
wallet.autosaveToFile(walletFile, 3 * 1000, TimeUnit.MILLISECONDS, null);
//清理钱包
wallet.cleanup();
}
创建地址二维码
String s = BitcoinURI.convertToBitcoinURI(address, null, null, null);
Bitmap bitmap = Qr.bitmap(s);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
bitmapDrawable.setFilterBitmap(false);
mQrImageView.setImageDrawable(bitmapDrawable);
public static Bitmap bitmap(final String content) {
try {
final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.MARGIN, 0);
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
final BitMatrix result = QR_CODE_WRITER.encode(content, BarcodeFormat.QR_CODE, 0, 0, hints);
final int width = result.getWidth();
final int height = result.getHeight();
final byte[] pixels = new byte[width * height];
for (int y = 0; y < height; y++) {
final int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = (byte) (result.get(x, y) ? -1 : 0);
}
}
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(pixels));
return bitmap;
} catch (final WriterException x) {
log.info(“problem creating qr code”, x);
return null;
}
}
Bitcoin钱包收款和转账
比特币钱包余额需要统计所有钱包地址对应的UTXO
Simplified Payment Verification (SPV):节点无需下载所有的区块数据,而只需要加载所有区块头数据(block header的大小为80B),即可验证这笔交易是否曾经被比特币网络认证过。
布隆过滤器(Bloom Filter):过滤掉那些不包含有目标地址的交易信息,这一步能避免掉大量不相关的数据下载。
创建区块链
//创建区块链文件
File blockChainFile = new File(getDir(“blockstore”, Context.MODE_PRIVATE), “blockchain”);
//创建SPVBlockStore,管理区块数据
blockStore = new SPVBlockStore(Constants.NETWORK_PARAMETERS, blockChainFile);
//加载检查点
final InputStream checkpointsInputStream = getAssets().open(“checkpoints-testnet.txt”);
CheckpointManager.checkpoint(Constants.NETWORK_PARAMETERS, checkpointsInputStream,
blockStore, earliestKeyCreationTime);
//创建区块链对象
blockChain = new BlockChain(Constants.NETWORK_PARAMETERS, wallet, blockStore);
同步区块链
//添加网络权限:
private void startup() {
Log.d(TAG, "startup: ");
peerGroup = new PeerGroup(Constants.NETWORK_PARAMETERS, blockChain);
peerGroup.setDownloadTxDependencies(0); // recursive implementation causes StackOverflowError
peerGroup.addWallet(wallet);//设置钱包,重要
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_ACTIVITIES);
peerGroup.setUserAgent(USER_AGENT, packageInfo.versionName);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
peerGroup.setMaxConnections(8);
int connectTimeout = (int) (15 * DateUtils.SECOND_IN_MILLIS);
peerGroup.setConnectTimeoutMillis(connectTimeout);
int discoveryTimeout = (int) (10 * DateUtils.SECOND_IN_MILLIS);
peerGroup.addConnectedEventListener(mPeerConnectedEventListener);
peerGroup.addDisconnectedEventListener(mPeerDisconnectedEventListener);
peerGroup.addDiscoveredEventListener(mPeerDiscoveredEventListener);
peerGroup.setPeerDiscoveryTimeoutMillis(discoveryTimeout);
//添加节点探索器,重要
peerGroup.addPeerDiscovery(new PeerDiscovery() {
private final PeerDiscovery normalPeerDiscovery = MultiplexingDiscovery
.forServices(Constants.NETWORK_PARAMETERS, 0);
@Override
public InetSocketAddress[] getPeers(final long services, final long timeoutValue,
final TimeUnit timeoutUnit) throws PeerDiscoveryException {
return normalPeerDiscovery.getPeers(services, timeoutValue, timeoutUnit);
}
@Override
public void shutdown() {
normalPeerDiscovery.shutdown();
}
});
peerGroup.startAsync();
peerGroup.startBlockChainDownload(null);
}
比特币收款
获取测试用比特币:testnet.manu.backend.hamburg/faucet 刚收到的币可能需要几分钟后才能使用
//监听比特币接受事件
wallet.addCoinsReceivedEventListener(mWalletListener);
//刷新余额
Coin balance = wallet.getBalance(Wallet.BalanceType.ESTIMATED);
比特币转账
比特币测试链转账查询 创建一个Tx,对Tx进行签名,对Tx进行P2P网络广播
Address address = Address.fromBase58(Constants.NETWORK_PARAMETERS, to);
//转账金额,以mBTC为单位
Coin coin = MonetaryFormat.MBTC.parse(amount);
//创建请求
SendRequest sendRequest = SendRequest.to(address, coin);
try {
//创建Transaction
Transaction transaction = wallet.sendCoinsOffline(sendRequest);
//通过P2P广播
BlockChainService.broadcastTransaction(BitcoinWalletActivity.this, transaction);
} catch (InsufficientMoneyException e) {
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
public static void broadcastTransaction(Context context, Transaction transaction) {
Intent intent = new Intent(ACTION_BROADCAST_TRANSACTION, null, context, BlockChainService.class);
intent.putExtra(ACTION_BROADCAST_TRANSACTION_HASH, transaction.getHash().getBytes());
context.startService(intent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: ");
if (intent != null) {
byte[] txHash = intent.getByteArrayExtra(“tx”);
if (txHash != null) {
Sha256Hash sha256Hash = Sha256Hash.wrap(txHash);
Transaction transaction = BitcoinWalletManager.getInstance().getWallet().getTransaction(sha256Hash);
peerGroup.broadcastTransaction(transaction);
Log.d(TAG, "onStartCommand: " + sha256Hash.toString());
}
}
return super.onStartCommand(intent, flags, startId);
}
以太坊钱包
以太坊钱包功能与比特币钱包功能类似,获取用户余额,管理地址和密钥,转账、智能合约调用。以太坊钱包一般不用在本地维护区块链数据,只需要使用JSON-RPC访问
钱包文件
KeyStore = 私钥 + 密码
如果使用ImToken创建钱包,创建了助记词,密码用来加密钱包地址对应的子私钥,加密的结果就是Keystore.
{
“address”: “001d3f1ef827552ae1114027bd3ecf1f086ba0f9”,
“crypto”: {
“cipher”: “aes-128-ctr”,
“ciphertext”: “233a9f4d236ed0c13394b504b6da5df02587c8bf1ad8946f6f2b58f055507ece”,
“cipherparams”: {
“iv”: “d10c6ec5bae81b6cb9144de81037fa15”
},
“kdf”: “scrypt”,
“kdfparams”: {
“dklen”: 32,
“n”: 262144,
“p”: 1,
“r”: 8,
“salt”: “99d37a47c7c9429c66976f643f386a61b78b97f3246adca89abe4245d2788407”
},
“mac”: “594c8df1c8ee0ded8255a50caf07e8c12061fd859f4b7c76ab704b17c957e842”
},
“id”: “4fcb2ba4-ccdb-424f-89d5-26cce304bf9c”,
“version”: 3
}
以太坊钱包地址创建过程
1、使用Secp256k1创建公私钥
2、通过Keccak算法得到公钥Hash值,进而得到长度为40的地址字符串
3、一般的,会在地址字符串签名加前缀"0x"
Web3j创建钱包
添加Web3j依赖
implementation ‘org.web3j:core:3.3.1-android’
创建新钱包
这里不涉及BIP协议,为非确定性钱包
Wallet.createStandard() 出现OOM, Out of Memory juejin.im/post/684490…
File walletDir = contextWrapper.getDir(“eth”, Context.MODE_PRIVATE);
//生成密钥对
ECKeyPair ecKeyPair = Keys.createEcKeyPair();
//WalletFile = KeyStore
WalletFile wallet = Wallet.createLight(PASSWORD, ecKeyPair);
String walletFileName = getWalletFileName(wallet);
File destination = new File(walletDir, walletFileName);
objectMapper.writeValue(destination, wallet);
加载钱包文件
File[] files = walletDir.listFiles();
wallet = objectMapper.readValue(files[0], WalletFile.class);
通过助记词创建钱包
涉及BIP协议,但没有遵循bitcoin地址只使用一次的原则,钱包一般只使用派生出来第一个地址
可通过工具检查派生的地址是否正确
//创建助记词
public List createMnemonics() throws MnemonicException.MnemonicLengthException {
SecureRandom secureRandom = new SecureRandom();
byte[] entropy = new byte[DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8];
secureRandom.nextBytes(entropy);
return MnemonicCode.INSTANCE.toMnemonic(entropy);
}
//m / 44’ / 60’ / 0’ / 0
//Hardened意思就是派生加固,防止获取到一个子私钥之后可以派生出后面的子私钥
//必须还有上一级的父私钥才能派生
public static final ImmutableList BIP44_ETH_ACCOUNT_ZERO_PATH =
ImmutableList.of(new ChildNumber(44, true), new ChildNumber(60, true),
ChildNumber.ZERO_HARDENED, ChildNumber.ZERO);
//通过助记词生成HD钱包
public void onCreateWallet(View view) {
byte[] seed = MnemonicCode.toSeed(words, “”);
DeterministicKey masterPrivateKey = HDKeyDerivation.createMasterPrivateKey(seed);
DeterministicHierarchy deterministicHierarchy = new DeterministicHierarchy(masterPrivateKey);
// m / 44’ / 60’ / 0’ / 0 / 0
DeterministicKey deterministicKey = deterministicHierarchy
.deriveChild(BIP44_ETH_ACCOUNT_ZERO_PATH, false, true, new ChildNumber(0));
byte[] bytes = deterministicKey.getPrivKeyBytes();
ECKeyPair keyPair = ECKeyPair.create(bytes);
try {
WalletFile walletFile = Wallet.createLight(PASSWORD, keyPair);
String address = walletFile.getAddress();
mAddress.setText(“0x” + address);
} catch (CipherException e) {
e.printStackTrace();
}
}
导出钱包
导出KeyStore
public String exportKeyStore(WalletFile wallet) {
try {
return objectMapper.writeValueAsString(wallet);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
导出私钥
public String exportPrivateKey(WalletFile wallet) {
try {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

如何做好面试突击,规划学习方向?
面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。
学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。
同时我还搜集整理2020年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。
在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。
学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。
同时我还搜集整理2020年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。
[外链图片转存中…(img-8LxW8pJL-1713526788745)]
在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。
[外链图片转存中…(img-xZ6sbU84-1713526788746)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!