精华内容
下载资源
问答
  • 比特币(BSV)知识库-Bitcoinwiki-目前为全英文内容,暂无中文译文,并且仍在持续编写和补充中。欢迎中国的开发者在文章底部评论,进行阐述和探讨。 Wallet import format Wallet Import Format (WIF, also known ...

    特别提示:

    比特币(BSV)知识库-Bitcoin wiki-目前为全英文内容,暂无中文译文,并且仍在持续编写和补充中。欢迎中国的开发者在文章底部评论,进行阐述和探讨。

    Wallet import format

    Wallet Import Format (WIF, also known as Wallet Export Format) is a way of encoding a private ECDSA key so as to make it easier to copy.

    A testing suite is available for encoding and decoding of WIF at:

    http://gobittest.appspot.com/PrivateKey

    Private key to WIF

    1 - Take a private key

       0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D
    

    2 - Add a 0x80 byte in front of it for mainnet addresses or 0xef for testnet addresses. Also add a 0x01 byte at the end if the private key will correspond to a compressed public key

       800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D
    

    3 - Perform SHA-256 hash on the extended key

       8147786C4D15106333BF278D71DADAF1079EF2D2440A4DDE37D747DED5403592
    

    4 - Perform SHA-256 hash on result of SHA-256 hash

       507A5B8DFED0FC6FE8801743720CEDEC06AA5C6FCA72B07C49964492FB98A714
    

    5 - Take the first 4 bytes of the second SHA-256 hash, this is the checksum

       507A5B8D
    

    6 - Add the 4 checksum bytes from point 5 at the end of the extended key from point 2

       800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D
    

    7 - Convert the result from a byte string into a base58 string using Base58Check encoding. This is the Wallet Import Format

       5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ
    

    WIF to private key

    1 - Take a Wallet Import Format string

       5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ
    

    2 - Convert it to a byte string using Base58Check encoding

       800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D
    

    3 - Drop the last 4 checksum bytes from the byte string

       800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D
    

    4 - Drop the first byte (it should be 0x80). If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01). If it corresponded to a compressed public key, the WIF string will have started with K or L instead of 5 (or c instead of 9 on testnet). This is the private key.

       0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D
    

    WIF checksum checking

    1 - Take the Wallet Import Format string

       5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ
    

    2 - Convert it to a byte string using Base58Check encoding

       800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D
    

    3 - Drop the last 4 checksum bytes from the byte string

       800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D
    

    3 - Perform SHA-256 hash on the shortened string

       8147786C4D15106333BF278D71DADAF1079EF2D2440A4DDE37D747DED5403592
    

    4 - Perform SHA-256 hash on result of SHA-256 hash

       507A5B8DFED0FC6FE8801743720CEDEC06AA5C6FCA72B07C49964492FB98A714
    

    5 - Take the first 4 bytes of the second SHA-256 hash, this is the checksum

       507A5B8D
    

    6 - Make sure it is the same, as the last 4 bytes from point 2

       507A5B8D
    

    7 - If they are, and the byte string from point 2 starts with 0x80 (0xef for testnet addresses), then there is no error.

    声明:

    比特币(BSV)知识库项目由比特币协会(Bitcoin Association)发起并支持,更多信息请参见知识库官网:https://wiki.bitcoinsv.io/


    • 对比特币区块链开发感兴趣的朋友可以通过CSDN站内私信联系我们,申请加入BSV开发者交流群。
    • 同时,您也可以扫描下方二维码,关注比特币协会官方微信公众号——BA资讯,了解更多区块链领域的实时资讯。

     

    展开全文
  • 这篇文章教大家如何自己编写一个极简的BSV钱包,可以生成私钥、地址,并发送交易。 准备工作 语言:JavaScript 环境:Node.js 操作系统:Win、Mac、Linux 推荐IDE:VSCode 首先,确保计算机上安装了Node....

    这篇文章教大家如何自己编写一个极简的BSV钱包,可以生成私钥、地址,并发送交易。

     

    准备工作

    语言:JavaScript

    环境:Node.js

    操作系统:Win、Mac、Linux

    推荐IDE:VSCode

     

    首先,确保计算机上安装了Node.js并配置了环境变量,如果没有安装Node.js,可以到官网(https://nodejs.org/zh-cn/)下载安装。

     

    新建一个工程文件夹,名字任意,例如SimplestWallet。用命令行打开这个文件夹。

    依次输入如下命令安装所需的依赖包:

    npm install bsv
    npm install mattercloudjs

    如果看到如下log,说明依赖包安装成功(版本号不一样没关系)。

    ...
    + bsv@1.5.3
    ...
    + mattercloudjs@1.1.2
    ...

    其中,bsv库(https://docs.moneybutton.com/docs/bsv-overview.html)是用于比特币BSV的事务数据操作方法的库。

    mattercloudjs库(https://www.mattercloud.net)封装了和比特币运营节点MatterCloud通信的网络API。

     

    然后,我们需要申请一下MatterCloud的免费API权限。到官网(https://www.mattercloud.net)点击 `Get API Key` 按钮,

    再点击Generate,即可得到Key。

     

     

    生成私钥和地址

    在制作钱包之前,我们先随机生成一个私钥。我们当然可以手动打入随机数,但正常做法是用随机算法生成,这样可以保证编码正确。

    创建一个random-key.js文件,代码如下

    const BSV = require('bsv') //引用bsv库
    let priKey = BSV.PrivateKey.fromRandom() //随机生成私钥
    console.log(priKey.toHex()) //输出Hex格式私钥
    console.log(priKey.toAddress()) //输出地址

    保存后,在命令行里用如下node命令运行该代码

    node .\random-key.js

    随即输出一个私钥和对应的地址,例如

    79f5876834b1e179de0112fd150e3170259fc2863328bb39a3f6f07403686158
    
    1F9929pRu1TETjzd69Yf3znzG5baywqFva

    记录好私钥,但千万不要公开。这个地址就可以用来收款。

    可以在https://cli.im自行制作地址的二维码,方便手机钱包扫码付款。

     

    转入少量BSV

    为了测试我们的钱包,需要一定的币,你也可以在写完代码后再转币。

    在此列举几个获取少量币的方法。

    https://sv.cafe/buy 小额人民币快速买BSV,下限1元。

    https://if.cash/bsv/faucet BSV水龙头,免费领取币,但可能失败。

    https://www.freebsv.com BSV水龙头,未测试。

     

    如果你想知道当前地址的余额,可以在区块链浏览器查询到,如

    https://whatsonchain.com

    https://satomoto.com/bitcoin-sv

    https://www.oklink.com/bsv

     

    编写转账功能

    在工程文件夹里新建文件send-money.js,粘贴如下代码

    const BSV = require('bsv') //引用bsv库
    const MatterCloud = require('mattercloudjs') //引用MatterCloud库
    
    const MatterCloudAPIKey = "CeJg9E1uuVJueBoJg6fkD5f5u7jBv4k7jj4cr6mmo8UTMzuqbq8Muzp3aXYTZ6bTh" //MatterCloud API Key
    const matterAPI = MatterCloud.instance({
        api_key: MatterCloudAPIKey, 
    })
    
    let priKeyHex = "79f5876834b1e179de0112fd150e3170259fc2863328bb39a3f6f07403686158" //钱包私钥
    
    let receiverAddresss = "1FjdJzaGXiUKQjnPBiTXqbuTPZMwypfoPC" //收款地址
    let value = 1000 //收款金额(单位:Sat)
    
    async function main() {
    
        let priKey = BSV.PrivateKey.fromHex(priKeyHex) //组装私钥
        let address = priKey.toAddress() //获取自己钱包的地址
    
        //获取锁定在钱包地址上的所有UTXO
        await matterAPI.getUtxos(address).then(async utxos => {
    
            let newTx = new BSV.Transaction() //创建一个transaction
    
            newTx.feePerKb(600) //设置手续费率为 0.6 Sat/Byte。 FIXME: 最终得到的tx的手续费和这里设置的不一致,原因不明
            //不同服务商的费率不同,经测试MatterCloud的广播API至少要 0.5 Sat/Byte。
    
            //将所有UTXO全部填入transaction的输入端(这是一种无脑做法,优秀的钱包应该智能地选择UTXO)
            let inputs = []
            utxos.forEach(utxo => {
                inputs.push(utxo)
            });
            newTx.from(inputs)
    
            //将收款地址对应的P2PKH输出添加到transaction的输出端
            let output = BSV.Transaction.Output({
                satoshis: value,
                script: BSV.Script.fromAddress(receiverAddresss)
            })
            newTx.addOutput(output)
    
            newTx.change(address) //将自己的地址设置为找零地址
    
            newTx.sign(priKey) //用私钥给transaction签署
    
            //生成16进制的transaction原数据,并输出,你可以用其他服务商来广播transaction
            let rawTx = newTx.toBuffer().toString('hex')
            console.log('RawTx:')
            console.log(rawTx)
    
            //用MatterCloud服务商广播transaction
            await matterAPI.sendRawTx(rawTx).then(res => {
    
                console.log('发送transaction成功:')
                console.log(res) //输出广播transaction的结果,包含txid
    
            }).catch(e => {
                console.error(e)
            })
        })
    }
    
    main()

     

    如果你的钱包地址上已经有币,就可以转账了。

    注意修改代码中的几个变量:

    • MatterCloudAPIKey。这是之前在MatterCloud免费获得的API Key。以后无需修改。
    • priKeyHex。这是你自己的钱包私钥,也就是刚刚生成的那个。以后很少修改。
    • receiverAddresss。本次转账的目标地址,每次转账时注意修改。
    • value。本次转账的金额,以Sat为单位,每次转账也应该修改。

     

    用node命令运行脚本:

    node .\send-money.js

    如果发送成功,则输出如下

    RawTx:
    0100000002616f4ed2d6f9ad733e0e79fc12ea25e5022b99ce028edc7fdce7c3cbebf3a482010000008b483045022100faf05e98a313f9272af265e4685ad101ff670d0a34f82b1fe880d3275f32f948022046b3e6ed50de5ef5847f1d96a2b48fe9ae5aaa5d4667bb43724092acbed45e594141048da20227dc514bc19ba554caa81d373fe2714828fed99e6dec0ca05503da6e2ea1f631deb355502b77d3ce4cde5584e3810fd7d25777878d1b3a1dc6a89bd76bffffffff631540441b4dd04f7dc970b0059cdbd76a008186d675d050c1e737c254a53366010000008a473044022028dfe3e8243513847995cb97060b9eab0042fb718382ebc71bb52fb7055de77502204aaf50c30daefd46da76291bed5bf5288ac6fbc172afd8c0898e46419069159a4141048da20227dc514bc19ba554caa81d373fe2714828fed99e6dec0ca05503da6e2ea1f631deb355502b77d3ce4cde5584e3810fd7d25777878d1b3a1dc6a89bd76bffffffff02e8030000000000001976a914a1a18aa70fe9c31c041c23eb6ee57f18c7cda25888aceb250000000000001976a9149b1bc88717b3d068d55a12519f4fd7fcc31f5ab588ac00000000
    发送transaction成功:
    { txid: '7467a9782d56230d3eefd857fbc3b57a6bf77a5e5034c2ce679ba334ae803d69' }

     

    最后得到的txid,就是transaction的hash,可以用来在区块链浏览器上查到刚刚发送的transaction。

     

    回顾

    log里输出的rawTx是16进制表示的transaction原数据。可以用https://whatsonchain.com/decode解码tx。如果广播不成功,还可以用https://whatsonchain.com/broadcast直接广播transaction原数据。

     

    本案例里用到了两个库。

    其中,bsv库用来处理比特币的数据结构、底层协议相关的东西,如生成私钥、地址,组装和签署transaction。

    MatterCloud是一个数据服务商,让我们可以通过远程API查询链上数据,或者广播transaction。我们可以把MatterCloud换成其他数据服务商(区块链浏览器也会提供数据服务),如

    https://developers.whatsonchain.com

    https://metasv.com/

     

    附录

    工程地址 https://github.com/fairwood/SimplestWallet

    展开全文
  • 本文主要利用bsv库来实践比特币应用开发需要理解的超大整数(BigNumber)、SECP256k1椭圆加密曲线、哈希函数(比如SHA256和RIPEMD160)、以及Base 58和Base 58 Check编码。本文主要以边翻译,边操作学习的方式进行。...

    e9c9c4ced20e177661633d9646fc8c84.png

    本文主要利用bsv库来实践比特币应用开发需要理解的超大整数(BigNumber)SECP256k1椭圆加密曲线哈希函数(比如SHA256和RIPEMD160)、以及Base 58和Base 58 Check编码。本文主要以边翻译,边操作学习的方式进行。

    Bitcoin SV库(bsv)

    bsv 是一个Javascript语言的比特币钱包应用开发库,主要可用来进行用户公私钥管理、创建并签名比特币交易(包括交易输入、交易输出和脚本)、签名和加密一般数据、以及实现任何基于web的客户端钱包需要的任何功能。

    bsv库目前主要有MoneyButton钱包公司团队在进行维护。至于MoneyButton是什么?它是一个比特币的快捷网页支付Button,具备链上身份ID功能,集成MoneyButton的应用有CityOnChain, Twetch, WeiBlock等BSV链上应用,当然更方便的在于你可以仅仅是一个网页你就可以集成MoneyButton支付按钮,实现用户在线打赏功能。 不了解的朋友自己去挖掘兔子洞吧。

    在应用中集成bsv,你可以使用node.js或者web浏览器。

    在node.js环境安装bsv,使用yarn:

    yarn add bsv

    或使用npm:

    npm install --save --save-exact bsv

    然后在代码中引用:

    let bsv = require('bsv')

    如果进行web网页开发,可以引用:

    <script type="text/javascript" src="https://unpkg.com/bsv@0.26.4/bsv.min.js"></script>

    超大整数(BigNumber)(bn.js)

    比特币用到的大量的加密和签名相关算法和技术,都需要使用Big Number。但是Javascript语言原生不支持Big Number,因此bsv封装了一个Big Number库(bn.js),这个库在bsv加密学中被广泛使用。

    BigNumber支持加(add)、减(sub)、乘(mul)、除(div)、模(mod)和逆模(invm),以及比较大小(eq, gt, lt, cmp)等。

    Javascript原生支持的最大安全整数为是(

    ),超过这个数的数学计算是不准确不安全的。下面进行实际操作, 在node命令行中运行,分别使用原生Javascript和bsv的BigNumber进行对比。
    > Number.MAX_SAFE_INTEGER
    9007199254740991
    > Number.MAX_SAFE_INTEGER + 1
    9007199254740992
    > Number.MAX_SAFE_INTEGER + 2
    9007199254740992
    > Number.MAX_SAFE_INTEGER + 3
    9007199254740994
    > Number.MAX_SAFE_INTEGER + 4
    9007199254740996
    > let bsv = require('bsv')
    undefined
    > let BN = bsv.crypto.BN
    undefined
    > let n = new BN(Number.MAX_SAFE_INTEGER)
    undefined
    > n.toString()
    '9007199254740991'
    > n.add(new BN(1)).toString()
    '9007199254740992'
    > n.add(new BN(2)).toString()
    '9007199254740993'
    > n.add(new BN(3)).toString()
    '9007199254740994'
    > n.add(new BN(4)).toString()
    '9007199254740995'
    > n.add(new BN(5)).toString()
    '9007199254740996'
    

    下面从https://whatsonchain.com 随便找一个区块中的交易ID进行超大整数演示:

    > let txid = 'b728d98b249c5ee65c6479f2c4b38297df1b3fcad7069b6d9a401f21252ede36'
    undefined
    > let buf = Buffer.from(txid, 'hex')
    undefined
    > let b = BN.fromBuffer(buf)
    undefined
    > b.toString()
    '82845426603574294407761060415928823803514934489778033567129254102697658736182'
    > b.toBuffer().toString('hex')
    'b728d98b249c5ee65c6479f2c4b38297df1b3fcad7069b6d9a401f21252ede36'
    > b.add(new BN(1)).toString()
    '82845426603574294407761060415928823803514934489778033567129254102697658736183'
    > b.add(new BN(1)).toString('hex')
    'b728d98b249c5ee65c6479f2c4b38297df1b3fcad7069b6d9a401f21252ede37'
    > b.toNumber()
    8.28454266035743e+76
    

    Secp256k1椭圆曲线点(point.js)

    关于椭圆曲线加密(Elliptic Curves Cryptography,ECC)、Secp256k1和椭圆曲线数字签名算法(ECDSA)的理论知识,请参考下面的几篇链接文章,有兴趣有实力的可以研读。本文只学习bsv中的相关接口调用,对于程序员只要会用就行了,加密数学实在太深奥...

    详解椭圆曲线加密(Elliptic Curves Cryptography,ECC)与ECDSA​blog.chinaaet.com
    99f70c2e6d31783569c72345b98df117.png
    椭圆曲线算法(ECC)学习(一) - FreeBuf互联网安全新媒体平台​www.freebuf.com
    636920a7a5d1b60956f66491970f812d.png
    椭圆曲线算法(ECC)学习(二)之Secp256k1 - FreeBuf互联网安全新媒体平台​www.freebuf.com
    636920a7a5d1b60956f66491970f812d.png

    椭圆曲线概要

    比特币协议的一个关键特性是ECDSA(称为椭圆曲线数字签名算法)。ECDSA是基于椭圆曲线的,椭圆曲线是有限域上的分组。这个组由点(Point)组成。可以将点相加(分组操作)并乘以大数(多次应用分组操作)。他们使用模运算和环绕。该分组是循环的,由基点G生成。

    您不太可能需要详细了解椭圆曲线来构建您的应用程序,但是有一个总体的概述认知是有帮助的。

    比特币使用的椭圆曲线为SECP256k1。这个曲线上的一个点可以表示为A = (Ax, Ay),这个点有两个值分别为Ax和Ay,都是Big Number。第二个点表示为B = (Bx, By)。

    将以上两个点相加是可行的,表示为A+B。注意,这不是普通的相加操作,这是一个分组的相加操作。Ax+Bx并不得到(A+B)x, (A+B)x必须使用分组操作。

    点可以相加并乘以Big Number,将一个点乘以一个Big Nubmer就是将一个点重复相加很多次。在SECP256k1椭圆曲线上有一个点叫G的曲线基点,曲线上的任何其它点都可以通过将G点乘以某个数计算得到。换句话说,曲线上的任意一个点P,都存在一个p数,使P = pG成立。

    如果p是一个私钥,那么P = pG就是所谓的公钥。

    如果Alice有私钥a和公钥A,Bob有私钥b和公钥B,那么它们可以进行Diffie-Hellman(迪菲-赫尔曼)秘钥交换。

    aB = abG = baG = bA

    也就是说,Alice的私钥乘以Bob的公钥等于Bob的私钥乘以Alice的公钥,这是只有Alice和Bob之间才能得到的秘密。

    Diffie-Hellman:一种确保共享KEY安全穿越不安全网络的方法,它是OAKLEY的一个组成部分。Whitefield与Martin Hellman在1976年提出了一个奇妙的密钥交换协议,称为Diffie-Hellman密钥交换协议/算法(Diffie-Hellman Key Exchange/Agreement Algorithm).这个机制的巧妙在于需要安全通信的双方可以用这个方法确定对称密钥。然后可以用这个密钥进行加密和解密。但是注意,这个密钥交换协议/算法只能用于密钥的交换,而不能进行消息的加密和解密。双方确定要用的密钥后,要使用其他对称密钥操作加密算法实现加密和解密消息。

    bsv上的Point

    可以通过G点获取一个新的的点:

    > let bsv = require('bsv')
    undefined
    > let b = bsv.crypto.Point.getG()
    undefined
    > let G = bsv.crypto.Point.getG()
    undefined
    > console.log(G)
    <EC Point x: 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 y: 483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8>
    undefined
    > console.log(G.mul(bsv.crypto.BN.fromString('100')))
    <EC Point x: ed3bace23c5e17652e174c835fb72bf53ee306b3406a26890221b4cef7500f88 y: e57a6f571288ccffdcda5e8a7a1f87bf97bd17be084895d0fce17ad5e335286e>
    undefined

    私钥是一个非常长的不可预知的超大整数,下面是一个随机生成私钥并获得对应公钥的例子:

    > let phex = bsv.crypto.Random.getRandomBuffer(32).toString('hex')
    undefined
    > let p = bsv.crypto.BN.fromBuffer(Buffer.from(phex, 'hex'))
    undefined
    > console.log(p)
    <BN: d5e3afa541a6a3fb938af706cddc43ebff00864d7898fc5a3c4f21129221a972>
    undefined
    > let P = G.mul(p)
    undefined
    > console.log(P)
    <EC Point x: e7e2570204bed9de6c3fd8afa6e230df6169197fee2910155dd5720d4385bf9c y: 8e6759f573a39afb9db456f2ae2b5e80998d58e9f8be1483c668f0b8b314b0b9>
    undefined

    注意,我们不能直接使用Point来管理私钥和公钥。bsv提供了PrivateKey和PublicKey来管理比特币的key。Point主要有add, mul, getX, getY几个接口。

    哈希函数(hash.js)

    比特币中广泛使用了加密哈希函数(Hash Function)。一个区块的ID是该区块的反向双SHA256哈希,这是比特币矿工挖矿时用nonce不停迭代计算的值-他们试图得到一个以大量零开头的哈希。一个交易的ID也是一个双SHA256哈希,一个地址是一个SHA256哈希的RIPEMD160哈希,最后,数字签名涉及对一个交易进行哈希来进行签名和验证。

    不仅比特币核心协议使用哈希函数,而且很多基于比特币的上层协议也要使用哈希函数,BIP32(分层确定性钱包), ECIES,以及很多其它标准有都使用各类哈希函数。

    bsv开放你在开发应用程序时需要使用的所有最重要的哈希函数,这些函数在bsv.crypto.Hash中的导出。

    比如:

    bsv.crypto.Hash.sha256

    本文中,哈希函数是指加密哈希函数,不是其它类型的哈希函数。其有以下这些特征:

    • 给定一个哈希值,唯一可确定是输入值的方法是穷举所有可能的输入值来找到正确的哈希值。
    • 当给定多个输入时,哈希中的所有位都有可能是0或1。
    • 改变输入的某一位将从根本上改变输出。
    • 它们有确定的输出长度(比如SHA256是256位,RIPEMD160是160位)。
    • 输入可以是任意长度(或者至少是非常长的长度,在大多数情况下没有实际限制)。

    给定数据,"this is the data I want to hash", 其SHA256哈希值是:

    f88eec7ecabf88f9a64c4100cac1e0c0c4581100492137d1b656ea626cad63e3

    稍微改变一下,在句尾添加一个句号,"this is the data I want to hash." 其SHA256哈希值变成了:

    37c8dbe24e14073958444f45db5b34c4496e87019ab9ce4ce06821c516b5c02d

    以上两条数据基本一致,但是两个哈希看不出任何关联性。以下是操作过程:

    > let bsv = require('bsv')
    undefined
    > let hash1 = bsv.crypto.Hash.sha256(Buffer.from('this is the data I want to hash')).toString('hex')
    undefined
    > console.log(hash1)
    f88eec7ecabf88f9a64c4100cac1e0c0c4581100492137d1b656ea626cad63e3
    undefined
    > let hash2 = bsv.crypto.Hash.sha256(Buffer.from('this is the data I want to hash.')).toString('hex')
    undefined
    > console.log(hash2)
    37c8dbe24e14073958444f45db5b34c4496e87019ab9ce4ce06821c516b5c02d
    undefined

    bsv中的哈希函数有:

    • sha256 - SHA256哈希函数,输出32Bytes(256bits)
    • sha256sha256 - 双SHA256哈希函数,输出32Bytes(256bits)
    • sha512 - SHA512哈希函数,输出64Bytes(512bits)
    • sha1 - SHA1哈希函数, 输出20Bytes(160bites)
    • ripemd160 - RIPEMD160哈希函数,输出20Bytes(160bites)
    • sha256ripemd160 - SH256哈希后再进行RIPEMD160, 输出20Bytes(160bites),用在比特币地址上。

    bsv还提供了一个广泛应用于应用程序的哈希相关的工具函数:HMAC(密钥相关的哈希运算消息认证码)。经常会有这样的情况:你可能希望将两段数据哈希在一起。有一个称为“哈希长度扩展攻击”的常见漏洞,该漏洞有时会出现在将机密数据与公共数据一起哈希的服务中,攻击者可以在该服务中控制某些输入。一般来说,如果需要将两类数据哈希在一起,最好使用HMAC,它是为防止长度扩展攻击而设计的。

    所以:hash(data1 + data2) =替换=> HMAC(data1, data2)

    HMAC本身是一个内部可使用任意哈希函数的是算法,你可以直接使用hmac,也可以使用两个方便的函数sha256hmac或者sha512hmac。

    关于HMAC更通俗易懂的解释,请参考:廖雪峰网站Phython中关于HMAC的介绍。

    Base 58编码(base58.js,base58check.js)

    比特币地址是一个公钥的哈希值。在比特币早期,用户必须来回复制粘贴地址,很容易出错。如果不小心把钱发错了地址,是非常糟糕的经历。所以中本聪为比特别地址使用一个自定义的Base58编码格式,来消除任何容易混淆的字母,比如没有小写的L和大写的i。另外,这个编码格式增加了一个哈希校验码确保拷贝地址不会出错。

    Base 58

    首先,让我们考虑没有校验码的情况。

    给定任何数据,我们可以使用下列58个字符来进行编码:

    123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

    使用bsv进行base58编码和解码:

    > var str = "this is my string"
    > var base58 = bsv.encoding.Base58.fromBuffer(Buffer.from(str)).toString()
    > console.log(base58)
    26St9k2Wa1oUK3T9MYpNXtzr
    >
    > var decoded = bsv.encoding.Base58.fromString(base58).toBuffer()
    > console.log(decoded.toString())
    this is my string

    Base 58 Check

    因为Base 58编码仍然有可能输错,所以我们采用校验码的格式。校验码就是放在数据前面的一段很短的数据的哈希值,如果数据不小心改变了,校验码就不匹配,因此可以检测数据异常。

    使用bsv进行base58check编码和解码,可以看到如果改变了编码后的数据进行解码,那么程序就会抛出异常(Checksum mismatch)。

    > let bsv = require('bsv')
    > var str = "this is my string"
    > var base58check = bsv.encoding.Base58Check.fromBuffer(Buffer.from(str)).toString()
    > console.log(base58check)
    8AArJ45YvcrtshadAYjNgHtXHRGtA
    >
    > var buf = bsv.encoding.Base58Check.fromString(base58check).toBuffer()
    > console.log(buf.toString())
    this is my string
    >
    > var wrong = bsv.encoding.Base58Check.fromString('8AArJ45YvcrtshadAYjNgHtXHRGtB').toBuffer()
    Error: Checksum mismatch
        at Function.Base58Check.decode (D:CodesBitcoinSVBsvUsenode_modulesbsvlibencodingbase58check.js:58:63)
        at Function.Base58Check.fromString (D:CodesBitcoinSVBsvUsenode_modulesbsvlibencodingbase58check.js:96:25)

    参考资料

    1. Bitcoin SV Library Overview by MoneyButton
    2. Money Button Documentation Series: Technical Preliminaries

    上一篇文章

    Derek:比特币链上应用开发学习指南​zhuanlan.zhihu.com
    f81579cdd296f901ad99dc492556822b.png
    展开全文
  • BSV Planaria框架技术总结二 Bitquery 此文是变形虫技术总结的第二篇,阅读此文之前建议先阅读关于变形虫的前两篇文章。 Bitcoin SV的开发哲学——变形虫框架 BSV Planaria框架技术总结一 节点搭建 前面的文章说过...

    BSV Planaria框架技术总结二 Bitquery

    此文是变形虫技术总结的第二篇,阅读此文之前建议先阅读关于变形虫的前两篇文章。

    Bitcoin SV的开发哲学——变形虫框架

    BSV Planaria框架技术总结一 节点搭建

    前面的文章说过变形虫是一个持久层框架,通过planaria组件爬取区块链上的交易数据,提取并加工所需的数据后将数据存储到MongoDB中,然后由planarium对外提供接口给应用程序来调取数据。在搭建好数据库之后,我们下一步关注的重点就是如何对数据库进行读写。前文已经介绍过,变形虫与传统数据库最大的区别就是读写分离,不能直接写数据库,只能通过客户端发起链上交易来修改状态机,因此向变形虫写数据本质上就是构造交易,这将在下一篇文章中总结。本文的重点就是如何高效优雅地从变形虫中读取数据。

    Bitquery简介

    planaria使用一套简单高效的查询语言,称为Bitquery,类似于sql语言,可以将变形虫中的数据进行各种图灵完备的组合和处理,输出各种形态的数据。由于变形虫的开发目的是一个可以满足一切基于比特币链上数据需求的持久层框架,因此必须具备一种专为链上交易数据服务的查询语言,bitquery也就因此而产生。

    bitquery查询语句本质上是一个json对象,之所以是这种形态是为了适应MongoDB的查询。这里有必要提及一下,变形虫框架为何内置数据库采用的是MongoDB而不是类似mysql的关系型数据库。照理说,链上交易数据是充分结构化的,各个交易之间,地址之间有很强的关联性,查询交易更适合使用关系型数据库,这样连表操作更为方便,性能也更好。但是对于变形虫而言,它关注的重点并不是交易本身,而是交易上携带的千变万化,各行各业的杂乱无章的非标准数据,这些数据没有统一的范式,也没有统一的关联性。在这种情形下,Nosql的优势就会凸显出来,将一个个非标准数据看做一个个”文件“,不强制要求文件内容的格式,更加符合变形虫的需求目标。我有一种设想,以后的变形虫甚至可以在内部同时集成关系型和非关系型两种数据库,关系型数据库专门处理关联性高的交易数据,提升关联连表查询效率,而非关系型数据库处理各种应用的非标准数据,提升拓展性和兼容性。两种内置的数据库组件各司其职,将变形虫的性能发挥到极致。

    bitquery示例如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PipQL7QC-1594465117826)(https://docs.planaria.network/bitquery.png)]

    bitquery基于两种非常强大的技术,MongoDB Query Language(mongoDB 查询语言)和JQ(一个基于栈操作的图灵完备的json处理语句)。查询语句如上图所示分为3部分,第一部分是协议的版本号,目前是3,第二部分是基于MongoDB的查询语句,筛选出数据,第三部分是返回结果处理语句,对第二部筛选出来的结果进行整理和包装,处理成应用程序需要的样式。

    第二部分查询语句主要基于MongoDB的查询语句,但是与原生的MongoDB查询略有不同,针对Planaria做了一定的适配,选取了部分查询功能,使用json对象的各个成员来标记搜索条件。

    第三部分处理搜索结果,第二步返回的结果是一个json,可能包含我们不需要的数据,或者不是我们所希望的数据格式,需要将其整理成我们需要的格式,这时候就需要用到JQ,变形虫内置JQ的处理组件,可以直接帮我们把数据在服务器端就处理好,不需要客户端自行进行数据的处理。

    query字段(q)可以包含下列成员(后文会依次详细说明):

    • find:同MongoDB的find条件语句。
    • aggregate:对应MongoDB的聚合语句。
    • project:对应mongoDB的project操作符
    • sort:对应MongoDB的排序操作符
    • limit:对应MongoDB的个数限制操作
    • skip:对应跳过操作,通过limit和skip可以实现翻页
    • db:选择db,目前有c(已确认交易库)和u(未确认交易库)

    response字段(r)包含一个f成员,就是后处理function,里面是一个JQ表达式,用于处理query返回的结果。

    Genesis数据储存格式

    目前我运行的是一个全量的Genesis数据库,因此在这里详细说明一下返回值的字段含义,如果不知道这些字段的含义,也就无从写query语句和Jq表达式了。没有节点的同学可以直接访问Unwriter的公开endpoint:

    genesis公开Endpoint

    打开后会看到默认的查询语句如下:

    {
      "v": 3,
      "q": {
        "find": {},
        "limit": 10
      }
    }
    

    这语句的意思是取最近的十条记录,没有其他条件。然后我们会看到下方有一个巨大的表格,里面填充了各种数据,并且有一些奇怪的表头名称。为了方便描述,我们修改一下查询语句,因为默认查询语句经常会查出一些巨大的交易数据体(二进制图片等数据),在本文有限的空间下没办法展示出来,我们采用如下的查询:

    {
      "v": 3,
      "q": {
        "find": { "out.h1": "6d02" },
        "limit": 1
      }
    }
    

    转换成get请求如下:

    GET /q/1FnauZ9aUH2Bex6JzdcV4eNX7oLSSEbxtN/ewogICJ2IjogMywKICAicSI6IHsKICAgICJmaW5kIjogeyAib3V0LmgxIjogIjZkMDIiIH0sCiAgICAibGltaXQiOiAxCiAgfQp9 HTTP/1.1
    Host: genesis.bitdb.network
    key: 1KWqy2WbNpEPC7hwvfJbvXy2vekS2LwGim
    Cache-Control: no-cache
    Postman-Token: 6445346e-1444-8450-4cb5-088197fe921e
    

    这个语句的意思是取出最近的一条memo.sv(一个bsv链上微博)的交易数据,原理我们后文介绍。

    可以看到这个查询语句返回了一系列数据,填充在表格中,我们将其json源码(从postman直接请求)贴下来,如下:

    {
        "u": [
        ],
        "c": [
            {
                "_id": "5ccfe65cd7faee16774712b0",
                "tx": {
                    "h": "15ec948360c54b0864fed10addc9370e7acf70624bae3e46f0db16ba33728e60"
                },
                "in": [
                    {
                        "i": 0,
                        "b0": "MEQCIGbAWT8cYm+sHj+/kJI6Ir7HznAZl+uI/Op1w119nyBWAiAmaWlVHAw7F1lPjkremq5YfDyuGr2bMQC7Ft2/uUwor0E=",
                        "b1": "A+8emT7UM3oOjjqstBg4sjn2I4wnIPPwY7ARQ6VkrGue",
                        "str": "3044022066c0593f1c626fac1e3fbf90923a22bec7ce701997eb88fcea75c35d7d9f20560220266969551c0c3b17594f8e4ade9aae587c3cae1abd9b3100bb16ddbfb94c28af41 03ef1e993ed4337a0e8e3aacb41838b239f6238c2720f3f063b01143a564ac6b9e",
                        "e": {
                            "h": "175553b769f70d93b6c3961e8bdefe20ab274f6bbb8bbbcbfbbe205fb347a610",
                            "i": 0,
                            "a": "1Gt8ZMCxkGbkTQfMuPnUZWx8y9ydWfuQA1"
                        },
                        "h0": "3044022066c0593f1c626fac1e3fbf90923a22bec7ce701997eb88fcea75c35d7d9f20560220266969551c0c3b17594f8e4ade9aae587c3cae1abd9b3100bb16ddbfb94c28af41",
                        "h1": "03ef1e993ed4337a0e8e3aacb41838b239f6238c2720f3f063b01143a564ac6b9e"
                    }
                ],
                "out": [
                    {
                        "i": 0,
                        "b0": {
                            "op": 118
                        },
                        "b1": {
                            "op": 169
                        },
                        "b2": "rjVo4QocscdXU/oca7cRtfhv+8E=",
                        "s2": "\ufffd5h\ufffd\n\u001c\ufffd\ufffdWS\ufffd\u001ck\ufffd\u0011\ufffd\ufffdo\ufffd\ufffd",
                        "b3": {
                            "op": 136
                        },
                        "b4": {
                            "op": 172
                        },
                        "str": "OP_DUP OP_HASH160 ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1 OP_EQUALVERIFY OP_CHECKSIG",
                        "e": {
                            "v": 26870,
                            "i": 0,
                            "a": "1Gt8ZMCxkGbkTQfMuPnUZWx8y9ydWfuQA1"
                        },
                        "h2": "ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1"
                    },
                    {
                        "i": 1,
                        "b0": {
                            "op": 106
                        },
                        "b1": "bQI=",
                        "s1": "m\u0002",
                        "b2": "QSBCaXRjb2luZXIgaW4gdGhlIHN3YXJtIG9mIHNwZWN1bGF0b3JzLi4uCgpodHRwczovL2kuaW1ndXIuY29tLzRNU0xZVW8ubXA0CgojQml0Y29pbg==",
                        "s2": "A Bitcoiner in the swarm of speculators...\n\nhttps://i.imgur.com/4MSLYUo.mp4\n\n#Bitcoin",
                        "str": "OP_RETURN 6d02 4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e",
                        "e": {
                            "v": 0,
                            "i": 1,
                            "a": "false"
                        },
                        "h1": "6d02",
                        "h2": "4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e"
                    }
                ],
                "blk": {
                    "i": 581188,
                    "h": "000000000000000005cf5c75cdd7f55b5e772e0d751dd0f4a60076702af5768f",
                    "t": 1557128792
                }
            }
        ]
    }
    

    然后我们根据上面的返回值,介绍一下返回值的各个字段含义。原版的文档请参考如下链接:

    Bitdb Indexer

    根路径的u和c代表两个不同的数据库collection,u是unconfirmed tx,未确认交易,存在于内存池中。c是confirmed tx,已经确认的交易,存在于区块链中。

    然后列表中的每一个对象都是一个交易,里面记载了交易的详情。

    对于交易层面而言,其结构如下:

    {
      "tx": {
        "h": [交易 HASH]
      },
      "blk" {
        "i": [区块高度],
        "h": [区块HASH],
        "t": [区块时间戳]
      },
      "in": [
        INPUT1,
        INPUT2,
        INPUT3,
        ...
      ],
      "out": [
        OUTPUT1,
        OUTPUT2,
        OUTPUT3,
        ...
      ]
    }
    

    对多输入多输出的交易,in和out分别是这笔交易输入input和输出output的列表。

    然后我们说输入输出脚本的层面,也就是上面input和output的每一个具体的对象,建议看这部分之前先了解比特币脚本的工作原理。

    这是从上面的output中摘取出来的一个输出脚本

    {  
       "i":0,
       "b0":{  
          "op":118
       },
       "b1":{  
          "op":169
       },
       "b2":"rjVo4QocscdXU/oca7cRtfhv+8E=",
       "s2":"\ufffd5h\ufffd\n\u001c\ufffd\ufffdWS\ufffd\u001ck\ufffd\u0011\ufffd\ufffdo\ufffd\ufffd",
       "b3":{  
          "op":136
       },
       "b4":{  
          "op":172
       },
       "str":"OP_DUP OP_HASH160 ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1 OP_EQUALVERIFY OP_CHECKSIG",
       "e":{  
          "v":26870,
          "i":0,
          "a":"1Gt8ZMCxkGbkTQfMuPnUZWx8y9ydWfuQA1"
       },
       "h2":"ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1"
    }
    

    我们知道,一个标准的P2PKH(Pay To Pubkey Hash)交易的输出脚本(也称为锁定脚本)如下所示:

    OP_DUP OP_HASH160 ae3568e10a1cb1c75753fa1c6bb711b5f86ffbc1 OP_EQUALVERIFY OP_CHECKSIG
    

    这个脚本可以根据空格拆成5个数据或者操作码块,在校验比特币交易时,5个块依次进栈参与运算。我们按照顺序将其依次标记为0,1,2,3,4。

    然后我们介绍上面的json的字段:

    • i:指这个输出(或输入)在交易中的标号index,是第几个输出,0是第一个
    • b0 b1 b2 b3 b4:是上述5个块的base64编码,如果是操作码,比如OP_DUP或者OP_HASH160,则储存其操作码编号,比如b0对应OP_DUP,其操作码编号是118,因此b0对应的就是op:118。如果不是操作码,是数据,则储存数据的Base64编码。(比特币操作码编号一栏
    • s0 s1 s2 s3 s4:是上述5个脚本块的UTF8字符串,由于标准脚本只有s2是数据,其他都是操作码,因此此处只返回了s2,操作码不储存成字符串
    • h0 h1 h2 h3 h4:是上述5个脚本串的十六进制(hex)编码,同样由于标准脚本只有h2是数据,是会变化的,因此此处只返回h2,操作码不储存hex
    • str:脚本原文
    • e:edge,边界,对于输入脚本,edge储存了输入脚本的来源交易,对于输出脚本,edge储存了这个utxo将花向何处
    • e(输入脚本中):h表示这个输入的来源交易的交易hash。i是作为来源的utxo在上一笔交易中的index。a是来源交易的发起人地址。
    • e(输出脚本中):v是输出的金额satoshis,i是位于输出的位置index,a是输出花出去的目标地址(如果该输出已经被花费)

    根据上面的解释,我们可以套用相同的模式来解析第二个输出:

    {  
       "i":1,
       "b0":{  
          "op":106
       },
       "b1":"bQI=",
       "s1":"m\u0002",
       "b2":"QSBCaXRjb2luZXIgaW4gdGhlIHN3YXJtIG9mIHNwZWN1bGF0b3JzLi4uCgpodHRwczovL2kuaW1ndXIuY29tLzRNU0xZVW8ubXA0CgojQml0Y29pbg==",
       "s2":"A Bitcoiner in the swarm of speculators...\n\nhttps://i.imgur.com/4MSLYUo.mp4\n\n#Bitcoin",
       "str":"OP_RETURN 6d02 4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e",
       "e":{  
          "v":0,
          "i":1,
          "a":"false"
       },
       "h1":"6d02",
       "h2":"4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e"
    }
    

    上面的输出是一个op return输出,也是变形虫关注的重点。根据上面对字段的解释,我们可以看出,这个输出脚本有三个块,OP_RETURN,6d02,以及后面的数据。

    OP_RETURN 6d02 4120426974636f696e657220696e2074686520737761726d206f662073706563756c61746f72732e2e2e0a0a68747470733a2f2f692e696d6775722e636f6d2f344d534c59556f2e6d70340a0a23426974636f696e
    

    这其实是一个标准的memo.sv协议的op return格式。开头的OP_RETURN是操作码,所以s0,h0没有储存,h1是6d02的hex,6d02声明了后面的内容属于memo.sv协议,再后面的内容就是hex化的数据体,关于bit协议的声明(6d02的来源和改良方案),可以参考bitcom协议规范,Ifwallet的bibodeng对此专门有讲解:

    Unwritter发布的Bitcom协议是什么?

    至此,我们详细介绍了变形虫中储存交易的格式,之后就是通过操作MongoDB以及JQ来查询和处理我们所需要的数据。

    查询语句

    然后我们研究Bitquery的query部分,查询体。

    query部分的本质是查询MongoDB,而且由于Planaria只可读,在使用它的时候我们只需要关注读取语句(当然如果你要自定制planaria.js,需要研究MongoDB的写入操作,这不在本文的讨论范畴内,以后专门写)。因此我们需要先对mongoDB的find操作的一些基本概念进行介绍,官方文档如下:

    MongoDB的Find操作

    find语句传入一个json对象,json对象中的每一个字段名代表要指定的字段名,字段的值代表搜索条件,多个字段可以并列,相当于sql语句中的and条件。

    这里由于篇幅不够,详细的操作无法全部说明,就挑一些官网的典型示例来说明一下。

    1.匹配相等,index为5,姓是Hopper

    db.bios.find( { _id: 5 } )
    
    db.bios.find( { "name.last": "Hopper" } )
    

    2.匹配范围或正则表达式

    db.bios.find(
       { _id: { $in: [ 5, ObjectId("507c35dd8fada716c89d0013") ] } }
    )
    
    db.bios.find( { birth: { $gt: new Date('1950-01-01') } } )
    
    db.bios.find(
       { "name.last": { $regex: /^N/ } }
    )
    

    3.匹配多个条件(相当于and)

    db.bios.find( {
       birth: { $gt: new Date('1920-01-01') },
       death: { $exists: false }
    } )
    

    4.匹配多个条件之一(相当于or)

    db.col.find(
       {
          $or: [
             {key1: value1}, {key2:value2}
          ]
       }
    )
    

    5.匹配非(相当于!)

    db.col.find({"likes":{$ne:50}})
    

    6.aggregate聚合函数(相当于count)
    聚合需要一些聚合操作,这些操作又称为管道pipeline,MongoDB将这些管道内的操作(称为stage)处理完成后,将结果聚合在一起。管道的操作列表及其说明参照下文:

    Aggregation Pipeline Stages

    例如,数据源如下

    { _id: 1, cust_id: "abc1", ord_date: ISODate("2012-11-02T17:04:11.102Z"), status: "A", amount: 50 }
    { _id: 2, cust_id: "xyz1", ord_date: ISODate("2013-10-01T17:04:11.102Z"), status: "A", amount: 100 }
    { _id: 3, cust_id: "xyz1", ord_date: ISODate("2013-10-12T17:04:11.102Z"), status: "D", amount: 25 }
    { _id: 4, cust_id: "xyz1", ord_date: ISODate("2013-10-11T17:04:11.102Z"), status: "D", amount: 125 }
    { _id: 5, cust_id: "abc1", ord_date: ISODate("2013-11-12T17:04:11.102Z"), status: "A", amount: 25 }
    

    使用聚合操作将它们聚合并算出个数

    db.orders.aggregate([
                         { $match: { status: "A" } },
                         { $group: { _id: "$cust_id", total: { $sum: "$amount" } } },
                         { $sort: { total: -1 } }
                       ])
    

    管道的操作是,首先选取status为A的,然后将它们按照cust_id字段进行分组,并统计出每组的amout总和,最后按照总和倒序排列。最后的返回结果如下:

    { "_id" : "xyz1", "total" : 100 }
    { "_id" : "abc1", "total" : 75 }
    

    7.project,或projection操作,指定返回的字段,指定返回则设置为1,指定不返回则设置为0

    例如:

    const cursor = db
      .collection('inventory')
      .find({
        status: 'A'
      })
      .project({ item: 1, status: 1 });
    

    在inventory表中选取status为A的记录,并返回_id,item,和status,3个字段。

    8.sort操作,指定排序方式,按照哪个字段排序,正序为1,倒序为-1

    例如:

    db.orders.find().sort( { amount: -1 } )
    

    在orders表中选取全部,并按照amount倒序排列。

    9.limit操作,指定返回的限制个数

    按照query查找,并限制返回number个

    db.collection.find(<query>).limit(<number>)
    

    10.skip操作,指定跳过的个数offset

    function printStudents(pageNumber, nPerPage) {
      print( "Page: " + pageNumber );
      db.students.find()
                 .skip( pageNumber > 0 ? ( ( pageNumber - 1 ) * nPerPage ) : 0 )
                 .limit( nPerPage )
                 .forEach( student => {
                   print( student.name );
                 } );
    }
    
    

    上面是一个配合limit使用的翻页逻辑,跳过前offset个记录

    至此,我们已经简单介绍了query部分可能涉及到的一些查询操作,更多高级用法请参阅MongoDB的使用规范和文档,本文不赘述。

    query部分示例与详解:

    {
      "v": 3,
      "q": {
        "find": {
          "$text": {
            "$search": "hello"
          },
          "out.h1": "6d02",
          "out.b2": "hello"
        },
        "skip": 5,
        "limit": 10,
        "sort": { "blk.i": 1 }
      }
    }
    

    上述的查询条件简单说明:搜索字段中是文本类型且带有hello的,而且out.h1字段等于6d02,而且out.b2字段等于hello的所有记录,跳过前5个,限制取10条记录,并根据blk.i(区块高度)正序排列。

    JQ json后处理语句

    在上一步获取到查询结果后,mongoDB返回的是一个json,如果我们希望对这个json进行一些后处理,就可以使用bitquery提供的另一个强大的工具,JQ后处理。

    JQ是一个轻量级,图灵完备的堆栈式命令行Json处理工具。官方网站如下:

    JQ官网

    bitquery集成了JQ,并将其写在请求json的第三个部分,r.f字段。如下所示

    {
      "v": 3,
      "q": {
        "find": { "out.h1": "534c5000", "out.s3": "GENESIS" },
        "limit": 20,
        "project": { "out.$": 1, "_id": 0 }
      },
      "r": {
        "f": "[.[] | .out[0] | { token_symbol: .s4, token_name: .s5, document_url: .s6} ]"
      }
    }
    

    f的一长串值就是JQ表达式,上述示例的含义就是取出所有query返回值中的out[0],然后将其中的s4拆出来作为字段token_symbol的值,将s5拆出来作为字段document_url的值,并返回一个新的列表。

    查询结果如下所示:

    {
      "u": [{
        ...
      }],
      "c": [{
        "token_symbol": "TEST",
        "token_name": "TEST",
        "document_url": "bitcoinfiles:b86b4bcbab7cd787b1c893ca101250c8c467dbba4df229b118218bd8a9e85a92"
      }, {
        "token_symbol": "VOTE",
        "token_name": "An Election",
        "document_url": "bitcoinfiles:a90e59ef7ca66b25b6ba98d028198ae222a8229804c4b0b3bc0b1bafe104738a"
      }, {
        "token_symbol": "WuCash",
        "token_name": "Wu Tang Cash",
        "document_url": "http://wu.cash"
      }, {
        "token_symbol": "bb23n",
        "token_name": "bb23",
        "document_url": "bb23n.com"
      }, {
        "token_symbol": "DBOOK01",
        "token_name": "Digital Book Example",
        "document_url": "https://digitalbookexampletokenurl.com"
      }, {
        "token_symbol": ""
        "token_name": ""
        "document_url": ""
      }, {
        "token_symbol": "MTT",
        "token_name": "MyTestToken",
        "document_url": ""
      }]
    }
    

    jq的使用方法unwriter简单概括如下:

    • 所有jq表达式都是基于堆栈的,因此从左向右阅读
    • 所有的表达式都假设传入一个json对象,并最后输出一个对象
    • 管道表达式类似于unix的管道,它将处理结果从左到右依次传递下去

    我们这里受篇幅的限制,简单描述一下一些常用的jq语法,英文好的朋友可以直接参照JQ官网提供的手册来学习语法,当然也可以参考下面的中文博客,介绍的很详细,并且可以使用里面的jq play 在线校验表达式。

    jq 常用操作

    我们这里列举几个比较常用的语法:

    1.获取object下某个字段的值,也可以在其后增加?,表示如果不存在也不会报错

    .key, .foo.bar, .["key"]
    
    .key?, .foo.bar?, .["key"]?
    

    2.获取所有的值

    .[]
    
    $ echo '{"url": "mozillazg.com", "name": "mozillazg"}' |jq .[]
    "mozillazg.com"
    "mozillazg"
    

    3.构造新数组,比如将上面所有的值组成新的数组

    [.[]]
    
    $ echo '{"url": "mozillazg.com", "name": "mozillazg"}' |jq [.[]]
    [
      "mozillazg.com",
      "mozillazg"
    ]
    

    4.切分数组,选取数组的某一项

    .[1]  .[0:2]
    
    $ echo '[{"name": "tom"}, {"name": "mozillazg"}, {"name": "jim"}]' |jq .[1]
    {
      "name": "mozillazg"
    }
    $ echo '[{"name": "tom"}, {"name": "mozillazg"}, {"name": "jim"}]' |jq .[0:2]
    [
      {
        "name": "tom"
      },
      {
        "name": "mozillazg"
      }
    ]
    

    5.使用多个filter,依次执行

    如下面依次执行选取url的值,选取name的值,所以打印了两个值出来

    ,
    
    $ echo '{"url": "mozillazg.com", "name": "mozillazg"}' |jq '.url, .name'
    "mozillazg.com"
    "mozillazg"
    

    6.管道操作符,类似于unix的管道操作,熟悉linux操作的一定不陌生

    Pipeline (Unix)

    相当于对管道前后的条件依次过滤,最后输出结果,如下面的语句,就是先选取json原文,然后再从中过滤出url的值

    |
    
    $ echo '{"url": "mozillazg.com", "name": "mozillazg"}' |jq '.|.url'
    "mozillazg.com"
    

    我们这里做一个简单的bitquery操作示例(unwriter的网站给出的),并稍微解释一下,更高级和深入的用法还需要多加研究jq的文档。

    {
      "v": 3,
      "q": {
        "find": { "out.h1": "6d02" },
        "limit": 10
      },
      "r": {
        "f": "[{ block: .blk.i?, timestamp: .blk.t?, content: .out[1].s2 }]"
      }
    }
    

    上述的jq表达式的意思是将每个结果的以下3个数据提取出来,构成一个新的对象列表。细心的朋友可能运行上述bitquery表达式会报错,我们仔细研究一下,发现确实有问题(unwriter大神也会犯错哈)。query部分返回的直接是个数组,在数组中选择.blk就会直接报错。这点我们通过变形虫执行上述命令的日志可以看出来错误原因:

    Query =  { find: { 'out.b1': 'bQI=' }, limit: 2 }
    before transform =  [ { _id: 5cd041b74171e816cb23c05b,
        tx: 
         { h: 'a1d58e3053bb0aea6c208eca40ba6299fc9e770fb74cfacac4cf90dcc92efbaa' },
        in: [ [Object] ],
        out: [ [Object], [Object] ],
        blk: 
         { i: 581226,
           h: '000000000000000008c75b2424d2f4975015c2fc5add0af997e9e332a9b42898',
           t: 1557152175 } },
      { _id: 5cd041b74171e816cb23c17a,
        tx: 
         { h: '0982091b7923b82bf3c64fe17a9e72bcb099d72388e30ce1ef1a43efff405628' },
        in: [ [Object] ],
        out: [ [Object], [Object] ],
        blk: 
         { i: 581226,
           h: '000000000000000008c75b2424d2f4975015c2fc5add0af997e9e332a9b42898',
           t: 1557152175 } } ]
    after transform =  [ { _id: 5cd041b74171e816cb23c05b,
        tx: 
         { h: 'a1d58e3053bb0aea6c208eca40ba6299fc9e770fb74cfacac4cf90dcc92efbaa' },
        in: [ [Object] ],
        out: [ [Object], [Object] ],
        blk: 
         { i: 581226,
           h: '000000000000000008c75b2424d2f4975015c2fc5add0af997e9e332a9b42898',
           t: 1557152175 } },
      { _id: 5cd041b74171e816cb23c17a,
        tx: 
         { h: '0982091b7923b82bf3c64fe17a9e72bcb099d72388e30ce1ef1a43efff405628' },
        in: [ [Object] ],
        out: [ [Object], [Object] ],
        blk: 
         { i: 581226,
           h: '000000000000000008c75b2424d2f4975015c2fc5add0af997e9e332a9b42898',
           t: 1557152175 } } ]
    running jq
    STR =  
    error jq SyntaxError: Unexpected end of JSON input
        at JSON.parse (<anonymous>)
        at Socket.child.stdout.on (/app/node_modules/bigjq/index.js:17:29)
        at emitNone (events.js:111:20)
        at Socket.emit (events.js:208:7)
        at endReadableNT (_stream_readable.js:1064:12)
        at _combinedTickCallback (internal/process/next_tick.js:139:11)
        at process._tickCallback (internal/process/next_tick.js:181:9)
    query =  { v: 3,
      q: { find: { 'out.b1': 'bQI=' }, limit: 2 },
      r: 
       { f: '[{ block: .blk.i?, timestamp: .blk.t?, content: .out[1].s2 }]' } }
    response =  { errors: [ 'SyntaxError: Unexpected end of JSON input' ] }
    
    

    可以从日志中看到,这段命令错误的原因在于对列表[]进行值选择.blk,那么根据jq的命令,正确的写法应该如下所示:

    {
      "v": 3,
      "q": {
        "find": { "out.h1": "6d02" },
        "limit": 10
      },
      "r": {
        "f": "[.[] | { block: .blk.i?, timestamp: .blk.t?, content: .out[1].s2 }]"
      }
    }
    

    我把此处修改为首先获取列表中的每一个对象,对每个对象进行管道操作,提取其中的blk.i,blk.t 和s2作为3个新的字段的值,组成一个新的对象列表。这样就可以返回正确的答案了(谁认识Unwriter麻烦把这个文档中的小错误告知他)。

    感兴趣的朋友可以在浏览器测试一下上面前后两个bitquery,来验证一下是否能正确执行。

    正确的结果应该如下所示(篇幅限制这里limit 3)

    {
        "u": [],
        "c": [
            {
                "block": 581226,
                "timestamp": 1557152175,
                "content": "capitalism has no answers when it comes to the peoples' interests."
            },
            {
                "block": 581226,
                "timestamp": 1557152175,
                "content": "Facebook just won't stop! They are now planning to have their own coin. http://bit.ly/2DSKT6j\nWill this be a good news or bad for the crypto market? That link is from Cointelegraph."
            },
            {
                "block": 581224,
                "timestamp": 1557150641,
                "content": "Time for another change of topic for my next Monday Memo. Maybe something to do with recreation. Yes, that'll do.\n\nIf you're into hiking like I am, try taking new paths or trails from time to time."
            }
        ]
    }
    

    结语

    这些技术文档是我自己学习变形虫协议的时候的学习总结,希望分享给各位开发者。我也希望更多开发者能够了解bsv所能实现的强大功能,多多参与进来,共同构建出更多好玩的应用,让区块链技术能够真正落地,不要仅仅停留在炒币和嘴炮上。

    写这些文章是会花费很多时间,也需要查阅很多资料,甚至需要自己亲自去一次次的真机实验,但是我相信这并不是无用功,通过写这些技术文档,我对这些技术的了解更为深入,也学习到很多思想和方法,我相信比特币是proof of work,只有真正脚踏实地地work,才能让比特币的价值得以沉淀,让比特币的精神真正发扬下去。欢迎各位技术大神交流指教。

    展开全文
  • BSV链常用接口使用

    2021-02-03 21:40:19
    返回以bsv为单位的钱包总余额 listaddressgroupings 显示bsv钱包中所有地址余额 请求示例 bitcoin-cli -rpcuser=admin -rpcpassword=123456 -rpcport=18443 listaddressgroupings curl --user admin:123456 --data-...
  • BSV Planaria框架技术总结一 节点搭建 变形虫Planaria是Unwriter大神基于bitdb在bsv链上开发的一个可编程化的持久层框架,关于变形虫的特点和编程思想,可以参考我之前的文章,强烈建议先看完前文再阅读本文。 ...
  • 比特币最强大的特性就是单一、可扩容、全球通用——任何事件都可以以一种通用格式生成单一记录,这些记录提供了信息交流与互操作性的机会,这是前所未有的,可以解决全球信息系统面临的最大挑战之一:整合。...
  • 协会今天宣布,BSV技术标准委员会(TSC)——一个由比特币协会提供支持的BSV生态系统行业标准化组织——已发布了该组织2021-2023年的路线图。 TSC的成立旨在使比特币技术的发展专业化,从而为大型企业、消费者和政府...
  • TSC是由比特币协会提供支持的组织,其主要目标是通过创建在BSV生态系统中实施的共同技术标准来促进行业协作,其成功有赖于行业利益相关者和企业在行业内的参与程度。在整个路线图制定过程中的各个阶段,TSC允许感...
  • BSV的星辰大海,Metanet之愿景

    千次阅读 2020-06-15 15:49:48
    BSV的星辰大海,Metanet之愿景 本文首发于知乎专栏 今天我们谈一谈metanet,这个被各大bsver所追捧的新一代网络架构究竟为何方神圣。 从去年11月分叉的时候,csw博士正式提出了这个被称为”元网“的宏大愿景,到近期...
  • BSV链常用接口使用 注:以下rpc接口测试均在开发网络完成 区块链API getblockcount 获取最新区块高度 getblockhash 获取指定高度区块的哈希 getblock 获取指定哈希的区块信息 挖矿类API generatetoaddress 挖矿 ...
  • 编程马拉松编程阶段会一直持续到8月18日,因此您还有足够的时间来准备参赛作品,并有机会向10万美元BSV奖池发起竞争。此外,随着Bitcoin SV开发者大会(2020)将在编程马拉松的线上比赛期间举行,您还可以学习全新的...
  • 聊聊比特币BSV上的智能合约(一) 之前的文章谈到过比特币UTXO模型上实现智能合约和以太坊智能合约的区别,没有读过的同学出门左转: 浅谈比特币UTXO模型和以太坊账户模型的优劣 那篇文章里提到,由于UTXO架构和账户...
  • title: ‘2020.07 BSV 线上研讨会:Bitcoin SV 应用层协议’ date: 2020-10-04 23:00:00 comments: true status: public categories: [Bitcoin] tags: Bitcoin, BSV, 应用层协议 num: Bt-006-2010 (BSV Webinar) ...
  • Coinbase交易与普通交易具有相同的格式,但与普通交易不同的是: 只有一个交易输入。 交易输入的前序输出哈希是0000…0000。 交易输入的前序输出index是0xffffff。 交易输入的前序输出脚本是一个任意字节数组,在...
  • BTC、BCH和BSV三者到底有什么区别?

    万次阅读 2018-12-06 14:45:03
    第三种是BSV。那这三种货币到底有什么区别呢?  BTC  BTC现在是继承了比特币绝大多数遗产,包括冠名权和整个生态。是目前共识最大的比特币。BTC成功地维持了1M区块和实现了隔离见证。  BTC的主要追求是主...
  • BSV有一个基于UTXO的层一token方案,但该方案无法完全在层一辨别token的真伪。有两种思路来解决这个问题: 在tx中携带历史tx链,但该方案引入了tx大小膨胀的问题。 在二层识别正确的UTXO集合,相当于把一大部分防伪...
  • Jimmy Nguyen 比特币协会创始主席 BSV在阿联酋 比特币协会最近结束了为期两周的阿联酋之旅,参与了地区性区块链和投资社区的活动,并展示了Bitcoin SV如何帮助阿联酋和迪拜等现代城市实现其雄心勃勃的发展目标。...
  • 重点围绕如何使用比特币私钥对交易进行签名,包括:交易摘要的计算、什么是DER格式以及签名的序列化等内容。 【线上研讨会】比特币(Bitcoin SV)签名技术(三)——对交易签名 比特币(Bitcoin SV)签名技术(四)...
  • BSV节点升级到v1.0.1版本后,getwalletinfo 命令执行失败: Safemode:Warning:Wedonotappeartofullyagreewithourpeers!Youmayneedtoupgrade,orothernodesmayneedtoupgrade. 解决方法: ./bitcoind --daemon -u...
  • 虽然ABC和BSV有差异,但是在最底层的交易格式上并无差别,而且BSV还主动移除了自动重放攻击保护,这就造成同样一笔交易能在两条链里进行有效广播;通俗一点就是,只要我们花了BCH,对方就可以伪造支取BSV,反之亦然...
  • 各种格式的文件使用工具打开

    万次阅读 2013-05-23 10:24:04
    SDF 系统数据文件格式—Legacy Unisys(Sperry)格式 SDK Roland S—系列软盘映像 SDL Smart Draw库文件 SDR Smart Draw绘图文件 SDS 原始Midi抽样转储标准文件 SDT SmartDraw模板 SDV 分号分隔的...
  • 引言 在遇到大数据时,不同数据处理工具包的优劣, 是否拥有丰富的数据处理函数; 是否读取数据够快;... 是否需要额外设备(例如GPU)的支持等等。... 存储一个大的文件,存成csv格式需要10G,
  • 以下包含我能做出来可以做出来播放的视频格式(测试ok),可能还有ogg和wmv的格式可以播放,但我没测试通过,所以不展示 if(strT == "video/mp4"||//.mp4文件 strT == "video/webm"||//.webm文件 strT == "flv"||//.flv...
  • " title="+1" name="I1_1376895554093" src="https://apis.google.com/u/0/_/+1/fastbutton?bsv=o&usegapi=1&size=small&hl=en-US&origin=http%3A%2F%2Fblog.nosqlfan.com&url=...
  • 而实现方案,简单来说就是利用BSV区块链的各种优势,将token资产以及操作写入到比特币的交易中,并利用比特币的公私钥地址系统来进行鉴权(authentication)和授权(authorization),使用自动代理节点来执行智能...
  • 序列化数据- 使用简明二进制对象表示 (CBOR) 数据格式进行简单、二进制友好的数据交换。 身份验证数据- 使用数字签名或消息身份验证代码 (MAC) 算法保护数据的完整性。 保护数据- 使用标准化的经过身份验证的加密...
  • 为了耍酷,我们偶尔需要一些含有特殊字母组合的靓号地址,例如 ...npm install bsv 安装bsv库。 bsv库(https://docs.moneybutton.com/docs/bsv-overview.html)是用于比特币BSV的数据结构操作方法的库。 ..

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 256
精华内容 102
关键字:

bsv格式