[V136] feat(zec): NU7 — sign under the zebra-reported branch, NU7 librustzcash crates, ZIP 316 Revision 2 addresses - #check-lint-warning

ZEC support for Zcash NU7 (consensus branch 0x77190ad9), for v1.136.0.

Signing branch from zebra

Bifrost takes the consensus branch id for each outbound from the local zebra node's upgrade schedule (getblockchaininfo), keyed on the tx's target height rather than the live tip, and passes it to the signing library. Transactions stay v5, which remains valid under NU7 (only v4 is rejected).

  • bifrost/pkg/chainclients/zcash/branch.go: the branch for a height is the upgrade with the highest activation height not above it. Upgrades sharing that height resolve through the node's nextblock, then chaintip, branch; anything else is rejected.
  • signer.go: the branch is resolved at build time and stored in the signing checkpoint, so retries sign the same digest. Checkpoints written before this change resolve the branch for their own target height.
  • tx_lifecycle.go: checkpoint staleness and broadcast validity use the node schedule, not the compiled one.
  • chain/zec: PartialTx.branch_id. When set, build, apply-signatures and txid sign under it. An id the library does not know is rejected, never replaced. 0 keeps the compiled schedule for other callers.

librustzcash: NU7-capable crates (no fork)

zcash_keys 0.17.0-pre.2, zcash_primitives 0.31.0-pre.2, zcash_protocol 0.11.0-pre.1 and matching zcash_address/zcash_transparent/orchard/sapling from crates.io. The orchard patch is dropped. lib/libzec.a and lib/sim/libzec.a are rebuilt.

Address rules from 1.136.0

The new crates parse ZIP 316 Revision 2 unified addresses (zu…/tu…). Consensus address handling is versioned on the network version, never on zebra:

  • New validate_address_v136, is_or_has_transparent_v136, normalize_address_for_outbound_v136, dispatched from 1.136.0. They accept Revision 2 addresses; receiver choice is unchanged (Sapling, then transparent, then Orchard).
  • Every rule set below 1.136.0 decodes through decode_rev0, which rejects Revision 2 containers, so it keeps the accept set of the library it shipped with.
  • Quotes and inbound addresses keep emitting u1… (encoding defaults to Revision 0 when representable).
  • ZEC memo limit: from 1.136.0 a Revision 2 shielded destination gets the 512-byte limit like u1… (Chain.MaxMemoLengthWithVersion, trimMemoToChainLimitV136, validateOutboundMemoLengthV136), applied from the new DepositHandler.handleV136 and ObservedTxInHandler.handleV136; below 1.136.0 unchanged.
  • Network classification: new getNetworkV136 resolves ZEC addresses with ValidateAddressV136, so a zutest1…/zuregtest1… address on a mainnet node is TestNet/MockNet (swap, trade withdrawal and slasher network checks reject it). getNetworkV135 is unchanged.

Fail closed

  • If the schedule can't be read from zebra, nothing is built or signed.
  • Apply-signatures refuses a ptx with no branch id.
  • A branch id the library does not know is refused.

Rollout: coordinated upgrade (not a patch)

The new crates build different transactions for spends with shielded outputs (cross-version corpus below), and each signer builds its own tx, so mixed-library signers cannot co-sign those outbounds. A shielded checkpoint saved by the old library can also rebuild with a different txid after the upgrade, even before expiry, while its input reservation still refers to the original txid. No unfinished old-library attempt may carry over.

Required before deployment, each step verified:

  1. Stop new ZEC activity and let existing outbounds finish on v1.135.
  2. Check the outbound queue and every signer's local state: all old attempts confirmed or safely retired, pending checkpoints and input reservations reconciled.
  3. Set HALTSIGNINGZEC=1 and wait for already-running signing attempts to finish.
  4. Roll out the binary. Lift HALTSIGNINGZEC only once every active signer runs v1.136.0.

Halting signing alone keeps unfinished checkpoints but does not make them compatible with the new library. Every validator's zebra must also report the same NU7 schedule (zebra 7.x) before outbounds near the activation height are signed. The zebra 7.x chart bump is a separate deploy item.

Tests

  • Branch selection around NU7, target height vs tip, shared activation heights, bad schedules; re-sign across the boundary keeps the checkpoint's branch and digest; zebra unavailable builds nothing; unknown branch id fails closed.
  • NU7 sighash differs from NU6.3 for the same tx (runs, no skip).
  • Revision 2 vectors (turegtest1…/zuregtest1…, same receivers as an existing fixture): rejected by every pre-1.136 path (v1/v124/v127/v130, Go at 1.124/1.127/1.131/1.135), accepted from 1.136.0, normalized to the same receiver as the Revision 0 form. The v1.135 library (develop d15755f26) also rejects them at every version.
  • Cross-version corpus vs the v1.135 library (chain/zec/go/zec/testdata/xver_lib_v135.txt): address derivation and transparent-only spends match byte for byte; spends with sapling/orchard/unified outputs differ (asserted, so a future change is noticed).
  • The released golden vector still matches, so pre-NU7 transparent digests are unchanged.

Open before merge

  • Regtest e2e across NU7 activation with zebra v7.0.0-rc.0.
Edited by Itzamna

Merge request reports

Loading
Loading