
比特币的所有权是通过数字密钥、比特币地址和数字签名来确立的。数字密钥实际上并不是存储在网络中,而是由用户生成并存储在一个文件或简单的数据库中,称为钱包。存储在用户钱包中的数字密钥完全独立于比特币协议,可由用户的钱包软件生成并管理,而无需区块链或网络连接。密钥实现了比特币的许多有趣特性,包括去中心化信任和控制、所有权认证和基于密码学证明的安全模型。
每笔比特币交易都需要一个有效的签名才会被存储在区块链。只有有效的数字密钥才能产生有效的数字签名,因此拥有比特币的密钥副本就拥有了该帐户的比特币控制权。密钥是成对出现的,由一个私钥和一个公钥所组成。公钥就像银行的帐号,而私钥就像控制账户的PIN码或支票的签名。比特币的用户很少会直接看到数字密钥。一般情况下,它们被存储在钱包文件内,由比特币钱包软件进行管理。
在比特币交易的支付环节,收件人的公钥是通过其数字指纹表示的,称为比特币地址,就像支票上的支付对象的名字(即“收款方”)。一般情况下,比特币地址由一个公钥生成并对应于这个公钥。然而,并非所有比特币地址都是公钥;他们也可以代表其他支付对象,譬如脚本,我们将在本章后面提及。这样一来,比特币地址把收款方抽象起来了,使得交易的目的地更灵活,就像支票一样:这个支付工具可支付到个人账户、公司账户,进行账单支付或现金支付。比特币地址是用户经常看到的密钥的唯一代表,他们只需要把比特币地址告诉其他人即可。
在本章中,我们将介绍钱包,也就是密钥所在之处。我们将了解密钥如何被产生、存储和管理。我们将回顾私钥和公钥、地址和脚本地址的各种编码格式。最后,我们将讲解密钥的特殊用途:生成签名、证明所有权以及创造比特币靓号地址和纸钱包。
4.1.1公钥加密和加密货币公钥加密发明于20世纪70年代。它是计算机和信息安全的数学基础。
自从公钥加密被发明之后,一些合适的数学函数被提出,譬如:素数幂和椭圆曲线乘法。这些数学函数都是不可逆的,就是说很容易向一个方向计算,但不可以向相反方向倒推。基于这些数学函数的密码学,使得生成数字密钥和不可伪造的数字签名成为可能。比特币正是使用椭圆曲线乘法作为其公钥加密的基础算法。
在比特币系统中,我们用公钥加密创建一个密钥对,用于控制比特币的获取。密钥对包括一个私钥,和由其衍生出的唯一的公钥。公钥用于接收比特币,而私钥用于比特币支付时的交易签名。
公钥和私钥之间的数学关系,使得私钥可用于生成特定消息的签名。此签名可以在不泄露私钥的同时对公钥进行验证。
支付比特币时,比特币的当前所有者需要在交易中提交其公钥和签名(每次交易的签名都不同,但均从同一个私钥生成)。比特币网络中的所有人都可以通过所提交的公钥和签名进行验证,并确认该交易是否有效,即确认支付者在该时刻对所交易的比特币拥有所有权。
4.1.2私钥和公钥一个比特币钱包中包含一系列的密钥对,每个密钥对包括一个私钥和一个公钥。私钥(k)是一个数字,通常是随机选出的。有了私钥,我们就可以使用椭圆曲线乘法这个单向加密函数产生一个公钥(K)。有了公钥(K),我们就可以使用一个单向加密哈希函数生成比特币地址(A)。在本节中,我们将从生成私钥开始,讲述如何使用椭圆曲线运算将私钥生成公钥,并最终由公钥生成比特币地址。私钥、公钥和比特币地址之间的关系如下图所示。

