Java版 Bitcoin
bitcoinj支持java版和JavaScript版,本文讲解的是java版的相关细节。本文参考bitcoinj官方文档
1.基本结构
bitcoinj主要使用的对象
- NetworkParameters 选择生产或测试环境网络
- Wallet 用于存储ECKey以及其他数据
- PeerGroup管理网络链接
- BLockChain管理共享的、全局的数据结构
- BlockStore保存区块链上的数据结构,如光盘一样
- WalletEventListener 接收钱包事件
- WalletAppKit可以方便的创建和使用上面几个对象
2.安装
设置日志系统
1 | BriefLogFormatter.init(); |
选择网络
- main或者“production”网络用与人们买卖交易
- public test network(testnet)用于测试实验,可以研究新特性
- 回归测试模式(regtest)不是一个共有网络,需要自己启动daemon
1 | //Figure out which network we should connect to. Each one gets its own set of files. |
每一个网络都拥有起始的区块、独立的端口号和地址前缀。这些都封装在NetworkParameters
单例对象中,使用时调用call()
方法。值得一提的是,在testnet
中可以免费从TestNet Faucet获取大量的币。在regtest模式中不存在公有的设施,我们可以使用bitcoind -regtest setgenerate true
产生新的区块。
3.密钥和地址
比特币交易通常是将钱发送到一个由椭圆曲线生成的公钥。发送者生成一个交易,交易中包含有接受者的地址。接受者的地址是由公钥经过hash得到的。关于私钥、公钥、地址的关系我总结如下:
- k私钥(CSPRNG随机产生,256bit二进制数,64位16进制表示)
- K公钥(私钥 通过椭圆曲线相乘产生,20字节160bit)
- 公钥哈希(公钥通过HSA256和RIPEMD160处理得到,20字节160bit)
- 比特币地址(公钥哈希通过Base58check编码得到)
椭圆曲线算法可以从私钥计算得到公钥,这是不可逆的过程。K=k*G,其中k是私钥,G是被称之为生成点的常数点。K是所得公钥,不可以从公钥计算得到私钥,此运算被称为“寻找离散对数”问题,难以解决。
密码学公式:
\(y^2 mod p=(x^3+7))mod p\)
上述的mod p(素数p取模),表明该曲线是在素数阶p的有限域内也写作 Fp,其中 \(p=2^{256}-2^{32}-2^9-2^8-2^7-2^6-2^4-1\) (这是一个非常大的素数)
4.Wallet App 工具
bitcionj有很多不同的层组成,每层的级别都低于后一层。在一个典型的操作场景中,一方想发送金额给另一方时至少需要使用到BlockChain
、BlockStore
、PeerGroup
和Wallet
。关于这几个对象间如何正确的连接并交换数据的内容,下一篇文章再详述。
往往有很多模板可以简化这个过程,bitcoinj提供了一个高级别的封装类名为WalletAppKit
。它设置bitcoinj为SPV模式(与之相对的是全校验模式),可以提供一些简单的属性和默认配置的修改。
1 | // Start up a basic app using a class that automates some boilerplate. Ensure we always have at least one key. |
这个工具需要传入三个参数
NetworkParameters
几乎所有的API都会需要- 文件存储目录
- 文件存储前缀(选填)
我们可以重写onSetupCompleted
方法,加入自己的代码。bitcoinj会为他起一个线程后台运行。接着检查钱包是否至少拥有一个key,如果没有就刷新一个新的key。下一步检查是否使用了regtest模式,如果是那么链接localhost。最后调用kit.startAsync()
。
5.处理事件
bitcoinj中的事件监听和我们平时大多数使用的监听一样,对象需要实现一个如下几个接口:
WalletEventListener
钱包中发生的事件BlockChainListener
区块链相关的事件PeerEventListener
关于网络中节点的事件TransactionConfidence.Listener
交易安全回滚级别相关的事件
对于大部分的应用来说不需要使用全部这些接口,因为每个接口中都定义了很多相关的事件。使用的时候我们只需实现抽象接口即可。Bitcoinj中后台的user thread
专门负责运行事件监听。
1 | kit.wallet().addEventListener(new AbstractWalletEventListener() { |
6.接收币
设置监听,实现onCoinReceived
方法并传入四个参数,示例中打印出收到的金额,设置Future回调
1 | kit.wallet().addEventListener(new AbstractWalletEventListener() { |
因为比特币是一个需要全球交易顺序达成一致的全球共识系统,所以每个交易都需要有一个置信对象(confidence object)。置信对象需要处理成功和失败两种情况,因为很可能我们自己认为的已经收到了金额,但是稍后发现全球其他人并不认同。我们可以使用置信对象包括的数据做出风险决策、计算我们实际收到钱的可能性、了解共识变化。
Futures是并发编程的一个重要概念,在bitcoinj中大量使用。bitcoinj使用Guava扩展了标准的JavaFuture
类,生成ListenableFuture
。ListenableFuture
可以获取一些future的计算和状态,可以等待执行结束也可以注册回调。Futures失败的时候会收到一个异常。
当交易被确认后调用forwardCoins
7.发送币
1 | Coin value = tx.getValueSentToMe(kit.wallet()); |
首先我们查询收到了多少金额(同样也可以通过onCoinsReceived
中的newBalance
获得),接着确定要发送的金额等同于收到的金额,设置交易手续费为最低,这样可能会延长确认时间。
最后调用sendCoins
,该方法会返回一个SendResult
对象其中包括创建的交易和ListenableFuture
。
交易手续费一方面可以用来防止拒绝服务另一方面可以用来激励矿工。用户可以自定义每笔交易的手续费。
1 | SendRequest req = SendRequest.to(address, value); |