Skip to content

chore(protocol): import the shared protocol type family - #471

Merged
ValarDragon merged 10 commits into
mainfrom
agent/import-zakura-protocol-20260921
Sep 23, 2026
Merged

ValarDragon merged 10 commits into
mainfrom
agent/import-zakura-protocol-20260921

Conversation

@p0mvn

@p0mvn p0mvn commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • import zcash_protocol 0.10.5, zcash_address 0.13.0, zcash_transparent 0.10.0, and zip321 0.9.0 from commit 97aefdc3
  • publish them as zakura-protocol, zakura-address, zakura-transparent, and zakura-zip321 while preserving their upstream library targets and APIs
  • rewire zakura-keys, zakura-primitives, and ZIP 321 through the local packages so the workspace resolves one public-type family
  • add workspace, CI, maintenance, changelog, and cargo-vet integration for the imported crates

Motivation

Zakura and its wallet libraries need a Zakura-owned protocol stack for NU7. Importing every crate that exposes address and protocol types closes the public-type dependency graph: values such as NetworkType, ZcashAddress, and Zatoshis no longer cross between distinct registry and workspace copies.

Tests

  • cargo check --locked -p zakura-address -p zakura-transparent -p zakura-protocol -p zakura-primitives -p zakura-keys --all-targets --all-features
  • cargo test --locked -p zakura-address -p zakura-transparent --all-features — 55 unit tests and 9 doctests passed
  • cargo test -p zakura-zip321 --all-features — 16 unit tests passed
  • cargo +1.91.0 check --locked -p zakura-address -p zakura-transparent -p zakura-protocol --all-targets --no-default-features
  • cargo fmt --all -- --check
  • ./scripts/changelog.py check
  • cargo deny --locked --workspace check bans licenses sources
  • cargo vet check --store-path ./supply-chain/ --locked --no-registry-suggestions
  • git diff --check
  • Cargo metadata contains only the Zakura address, protocol, transparent, and ZIP 321 packages, with no upstream copies

actionlint reports the existing SC2086 finding in the no-std build command; the same finding is present on main.

AI assistance

Codex assisted with the workspace adaptation and validation.

@p0mvn
p0mvn marked this pull request as ready for review September 21, 2026 07:38
@v12-auditor

v12-auditor Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Note

Complete: Audit complete. V12 found 12 issues worth reviewing.

Open the full results here.

FindingSeverityDetails
F-283635 🟡 Medium
Zero denomination floor loops forever

largest_one_two_five is public and accepts an unconstrained u64 floor. Calling it with floor == 0 bypasses the hi < floor return, initializes pow to zero, and repeatedly observes that 0 * 10 is still at most any unsigned hi. The loop body leaves pow unchanged at zero, so the function never returns. Any downstream migration-sizing path that forwards an unvalidated floor can therefore hang indefinitely.

F-283641 🔵 Low
Saturated expiries fail canonical validation

canonical_expiry adds the window using saturating BlockHeight arithmetic, but is_canonical_expiry_value recognizes only exact modulus multiples. Even with the default ZIP-318 parameters, references near u32::MAX generate a saturated expiry of u32::MAX, which the reference-based method accepts. The value-only method rejects that same generated value because u32::MAX is not divisible by 34,560. The two public canonicality forms therefore disagree in the saturation region despite internally consistent parameters.

F-283642 🔵 Low
Incomplete evidence becomes cacheable conformance

classify rejects anchor_on_grid and fee_is_canonical only when they are explicitly Some(false); missing values are dropped from the predicate. Otherwise canonical evidence with those checks omitted therefore returns Conforms, and to_code persists the same code used when both checks were positively verified. The evidence contract simultaneously permits None to become a value as wallet data arrives, so resolving an omitted field to Some(false) changes the result from Conforms to Nonconforming. This contradicts the documented monotonicity and cacheability guarantee for classification decisions.

F-283643 🔵 Low
Contradictory evidence passes classification

The public evidence builder permits mutually inconsistent combinations, and each shape-specific classifier ignores fields it does not consume. Preparation evidence can declare zero destination actions while also declaring a destination value and still return Conforms(Preparation), even though zero actions is documented as no destination bundle. Conversely, a crossing can declare source_is_send_to_self = Some(false) and still return Conforms(Transfer) because that branch ignores the explicit negative fact. Affirmative classifications can therefore be derived from evidence that cannot consistently describe the claimed transaction shape.

