Skip to content

fix(chain): reject coinbase Sapling spends during deserialization - #10527

Merged
conradoplg merged 3 commits into
mainfrom
fix/ghsa-rgwx-8r98-p34c
May 1, 2026
Merged

fix(chain): reject coinbase Sapling spends during deserialization#10527
conradoplg merged 3 commits into
mainfrom
fix/ghsa-rgwx-8r98-p34c

Conversation

@upbqdn

@upbqdn upbqdn commented May 1, 2026

Copy link
Copy Markdown
Member

Motivation

Closes GHSA-rgwx-8r98-p34c (CWE-770: Allocation of Resources Without Limits or Throttling).

The V4, V5, and V6 transaction deserializers read and allocate Sapling spend vectors
before any coinbase check runs. An attacker can craft a coinbase transaction with a
large (but within TrustedPreallocate bounds) Sapling spend count, forcing the parser
to allocate ~5,000+ spend structs before the consensus layer ever rejects the
transaction.

The advisory's claim that v4.3.1 is not vulnerable is incorrect — the parser code is
byte-for-byte identical between 4.3.0 and 4.3.1. Both versions allocate spend vectors
before any coinbase rule check.

Solution

Read the Sapling spend count as a CompactSizeMessage and reject coinbase transactions
with spend_count > 0 before allocating the spend vector. The check is added to all
three deserialization paths (V4, V5, V6).

For V5/V6 the existing ZcashDeserialize impl for Option<ShieldedData<SharedAnchor>>
is extracted into a standalone deserialize_v5_sapling_shielded_data(reader, is_coinbase)
helper so the coinbase flag can be threaded through without changing the trait signature.
The trait impl delegates with is_coinbase: false, preserving the existing call sites.

For V4 the check is inlined at the spend-count read site.

Tests

The commits are structured for reviewer auditability:

  1. 77075cfd — reproduction test: constructs a coinbase V5 transaction with Sapling
    spends and confirms the parser accepts it (proving the vulnerability exists).
  2. 5442ea47 — fix only: modifies only serialize.rs. All 242 existing tests
    (excluding proptests that generate the now-rejected combination) pass without any
    test changes, proving the fix does not alter non-coinbase deserialization behavior.
  3. 2ef0e51a — test updates: updates transaction_roundtrip, block_roundtrip,
    and the reproduction vector test to assert rejection. The arbitrary generators are
    intentionally left unchanged — proptests still generate coinbase+spends and verify
    that specific combination is rejected.
cargo test -p zebra-chain    # 244 passed, 0 failed

Specifications & References

  • Advisory: GHSA-rgwx-8r98-p34c
  • CWE-770: Allocation of Resources Without Limits or Throttling
  • Zcash protocol spec §7.1: coinbase transactions must not have Sapling spends

Follow-up Work

  • Update the advisory to note that v4.3.1 is also vulnerable.
  • Consider whether the TrustedPreallocate bounds for Sapling spends should be
    tightened independently of this fix.

AI Disclosure

  • No AI tools were used in this PR
  • AI tools were used: Claude Code for implementation, test construction, and PR description

PR Checklist

  • The PR title follows conventional commits format
  • The PR follows the contribution guidelines
  • This change was discussed in an issue or with the team beforehand
  • The solution is tested
  • The documentation and changelogs are up to date

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

mpguerra
mpguerra previously approved these changes May 1, 2026
@upbqdn upbqdn changed the title fix(chain): reject coinbase Sapling spends during deserialization (GHSA-rgwx-8r98-p34c) fix(chain): reject coinbase Sapling spends during deserialization May 1, 2026
@upbqdn
upbqdn force-pushed the coinbase-data-alloc branch from 043612b to cff74ac Compare May 1, 2026 13:15
@upbqdn
upbqdn force-pushed the fix/ghsa-rgwx-8r98-p34c branch from 3c5a047 to 5f5b8d0 Compare May 1, 2026 13:15
@upbqdn
upbqdn force-pushed the coinbase-data-alloc branch from cff74ac to b21321d Compare May 1, 2026 15:25
@upbqdn
upbqdn force-pushed the fix/ghsa-rgwx-8r98-p34c branch from 5f5b8d0 to 0ad6ff6 Compare May 1, 2026 15:25
@conradoplg
conradoplg force-pushed the coinbase-data-alloc branch from b21321d to 6246fd5 Compare May 1, 2026 17:00
Base automatically changed from coinbase-data-alloc to main May 1, 2026 17:01
@conradoplg
conradoplg dismissed mpguerra’s stale review May 1, 2026 17:01

