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:
- 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.
- 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.
- 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.
- 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
- Create a ZEC wallet, note the mnemonic. Receive ZEC on a "Transparent (Disposable)" address.
- 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.
- 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.
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'sm/44'/133'/0'/0/naddresses.Consequences:
_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.Reproduction
m/44'/133'/0'/{0,1,2}/nfor n < 100 with any BIP-44 tool: the address does not appear.:tgen:<CRC32 of the mnemonic string>(standard CRC-32, decimal) to the same mnemonic and derivem/44'/133'/0'/0/0: this is the first disposable address. Verified on mainnet with a real address (match atm/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
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.: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_zcashatdevHEAD.