02 · 密码学三原语:哈希 / 公私钥 / 数字签名
承接
01。01 说"密码学保证不可篡改 + 所有权",这篇把那三块 拆开讲清。沿用 01 的"村庄账本 / 私章 / 防伪封条"画面。结构见README.md§1。
0. 一句话本质
三个工具合起来回答同一个问题:在一本人人可见、没有裁判的公共账本上,怎么证明"这笔操作确实是某地址的主人授权的、且内容没被动过手脚"——不暴露任何秘密、谁也伪造不了、改一个字就露馅。
哈希 = 防伪封条;私钥 = 只有你的私章;公钥/地址 = 公开备案的印鉴 + 户名;数字签名 = 把私章以"锁死这张单子内容"的方式盖上去。
1. 怎么产生的:它解决什么问题
01 留了三个坑,全靠这篇填:
- "改任何历史一个字节,全网立刻识别"——靠什么识别? → 哈希。
- "谁掌握私钥谁就拥有资产"——这个'拥有'在数学上怎么成立? → 公私钥。
- "付款人签字,全网能验真且签完改不了"——怎么做到既能验真又不泄密又防伪? → 数字签名。
核心难点:账本全公开(人人能看每笔流水),又没有裁判(没有银行帮你确认身份)。 传统世界靠"出示身份证 + 银行核验",区块链里没有这个角色。必须用纯数学,让 任何人都能独立验证"这是本人授权的、没被篡改的",同时不需要本人在场、不需要可信第三方、秘密永不离开本人。
2. 它本质在做的"那一件事"
把"信任一个人/机构"替换成"验证一个数学事实"。
- 不再"我信银行说这是你",而是"任何人用公开信息就能算出:能产生这个签名的人,必然掌握那个私钥;而那个私钥对应那个地址"。
- 不再"我信账本没被改",而是"任何人重算哈希,对不上就是被改过"。
信任被外包给数学。这就是 01 说的 trustless 的密码学底座。
3. 类比(村庄账本视角,逐个深化 01 的小词典)
3.1 哈希 = 一台"魔法榨汁机"(防伪封条的本体)
把任意文件丢进榨汁机,吐出一杯固定容量、颜色独一无二的果汁:
- 同样的文件 → 永远同样的颜色(确定性)。
- 改一个标点 → 颜色完全变样(雪崩效应,avalanche)。
- 看着果汁,没法还原出文件(单向,不可逆)。
- 几乎不可能找到两份不同文件榨出同色(抗碰撞)。
账本每一页页脚写着"上一页榨出的果汁颜色"。想偷改第 100 页?它的颜色变了 → 第 101 页页脚记的"第 100 页颜色"对不上 → 你得重榨 101、102…一直到最新页, 还要比全村继续往后榨的速度快。这就是 01 "特殊胶水粘死每页"的数学真身。
关键:哈希不是加密。加密有钥匙、能解密;哈希没钥匙、设计上就不可逆。 以太坊用 Keccak-256,比特币用 SHA-256。我们项目
demo.ts里keccak256(abi.encode(addr, 9))算 USDC 余额存储槽——用的就是这台榨汁机。
3.2 私钥 / 公钥 = 一枚刻不出第二枚的私章 + 它的公开印鉴
- 私钥:一个 256 位的随机秘密数(≈ 在 2²⁵⁶ 个数里抽一个,比宇宙原子数还多)。 生成它不需要联网、不需要注册、不需要任何机构——你扔 256 次硬币就能造一个, 这串数本身就是私章。这是"自我保管(self-custody)"成立的根。
- 公钥:由私钥经椭圆曲线乘法算出(
Q = d × G,secp256k1 曲线)。 这一步单向:知私钥能秒算公钥;知公钥反推私钥在数学上不可行 (椭圆曲线离散对数难题)。好比:从私章能盖出印鉴,但拿着印鉴谁也刻不出那枚章。 - 地址:再把公钥丢进哈希榨汁机、取末 20 字节 = 你账本上的户名
(以太坊:
address = 末20字节( Keccak256(公钥) ))。又一次单向。
两段都不可逆,所以链路是:私钥 →(单向) 公钥 →(单向) 地址。
你能从私钥推出地址,永远不能从地址倒推私钥。 我们核查 0x6bdc / 0x65A8
就是只看得到地址(户名)和它的全部流水,但永远控制不了它——因为没有那把私钥。
3.3 数字签名 = 把私章以"锁死这张单子"的方式盖上去
签名做两件事,一次完成:
- 证明是本人:只有持私钥者能生成对该消息有效的签名(ECDSA 算法)。
- 锁死内容:签名是对"这张单子的哈希"签的。改单子任何一处 → 哈希变 → 原签名验不过。签完想改金额?签名当场失效。
验证只需公开信息:任何节点拿签名 + 单子,用 ecrecover 反算出公钥 →
推出地址 → 看是否等于 from。等于 ⇒ "能签出这个的人必持有 from 的私钥,
且单子没被动过"。全程私钥从未离开你的设备,没有第三方参与。
注意:签名不加密交易。链上交易是明文公开的(金额、地址全看得见)。 签名只解决"是谁授权的 + 有没有被改",不解决保密。公链上几乎不用加密, 因为一切本就公开——这点极反直觉,务必记牢(见 §7 误区)。
4. 核心关键点(5 条)
- 哈希 = 单向防伪指纹。确定性、定长、雪崩、不可逆、抗碰撞。用于:区块链接、
交易 ID、地址派生、Merkle 树(
03待写)、合约存储槽、 CREATE 合约地址(= 哈希(部署者, nonce))。 - 非对称密钥 = 两段不可逆链路:
私钥 → 公钥 → 地址,正向易、反向不可行。 "拥有资产" 在数学上 = "掌握那把私钥",没有别的定义、没有别的凭证。 - 数字签名 = 认证 + 完整性,一次搞定:证明本人授权 + 锁死消息内容; 验证仅需公开信息;秘密永不外泄。
- 信任被外包给数学:不信人/机构,验数学事实。这是 01 trustless 的实现层。
- 没有"找回/重置":私钥是唯一所有权凭证。无中心机构 ⇒ 丢了=资产永久锁死、 被骗走=无人能追回。安全的全部重量压在"私钥/助记词保管"这一点上。
5. 必须先认识的前置名词(小词典)
| 名词 | 一句话 | 类比 |
|---|---|---|
| Keccak-256 / SHA-256 | 以太坊 / 比特币用的哈希函数 | 两个牌子的魔法榨汁机 |
| 雪崩效应 (Avalanche) | 输入改 1 bit,输出约一半 bit 翻转 | 改个标点,果汁全变色 |
| 抗碰撞 (Collision-resistance) | 找两份不同输入同哈希,实际做不到 | 两份文件榨出同色,碰不到 |
| 非对称加密 (Asymmetric) | 一对密钥:私钥保密、公钥公开,单向派生 | 私章 / 公开印鉴 |
| secp256k1 | 比特币/以太坊用的椭圆曲线 | 印章雕刻所用的那套规格 |
| ECDSA | 基于椭圆曲线的签名算法 | 盖章 + 验章的具体工艺 |
| ecrecover | 由签名 + 消息反算出签名者公钥/地址 | 由印迹反查是谁的印鉴 |
| 助记词 / 种子 (Mnemonic, BIP-39) | 把主私钥编码成 12/24 个人类可读单词 | 把私章拆成一句口诀,背得出就等于拿着章 |
| HD 钱包 (BIP-32/44) | 一个种子按路径派生出无数子私钥/地址 | 一句母口诀派生整套分号印章 |
| nonce(签名场景) | 每次签名用的一次性随机数;重复使用会泄露私钥 | 盖章时垫的一次性防伪纸,重复用会被看穿 |
| 地址校验和 (EIP-55) | 地址里大小写混写用于防手误 | 户名里的防错字母 |
助记词那条解释了项目里反复的警告:任何让你把助记词/私钥输进网页/聊天/AI 的, 都是直接索取你的私章本身。
0x6bdc那次"朋友给地址让你研究"也是同一类 社工的远端表亲。
6. 一笔转账里三原语怎么咬合(端到端,放大 01 §6 的"签名"那一步)
01 §6 第 2 步一笔带过的"签名",放大看其实是三原语连环:
① 私钥 d :256-bit 随机秘密数(离线即可生成,无需任何机构)
② 公钥 Q :Q = d × G (椭圆曲线乘法;单向,Q 推不回 d)
③ 地址 A :A = 末20( Keccak256(Q) ) (哈希;单向,A 推不回 Q)
—— 你的"户名"。别人能往 A 打钱,但动不了 A 里的钱(没你的 d)
④ 造交易 tx:{to, value, nonce, gas, data} —— 明文,将公开
⑤ 指纹 h :h = Keccak256( 序列化(tx) ) —— 榨汁机出指纹
⑥ 签名 :sig(r,s,v) = ECDSA_sign(h, d) —— 必须有 d 才签得出
⑦ 广播 :把 (tx, sig) 发到全网 —— 明文,进 mempool(人人可见)
⑧ 全网每个节点独立验证:
Q' = ecrecover(h, sig) 由签名+指纹反算公钥
A' = 末20( Keccak256(Q') )
检查 A' == tx.from ?
是 ⇒ 能产生 sig 的人必持有 from 的私钥,且 tx 一字未改 ⇒ 接受
(改 tx 任意一处 → h 变 → ecrecover 出错地址 → 拒绝)
全程 d 从未离开你的设备;没有银行、没有"登录"、没有第三方。 "所有权"在这套数学里被精确定义为:能对 from 地址的交易产出有效签名 ⇔ 掌握 d。 这就是为什么私钥即一切,也是为什么链上世界"无需信任"却能防赖账。
7. 常见误区(高频踩,逐条拍死)
| 误区 | 实情 |
|---|---|
| "区块链把我的交易加密了" | 没有。链上交易明文公开(金额/地址全可见)。签名 ≠ 加密,它只证授权+防篡改 |
| "哈希是一种加密,能解密回去" | 不能。哈希无密钥、设计上单向不可逆。它是指纹不是密文 |
| "公钥能反推私钥" / "地址能反推公钥" | 都不能。两段都是单向(ECC 离散对数 / 哈希)。这是整个所有权模型的根 |
| "地址就是公钥" | 不是。地址 = 哈希(公钥) 取末 20 字节,又单向一层 |
| "生成私钥要联网/找平台注册" | 不用。私钥就是个 256-bit 随机数,离线扔硬币都能造;地址本地可算。这正是自我保管的根 |
| "签名会暴露私钥" | 正确实现下不会。但同一私钥用重复 nonce 签两次会泄露私钥(历史上真实事故,§5 nonce 条) |
| "私钥丢了能找回 / 重置密码" | 无中心机构 = 无重置。私钥/助记词是唯一凭证,丢=永久锁死,被骗走=无人能追 |
| "我只是把助记词贴给客服核验一下" | 等于把私章原件交出去。任何索要助记词/私钥的,无一例外是抢钱 |
| "签名了就等于交易生效" | 签名只证授权。生效仍要进 mempool→出块→确认(01 §6) |
8. 与本项目实践的对应
你早就用过这三样,这篇补的是它们脚下的数学:
- 私钥 → signer:
watcher里new Wallet(PRIVATE_KEY, provider)、Wallet.createRandom()(demo-flashbots 的 authSigner)——就是 §6 ①②③。 - anvil key
0xac09…:公开周知的测试私钥,对应地址0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266(= 末20(Keccak256(它的公钥)))。 我们全程敢硬编码它,正因为它无主网资金——私钥即所有权,没钱的章随便盖。 - 数字签名实战:
flashbots.ts的signBundle(签交易)+ X-Flashbots-Signature 认证头(用 authSigner 私钥对请求签名,让 relay 认得是同一 searcher)—— §3.3 的两段式签名活例。 - 哈希实战:
demo.tsdealUsdc里keccak256(abi.encode(recipient, 9))算 USDCbalances[addr]存储槽;合约 CREATE 地址 = 哈希(deployer, nonce) (Phase 4 部署得到的0xDda8…/0x0392…就这么来的)。 - 地址只读不可控:
0x6bdc(假)/0x65A8(真)我们能看全部流水却永远动不了 ——正因 §3.2 的单向链路:有地址,没私钥,免谈。
→ 回头看这些代码,从"调 API" 升级为"知道每个 Wallet/keccak256 在数学上担保了什么"。
9. 自检问题(答得出才算过)
- 哈希和加密的本质区别是什么?为什么说"哈希不可解密"不是缺陷而是设计目标?
私钥 → 公钥 → 地址为什么是两段都不可逆?哪一步用椭圆曲线、哪一步用哈希?- 数字签名同时保证了哪两件事?验证签名为什么只需要公开信息?
- 为什么"生成私钥不需要任何中心机构"是自我保管成立的根?这对"私钥丢失/被骗"意味着什么?
- 链上交易是加密的吗?签名让交易保密了吗?(小心,这题专坑直觉)
- 我们项目里敢把 anvil 私钥
0xac09…写进代码、推上 git,为什么不算泄密? 0x65A8流水全公开、我们看得一清二楚,为什么仍然完全无法动它一分钱?
── Q&A / 更正记录区(按日期追加)──
规范见
README.md§0。质疑/反馈/纠错按日期累积,不抹错误轨迹。
-
(2026-05-19 建立。暂无 Q&A。)
-
(2026-07-26) 问:详细补充哈希函数的算法细节,包括 hash 过程怎么"求解"出前面几位固定值。
答(补充 §3.1 直觉层面之下的算法内部细节,未更正原文):
先澄清关键区分:哈希算法本身的计算是一次性的、确定性的运算,不含任何"搜索/求解"——给定输入走完固定步骤直接吐出输出。"求解前导零"是 PoW 协议在哈希算法外部加的暴力搜索循环,两者不能混为一谈(挖矿部分的完整展开见
04-共识机制.md同日条目)。-
SHA-256 内部机制(比特币用,双重哈希):
- 预处理:消息补 1 个比特
1,再补0到长度 ≡448 (mod 512),最后 64 位写原始消息比特长度——任意长度输入都能切成整数个 512 位块,是输出永远定长的机制层原因。 - 初始值与常量:8 个初始哈希值 H0-H7 取自前 8 个质数平方根小数部分前 32 位;64 个轮常量 K 取自前 64 个质数立方根小数部分前 32 位——这些是"nothing-up-my-sleeve numbers",可公开复现,防设计者留后门。
- 压缩函数(每 512 位块):消息扩展成 W0..W63;8 个工作变量
a..h初始化为当前 H0-H7;跑 64 轮,每轮T1 = h + Σ1(e) + Ch(e,f,g) + K[i] + W[i],T2 = Σ0(a) + Maj(a,b,c),然后变量整体右移一位、a = T1+T2;其中Ch(e,f,g)=(e AND f) XOR (NOT e AND g)(e 是 1 听 f、e 是 0 听 g),Maj(a,b,c)是三者少数服从多数,Σ0/Σ1是不同幅度的循环右移异或。64 轮跑完把 a..h 加回 H0-H7(mod 2³²)——这个"加回"+64 轮的持续移位异或传播,是雪崩效应真正的来源。 - 双重哈希
SHA256(SHA256(x)):为了防 length-extension attack(单层 SHA-256 的内部结构使攻击者能在不知道原文时"续接"消息并算出合法哈希,双重哈希切断这个可能性)。
- 预处理:消息补 1 个比特
-
Keccak-256 内部机制(以太坊用,算地址/状态树/几乎所有链上哈希):
- 海绵结构(sponge):1600 位状态,分速率 r=1088 位(对外交互)+ 容量 c=512 位(内部混合、不对外暴露,c 越大抗碰撞/抗原像安全边界越高)。
- 吸收阶段:消息切 1088 位一块,与状态 r 部分异或,整个 1600 位状态过一次
Keccak-f[1600]置换,重复到吸收完。 Keccak-f[1600]:跑 24 轮,每轮 θ、ρ、π、χ、ι 五个步骤映射,在 5×5×64 的三维比特数组上做异或/循环移位/查表替换。- 挤出阶段:直接读状态 r 部分的 256 位作输出。
- 常见混淆点:以太坊的"SHA3"实际是原始 Keccak 的填充规则,跟 NIST 定稿后的标准 SHA3 填充不同(NIST 定稿时多加了两位后缀区分用途)——
keccak256≠ 严格意义的sha3_256,本项目demo.ts里keccak256(abi.encode(addr, 9))算 USDC 余额存储槽用的就是这台"魔法榨汁机"。
完整原始记录(含 PoW 概率推导与可运行 Python 示例)见
../total/Q&A/2026-07-26.mdQ2。 -