The base branch was changed.

…ejection

Update transaction_roundtrip, block_roundtrip, and the reproduction
vector test to assert that coinbase transactions with Sapling spends
are rejected during deserialization.  The arbitrary generators are
intentionally left unchanged so they continue producing the invalid
combination — the proptests now verify rejection for that case and
round-trip for all others.
@conradoplg
conradoplg force-pushed the fix/ghsa-rgwx-8r98-p34c branch from 0ad6ff6 to 397ab72 Compare May 1, 2026 17:04
@conradoplg
conradoplg merged commit f4267ef into main May 1, 2026
63 of 73 checks passed
@conradoplg
conradoplg deleted the fix/ghsa-rgwx-8r98-p34c branch May 1, 2026 17:05
upbqdn added a commit that referenced this pull request May 1, 2026
…ction proptests

The `any::<Transaction>()` arbitrary strategy can produce coinbase
transactions that contain Sapling spends. Since #10527 (GHSA-rgwx-8r98-p34c),
those transactions are rejected during deserialization, so
`send_raw_transaction` returns a deserialization error before reaching the
mempool. The four affected proptests then time out waiting for a mempool
request that never arrives.

Skip those cases with `prop_assume!` so each test exercises the mempool
path it is intended to cover. The `transaction_roundtrip` proptest in
`zebra-chain` was updated to handle the same combination in #10527.
upbqdn added a commit that referenced this pull request May 1, 2026
…ction proptests

The `any::<Transaction>()` arbitrary strategy can produce coinbase
transactions that contain Sapling spends. Since #10527 (GHSA-rgwx-8r98-p34c),
those transactions are rejected during deserialization, so
`send_raw_transaction` returns a deserialization error before reaching the
mempool. The four affected proptests then time out waiting for a mempool
request that never arrives.

Skip those cases with `prop_assume!` so each test exercises the mempool
path it is intended to cover. The `transaction_roundtrip` proptest in
`zebra-chain` was updated to handle the same combination in #10527.
upbqdn added a commit that referenced this pull request May 1, 2026
…ction proptests

The `any::<Transaction>()` arbitrary strategy can produce coinbase
transactions that contain Sapling spends. Since #10527 (GHSA-rgwx-8r98-p34c),
those transactions are rejected during deserialization, so
`send_raw_transaction` returns a deserialization error before reaching the
mempool. The five affected proptests then time out waiting for a mempool
request that never arrives.

Skip those cases with `prop_assume!` so each test exercises the mempool
path it is intended to cover. The `transaction_roundtrip` proptest in
`zebra-chain` was updated to handle the same combination in #10527.
upbqdn added a commit that referenced this pull request May 1, 2026
After #10527 (GHSA-rgwx-8r98-p34c) rejects coinbase transactions with
Sapling spends during deserialization, three `zebra-state` proptests
panic when arbitrary chains contain such transactions:

- `roundtrip_transaction` reads back a generated `Transaction` via
  `IntoDisk`/`FromDisk` and panics in `FromDisk::from_bytes`.
- `blocks_with_v5_transactions` and
  `all_upgrades_and_wrong_commitments_with_fake_activation_heights`
  commit chains generated by `PreparedChain` to the finalized state,
  whose underlying `partial_chain_strategy` produces coinbase
  transactions with arbitrary Sapling shielded data.

Fix the chain-builder path by clearing `sapling_shielded_data` on
coinbase transactions in `fix_generated_transaction`. Add a
`prop_assume!` to `roundtrip_transaction` since it bypasses the chain
builder and uses `any::<Transaction>()` directly.

