Skip to content

feat: ZID identity + zigner subscribe flow#6

Merged
hitchho merged 15 commits into
mainfrom
feat/zid-for-zigner
Apr 22, 2026
Merged

feat: ZID identity + zigner subscribe flow#6
hitchho merged 15 commits into
mainfrom
feat/zid-for-zigner

Conversation

@Catopish

@Catopish Catopish commented Apr 21, 2026

Copy link
Copy Markdown

Summary

  • Embed ZID in FVK QR for cross-network dedup — UR + binary parsers extract the ZID; importing the same zigner device for zcash and penumbra now merges into a single vault via a new `zigner-merge` module instead of creating two.
  • ZID signing approval + delivery — `zafu_sign` flow, password-gated approval, share log written in the service worker.
  • Subscribe page — review step, password gate, live send-progress, pro license always fetched from server. Zigner wallets now complete the full pay flow inline (build → sign-request QR → signature scan → broadcast) instead of being bounced to the send page.
  • Fix false "pro activated" on extensions — polling captures baseline `expires` and waits for server-credited increase instead of flipping true on any existing pro license.

Paired with zcli license-server changes: rotkonetworks/zcli#3

Test plan

  • Scan a zigner FVK QR (zcash then penumbra): one vault appears with both networks, not two.
  • Identity page shows ZID automatically after zigner import (no separate ZID scan needed).
  • Subscribe as a mnemonic wallet: review → password → live progress → pro activated.
  • Subscribe as a zigner wallet: review → sign-request QR → scan signature → broadcast → activated, all on the subscribe page.
  • Extend an existing pro subscription: UI does NOT flip to "activated" until the server credits the new on-chain memo (verify by watching license-server log).

🤖 Generated with Claude Code

Catopish and others added 10 commits April 15, 2026 13:52
Rewrite sign approval page with two paths:
- Mnemonic wallets: review → password confirmation → sign
- Zigner wallets: review → show challenge QR → scan response QR

Thread isAirgap flag from sign-request listener through popup request
system to the approval component. Detect wallet type by reading the
selected vault before opening the popup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…proval UI

- Add zafu_sign to delegated types in easteregg listener so it doesn't
  race with the sign-request listener and return "unknown message type"
- Add CAPABILITY_APPROVAL to hidden header/tab routes for clean standalone UI
- Remove capability pre-check from sign-request listener — the sign
  approval itself is the permission check
- Auto-grant sign_identity capability on successful signature so sites
  appear in the identity page
- Improve test-zid.html with step-by-step developer documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The popup closes (window.close) before the async storage write
completes, so the share log was never persisted. Move the zidShareLog
write and sign_identity capability grant to the service worker side
(sign-request.ts) where it runs to completion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the license was cached in chrome.storage.local and persisted
across sessions, so a revoked/expired license would still show as pro
until the user manually triggered a refresh.

Now the server is always the source of truth:
- on wallet unlock (via setSessionPassword, unlock, or init auto-unlock)
  the license is fetched fresh from the server by ZID pubkey
- server pro → show pro, server free → show free
- server unreachable → show free (offline treated as no subscription)
- no local storage reads/writes for license state

Removed loadLicense/setLicense slice methods since state lives only
in memory now.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r pay

Rework the subscribe flow so both mnemonic and zigner wallets can pay for
pro entirely from within zafu:

- Add review step with tx summary (amount, duration, recipient, memo)
  before broadcasting
- Gate confirmation behind the password modal (matches zcash-send pattern)
- Show live build/broadcast progress with step-by-step timeline from the
  worker's zcash-send-progress events
- For zigner wallets, route to the existing zcash-send flow with prefilled
  recipient/amount/memo so they can use the QR signing flow
- Unify new-subscribe and extend-subscription into a single UI (same month
  selector, same review flow, button label changes to "extend +N days"
  when already pro)
- Read ZID pubkey from vault.insensitive['zid'] for zigner wallets (no
  mnemonic available to derive from)
- Fix menu drawer to fall back to any vault's ZID when active wallet
  doesn't have one imported

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- zigner FVK QRs now carry the device ZID pubkey. UR format uses CBOR
key 4 in the account map.
- importing the same zigner device for both zcash and penumbra no longer
creates two vaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g pro

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hitchho and others added 5 commits April 21, 2026 21:50
These test vectors are shared with zigner/rust/signer/src/auth.rs
(test_zid_pubkey_matches_zafu, test_sign_zid_site_specific_matches_zafu).

If either repo's derivation drifts, CI catches it in both places
instead of silently producing different pubkeys for the same seed —
which would break the "same device across zafu and zigner = same
identity" guarantee.

Uses @vitest-environment node to bypass the jsdom navigator.locks
setup issue that blocks existing state tests.
Cuts max witness replay from 50k blocks to 10k blocks, bounding
merkle witness build time to ~5s instead of 70s for old notes.

Storage cost: ~150 snapshots per year of chain (~300KB) vs ~15 before.
Acceptable tradeoff for 10x faster spend on historical notes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kills the 70s "rebuild merkle paths from a checkpoint" on every spend.
Witnesses now live in IDB per note and advance alongside sync.

