- 区块链
- Web3
【免费下载链接】grin
Minimal implementation of the Mimblewimble protocol.
Mimblewimble 是一种区块链格式与协议,它仅依赖椭圆曲线密码学(ECC)这一被长期检验的密码学原语,就同时实现了出色的可扩展性、隐私性与可互换性(fungibility)。Grin 则是 Mimblewimble 的开源实现,补齐了部署一条完整区块链与加密货币所需的全部环节。本文以仓库文档 doc/translations/intro_KR.md 及其英文原版 doc/intro.md 为骨架,结合 core 与 keychain 的源码实现,系统讲解 Grin 交易与区块的底层原理,帮助你理解 Pedersen 承诺、交易内核(Transaction Kernel)、kernel offset 与 cut-through 等核心概念,以及它们如何在真实代码中落地。
Mimblewimble 与 Grin 的项目定位
Mimblewimble 是一个区块链格式(format)兼协议(protocol)。它通过强密码学原语,提供现有绝大多数区块链实现所缺失的特性组合:
- 默认隐私(Privacy by default):隐私是内建默认行为,在保证完全可互换性的同时,并不妨碍按需选择性披露信息。
- 以用户规模为主的扩展性:链上数据增长主要随用户数量增长,而几乎不随交易数量增长——每笔交易的
kernel不足 100 字节,相比其他区块链大幅节省存储空间。 - 强且经过验证的密码学:Mimblewimble 只依赖椭圆曲线密码学(ECC),而 ECC 已被业界测试和使用数十年。
- 设计简洁:简单性使得协议长期来看易于审计与维护。
- 社区驱动:鼓励挖矿去中心化,项目由社区推动。
从源码结构看,这套设计在仓库中被明确分层实现:corecrate 定义了交易、区块、输出、内核等核心数据结构(见 core/src/core/transaction.rs 与 core/src/core/block.rs),keychaincrate 负责密钥派生与 Pedersen 承诺的计算(见 keychain/src/keychain.rs),chaincrate 则负责将这些数据结构组织成可验证的链式状态。下面我们按文档的叙述顺序,从密码学地基开始。
密码学地基:椭圆曲线上的标量乘法
理解 Mimblewimble 不需要深入 ECC 的全部细节,只需掌握几条性质。密码学语境下的椭圆曲线,可以简单理解为一个巨大的点集,我们称之为曲线C。这些点可以相加、相减,结果仍是曲线上的点;一个点还可以反复加到自己身上,即用整数(标量)做点乘。
给定曲线上的点H和整数k,通过标量乘法可以算出k*H,它仍是曲线C上的点。给定另一个整数j,还可以计算(k+j)*H,它等于k*H + j*H。加法与标量乘法保持分配律:
(k+j)*H = k*H + j*H在 ECC 中,如果把一个(非常大的)整数k当作私钥,那么k*H就是对应的公钥。即使知道公钥k*H的值,反推k也几乎不可能——乘法是平凡的,而"除以曲线点"极其困难。
公式(k+j)*H = k*H + j*H(k与j都是私钥)说明:由两个私钥相加得到的公钥(k+j)*H,与这两个私钥各自公钥相加的结果k*H + j*H完全一致。比特币的 HD(分层确定性)钱包正是依赖这一原理,Mimblewimble 与 Grin 的实现同样如此。
Mimblewimble 交易:两大支柱属性
交易结构体现了 Mimblewimble 的核心信条:强隐私与保密性保证。Mimblewimble 交易的验证依赖两条基本性质:
- 零和验证(Verification of zero sums):输出之和减去输入之和恒等于零,从而证明交易没有凭空创造新币,且无需暴露真实金额。
- 私钥持有(Possession of private keys):与其他加密货币一样,交易输出的所有权由 ECC 私钥保证。但证明某实体持有这些私钥,并非通过直接对交易签名来实现。
接下来的余额、所有权、找零与证明各节,将说明这两条性质如何实现。
余额:用 Pedersen 承诺隐藏金额
基于上面的 ECC 性质,可以把交易中的数值隐藏起来。若v是某笔输入或输出的金额、H是曲线C上的点,那么可以在交易中嵌入v*H而不是明文v。由于 ECC 运算的性质,仍然可以验证交易输出之和等于输入之和:
v1 + v2 = v3 => v1*H + v2*H = v3*H对每笔交易验证该性质,协议便能在不知道真实金额的情况下,确认交易没有凭空造币。但注意:若知道了v1(例如来自之前某笔交易)以及对应的v1*H,就能在整条链上识别出所有金额为v1的输出,这是隐私漏洞。
为解决此问题,引入同一条椭圆曲线上的第二个生成点G(实践中G只是与H同群中的另一个生成点),以及一个用作blinding factor(盲化因子)的私钥r。于是交易中的输入或输出可表示为:
r*G + v*H其中:
- r是作为盲化因子的私钥,G是曲线C上的点,
r*G是r以G为生成点的公钥。 - v是输入或输出的金额,H是曲线C上另一个点,
v*H是以H为生成点的公钥。
利用 ECC 的基本性质,v与r均无法被反推。r*G + v*H被称为Pedersen Commitment(Pedersen 承诺)。
在 Grin 的 keychain 实现中,承诺的生成逻辑清晰可见:keychain/src/keychain.rs 的commit方法先从主密钥按路径派生金额对应的私钥,再调用secp.commit(amount, key)得到承诺;对应的blind_sum方法则负责把多个正负盲化因子汇总为一个BlindingFactor。也就是说,r*G + v*H在代码层面正是secp.commit的一次调用。
作为示例,假设要构造一笔两输入一输出的交易(忽略手续费):
- vi1、vi2为输入金额;
- vo3为输出金额。
满足:
vi1 + vi2 = vo3为每个输入金额生成一个作为盲化因子的私钥,并把每个金额替换为对应的 Pedersen 承诺,得到:
(ri1*G + vi1*H) + (ri2*G + vi2*H) = (ro3*G + vo3*H)其推论是:
ri1 + ri2 = ro3这就是 Mimblewimble 的第一根支柱:验证交易所需的算术运算可以在不知道任何金额的情况下完成。这一思想源自 Greg Maxwell 的 Confidential Transactions(机密交易),而后者又源自 Adam Back 关于将同态金额应用于比特币的提议。
所有权:超额值与交易内核
上一节把私钥作为盲化因子隐藏了交易金额;Mimblewimble 的第二个洞见是:这个私钥本身可以被用来证明对金额的所有权。
设想 Alice 转给你 3 个币,为了隐藏金额,你选择了盲化因子 28(实际中盲化因子是私钥,是极大的数)。区块链上出现如下输出,且只有你能花费它:
X = 28*G + 3*HX(加法结果)对所有人可见;金额 3 只有你和 Alice 知道;28 只有你自己知道。
要把这 3 个币再次转出,协议要求 28 以某种方式被"知道"。假设你要把这 3 个币转给 Carol,需要构造一笔简单的交易:
Xi => Y其中Xi是花费你的输出X的输入,Y是 Carol 的输出。不知道你的私钥 28,就无法构造并配平这笔交易。实际上,若由 Carol 来配平这笔交易,她需要同时知道发送金额和你的私钥,于是:
Y - Xi = (28*G + 3*H) - (28*G + 3*H) = 0*G + 0*H通过检查一切归零,可以再次确认没有新币被创造。
但问题来了:现在你知道了 Carol 输出中的私钥(在此例中为了配平必须与你的相同),你就能把币从 Carol 那里偷回来!解决办法是让 Carol 使用她自己选择的私钥。若 Carol 选择 113,那么区块链上最终呈现的是:
Y - Xi = (113*G + 3*H) - (28*G + 3*H) = 85*G + 0*H此时交易不再归零,产生了超额值(excess value)85——它是所有盲化因子求和(以及相应相减)的结果。注意85*G是以生成点G计算的有效公钥。
因此,协议需要验证:交易各方能够共同为结果点Y - Xi生成私钥(上例中的 85,即对应的公钥85*G)。一个简单的做法是用公钥Y - Xi(即85*G)来验证一个用超额值 85 作为私钥签出的签名。这保证了:
- 交易各方能够共同提供公钥(
Y - Xi)对应的私钥(超额值); - 输出金额之和减去输入金额之和为零(否则公私钥无法对应,这正是签名的意义所在)。
附加在每笔交易上的这个签名,连同一些附加数据(如挖矿手续费),被称为transaction kernel(交易内核),并由所有验证者检查。
在源码中,交易内核被精确建模为 core/src/core/transaction.rs 的TxKernel结构体,包含三个字段:features(内核特性,如 Coinbase/Plain/HeightLocked/NRD)、excess(所有交易承诺之和的余项,若交易构造正确,金额部分应合计为零,因此 excess 是一个有效公钥)、excess_sig(证明 excess 是有效公钥、并签署交易手续费字段的签名)。内核的序列化只写这三个字段,与文档"kernel 不足 100 字节"的描述一致。
找零与范围证明
找零(Change)
假设你只想把 Alice 给的 3 个币中的 2 个转给 Carol。为此需要把剩下的 1 个币作为找零返还给自己。你需要再生成一个私钥(比如 12)作为盲化因子来保护找零输出;Carol 则继续使用她自己的私钥:
找零输出: 12*G + 1*H Carol 输出: 113*G + 2*H区块链上最终呈现的结果与之前非常相似,签名仍然用超额值构建,此例中超额值为 97:
(12*G + 1*H) + (113*G + 2*H) - (28*G + 3*H) = 97*G + 0*H范围证明(Range Proofs)
上述所有计算都依赖交易金额恒为正。负金额的出现会带来严重问题——有人可以在每笔交易中凭空造币。例如,可以构造一笔输入为 2、输出为 5 和 -3 的交易,它仍然是配平的。这在表面上难以察觉,因为即使x为负,曲线上的对应点x*H看起来与其他点无异。
为解决此问题,Mimblewimble 借助另一个同样来自 Confidential Transactions 的密码学概念——范围证明(range proof):在不泄露数字本身的前提下,证明某个数字落在给定范围内。对于任意r*G + v*H,可以构造一个证明,表明v非负且不会溢出。
还需要强调一点:不仅金额,盲化因子本身也需要范围证明。原因是这可以阻止一种审查攻击:第三方可以在不知道别人私钥的情况下锁定其 UTXO。攻击者构造如下交易:
Carol 的 UTXO: 113*G + 2*H 攻击者的输出: (113 + 99)*G + 2*H由于 Carol 的盲化因子在Y - Xi的等式推导中恰好抵消,攻击者可以为该交易签名:
Y - Xi = ((113 + 99)*G + 2*H) - (113*G + 2*H) = 99*G这个输出((113 + 99)*G + 2*H)在被花费时需要同时知道 113 和 99 两个数;攻击者因此成功锁定了 Carol 的 UTXO。对盲化因子要求范围证明可以阻止此攻击:攻击者不知道 113,也就不知道 113 + 99——换言之,不知道输出中的金额,无法为它生成有效的范围证明。
在 Grin 中,范围证明采用 bulletproof 方案,代码可直接印证:core/src/core/transaction.rs 的Output结构体由identifier(特性与承诺)和proof: RangeProof组成;core/src/core/transaction.rs 的verify_proof与batch_verify_proofs分别调用verify_bullet_proof与verify_bullet_proof_multi来验证单个或批量承诺上的范围证明。
组合在一起:一笔 Mimblewimble 交易包含什么
综合上述内容,一笔 Mimblewimble 交易包含以下部分:
- 一组输入(inputs):引用并花费一组之前的输出。
- 一组新输出(outputs),其中每个输出包含:
- 盲化因子r与金额v,分别用于曲线点 G、H 的标量乘法并求和为
r*G + v*H; - 一个范围证明,证明v非负(及其他性质)。
- 盲化因子r与金额v,分别用于曲线点 G、H 的标量乘法并求和为
- 明文交易手续费(fee)。
- 一个签名:以超额值(所有输出金额与手续费之和减去输入金额)为私钥签出。
在代码层面,core/src/core/transaction.rs 的TransactionBody恰好由inputs、outputs、kernels三部分组成;core/src/core/transaction.rs 的Transaction则在此基础上增加了一个offset: BlindingFactor(即内核偏移量 k2,下一节详述)。交易的完整校验入口是Transaction::validate(见 core/src/core/transaction.rs),它依次校验特性、权重与体积,并调用verify_kernel_sums核对承诺之和。
区块与链状态:从聚合到 cut-through
上文说明了 Mimblewimble 交易如何在维持有效区块链所需性质(不造币、通过私钥证明所有权)的同时提供强匿名性。Mimblewimble 区块格式在此基础上引入一个额外概念——cut-through(贯穿削减),由此获得:
- 极佳的可扩展性:绝大多数交易数据可以随时间被消除,且不牺牲安全性。
- 额外的匿名性:通过混合与移除交易数据进一步增强隐私。
交易聚合(Transaction Aggregation)
回顾一下,一笔交易由以下部分组成:
- 一组引用并花费之前输出的输入;
- 一组新输出;
- 一个交易内核,包含:
- kernel excess(与超额值对应的公钥);
- 由超额值签名的交易签名(用 kernel excess 验证)。
对任意有效交易(为简洁忽略手续费),以下恒等式成立:
sum(outputs) - sum(inputs) = kernel_excess一旦认识到区块本质上就是一组聚合的输入、输出与交易内核,同样的恒等式对区块也成立:对输出求和、减去输入,并将结果 Pedersen 承诺与所有 kernel excess 之和相等:
sum(outputs) - sum(inputs) = sum(kernel_excess)简化表述(再次忽略手续费)就是:Mimblewimble 区块可以完全当作 Mimblewimble 交易来处理。
在 Grin 的验证代码中,这条恒等式正是 core/src/core/committed.rs 的verify_kernel_sums:它把输入/输出/overage(手续费或奖励)承诺汇总得到utxo_sum,把各内核 excess 连同 kernel offset 汇总得到kernel_sum_plus_offset,二者不相等即返回KernelSumMismatch错误。该函数同时被交易(Transaction::validate)与区块(Block::validate,见 core/src/core/block.rs)共用,印证了"区块即交易"的设计。
Kernel 偏移量(Kernel Offsets)与 Subset 问题
上述描述的 Mimblewimble 区块与交易存在一个微妙问题:有可能(甚至在某些情况下非常容易)重构出区块中的组成交易。这对隐私显然不利,这就是"subset(子集)"问题:给定一组输入、输出与交易内核,其中某个子集可以重新组合成一条有效交易。
考虑以下两笔交易:
(in1, in2) -> (out1), (kern1) (in3) -> (out2), (kern2)它们可以被聚合成如下区块(或聚合交易):
(in1, in2, in3) -> (out1, out2), (kern1, kern2)尝试所有可能的排列组合,可以轻而易举地恢复出其中一笔交易(恰好求和为零的那一组):
(in1, in2) -> (out1), (kern1)并且剩余部分也必然能重构出另一笔有效交易:
(in3) -> (out2), (kern2)注意 kernel excessr*G就是超额值r的公钥。为缓解此问题,把 kernel excess 从r*G重新定义为(r-kernel_offset)*G,并将kernel offset(内核偏移量)随每个交易内核一起发布。kernel offset 因而是一个需要加到超额值上、以保证承诺之和为零的盲化因子:
sum(outputs) - sum(inputs) = r*G = (r-kernel_offset)*G + kernel_offset*G或者等价地:
sum(outputs) - sum(inputs) = kernel_excess + kernel_offset*G对于承诺r*G + 0*H与偏移量a:交易用(r-a)签名,并发布a,这样任何人都可以计算出r*G来验证交易有效性——给定内核 excess(r-a)*G(它是交易内核的一部分)与偏移量a,计算a*G后得到(r-a)*G + a*G = r*G。在区块构造过程中,所有 kernel offset 被求和为一个单一的聚合 kernel offset覆盖整个区块,任何单笔交易的 kernel offset 都不可恢复,subset 问题由此解决。
这一机制在代码中有三重对应:
Transaction.offset字段(core/src/core/transaction.rs)正是文档所说的"内核偏移量 k2"——注释明确写道"excess 是拆分密钥 k = k1 + k2 后的 k1G"。BlockHeader.total_kernel_offset(core/src/core/block.rs)保存"自创世块以来的 kernel offset 累积总和",可通过前一个区块头推导出本区块的 kernel offset 总和——这正是"单一聚合 offset"落地的位置。- core/src/core/committed.rs 的
sum_kernel_offsets工具函数负责把正负盲化因子汇总为一个BlindingFactor,供构造区块时使用。
Cut-through:削减匹配的输入与输出
区块让矿工把多笔交易组装成一个集合附加到链上。下面的区块表示包含 3 笔交易,只展示交易的输入与输出。输入引用它们花费的输出;包含在之前区块中的输出用小写 x 标记:
I1(x1) --- O1 |- O2 I2(x2) --- O3 I3(O2) -| I4(O3) --- O4 |- O5观察两个性质:
- 在这个区块内,某些输出被紧随其后的输入直接花费(I3花费O2,I4花费O3)。
- 每笔交易的结构其实无关紧要:既然所有交易各自求和为零,那么全部交易输入与输出的总和也必然为零。
与交易类似,区块需要检查的只是:所有权已被证明(这来自transaction kernels),以及整个区块没有凭空创造任何币(除挖矿奖励允许的部分外)。因此,匹配的输入与输出可以被消除,因为它们对总和的贡献恰好抵消。这就得到下面紧凑得多的区块:
I1(x1) | O1 I2(x2) | O4 | O5注意:所有交易结构都被消除了,输入与输出的顺序不再重要;但所有输入与输出的总和仍保证为零。
源码中的cut_through函数(core/src/core/transaction.rs)正是这一机制的工程化实现:它对输入、输出按承诺排序后双指针扫描,凡输入与输出承诺相等即配对削减(ncut计数),返回削减后剩余的输入/输出以及被削减的元素本身,并校验削减后无重复输入/输出。文档中的图示与该函数的(inputs, outputs, inputs_cut, outputs_cut)返回值一一对应。配套的聚合函数aggregate(core/src/core/transaction.rs)把多条交易聚合为带 cut-through 的多内核交易。
测试用例同样直接验证了这些行为:核心测试 core/tests/core.rs 构造两笔交易后用aggregate聚合出 cut-through 交易,并验证deaggregate能按任意子集拆分回原交易(这正是文档描述的 subset 问题的正反两面);core/tests/block.rs 则专门构造"一笔交易花费另一笔"的区块,断言 cut-through 确实发生。
区块的构成
经过 cut-through 之后,一个区块简单来说由以下部分组成:
- 区块头(block header)。
- cut-through 后剩余的输入列表。
- cut-through 后剩余的输出列表。
- 一个覆盖整个区块的单一 kernel offset(所有 kernel offset 之和)。
- 交易内核,对每笔交易包含:
- 公钥
(r-a)*G,即(修正后的)kernel excess; - 用(修正后的)超额值
(r-a)作为私钥(签名密钥)生成的签名; - 挖矿手续费。
- 公钥
区块内容满足:
sum(outputs) - sum(inputs) = sum(kernel_excess) + kernel_offset*G在 core/src/core/block.rs 中,Block结构体正是由header: BlockHeader与body: TransactionBody组成;core/src/core/committed.rs 的sum_kernel_excesses则实现"内核 excess 之和加上由 offset 构造的零值承诺"这一求和逻辑,与上述公式逐项对应。
按这种方式构造的 Mimblewimble 区块能提供极佳的隐私保证:
- 中间(被 cut-through 的)交易只以其交易内核的形式存在;
- 所有输出看起来都一样:都是无法彼此区分的大数。若想排除某个特定输出,就必须排除全部;
- 所有交易结构都被移除,无法判断哪个输入对应哪个输出。
而且——这一切依然能够通过验证。
彻底 Cut-through:链状态只需三样东西
回到上一个示例区块,输出x1与x2被I1与I2花费,它们必然更早出现在区块链中。因此在该区块被添加之后,这些输出以及I1、I2也可以从链中移除,因为它们现在只是中间交易。
由此可以得出结论:任意时刻的链状态(排除区块头)仅由以下三部分信息概括:
- 链上通过挖矿创造的总币量;
- 全部未花费输出(UTXO)的集合;
- 每笔交易的交易内核。
其中第一项可以通过区块高度(距创世块的距离)直接推导。而 UTXO 与交易内核都极其紧凑。这带来两个重要后果:
- 节点需要维护的区块链非常小(对于一个比特币规模的区块链,约数 GB 量级,且有可能优化到数百 MB 量级);
- 新节点加入网络时需要传输的信息量非常小。
此外,UTXO 集合无法被篡改:即使只添加或移除一个输入或输出,也会使交易之和不再为零,从而破坏内核 excess 与承诺之和的对应关系。Grin 将这一"小状态"落地为交易哈希集(TxHashSet)与各类 MMR 结构,其中输出、范围证明、内核分别维护在各自的 PMMR 中(参见 core/src/core/pmmr 与 chain/src/txhashset),并有 chain/tests/test_txhashset.rs 等测试覆盖其行为。
结论
本文覆盖了 Mimblewimble 区块链的基础原理:通过椭圆曲线点的加法,可以构造出完全不可读、却又能被正确验证的交易——金额以 Pedersen 承诺r*G + v*H的形式隐藏,所有权由超额值与内核签名证明,负数金额由范围证明杜绝;把这些性质推广到区块层面,则可以用 cut-through 与 kernel offset 消除大量区块链数据,实现出色的扩展性与新节点的快速同步。
如果你希望继续深入,建议按以下路径阅读仓库:先读 doc/intro.md(英文原版,含本韩文版未展开的盲化因子范围证明攻击示例),再看交易与区块的数据结构与校验逻辑 core/src/core/transaction.rs 与 core/src/core/block.rs,随后阅读承诺汇总验证 core/src/core/committed.rs,最后通过测试 core/tests/core.rs 与 core/tests/block.rs 观察聚合、cut-through 与反聚合的实际运行效果。
- 区块链
- Web3
【免费下载链接】grin
Minimal implementation of the Mimblewimble protocol.
相关推荐
Grin 与 Mimblewimble 技术入门:从椭圆曲线密码学到 Cut-through 隐私架构
Grin 与 Mimblewimble 技术入门:从椭圆曲线密码学到 Cut through 隐私架构 导读 本文以 Grin 项目官方文档《Introducc
区块链Web3Grin 与 Mimblewimble 协议概览:基于椭圆曲线密码学的隐私区块链设计原理
Grin 与 Mimblewimble 协议概览:基于椭圆曲线密码学的隐私区块链设计原理 本指南以仓库中 doc/translations/intro_JP.m
区块链Web3Grin 与 Mimblewimble 核心原理详解:椭圆曲线密码学、Pedersen Commitment 与 Cut-through 剪除机制
Grin 与 Mimblewimble 核心原理详解:椭圆曲线密码学、Pedersen Commitment 与 Cut through 剪除机制 Mimble
区块链Web3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考