New Token Holder Voting Implementation

Yes! The idea was to pass the vote specific nullifier to KS. Just looked at the source though, the nullifier appears checked in keystone firmware. (Previously I read the wrong library and thought it wasn’t: keystone3-firmware/rust/apps/zcash/src/pczt/check.rs at master · KeystoneHQ/keystone3-firmware · GitHub ).

Going to empirically test if this is the case, or if invalid signs are still permitted.

If not (as I’d assume would be the case), this changes what we have to do to enable signing from KS. I have a suggested re-design in mind, but going to spend the day writing it up thoroughly / better battle test the assumptions with actual keystone signing, and post here tomorrow. But TL;DR for it:

  • Still use existing nullifier
  • Make it clearly unspendable to the user with a second input thats clearly unspendable. (e.g. address is 0xdeadbeef or smth)
  • Make the “voting ZKP” hide the nullifier from getting revealed. (Requires Implementing blake2b in our circuit)

It’d be interesting to see how the validator checks the signature without knowing the nullifier (which is included in the sighash).

Edit: Added documentation about the workflow at GitHub - hhanh00/zcash-vote-app

1 Like

The idea is to show the sighash publicly. However we hide the nullifier for one Orchard Action Hash. We zkp prove a blake2b invocation that takes in:

  • All inputs to the action hash, except the nullifier. (As Public inputs)
  • Orchard action hash (as public input)
  • nullifier as auxilliary input

And then shows that the inputs + nullifier hash to the action hash. (And then you use the Orchard Action hash to get the sighash as normal)

You have to make a circuit that computes the tx sighash. It is difficult because of the variable length of the data. Circuits aren’t good at dealing with loops and conditionals. The tx can have multiple actions.

Sorry for delay on a new design post. We got a ZKP for the blake2b compact hash to avoid leaking the nullifier, and got the proving time fast (3s/note) on an iPhone 13. However, today we found a design that (imo) elegantly enables the original goals + removes the blake2b hashing from the circuit.

Working to writeup that variant of the scheme. Core intuition:

  • User signs a completely dummy action, not their actual notes. Its effectively a 0-value transfer, so they know they aren’t being scammed.
    • Your Keystone can’t tell if the input is actually on-chain, therefore it can sign this. (Its a perfectly valid note, just not really on-chain)
  • This action will end up “authorizing” a governance hotkey to vote on your behalf, from your keystone.
  • In the ZKP, we prove that you own some notes at the anchor height, and that they have not been spent before. These notes are owned by the same key that signed this vote.
    • We reveal gov_commitment(governance_hotkey, total_note_value), and prove validity of this commitment.
    • Furthermore, we make a new gov_nullifier for each note, and reveal this nullifier. Everyone checks uniqueness to ensure no double delegating.
  • Because were signing a dummy action, we can “make up” the rho&psi which derive the nullifier. We make rho depend on the note commitments used, and the vote ID. Therefore this signature could not get re-used for other votes/notes in the future.

Then we have a second voting circuit, for every vote. We make a merkle tree of all the gov commitments. The second vote circuit handles:

  • Hiding which gov commitment is voting
  • Proving that the vote is coming from the gov commitment’s (randomized) key
  • Choosing which split of your vote your using (for hiding your total balance)
    • We still suggest that we do a “base 2” or “base 10” decomposition of your vote.
  • Creating a unique nullifier per vote_split and gov_commitment and proposal

Working on a writeup with more details of this proposal + intended UX flows as were implementing it.

As the keystone user, how do I know what I am voting on and by how much since the KS shows a dummy transaction?