Zcash 2.0.4 版本深度解读:Sprout 余额校验修复、矿工地址选择与 Testnet 旋转门共识规则
【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash
Zcash 2.0.4(release-notes-2.0.4)是一次以"正确性"为核心的维护版本,聚焦三大技术主题:修复钱包对 Sprout z-address 余额的校验缺陷(涉及 note commitment 与密文值不一致的攻击面)、修正从上游 Bitcoin Core 继承的矿工固定地址选择行为(-mineraddress选项)、以及在 Testnet 启用 ZIP 209 旋转门(turnstile)共识规则。本文基于 release-notes-2.0.4.md 展开,并结合 src/wallet/wallet.cpp、src/miner.cpp、src/main.cpp 等当前仓库源码,逐一还原每个修复的底层实现与实操影响,帮助读者理解:升级后如何正确重扫链上数据、如何配置矿工出块地址、以及旋转门共识规则如何保障屏蔽池价值守恒。
一、版本概况与升级要点
Zcash 2.0.4 属于 Sapling 激活后的维护版本,其发布背景与核心技术动作如下:
- 钱包正确性修复:修复 Sprout note 校验缺陷,升级后需执行
zcashd -rescan重扫区块链以修正 z-address 余额显示; - 矿工行为修复:合并上游 Bitcoin Core 0.12 的修复,内部矿工与
getblocktemplateRPC 不再长期复用固定透明地址,每出一个块默认选择新地址,同时新增-mineraddress选项供矿工固定出块地址; - Testnet 共识规则新增:Testnet 节点强制拒绝会导致 Sprout 或 Sapling 屏蔽价值池发生旋转门违规(turnstile violation)的区块,该规则后续将在主网启用;
- 依赖与工具链升级:OpenSSL 1.1.0h→1.1.1a、Boost v1.66.0→v1.69.0、Rust v1.28.0→v1.32.0、Proton 0.17.0→0.26.0;
- 链参数更新:基于区块 497000 更新
nMinimumChainWork,并新增对应检查点(checkpoint)。
对运行 zcashd 的节点运营者而言,本版本的升级动作集中在两点:重扫(修正历史 Sprout 余额)与确认矿工地址策略(使用固定地址则显式配置-mineraddress)。以下各节逐一展开。
二、Sprout note 校验缺陷修复:钱包余额为何会出错
2.1 缺陷本质:note commitment 与密文值不一致
Zcash 的 Sprout 屏蔽交易中,每个输出 note 由两部分关键数据组成:
- note commitment(
commitments[n]):链上公开的、对 note 内容的承诺值,是交易被网络认可的依据; - 密文(ciphertext):发送方用接收方密钥加密的消息,包含 note 明文(金额、随机数
rho、r等)。
正常情况下,发送方会保证两者一致。但攻击者可以故意构造一笔交易,使note commitment 中编码的金额与密文中实际提供(加密)的金额不一致。此时网络验证的是 commitment,而接收方钱包解密得到的是密文里的金额,两者一旦分叉,钱包就会把"密文金额"当作真实余额累加,从而显示一个不正确(通常被夸大)的 Sprout z-address 余额。
2.2 修复位置:wallet.cpp 中的一致性校验
release notes 中 Sean Bowe 的提交 "(wallet) Check that the commitment matches the note plaintext provided by the sender" 正是针对此缺陷。在 src/wallet/wallet.cpp 的CWallet::GetSproutNoteNullifier中可以看到该校验的落地实现:
auto note_pt = libzcash::SproutNotePlaintext::decrypt( dec, jsdesc.ciphertexts[n], jsdesc.ephemeralKey, hSig, (unsigned char) n); auto note = note_pt.note(address); // Check note plaintext against note commitment if (note.cm() != jsdesc.commitments[n]) { throw libzcash::note_decryption_failed(); }其逻辑是:解密得到 Sprout note 明文后,用明文重建出 note(note_pt.note(address)),重新计算其 commitment(note.cm()),与链上 JoinSplit 描述(jsdesc)中的commitments[n]比对;不一致即抛出note_decryption_failed,该 note 不会被计入钱包余额。从当前源码的 src/zcash/Note.hpp 可以看到,SproutNote的cm()由a_pk、金额、rho、r派生,因此只要密文中的任意字段被篡改,重算的 commitment 必然与链上承诺值不符,从而被拦截。
2.3 Sapling 不受影响的原因
release notes 明确说明Sapling z-address 不受此缺陷影响。从当前仓库的 src/wallet/wallet.cppDecryptSaplingNote实现可以推断:Sapling note 的明文解密流程(通过 Rust 侧的try_sapling_note_decryption)与 Sprout 的SproutNotePlaintext::decrypt走的是不同代码路径,且 Sapling 协议层面对 note 明文与 commitment 的绑定约束更严格,因此该缺陷仅在 Sprout 协议路径上出现。
2.4 升级后必须执行的操作:zcashd -rescan
由于缺陷影响的是历史已入账 note 的余额统计,仅升级二进制文件并不会自动修正既有的错误余额。release notes 给出的官方操作是:
zcashd -rescan-rescan会指示 zcashd 钱包重新扫描整条链上的交易,对每个属于本钱包的 Sprout note 重新执行"解密 → 重算 commitment → 比对链上承诺"的校验,剔除被篡改 note 后再重新统计余额。执行完成后,Sprout z-address 显示的余额才会正确。作为配套措施,release notes 同时致谢 Alexis Enston 报告了该问题,相关修复见 Security Announcement 2019-03-19 与 Pull request(原文外部链接,读者可自行查阅)。
三、矿工地址选择行为修复与 -mineraddress 配置
3.1 上游继承缺陷:固定透明地址问题
Zcash 从上游 Bitcoin Core 继承了一个缺陷:内部矿工与getblocktemplateRPC 会一直使用同一个固定透明地址作为出块收款地址,直到用户调用getnewaddressRPC 为止。也就是说,在节点启动到用户第一次调用getnewaddress之间挖出的所有块,奖励都落入同一个(可能并非用户本意的)地址;而正确行为应当是每个新块都从钱包密钥池中选取一个新的透明地址。
该问题在 Bitcoin 0.12 已修复,Zcash 2.0.4 合并了这一改动。release notes 中 Jonas Schnelli 的相关提交揭示了修复的技术层次:
detach wallet from miner:将矿工与钱包解耦,矿工不再隐式依赖钱包内部状态;fix GetScriptForMining() CReserveKey::keepKey() issue:修复挖矿脚本生成时保留密钥的缺陷;add CReserveScript to allow modular script keeping/returning:引入CReserveScript抽象,使"预留/归还"地址脚本可模块化。
3.2 当前源码中的实现印证
在 src/miner.cpp 中可以看到该机制在 Zcash 中的落地形态:
class MinerAddressScript : public CReserveScript { // CReserveScript requires implementing this function, so that if an // internal (not-visible) wallet address is used, the wallet can mark it as // important when a block is mined (so it then appears to the user). // If -mineraddress is set, the user already knows about and is managing the // address, so we don't need to do anything here. void KeepScript() {} };MinerAddressScript继承自CReserveScript,其KeepScript()为空实现——若用户显式配置了-mineraddress,说明地址由用户自行管理,无需钱包再标记;反之,矿工每轮从钱包密钥池预留新地址,出块后通过该回调使地址在钱包中可见。随后 src/miner.cpp 的GetMinerAddress读取-mineraddress参数并解析为MinerAddress,作为出块收款地址。
3.3 -mineraddress 参数说明与使用
对于希望每个块都使用同一地址收款的矿工,release notes 给出的方案是显式指定-mineraddress。从当前仓库 src/init.cpp 的帮助文本可见其定义:
-mineraddress=<addr> Send mined coins to a specific single address该参数支持透明 P2PKH 地址(t1.../t2...)、Sapling 地址(zs1...)以及包含合法接收者的统一地址(UA);从 src/init.cpp 的解析逻辑看,无法解析为合法 Zcash 地址时会直接报错退出。实测命令行用法示例:
# 使用固定透明地址出块 zcashd -mineraddress=t1T8yaLVhNqxA5KJcmiqqFN88e8DNp2PBfF # 使用 Sapling 地址出块(需配合屏蔽挖矿相关设置) zcashd -mineraddress=zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9slyasrc/gtest/test_miner.cpp 中专门为-mineraddress编写了参数解析测试,覆盖非法地址、前后空白、透明/Sapling 地址等场景,验证了解析与校验路径的健壮性。
四、Testnet 新共识规则:ZIP 209 旋转门(Turnstile)约束
4.1 什么是旋转门违规(turnstile violation)
Zcash 的屏蔽池(Sprout、Sapling 等)设计了一条"价值守恒"铁律:某一时刻屏蔽池中的总价值 = 历史进入池中的资金 − 历史流出池中的资金。这个约束被称为"旋转门"(turnstile)——如同单向闸机,资金不能凭空多出来。若某个区块导致某屏蔽池的累计余额超出合法范围(例如金额溢出、为负或超过货币供应总量),则该区块构成旋转门违规,在无约束的网络中可能成为铸造假币或价值失真攻击的起点。
4.2 ZIP 209 的启用策略
release notes 说明:Zcash 2.0.4 在Testnet上强制实施该共识规则,标记导致 Sprout 或 Sapling 池旋转门违规的区块为无效;主网将在未来版本启用(当前仓库的ZIP209Enabled()开关即为该部署策略的体现)。从 src/chainparams.cpp 可看到 Testnet 的配置:
nSproutValuePoolCheckpointHeight = 440329; nSproutValuePoolCheckpointBalance = 40000029096803; fZIP209Enabled = true; hashSproutValuePoolCheckpointBlock = uint256S("000a95d08ba5dcbabe881fc6471d11807bcca7df5f1795c99f3ec4580db4279b");而 src/chainparams.cpp 则展示了主网(Mainnet)侧的准备数据:fZIP209Enabled = true与硬编码的 Sprout 池检查点(高度 520633、余额 22145062442933),表明主网同样具备完整的旋转门审计基础设施,只是按升级计划分阶段开放。值得注意的细节:release notes 中 Sean Bowe 的提交说明regtest 模式当时未启用 ZIP209,而 src/chainparams.cpp 提供了SetRegTestZIP209Enabled()供开发者调试旋转门违规时显式开启。
4.3 区块验证端的落地实现
在区块连接验证路径 src/main.cpp 中,旋转门检查以chainparams.ZIP209Enabled()为总开关:
if (chainparams.ZIP209Enabled()) { // Sprout if (!MoneyRange(pindex->nChainSproutValue.value())) { return state.DoS(100, error("%s: turnstile violation in Sprout shielded value pool at height %d (sprout=%d, sapling=%d, orchard=%d, lockbox=%d)", __func__, ...), REJECT_INVALID, "turnstile-violation-sprout-shielded-pool"); } // Sapling(nChainSaplingValue)与 Orchard(nChainOrchardValue)同理 }即:区块所代表的各屏蔽池累计余额必须落在MoneyRange的合法区间内,否则该区块被判REJECT_INVALID,并携带明确的拒绝原因字符串(turnstile-violation-sprout-shielded-pool/-sapling-shielded-pool等)。同时 src/main.cpp 处理了一个重要的迁移场景:旧版本客户端(早于 1.0.14)写入的区块索引可能缺少 Sprout 池价值字段(nChainSproutValue == std::nullopt),此时节点会中止并提示用户以-reindex重启,从磁盘重新计算每块 Sprout 价值增量后再继续验证。
4.4 配套机制:硬编码检查点回退与矿工侧规避
- 回退检查点:为防止重组(reorg)导致历史池余额回溯,src/main.cpp 的
FallbackSproutValuePoolBalance在检查点高度将池余额硬编码回退到链参数中记录的权威值(并校验块哈希),确保旋转门审计从可信起点开始; - 矿工侧规避:release notes 中 "(testnet/regtest) Avoid mining transactions that would violate the turnstile" 表明矿工在组装区块时也会检查候选交易是否会导致池价值越界,从源头避免挖出将被共识规则拒绝的区块。
五、其他值得关注的变化
- 依赖链升级:OpenSSL 1.1.0h→1.1.1a、Boost v1.66.0→v1.69.0、Rust v1.28.0→v1.32.0、Proton 0.17.0→0.26.0(含针对最小化构建的补丁及其回归修复),见 Cargo.toml 与 rust-toolchain.toml 可继续追踪当前依赖版本;
- RPC 增强:Jonathan "Duke" Leto 将
size_on_disk回移植到getblockchaininfoRPC,便于运营者直接查询区块链目录占用,并配套了对应测试; - 链参数与检查点:基于区块 497000 更新
nMinimumChainWork并新增检查点(release notes 中 Simon Liu 的提交),强化对最重链(nChainWork)下限的约束; - 文档与安全:补充了 DNS rebinding 攻击相关文档警告(issue #3841)以及针对 issue #3869 的负责任披露声明;SECURITY.md 可查阅当前项目的安全报告流程。
六、升级与验证建议(实操清单)
- 升级前备份钱包:升级属于二进制替换,钱包数据目录需保持完好(切勿在未备份时执行任何重扫操作);
- 执行重扫:以
zcashd -rescan启动,等待区块重扫完成,确认 Sprout z-address 余额已修正;若节点曾由早于 1.0.14 的版本升级而来且旋转门验证报错,则需改用-reindex; - 核对矿工地址:若运行挖矿节点且希望固定出块地址,在启动参数中显式加入
-mineraddress=<addr>(透明 P2PKH / Sapling / 含合法接收者的 UA),否则默认行为将是每个新块使用新的透明地址; - Testnet 节点须知:确认已运行 2.0.4+ 以同步最新 Testnet 共识规则;主网节点暂不受旋转门共识强制执行影响,但后续版本将启用,应保持升级节奏;
- 依赖环境:本版本工具链升级(Rust 1.32、OpenSSL 1.1.1a 等)主要影响源码构建者,预编译二进制用户无需额外操作;从源码构建请参考 INSTALL 与 doc/developer-notes.md。
参考文件索引(均在当前仓库内,可继续深挖):
- 版本说明:doc/release-notes/release-notes-2.0.4.md
- Sprout 余额校验修复:src/wallet/wallet.cpp、src/zcash/Note.hpp
- 矿工地址选择与
-mineraddress:src/miner.cpp、src/init.cpp、src/gtest/test_miner.cpp - ZIP 209 旋转门共识:src/main.cpp、src/chainparams.cpp
- RPC 变更:
getblockchaininfo的size_on_disk字段(见 src/rpc/blockchain.cpp)
【免费下载链接】zcashZcash - Internet Money项目地址: https://gitcode.com/GitHub_Trending/zc/zcash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考