回目录 《项目实战:比特币充提款集成》

最近做了一个比特币充提款试验性质的功能,作为一个技术爱好者,之前并没有这方面的经验,网上也很难找到比较合适的资料,所以自己摸索着做了个demo并写下本文,思路可能不够成熟,到真正的生产环境还需要有更多的考虑和设计,比如关于手续费的考虑,希望有相关经验的大牛给与指点和指正错误之处。

目标: 假设某网站提供会员充提款功能,用户通过传统网银收银台或者微信支付宝充钱进来,转成平台币或者平台积分或者单纯的对应法币余额,然后可以享受更好的折扣和奖励,当然平台也允许用户提款。现在目标是增加一个比特币的充提款渠道,具体功能如下:

# 1.1 功能要求

  1. 允许用户通过比特币充值,用户转入比特币,平台自动转成平台支持的法币余额或者积分。

  2. 允许用户提款平台余额,平台转成对应的BTC数量并转入用户比特币钱包

# 1.2 设计考虑

  1. 系统如何在为用户生成比特币地址的时候可以不暴露私钥?

采用BIP32 Hierarchical deterministic key即分级确定性密钥来创建, 我们可以通过父节点的扩展公钥生成子节点的比特币地址,所以作为一个网站直接跟外部网络交互,可以基本不用担心安全问题,父节点的扩展公钥是无法生成子节点的私钥的,所以不会暴露私钥,但是尽可能不要把这里的扩展公钥当成普通公钥暴露在外面,还是要妥善保存好,原因如下:

Note:BIP32有normal key普通密钥和hardened key加强密钥,只有当父节点是普通密钥时,才可以通过其扩展公钥(实际上还要加上一个chain code)才可以生成子节点公钥和地址,另外需要特别小心的是,对于普通密钥节点,一旦黑客掌握了子节点的私钥和其父节点的扩展公钥可以反推出父节点的私钥,从而会推出该父节点下面所有子节点。

[Refer to https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki]

  1. 每个用户都会分配一个比特币地址,如何有效管理如此众多的比特币地址?

采用BIP44协议,可以比较规范的管理所有的比特币地址,比如按照BIP44,我们可以生成两个主账号:

外部账号(分配给用户): m/44'/0'/0'/0/address_index

内部账号(分配给内部): m/44'/0'/1'/0/address_index

通过父节点的扩展密钥和索引编号我们可以给用户和内部生成比特币地址,进一步在数据库中绑定用户ID和比特币地址信息(比特币地址,编号,创建日期),我们可以实现更多的复杂逻辑,比如通过交叉对比充值记录回收三个月内没有使用的比特币地址。

  1. 如何管理充值到分配给用户的成千上万个地址中零散的比特币?

可以及时转移集中到内部账号上,比如当我们检测用户充值成功后,可以加入这一步转移的动作

  1. 怎样给用户提现,比如是不是从一个中心钱包转出’?

我确实没有想到更好更合理的方案,所以在我的设计中,确实要用一个中心钱包做转出,参照2)提到的内部账号m/44'/0'/1'/0/address_index,为了简化,可以直接用一个固定账号m/44'/0'/1'/0/0作为一个“结算准备金账号”,我们需要实时监控这个账号的额度状态,以免造成无法提款的情况。

Note:简单的监控可以采用grafana这个btc exporter https://grafana.com/dashboards/6973

  1. 在我们的这个需求设计中如何设计冷热钱包?

对于安全性的考虑不是简单的一层,而应该是多层的设计,根据不同的风险等级设计对应的安全策略,大概的考虑如下:

i) 分配给用户的外部账号安全基本相对较低, 对于个人用户,充值的额度相对较低,而且根据3)充值到外部账号的比特币会及时被转移出去,所以外部账号应该是热钱包

ii) 内部账号的安全级别较高, 因为内部账号管理的比特币较多,所以应该提高安全等级,是否可以做成100%冷钱包?应该不可以,因为我们还是需要从内部账号提款给用户的,提款首先需要知道用户提款记录,即使不直接连接数据库获取也要连接内部系统获取,而且即使离线签名转账也要需要知道UTXO,如果是物理断网的冷钱包,不连接其他系统的话是无从得知的,我们只能通过内部网络的安全防护,比如防火墙端口控制安全通信等来提高其安全性,我们可以把他做成一个semi-cold半冷钱包

iii) 只要如上面两点提到的没有物理隔绝断网的钱包都不能称为冷钱包,那么为了进一步提高资金安全性,我们可以把一部分比特币存储在冷钱包内,比如60%放在冷钱包,40%放在ii)半冷钱包。

Note:至于冷钱包如何跟系统交互,在我这个demo中是忽略掉的,我在网上看到资料,大概知道这么两种方式

a. 先说一个比较奇葩的设计是通过几台电脑摄像头互扫二维码,比如热钱包生成rawtransaction的二维码,冷钱包摄像头扫描二维码签名,生成signedtransaction二维码,热钱包再扫描进行广播

b. 通过BIP32我们知道,我们给一个地址转账,这个地址的私钥可以不需要被算出来,换句话说可以压根在这个世界上没有出现过,那么好了,我们都不需要有一个机器存储了,我们可以直接拿一个私钥没有出现过的账号当成冷钱包,每次冷钱包发生转账时就全部花掉,零钱放到另外一个新的冷钱包

我很好奇真正的交易所是如何定义冷热钱包并实现交互的