私钥就是一个随机选出的数字而已。一个比特币地址中的所有资金的控制取决于相应私钥的所有权和控制权。在比特币交易中,私钥用于生成支付比特币所必需的签名以证明资金的所有权。私钥必须始终保持机密,因为一旦被泄露给第三方,相当于该私钥保护之下的比特币也拱手相让了。私钥还必须进行备份,以防意外丢失,因为私钥一旦丢失就难以复原,其所保护的比特币也将永远丢失。
从一个随机数生成私钥更准确地说,私钥可以是1和n-1之间的任何数字,其中n是一个常数(n=1.158*1077,略小于2256),并由比特币所使用的椭圆曲线的阶所定义(见4.1.5椭圆曲线密码学解释)。要生成这样的一个私钥,我们随机选择一个256位的数字,并检查它是否小于n-1。从编程的角度来看,一般是通过在一个密码学安全的随机源中取出一长串随机字节,对其使用SHA256哈希算法进行运算,这样就可以方便地产生一个256位的数字。如果运算结果小于n-1,我们就有了一个合适的私钥。否则,我们就用另一个随机数再重复一次。
以下是一个随机生成的私钥(k),以十六进制格式表示(256位的二进制数,以64位十六进制数显示,每个十六进制数占4位):
1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD
要使用比特币核心客户端生成一个新的密钥(参见第3章),可使用getnewaddress命令。出于安全考虑,命令运行后只显示生成的公钥,而不显示私钥。如果要bitcoind显示私钥,可以使用dumpprivkey命令。dumpprivkey命令会把私钥以Base58校验和编码格式显示,这种私钥格式被称为钱包导入格式(WIF,WalletImportFormat),在“私钥的格式”一节有详细讲解。下面给出了使用这两个命令生成和显示私钥的例子:
$bitcoindgetnewaddress1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy$bitcoinddumpprivkey1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZyKxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
dumpprivkey命令只是读取钱包里由getnewaddress命令生成的私钥,然后显示出来。bitcoind的并不能从公钥得知私钥。除非密钥对都存储在钱包里,dumpprivkey命令才有效。
你也可以使用命令行sx工具(参见“3.3.1Libbitcoin和sxTools”)用newkey命令来生成并显示私钥:
$sxnewkey5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn4.1.4公钥
通过椭圆曲线乘法可以从私钥计算得到公钥,这是不可逆转的过程:K=k*G。其中k是私钥,G是被称为生成点的常数点,而K是所得公钥。其反向运算,被称为“寻找离散对数”——已知公钥K来求出私钥k——是非常困难的,就像去试验所有可能的k值,即暴力搜索。在演示如何从私钥生成公钥之前,我们先稍微详细学习下椭圆曲线加密学。
4.1.5椭圆曲线密码学解释椭圆曲线加密法是一种基于离散对数问题的非对称(或公钥)加密法,可以用对椭圆曲线上的点进行加法或乘法运算来表达。

上图是一个椭圆曲线的示例,类似于比特币所用的曲线。
比特币使用了secp256k1标准所定义的一条特殊的椭圆曲线和一系列数学常数。该标准由美国国家标准与技术研究院(NIST)设立。secp256k1曲线由下述函数定义,该函数可产生一条椭圆曲线:
y2=(x3+7)}over(Fp)
或
y2modp=(x3+7)modp
上述modp(素数p取模)表明该曲线是在素数阶p的有限域内,也写作Fp,其中p=2256–232–29–28–27–26–24–1,这是一个非常大的素数。
因为这条曲线被定义在一个素数阶的有限域内,而不是定义在实数范围,它的函数图像看起来像分散在两个维度上的散点图,因此很难画图表示。不过,其中的数学原理与实数范围的椭圆曲线相似。作为一个例子,下图显示了在一个小了很多的素数阶17的有限域内的椭圆曲线,其形式为网格上的一系列散点。而secp256k1的比特币椭圆曲线可以被想象成一个极大的网格上一系列更为复杂的散点。