F-283644 🔵 Low
Preparation transactions wrongly require grid anchors

The module states that preparation transactions need temporal spacing but no anchor bucketing, while only pool-crossing transfers anchor to retained boundaries. classify nevertheless applies anchor_on_grid == Some(false) before selecting the preparation or transfer branch. An otherwise canonical preparation with a truthful off-grid anchor is therefore returned as Nonconforming. Even anchor evidence alone can definitively rule out every ZIP-318 shape despite being irrelevant to preparations.

F-283646 🔵 Low
Unknown codes are lost on re-encoding

from_code maps every unrecognized persisted integer to Unknown, while Unknown::to_code() always emits zero. An older consumer reading a valid classification code introduced by a newer append-only producer therefore loses the original integer. A normal decode, update, and re-encode sequence transforms code 4 or any later code into the default unclassified value 0. Invalid or corrupted integers are also indistinguishable from rows that were genuinely never classified.

F-283650 🔵 Low
Partial schedules yield contradictory branch ranges

LocalNetwork accepts partial schedules and defines None as an upgrade that is not activated. With Overwinter at height 1, Sapling absent, and Blossom at height 10, BranchId::for_height(..., 10) selects Blossom because it scans all configured upgrades in reverse order. BranchId::Overwinter.height_bounds(), however, uses only Sapling as its upper bound and returns an unbounded range starting at 1, incorrectly including height 10. An all-None schedule similarly selects Sprout at every height while Sprout.height_bounds() returns None, documented as meaning Sprout is never in effect.

F-283651 🔵 Low
Rounded-up height is not a boundary

boundary_at_or_above promises the least interval boundary not below its input, but rounds with saturating addition. For AnchorBucketInterval::ZIP_318 and height = u32::MAX, rounding up mathematically requires a height 33 blocks beyond the representable range. The method saturates to u32::MAX, even though u32::MAX % 144 == 111 and is_boundary therefore returns false for the result. The two APIs give contradictory answers about the same returned checkpoint.

F-283652 🔵 Low
Truncated memos are silently canonicalized

Memo::from_bytes documents rejection of inputs that are not 512 bytes, but it delegates to MemoBytes::from_bytes, which accepts every length up to 512 and fills the missing suffix with zeroes. A one-byte [0xFF] input is expanded into an arbitrary memo with 511 zero payload bytes; [0xF6] becomes an empty memo; and [0xF7] becomes a zero-filled future-format memo. Calling encode then emits a complete 512-byte value that was never present in the input. The parser consequently canonicalizes truncated wire data instead of rejecting it.

F-283653 🔵 Low
Nonzero text padding is accepted

Canonical text memos contain UTF-8 followed by NUL padding, so every byte after the first padding NUL must be zero. MemoBytes::as_slice instead searches backward for the last nonzero byte and removes only the final run of zeroes. An exact 512-byte input containing A\0B followed by zeroes is therefore passed to str::from_utf8 as A\0B, which is valid UTF-8, and becomes Memo::Text. Encoding the text copies the embedded NUL and later nonzero byte back verbatim, preserving a noncanonical memo.

F-283654 🔵 Low
Malformed empty memos become future formats

The canonical 0xF6 memo marker is valid only when followed by 511 zero bytes. If any suffix byte is nonzero, the guarded empty branch fails and the wildcard branch accepts the buffer as Memo::Future. Future serialization preserves the raw bytes unchanged. Known malformed instances of the empty format are therefore conflated with reserved future formats, even though valid future tags are separate values.

F-283655 🔵 Low
Raw memo accessor truncates payloads

Memo::Arbitrary defines all 511 bytes after its 0xFF format byte as payload, including zero-valued suffix bytes. MemoBytes::as_slice nevertheless removes every trailing zero from the entire representation without checking the format. Encoding Memo::Arbitrary([0; 511]) and then calling as_slice() returns only [0xFF], not the complete arbitrary memo representation. Reserved future formats with meaningful trailing zero bytes are affected for the same reason.

And 10 more auto-invalidated findings.

Analyzed 11 files, diff c6cb513...4e1376a.

@p0mvn p0mvn changed the title chore(zakura-protocol): import 0.10.5 workspace crate chore(protocol): import address, protocol, and transparent crates Sep 21, 2026
@socket-security

