[币严BIZZAN区块链]数字货币交易所钱包对接之比特币(BTC)

在币严BIZZAN开发数字货币交易所的过程当中,一共有两大难点,一个是高速撮合交易引擎,另外一个是钱包对接,这二者是咱们团队之前没有接触过的。这个系列的文章主要介绍数字货币交易所钱包对接实现技术。第一个要对接的是比特币BTC,由于BTC的提现过程比较简单,用户在交易所提交提币需求,而后由交易所进行审核,接着进行系统打币或者人工打币。java

交易所对接比特币钱包的方式

交易所对接比特币钱包的方式有两种:
1. 自建BTC节点
BTC的节点支持经过RPC的方式进行访问,所以自建一个节点能够很快速对区块和交易进行查询。可是由于BTC节点会同步全部区块,截至到如今(2018/09/10)的区块高度已达500000,所以这须要大量的硬盘存储空间,16年就已须要至少160G。若是每一个币种都自建节点,耗费的硬盘存储空间会很大,而且要承担维护节点的工做。而初创团队对资金的合理利用要求比较高,所以,咱们舍弃了这种方式,而采用了第二种方式。
2. 第三方API+区块链浏览器
经过第三方API能够对比特的钱包进行操做,如生成密钥对、打币等。同时经过区块链浏览器提供的开放接口也能够进行交易的查询。所以咱们团队采用了这种方式对接比特币钱包。数据库

比特币钱包知识点

1.比特币钱包密钥生成
这里须要了解的知识点是比特币钱包:首先、比特币钱包能够离线生成,经过一系列的加密运算过程,若是你不嫌麻烦也能够本身去实现,可是不必重复造轮子;其次、比特币钱包离线生成也能够保证不重复。你不用担忧比特币地址会用完,由于理论上比特币钱包能够生成地址数量比宇宙中全部原子加起来的数量都多,因此担忧离线生成的钱包地址与别人重复这种担忧是彻底不必的。apache

2.比特币网络中没有帐户概念
若是你接触过以太坊,就知道以太坊有帐户概念,帐户这个概念更加贴近于咱们现实生活,有了帐户就能够看到余额。可是比特币没有这样的概念,感兴趣的能够去研究一下比特币里的UTXO。比特币只有交易,那你很好奇,只有流水帐那怎么知道一个地址的余额的?比特币是经过计算你的钱包地址全部转入与全部转出之间的差值来计算出来的。你可能还想问,要转帐给某个地址10个比特币的过程是怎样的?首先比特币网络会将你当前余额计算出来,看看够不够转,若是够,那么比特币网络会把之前转入你地址的交易所谓输入,也就是input,再把你转入到其余地址的交易做为输出,也就是output。咱们从区块链浏览器blockchain.info提供的API能够查询到下面这样的结果:json

 { "hash":"b6f6991d03df0e2e04dafffcd6bc418aac66049e2cd74b80f14ac86db1e3f0da", "ver":1, "vin_sz":1, "vout_sz":2, "lock_time":"Unavailable", "size":258, "relayed_by":"64.179.201.80", "block_height, 12200,
    "tx_index":"12563028", "inputs":[ { "prev_out":{ "hash":"a3e2bcc9a5f776112497a32b05f4b9e5b2405ed9", "value":"100000000", "tx_index":"12554260", "n":"2" }, "script":"76a914641ad5051edd97029a003fe9efb29359fcee409d88ac" } ], "out":[ { "value":"98000000", "hash":"29d6a3540acfa0a950bef2bfdc75cd51c24390fd", "script":"76a914641ad5051edd97029a003fe9efb29359fcee409d88ac" }, { "value":"2000000", "hash":"17b5038a413f5c5ee288caa64cfab35a0c01914e", "script":"76a914641ad5051edd97029a003fe9efb29359fcee409d88ac" } ] }

 

查询连接:https://blockchain.info/rawtx/b6f6991d03df0e2e04dafffcd6bc418aac66049e2cd74b80f14ac86db1e3f0da
上面是查询一个交易(Transaction)查询到的结果,能够看到其中int和out包含多条结果。浏览器

交易所充值逻辑设计

 
 

上图是交易所充值逻辑的设计图,下面简单说一下流程:
首先、咱们经过比特币钱包API批量生成如10000个比特币钱包地址。当用户在交易所中获取本身的BTC充值地址时,咱们从这10000个地址中为他分配一个(地址绑定UID)
而后、咱们会事先运行一个监放任务程序,这个程序负责从比特币区块链浏览器获取最新的区块信息,而后解析其中的交易涉及到的地址,当发现output中出现咱们已分配的地址时,咱们就为这个地址绑定的用户资产表中添加BTC余额。
最后、提醒用户充值到帐。网络

实现方法代码演示(Java)

1. 经过第三方钱包API生成比特币地址:
这里咱们使用的是开源代码bitcoinj,经过maven导入这个库,示例代码以下maven

