以比特币中的P2PKH的支付方式为例,在这种支付方式下,交易输出的锁定脚本格式为:
OP_DUP (对应操作码0x76)
OP_HASH160 (对应操作吗0xa9)
0x14 (0x01~0x4b, 表示把接下来相应的字节数压入栈)
Public key hash (支付地址)
OP_EQRIFY (对应操作码0x88)
OP_CHECKSIG (对应操作码0xac)
其中的public key hash就是对方的地址,public key hash就是比特币接收转账交易的地址,这个地址通过对公钥进行HASH160操作而来,即首先度公钥进行SHA256操作,然后再对得到的结果进行RIPEMD160的操作,得到一个20字节长度的哈希值,我们把这个作为比特币的接收地址,这个地址的产生放肆如下图所示。
在一般的区块链浏览器中是以16进制的形式展示的,而实际上比特币的地址是用base58check的编码形式展示的,Base58check的编码形式是比特币中特有的编码形式,编码的方法如下:
为了增加防止打印和转录错误的安全性,Base58Check是一种常用在比特币中的Base58编码格式,比特币有内置 的检查错误的编码。检验和是添加到正在编码的数据末端的额外4个字节。校验和是从编码的数据的哈希值中得到
的,所以可以用来检测并避免转录和输入中产生的错误。使用Base58check编码时,解码软件会计算数据的校验和 并和编码中自带的校验和进行对比。二者不匹配则表明有错误产生,那么这个Base58Check的数据就是无效的。一
个错误比特币地址就不会被钱包软件认为是有效的地址,否则这种错误会造成资金的丢失。
为了将数据(数字)转换成Base58Check格式,首先我们要对数据添加一个称作“版本字节”的前缀,这个前缀用来 识别编码的数据的类 型。例如,比特币地址的前缀是0(十六进制是0x00),而对私钥编码时前缀是128(十六进制是0x80)。 表4-1会列出一些常见版本的前缀。 接下来,我们计算“双哈希”校验和,意味着要对之前的结果(前缀和数据)运行两次SHA256哈希算法:
checksum = SHA256(SHA256(prefix+data))
在产生的长32个字节的哈希值(两次哈希运算)中,我们只取前4个字节。这4个字节就作为检验错误的代码或者校验和。校验码会添加到数据之后。
结果由三部分组成:前缀、数据和校验和。这个结果采用之前描述的Base58字母表编码。(这些内容摘自《精通比特币第二版》)
为了方便,我这里以python为例进行示范,同时我们选择比特币区块链浏览器中的一段数据作为示例,例子来源点击这里.
具体的代码如下:
"""本程序主要作用是将比特币脚本中的支付地址转换成Base58的格式"""
"""
下面的2个示例来自blockchain网站
具体链接: https://www.blockchain.com/btc/tx/08e08a40a2e82598f12a3482168601a059a8abb6033bafb4956a02f17672d527
示例:1
支付地址:1LC2jC4dEPnTFKn9JcqmCJ5okLbfsUoHKt
锁定脚本:
OP_DUP
OP_HASH160
d280684eeecafd8ac6d13cfd9c821ddabd857ad0
OP_EQUALVERIFY
OP_CHECKSIG
示例2:
支付地址:1EFFSQKVrsq7KgmqxRB8gyLzccLFj3KzLk
锁定脚本
OP_DUP
OP_HASH160
914b82c7d2a02402b3055a6131eb204eea62058e
OP_EQUALVERIFY
OP_CHECKSIG
"""
import base58
import hashlib
# 将16进制的字符串转换成比特币地址
def CalBitcoinAddress(hexHash160):
# 将16进制的字符串转换成二进制数组
binHash160 = bytes.fromhex(hexHash160)
# 比特币地址前缀为0x00,加入前缀
prefix_binHash160 = b'\x00' + binHash160
# 第一次求sha256
hash256 = hashlib.sha256(prefix_binHash160).digest()
# 第二次求sha256
double_hash256 = hashlib.sha256(hash256).digest()
# 取double hash256后面4个字节作为校验和
prefix_binHash160_checkSum = prefix_binHash160 + double_hash256[0:4]
# 进行base58编码
base58checkAddr = base58.b58encode(prefix_binHash160_checkSum)
return base58checkAddr
if __name__ == "__main__":
hexstr1 = "d280684eeecafd8ac6d13cfd9c821ddabd857ad0"
hexstr2 = "914b82c7d2a02402b3055a6131eb204eea62058e"
addr1 = CalBitcoinAddress(hexstr1)
addr2 = CalBitcoinAddress(hexstr2)
print("addr1: {0}".format(addr1))
print("addr2: {0}".format(addr2))
最终的输出结果如下:
可以看出转换的地址和比特币浏览器显示的地址相同。
P2PK支付方式,其输出脚本是接收转账方的公钥,而公钥的表示形式有两种,即非压缩公钥和压缩公钥。
公钥是在椭圆曲线上的一个点,由一对坐标(x,y)组成。公钥通常表示为前缀04紧接着两个256比特的数字。其中一个256比特数字是公钥的x坐标,另一个256比特数字是y坐标。前缀04是用来区分非压缩格式公钥, 压缩格式公钥是以02或者03开头。
下面是公钥的示例,其坐标x和y如下: x =
F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A
y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
下面是同样的公钥以520比特的数字(130个十六进制数字)来表达。这个520比特的数字以前缀04开头,紧接着是x及y 坐标,组成格式为04 x y:
K =
04F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE 52DDFE2E505BDB
引入压缩格式公钥是为了减少比特币交易的字节数,从而可以节省那些运行区块链数据库的节点磁盘空间。大部分比特币交易包含了公钥,用于验证用户的凭据和支付比特币。每个公钥有520比特(包括前缀,x坐标,y坐标)。如果每个区块有数百个交易,每天有成千上万的交易发生,区块链里就会被写入大量的数据。
一个公钥是一个椭圆曲线上的点(x, y)。而椭圆曲线实际是一个数学方程,曲线上的点实际是该方程的一个解。因此,如果我们知道了公钥的x坐标,就可以通过解方程y2 mod p = (x3 + 7) mod p得到y坐标。这种方案可以让我们只存储公钥的x坐标,略去y坐标,从而将公钥的大小和存储空间减少了256比特。每个交易所需要的字节数减少了近一半,随着时间推移,就大大节省了很多数据传输和存储。
未压缩格式公钥使用04作为前缀,而压缩格式公钥是以02或03作为前缀。需要这两种不同前缀的原因是:因为椭圆曲 线加密的公式的左边是y2 ,也就是说y的解是来自于一个平方根,可能是正值也可能是负值。更形象地说,y坐标可能在 x坐标轴的上面或者下面。椭圆曲线是对称的,从x轴看就像对称的镜子两面。因此,如果我们略去y坐标,就必须储存y的符号(正值或者负值)。
换句话说,对于给定的x值,我们需要知道y值在x轴的上面还是下面,因为它们代表椭圆曲线上不同的点,即不同的公钥。当我们在素数p阶的有限域上使用二进制算术计算椭圆曲线的时候,y坐标可能是奇数或者偶数,分别对应前面所讲的y值的正负符号。因此,为了区分y坐标的两种可能值,我们在生成压缩格式公钥时,如果y是偶数,则使用02作为前缀;如果y是奇数,则使用03作为前缀。这样就可以根据公钥中给定的x值,正确推导出对应的y坐标,从而将公钥解压缩为在椭圆曲线上的完整的点坐标。
下图阐释了公钥压缩:
下面是前述公钥使用了264比特(66个十六进制数字)的压缩格式公钥格式,其中前缀03表示y坐标是一个奇数:
K =03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A
这个压缩格式公钥对应着同样的一个私钥,这意味它是由同样的私钥所生成。但是压缩格式公钥和非压缩格式公钥看起来不同。更重要的是,如果我们使用双哈希函数(RIPEMD160(SHA256(K)))将压缩格式公钥转化成比特币地址,得到的地址将会不同于由非压缩格式公钥产生的地址。这种结果会让人迷惑,因为一个私钥可以生成两种不同格式的公钥——压缩格式和非压缩格式,而这两种格式的公钥可以生成两个不同的比特币地址。但是,这两个不同的比特币地址的私钥是一样的。
压缩格式公钥渐渐成为了各种不同的比特币客户端的默认格式,它可以大大减少交易所需的字节数,同时也让存储区块链所需的磁盘空间变小。然而,并非所有的客户端都支持压缩格式公钥,于是那些较新的支持压缩格式公钥的客户端就不得不考虑如何处理那些来自较老的不支持压缩格式公钥的客户端的交易。这在钱包应用导入另一个钱包应用的私钥的 时候就会变得尤其重要,因为新钱包需要扫描区块链并找到所有与这些被导入私钥相关的交易。比特
币钱包应该扫描哪个比特币地址呢?新客户端不知道应该使用哪个公钥:因为不论是通过压缩的公钥产生的比特币地址,还是通过非压缩的公钥产生的地址,两个都是合法的比特币地址,都可以被私钥签名,但是他们是不同的比特币地址。
为了解决这个问题,当私钥从钱包中被导出时,代表私钥的WIF在较新的比特币钱包里被处理的方式不同,表明该私钥已经被用来生成压缩的公钥和因此压缩的比特币地址。这个方案可以解决导入私钥来自于老钱包还是新钱包的问题,同时也解决了通过公钥生成的比特币地址是来自于压缩格式公钥还是非压缩格式公钥的问题。最后新钱包在扫描区块链时,就可以使用对应的比特币地址去查找该比特币地址在区块链里所发生的交易。
如果要将公钥转换为比特币转账地址,我们先后用sha256和ripemid160两个函数对输出脚本中的公钥进行哈希,然后将得到的结果进行Base58编码即可。具体代码实现如下:
本示例来自https://www.blockchain.com/zh/btc/tx/b728387a3cf1dfcff1eef13706816327907f79f9366a7098ee48fc0c00ad2726
交易中的第0个输出脚本示例
import base58
import hashlib
# 根据公钥的16进制字符串计算OP_Hash160
def CalHash160(hexPK):
binPK = bytes.fromhex(hexPK)
hash256 = hashlib.sha256(binPK).digest() # 第一次求sha256
hash160 = hashlib.new('ripemd160', hash256).hexdigest()
return hash160
if __name__ == "__main__":
hexpk = "0412b80271b9e034006fd944ae4cdbdbc45ee30595c1f8961439385575f1973019b3ff615afed85a75737ff0d43cd81df74bc76004b45a6e7c9e2d115f364da1d7"
addr3 = CalBitcoinAddressFromPK(hexpk)
print("addr3: {0}".format(addr3))
# 运行结果: addr3: b'1A2UnusrwYr7DbiFbFesRKrbQMiYkj6E84'
最后如果你对比特币的原理很感兴趣,我非常推荐你阅读《精通比特币》这本书,这本书介绍的实在太详细了,以太坊迄今为止的所有教程的价值加起来都比不上这本书的价值。