勤学苦链
首发于勤学苦链
[block #14] 详解比特币地址 #0:P2PKH地址和WIF

[block #14] 详解比特币地址 #0:P2PKH地址和WIF

比特币地址是一串字母和数字的组合,收款时你可以放心地把它分享给发款方。地址既不是你的钱包私钥也不是公钥,别人光知道地址是无法动用你的资金的,不过可以根据公开的区块链信息查阅所有进出这个地址的交易记录。


比特币地址有几种形式。最常见的莫过于以数字“1”开头的P2PKH(Pay-to-Public-Key-Hash)地址,例如1Nekoo5VTe7yQQ8WFqrva2UbdyRMVYCP1t。它表示的是最简单的、用一对私钥和公钥控制的钱包。


另一种越来越普及的格式是以数字“3”开头的P2SH(Pay-to-Script-Hash)地址,例如3MotoZaxiHoCTDXuAjWXRHP37mprGAAEKU。多重签名、SegWit以及一些智能合约(没错,比特币也支持简单的智能合约)通常都采用这种“3”型地址。


以“2”、“m”或“n”开头的地址非常罕见,仅仅被用于比特币的测试网络。首字符的不同让我们很容易区分它们的用途,也能防止主链的币被误发到测试网络上。


首字符是“5”、“K”或“L”的不是地址,而是WIF(Wallet Import Format)格式的私钥,务必要妥善保管,不可泄漏。


以下将详细讲解P2PKH地址的生成步骤,非程序员可以略过。


首先随机生成一个256位的私钥P,例如:

P = 0x9B257AD1E78C14794FBE9DC60B724B375FDE5D0FB2415538820D0D929C4AD436


其次求出P对应的公钥K:

K = 0x0497FCFAA24237514FD4C00A33491F835D7D019DE4E9CEB0E24916371BAE329E
    622260575B83A1542D93418DABBBE65109B4E27A1A0737B2FD980698C3D0188839

K的首字节0x04是类型标记,表示无压缩公钥。之后紧跟的分别是256位的x和y坐标,共计65字节。(x, y)坐标对应的是方程y² mod p = (x³ + 7) mod p在一个有限域上的整数解(p是个接近2^256的庞大质数)。


由于p是个固定值,所以在已知x的情况下,我们可以求算出y的两个解。在实数域上它们显然是一正一负两个值;而在secp256k1椭圆曲线的整数域上则是一奇一偶两个数。那么只需记录x坐标和y的奇偶性(而不是y本身),我们就能砍掉近一半的空间表示同样的公钥:

K' = 0x0397FCFAA24237514FD4C00A33491F835D7D019DE4E9CEB0E24916371BAE329E62

这个K'被称为压缩公钥,首字节是0x02(偶数y)或0x03(奇数y),共计33字节。


下一步,依次用SHA-256和RIPEMD160求算K'的双重哈希H:

H = RIPEMD160(SHA256(K'))
  = RIPEMD160(0x400952fb04739c06cc9f84c8ce35c83d311b97d7485be9a0ea7ae74d27a0ead4)
  = 0xed7e99a18a7e2f4307240a8936149e7b172007a3


然后在160位的哈希H前添上代表版本的0x00前缀,再对这21个字节算两次SHA-256,取前四个字节作为校验码C:

C = CHECK(0x00 + H)
  = SLICE(SHA256(SHA256(0x00 + H)), 0, 4)
  = SLICE(SHA256(SHA256(0x00ed7e99a18a7e2f4307240a8936149e7b172007a3)), 0, 4)
  = SLICE(SHA256(0x1299fd46aa462709ad88e102d2f013a7381e33b0700ee9e7150d6ad115eb2576), 0, 4)
  = SLICE(0x4e377c733f5e52853e012060c30dd504a9060e93b34b34e379a8bc3959298077, 0, 4)
  = 0x4e377c73


最后把0x00、H、C串在一起,作Base58编码得到最终的P2PKH地址A:

A = Base58(0x00 + H + C)
  = Base58(0x00ed7e99a18a7e2f4307240a8936149e7b172007a34e377c73)
  = "1Nekoo5VTe7yQQ8WFqrva2UbdyRMVYCP1t"


Base58类似于常用的Base64变换。它从大小写英文字母加数字的集合中去掉了四个容易引起混淆的字符(数字零:“0”,大写的o:“O”,小写的L:“l”,大写的i:“I”),用以下的58个字符对输入数据编码:

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz


由Base58地址能很方便地推算出H和C,比较两者即可校验比特币地址的正确性。但从哈希H无法反推出公钥K和私钥P——除非SHA256和RIPEMD160这两个哈希函数都存在严重漏洞。


Base58不光用于地址,还能编码私钥,生成WIF(Wallet Import Format)格式的字符串,便于导入各种钱包软件。拿上文中的私钥P为例:

WIF = Base58(0x80 + P + CHECK(0x80 + P) + 0x01)
    = Base58(0x80 +
             0x9B257AD1E78C14794FBE9DC60B724B375FDE5D0FB2415538820D0D929C4AD436 +
             0x36dfd253 +
             0x01)
    = "L2RJ5NYR3qJLdmbaNPVfvNWSqG9g9XwcLZSuni8aT9u4y4dN1c3J"

其中前缀0x80表示私钥类型。后缀0x01表示公钥采用压缩格式(K')。如果用非压缩公钥(K)则不加这个后缀,不过没有任何理由需要使用非压缩公钥。


最后探讨一下中本聪的设计思路。首先为什么不直接拿公钥做地址呢?因为直接使用公钥的话太不灵活,无法方便地支持多重签名等需求。而且用哈希可以缩短地址长度。


那为什么SHA256之后还要用RIPEMD160再次哈希呢?RIPEMD160可以在不增加太多哈希冲突的情况下让地址更短一些。更主要的原因可能是为了防止单一哈希存在现在未知的漏洞,降低系统的安全性。


加上四字节的校验码能帮助我们快速检验地址是否输入正确。这个校验不需要太强的安全性,四个字节绰绰有余了。我个人认为截取第一次SHA256的前几个字节就足够,双重SHA256没有什么特别好处。


至于加0x00、0x80等前缀则显然是明智的设计。它不仅给未来的格式扩展打下了基础,还通过Base58后的首字符反映出编码数据的类型,方便我们快速识别。


本文发布后做了少量修改,感谢 @RSA1024@毛鳴 的指正。


后续文章会详细介绍比特币的P2SH地址。

编辑于 2017-10-21

文章被以下专栏收录

    勤学苦链(Chain-Shackle-Link)是一个偏重加密货币、智能合约等区块链技术主题的专栏