Motivation
ZIP 2005 makes new Orchard-protocol (Ironwood) notes quantum-recoverable: if the discrete-log assumption on the curves Zcash uses is broken, funds committed in the recoverable format can be moved into a future post-quantum protocol by proving —in zero knowledge— that you know the secret a recovery is entitled to, without revealing anything a discrete-log break would let an attacker exploit.
This issue asks a similar question for the transparent protocol: which transparent UTXOs could be recovered the same way? The point is to scope what a transparent recovery statement would have to cover, and to identify what is recoverable in principle versus what is fundamentally exposed.
Scope
Restrict attention to UTXOs that are:
- sent to a representable t-address (i.e. a
t1... P2PKH address or a t3... P2SH address — these are the only transparent address forms Zcash defines); and
- spendable by
zcashd or librustzcash; and
- have not had their ECDSA public keys revealed to a potential adversary.
Note that as long as we can still only spend these UTXOs via the existing transparent protocol, the form of quantum recoverability obtained is weaker than for Ironwood: once the public key(s) are exposed, any funds subsequently sent to that address are vulnerable. For Ironwood, spending using the existing protocol does not expose keys to attack.
Note that this analysis also does not consider attacks against $\mathsf{Hash160}$ via Grover's algorithm, although there is reason to believe these are not likely to be practical on the same timescale as attacks using Shor's algorithm.
Script-type numbering below refers to the standard types in qa/rpc-tests/decodescript.py.
Standard output (scriptPubKey) types
1. P2PK and 3. bare multisig — out of scope: neither has a t-address, and neither is spendable at the top level in a way reachable via an address. zcashd's IsMine will spend a top-level P2PK output if it holds the key, but there is no way to send to one via an address. Bare multisig is explicitly never spendable at the top level — IsMineInner breaks when sigversion == TOP.
5. OP_RETURN (TX_NULL_DATA) — out of scope: provably unspendable, no funds at risk.
2. P2PKH — OP_DUP OP_HASH160 $\langle\mathsf{PubKeyHash}\rangle$ OP_EQUALVERIFY OP_CHECKSIG. Recoverable. Prove
$$\mathsf{ZKPoK} { (\mathsf{privkey}) : \mathsf{PubKeyHash} = \mathsf{Hash160}(\mathsf{PubKeyOf}(\mathsf{privkey})) }$$
i.e. knowledge of a private key whose public key hashes to the committed $\mathsf{PubKeyHash}$. Note that $\mathsf{Hash160}(\bullet) = \mathsf{RIPEMD160}(\mathsf{SHA256}(\bullet))$ commits to a specific public-key encoding (compressed vs. uncompressed), so the recovery statement must account for that encoding.
4. P2SH — OP_HASH160 $\langle\mathsf{Hash160}(\mathsf{redeemScript})\rangle$ OP_EQUAL. The on-chain output commits only to $\mathsf{Hash160}(\mathsf{redeemScript})$; the $\mathsf{redeemScript}$ itself is revealed only when spending. The $\mathsf{redeemScript}$ forms that zcashd or librustzcash will spend (one level deep; nested P2SH is not standard) are:
-
P2SH-wrapped P2PKH ($\mathsf{redeemScript}$ = a P2PKH script): recoverable, for the same reason as (2).
-
P2SH-wrapped P2PK ($\mathsf{redeemScript} = \langle\mathsf{pubkey}\rangle$
OP_CHECKSIG): recoverable. Unlike a top-level P2PK (case 1), the P2SH wrapper keeps the public key hidden until spend, so it is recoverable in the same way as P2SH-multisig below. The circuit must compute the $\mathsf{redeemScript}$ hash so that the public key is never exposed, plus a ZKPoK of the private key for that public key.
-
P2SH-wrapped threshold multisig ($\mathsf{redeemScript} = \langle m\rangle \langle\mathsf{pubkey}_1\rangle \ldots \langle\mathsf{pubkey}_n\rangle \langle n\rangle$
OP_CHECKMULTISIG): recoverable in principle. Because the $\mathsf{redeemScript}$ carries the public keys in the clear, recovery cannot reveal it; the $\mathsf{Hash160}$ of the $\mathsf{redeemScript}$ must be computed in zero knowledge to protect the keys.
This makes the statement sensitive to the $\mathsf{redeemScript}$ being exactly this form (the circuit reconstructs the precise script bytes and hashes them). It is feasible but needs either recursive proofs or a large circuit (SHA-256 + RIPEMD-160 over the script, plus $m$ secp256k1 scalar multiplications).
Recoverable vs. exposed — summary
| Output type |
t-address |
Spendable by |
Recoverable? |
| P2PK (top-level) |
— |
zcashd (no send path) |
n/a (out of scope) |
| bare multisig (top-level) |
— |
never (top level) |
n/a (out of scope) |
| OP_RETURN |
— |
unspendable |
n/a (no funds) |
| P2PKH |
t1... |
zcashd, librustzcash |
yes — ZKPoK of privkey for the committed hash |
| P2SH -> P2PKH |
t3... |
zcashd |
yes — as P2PKH |
| P2SH -> P2PK |
t3... |
zcashd |
yes — in-circuit redeemScript hashing + ZKPoK |
| P2SH -> multisig |
t3... |
zcashd, librustzcash |
yes, in principle — in-circuit redeemScript hashing + ZKPoK of $m$ of $n$
|
Implementation notes
zcashd vs. librustzcash divergence for P2SH. The librustzcash transaction builder currently constructs only P2SH-multisig spends (p2sh_input_serialized_len: "currently only P2MS-in-P2SH is supported"). P2SH-wrapped P2PKH and P2PK are spendable via zcashd's wallet, not the librustzcash builder — so they are in scope under "spendable by zcashd or librustzcash", but only zcashd actually builds those spends today. (The librustzcash PCZT signer does recognize PubKey, PubKeyHash, and MultiSig redeemScript kinds via zcash_script's solver::ScriptKind.)
Open questions
- For P2SH-multisig, is the cost (recursive proofs or a large circuit to take into account the in-circuit $\mathsf{Hash160}(\mathsf{redeemScript})$ and $m$ secp256k1 scalar multiplications) justified, given how transparent multisig funds are actually used? It could also be reasonable to just classify these funds as not recoverable, and require transparent multisig holders to spend them into P2PKH or a FROST-based multisig in the Ironwood (or Tachyon) pool.
- Are P2SH-wrapped P2PKH or P2SH-wrapped P2PK actually used at all?
Next steps
- Survey the chain to see what proportion of funds are currently recoverable and in each category. This includes Ironwood funds if Ironwood has already activated. Note that funds held in P2SH addresses can't be definitively classified as recoverable based on public information. (They can be classified as non-recoverable if the corresponding script has been seen.)
- Estimate the cost of the circuit to spend P2PKH and P2SH-wrapped-P2PKH funds.
- Deploy that circuit.
Drafted with the assistance of Claude Opus 4.8 (1M context).
Motivation
ZIP 2005 makes new Orchard-protocol (Ironwood) notes quantum-recoverable: if the discrete-log assumption on the curves Zcash uses is broken, funds committed in the recoverable format can be moved into a future post-quantum protocol by proving —in zero knowledge— that you know the secret a recovery is entitled to, without revealing anything a discrete-log break would let an attacker exploit.
This issue asks a similar question for the transparent protocol: which transparent UTXOs could be recovered the same way? The point is to scope what a transparent recovery statement would have to cover, and to identify what is recoverable in principle versus what is fundamentally exposed.
Scope
Restrict attention to UTXOs that are:
t1...P2PKH address or at3...P2SH address — these are the only transparent address forms Zcash defines); andzcashdorlibrustzcash; andNote that as long as we can still only spend these UTXOs via the existing transparent protocol, the form of quantum recoverability obtained is weaker than for Ironwood: once the public key(s) are exposed, any funds subsequently sent to that address are vulnerable. For Ironwood, spending using the existing protocol does not expose keys to attack.
Note that this analysis also does not consider attacks against$\mathsf{Hash160}$ via Grover's algorithm, although there is reason to believe these are not likely to be practical on the same timescale as attacks using Shor's algorithm.
Script-type numbering below refers to the standard types in
qa/rpc-tests/decodescript.py.Standard output (
scriptPubKey) types1.P2PK and3.bare multisig — out of scope: neither has a t-address, and neither is spendable at the top level in a way reachable via an address.zcashd'sIsMinewill spend a top-level P2PK output if it holds the key, but there is no way to send to one via an address. Bare multisig is explicitly never spendable at the top level —IsMineInnerbreaks whensigversion == TOP.5.OP_RETURN (TX_NULL_DATA) — out of scope: provably unspendable, no funds at risk.2.P2PKH —OP_DUP OP_HASH160OP_EQUALVERIFY OP_CHECKSIG. Recoverable. Provei.e. knowledge of a private key whose public key hashes to the committed$\mathsf{PubKeyHash}$ . Note that $\mathsf{Hash160}(\bullet) = \mathsf{RIPEMD160}(\mathsf{SHA256}(\bullet))$ commits to a specific public-key encoding (compressed vs. uncompressed), so the recovery statement must account for that encoding.
4.P2SH —OP_HASH160OP_EQUAL. The on-chain output commits only tozcashdorlibrustzcashwill spend (one level deep; nested P2SH is not standard) are:OP_CHECKSIG): recoverable. Unlike a top-level P2PK (case 1), the P2SH wrapper keeps the public key hidden until spend, so it is recoverable in the same way as P2SH-multisig below. The circuit must compute theOP_CHECKMULTISIG): recoverable in principle. Because theThis makes the statement sensitive to the$\mathsf{redeemScript}$ being exactly this form (the circuit reconstructs the precise script bytes and hashes them). It is feasible but needs either recursive proofs or a large circuit (SHA-256 + RIPEMD-160 over the script, plus $m$ secp256k1 scalar multiplications).
Recoverable vs. exposed — summary
t1...t3...t3...t3...Implementation notes
zcashdvs.librustzcashdivergence for P2SH. Thelibrustzcashtransaction builder currently constructs only P2SH-multisig spends (p2sh_input_serialized_len: "currently only P2MS-in-P2SH is supported"). P2SH-wrapped P2PKH and P2PK are spendable viazcashd's wallet, not thelibrustzcashbuilder — so they are in scope under "spendable byzcashdorlibrustzcash", but onlyzcashdactually builds those spends today. (ThelibrustzcashPCZT signer does recognizePubKey,PubKeyHash, andMultiSigredeemScript kinds viazcash_script'ssolver::ScriptKind.)Open questions
Next steps
Drafted with the assistance of Claude Opus 4.8 (1M context).