惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
ThreatConnect
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
H
Help Net Security
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
A
Arctic Wolf
G
Google Developers Blog
量子位
U
Unit 42
I
InfoQ
V
V2EX
F
Fox-IT International blog
P
Privacy & Cybersecurity Law Blog
V
Visual Studio Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
C
CERT Recently Published Vulnerability Notes
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
The Hacker News
The Hacker News
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
C
Cisco Blogs
I
Intezer
Simon Willison's Weblog
Simon Willison's Weblog
O
OpenAI News
Recorded Future
Recorded Future
T
Tenable Blog
W
WeLiveSecurity
腾讯CDC
Stack Overflow Blog
Stack Overflow Blog
T
The Blog of Author Tim Ferriss
www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
Docker
C
Cybersecurity and Infrastructure Security Agency CISA
PCI Perspectives
PCI Perspectives

追梦人物的博客

0x05:Merkle Tree & Patricia Trie 0x04:ECDSA LeetCode 105 从前序与中序遍历构造二叉树 迭代算法原理 + 完整证明 0x03:Address 0x00:专栏开篇 0x01:RLP 编码 Bellman-Ford 算法原理及其在 DeFi 套利中的应用 迪杰斯特拉(Dijkstra)最短路径算法原理、实现与证明 实战:CEX-DEX 稳定币套利监控程序开发 CEX-DEX 稳定币套利模型 Uniswap 手续费和协议费机制剖析 Uniswap 流动性机制及相关数学原理分析 uv 替代 pyenv + pipx + poetry 环境管理实践 Rust 项目从创建到发布 VS Code 调试 Python 比特币跨市场套利的数学模型 ERC-20 相关知识点总结 Django 老项目如何从 SQLite 迁到 PostgreSQL 自动生成接口文档 单元测试 限制接口访问频率 API 版本管理 如何在 Windows 下搭建高效的 django 开发环境
0x02:Secp256k1 - 以太坊设计与实现
2026-04-01 · via 追梦人物的博客

如果你使用过加密货币钱包,在创建钱包时,它通常会给你生成 12 个单词,称为“助记词”,它会强制让你用笔抄下来(不允许你截图),这就是你钱包的“口令”,持有此口令者即可随意操作对应的钱包,因此绝对不能泄露。钱包创建后会显示一个地址,其他人可以向这个地址转账。

这里面实际涉及到这样一个转换过程:助记词 -> 私钥 -> 公钥 -> 地址。

其中助记词 -> 私钥的转换过程并不包含在以太坊的核心实现中,而是由一个外部规范定义,主要由钱包应用实现。私钥 -> 公钥 -> 地址的转换过程属于以太坊的核心实现,私钥 -> 公钥是本篇关注的内容,公钥 -> 地址的转换将在下一篇详细介绍。

可以看到,私钥 -> 公钥这个链条必须是单向转换的,因为公钥会被分享出去,如果公钥可以反过来推出私钥,那就毫无安全可言了。提到单向转换,可能最容易想到的就是哈希函数。但是私钥和公钥在以太坊中的一个重要功能是私钥签名,公钥验证,哈希函数不具备这样的性质。需要选择一个数学性质更为良好的工具来做这个单向转换,比特币选择了 Secp256k1,以太坊则直接沿用了它前辈的选择。

本篇我们来探索私钥 -> 公钥转换涉及的 Secp256k1 相关概念,彻底理解这一过程。

群和域

为了更好的理解 Secp256k1,我们需要先来回顾一些抽象代数的基础概念。虽然这门课程很多专业都要到研究生阶段才会开设,但这里我们只需要了解基础的概念就行了,即使以前没有接触过,用小学学过的整数加减乘除类比就能理解。

群(Group)是抽象代数中最基础的代数结构之一,由非空集合和一个二元运算构成,满足四条公理,具体定义如下:

