Skip to content

ZEC "Transparent (Disposable)" addresses derive use non standard derivation  #3614

Description

@simon202011

Summary

Zcash disposable transparent addresses are not derived from the user's seed. cw_zcash/lib/src/zcash_taddress_rotation.dart (seedForOffset) creates a second zkool account from the same mnemonic plus a computed BIP-39 passphrase, :tgen:<CRC32(mnemonic)> (or <passphrase_with_underscores>:tgen:<CRC32("mnemonic passphrase_")>), and hands out that account's m/44'/133'/0'/0/n addresses.

Consequences:

  1. No other wallet can recover these coins. Restoring the 24 words (with the user's own passphrase, if any) in Zashi, YWallet, Zingo, zkool, or any BIP-44 tool yields a zero balance for every disposable address. The user's backup is incomplete without knowing this repository's source.
  2. Small amounts are stranded by design. _sweepThreshold (30,000 zat; the header comment says 15,000) means anything below it is never auto-shielded and, per the comment, "essentially burned". The app will not spend it and the user cannot.
  3. Autoshield only runs while the app is open and synced. ZEC received on a disposable address while the app is closed sits on a transparent, publicly visible UTXO under this undocumented derivation until the next launch — indefinitely if the user has moved on.
  4. The mechanism is documented only in a source comment. Nothing in the app, the backup screen, or docs.cakewallet.com tells the user that a hidden passphrase exists.

Reproduction

  1. Create a ZEC wallet, note the mnemonic. Receive ZEC on a "Transparent (Disposable)" address.
  2. Restore the same mnemonic in any other wallet, or derive m/44'/133'/0'/{0,1,2}/n for n < 100 with any BIP-44 tool: the address does not appear.
  3. Apply the passphrase :tgen:<CRC32 of the mnemonic string> (standard CRC-32, decimal) to the same mnemonic and derive m/44'/133'/0'/0/0: this is the first disposable address. Verified on mainnet with a real address (match at m/44'/133'/0'/0/0).

Why a computed passphrase is the wrong tool

Every goal the rotation has — a separate chain, one-shot addresses, keeping the main account's external chain clean — is a question of where in the BIP-32 tree to derive, and all such choices remain reachable from the mnemonic alone. A BIP-39 passphrase changes the root; it exists for a secret the user holds. Using it for a secret only the app holds turns the user's backup into a partial backup without telling them. It adds no privacy (the main account's transparent xpub is never exported, and privacy lives on the shielded side), only unrecoverability.

The header comment itself says the approach is a placeholder "if standard would get established"; that standard already exists.

Proposed fix

  • Derive disposable addresses under the user's own seed on the ZIP 316 rev 1 ephemeral chain, m/44'/133'/account'/2/n, which was reserved precisely for one-shot transparent addresses and which Zashi and other ZIP-316 wallets already scan on restore. (…/1/n, the ordinary change chain, would also be found by every generic BIP-44 recovery tool.) zkool already syncs the rotation account with a gap limit, so most of the machinery exists.
  • Sweep or at least spend sub-threshold balances when the user asks; never leave coins that neither the app nor the user will ever move.
  • Until the derivation changes: show the computed passphrase (or the recipe) on the backup/seed screen, and document it on docs.cakewallet.com, so existing users can recover their disposable-address funds without reading this repository.
  • Migration: keep scanning the legacy :tgen: account for existing wallets and sweep it into the shielded pool.

Environment

Cake Wallet (iOS and Android), ZEC wallet created in-app, mainnet; address verified against cw_zcash at dev HEAD.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions