栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 前沿技术 > 区块链 > 其他

区块链开发教程 # 1 比特币公链

其他 更新时间:发布时间: 百科书网 趣学号

科普向介绍请参考 tutorial。技术开发教程请参考本文。本系列文章更偏向技术入门。参考网上资料根据个人理解整理而成。本文主要参考廖雪峰博客。

1 区块链1.0时代:Bitcoin 比特币

中本聪2008年发布《Bitcoin: A Peer-to-Peer Electronic Cash System》宣布了Bitcoin的诞生。人类经济生活最早期是物物交换,随着经济和贸易的发展,迫切需要一种“一般等价物”来作为商品交换的“中介”,这种一般等价物就是货币。货币由贝壳变成了金属再变成了纸币。而20-21世纪信息革命造成社会巨变,货币也进一步进化成了数字货币/加密货币。比特币和网银/支付宝/微信不同的是,比特币不需要一个类似银行的中央信任机构,就可以通过全球P2P网络进行发行和流通。比特币通过技术手段解决了现金电子化以后交易的清结算问题。传统的基于银行等金融机构进行交易,本质上是通过中央数据库,确保两个交易用户的余额一增一减。这些交易高度依赖专业的开发和运维人员,以及完善的风控机制。比特币则是通过区块链技术,把整个账本全部公开,人手一份,全网相同,因此,修改账本不会被其他人承认。比特币的区块链就是一种存储了全部账本的链式数据库,通过一系列密码学理论进行防篡改,防双花。
从现金和存款的角度看,现金是M0,而银行存款是M1和M2。银行存款本质上已经不是现金,而是用户的资产,对应着银行的负债。因为银行只记录用户在银行的资产余额,因此,用户A通过银行把100元转账给用户B的时候,用户A的资产减少100元,相应的,用户B的资产增加100元,银行对用户A和用户B的总负债不变。换句话说,存款是用户的“提款期权”。比特币由用户自己保存,不需要经过银行。比特币解决的是现金电子化后无需中央信任机构的交易问题,即M0如何通过网络进行价值传输。比特币背后对应的技术就是区块链。

1.1 区块链原理


比特币使用两种哈希算法,一种是对数据进行两次SHA-256计算,这种算法被称为hash256或者dhash。另一种是先计算SHA-256,再计算RipeMD160,这种算法被称为hash160。
每个区块都有一个唯一的哈希标识,被称为区块哈希,指向上一个区块。每一个区块还有一个Merkle哈希用来确保该区块的所有交易记录无法被篡改。区块链中的主要数据就是一系列交易,第一条交易通常是Coinbase交易,也就是矿工的挖矿奖励,后续交易都是用户的交易。

const
    bitcoin = require('bitcoinjs-lib'),
    createHash = require('create-hash');
function standardHash(name, data) {
    let h = createHash(name);
    return h.update(data).digest();
}

function hash160(data) {
    let h1 = standardHash('sha256', data);
    let h2 = standardHash('ripemd160', h1);
    return h2;
}

function hash256(data) {
    let h1 = standardHash('sha256', data);
    let h2 = standardHash('sha256', h1);
    return h2;
}
console.log('ripemd160 = ' + standardHash('ripemd160', s).toString('hex'));
console.log('  hash160 = ' + hash160(s).toString('hex'));
console.log('   sha256 = ' + standardHash('sha256', s).toString('hex'));
console.log('  hash256 = ' + hash256(s).toString('hex'));
Merkle Hash

在区块的头部,有一个Merkle Hash字段,它记录了本区块所有交易的Merkle Hash:

计算方法:先对每笔交易计算hash,然后对hash值自底向上2个一组拼接然后hash,不够2个一组的复制自身拼接然后hash。从Merkle Hash的计算方法可以得出结论:修改任意一个交易哪怕一个字节,或者交换两个交易的顺序,都会导致Merkle Hash验证失败,也就会导致这个区块本身是无效的,所以,Merkle Hash记录在区块头部,它的作用就是保证交易记录永远无法修改。

Block Hash

区块本身用Block Hash——也就是区块哈希来标识。但是,一个区块自己的区块哈希并没有记录在区块头部,而是通过计算区块头部的哈希得到的。区块链的第一个区块(又称创世区块)并没有上一个区块,因此,它的Prev Hash被设置为00000000…000。

Note:每个新矿机加入挖矿需要硬盘存储过去的账本,目前是几百个G的账本。矿池只需要计算,有专门负责存储账本的设备。

1.2 P2P交易原理 签名

比特币采用的签名算法是椭圆曲线签名算法:ECDSA,使用的椭圆曲线是一个已经定义好的标准曲线secp256k1: y 2 = x 3 + 7 y^2=x^3+7 y2=x3+7。比特币的私钥是一个随机的非常大的256位整数。比特币账本是全网公开的,任何人都可以根据公钥查询余额,只有持有私钥可以控制对应账户。

钱包

比特币钱包实际上就是帮助用户管理私钥的软件。本地钱包、手机钱包、在线钱包(第三方在线服务商保存私钥)、硬件钱包。

交易

每个区块都记录了至少一笔交易,一笔交易就是把一定金额的比特币从一个输入转移到一个输出:

比特币协议规定一个输出必须一次性花完,一个输入可以对应多个输出:

不能直接凭空产生数或求和,输入需要关联到交易,一个交易对应多输入1输出:

多输入多输出:

在实际的交易中,输入比输出要稍微大一点点,这个差额就是隐含的交易费用(gas),交易费用会算入当前区块的矿工收入中作为矿工奖励的一部分:

未花费的输出被称为UTXO(Unspent Transaction Ouptut)。
当我们要简单验证某个交易的时候,例如,对于交易f36abd,它记录的输入是3f96ab,索引号是1(索引号从0开始,0表示第一个输出,1表示第二个输出,以此类推),我们就根据3f96ab找到前面已发生的交易,再根据索引号找到对应的输出是0.5个比特币,所以,这笔交易的输入总计是0.5个比特币,输出分别是0.4个比特币和0.09个比特币,隐含的交易费用是0.01个比特币:

私钥
const bitcoin = require('bitcoinjs-lib');
let keyPair = bitcoin.ECPair.makeRandom();
console.log('private key = ' + keyPair.d);

私钥编码格式/钱包导入格式:WIF(Wallet Import Format),带校验的Base58编码
在32字节的私钥前添加一个0x80字节前缀,对其计算4字节的校验码(两次SHA256,取开头4字节),附加到最后,一共得到37字节的数据:

进行Base58编码,得到总是以5开头的字符串编码:

const wif = require('wif');
// 十六进制表示的私钥:
let privateKey = '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d';
// 对私钥编码:
let encoded = wif.encode(
        0x80, // 0x80前缀
        Buffer.from(privateKey, 'hex'), // 转换为字节
        false // 非压缩格式
);
console.log(encoded);

压缩的私钥格式会在32字节的私钥前后各添加一个0x80字节前缀和0x01字节后缀,共34字节的数据,对其计算4字节的校验码,附加到最后,一共得到38字节的数据:


得到总是以K或L开头的字符串编码。非压缩的格式几乎已经不使用了。

公钥和地址

私钥k根据ECDSA计算出2个256位整数(x,y),即为非压缩格式公钥。根据ECC曲线特点,由x也可推算出y,但需要知道y的奇偶性。因此,可以根据(x, y)推算出x’,作为压缩格式的公钥。压缩格式的公钥实际上只保存x这一个256位整数,但需要根据y的奇偶性在x前面添加02或03前缀,y为偶数时添加02,否则添加03。

let
    wif = 'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617',
    ecPair = bitcoin.ECPair.fromWIF(wif); // 导入私钥
// 计算公钥:
let pubKey = ecPair.getPublicKeyBuffer(); // 返回Buffer对象
console.log(pubKey.toString('hex')); // 02或03开头的压缩公钥

要特别注意,比特币的地址并不是公钥,而是公钥的哈希,即从公钥能推导出地址,但从地址不能反推公钥,因为哈希函数是单向函数。

以压缩格式的公钥为例,从公钥计算地址的方法是:

let
    publicKey = '02d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c',
    ecPair = bitcoin.ECPair.fromPublicKeyBuffer(Buffer.from(publicKey, 'hex')); // 导入公钥
// 计算地址:
let address = ecPair.getAddress();
console.log(address); // 1开头的地址
签名