The `Transaction::Arbitrary` strategy is intentionally left unchanged so
the rejection path is still exercised by the `transaction_roundtrip`
proptest and the GHSA-rgwx-8r98-p34c reproduction vector in
`zebra-chain` (per the design choice in #10527).
conradoplg pushed a commit that referenced this pull request May 1, 2026
#10527 (#10533)

* test(rpc): skip invalid coinbase Sapling-spend txs in send_raw_transaction proptests

The `any::<Transaction>()` arbitrary strategy can produce coinbase
transactions that contain Sapling spends. Since #10527 (GHSA-rgwx-8r98-p34c),
those transactions are rejected during deserialization, so
`send_raw_transaction` returns a deserialization error before reaching the
mempool. The five affected proptests then time out waiting for a mempool
request that never arrives.

Skip those cases with `prop_assume!` so each test exercises the mempool
path it is intended to cover. The `transaction_roundtrip` proptest in
`zebra-chain` was updated to handle the same combination in #10527.

* style: rustfmt drift in inbound/downloads.rs

* test(state): handle coinbase Sapling spends in finalized-state proptests

After #10527 (GHSA-rgwx-8r98-p34c) rejects coinbase transactions with
Sapling spends during deserialization, three `zebra-state` proptests
panic when arbitrary chains contain such transactions:

- `roundtrip_transaction` reads back a generated `Transaction` via
  `IntoDisk`/`FromDisk` and panics in `FromDisk::from_bytes`.
- `blocks_with_v5_transactions` and
  `all_upgrades_and_wrong_commitments_with_fake_activation_heights`
  commit chains generated by `PreparedChain` to the finalized state,
  whose underlying `partial_chain_strategy` produces coinbase
  transactions with arbitrary Sapling shielded data.

Fix the chain-builder path by clearing `sapling_shielded_data` on
coinbase transactions in `fix_generated_transaction`. Add a
`prop_assume!` to `roundtrip_transaction` since it bypasses the chain
builder and uses `any::<Transaction>()` directly.

The `Transaction::Arbitrary` strategy is intentionally left unchanged so
the rejection path is still exercised by the `transaction_roundtrip`
proptest and the GHSA-rgwx-8r98-p34c reproduction vector in
`zebra-chain` (per the design choice in #10527).
mergify Bot pushed a commit that referenced this pull request May 1, 2026
* test(rpc): skip invalid coinbase Sapling-spend txs in send_raw_transaction proptests

The `any::<Transaction>()` arbitrary strategy can produce coinbase
transactions that contain Sapling spends. Since #10527 (GHSA-rgwx-8r98-p34c),
those transactions are rejected during deserialization, so
`send_raw_transaction` returns a deserialization error before reaching the
mempool. The five affected proptests then time out waiting for a mempool
request that never arrives.

Skip those cases with `prop_assume!` so each test exercises the mempool
path it is intended to cover. The `transaction_roundtrip` proptest in
`zebra-chain` was updated to handle the same combination in #10527.

* style: rustfmt drift in inbound/downloads.rs

* test(state): handle coinbase Sapling spends in finalized-state proptests

After #10527 (GHSA-rgwx-8r98-p34c) rejects coinbase transactions with
Sapling spends during deserialization, three `zebra-state` proptests
panic when arbitrary chains contain such transactions:

- `roundtrip_transaction` reads back a generated `Transaction` via
  `IntoDisk`/`FromDisk` and panics in `FromDisk::from_bytes`.
- `blocks_with_v5_transactions` and
  `all_upgrades_and_wrong_commitments_with_fake_activation_heights`
  commit chains generated by `PreparedChain` to the finalized state,
  whose underlying `partial_chain_strategy` produces coinbase
  transactions with arbitrary Sapling shielded data.

Fix the chain-builder path by clearing `sapling_shielded_data` on
coinbase transactions in `fix_generated_transaction`. Add a
`prop_assume!` to `roundtrip_transaction` since it bypasses the chain
builder and uses `any::<Transaction>()` directly.

The `Transaction::Arbitrary` strategy is intentionally left unchanged so
the rejection path is still exercised by the `transaction_roundtrip`
proptest and the GHSA-rgwx-8r98-p34c reproduction vector in
`zebra-chain` (per the design choice in #10527).

* docs(changelog): prepare 4.4.0 release notes

* docs(changelog): consolidate allocation-amplification advisories into GHSA-438q-jx8f-cccv

* docs(changelog): cover all API changes since 4.3.1

Audited against `check-api.sh 4.3.1 HEAD` and the PR list since 4.3.1.

Workspace CHANGELOG.md:
- Add Security entry for the stalled-peer-disconnect change merged from the
  private fork (FindBlocks/FindHeaders stall classification in PeerSet).

zebra-chain:
- Consolidate `[7.0.0] - PLANNED` into `[Unreleased]` so the 4.4.0 cut isn't
  split across two sections.
- Document new `serialization::MAX_HEADERS_PER_MESSAGE` constant.

zebra-consensus:
- Document groth16 module cleanup: removed `DescriptionWrapper`, `SAPLING`,
  `Description` trait + impl, and the `TryFrom<DescriptionWrapper<&T>>`
  conversions; added `groth16::Item::from_joinsplit`.

zebra-network:
- Document the new `Option<PeerSocketAddr>` field on `Request::AdvertiseBlock`.

zebra-rpc:
- Consolidate `[7.0.0] - PLANNED` into `[Unreleased]`.
- Document `BlockObject::new` gaining a required `n_tx: usize` parameter and
  the new `BlockObject::n_tx` accessor.
- Document `HttpRequestMiddleware{,Layer}::new` gaining a required
  `max_request_body_size: usize` parameter (GHSA-8r29-5wjm-jgvx).

zebra-script:
- Consolidate `[6.0.0] - PLANNED` into `[Unreleased]`.

* release: bump crate versions for Zebra 4.4.0

Drove the bumps via `cargo release version` per the release checklist.

- zebra-chain     6.0.2 -> 7.0.0  (major: VerifiedUnminedTx::new signature, librustzcash 0.27)
- zebra-consensus 5.0.2 -> 6.0.0  (major: groth16 module cleanup)
- zebra-network   5.0.1 -> 6.0.0  (major: Request::AdvertiseBlock tuple shape)
- zebra-rpc       6.0.2 -> 7.0.0  (major: BlockObject::new + middleware constructors)
- zebra-script    5.0.1 -> 6.0.0  (major: Sigops::scripts return type, librustzcash 0.27)
- zebra-state     5.0.0 -> 5.0.1  (patch: internal-only changes)
- zebrad          4.3.1 -> 4.4.0  (minor: nTx field, security fixes, sentry module
                                   made crate-private)

Also ran `cargo release replace -p zebrad` (rewrote the README install tag to
v4.4.0) and refreshed Cargo.lock with `cargo update -w`. Verified with
`cargo check --workspace --locked` on d.lan.

* docs(changelog): finalize crate changelog headers for 4.4.0 release

Replace `[Unreleased]` placeholders with versioned entries dated 2026-05-01:
- zebra-chain     -> [7.0.0]
- zebra-consensus -> [6.0.0]
- zebra-network   -> [6.0.0]  (added security context for the inbound
                                deserializer hardening cohort)
- zebra-rpc       -> [7.0.0]  (added Security section listing the four RPC
                                advisories fixed in this cycle)
- zebra-script    -> [6.0.0]
- zebra-state     -> [5.0.1]  (note: internal-only changes, no public-API delta)

* docs(changelog): adopt librustzcash changelog style for 4.4.0 entries

- Drop the `### Breaking Changes` header in favor of `### Added`/`### Changed`/
  `### Removed` (matches `~/zcash/librustzcash/*/CHANGELOG.md` convention).
- Group sub-items under module-prefix headers ending with `:`.
- zebra-consensus groth16 cleanup is split into a `### Removed` section.
- Wrap entries at ~78 chars and use `impl Trait for Type` form for impl items.

* docs(changelog): prune trivial detail in 4.4.0 entry

- Shorten the consensus-divergence sighash defense-in-depth bullet; the long
  technical explanation lives in the GHSA, not the operator changelog.
- Merge the Sentry SDK upgrade, Sentry CI metadata, OpenTelemetry default-
  release-binaries inclusion, and `zebrad::sentry` privatization (all from
  PR #10490) into a single observability-refresh bullet.

* docs(changelog): pad librustzcash-style subheadings for markdown-lint

Add blank lines around the `### Added` / `### Changed` / `### Removed`
subheadings introduced in the 4.4.0 entries (MD022/MD032).
judah-caruso pushed a commit to ShieldedLabs/zebra-crosslink-staging that referenced this pull request May 28, 2026
…ashFoundation#10527)

* test: add reproduction for GHSA-rgwx-8r98-p34c coinbase Sapling spend allocation gap

* fix: reject coinbase Sapling spends during deserialization (GHSA-rgwx-8r98-p34c)

* test: update proptests and vectors to expect coinbase Sapling spend rejection

Update transaction_roundtrip, block_roundtrip, and the reproduction
vector test to assert that coinbase transactions with Sapling spends
are rejected during deserialization.  The arbitrary generators are
intentionally left unchanged so they continue producing the invalid
combination — the proptests now verify rejection for that case and
round-trip for all others.

---------

Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
judah-caruso pushed a commit to ShieldedLabs/zebra-crosslink-staging that referenced this pull request May 28, 2026
ZcashFoundation#10527 (ZcashFoundation#10533)

* test(rpc): skip invalid coinbase Sapling-spend txs in send_raw_transaction proptests

The `any::<Transaction>()` arbitrary strategy can produce coinbase
transactions that contain Sapling spends. Since ZcashFoundation#10527 (GHSA-rgwx-8r98-p34c),
those transactions are rejected during deserialization, so
`send_raw_transaction` returns a deserialization error before reaching the
mempool. The five affected proptests then time out waiting for a mempool
request that never arrives.

Skip those cases with `prop_assume!` so each test exercises the mempool
path it is intended to cover. The `transaction_roundtrip` proptest in
`zebra-chain` was updated to handle the same combination in ZcashFoundation#10527.

* style: rustfmt drift in inbound/downloads.rs

* test(state): handle coinbase Sapling spends in finalized-state proptests

After ZcashFoundation#10527 (GHSA-rgwx-8r98-p34c) rejects coinbase transactions with
Sapling spends during deserialization, three `zebra-state` proptests
panic when arbitrary chains contain such transactions:

- `roundtrip_transaction` reads back a generated `Transaction` via
  `IntoDisk`/`FromDisk` and panics in `FromDisk::from_bytes`.
- `blocks_with_v5_transactions` and
  `all_upgrades_and_wrong_commitments_with_fake_activation_heights`
  commit chains generated by `PreparedChain` to the finalized state,
  whose underlying `partial_chain_strategy` produces coinbase
  transactions with arbitrary Sapling shielded data.

Fix the chain-builder path by clearing `sapling_shielded_data` on
coinbase transactions in `fix_generated_transaction`. Add a
`prop_assume!` to `roundtrip_transaction` since it bypasses the chain
builder and uses `any::<Transaction>()` directly.

The `Transaction::Arbitrary` strategy is intentionally left unchanged so
the rejection path is still exercised by the `transaction_roundtrip`
proptest and the GHSA-rgwx-8r98-p34c reproduction vector in
`zebra-chain` (per the design choice in ZcashFoundation#10527).
judah-caruso pushed a commit to ShieldedLabs/zebra-crosslink-staging that referenced this pull request May 28, 2026
* test(rpc): skip invalid coinbase Sapling-spend txs in send_raw_transaction proptests

The `any::<Transaction>()` arbitrary strategy can produce coinbase
transactions that contain Sapling spends. Since ZcashFoundation#10527 (GHSA-rgwx-8r98-p34c),
those transactions are rejected during deserialization, so
`send_raw_transaction` returns a deserialization error before reaching the
mempool. The five affected proptests then time out waiting for a mempool
request that never arrives.

Skip those cases with `prop_assume!` so each test exercises the mempool
path it is intended to cover. The `transaction_roundtrip` proptest in
`zebra-chain` was updated to handle the same combination in ZcashFoundation#10527.

* style: rustfmt drift in inbound/downloads.rs

* test(state): handle coinbase Sapling spends in finalized-state proptests

After ZcashFoundation#10527 (GHSA-rgwx-8r98-p34c) rejects coinbase transactions with
Sapling spends during deserialization, three `zebra-state` proptests
panic when arbitrary chains contain such transactions:

- `roundtrip_transaction` reads back a generated `Transaction` via
  `IntoDisk`/`FromDisk` and panics in `FromDisk::from_bytes`.
- `blocks_with_v5_transactions` and
  `all_upgrades_and_wrong_commitments_with_fake_activation_heights`
  commit chains generated by `PreparedChain` to the finalized state,
  whose underlying `partial_chain_strategy` produces coinbase
  transactions with arbitrary Sapling shielded data.

Fix the chain-builder path by clearing `sapling_shielded_data` on
coinbase transactions in `fix_generated_transaction`. Add a
`prop_assume!` to `roundtrip_transaction` since it bypasses the chain
builder and uses `any::<Transaction>()` directly.

The `Transaction::Arbitrary` strategy is intentionally left unchanged so
the rejection path is still exercised by the `transaction_roundtrip`
proptest and the GHSA-rgwx-8r98-p34c reproduction vector in
`zebra-chain` (per the design choice in ZcashFoundation#10527).

* docs(changelog): prepare 4.4.0 release notes

* docs(changelog): consolidate allocation-amplification advisories into GHSA-438q-jx8f-cccv

* docs(changelog): cover all API changes since 4.3.1

Audited against `check-api.sh 4.3.1 HEAD` and the PR list since 4.3.1.

Workspace CHANGELOG.md:
- Add Security entry for the stalled-peer-disconnect change merged from the
  private fork (FindBlocks/FindHeaders stall classification in PeerSet).

zebra-chain:
- Consolidate `[7.0.0] - PLANNED` into `[Unreleased]` so the 4.4.0 cut isn't
  split across two sections.
- Document new `serialization::MAX_HEADERS_PER_MESSAGE` constant.

zebra-consensus:
- Document groth16 module cleanup: removed `DescriptionWrapper`, `SAPLING`,
  `Description` trait + impl, and the `TryFrom<DescriptionWrapper<&T>>`
  conversions; added `groth16::Item::from_joinsplit`.

zebra-network:
- Document the new `Option<PeerSocketAddr>` field on `Request::AdvertiseBlock`.

zebra-rpc:
- Consolidate `[7.0.0] - PLANNED` into `[Unreleased]`.
- Document `BlockObject::new` gaining a required `n_tx: usize` parameter and
  the new `BlockObject::n_tx` accessor.
- Document `HttpRequestMiddleware{,Layer}::new` gaining a required
  `max_request_body_size: usize` parameter (GHSA-8r29-5wjm-jgvx).

zebra-script:
- Consolidate `[6.0.0] - PLANNED` into `[Unreleased]`.

* release: bump crate versions for Zebra 4.4.0

Drove the bumps via `cargo release version` per the release checklist.

- zebra-chain     6.0.2 -> 7.0.0  (major: VerifiedUnminedTx::new signature, librustzcash 0.27)
- zebra-consensus 5.0.2 -> 6.0.0  (major: groth16 module cleanup)
- zebra-network   5.0.1 -> 6.0.0  (major: Request::AdvertiseBlock tuple shape)
- zebra-rpc       6.0.2 -> 7.0.0  (major: BlockObject::new + middleware constructors)
- zebra-script    5.0.1 -> 6.0.0  (major: Sigops::scripts return type, librustzcash 0.27)
- zebra-state     5.0.0 -> 5.0.1  (patch: internal-only changes)
- zebrad          4.3.1 -> 4.4.0  (minor: nTx field, security fixes, sentry module
                                   made crate-private)

Also ran `cargo release replace -p zebrad` (rewrote the README install tag to
v4.4.0) and refreshed Cargo.lock with `cargo update -w`. Verified with
`cargo check --workspace --locked` on d.lan.

* docs(changelog): finalize crate changelog headers for 4.4.0 release

Replace `[Unreleased]` placeholders with versioned entries dated 2026-05-01:
- zebra-chain     -> [7.0.0]
- zebra-consensus -> [6.0.0]
- zebra-network   -> [6.0.0]  (added security context for the inbound
                                deserializer hardening cohort)
- zebra-rpc       -> [7.0.0]  (added Security section listing the four RPC
                                advisories fixed in this cycle)
- zebra-script    -> [6.0.0]
- zebra-state     -> [5.0.1]  (note: internal-only changes, no public-API delta)

* docs(changelog): adopt librustzcash changelog style for 4.4.0 entries

- Drop the `### Breaking Changes` header in favor of `### Added`/`### Changed`/
  `### Removed` (matches `~/zcash/librustzcash/*/CHANGELOG.md` convention).
- Group sub-items under module-prefix headers ending with `:`.
- zebra-consensus groth16 cleanup is split into a `### Removed` section.
- Wrap entries at ~78 chars and use `impl Trait for Type` form for impl items.

* docs(changelog): prune trivial detail in 4.4.0 entry

- Shorten the consensus-divergence sighash defense-in-depth bullet; the long
  technical explanation lives in the GHSA, not the operator changelog.
- Merge the Sentry SDK upgrade, Sentry CI metadata, OpenTelemetry default-
  release-binaries inclusion, and `zebrad::sentry` privatization (all from
  PR ZcashFoundation#10490) into a single observability-refresh bullet.

* docs(changelog): pad librustzcash-style subheadings for markdown-lint

Add blank lines around the `### Added` / `### Changed` / `### Removed`
subheadings introduced in the 4.4.0 entries (MD022/MD032).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants