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)
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.