签名算法是使用私钥签名,公钥验证的方法,对一个消息的真伪进行确认。如果一个人持有私钥,他就可以使用私钥对任意的消息进行签名,即通过私钥sk对消息message进行签名,得到signature:

let
    message = 'a secret message!', // 原始消息
    hash = bitcoin.crypto.sha256(message), // 消息哈希
    wif = 'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617',
    keyPair = bitcoin.ECPair.fromWIF(wif);
// 用私钥签名:
let signature = keyPair.sign(hash).toDER(); // ECSignature对象
// 打印签名:
console.log('signature = ' + signature.toString('hex'));
// 打印公钥以便验证签名:
console.log('public key = ' + keyPair.getPublicKeyBuffer().toString('hex'));
let signAsStr = '304402205d0b6e817e01e22ba6ab19c0'
              + 'ab9cdbb2dbcd0612c5b8f990431dd063'
              + '4f5a96530220188b989017ee7e830de5'
              + '81d4e0d46aa36bbe79537774d56cbe41'
              + '993b3fd66686'

let
    signAsBuffer = Buffer.from(signAsStr, 'hex'),
    signature = bitcoin.ECSignature.fromDER(signAsBuffer), // ECSignature对象
    message = 'a secret message!', // 原始消息
    hash = bitcoin.crypto.sha256(message), // 消息哈希
    pubKeyAsStr = '02d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c',
    pubKeyAsBuffer = Buffer.from(pubKeyAsStr, 'hex'),
    pubKeyOnly = bitcoin.ECPair.fromPublicKeyBuffer(pubKeyAsBuffer); // 从public key构造ECPair

// 验证签名:
let result = pubKeyOnly.verify(hash, signature);
console.log('Verify result: ' + result);
1.3 挖矿原理

比特币的挖矿原理是工作量证明机制POW,Proof of Work

矿工:不停地计算,试图把新的交易打包成新的区块并附加到区块链上
挖矿:每打包一个新的区块,打包该区块的矿工就可以获得一笔比特币作为奖励。

工作量证明 POW

工作量证明:计算非常复杂,需要消耗一定的时间,但验证过程相对简单的运算(实际上哈希满足这种要求)。
通过改变区块头部的一个nonce字段的值,计算机可以计算出不同的区块哈希值:

直到计算出某个特定的哈希值的时候,计算结束。这个哈希和其他的哈希相比,它的特点是前面有好几个0:

hash256(block data, nonce=0) = 291656f37cdcf493c4bb7b926e46fee5c14f9b76aff28f9d00f5cca0e54f376f
hash256(block data, nonce=1) = f7b2c15c4de7f482edee9e8db7287a6c5def1c99354108ef33947f34d891ea8d
hash256(block data, nonce=2) = b6eebc5faa4c44d9f5232631f39ddf4211443d819208da110229b644d2a99e12
hash256(block data, nonce=3) = 00aeaaf01166a93a2217fe01021395b066dd3a81daffcd16626c308c644c5246
hash256(block data, nonce=4) = 26d33671119c9180594a91a2f1f0eb08bdd0b595e3724050acb68703dc99f9b5
hash256(block data, nonce=5) = 4e8a3dcab619a7ce5c68e8f4abdc49f98de1a71e58f0ce9a0d95e024cce7c81a
hash256(block data, nonce=6) = 185f634d50b17eba93b260a911ba6dbe9427b72f74f8248774930c0d8588c193
hash256(block data, nonce=7) = 09b19f3d32e3e5771bddc5f0e1ee3c1bac1ba4a85e7b2cc30833a120e41272ed
...
hash256(block data, nonce=124709132) = 00000000fba7277ef31c8ecd1f3fef071cf993485fe5eab08e4f7647f47be95c

比特币挖矿的工作量证明原理就是,不断尝试计算区块的哈希,直到计算出一个特定的哈希值,它比难度值要小。
对于给定难度的SHA-256:假设我们用难度N表示必须算出首位N个0,那么,每增加一个难度,计算量将增加16倍。
对于比特币挖矿来说,就是先给定一个难度值,然后不断变换nonce,计算Block Hash,直到找到一个比给定难度值低的Block Hash,就算成功挖矿。