设非空集合为 \(G\)\(+\) 是定义在 \(G\) 上的二元运算,若 \((G,+)\) 满足以下四条群公理,则称 \((G,+)\) 为一个

  1. 封闭性:对任意的 \(a,b\in G\)\(a+b\in G\)
  2. 结合律:对任意的 \(a,b,c\in G\)\((a+b)+c = a+(b+c)\)
  3. 存在单位元:存在 \(e\in G\),使得对任意的 \(a\in G\)\(e+a = a+e = a\),该元素 \(e\) 称为群 \(G\) 的单位元
  4. 存在逆元:对任意的 \(a\in G\),存在 \(a^{-1}\in G\),使得 \(a+a^{-1} = a^{-1}+a = e\),元素 \(a^{-1}\) 称为元素 \(a\) 的逆元

例如整数和加法便构成一个群,称为整数加群,其中 0 是单位元,\(a\) 的逆元为其相反数 \(-a\)

特殊的,如果群还满足交换律(任意 \(a,b \in G\)\(a + b=b + a\)),则称为交换群(阿贝尔群,Abelian Group),显然整数加群是一个交换群。

由于结合律,\(a+a+...+a\) 结果无歧义,因此可将这种同一元素多次相加的形式简记为乘法形式 \(k{a}\),且规定若 \(k=0\)\(ka\) 的结果为单位元。

若群中存在元素 \(g \in G\),使得 \(G\) 中任意元素均可表示为 \(kg\)\(k\) 为整数),则称 \((G,+)\) 为循环群,元素 \(g\) 称为 \(G\) 的生成元(generator),记为 \(G=⟨g⟩\)。整数加群是一个循环群,其生成元是 1。

域(Field)是比群更复杂的代数结构,由非空集合和两个二元运算(分别记作加法和乘法)构成,其中加法构成交换群,非零元的乘法也构成交换群,且乘法对加法满足分配律,具体定义如下:

设非空集合为 \(F\),在 \(F\) 上定义两个二元运算:\(+\)(加法)和 \(\cdot\)(乘法),若 \((F, + , \cdot)\) 满足以下三组公理,则称 \(F\) 为一个

  1. 加法交换群\((F,+)\) 是交换群
  2. 非零元乘法交换群:设 \(F^* = F \setminus \{e\}\)\(F\) 中去掉加法单位元 \(e\)),则 \((F^*, \cdot)\) 是交换群
  3. 乘法对加法的分配律:对任意 \(a,b,c \in F\),有\(a \cdot (b+c) = a \cdot b + a \cdot c\)\((b+c) \cdot a = b \cdot a + c \cdot a\)(左右分配律)。

注意这里加法和乘法只是对两种运算的抽象称呼,加法和乘法是两种独立运算,不要将乘法和上一节中介绍的多个相同群元素相加的乘法简记形式(\(a+a+...+a = ka\))混淆。

若域中元素个数 \(q\) 有限,则称为有限域(伽罗瓦域,Galois Field),记为 \(GF(q)\)\(F_q\)\(q\) 为域的阶。

和本篇内容息息相关的一个域叫做 \(p\) 阶素数域 \(F_p\)。集合 \(F_p = \{0, 1, 2, ..., p-1\}\),其中 \(p\) 为素数。域上的加法和乘法运算都是模 \(p\) 的:\(a + b \equiv (a + b) \pmod p\)\(a \times b \equiv (a \times b) \pmod p\)。可以证明以上集合和运算构成一个域(套入域的三组公理可以验证,除了乘法逆元不那么显然以外,其他性质均继承自整数的加法和乘法,关于乘法逆元的存在性可问下 AI,这里不再占用过多篇幅进行说明)。

椭圆曲线

定义

Secp256k1 的椭圆曲线定义为:

$\(y^2 \equiv x^3 + 7 \pmod p\)$
其中 \(\equiv\) 是同余符号,可以理解为 \(y^2\)\(x^3 + 7\)\(p\) 的余数相同。椭圆曲线上的有效点是所有满足方程 \(y^2 \equiv x^3 + 7 \pmod p\) 的坐标对 \((x, y)\),其中 \(x, y \in F_p\)\(F_p\) 为上一节提到的 \(p\) 阶素数域。