图为:椭圆曲线密码学F(p)上的椭圆曲线,其中p=17
下面举一个例子,这是secp256k1曲线上的点P,其坐标为(x,y)。可以使用Python对其检验:
P=(5506626302227734366957873262506034537775940389116729240,326705100207588169780830854471273380659243275938904335757337482424)(default,Mar302014,19:23:13)[()]ondarwinType"help","copyright","credits"or"license"=131619542357098500868790785326998466564056403945758671663x=5506626302227734366957873262506034537775940389116729240y=326705100207588169780830854471273380659243275938904335757337482424(x**3+7-y**2)%p0
在椭圆曲线的数学原理中,有一个点被称为“无穷远点”,这大致对应于0在加法中的作用。计算机中,它有时表示为X=Y=0(虽然这不满足椭圆曲线方程,但可作为特殊情况进行检验)。还有一个+运算符,被称为“加法”,就像小学数学中的实数相加。给定椭圆曲线上的两个点P1和P2,则椭圆曲线上必定有第三点P3=P1+P2。
几何图形中,该第三点P3可以在P1和P2之间画一条线来确定。这条直线恰好与椭圆曲线上的一点相交。此点记为P3'=(x,y)。然后,在x轴做映射获得P3=(x,-y)。
下面是几个可以解释“无穷远点”之存在需要的特殊情况。若P1和P2是同一点,P1和P2间的连线则为点P1的切线。曲线上有且只有一个新的点与该切线相交。该切线的斜率可用微分求得。即使限制曲线点为两个整数坐标也可求得斜率!
在某些情况下(即,如果P1和P2具有相同的x值,但不同的y值),则切线会完全垂直,在这种情况下,P3=“无穷远点”。
若P1就是“无穷远点”,那么其和P1+P2=P2。类似地,当P2是无穷远点,则P1+P2=P1。这就是把无穷远点类似于0的作用。
事实证明,在这里+运算符遵守结合律,这意味着(A+B)C=A(B+C)。这就是说我们可以直接不加括号书写A+B+C,而不至于混淆。
至此,我们已经定义了椭圆加法,为扩展加法下面我们对乘法进行标准定义。给定椭圆曲线上的点P,如果k是整数,则kP=P+P+P+…+P(k次)。注意,k被有时被混淆而称为“指数”。
4.1.6生成公钥以一个随机生成的私钥k为起点,我们将其与曲线上已定义的生成点G相乘以获得曲线上的另一点,也就是相应的公钥K。生成点是secp256k1标准的一部分,比特币密钥的生成点都是相同的:
K=1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD*G
其中k是私钥,G是生成点,在该曲线上所得的点K是公钥。因为所有比特币用户的生成点是相同的,一个私钥k乘以G将得到相同的公钥K。k和K之间的关系是固定的,但只能单向运算,即从k得到K。这就是可以把比特币地址(K的衍生)与任何人共享而不会泄露私钥(k)的原因。
为实现椭圆曲线乘法,我们以之前产生的私钥k和与生成点G相乘得到公钥K:
K=1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD*G
公钥K被定义为一个点K=(x,y):
K=(x,y)其中,x=F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341Ay=07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
为了展示整数点的乘法,我们将使用较为简单的实数范围的椭圆曲线。请记住,其中的数学原理是相同的。我们的目标是找到生成点G的倍数kG。也就是将G相加k次。在椭圆曲线中,点的相加等同于从该点画切线找到与曲线相交的另一点,然后映射到x轴。

上图显示了在曲线上得到G、2G、4G的几何操作。
4.2比特币地址比特币地址是一个由数字和字母组成的字符串,可以与任何想给你比特币的人分享。由公钥(一个同样由数字和字母组成的字符串)生成的比特币地址以数字“1”开头。下面是一个比特币地址的例子:
1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
在交易中,比特币地址通常以收款方出现。如果把比特币交易比作一张支票,比特币地址就是收款人,也就是我们要写入收款人一栏的内容。一张支票的收款人可能是某个银行账户,也可能是某个公司、机构,甚至是现金支票。支票不需要指定一个特定的账户,而是用一个普通的名字作为收款人,这使它成为一种相当灵活的支付工具。与此类似,比特币地址的使用也使比特币交易变得很灵活。比特币地址可以代表一对公钥和私钥的所有者,也可以代表其它东西,比如会在132页的“P2SH(Pay-to-Script-Hash)”一节讲到的付款脚本。现在,让我们来看一个简单的例子,由公钥生成比特币地址。
比特币地址可由公钥经过单向的加密哈希算法得到。哈希算法是一种单向函数,接收任意长度的输入产生指纹摘要。加密哈希函数在比特币中被广泛使用:比特币地址、脚本地址以及在挖矿中的工作量证明算法。由公钥生成比特币地址时使用的算法是SecureHashAlgorithm(SHA)和theRACEIntegrityPrimitivesEvaluationMessageDigest(RIPEMD),特别是SHA256和RIPEMD160。
以公钥K为输入,计算其SHA256哈希值,并以此结果计算RIPEMD160哈希值,得到一个长度为160比特(20字节)的数字:
A=RIPEMD160(SHA256(K))
公式中,K是公钥,A是生成的比特币地址。
通常用户见到的比特币地址是经过“Base58Check”编码的(参见72页“Base58和Base58Check编码”一节),这种编码使用了58个字符(一种Base58数字系统)和校验码,提高了可读性、避免歧义并有效防止了在地址转录和输入中产生的错误。Base58Check编码也被用于比特币的其它地方,例如比特币地址、私钥、加密的密钥和脚本哈希中,用来提高可读性和录入的正确性。下一节中我们会详细解释Base58Check的编码机制,以及它产生的结果。下图描述了如何从公钥生成比特币地址。