关于16倍的原因:这里的一位0是16进制数。如果选择的hash算法足够优秀,理论上遍历相邻16个数得到的hash值会遍历0-F。这里是近似计算。【在不同情形下难度也可能指bit位,就是2倍而不是16倍】

矿工先确定Prev Hash,Merkle Hash,Timestamp,bits然后,不断变化nonce来计算哈希,直到找出连续n个0开头的哈希值。
实际的难度是根据bits由一个公式计算出来,比特币协议要求计算出的区块的哈希值比难度值要小,这个区块才算有效:

Difficulty = 402937298
           = 0x18 0455d2
           = 0x0455d2 * 28 * (0x18 - 3)
           = 106299667504289830835845558415962632664710558339861315584
           = 0x00000000000000000455d2000000000000000000000000000000000000000000

注意,难度值越小,说明哈希值前面的0越多,计算难度越大。(难度值不等于计算难度)。
比特币网络的难度是不断变化的,它的难度保证大约每10分钟产生一个区块,而难度值在每2015个区块调整一次:如果区块平均生成时间小于10分钟,说明全网算力增加,难度也会增加,如果区块平均生成时间大于10分钟,说明全网算力减少,难度也会减少。因此,难度随着全网算力的增减会动态调整。
比特币设计时本来打算每2016个区块调整一次难度,也就是两周一次,但是由于第一版代码的一个bug,实际调整周期是2015个区块。根据比特币每个区块的难度值和产出时间,就可以推算出整个比特币网络的全网算力。目前全网算力超过了100EH/s,而单机CPU算力不过几M,GPU算力也不过1G,所以,单机挖矿的成功率几乎等于0。比特币挖矿已经从早期的CPU、GPU发展到专用的ASIC芯片构建的矿池挖矿。

当某个矿工成功找到特定哈希的新区块后,他会立刻向全网广播该区块。其他矿工在收到新区块后,会对新区块进行验证,如果有效,就把它添加到区块链的尾部。同时说明,在本轮工作量证明的竞争中,这个矿工胜出,而其他矿工都失败了。失败的矿工会抛弃自己当前正在计算还没有算完的区块,转而开始计算下一个区块,进行下一轮工作量证明的竞争。比特币总量被限制为约2100万个比特币,初始挖矿奖励为每个区块50个比特币,以后每4年减半。

共识算法

如果两个矿工在同一时间各自找到了有效区块,注意,这两个区块是不同的,因为coinbase交易不同,所以Merkle Hash不同,区块哈希也不同。但它们只要符合难度值,就都是有效的。这个时候,网络上的其他矿工应该接收哪个区块并添加到区块链的末尾呢?答案是,都有可能。通常,矿工接收先收到的有效区块,由于P2P网络广播的顺序是不确定的,不同的矿工先收到的区块是有可能的不同的。这个时候,我们说区块发生了分叉:

在分叉的情况下,有的矿工在绿色的分叉上继续挖矿,有的矿工在蓝色的分叉上继续挖矿:

总有一个分叉首先挖到后续区块,这个时候,由于比特币网络采用最长分叉的共识算法,绿色分叉胜出,蓝色分叉被废弃,整个网络上的所有矿工又会继续在最长的链上继续挖矿。

由于区块链虽然最终会保持数据一致,但是,一个交易可能被打包到一个后续被孤立的区块中。所以,要确认一个交易被永久记录到区块链中,需要对交易进行确认。如果后续的区块被追加到区块链上,实际上就会对原有的交易进行确认,因为链越长,修改的难度越大。一般来说,经过6个区块确认的交易几乎是不可能被修改的。

1.4 可编程支付原理

比特币的支付实际上并不是直接支付到对方的地址,而是一个脚本,这个脚本的意思是:谁能够提供另外一个脚本,让这两个脚本能顺利执行通过,谁就能花掉这笔钱。比特币交易的输出是一个锁定脚本,而下一个交易的输入是一个解锁脚本。必须提供一个解锁脚本,让锁定脚本正确运行,那么该输入有效,就可以花

FROM: UTXO Hash#index
AMOUNT: 0.5 btc
TO: OP_DUP OP_HASH160  OP_EQUALVERIFY OP_CHECKSIG

