Note:All the math in this answer assumes
- prime order finite field with generator
G
- Capital letters are group elements (EG:
P,Q)
- lowercase letters are scalars. (EG:
s,t)
- Additive notation.
A non interactive zero knowledge proof can be forged if Peggy is allowed to modify random oracle outputs. In the case of the Schnorr identification protocol if Peggy can change e to a value of her choosing she can trivially find an (R,e) pair that satisfy the verification equation.
To provide deniability, a public coin protocol can be made deniable if Victor pre-commits to all his random challenge values. Taking the Schnorr identification protocol as an example, if Victor commits to a challenge e before the protocol starts, he can't choose it deterministically based on Peggy's initial statement R so he cannot apply the Fiat-Shamir heuristic to generate a non-interactive proof.
Deniable (mostly) non-interactive proofs
For more complex multi-round proof systems, efficiency can be improved by having Peggy produce a non-interactive proof that is easy to forge unless Victor keeps a pre-commited challenge value secret. We take a function for generating non-interactive zero knowledge proofs prove(input_data)--> proof and modify it prove_deniable(salt,input_data,T[n])-->proof. This function takes two additional parameters, a salt value to be added to the beginning of the transcript and a vector T[n] of values which modify random oracle outputs allowing forgery. In the non-interactive Schnorr proof where R=e*Y+s*G the challenge of e=SHA256(Y,R) or similar becomes e=SHA256(salt,Y,R)+T[0] to depend on the salt and allow modification using the first value in the T[n] array.
The interactive protocol operates as follows:
- Victor: generate random
salt
- V-->P:
commit(salt)
- Peggy: generate random list of values
T[n]
- P-->V:
commit(T[n])
- V-->P:
salt
- Peggy: compute
proof=prove_deniable(salt,input_data,T[n])
- P-->V:
T[n],proof
Victor's salt value is added to the beginning of the NIZKP transcript and influences all random oracle outputs. Peggy must choose and commit to her T[n] values before knowing this and so cannot forge the NIZKP. If Victor and Peggy collude, or if a forger creates a forged transcript of such a protocol, they can choose T[n] after choosing the salt value to create a forgery.
Designated verifier proofs
Designated Verifier proofs do something similar but non-interactively. A trapdoor function based on a public key held by Victor is used to allow the holder of the private key to generate T[n] independently of the salt, which is otherwise dependent on the chosen T[n] values.
Consider a Pedersen commitment C=m*G+r*Y where Y=x*G is Victor's public key. Victor can trivially open a commitment to reveal any message but Peggy who does not know x cannot.
Peggy generates m=Hash(T[n]) for a randomly chosen T[n] vector, salt = C = m*G+r*Y for a random r, and proof=prove_deniable(salt,input_data,T[n]) and sends Victor the tuple (T[n],r,proof).
Victor can choose a commitment value C=k*G find an m=Hash(T[n]) that creates a forged proof, then calculate r=(k-m)/x that opens the commitment to the chosen value m.
So either the proof is honest or Victor created a forgery.
To prevent verification by third parties who might trust Victor not to forge a proof, assuming we take the "knowledge of exponent assumption"(IE:ECDH is secure), Peggy can do something like a DH key exchange to prove knowledge of r. For a commitment C=m*G+r*Y, she calculates P=r*G sending (C,m,P) to Victor. Victor finds the value of the r*Y term in the above equation r*Y = Q = C-m*G and checks that Q = P*x.
Note: Q = P*x --> (r*Y)=(r*G)*x --> r*x*G = r*x*G.
Short lived proofs based on verifiable delay functions
Similar constructions are also possible using verifiable delay functions. This uses a trusted random number generating beacon whose output must be run through a VDF requiring n sequential computation steps to forge the proof. If Victor sees such a proof before anyone could have performed the computation on that beacon value, he knows the proof was not forged.
Reducing implementation effort by chaining deniable and non-deniable proofs
Modifying a complex proof to be deniable in the above way is hard. It's much easier from an implementation standpoint to deniably prove a simpler statement (EG:this Pedersen commitment opens to m, I know x such that P=x*H, etc.). If the values used in the complex proof are homomorphic this can be used to make that proof deniable.
Instead of doing a range proof that 0≤x<2^64 for the Pedersen commitment C=x*G+r*H prove knowledge of p such that P=p*H deniably and do the range proof on C'=C+P. Without knowing P was generated honestly the range proof on C' is worthless. This makes re-using existing proof code much easier. I wouldn't want to mess around with the code for bulletproofs for example but the above is very simple to implement.