import java.io.File; import java.io.IOException; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.wallet.Wallet; /** * 演示生成/保存密钥对和钱包地址到文件 * @author bizzan.com * */
public class GenerateKeyPair { public static void main(String[] args) { // 如在主网中生成钱包密钥对,须要改成MainNetParms
        NetworkParameters params  = TestNet3Params.get(); Wallet wallet = null; // 建立一个文件用于保存钱包文件
        final File walletFile = new File("test.wallet"); wallet = new Wallet(params); // 循环生成10个密钥对并添加到钱包
        for(int i = 0; i < 10000; i++) { ECKey key = new ECKey(); wallet.importKey(key); } try { // 保存钱包文件
 wallet.saveToFile(walletFile); // 打印钱包内容信息
            System.out.println(wallet.toString(true, true, true, null)); } catch (IOException e) { e.printStackTrace(); } } }

 

经过这段代码就能够生成10000个钱包地址,你能够把它保存在文件里。实际应用时,你还须要把10000个钱包地址保存到数据库中,可是私钥千万不要保存到联网数据库中。
经过下面的代码能够读取你保存到文件里的钱包内容:区块链

import java.io.File; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.wallet.UnreadableWalletException; import org.bitcoinj.wallet.Wallet; /** * 从文件导入钱包,注意须要先运行GenerateKeyPair生成钱包文件 * @author bizzan.com * */
public class ImportWalletFromFile { public static void main(String[] args) { // 如在主网中生成钱包密钥对,须要改成MainNetParms
        NetworkParameters params  = TestNet3Params.get(); Wallet wallet = null; // 建立一个文件用于保存钱包文件
        final File walletFile = new File("test.wallet"); try { // 从文件加载钱包
            wallet = Wallet.loadFromFile(walletFile); // 打印钱包信息
            System.out.println(wallet.toString(true, true, true, null)); // 获取钱包第一个密钥对
            ECKey firstKey = wallet.getImportedKeys().get(0); // 打印密钥对信息
            System.out.println("The first key is: \n" + firstKey.toString()); // 打印密钥对中的私钥(HEX)
            System.out.println("The first key Private Key(HEX) is: " + firstKey.getPrivateKeyAsHex()); // 打印密钥对中的私钥(WIF=Wallet Import Format)
            System.out.println("The first key Private Key(WIF) is: " + firstKey.getPrivateKeyAsWiF(params)); // 打印密钥对中的公钥:
            System.out.println("The first key Public Key is: " +firstKey.getPubKey()); // 打印密钥对中的公钥(HEX)
            System.out.println("The first key Public Key(HEX) is: " + firstKey.getPublicKeyAsHex()); // 打印密钥对中的公钥(Hash)
            System.out.println("The first key Public Key(Hash) is: " + firstKey.getPubKeyHash()); // 打印密钥对钱包地址
            System.out.println("The first key Wallet Address is: " + firstKey.toAddress(params)); // 经过公钥查找密钥对:findKeyFromPubHash(byte[] pubkeyHash)
            ECKey resultKey1 = wallet.findKeyFromPubHash(firstKey.getPubKeyHash()); System.out.println("Find Key From PubHash: " + resultKey1.toString()); // 判断公钥是否在钱包:isPubKeyHashMine(byte[] pubkeyHash)
            System.out.println("Is PubKeyHash Mine: " + wallet.isPubKeyHashMine(firstKey.getPubKeyHash())); // 经过公钥查找密钥对:findKeyFromPubKey(byte[] pubkey)
            ECKey resultKey2 = wallet.findKeyFromPubKey(firstKey.getPubKey()); System.out.println("Find Key From PubKey: " + resultKey2.toString()); // 判断公钥是否在钱包:isPubKeyMine(byte[] pubkey)
            System.out.println("Is PubKey Mine: " + wallet.isPubKeyMine(firstKey.getPubKey())); } catch (UnreadableWalletException e) { e.printStackTrace(); } } }

 

2. 经过区块链浏览器监听钱包地址:
经过轮询的方式,如每隔1个小时查询一次区块链浏览器,获取最新的区块信息,再对其中的地址信息进行解析,示例代码以下:ui

import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import org.apache.http.HttpEntity; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; /** * 演示经过Blockchain.info提供的JSON Http获取比特币区块信息 * @author bizzan.com * */
public class ApiApp { private final static String requstHttpUrl = "https://blockchain.info/"; public static void main(String[] args) { String param = "block-height/500000?format=json"; String result = sendGet(requstHttpUrl, param); JSONObject jsonObject = JSONObject.parseObject(result); JSONArray arr = jsonObject.getJSONArray("blocks"); JSONArray txList = arr.getJSONObject(0).getJSONArray("tx"); for(int i = 0; i < txList.size(); i++) { JSONObject txObj = txList.getJSONObject(i); JSONArray outs = txObj.getJSONArray("out"); for(int j = 0; j < outs.size(); j++) { JSONObject outObj = outs.getJSONObject(j); // 这里已获取交易信息(包含收币地址信息),由此可将此地址与充值地址库对比,进行充值后续工做
                System.out.println("tx_index:" + outObj.getIntValue("tx_index") + " - addr:" + outObj.getString("addr")); } } } public static String sendGet(String url, String param) { CloseableHttpClient httpClient = null; CloseableHttpResponse response = null; String result = ""; try { httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url + param); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000) .setConnectionRequestTimeout(35000) .setSocketTimeout(60000) .build(); httpGet.setConfig(requestConfig); response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); result = EntityUtils.toString(entity); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (null != response) { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != httpClient) { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } return result; } }

 

上面的代码是获取高度为500000的区块信息,并对其中的地址进行解析。加密

BIZZAN(币严) 数字货币交易所官方网址:

www.bizzan.com