为了更简洁方便地表示长串的数字,许多计算机系统会使用一种以数字和字母组成的大于十进制的表示法。例如,传统的十进制计数系统使用0-9十个数字,而十六进制系统使用了额外的A-F六个字母。一个同样的数字,它的十六进制表示就会比十进制表示更短。更进一步,Base64使用了26个小写字母、26个大写字母、10个数字以及两个符号(例如“+”和“/”),用于在电子邮件这样的基于文本的媒介中传输二进制数据。Base64通常用于编码邮件中的附件。Base58是一种基于文本的二进制编码格式,用在比特币和其它的加密货币中。这种编码格式不仅实现了数据压缩,保持了易读性,还具有错误诊断功能。Base58是Base64编码格式的子集,同样使用大小写字母和10个数字,但舍弃了一些容易错读和在特定字体中容易混淆的字符。具体地,Base58不含Base64中的0(数字0)、O(大写字母o)、l(小写字母L)、I(大写字母i),以及“+”和“/”两个字符。简而言之,Base58就是由不包括(0,O,l,I)的大小写字母和数字组成。
例4-1比特币的Base58字母表
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
Base58Check是一种常用在比特币中的Base58编码格式,增加了错误校验码来检查数据在转录中出现的错误。校验码长4个字节,添加到需要编码的数据之后。校验码是从需要编码的数据的哈希值中得到的,所以可以用来检测并避免转录和输入中产生的错误。使用Base58check编码格式时,编码软件会计算原始数据的校验码并和结果数据中自带的校验码进行对比。二者不匹配则表明有错误产生,那么这个Base58Check格式的数据就是无效的。例如,一个错误比特币地址就不会被钱包认为是有效的地址,否则这种错误会造成资金的丢失。
为了使用Base58Check编码格式对数据(数字)进行编码,首先我们要对数据添加一个称作“版本字节”的前缀,这个前缀用来明确需要编码的数据的类型。例如,比特币地址的前缀是0(十六进制是0x00),而对私钥编码时前缀是128(十六进制是0x80)。表4-1会列出一些常见版本的前缀。
接下来,我们计算“双哈希”校验码,意味着要对之前的结果(前缀和数据)运行两次SHA256哈希算法:
checksum=SHA256(SHA256(prefix+data))
在产生的长32个字节的哈希值(两次哈希运算)中,我们只取前4个字节。这4个字节就作为校验码。校验码会添加到数据之后。
结果由三部分组成:前缀、数据和校验码。这个结果采用之前描述的Base58字母表编码。下图描述了Base58Check编码的过程。

