# BTC 学习笔记连载(9)——私钥-公钥-地址

By [Crypto 吊车尾](https://paragraph.com/@imsongoku) · 2022-11-17

---

欢迎交流：[twitter.com/songoku\_web3](http://twitter.com/songoku_web3)

转载请注明出处~

公钥密码学从20世纪70年代诞生以来，就是计算机及信息安全的数学基础！

之前通过《[BTC 学习笔记连载(4)——公钥密码学：D-H & RSA](https://mirror.xyz/imsongoku.eth/VyagTWGQRczPLBLovcV5Ns6zvncrN2S_VMzeuADhctY)》、《[BTC 学习笔记连载(5)——公钥密码学：ECC](https://mirror.xyz/imsongoku.eth/I5p0jk9MMv8MeOvh-Kvib-G23LijEf7SUaTWbWvTGJ4)》2篇文章介绍了公钥密码学，包括作为比特币密码学基础的椭圆曲线加密。RSA和ECC都是不可逆的，很容易在一个方向上计算，但无法在相反的方向上被推导，属于典型的NP问题。

公钥密码学是创建安全的比特币地址和不可伪造的数字签名的基础！

毫不夸张的说Bitcoin的所有权就是私钥和数字签名保障的！

随机 & 伪随机函数
----------

维基百科里对随机的定义：一个随机的过程是一个不定因子不断产生的重复过程，但它可能遵循某个概率分布。

![](https://storage.googleapis.com/papyrus_images/36ceba7bd4b7392e3ae16e203ac279fe6fbc5e1d702f8e0eb9cf67915f64e8b6.png)

像如图这样不讲人话，普通人通常是比较难理解的。

我们以掷骰子为例帮助理解，掷骰子无非有6种结果，而这也就构成了掷骰子的**概率空间**，无论怎么掷都不会有其它结果。掷一次骰子得到的结果就称之为**随机变量**，这个随机变量只能落在其概率空间中，也就是6种情况只能有一种发生。随机变量在概率空间的数学表达形式就是**分布函数**。你一直不停地掷骰子的个过程就是一个**随机过程**。

掷骰子的每一种结果都是有发生概率的，比如掷到6点的概率为1/6，这都是通过统计方法总结出来的。你可以掷十万次并记录每次结果，最终算出每个数字的概率分布。只要你掷的骰子是质地均匀的，只要你掷的次数足够多，那最终结果分布是确定的。

我们可以确定某次单一投掷行为是足够随机的！

但计算机不是，计算机只能产生伪随机数。这里说的伪随机数并不是假随机数，这里的“伪”是有规律的意思，既随机又有规律的数。

怎样理解呢？

也就是产生的随机数有时遵守一定的规律，有时不遵守任何规律。或者有一部分遵守一定的规律，另一部分不遵守任何规律。

我们常说世上没有两片完全相同的树叶，这就是随机性。每片叶子都有近似的形状，这就是规律性。

计算机中随机数是由随机种子产生的，没有绝对的随机源计算机只能产生伪随机数，而不可能绝对随机。实际上只要给定边界条件，严格来讲真随机数就并不存在，可是如果产生一个真随机数样本的边界条件十分复杂且难以捕捉（比如当地的背景辐射波动值），我们也可以认为用这个方法得到了真随机数。但实际上这也只是非常接近真随机数，一般认为无论是背景辐射、物理噪音、鼠标运行轨迹、抛硬币等等都是可被观察了解的，任何基于经典力学产生的随机数，都只是伪随机数。

不过我们一般的应用场景，抛硬币获得的随机数已经足够随机了。

私钥—公钥—地址
--------

在现实世界中要有资金往来，我们需要先去银行填写资料开个账户。

在Crypto的世界中没有这个环节，Bitcoin甚至没有账户的概念！

我们说Bitcoin是：**Transaction Base Ledger**

在比特币的系统中，我们使用公钥密码学来创建一个控制Bitcoin的密钥对，包括一个私钥和它派生出来的公钥，公钥生成的地址用于接受Bticoin，私钥用于为支付Bitcoin进行签名。

Bitcoin的私钥和公钥存在一种数学关系，公钥是基于私钥使用椭圆曲线乘法单向生成的，地址又是基于公钥单向生成的，这就是我们平常了解到的私钥-公钥-地址之间的单向关系，要想从地址反推公钥或从公钥反推私钥是不可能的：

![](https://storage.googleapis.com/papyrus_images/95f49ade854cb6d65f0a1f5f9036447f5509b83f1bf501d1e6b3d4f444f7969e.png)

### 那私钥是怎么生成的呢？

Bitcoin的私钥是一串256位的二进制数，即1—2^256之间的一个数：

![](https://storage.googleapis.com/papyrus_images/6e0cc32a1389a369756a23a4fcf76977035121a19504b4b88e4812a24b2363b5.png)

只要生成这串数字的过程是足够随机、不可预测的，那使用什么方法并不重要，最简单的方法就是连续抛硬币256次，正面记为1反面记为0。

所以我们随时都可以生成私钥，**不需要经过任何机构，也不需要任何人同意！**

256位的二进制数有2^256种输出空间，约等于10^77，这是一个天文数字。很显然任何人抛256次硬币撞到跟你相同私钥的概率比地球爆炸还小。 ( _目前可见宇宙原子数量预估为10^80个_ )

那计算机怎么生成私钥的呢？

计算机不可能强行对标物理世界，要生成一个足够安全的私钥，首先要找到一个足够安全的熵源。比特币软件使用操作系统底层的随机数生成器来产生熵，而通常这又是由人工随机源来进行初始化的，比如系统会要求你晃动鼠标几秒钟。

更准确地讲私钥是1—n-1之间的任意数字，其中n = 1.158\*10^77，这个数略小于2^256。从编程的角度来看，一般是通过在一个密码学安全的随机源中取出一长串随机字节，然后使用SHA256哈希函数运算得到一个256位二进制数。如果这个数小于n-1，就生成了一个合适的私钥，否则就重复这个过程。

《[BTC 学习笔记连载(3)——Hash](https://mirror.xyz/imsongoku.eth/LrjVzhrwY3TP97Wu6luIfqeqNl6WC7NM_KufMRtlxIk)》介绍过SHA256，这个哈希函数的输出就是256位的二进制数，切记不要随意在网上生成私钥，你并不知道它到底随不随机、或有什么问题。

### 私钥生成公钥，公钥生成地址

我们已经知道私钥就是一串数字，有了私钥怎么生成公钥呢？

通过椭圆曲线乘法计算即可从私钥得到公钥。

我们在《[BTC 学习笔记连载(5)——公钥密码学：ECC](https://mirror.xyz/imsongoku.eth/I5p0jk9MMv8MeOvh-Kvib-G23LijEf7SUaTWbWvTGJ4)》介绍过椭圆曲线乘法，要强调的是并非所有的椭圆曲线都适用于加解密，Bitcoin使用的是一个特定的椭圆曲线：

**y^2 = x^3 + a\*x +b** 其中 a = 0，b = 7，且 4\*a^3 + 27\*b^2 ≠ 0

即满足 **y^2 = x^3+7** 的曲线：

![](https://storage.googleapis.com/papyrus_images/abffa36a4856c2f12eccf72ebebf25b83f0dfa2f4a3fea541a885f9faa7e5cb3.png)

这是美国国家标准与技术研究所NIST制定的标准**secp256k1：**

sec：来自SECG标准 p：曲线坐标为素数域 256：256位长的素数 k：Koblitz曲线变体 1：标识是第1个标准的此类曲线 ( 比特币之后secp2256k1 已成为数字货币默认的数字签名算法，eth也是采用此算法 )

基于一个随机生成的私钥k，我们将其与椭圆曲线上的一个基点G相乘，获得的另一个点即可得到公钥：

**K = k \* G**

k：私钥，一个非常大的整数

G：椭圆曲线上的基点坐标 (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)

由于基点不变，很显然如果私钥k不变，将得到相同的公钥K。

为了可视化这个计算过程，我们选用较为简单的实数域范围的椭圆曲线演示，其中的数学原理是相通的。我们的目标就是计算基点G的k倍，即将k个G相加：

![](https://storage.googleapis.com/papyrus_images/6dc8d0f5b05cf2f3e1f74228c09fa1d1041c1b30fd8e9bbbf0fdd4c854ceda53.png)

《[BTC 学习笔记连载(5)——公钥密码学：ECC](https://mirror.xyz/imsongoku.eth/I5p0jk9MMv8MeOvh-Kvib-G23LijEf7SUaTWbWvTGJ4)》这篇文章已经介绍过计算过程：

2G = G + G

基于G点的切线相交于椭圆曲线上的另一点即为-2G，相对X轴翻转即可得到点2G。

以此类推我们可以很快计算出k为2^n的K：

G → 2G → 4G → 8G → … → 2^n-1\*G

任何一个大整数k，都可以拆分成多个2^n\*G的和，它的时间复杂度是多项式的。

但实际过程并没有那么直观，椭圆曲线是连续的，并不适合用于加密。Bitcoin使用的椭圆曲线被定义到一个质数阶的有限域内，而不是实数范围，它的函数图像是分散在平面坐标系内的离散的点，我们通常记为：

**y^2 ≡ x^3+7（mod p）**

p = 2^256 − 2^32 − 2^9 − 2^8 − 2^7 − 2^6 − 2^4 − 1

这是一个非常大的素数，我们以一个质数阶为17的有限域内的椭圆曲线展示一下（实际上它是一个极大的平面上的一系列更加复杂的散点）：

![](https://storage.googleapis.com/papyrus_images/6e7ffa1357528d9a21c9a689f9341ef8ac0e26c0330caf3c38538057092327b1.jpg)

Anyway，最终我们得到的K就是一个坐标点（x，y），这个坐标点的x、y都是256位二进制数，即64位16进制数。

### 公钥生成地址

有了公钥就可以生成地址了，Bitcoin的地址是一个由数字和字母组成的字符串：

3AdwgfQ1tCxs7fNk8zZtL8dwEVVTyK5Uho

如果你往这个地址转账Bitcoin，我就先感谢了~

如果跟银行账户对比，Bitcoin地址就相当于银行账号，只要是通过自己私钥最终生成的地址，就可以对外公布并用于接收Bitcoin了。

在Bitcoin协议中公钥生成地址使用的算法是SHA256和RIPEMD160，这2个哈希函数在[之前文章](https://mirror.xyz/imsongoku.eth/I5p0jk9MMv8MeOvh-Kvib-G23LijEf7SUaTWbWvTGJ4)中都介绍过，我们可以表示为：

**A = RIPEMD160(SHA256(K))**

这个公式中K就是公钥，A就是生成的Bitcoin地址，很明显地址是一个160位的二进制数，也可以表示为40位16进制数。

这显然是不易用的！

Bitcoin中大多数需要向用户展示的数据都使用Base58Check编码，它不但可以压缩数据、提高可读性、避免歧义，为了增加安全性还内置了错误校验，能有效防止数据在输入、转录过程中出错。

当然地址也不例外：

![](https://storage.googleapis.com/papyrus_images/64e1fa0fea30ec5405776ceedb410dac4d5a1af456565f2bddef8e4933a9e84b.png)

**版本前缀（版本号）**：明确了被编码的数据类型

![](https://storage.googleapis.com/papyrus_images/628848b1c2cf5d04804228fc1ed50d84474eed1f364012a004b9aee635d76572.png)

比特币也存在多种地址格式，主要的比特币输出脚本格式有3种：**P2PKH**、**P2SH** 和 **Bech32**

**P2PKH**： Pay-to-Pubkey Hash，即支付给收件人的公钥的哈希值。如图P2PKH地址的前缀为0x00，Base58Check编码后的比特币地址以1开头，这是比特币的传统地址，至今仍然可以正常使用。但是传统地址与segwit不兼容，你可以将BTC从P2PKH地址发送到segwit地址，但平均费用高于从segwit地址发送时的平均费用，因为传统地址事务的大小更大。

**P2SH**： Pay-to-Script-Hash，代表付费脚本散列，它支持比传统地址更复杂的功能。P2SH脚本函数最常用于multisig地址，这些地址可以指定多个数字签名来授权事务，这个地址类型得到广泛支持，可以用于向 P2PKH 和 Bech32 地址发送资金。

**Bech32**： BECH32编码的地址，以“ bc1 ”开头，是专为segwit开发的地址格式，大多数软硬件钱包都支持，但交易所支持的比较少，很少有交易所以这种格式接收资金，目前可能只有1%的BTC存储在bech32地址中。

只有少数服务提供商支持所有这些格式，很可能你首选的钱包或交易所至少不支持其中一种格式，其中 Bech32 最有可能被忽略，但P2SH和Bech32地址相对于P2PKH地址，更小且手续费更低，可提升BTC区块打包速度。

但需要强调的是：

*   同一个私钥对应的是3个不同类型的地址，都是合法的正常地址
    
*   3个类型的地址是彼此独立的，如果向这3个地址分别转账1个BTC，那么每个地址余额为1个BTC
    

大多数情况下，这些地址不会相互冲突。

**校验和**：校验和是对版本号+地址进行双重哈希计算得到的( 只取前4个字节 )，可以用来检测并避免转录及输入中的错误，解码软件会计算数据的校验和并和编码中自带的校验和对比，不匹配则表示地址不正确。

至此，我们已经了解了从**私钥-公钥-地址**的整个生成过程。

还记得这个地址么：

3AdwgfQ1tCxs7fNk8zZtL8dwEVVTyK5Uho

这是我在MathWallet生成的一个比特币地址，很显然这是一个**P2SH**格式地址。

秘钥的格式
-----

任何数字都可以由不同的格式来表示，比如2049这个十进制数以不同的进制或编码格式就会看起来不一样：

二进制：100000000001

16进制：801

Base58：cL

Base64：gQ==

它们表达的都是同一个数，只是以不同的格式呈现而以。

Base58编码《[在BTC 学习笔记连载(1)——古典密码学](https://mirror.xyz/imsongoku.eth/ogK67tcc8uDwsV_d5xq_DFuFQlX3cV7YuF-j9TdHbHY)》已经介绍过了，Base58编码本质上是将10进制数以58进制来表示，这里不展开了。

私钥是一串数字，当然也可以用不同的格式来表示：

![](https://storage.googleapis.com/papyrus_images/53a62b7e9e4523459933d19c84c59d81c623149593c80e9725071f1eb8fb0492.png)

不同的格式可以在不同的场景下使用。原始格式就是256位二进制数，16进制就是将这个二进制数转换成16进制，这2种格式一般用在应用软件内部，很少展示给用户看。

**WIF格式是什么？**

**WIF**(Wallet Import Format)，这是用在钱包之间私钥的输入和输出的格式，也用于现实私钥的二维码。就是对私钥进行Base58Check编码，私钥的版本前缀为“0x80”，依然加上4个byte的校验和。

**压缩的WIF又是什么？**

要将压缩格式的私钥进行Base58Check编码，仅需要在16进制私钥后面添加后缀“0x01”即可，我们看看几种不同格式的私钥表现形式（网上随便截了张图）：

![](https://storage.googleapis.com/papyrus_images/795d8df8b46a0d2a4cddb7064dde41dddad8e881b5453598e7875f47b62ff6cf.png)

细心的大兄弟应该看出来了，压缩的私钥格式为啥没压缩反而边长了呢？

要不思考一下，我们后面解释。

### 压缩格式的公钥

前面介绍过公钥是在椭圆曲线上的一个点，那自然应该由一个坐标（x，y）组成，且横纵坐标都是256位二进制数（也就是64位16进制数，这里就随便找2个值让大家看得更直观一些）：

(0x9284a85154954aa424f950aaaa5095db5a9552aa46492a43ca9541756556aaa5, 0x4a2324b294a952a95c9512a5295250ab95295255254a9a0fcaa5294a54a952ac)

非压缩的公钥以前缀“0x04”紧接着2个坐标值的数表示：

K = 049284a85154954aa424f950aaaa5095db5a9552aa46492a43ca9541756556aaa54a2324b294a952a95c9512a5295250ab95295255254a9a0fcaa5294a54a952ac

这是一个130位的16进制数，也代表一个520位的二进制数。

这个数太长了，在Bitcoin的协议中广播一笔Transaction时还需要带上公钥及签名，公钥是用来验证签名的，这样会加重网络传输及存储的负担。

正好椭圆曲线是相对于X轴对称的一条曲线，只要知道x就可以通过**y^2 ≡ x^3+7**求得y，且这个y一定是绝对值相同的2个值，一个为正一个为负。

根据这个特性其实我们就只需要存储x，节省y坐标直接就能将公钥的大小和存储空间减少256个bit，每笔交易的字节数几乎就减少了一半，如果每个Block都有上千笔交易，这将大大节省网络广播及存储的负担，日积月累这绝对是一件有巨大收益的事情。

但对于给定的x值，想要省略y就需要知道它的正负号，即我们要知道y在X轴的上方还是下方，因为这是坐标平面不同的2个点，也代表2个不同的公钥。

![](https://storage.googleapis.com/papyrus_images/be3e5398537dda69299ec528df271418301b7595bb69b9ca4a884b1558d29c88.png)

如图右半部分，我们在网上随处都可以找到这张图示，当y为偶数时压缩公钥前缀为“0x02”，当y为奇数时压缩公钥前缀为“0x03”，前面举例的那个坐标，如果计算出来y为奇数，那么公钥就是这样的：

K = 039284a85154954aa424f950aaaa5095db5a9552aa46492a43ca9541756556aaa5

**不是正负么？**

**为什么是奇偶？**

我刚开也有点懵逼，喜欢钻研的大兄弟可以花几天自己研究一下？

……

……

……

比特币使用的是 **y^2 ≡ x^3+7（mod p）**，这是在素数p阶有限域内离散的点，在有限域上y值只能为正值，因为-y mod p = p – y，这里有2种方法来判断y的正负：

1.  通过y值得奇偶性来判断
    
    ∵ p是素数，且 -y mod p = p - y； ∴ 当y为偶数时，p – y是奇数；当y是奇数时，p – y是偶数；
    
    且通过x值和方程式计算出的y值只会在正数域。
    
    当公钥y值在“负”数域时 ，那么其在正数域的对称点p – y（即为通过x值和方程式计算出的y值）的奇偶性必然与其相反。
    
    所以当y为偶数时其值对应负数，带上前缀“0x02”；当y为奇数时其值对应正数，带上前缀“0x03”；
    
2.  带入**y^2 = x^3 + 7**
    
    将公钥坐标(x,y) 带入实数域曲线方程y^2 = x^3 + 7
    
    如果等式成立y值为正，否则y为负，因为y为负数时会被映射到素数p的有限域上的另一个正数，带入实数域方程等式肯定不成立。
    

非压缩的公钥和压缩的公钥看起来就很不同，如果再将这2个不同的公钥经过\*\*RIPEMD160(SHA256(K))\*\*进行2轮哈希，转化出来的地址就更是风马牛不相及了，2个完全不同的地址来自于1个私钥，这多少会让人感到懵逼。

![](https://storage.googleapis.com/papyrus_images/eeb1c5076e011be660ccb0caccfbb4c6a169436d75321e1098766469bb9987d9.png)

哪个是合法的地址？到底应该用哪一个？

只要私钥签名，对应的公钥能验证，交易就是合法的。

主要还是考虑网络传输及存储的问题，理所应当选择更好的方案。

好在不同的Bitcoin客户端逐渐都采用了压缩格式的公钥，但是并非所有的应用都支持，于是那些比较新的应用是不是就应该兼容那些还未支持压缩格式的应用？特别是钱包，当一个Wallet导入另一个Wallet的私钥时，需要扫描区块链并找到所有与这个私钥相关的Transaction，问题来了！应该扫描哪个地址呢？无论是通过压缩公钥产生的地址还是非压缩公钥产生的地址，都是合法的地址，都可以被私钥签名。

在实现了压缩格式公钥的钱包中，私钥只能被导出为WIF压缩格式，前缀为K/L； 在未实现压缩格式公钥的钱包中，私钥只能被导出为WIF格式，前缀为5；

这样任何钱包在私钥导入时都能明确应该搜索哪个地址，当从一个压缩格式公钥的钱包导出私钥时，钱包导入格式将为确定为WIF压缩格式，该格式会在私钥后面附加一个后缀“0x01”，所以最终Base58Check编码开头才会从5变为K/L。

OK，我们上面遗留了一个问题，为什么会有**压缩格式私钥**这种说法？

这是一个名称上的误导，私钥并没有压缩也不能被压缩，不但没有压缩还在Base58Check编码之前加了一个后缀“0x01”，压缩的私钥其实是指：**用于生成压缩格式公钥的私钥！**

其实公钥也并没有被压缩，这里“压缩”只是针对其表示方法，而不是私钥和公钥本身。

靓号地址
----

我们知道从私钥最终生成的地址是这样的一串数字：

3AdwgfQ1tCxs7fNk8zZtL8dwEVVTyK5Uho

这串数字没有任何含义，于是一些闲得蛋疼的大兄弟就坐不住了，不在地址里整点特殊的东西就感觉没有存在感一样，比如：

3**Boss123**tCxs7fNk8zZtL8dwEVVTyK5Uho 1**SexyGirl**7117fNk8zZtL8dwEVVTyK5Uho

这种Bitcoin地址中包含某个特殊单词或数字组合的，就是靓号地址。

理论上通过数亿次尝试，是有可能找到的，不停地尝试私钥→公钥→地址这个过程，直到找到一个满足靓号的私钥。

曾经有统计，如果一台普通性能的桌面电脑，找到“3**Boss123…**”这个靓号大概需要3~4个月，找到“1**SexyGirl…**”这个靓号大概需要13~18年。每多增加1个字母就需要增加58倍计算难度，超过7个字符通常需要专门的硬件才能胜任。

也有靓号旷工矿池提供这种服务，这是一种为他人寻找靓号地址来获取比特币的服务，关键是它找到了你敢用么？

靓号地址本质上不会增加、降低你地址的安全性，网上有些靓号地址能增加安全性的说法我觉得也仅仅在有限的一些场景及情况下。

地址就是一个字符串而以，只是看起来有几个字母排列到刚好满足你“需求”而以，你整个“1SunCity…”的地址别人也可以整一个，并不像域名那样有独占性。

前段时间某提供靓号服务的应用就出事了，因为它的寻址空间很有限，然后就被撞出来了，朋友中也有中招的，所以闲得蛋疼去搞那些干啥？

地址和DID
------

最近炒得比较或的DID，也就是Decentralized ID，比较火的项目有：

.eth

.bnb

.bit

直接能够将用户以太坊、BSC等公链的地址映射到一个短短的区块链域名下，比如我的mirror就使用了[imsongoku.eth](https://mirror.xyz/imsongoku.eth)这个域名，又比如3525.bit这个账号。

基于DID用户在识别、使用上就更easy了，而且是唯一的。

DID的话题我们另外开文章分享，目前的域名生态基本还处于售卖域名的闭环中，资产跨链这种比较有价值但困难重重的应用基本还看不到端倪。

.bit 是腾讯朋友出来做的项目，比较有格局的团队，期望未来有亮眼的表现。

![](https://storage.googleapis.com/papyrus_images/1f567cc754983454dfaca802a22425a54ecf8b19f41ec05f21b15fcdccdc3079.png)

今天就先介绍到这了，我们以一张图形象地看一看私钥、公钥、地址，但特别重要的是要理解它的生成过程。

保护好你的私钥，不管是任何格式的私钥本质上都是一个256位的二进制数，只要你的私钥不被泄露，那你的Bitcoin所有权就是神圣不可侵犯的，这是ECC、数学所保障的，比任何银行保险柜都保险。

但是这样一长串私钥，即便以16进制存储也并不好管理。

有没有什么更好的方式呢？

下一篇我们介绍**钱包&助记词**。

---

*Originally published on [Crypto 吊车尾](https://paragraph.com/@imsongoku/btc-9)*