以真实的比特币交易为例,某个交易的某个输出脚本是76a914dc…489c88ac这样的数据,而花费该输出的某个交易的输入脚本是48304502…14cf740f:

先看锁定脚本,锁定脚本的第一个字节76翻译成比特币脚本的字节码就是OP_DUP,a9翻译成比特币脚本的字节码就是OP_HASH160。14表示这是一个20字节的数据,于是我们得到20字节的数据。最后两个字节,88表示字节码OP_EQUALVERIFY,ac表示字节码OP_CHECKSIG,所以整个锁定脚本是:

        OP_DUP 76
    OP_HASH160 a9
          DATA 14 (dc5dc65c...fe9f489c)
OP_EQUALVERIFY 88
   OP_CHECKSIG ac

解锁脚本。解锁脚本的第一个字节48表示一个72字节长度的数据。接下来的字节21表示一个33字节长度的数据。因此,该解锁脚本实际上只有两个数据。

DATA 48 (30450221...68fa9b01)
DATA 21 (03dd8763...14cf740f)

接下来,要验证这个交易是否有效。要验证这个交易,首先,我们要把解锁脚本和锁定脚本拼到一块,然后,开始执行这个脚本:

     	  DATA 48 (30450221...68fa9b01)
          DATA 21 (03dd8763...14cf740f)
        OP_DUP 76
    OP_HASH160 a9
          DATA 14 (dc5dc65c...fe9f489c)
OP_EQUALVERIFY 88
   OP_CHECKSIG ac

比特币脚本是一种基于栈结构的编程语言,先准备一个空栈,用来执行比特币脚本。然后执行第一行代码,由于前俩行代码是数据,所以直接把数据压栈:

OP_DUP指令,这条指令会把栈顶的元素复制一份。执行OP_HASH160指令,这条指令会计算栈顶数据的hash160,也就是先计算SHA-256,再计算RipeMD160。对十六进制数据03dd8763f8c3db6b77bee743ddafd33c969a99cde9278deb441b09ad7c14cf740f计算hash160后得到结果dc5dc65c7e6cc3c404c6dd79d83b22b2fe9f489c,然后用结果替换栈顶数据:

接下来的指令是一条数据,所以直接压栈:

执行OP_EQUALVERIFY指令,它比较栈顶两份数据是否相同(比较地址是否正确),如果相同,则验证通过,脚本将继续执行,如果不同,则验证失败,整个脚本就执行失败了。在这个脚本中,栈顶的两个元素是相同的,所以验证通过,脚本将继续执行(栈顶两份数据移除):

最后,执行OP_CHECKSIG指令,它使用栈顶的两份数据,第一份数据被看作公钥,第二份数据被看作签名,这条指令就是用公钥来验证签名是否有效。根据验证结果,成功存入1,失败存入0。最后,当整个脚本执行结束后,检查栈顶元素是否为0,如果不为0,那么整个脚本就执行成功,这笔交易就被验证为有效的。

因为比特币的脚本不含条件判断、循环等复杂结构。上述脚本就是对输入的两个数据视作签名和公钥,然后先验证公钥哈希是否与地址相同,再根据公钥验证签名,这种标准脚本称之为P2PKH(Pay to Public Key Hash)脚本。

当小明给小红支付一笔比特币时,实际上小明创建了一个锁定脚本,该锁定脚本中引入了小红的地址。要想通过解锁脚本花费该输出,只有持有对应私钥的小红才能创建正确的解锁脚本(因为解锁脚本包含的签名只有小红的私钥才能创建),因此,小红事实上拥有了花费该输出的权利。

使用钱包软件创建的交易都是标准的支付脚本,但是,比特币的交易本质是成功执行解锁脚本和锁定脚本,所以,可以编写各种符合条件的脚本。比如,有人创建了一个交易,它的锁定脚本像这样:

OP_HASH256
      DATA 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000
  OP_EQUAL

谁能够提供一个数据,它的hash256等于6fe28c0a…,谁就可以花费这笔输出。所以,解锁脚本实际上只需要提供一个正确的数据,就可以花费这笔输出。
比特币的脚本通过不同的指令还可以实现更灵活的功能。例如,多重签名可以让一笔交易只有在多数人同意的情况下才能够进行。最常见的多重签名脚本可以提供3个签名,只要任意两个签名被验证成功,这笔交易就可以成功。

