了解 secp256k1 与 多签钱包
Source: Dev.to

“我们需要的是一种基于密码学证明而非信任的电子支付系统。” — 中本聪
每天,数以百万计的交易在比特币和以太坊网络中流动。然而,令人惊讶的是,真正理解支撑这一切的密码学基础——secp256k1(为两条区块链提供动力的椭圆曲线)的开发者寥寥无几。
在构建我的 Rust 多签钱包 时,我意识到大多数开发者(包括我自己起初)都把椭圆曲线密码学当作黑盒。我们只会导入库、调用函数,并相信魔法会自行发生。
本文将改变这种现状。我们将揭开 secp256k1 的神秘面纱,探讨多签钱包的工作原理,并说明为什么 Rust 是构建密码系统的完美语言。

什么是 secp256k1?
secp256k1 是一个椭圆曲线,由以下简单方程(在有限域上)定义:
y² = x³ + 7
这个看似简单的方程产生了一条具有卓越特性的曲线:
- 点加法 – 你可以“相加”曲线上的两个点得到第三个点。
- 标量乘法 – 将一个点乘以整数会得到另一个点。
- 单向函数 – 从私钥 → 公钥的转换很容易;而逆向过程在计算上是不可行的。

为什么选择这条曲线?
比特币的创始人选择 secp256k1 有以下几个原因:
- 效率 – 方程
y² = x³ + 7没有x²和x项,使得模运算更快。 - 安全性 – 256 位的密钥空间提供约 2²⁵⁶ 个可能的密钥(比可观测宇宙中的原子还多)。
- 非 NSA – 与某些 NIST 曲线不同,secp256k1 的参数并非由任何政府机构挑选。
Source: …
如何使用密码学密钥

私钥
256 位随机数,例如:
0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b
公钥
通过标量乘法得到曲线上的一点:
PublicKey = PrivateKey × G
其中 G 是生成点(secp256k1 上的固定点)。
地址
- 使用 SHA‑256 对公钥进行哈希,然后再使用 RIPEMD‑160。
- 添加版本字节和校验和。
- 编码(通常为 Base58Check) → 你的钱包地址。
多签钱包:共享所有权
什么是多签钱包?
需要 M out of N(M 署名中的 N)签名才能授权交易的钱包。
示例
| M‑of‑N | 用例 |
|---|---|
| 2‑of‑3 | 公司钱包(CEO、CFO、CTO——任意两人即可批准) |
| 3‑of‑5 | DAO 金库(多数批准) |
| 1‑of‑2 | 个人备份(主钥匙 + 恢复钥匙) |

为什么多签很重要
- 安全性 – 没有单点故障;丢失一把钥匙不会危及资金。
- 治理 – 重大决策需要共识。
- 最小化信任 – 无需信任单一方。
- 恢复 – 内置钥匙丢失的恢复机制。
用 Rust 构建:为什么?
Rust 不仅仅是一门流行的语言——它几乎是为密码学而生的。
// Rust's type system prevents common crypto bugs
pub struct PrivateKey([u8; 32]); // Exactly 32 bytes, no more, no less
impl PrivateKey {
// Ownership system ensures keys aren't accidentally copied
pub fn sign(&self, message: &[u8]) -> Signature {
// Compile‑time guarantee of memory safety:
// - No buffer overflows
// - No use‑after‑free
}
}
// Error handling forces you to deal with failures
match wallet.verify_signature(&tx, &sig) {
Ok(valid) => { /* proceed */ }
Err(e) => { /* must handle error */ }
}
Rust 在密码学中的关键优势
- 内存安全 – 防止缓冲区溢出、悬空指针和数据竞争。
- 零成本抽象 – 高层次的易用性而没有运行时开销。
- 强类型系统 – 防止混淆密钥、签名、哈希等。
- 显式错误处理 – 强制开发者处理失败情况,减少静默错误。
- Cargo 与 Crates.io – 轻松的依赖管理和可复现的构建(对安全审计至关重要)。
有了这些特性,Rust 让你能够自信地实现 secp256k1 操作和多签逻辑,因为编译器会为你把关。
祝编码愉快,愿你的密钥永远保密!
零成本抽象
高级代码编译为快速的汇编代码。
显式错误处理
加密失败不可忽视。
无垃圾回收
可预测的性能,没有意外的暂停。
用 Rust 构建:核心架构
以下是一个典型的 Rust 多签钱包结构示例。它展示了关键概念——完整实现位于我的 GitHub 仓库 中,包含了事务排队、增强的序列化以及更健壮的错误处理等附加功能:
// Core data structures
pub struct MultisigWallet {
pub threshold: usize, // M in M‑of‑N
pub signers: Vec<PublicKey>, // N public keys
pub nonce: u64, // Prevent replay attacks
}
pub struct Transaction {
pub to: Address,
pub amount: u64,
pub nonce: u64,
}
// The critical signing and verification functions
impl MultisigWallet {
pub fn create_signature(
&self,
tx: &Transaction,
private_key: &PrivateKey,
) -> Result<Signature, Error> {
// Hash the transaction
let msg_hash = hash_transaction(tx);
// Sign with secp256k1
sign_ecdsa(&msg_hash, private_key)
}
pub fn verify_and_execute(
&mut self,
tx: &Transaction,
signatures: Vec<Signature>,
) -> Result<(), Error> {
// Verify we have enough signatures
if signatures.len() < self.threshold {
return Err(Error::InsufficientSignatures);
}
// Verify each signature is from a valid signer
let msg_hash = hash_transaction(tx);
for sig in signatures.iter().take(self.threshold) {
let pub_key = recover_public_key(&msg_hash, sig)?;
if !self.signers.contains(&pub_key) {
return Err(Error::UnauthorizedSigner);
}
}
// Execute transaction
self.execute_transaction(tx)
}
}
我面临的挑战
1. 签名验证的边缘情况
挑战: 如果同一密钥签名两次会怎样?如果签名顺序错误会怎样?
解决方案: 从每个签名中恢复公钥并与签名者列表进行比对。使用 HashSet 来防止重复签名。
2. 交易重放攻击
挑战: 没有 nonce,攻击者可能会重放旧的有效交易。
解决方案: 包含一个随每笔交易递增的 nonce。旧的交易将失效。
3. 密钥序列化
挑战: 公钥可以是压缩的(33 bytes)或未压缩的(65 bytes)。
解决方案: 为保持一致性始终使用压缩格式。Rust 的类型系统有助于强制执行这一点。
我学到的
- 密码学毫不宽容: 一个字节错误 = 完全失败。Rust 的严格性是特性,而非缺陷。
- 测试至关重要: 使用已知向量、模糊测试和基于属性的测试。
- 理解胜过记忆: 知道 secp256k1 为什么有效,使调试变得极其容易。
- 错误处理即安全: 每个
Result若处理不当都是潜在漏洞。
关键要点
亲自尝试
完整实现已开源并有文档说明。您将看到:
- 完整的 secp256k1 签名创建与验证。
- M‑of‑N 多签钱包实现。
- 交易序列化与哈希。
- 全面的错误处理。
- 包含已知向量的测试套件。
如果您觉得这有帮助,请留下评论或反馈。有任何问题?发现 bug?想要贡献?
打开 issue 或 PR —— 让我们一起构建安全的加密系统吧! 🦀