在比特币中,大多数需要向用户展示的数据都使用Base58Check编码,可以实现数据压缩,易读而且有错误检验。Base58Check编码中的版本前缀是数据的格式易于辨别,编码之后的数据头包含了明确的属性。这些属性使用户可以轻松明确被编码的数据的类型以及如何使用它们。例如我们可以看到他们的不同,Base58Check编码的比特币地址是以1开头的,而Base58Check编码的私钥WIF是以5开头的。表4-1展示了一些版本前缀和他们对应的Base58格式。
表4-1Base58Check版本前缀和编码后的结果
种类版本前缀(hex)Base58格式BitcoinAddress0x001Pay-to-Script-HashAddress0x053BitcoinTestnetAddress0x6FmornPrivateKeyWIF0x805,KorLBIP38EncryptedPrivateKey0x01426PBIP32ExtedPublicKey0x0488B21Expub
我们回顾比特币地址产生的完整过程,从私钥、到公钥(椭圆曲线上某个点)、再到两次哈希的地址,最终产生Base58Check格式的比特币地址。例4-2的C++代码完整详细的展示了从私钥到Base58Check编码后的比特币地址的步骤。代码中使用“3.3其他客户端、资料库、工具包”一节中介绍的libbitcoinlibrary来实现某些辅助功能。
例4-2从私钥产生一个Base58Check格式编码的比特币地址
$g++-$(pkg-config--cflags--libslibbitcoin)Generatearandomprivatekeyvalid_private_key=Falsewhilenotvalid_private_key:private_key=_key()decoded_private_key=_privkey(private_key,'hex')valid_private_key=0decoded_private_"PrivateKey(hex)is:",private_keyprint"PrivateKey(decimal)is:",decoded_private_keyAddsuffix"01"toindicateacompressedprivatekeycompressed_private_key=private_key+'01'print"PrivateKeyCompressed(hex)is:",compressed_private_keyMultiplytheECgeneratorpointGwiththeprivatekeytogetapublickeypointpublic_key=_multiply(,decoded_private_key)print"PublicKey(x,y)coordinatesis:",public_keyCompresspublickey,adjustprefixdepingonwhetheryisevenorodd(public_key_x,public_key_y)=public_keyif(public_key_y%2)==0:compressed_prefix='02'else:compressed_prefix='03'hex_compressed_public_key=compressed_prefix+(public_key_x,16)print"CompressedPublicKey(hex)is:",hex_compressed_public_keyGeneratecompressedbitcoinaddressfromcompressedpublickeyprint"CompressedBitcoinAddress(b58check)is:",\_to_address(hex_compressed_public_key)
例4-5显示了上段代码运行结果。
例4-5运行
$(hex)is:3aba4162c7251c891207b747840551a71939b0de081f85c4e44cf7c13e41daa6PrivateKey(decimal)is:265632300484379575922325538266636964406067566859201673293013768870PrivateKey(WIF)is:5JG9hT3beGTJuUAmCQEmNaxAuMacCTfXuw1R3FCXig23RQHMr4KPrivateKeyCompressed(hex)is:3aba4162c7251c891207b747840551a71939b0de081f85c4e44cf7c13e41daa601PrivateKey(WIF-Compressed)is:KyBsPXxTuVD82av65KZkrGrWi5qLMah5SdNq6uftawDbgKa2wv6SPublicKey(x,y)coordinatesis:(416373227866463252588396900663353932545912953362782457239403430124L,163889351287812384055267104667247420864332266L)PublicKey(hex)is:045c0de3b9c8ab18dd04e3511243ec2952002dbfadc864b9628910169d9b9b00ec↵243bcefdd4347074d44bd7356d6a53c495737dd96295e2a9374bf5f02ebfc176CompressedPublicKey(hex)is:025c0de3b9c8ab18dd04e3511243ec2952002dbfadc864b9628910169d9b9b00ecBitcoinAddress(b58check)is:1thMirt546nngXqyPEz532S8fLwbozud8CompressedBitcoinAddress(b58check)is:14cxpo3MBCYYWCgF74SWTdcmxipnGUsPw3
例4-6是另外一个示例,使用的是PythonECDSA库来做椭圆曲线计算而非使用bitcoin的库。
例4-6使用在比特币密钥中的椭圆曲线算法的脚本
_to_number,number_to_=random_secret()print"Secret:",secretGiventhepoint(x,y)wecancreatetheobjectusing:point1=(curve,(),(),ec_order)assertpoint1==point
例4-7显示了运行脚本的结果。
例4-7安装PythonECDSA库,运行ec_脚本
runningtheec_$InstallthePythonECDSAlibrary$sudopipinstallecdsa$createanewmasterprivatekeyfromaseedandstoreinfile"m"$catmgeneratetheM/0extedpublickeyxpub67xpozcx8pe95XVuZLHXZeG6XWXHpGq6Qv5cmNfi7cS5mtjJ2tgypeQbBs2UAR6KE-CeeMVKZBPLrtJunSDMstweyLXhRgPxdp14sk9tJPW9$catm|sxhd-priv0showtheprivatekeyofm/0asaWIFL1pbvV86crAGoDzqmgY85xURkz3c435Z9nirMt52UbnGjYMzKBUN$catm|sxhd-pub0|sxhd-to-addressgeneratem/0/12'/4xprv9yL8ndfdPVeDWJenF18oiHguRUj8jHmVrqqD97YQHeTcR3LCeh53q5PXPkLsy2kRaqgwoS6YZ-BLatRZRyUeAkRPe1kLR1P6Mn7jUrXFquUt