FROM: UTXO Hash#index
AMOUNT: 10.5 btc
TO: P2SH: OP_2 pk1 pk2 pk3 OP_3 OP_CHECKMULTISIG

从比特币支付的脚本可以看出,比特币支付的本质是由程序触发的数字资产转移。这种支付方式无需信任中介的参与,可以在零信任的基础上完成数字资产的交易,这也是为什么数字货币又被称为可编程的货币。

由此催生出了智能合约:当一个预先编好的条件被触发时,智能合约可以自动执行相应的程序,自动完成数字资产的转移。保险、贷款等金融活动在将来都可以以智能合约的形式执行。智能合约以程序来替代传统的纸质文件条款,并由计算机强制执行,将具有更高的更低的信任成本和运营成本。

1.5 UTXO模型

UTXO:Unspent TX Output
给定任何一个区块,计算当前所有的UXTO金额之和,等同于自创世区块到给定区块的挖矿奖励之和。想要确定某个人拥有的比特币,并无法通过某个账户查到,必须知道此人控制的所有UTXO金额之和。

在钱包程序中,钱包管理的是一组私钥,对应的是一组公钥和地址。钱包程序必须从创世区块开始扫描每一笔交易,如果:

  1. 遇到某笔交易的某个Output是钱包管理的地址之一,则钱包余额增加;
  2. 遇到某笔交易的某个Input是钱包管理的地址之一,则钱包余额减少。

如果刚装了一个新钱包,导入了一组私钥,在钱包扫描完整个比特币区块之前,是无法得知当前管理的地址余额的。怎么加速计算?
对区块链进行查询之前,先扫描整个区块链,重建一个类似关系数据库的地址-余额映射表。一开始,这是一个空表。每当扫描一个区块的所有交易后,某些地址的余额增加,另一些地址的余额减少,两者之差恰好为区块奖励:

扫描完所有区块后,我们就得到了整个区块链所有地址的完整余额记录,查询的时候,并不是从区块链查询,而是从本地数据库查询。大多数钱包程序使用LevelDB来存储这些信息,手机钱包程序则是请求服务器,由服务器查询数据库后返回结果。
可见,比特币的区块链记录的是修改日志,而不是当前状态。

1.6 Segwit地址

Segwit地址(隔离见证地址)。在比特币区块链上,经常可以看到类似bc1qmy63mjadtw8nhzl69ukdepwzsyvv4yex5qlmkd这样的以bc开头的地址,这种地址就是隔离见证地址。
Segwit地址有好几种,一种是以3开头的隔离见证兼容地址(Nested Segwit Address),从该地址上无法区分到底是多签地址还是隔离见证兼容地址,好处是钱包程序不用修改,可直接付款到该地址。
另一种是原生隔离见证地址(Native Segwit Address),即以bc开头的地址,它本质上就是一种新的编码方式。

bc地址使用的不是Base58编码,而是Bech32编码,它的算法是:

  • 根据公钥计算hash160;
  • 使用Base32编码得到更长的编码;
  • 以bc作为识别码进行编码并带校验。

Bech32编码由两部分组成:一部分是bc这样的前缀,被称为HRP(Human Readable Part,用户可读部分),另一部分是特殊的Base32编码,使用字母表qpzry9x8gf2tvdw0s3jn54khce6mua7l,中间用1连接。对一个公钥进行Bech32编码的代码如下:

const
    bitcoin = require('bitcoinjs-lib'),
    bech32 = require('bech32'),
    createHash = require('create-hash');

// 压缩的公钥:
let publicKey = '02d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c';

// 计算hash160:
let
    sha256 = createHash('sha256'),
    ripemd160 = createHash('ripemd160'),
    hash256 = sha256.update(Buffer.from(publicKey, 'hex')).digest(),
    hash160 = ripemd160.update(hash256).digest();

// 计算bech32编码:
let words = bech32.toWords(hash160);
// 头部添加版本号0x00:
words.unshift(0);

// 对地址编码:
let address = bech32.encode('bc', words);
console.log(address); // bc1qmy63mjadtw8nhzl69ukdepwzsyvv4yex5qlmkd

