Terms and Conditions
- I agree to the Grant Agreement terms if funded
- I agree to Provide KYC information if funded above $50,000 USD
- I agree to disclose conflicts of interest
- I understand that this grant program is only eligible for completed work, as it is a retroactive grant program. Applications for planned or partially completed work will not be considered. All completed work will be verified and accepted by its intended users or their representatives, who will confirm that the outputs meet the required quality, functionality, and usability before the work is listed as an option for Coinholder voting.
- I agree that for any new open-source software, I will create a CONTRIBUTING.md file that reflects the high standards of Zcash development, using the
librustzcashstyle guides as a primary reference. - I understand when contributing to existing Zcash code, I am required to adhere to the project specific contribution guidelines, paying close attention to any merge, branch, pull request, and commit guidelines as exemplified in the librustzcash repository.
- I understand all grants are valued in USD but will be disbursed in Shielded ZEC. I acknowledge and accept that disbursement amounts may fluctuate based on the ZEC/USD exchange rate at the time of payment.
Application Owners (@octocat, @octocat1)
@ouicate
Organization or Individual Name
ouicate (Quorint)
Additional Team Members
None. I am applying as an individual for my own security research contribution.
How did you learn about the Lockbox: Coinholder Retroactive Grants Program?
The Zcash Community Forum: the Q4 2026 call for proposals, and the Q3 2026 retroactive grants for Zebra security findings.
Requested Grant Amount (USD)
$300,000
Category
Research & Development
Project Summary
I found and privately reported two Critical consensus vulnerabilities in the code headed for Zebra's next release: one would have let anyone split the Zcash network with a single transaction, and the other would have let any miner do it with a single block. The Zcash Foundation made them a release blocker, fixed both within six days (PR #11387), and shipped Zebra 6.4.0 without them, crediting me for "identifying and mitigating what would have been a nasty consensus bug in Zebra before we shipped it."
Project Description
At a glance
- What: two consensus vulnerabilities in the code headed for Zebra 6.4.0. In a release, either one would have split the Zcash network. C-01 would have needed only one transaction from anyone, and C-02 only one block from any miner.
- Outcome: I reported both privately on 2 September 2026. ZF made them a release blocker, merged the fix six days later, and shipped 6.4.0 on 23 September with both rules enforced. No node was ever exposed.
- In ZF's words: "Both findings identified and responsibly disclosed by @ouicate. We appreciate your contribution in identifying and mitigating what would have been a nasty consensus bug in Zebra before we shipped it." (Issue #11383)
- Request: $150,000 per finding, which is the Core Node Critical base. That is the same per-finding amount coinholders approved for sole-reporter Zebra consensus findings in Q3, with 96.33% in favour. I am not requesting a bonus, and nothing has been paid for these findings.
What I found
PR #10461 replaced Zebra's own transaction parser with the shared zcash_primitives library and was merged into main on 22 August 2026. Two rules that every Zcash node must enforce were lost in the move:
| Rule | What Zebra's main branch did | Who could trigger it | |
|---|---|---|---|
| C-01 | A non-coinbase transaction's nExpiryHeight must be at most 499,999,999 (ZIP 203; protocol spec §7.1.2) | A new conversion turned every value from 2^31 to 2^32−1 into "no expiry", so about 2.1 billion invalid values were accepted | Anyone: one transaction, with no mining and no authentication |
| C-02 | A coinbase scriptSig must be 2 to 100 bytes (protocol spec §7.1.2) | The check survived only in a function that production code no longer called. Its unit tests kept passing | Any miner: one block |
These are two separate findings. They break different rules, different people could trigger them, and each would have split the network on its own. PR #11387 fixes them in separate commits.
ZF's tracking issue, #11383, says: "Both were reported by @ouicate." The fix, PR #11387, says: "Fixes #11383. Reported by @ouicate."
The full technical write-up, with code paths and deterministic tests, is on the Quorint research blog: Two Critical Consensus-Divergence Vulnerabilities in Zcash's Zebra Node.
What would have happened
Since zcashd reached end-of-life in July 2026, Zcash has run on two full-node implementations: Zebra, and Zakura, an independent Zebra-derived node. Zakura v1.3.1 and Zebra 6.0.0 to 6.3.0 enforce both rules (#11383). Had Zebra 6.4.0 shipped with C-01, this is what would have happened:
- Anyone broadcasts one transaction with an expiry height of 2^31 or more. Upgraded Zebra nodes accept it, relay it, and keep it as "never expires".
- The next Zebra-based pool that calls
getblocktemplatemines it. - Upgraded Zebra nodes accept that block and build on it. Zakura and older Zebra nodes reject it and follow the other branch.
- Exchanges, pools, Zaino indexers and Zallet wallets now see two different chains. A deposit confirmed on one chain can be double-spent on the other.
- Zebra does not roll back more than 1,000 blocks (
MAX_BLOCK_REORG_HEIGHT, about 21 hours). After that, ending the split requires either resyncing every affected node or writing an exception for the bad block into the consensus rules.
C-02 leads to the same split. All it takes is a miner padding one coinbase scriptSig past 100 bytes.
Why these are Critical
ZF publishes severity ratings in its security advisories, and advisories are written for affected releases. No release was affected, so ZF tracked the fix in a public issue and published no rating. Three things show where these findings belong:
1. ZCG's severity framework, which this request uses as its reference. It defines Critical as a defect that, "at scale, directly causes" one of Zcash's core harms, and it lists "unintended chain splits" as one of them. Either finding would have split every upgraded Zebra node from the rest of the network at the same moment.
2. ZF's own ratings for the same attack paths. When bugs of these shapes shipped, ZF rated them Critical:
| My finding | Closest shipped Zebra advisory, in ZF's words | ZF's rating |
|---|---|---|
C-01: anyone's transaction travels through Zebra's mempool and getblocktemplate into a block that the rest of the network rejects | GHSA-cwfq-rfcr-8hmp: a transaction "accepted by Zebra, retained in Zebra's mempool, selected into Zebra getblocktemplate, mined into a block, and then rejected by zcashd" | Critical |
| C-01: no mining needed | GHSA-2prc-cj5x-4443: "An attacker needs no mining capability." | Critical |
| C-02: a block that a miner builds and only Zebra accepts | GHSA-jv4h-j224-23cc: "A miner who produces such a block can split the network." | Critical |
3. ZF's response. ZF treated the findings as a release blocker and labelled the fix consensus and security. It described the outcome as preventing "a nasty consensus bug in Zebra before we shipped it."
These splits would not have healed on their own. C-01 could have been triggered by anyone, at any time after the release. Once a split passes Zebra's 1,000-block rollback window, ending it takes either a coordinated resync of every affected node or a hard fork. ZCG's framework lists an "unintended permanent chain split requiring a hard fork to resolve" as Critical.
What the Zcash Foundation did
- 2 September: I reported both findings privately through Zebra's GitHub security advisory process. Each report included deterministic tests, the end-to-end split scenario and a suggested fix.
- 3 September: ZF closed the private reports and opened #11383 the same day: "No released version is affected. [...] This is a release blocker for the next release cut from
main." - 3 to 8 September: PR #11387 restored both rules, with tests through the production path that fail without the fix. It also merged Zebra's two deserialization paths into one checked path, and pinned tests for the rules the refactor had moved to the verifier. Finally, it added a permanent chapter to the Zebra Book, Refactoring Consensus-Critical Code, which the PR describes as "distilled from this incident". The chapter describes both bugs, and says its checklist "exists so the next refactor does not repeat that."
- 4 to 8 September: ZF also restored a third check the refactor had changed, the null-prevout check (PR #11400).
- 22 September: ZF's engineering update reported "Security checks restored before release" and thanked "the Zakura security team and @ouicate for reporting these issues." My two findings are the checks it describes as covering "mining-reward inputs and transaction expiry".
- 23 September: Zebra 6.4.0, the first release after the refactor, shipped with both rules enforced.
Why I reported before release
I could have waited. I said so in #11383 on 3 September, before any grant was on the table:
I could have waited for it to ship and then reported both as live, in-the-wild Criticals, with CVEs and published advisories — the same paper trail the earlier consensus-divergence findings carry. I chose the opposite: report them now and get two would-be Critical consensus splits eliminated before they touched a single production node. Catching a critical before it ships is a better outcome for the network than documenting one after it does — and that's the behavior I'd want the ecosystem to encourage.
On 22 September, in ZF's update thread, I urged other researchers to do the same:
To other researchers: report a consensus bug when you confirm it on the development branch. Waiting for a tagged release, a CVE, or a published advisory would have left these in the first release that carried them, and put Zcash at risk for no benefit. Finding them early is what kept them off the network. Credit in the issue and in this update is the right outcome.
That credit is the right public record for a bug that never shipped. Whether reporting early should also be paid like reporting late is the question this application puts to coinholders.
Why the full reference amount
Coinholders have already funded Critical security work at and above this level. All three of these Q3 applications were approved and paid:
| Q3 application | Requested | Approval |
|---|---|---|
| #51: Orchard counterfeiting vulnerability (Taylor Hornby) | $750,000 | 99.99% |
| #57: exploit chain across zcashd and Zebra (Alex Sol) | $400,000 | 98.66% |
| #58: five Critical Zebra consensus divergences (sangsoo) | $425,000 | 96.33% |
The approval figures are ZEC-weighted shares of the non-abstaining votes in the finalized Q3 tally hosted by the Zcash Foundation.
#58 is the closest match. It priced each sole-reporter Zebra consensus divergence at the $150,000 Core Node Critical base from ZCG's 2026 schedule, priced each shared one at half, and asked for no bonus. I use exactly that method. I have not discounted for "never shipped", for three reasons:
- The difference in exposure was timing, not severity. One of the #58 findings, GHSA-2prc-cj5x-4443, was live for about 20 hours. Zebra 4.5.0 shipped it at 01:25 UTC on 29 May, and 4.5.1 fixed it at 21:34 UTC the same day. ZF rated it Critical, and coinholders approved $150,000 for it. I reported mine 21 days before the release that would have carried them.
- They would have shipped. 6.4.0 was the next release after the refactor, and ZF made the fix a blocker for it. The same refactor did ship a vulnerability that was not caught before release: GHSA-7jp4-mq53-4cvq, rated High, which affected Zebra 6.4.0 to 6.4.2 until 7.0.0 fixed it. ZF's own checklist explains why bugs like these slip through review: "a rule can stop being enforced without any test failing and without any deleted line showing a check being removed. [...] Absence is invisible in a diff, so it has to be hunted deliberately."
- A discount pays researchers to wait. If a consensus bug found before release is worth less than the same bug found after it, the rational move is to sit on it until it ships. Paying the same keeps early reporting the right choice for the researcher as well as for the network.
| Finding | Credit | Reference | Requested |
|---|---|---|---|
C-01: nExpiryHeight above the ZIP 203 maximum accepted | Sole reporter | $150,000 Core Node Critical base | $150,000 |
C-02: coinbase scriptSig length rule not enforced | Sole reporter | $150,000 Core Node Critical base | $150,000 |
| Total | $300,000 |
No bonus is requested. I use ZCG's schedule as a reference point, not as an entitlement.
Researcher background
I am an independent security researcher and publish my work on the Quorint research blog.
Technical Approach (how you did it)
PR #10461 was merged on 22 August. Zakura v1.3.1 never adopted the refactor, which made it a reference to diff against: any transaction or block that Zakura rejects and Zebra main accepts is a consensus divergence.
- Old rejections, new homes. For each consensus rejection the pre-refactor deserializers performed, I looked for where
mainstill enforces it. That could be the upstreamzcash_primitivesandzcash_transparentcrates at the versions pinned inCargo.lock, Zebra's re-added checks, or a later verification step. C-02 had no live home: the check survived only in a helper whose remaining callers were its own tests. - Lossy conversions. I traced the conversions between upstream types and the values Zebra's consensus checks read, looking for errors collapsed into
Noneor a default. That is how C-01 showed up..ok()turned an out-of-range expiry into "no expiry", which every downstream check treats as valid. - Production paths. For each gap I followed the path a real node uses: P2P and RPC into the mempool, then
getblocktemplate, then block verification. I did not rely on the helper functions the unit tests call. - Proof. I wrote deterministic tests showing
mainaccepting bytes that Zakura v1.3.1 and Zebra v6.3.0 reject. They are published in the write-up. - Disclosure. I reported bothr privately to the Zcash Foundation on 2 September, each with the analysis, the tests and a suggested fix.
Tools: I used LLM-assisted code review to map the refactor and generate leads, and confirmed both findings by hand with the tests above before reporting.
Time Period of Work Completion
22 August to 23 September 2026. The review began after PR #10461 merged (22 August). I reported privately on 2 September. The fix was merged on 8 September (PR #11387), and Zebra 6.4.0 shipped with both rules enforced on 23 September.
Total Budget (USD)
0
Budget Breakdown
This is a retroactive request for completed work, not a reimbursement of costs.
Previous Funding
No
Previous Funding Details
I have not received funding from the Coinholder Grants Program.
Other Funding Sources
No
Other Funding Sources Details
I have not received, and do not expect, payment from any other source for these two findings.
Success Metrics
Quantitative
- 2 Critical consensus vulnerabilities removed before any release carried them, restoring the ZIP 203 expiry maximum and the coinbase
scriptSiglength rule (§7.1.2). - 0 releases affected and 0 production nodes exposed. Zebra 6.4.0 shipped with both rules enforced, 21 days after my report.
- 1 day from my report to ZF's tracking issue, and 6 days to the merged fix.
- About 2.1 billion invalid
nExpiryHeightvalues (2^31 to 2^32−1) thatmainwould have accepted from anyone. - Tests through the production path for both rules, plus pinned tests for the rules the refactor moved to the verifier (PR #11387).
- 1 new permanent Zebra Book chapter, with author and reviewer checklists for consensus-critical refactors.
Qualitative
- Consensus held at the point where it was most fragile. With zcashd retired, Zcash depends on Zebra and Zakura agreeing exactly.
- The bugs were fixed at the cheapest possible point: no emergency release, operator upgrades, exchange halts or rollback were needed.
- The process change outlasts the fix. The next refactor of Zebra's consensus parsing has a checklist written from this case.
- The public write-up lets other node implementers check their own refactors for the same two patterns.
Proof of completion
Repository/Commit
- PR #11387, the fix (merged 8 September 2026): https://github.com/ZcashFoundation/zebra/pull/11387
- Issue #11383, analysis and credit: https://github.com/ZcashFoundation/zebra/issues/11383
- PR #10461, the refactor that introduced both vulnerabilities: https://github.com/ZcashFoundation/zebra/pull/10461
Publication
- Technical write-up (Quorint research blog): https://quorint.xyz/research/zebra-pre-release-consensus-divergences/
- Zebra Book, Refactoring Consensus-Critical Code: https://github.com/ZcashFoundation/zebra/blob/main/book/src/dev/consensus-refactors.md
- ZF Engineering Update, 7–20 September 2026 ("Security checks restored before release"): https://forum.zcashcommunity.com/t/zf-engineering-update-7-20-september-2026/57756
Deployment/Release
- Zebra 6.4.0, the first release after the refactor, with both rules enforced: https://github.com/ZcashFoundation/zebra/releases/tag/v6.4.0
- Zebra 6.3.0, the last release before the refactor: https://github.com/ZcashFoundation/zebra/releases/tag/v6.3.0
Other Evidence
- My original private reports, reported it privately to the Zcash Foundation on 2 September 2026
- My comment in #11383 (3 September): https://github.com/ZcashFoundation/zebra/issues/11383#issuecomment-5527705406
- My reply in ZF's update thread (22 September): https://forum.zcashcommunity.com/t/zf-engineering-update-7-20-september-2026/57756/2
- Severity comparisons, all rated Critical by ZF: GHSA-cwfq-rfcr-8hmp, GHSA-2prc-cj5x-4443 and GHSA-jv4h-j224-23cc (links in the Project Description)
- The same refactor's shipped vulnerability, GHSA-7jp4-mq53-4cvq (High): https://github.com/ZcashFoundation/zebra/security/advisories/GHSA-7jp4-mq53-4cvq
- Q3 precedent, sangsoo's application: https://github.com/Financial-Privacy-Foundation/ZcashCoinholderGrantsProgram/issues/58
- ZCG's severity framework and payout schedule: https://forum.zcashcommunity.com/t/zcg-security-vulnerability-disclosure-initiative/55545
Conflict of Interest Disclosure
None. I have no employment, contracting or governance relationship with the Zcash Foundation, ZCG, FPF, or any keyholder organization. This round I am also applying separately for two other Zebra advisories, GHSA-jq45-ghw5-v37x and GHSA-3c94-hf7p-g5mf, in Zebra High and Medium Severity Sync Vulnerabilities (#78).
Community Forum Posting
- I understand it is my responsibility to post a link to this issue on the Zcash Community Forums after this application has been submitted so the community can give input. I understand this is required in order for the community to discuss and vote on this grant application. Note: If you are unable to post on the forum (for example, due to new user restrictions), please leave a comment below, and we will adjust your posting permissions.