Worker changes:
- DecryptedNote gains witness_hex + witness_tree_size.
- Sync loop maintains a running frontier (persisted with its height).
  Each batch calls witness_sync_update once, advancing every tracked
  witness and seeding new ones for notes decrypted in-range. Bootstrap
  drops stale witnesses to avoid silent drift when refetching frontier.
- buildWitnesses has three tiers: extract stored witnesses (fast),
  fast-forward over sync->tip gap via witness_sync_update, or one-shot
  build_witnesses_and_paths for pre-upgrade notes (writes back so the
  next spend takes the fast path). Witness root cross-checked against
  the network anchor before every spend.

Upgrade cost: first spend on a wallet that existed before this change
still replays once to seed witnesses; every subsequent spend is near-
instant. New wallets never pay the replay cost.

Paired with the zafu-wasm APIs added to zcli (witness_sync_update,
witness_extract_path, build_witnesses_and_paths).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hitchho hitchho merged commit 51957b6 into main Apr 22, 2026
1 of 3 checks passed
hitchho added a commit that referenced this pull request May 16, 2026
…R, stall, provenance

Cross-referenced findings from an 8-reviewer audit (redshiftzero, hdevalence,
systems, consensus, wallet-UX, QA, supply-chain, release-mgmt). Tractable
high-consensus fixes:

#1b merkle anchor TOCTOU (QA, BLOCKING — the hardware bug):
buildWitnesses anchored at the live tip, where zidecar replicas diverge and
reorgs happen, across 3 unsynchronized RPCs → intermittent "tree root
mismatch". Decouple: anchorHeight = tip - ANCHOR_CONFIRMATIONS(3); keep
target_height at the live tip for branch_id/expiry. Orchard anchors are
historical roots with no max-age, so a slightly-behind anchor is fully valid.

#1a merkle filter_map (release-mgmt): both sites decode positionally with a
precise `sibling i/j not canonical: <hex>` error instead of a lossy generic
count error. (zcli commit 2038eb6e.)

#2 UFVK validation (security + redshiftzero, BLOCKING): authoritative
zcash_keys decode (new wasm `validate_ufvk`) at the persistence boundary in
wallet-entries.ts, before any record is written. Throws loud on a bogus
UFVK instead of deferring to first-send and poisoning dedup. @repo/wallet
stays structural-only and wasm-free.

#3 CBOR signed-shift (UX + security): unwrapCborSinglePczt 4-byte length
used signed `<<` → high-bit length went negative, bypassed the bounds
guard, returned a silently-truncated PCZT. Rewrote with *256 accumulation,
per-byte bounds, exact-consume canonical check. TDD: 3 RED adversarial
cases → 12/12 green.

#4 wasm-init (UX, HIGH): contacts/index.tsx called address_from_ufvk
without awaiting the bindgen default() initializer (same class of bug Al
fixed in the scanner) — silently degraded contact-card privacy. Added
await default().

#5 UR receive stall (UX): no way to distinguish "need more frames" from
"dead/corrupt stream" → unbounded "scanning...". Added a 12s
no-new-unique-part watchdog that surfaces a hard error via onError.

#6 wasm provenance (systems + release-mgmt, SERIOUS): vendored blobs had
zero binding to a zcli source rev. Added BUILD_PROVENANCE.md pinning the
exact zcli commit + per-artifact sha256 + reproduction commands. Both
single-thread and parallel artifacts rebuilt from zcli 2038eb6e.

Not done (documented, needs human decision / hardware, NOT silently shipped):
- Behavioral merkle-rejection test — needs notes+path fixture infra that
  doesn't exist; would be a contorted box-tick. Tracked as the remaining
  coverage gap (matches QA/redshiftzero).
- #7 commit hygiene — branch is shared with Al; rewriting history is a
  coordination hazard. Disposition: curate the squash message at merge and
  split the unrelated release commits into their own PR off main. Process
  instruction, not an autonomous shared-history rewrite.
- The #1b anchor fix MUST be re-validated on hardware before the merkle
  bug is claimed resolved.

zafu prod build green (browser + worker). zcli tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hitchho added a commit that referenced this pull request Jun 4, 2026
…aging tip 686d174)

Parallel wasm was still at zcli 2038eb6e from PR #6 merge. Rebuilt
against staging tip 686d174 which carries:
- chore(staging): cap ur_decode_frames inputs (DoS hardening)
- chore(staging): fix sled type-inference errors in wallet.rs

Re-applied the LOCAL PATCH to snippets/wasm-bindgen-rayon-*/src/workerHelpers.js
(stock await import('../../..') is a directory import Chrome extensions reject).

Verified shared-imported memory in the rebuilt blob:
  (import "./zafu_wasm_bg.js" "memory" (memory $mimport$0 51 32768 shared))

New shas:
- single-thread (already updated by 524cc75 on Jun 2):
  dee6a25b604f27397522bbc3c8e143d96c18cfe24d435c3d2ac97ebbb164dfd3
- parallel (this commit):
  af3359357739b223804a9836ec00c0d2ef970d787627671e7cf0acb9ab923adf

STAGING-ONLY commit; the proper landing path is for a fresh wasm rebuild
to ride along with the next zcli master merge.

Assisted-by: Claude:claude-opus-4-7
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.

2 participants