bitcoin: 压缩公钥与非压缩公钥

> ![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169726/3bde7778-917f-d068-79e7-2d757ae8d396.jpeg)

## 前文介绍

[生成 bitcoin 地址](https://qiita.com/shooter/items/85246781a27a42e3785c) 文章中得到了公钥 `04d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f691757b28e31be71f09f24673eed52348e58d53bcfd26f4d96ec6bf1489eab429d`。

**公钥其实是 secp256k1 椭圆曲线的一个坐标点**，即 (x,y) 形式，用 16 进制表示是
(0xd061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f69,
0x1757b28e31be71f09f24673eed52348e58d53bcfd26f4d96ec6bf1489eab429d)

而且 (x,y) 必然符合：

```python
# python code
Pcurve = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 -1 #有限域
x = 0xd061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f69
y = 0x1757b28e31be71f09f24673eed52348e58d53bcfd26f4d96ec6bf1489eab429d

x_res = x**3+7
y_res = y**2

(x_res%Pcurve) == (y_res%Pcurve)
```

## 为啥符合呢

比特币 secp256k1 椭圆曲线公式是 $y^2=x^3+7$
**椭圆曲线加密算法** 定义在有限域 $\mathbb{F}_p$上
假设 $y^2=x^3+7$ 在 $\mathbb{F}_{23}$，

$x^3+7 \ mod \ 23$ 就是 `((x**3)+7) % 23`

$y^2 \ mod \ 23$ 就是 `(y**2)%23`

`((x**3)+7) % 23 == (y**2)%23` 必然成立，不成立就不符合椭圆曲线加密的定义了。
secp256k1 的有限域是 Pcurve，Pcurve 是个质数。

## 未压缩公钥

前缀 04+x 坐标+y 坐标
`04d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f691757b28e31be71f09f24673eed52348e58d53bcfd26f4d96ec6bf1489eab429d`

## 压缩公钥

前缀 03+x(如果 y 是奇数)，前缀 02+x(如果 y 是偶数)

`0x1757......429d`从最后一位 `0xd`来看，这个数是奇数，所以压缩公钥是`03d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f69`

现在一般都使用压缩公钥，压缩/未压缩公钥生成的地址确实会不一样，未压缩公钥早已成了非主流。

## 通过压缩公钥得到未压缩公钥

### python code

想得到未压缩私钥，常规方法不可行，计算量非常大，需要用到 **二次剩余定理**，二次剩余在密码学以及大数分解中都很有用，还有个很出名的 **Cipolla 算法** 。

```python
def pow_mod(x, y, z):
    "Calculate (x ** y) % z efficiently"
    number = 1
    while y:
        if y & 1:
            number = number * x % z
        y >>= 1
        x = x * x % z
    return number

def get_uncompressed_key(compressed_key):
    Pcurve = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 -1

    y_parity = int(compressed_key[:2]) - 2
    x = int(compressed_key[2:], 16)

    a = (pow_mod(x, 3, Pcurve) + 7) % Pcurve

    y = pow_mod(a, (Pcurve+1)//4, Pcurve)

    if y % 2 != y_parity:
        y = -y % Pcurve

    uncompressed_key = '04{:x}{:x}'.format(x, y)
    print(uncompressed_key)

get_uncompressed_key("03d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f69")
```

### js code

```js
var compressedKey = "03d061e9c5891f579fd548cfd22ff29f5c642714cc7e7a9215f0071ef5a5723f69";
var hex = compressedKey.replace(/^0x/, "");
var b = bitcoinjs.Buffer.Buffer.from(hex, "hex");

var keypair = bitcoinjs.bitcoin.ECPair.fromPublicKeyBuffer(b);
keypair.getPublicKeyBuffer().toString("hex");

var o = { compressed: false };
var displayKey = new bitcoinjs.bitcoin.ECPair(null, keypair.__Q, o);

console.log(displayKey.getPublicKeyBuffer().toString("hex"));
```

### 求 secp256r1 未压缩公钥

这是`secp256r1`，是 `prime256v1`，是`NIST256p`，是 `ecdsa-sha2-nistp256`，也是 `P256` 椭圆曲线的计算代码。
并不是 btc 使用的`secp256k1`曲线！
并不是 btc 使用的`secp256k1`曲线！
并不是 btc 使用的`secp256k1`曲线！

```js
const { PublicKey } = require('bitcore-lib-p256')

const uncompress = key => {
  if (!key.compressed) {
    throw new Error('Publick key is not compressed.')
  }

  const x = key.point.getX()
  const y = key.point.getY()
  const xbuf = x.toBuffer({ size: 32,})

  const ybuf = y.toBuffer({ size: 32,})

  return Buffer.concat([Buffer.from([0x04]), xbuf, ybuf])
}

const pubKey = '023e3df0d294c19ed29a3e83a21648f7fc6ef9c1363c7dffc7b3650e1f08d98032'
const pubKeyObj = PublicKey.fromString(pubKey)
const rs = uncompress(pubKeyObj).toString('hex')
console.log('rs', rs)
```

## 比特币地址

以下是同一个私钥，不同类型的公钥生成的不同地址。
代码见 [gen_addr](https://github.com/liushooter/learn-blockchain/blob/master/gen_addr.rb)

```
#############未压缩公钥生成的地址#############
14xfJr1DArtYR156XBs28FoYk6sQqirT2s
35egEPVeimCvWAmXeHXcYtAUtdA8RtsNUY
mjUcbu6BytKoC7YiEkqPxB1sc6U7nnjFse

#############压缩公钥生成的地址#############
1ASfqPzBTfPSBA9DWdHYYNk4qM5NoGNtzL
3B8gkwUd1ZhpGKqedix8y16zysN6QWqQxS
mpxd8T5AGgpgxGcqECFvNHxPhLg5of8Sh3
```
---

参考：

https://github.com/iancoleman/keycompression

https://www.mina.moe/archives/11441

https://blog.mythsman.com/post/5d2c986667f841464434a58e/

https://bitcointalk.org/index.php?topic=644919

https://git.coolaj86.com/coolaj86/eckles.js

https://crypto.stackexchange.com/a/42906