数学家们发现的椭圆曲线的一些性质:

  1. 关于 x 轴对称
  2. 非 x 轴对称的两点之连线(可为同一个点,此时退化为切线),必与曲线上某点相交

以下是一个椭圆曲线的可视化示例:

elliptic_curve.png

给定点 \((x, y)\),很容易验证其是否在椭圆曲线上。👇下面是 Python 写的验算程序:

# secp256k1 曲线参数
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
a = 0
b = 7

# 生成点 G 的坐标
Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8


def is_on_curve(x, y, p, a, b):
    """验证点 (x, y) 是否满足椭圆曲线方程 y² ≡ x³ + ax + b (mod p)"""
    left = (y * y) % p
    right = (x * x * x + a * x + b) % p
    return left == right


# 验证生成点 G
print(f"生成点 G ({hex(Gx)}, {hex(Gy)}) 在曲线上: {is_on_curve(Gx, Gy, p, a, b)}")

x = 0x6E145CCEF1033DEA239875DD00DFB4FEE6E3348B84985C92F103444683BAE07B
y = 0x83B5C38E5E2B0C8529D7FA3F64D46DAA1ECE2D9AC14CAB9477D042C84C32CCD0
print(f"验证点 ({hex(x)}, {hex(y)}) 在曲线上: {is_on_curve(x, y, p, a, b)}")

加法运算

给定两个不同的点 \(P = (x_1, y_1)\)\(Q = (x_2, y_2)\),且 \(P \neq -Q\),它们的和 \(R = P + Q = (x_3, y_3)\) 定义如下:

几何规则:通过 \(P\)\(Q\) 画一条直线,这条直线与椭圆曲线相交于第三个点,取该点关于 x 轴的对称点,即为 \(R\)

elliptic_curve_add_p1_p2.png

给定两个相同的点 \(P = (x_1, y_1)\),它们的和 \(R = P + P = 2P = (x_3, y_3)\) 定义如下:

几何规则:在 \(P\) 点处作椭圆曲线的切线,这条切线与椭圆曲线相交于第二个点,取该点关于 x 轴的对称点,即为 \(2P\)

elliptic_curve_add_p1_p1.png
如果两个点关于 x 轴对称,从图上看没有交点,但是我们特殊规定其与曲线交于无穷远点,记作 \(O\)

elliptic_curve_add_vertically.png

另特殊规定,对于任意点 \(P\),其和无穷远点相加都有:

\[P + O = O + P = P\]

点的逆元

\(P = (x, y)\) 关于 x 轴的对称点 \(-P\) 是其逆元。在椭圆曲线上,逆元的定义非常简单:

\[-P = (x, -y \pmod p) = (x, p - y)\]

点与它的逆元相加,结果是无穷远点:

\[P + (-P) = O\]

群结构

根据前面的分析:

  1. 椭圆曲线(含无穷远点)上两个点相加,结果仍在曲线上,满足封闭性
  2. 无穷远点充当了单位元的功能,即存在单位元
  3. \(P\) 关于 x 轴对称的点 \(-P\) 是其逆元,即存在逆元
  4. 加法满足结合律(较为复杂,此处略过)和交换律

因此椭圆曲线上的点(包括无穷远点)构成一个加法交换群。进一步地还可以证明,这是一个循环群。

曲线参数

以下是椭圆曲线 secp256k1 的一些参数值,包括素数 \(p\)、阶 \(N\) 和生成点 \(G\) 的坐标。

素数 \(p\)(定义域的阶):

\(p = 2^{256} - 2^{32} - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1\)

十进制: 115792089237316195423570985008687907853269984665640564039457584007908834671663

十六进制:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F

\(N\)(椭圆曲线所有点含无穷远点的总数):
十进制:

115792089237316195423570985008687907852837564279074904382605163141518161494337

十六进制:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