socket-security Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​base64@​0.22.110010093100100
Addedcargo/​nom@​7.1.310010093100100
Addedcargo/​percent-encoding@​2.3.210010094100100

View full report

@p0mvn p0mvn changed the title chore(protocol): import address, protocol, and transparent crates chore(protocol): import the shared protocol type family Sep 21, 2026
p0mvn and others added 2 commits September 21, 2026 15:27
* feat(protocol): expose NU7 consensus branch

Adapted from valargroup/librustzcash#76.

Co-authored-by: Evan Forbes <evan.samuel.forbes@gmail.com>

* Add changelog for NU7 protocol activation

* fix(primitives): satisfy clippy for NU7 branch matching

* Apply suggestion from @ValarDragon

---------

Co-authored-by: Evan Forbes <evan.samuel.forbes@gmail.com>
Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
@ValarDragon
ValarDragon merged commit 22f98f8 into main Sep 23, 2026
78 of 79 checks passed
@ValarDragon
ValarDragon deleted the agent/import-zakura-protocol-20260921 branch September 23, 2026 08:26

fn into_iter(self) -> Self::IntoIter {
NonHardenedChildIter {
next: Some(self.0.start),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
next: Some(self.0.start),
next: (self.0.start < self.0.end).then_some(self.0.start)),

@ebfull ebfull mentioned this pull request Sep 23, 2026
ebfull added a commit that referenced this pull request Sep 23, 2026
…lease

`scripts/changelog.py release vX.Y.Z` only collapsed `X.Y.Z-rc*` sections, so
the published `1.3.0-alpha.1` sections would have stayed separate from the
`1.3.0` section. Treat every `X.Y.Z-<pre-release>` section the same way, add a
unit test, and update the guidelines, fragment README, and release skill. The
skill also gains the four crates imported in #471 in its publish order and
crate counts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ebfull added a commit that referenced this pull request Sep 23, 2026
Bump the workspace version and intra-workspace requirements from
1.3.0-alpha.1 to 1.3.0 and assemble the v1.3.0 changelog sections,
consuming all 17 pending fragments and folding the 1.3.0-alpha.1 sections
into the stable sections.

Editorial changes to the assembled entries:
- Record the #471 dependency swap in zakura-primitives and zakura-keys.
- Link #492 from the #474 NU7 entry: the release ships the 0x77190AD9
  consensus branch ID that the fragment documented.
- Drop the stale `zcash_unstable="nu7"` qualifier from the #458 entry.
- Remove internal-only wording from the #437 and #451 halo2_proofs entries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ebfull added a commit that referenced this pull request Sep 23, 2026
…lease

`scripts/changelog.py release vX.Y.Z` only collapsed `X.Y.Z-rc*` sections, so
an `X.Y.Z-alpha.N` section would stay separate from the stable `X.Y.Z`
section. Treat every `X.Y.Z-<pre-release>` section the same way, add a unit
test, and update the guidelines, fragment README, and release skill. The
skill also gains the four crates imported in #471 in its publish order and
crate counts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ebfull added a commit that referenced this pull request Sep 23, 2026
Bump the workspace version and intra-workspace requirements from
1.3.0-alpha.1 to 2.0.0 and assemble the v2.0.0 changelog sections,
consuming all 17 pending fragments. The bump is major because #471
replaced the `zcash_protocol`, `zcash_transparent`, and `zcash_address`
crates behind types in the public APIs of zakura-primitives and
zakura-keys, and #458 removed the `zip-233` feature and its APIs.

No stable 1.3.0 ships, so the published `1.3.0-alpha.1` sections belong
to the 2.0.0 delta from 1.2.0: they were folded with
`./scripts/changelog.py release v1.3.0` and the section renamed to 2.0.0.

Editorial changes to the assembled entries:
- Record the #471 dependency swap in zakura-primitives and zakura-keys.
- Link #492 from the #474 NU7 entry: the release ships the 0x77190AD9
  consensus branch ID that the fragment documented.
- Drop the stale `zcash_unstable="nu7"` qualifier from the #458 entry.
- Remove internal-only wording from the #437 and #451 halo2_proofs entries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ebfull ebfull mentioned this pull request Sep 23, 2026
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.

3 participants