和Base58地址相比,Bech32地址的优点有:

  • 不用区分大小写,因为编码用的字符表没有大写字母;
  • 有个固定前缀,可任意设置,便于识别;
  • 生成的二维码更小。

它的缺点是:

  • 和现有地址不兼容,钱包程序必须升级;
  • 使用1作为分隔符,却使用了字母l,容易混淆;
  • 地址更长,有42个字符。

引入Segwit地址目的是为了解决比特币交易的延展性(Transaction Malleability)攻击。

tx = ... input#index ... signature ... output-script ...
tx-hash = dhash(tx)
  • 延展性攻击
    只有私钥持有人才能正确地签名,所以,只要签名是有效的,tx本身就应该固定下来。在ECDSA签名算法上,算法基于私钥计算的签名实际上是两个整数,记作(r, s),但由于椭圆曲线的对称性,(r, -s mod N)实际上也是一个有效的签名(N是椭圆曲线的固定参数之一)。换句话说,对某个交易进行签名,总是可以计算出两个有效的签名,并且这两个有效的签名还可以互相计算出来。
    黑客可以在某一笔交易发出但并未落块的时间内,对签名进行修改,使之仍是一个有效的交易。注意黑客并无法修改任何输入输出的地址和金额,仅能修改签名。但由于签名的修改,使得整个交易的哈希被改变了。如果修改后的交易先被打包,虽然原始交易会被丢弃,且并不影响交易安全,但这个延展性攻击可用于攻击交易所。
    要解决延展性攻击的问题,有两个办法,一是对交易签名进行归一化(Normalize)。因为ECDSA签名后总有两个有效的签名(r, s)和(r, -s mod N),那只接受数值较小的那个签名,为此比特币引入了一个SCRIPT_VERIFY_LOW_S标志仅接受较小值的签名。
    另一个办法是把签名数据移到交易之外,这样交易本身的哈希就不会变化。不含签名的交易计算出的哈希称为wtxid,为此引入了一种新的隔离见证地址。
1.7 HD钱包

出于保护隐私的目的,同一用户如果控制的UTXO其地址都是不同的,那么很难从地址获知某个用户的比特币持币总额。但是,管理一组成千上万的地址,意味着管理成千上万的私钥,管理起来非常麻烦。
能不能只用一个私钥管理成千上万个地址?实际上是可以的。虽然椭圆曲线算法决定了一个私钥只能对应一个公钥,但是,可以通过某种确定性算法,先确定一个私钥k1,然后计算出k2、k3、k4……等其他私钥,就相当于只需要管理一个私钥,剩下的私钥可以按需计算出来。根据某种确定性算法,只需要管理一个根私钥,即可实时计算所有“子私钥”的管理方式,称为HD钱包。
HD,Hierarchical Deterministic意思是分层确定性。先确定根私钥root,然后根据索引计算每一层的子私钥:

这种计算被称为衍生(Derivation)。
HD钱包采用的计算子私钥的算法是一个扩展的512位私钥,记作xprv,它通过SHA-512算法配合ECC计算出子扩展私钥,仍然是512位。通过扩展私钥可计算出用于签名的私钥以及公钥。
简单来说,只要给定一个根扩展私钥(随机512位整数),即可计算其任意索引的子扩展私钥。扩展私钥总是能计算出扩展公钥,记作xpub:

从xprv及其对应的xpub可计算出真正用于签名的私钥和公钥。之所以要设计这种算法,是因为扩展公钥xpub也有一个特点,那就是可以直接计算其子层级的扩展公钥:

xpub只包含公钥,不包含私钥,因此,可以安全地把xpub交给第三方(例如,一个观察钱包),它可以根据xpub计算子层级的所有地址,然后在比特币的链上监控这些地址的余额,但因为没有私钥,所以只能看,不能花。

HD钱包通过分层确定性算法,实现了以下功能:

  • 只要确定了扩展私钥xprv,即可根据索引计算下一层的任何扩展私钥;
  • 只要确定了扩展公钥xpub,即可根据索引计算下一层的任何扩展公钥;
  • 用户只需保存顶层的一个扩展私钥,即可计算出任意一层的任意索引的扩展私钥。

Reference
  1. 廖雪峰的教程
  2. 教程
  3. 区块链教程
转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/848401.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号