生成点 \(G\)(基点,Base Point)的坐标:

Gx (十六进制):0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798

Gx (十进制): 55066263022277343669578718895168534326250603453777594175500187360389116729240

Gy (十六进制):0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8

Gy (十进制): 32670510020758816978083085130507043184471273380659243275938904335757337482424

生成点 G 是椭圆曲线的基点,可以生成曲线上的所有其它点。

私钥

私钥是一个在特定范围内的随机整数。具体来说,私钥 \(d\) 满足:

\[1 \leq d < N\]

其中 \(N\) 是 secp256k1 椭圆曲线的阶,即椭圆曲线所有点(含无穷远点)的总数。

可见私钥并无任何特殊性,它仅仅只是一个取值范围为 \([1, N)\) 的整数。但是私钥的选择必须极其讲究,不能选择容易被人猜到或者瞎撞就能撞到的值。目前被广泛采用的一种标准是由 BIP39 定义的助记词方案,你在加密钱包中看到的 12 个单词助记词就是这个标准规定的的产物,它在保证私钥熵源的不可猜测性(高安全性)的同时,兼顾了人类可记忆性、易备份性与输入容错性。

公钥

从私钥 \(d\) 到公钥 \(Q\) 的转换非常简单:

\[ Q = d \times G \]

其中,\(G\) 是 secp256k1 的生成点。乘法是在此前关于群的章节中定义的乘法,\(d \times G\) 即点 \(G\) 相加 \(d\) 次。

前面已经说过,secp256k1 椭圆曲线的点构成循环群,所以公钥也是椭圆曲线上的一个点 \((x, y)\),其中 \(x, y \in F_p\)。在实际应用中,公钥有两种主要的序列化格式:未压缩格式(uncompressed)和压缩格式(compressed)。

未压缩格式占 65 字节,格式为 0x04 前缀 + 32 字节 x 坐标 + 32 字节 y 坐标。例如生成点 G 的未压缩格式为:

0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8

压缩格式占 33 字节,格式为前缀 0x020x03 + 32 字节 x 坐标。前缀 0x02 表示 y 坐标是偶数,0x03 表示 y 坐标是奇数。压缩格式的原理是:已知椭圆曲线方程 \(y^2 \equiv x^3 + 7 \pmod p\),如果知道 x 坐标,可以解出 \(y \equiv \pm\sqrt{x^3 + 7} \pmod p\)。对于每个 x,最多有两个对应的 y 值:\(y\)\(-y\)。这两个值一个是偶数,一个是奇数(因为 \(p\) 是奇数),因此只需要一个前缀位来区分。

压缩格式将公钥大小从 65 字节减少到 33 字节,节省了近 50% 的存储空间和传输带宽。以太坊的私钥导入格式(如 Keystore 文件)中通常存储未压缩公钥,但在交易签名和地址生成时使用压缩公钥。

go-ethereum 中的实现

本节阅读提示

本篇重点主要还是理解 secp256k1 椭圆曲线的数学性质和私钥到公钥的转换机制,具体的代码实现细节偏密码学,了解有哪些实用的函数和接口就好,对理解以太坊本身没有太大帮助,所以看看就好。

go-ethereum 以太坊客户端中包含了完整的 secp256k1 椭圆曲线实现。这些实现位于 crypto/secp256k1 包中,提供了私钥生成、公钥生成、点运算等核心功能。

实现概述

go-ethereum 提供了两种实现方式:
- Go 纯实现:完全用 Go 语言编写的实现,位于 curve.goscalar_mult_nocgo.go
- C 优化实现:调用 libsecp256k1 库的优化实现,位于 scalar_mult_cgo.go

外部统一使用 Go 接口,内部会根据编译配置自动选择最优实现。

曲线参数定义

crypto/secp256k1/curve.go 文件中定义了 secp256k1 的所有参数:

// BitCurve 表示一条 Koblitz 曲线(a=0 的椭圆曲线)
type BitCurve struct {
    P       *big.Int // 有限域的素数
    N       *big.Int // 曲线的阶(order)
    B       *big.Int // 椭圆曲线方程的常数项(y² = x³ + b)
    Gx, Gy  *big.Int // 生成点 G 的 (x, y) 坐标
    BitSize int      // 位的长度(256位)
}

// secp256k1 曲线参数(在包初始化时设置)
var theCurve = new(BitCurve)

func init() {
    // 素数 p:定义有限域 F_p
    theCurve.P, _ = new(big.Int).SetString(
        "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 0)

    // 阶 N:曲线上有效点的总数
    theCurve.N, _ = new(big.Int).SetString(
        "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 0)

    // 常数 b = 7(曲线方程 y² = x³ + 7)
    theCurve.B, _ = new(big.Int).SetString(
        "0x0000000000000000000000000000000000000000000000000000000000000007", 0)

    // 生成点 G 的 x 坐标
    theCurve.Gx, _ = new(big.Int).SetString(
        "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 0)

    // 生成点 G 的 y 坐标
    theCurve.Gy, _ = new(big.Int).SetString(
        "0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 0)

    // 位长度:256位
    theCurve.BitSize = 256
}

// S256 返回 secp256k1 椭圆曲线实例
func S256() *BitCurve {
    return theCurve
}

这些参数与我们在前面的章节中讨论的完全一致。S256() 函数是获取 secp256k1 曲线实例的入口,所有椭圆曲线操作都通过这个实例进行。

私钥生成

私钥生成在 crypto/crypto.go 中实现,使用 Go 标准库的密码学安全随机数生成器:

import (
    "crypto/ecdsa"
    "crypto/rand"
    "github.com/ethereum/go-ethereum/crypto/secp256k1"
)

// GenerateKey 生成一个新的 secp256k1 私钥
func GenerateKey() (*ecdsa.PrivateKey, error) {
    // 使用 S256() 获取 secp256k1 曲线
    // rand.Reader 提供密码学安全的随机数
    return ecdsa.GenerateKey(S256(), rand.Reader)
}

公钥生成

从私钥生成公钥通过标量乘法实现。crypto/secp256k1/curve.go 中的 ScalarBaseMult 函数:

// ScalarBaseMult 计算 k × G,其中 G 是生成点,k 是标量(私钥)
// 参数 k 是大端序(big-endian)字节数组
// 返回值是公钥的 (x, y) 坐标
func (bitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
    // 调用通用标量乘法函数,以生成点 G 为基点
    return bitCurve.ScalarMult(bitCurve.Gx, bitCurve.Gy, k)
}

ScalarBaseMultScalarMult 的特例,专门用于基点 G 的标量乘法。这个函数会被编译器优化,因为基点是固定的,可以使用预计算表加速。

通用的标量乘法函数 ScalarMult 实现了完整的 \(k \times P\) 运算(P 可以是任意点)。go-ethereum 提供了两个版本:Go 版本(scalar_mult_nocgo.go)和 C 优化版本(scalar_mult_cgo.go)。C 版本使用了 libsecp256k1 库的高性能实现。

点运算

点运算包括点加法(Add)和点加倍(Double),是椭圆曲线运算的基础。

点加法

// Add 计算 (x1, y1) + (x2, y2)
// 返回和点的 (x, y) 坐标
func (bitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
    // 处理无穷远点(单位元)
    // 如果一个点是无穷远点,返回另一个点
    if x1.Sign() == 0 && y1.Sign() == 0 {
        return x2, y2
    }
    if x2.Sign() == 0 && y2.Sign() == 0 {
        return x1, y1
    }

    z := new(big.Int).SetInt64(1) // 仿射坐标的 z = 1

    // 如果两个点相同,调用点加倍
    if x1.Cmp(x2) == 0 && y1.Cmp(y2) == 0 {
        return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z))
    }

    // 否则调用 Jacobian 坐标系下的点加法
    return bitCurve.affineFromJacobian(bitCurve.addJacobian(x1, y1, z, x2, y2, z))
}

点加法的实现中,有一个重要的优化:Jacobian 坐标系

Jacobian 坐标系优化

在仿射坐标(affine coordinates)中,椭圆曲线的点用 \((x, y)\) 表示。在 Jacobian 坐标系中,点用 \((x, y, z)\) 表示,其中:

\[x_{\text{affine}} = \frac{x}{z^2} \pmod p\]

\[y_{\text{affine}} = \frac{y}{z^3} \pmod p\]

Jacobian 坐标系的优势在于:点加法和点加倍运算不需要进行昂贵的模逆运算(modular inversion)。模逆运算的计算复杂度远高于模乘法,是椭圆曲线运算的主要性能瓶颈。

在 Jacobian 坐标系下完成一系列运算后,通过 affineFromJacobian 函数转换回仿射坐标:

// affineFromJacobian 将 Jacobian 坐标转换为仿射坐标
// 输入:(x, y, z),输出:(xOut, yOut)
func (bitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
    // 如果 z = 0,表示无穷远点
    if z.Sign() == 0 {
        return new(big.Int), new(big.Int)
    }

    // 计算 z^(-1) mod p
    zinv := new(big.Int).ModInverse(z, bitCurve.P)

    // 计算 z^(-2)
    zinvsq := new(big.Int).Mul(zinv, zinv)

    // xOut = x * z^(-2) mod p
    xOut = new(big.Int).Mul(x, zinvsq)
    xOut.Mod(xOut, bitCurve.P)

    // yOut = y * z^(-3) mod p
    zinvsq.Mul(zinvsq, zinv)
    yOut = new(big.Int).Mul(y, zinvsq)
    yOut.Mod(yOut, bitCurve.P)

    return
}

点加倍

// Double 计算 2 × (x, y) = (x, y) + (x, y)
func (bitCurve *BitCurve) Double(x, y *big.Int) (*big.Int, *big.Int) {
    z := new(big.Int).SetInt64(1) // 仿射坐标的 z = 1
    // 在 Jacobian 坐标系下执行点加倍,然后转换回仿射坐标
    return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x, y, z))
}