# 1.3 工作流程

  1. 充值流程

i) 用户选择比特币充值,输入15000人民币,网站根据当前汇率自动显示0.5 BTC,用户确认并提交

ii) 网站生成比特币充值地址(M/44'/0'/0'/0/address_index)并生成一个订单 [insert into topup_order <uuid,userid,btcaddress,btcindex,requestamt,exchangerate,createdate,status>],

同时网站还生成一个收款二维码, 格式:bitcoin:<address>?amount=<value>&message=<message>

iii) 用户从交易所或者自己的钱包手动输入或者扫描二维码进行转账

iv) 定时任务程序会从数据库获取充值请求,通过RPC请求比特币节点检测跟请求地址相关的交易,一旦检测到更新用户平台余额并保留比特币交易信息 txid/vout/scriptPub[update topup_order<uuid,userid,btcaddress,btcindex,requestamt,exchangerate,createdate,status,realamt,txid,vout,scriptpub>].

v) 定时任务程序基于realamt,txid,vout,scriptpub创建rawtransaction,并转移比特币到内部账号

  1. 取款流程

i) 用户选择将平台余额转成比特币提现,输入15000人民币并提供个人比特币地址,网站程序根据当前汇率自动显示0.5 BTC,用户确认并提交取款请求

ii) 网站验证用户余额和比特币地址有效性,并创建取款记录 [insert into withdraw_order <uuid,userid,btcaddress,btcindex,balance,btcamt,exchangerate,createdate,status>]

iii) 定时任务程序从数据库获取提款请求,从内部账号批量转出比特币到用户比特币地址

# 1.4 准备工作

  1. Bitcoin core v0.17.1

https://github.com/bitcoin/bitcoin/blob/master/doc/build-unix.md

  1. Bx tool (libbitcoin)

https://github.com/libbitcoin/libbitcoin-explorer

https://github.com/libbitcoin/libbitcoin-explorer/wiki

Note: 注意testnet或regtest的配置需要更改

~/libbitcoin-explorer/etc/libbitcoin/bx.cfg

  1. Python库

pip install python-bitcoinrpc

https://github.com/jgarzik/python-bitcoinrpc

bip32utils

https://github.com/lyndsysimon/bip32utils

  1. 其他

BTC-Fiat exchange rate api

https://blockchain.info/tobtc?currency=CNY&value=1

BTC transaction fees api

https://bitcoinfees.21.co/api

# 1.5 上手操作

# 1.5.1 创建密钥(服从bip32 bip39 bip44标准)

注意如果是testnet或regtest要根据配置好config文件,如果跟我一样用bx工具,配置参照1.4的说明; 另外testnet的cointype是1, https://github.com/satoshilabs/slips/blob/master/slip-0044.md

现在开始用bx工具创建mnemonic助记词和根节点master node

网站程序使用xpub(m/44'/1'/0'/0)[tpubDFCmqNxHDiBWw9e8XUEhkHqcw1i4drCe2mwwpR83eA2Arfmq8hJkUeVYY7hYaQWEo4HZDQ86FiRYj8Lr3e9UT8bYi7yLvbNbXgqyJeqLYii] 来生成用户充值地址;

定时任务程序使用xprv(m/44'/1'/0'/0)[tprv8iWjgxv35LVr3gcLdpa7LtBWMzC8UX1jTUMAXu5kDtDn2BX4WJVAJ9sgMzFjuoiWjhUdamEeB7sxPS6uzkmcEAAXNAevuaRWYQFMwX713mP] 来转移集中比特币到内部账号,为简化本demo使用固定的账号地址addr( m/44'/1'/1'/0/0)[muz1awk6YXQkP29dt1tdRpBTonmqAqwdst]).

# 1.5.2 设置测试环境

  1. 使用bitcoin testnet wallet https://play.google.com/store/apps/details?id=de.schildbach.wallet_test
  2. 获取测试比特币

https://coinfaucet.eu/en/btc-testnet/

  1. 允许bitcoin core

testnet配置~/.bitcoin/bitcoin.conf

bitcoind -testnet -printtoconsole/-daemon

然后导入内部账号测试地址

bitcoin-cli -testnet importprivkey cVHBtHK7kzze7yqF5En4Psbw2ZZdUEJ8jF4KAMXhLpuSwUf4fZAU "internal0" true

  1. Testnet explorer

https://live.blockcypher.com/btc-testnet

# 1.5.3 网站程序

可以使用nodejs webpack创建一个简单的web app https://webpack.js.org/api/node/

引用 bitcoinjs库,参考代码:

https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js

使用xpub(m/44'/1'/0'/0)给用户生成地址

# 1.5.4 定时任务程序

  1. 充值

  2. 转移集中用户充值到内部账号

  3. 取款

Note: 对于取款即从内部账号转btc到用户个人地址,本来想采用signtransactionwithkey离线签名的方式,但是问题是这个需要知道所有的utxo,意味着要很好的管理所有的utxo,当然可以通过观察钱包的方式获取utxo(还没有测试),导入pubkey到bitcoin core,rpc获取unspent transaction,但是还是需要一定的策略去使用这些utxo,比如每次先用余额低的输出,不然可能会产生越来越多小额的输出,所以实际上这个demo偷个懒,直接用sendmany的方式来处理了

# 如何购买

推荐coinhako,目前有赠送活动 (opens new window)