What do we do about legacy value pools?

This is actually the direction I would prefer to go. I agree it is more work and more risk (due to writing another new circuit), but it might be the required direction if we want to keep Sprout support in a scalable design. We’re actually lucky here that Sprout doesn’t rely on curve-specific primitives (we already leveraged this to switch to Sprout-on-Groth16). Deprecating Sapling would be more costly in a non-BLS12-381 circuit, but I’m okay with higher prover costs (minutes, not hours) for legacy redemption (and a recursive or accumulating proving system would minimise the verification cost of legacy redemptions for full nodes).

One of the benefits to the Zcash protocol’s current design is that it explicitly doesn’t support the “create a transaction now and mine it in 10 years” use case, because transactions commit to the NU they must be spent in, and we have regular NUs. So the design space for “legacy Sprout support” (or legacy support for any other Zcash pool) is only constrained by the key trees (because people back up spending keys and addresses), and the way in which note commitments and nullifiers are generated.

This is what I think a legacy Sprout could look like:

  • The Sprout commitment tree is frozen, and the chain is checkpointed. Full nodes delete all the code for handling existing Sprout commitment trees (including intra-transaction trees).
  • All Sprout commitments that were leaves of the now-frozen tree are formed into a new tree that is efficient to compute inside the new circuit.
  • A new “Sprout redemption” circuit is written. It only supports spending notes from the new fixed tree (we’d bake its root into the circuit), and uses the exact same value commitment method as Sapling to balance values (eliminating the need for intra-transaction tree logic outside the circuit).
  • The vjoinsplit section of the transaction format is deleted, replaced with a section dedicated to legacy pool redemption (ideally of any legacy pool type).
    • If TZEs are deployed then we could decouple this even further (TZEs are required to interact with the chain through a Context object, so we can tightly scope the interface to minimise its interaction with the rest of the Zcash protocol).
  • Full nodes still need to append to the Sprout nullifier set, and check for duplicates. Hopefully the nullifier component of any scaling solution we deploy could be applied here, but this is the most problematic component.

More generally, in a post-halo2 Zcash world, changing the circuit is as “easy” as any other consensus change (due to no longer requiring a new MPC for generating parameters) - still non-trivial skilled engineering work, but not impractical. I think we will see more circuit changes deployed over time, as the primitives improve, we identify new functionality we want to add to existing pools, or we deprecate older pools.

8 Likes