doubleJacobian 函数实现了 Jacobian 坐标系下的点加倍运算,使用了优化的算法减少模运算次数。

公钥序列化

go-ethereum 提供了公钥的序列化和反序列化函数,支持未压缩格式:

// Marshal 将点 (x, y) 序列化为未压缩格式
// 格式:0x04 + x(32字节) + y(32字节),共65字节
func (bitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
    byteLen := (bitCurve.BitSize + 7) >> 3 // 计算字节长度:(256+7)/8 = 32
    ret := make([]byte, 1+2*byteLen)
    ret[0] = 4 // 未压缩格式的前缀

    // 将 x 和 y 写入字节数组(大端序)
    math.ReadBits(x, ret[1:1+byteLen])
    math.ReadBits(y, ret[1+byteLen:])
    return ret
}

// Unmarshal 将序列化的数据解析为点 (x, y)
func (bitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
    byteLen := (bitCurve.BitSize + 7) >> 3

    // 验证数据长度和前缀
    if len(data) != 1+2*byteLen {
        return
    }
    if data[0] != 4 { // 必须是未压缩格式的前缀
        return
    }

    // 解析 x 和 y
    x = new(big.Int).SetBytes(data[1 : 1+byteLen])
    y = new(big.Int).SetBytes(data[1+byteLen:])
    return
}

这些函数与之前讨论的公钥格式完全对应。

总结

  • secp256k1 椭圆曲线定义在 \(p\) 阶素数域上,方程为 \(y^2 \equiv x^3 + 7 \pmod p\),曲线上的点构成加法群,且是循环群,阶为 \(N\),选择群中的一个点作为生成点 \(G\)
  • 私钥 \(d\)\((0,N]\) 的一个整数,私钥的选择要尽可能随机,不能被爆破
  • 公钥 \(Q = d \times G\)

-- EOF --