fix(pczt): drop rotko-invented anchor-equality gate; match Keystone#12
Merged
Conversation
The PCZT signing path required the bundle's Orchard anchor to byte-equal
the locally synced verified anchor — and refused with "Sync notes to
current state first" otherwise. That gate was rotko-invented and does
not appear in the canonical Zashi → Keystone flow:
- Keystone firmware never reads `pczt.orchard().anchor()` for
validation. The anchor is committed to via the ZIP-244 sighash
(handled inside the standard sign_orchard pipeline) but never
compared against any reference. See
`/steam/rotko/keystone3-firmware/rust/apps/zcash/src/pczt/{sign,
check}.rs` — anchor is absent from every check function.
- The Keystone Android SDK is pure transport. Single hex blob in,
single hex blob out. Zero anchor handling at any layer.
- Zashi-android's `SignKeystoneTransactionVM.kt` is just
redactPcztForSigner → UR-encode → wait for response → submit. No
anchor check on the host side either.
Removing this gate makes zigner a drop-in Keystone-equivalent signer
for the canonical Zcash hardware-signing flow.
What stays as zigner's distinguishing safety value:
- `known_spends == 0 && action_count > 0` still rejects PCZTs that
spend zero verified nullifiers. Keystone cannot do this because
it has no notion of a verified-notes store; zigner does. Blind-
signing footgun that's worth keeping.
- Per-spend "verified / UNKNOWN" indicator in the UI.
- "Spend not in verified notes" warning when only some spends are
recognized.
- Net-value sanity check against total verified balance.
What's removed:
- `anchor_matches: bool` + `anchor_hex: String` fields from
`ZcashPcztInspection` (Rust struct + UDL dictionary + UI).
- The "PCZT anchor does not match verified anchor" rejection.
- The "No verified notes && anchor mismatch" rejection (the bare
no-verified-notes case is already covered by the known_spends
check; the anchor coupling was incidental).
- Anchor display row + mismatch warning banners on both Android
and iOS PCZT review screens.
The user-managed `anchor_verifiers` registry (e4ed943) and the
note-bundle FROST attestation path (`verify_anchor_attestation`,
`bundle.anchor_attestation`) are untouched — those serve the note-
sync trust flow on a separate screen and are not part of the PCZT
signing path.
Assisted-by: Claude:claude-opus-4-7
hitchho
added a commit
that referenced
this pull request
Jun 1, 2026
Defense-in-depth against the postcard-based pczt deserialization:
- Reject pczt_bytes > 1 MiB upfront (real PCZTs are KBs)
- Reject Orchard bundles claiming > 4096 actions (real bundles are tens)
- Wrap Pczt::parse in std::panic::catch_unwind so a malformed input
surfaces as a clean ErrorDisplayed instead of unwinding into the
UDL/Kotlin/Swift FFI boundary
postcard doesn't bound declared collection lengths against remaining
slice length, so a hostile PCZT declaring Vec<Action> of 2^32 items
triggers allocation-driven OOM or panics on overflow. Catching this
client-side keeps a malicious or buggy host coordinator from crashing
the cold signer process.
Applied to inspect_zcash_pczt (the read path). sign_zcash_pczt reuses
inspect_zcash_pczt internally so it inherits both gates.
STAGING-ONLY commit; should land on the original PR #12 branch before
master merge.
Assisted-by: Claude:claude-opus-4-7
hitchho
added a commit
that referenced
this pull request
Jun 7, 2026
…tUiKit, security review pass Includes: - PCZT Approve & Sign UI + animated signed-UR display + fountain progress cleanup (PR #11) - Drop rotko-invented anchor-equality gate; match Keystone (PR #12) — anchor still bound through ZIP-244 sighash inside sign_orchard - PCZT review screen Basic/Advanced tabs + glossary note + tx-meta surfacing (tx_version, consensus_branch_id, expiry) (Android) - Light-theme toggle + slower PCZT QR cycle for webcam scan-back - Multisig management: Settings restructure, FrostUiKit, selective backup flow, multisig detail screen - Zafu-style home: wallet card, per-network sections, scan-first bottom bar - Dark-mode FVK input visibility fix + FVK compat info dialog - Restore for multisig batch backup - PCZT parse hardening against adversarial input (1 MiB cap, 4096-action cap, catch_unwind → ErrorDisplayed) - Security review pass: FLAG_SECURE, threshold cross-check (KeyPackage/PublicKeyPackage cryptographic ground truth), passphrase floor - TODO(sync-flow) now cites tracking issue #16 Cold-storage wire formats unchanged (sighash, PCZT, FROST share, backup envelope JSON). Only ZcashPcztInspection dict shape changed — display-only struct, not persisted, not on the wire. Both Swift and Kotlin call sites updated in same diff. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Removes the PCZT-signing path's strict byte-equality check between the bundle's Orchard anchor and zigner's locally-synced verified anchor. Aligns the zigner cold-signing flow with the canonical Zashi → Keystone PCZT protocol.
Why
sign_zcash_pcztpreviously refused PCZTs with "Sync notes to current state first" whenever the PCZT's anchor didn't byte-match the last note-sync QR import. The mismatch fires every time the chain tip moves between sync and signing — every ~75s on Zcash mainnet — forcing a re-sync round-trip per signing session.That check is rotko-invented. It does not exist in:
/steam/rotko/keystone3-firmware/rust/apps/zcash/src/pczt/{sign,check}.rs): the signing path never readspczt.orchard().anchor()for validation. The anchor is committed to via the ZIP-244 sighash (handled inside the standardsign_orchardpipeline) but never compared against any reference.check_actionvalidatescv_net/ nullifier /rk/ note commitment / output OVK ownership; anchor is absent.KeystoneHQ/keystone-sdk-android): pure transport — single hex blob in, single hex blob out, zero anchor handling at any layer.SignKeystoneTransactionVM.kt):redactPcztForSigner→ UR-encode → wait for scanned response → submit. No anchor check on the host side either.Removing the gate makes zigner a drop-in Keystone-equivalent signer for the canonical Zcash hardware-signing flow, which is exactly the model zafu PR #11 (rotkonetworks/zafu#11) is migrating to.
What stays
zigner's distinguishing safety value over a vanilla Keystone-shape signer is the verified-notes store. That store still drives:
known_spends == 0 && action_count > 0hard reject — Keystone cannot do this (no notes store); zigner can. Blind-signing footgun worth keeping.What's removed
anchor_matches: bool+anchor_hex: Stringfields fromZcashPcztInspection(Rust struct + UDL dictionary + Kotlin/Swift consumers).ZcashPcztScreen.kt,UnifiedTransactionScreen.kt) and iOS (ZcashPcztView.swift) PCZT review screens.Out of scope (intentionally untouched)
anchor_verifiersregistry (e4ed943).verify_anchor_attestation,bundle.anchor_attestationintransaction_signing/src/zcash.rs).Both serve the note-sync trust flow on a separate screen and are not part of the PCZT signing path. The registry's bootstrap-with-rotko-key + Ring-VRF infrastructure remains intact for the note-bundle use case.
Companion / related work
Test plan
cargo check -p signer -p definitions— clean (no warnings on touched code).cargo test -p signer --lib— 31/31 pass.db_handlingcomplexity errors on master also present on unchanged baseline; unrelated).Disclosure
AI tooling assisted in drafting this patch and the commit message. Cross-referenced the Keystone firmware and SDK paths via static review pass.
🤖 Generated with Claude Code