waffles_solana_zk_token_sdk/sigma_proofs/
pubkey_proof.rs

1//! The public-key (validity) proof system.
2//!
3//! A public-key proof is defined with respect to an ElGamal public key. The proof certifies that a
4//! given public key is a valid ElGamal public key (i.e. the prover knows a corresponding secret
5//! key). To generate the proof, a prover must prove the secret key for the public key.
6//!
7//! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect
8//! zero-knowledge in the random oracle model.
9
10#[cfg(not(target_os = "solana"))]
11use {
12    crate::encryption::{
13        elgamal::{ElGamalKeypair, ElGamalPubkey},
14        pedersen::H,
15    },
16    rand::rngs::OsRng,
17    zeroize::Zeroize,
18};
19use {
20    crate::{
21        errors::ProofVerificationError, sigma_proofs::errors::PubkeyValidityProofError,
22        transcript::TranscriptProtocol,
23    },
24    arrayref::{array_ref, array_refs},
25    curve25519_dalek::{
26        ristretto::{CompressedRistretto, RistrettoPoint},
27        scalar::Scalar,
28        traits::{IsIdentity, VartimeMultiscalarMul},
29    },
30    merlin::Transcript,
31};
32
33/// Public-key proof.
34///
35/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
36#[allow(non_snake_case)]
37#[derive(Clone)]
38pub struct PubkeySigmaProof {
39    Y: CompressedRistretto,
40    z: Scalar,
41}
42
43#[allow(non_snake_case)]
44#[cfg(not(target_os = "solana"))]
45impl PubkeySigmaProof {
46    /// Public-key proof constructor.
47    ///
48    /// The function does *not* hash the public key and ciphertext into the transcript. For
49    /// security, the caller (the main protocol) should hash these public key components prior to
50    /// invoking this constructor.
51    ///
52    /// This function is randomized. It uses `OsRng` internally to generate random scalars.
53    ///
54    /// This function panics if the provided keypair is not valid (i.e. secret key is not
55    /// invertible).
56    ///
57    /// * `elgamal_keypair` = The ElGamal keypair that pertains to the ElGamal public key to be
58    /// proved
59    /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
60    pub fn new(elgamal_keypair: &ElGamalKeypair, transcript: &mut Transcript) -> Self {
61        transcript.pubkey_proof_domain_sep();
62
63        // extract the relevant scalar and Ristretto points from the input
64        let s = elgamal_keypair.secret.get_scalar();
65
66        assert!(s != &Scalar::zero());
67        let s_inv = s.invert();
68
69        // generate a random masking factor that also serves as a nonce
70        let mut y = Scalar::random(&mut OsRng);
71        let Y = (&y * &(*H)).compress();
72
73        // record masking factors in transcript and get challenges
74        transcript.append_point(b"Y", &Y);
75        let c = transcript.challenge_scalar(b"c");
76
77        // compute masked secret key
78        let z = &(&c * s_inv) + &y;
79
80        y.zeroize();
81
82        Self { Y, z }
83    }
84
85    /// Public-key proof verifier.
86    ///
87    /// * `elgamal_pubkey` - The ElGamal public key to be proved
88    /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
89    pub fn verify(
90        self,
91        elgamal_pubkey: &ElGamalPubkey,
92        transcript: &mut Transcript,
93    ) -> Result<(), PubkeyValidityProofError> {
94        transcript.pubkey_proof_domain_sep();
95
96        // extract the relvant scalar and Ristretto points from the input
97        let P = elgamal_pubkey.get_point();
98
99        // include Y to transcript and extract challenge
100        transcript.validate_and_append_point(b"Y", &self.Y)?;
101        let c = transcript.challenge_scalar(b"c");
102
103        // check that the required algebraic condition holds
104        let Y = self
105            .Y
106            .decompress()
107            .ok_or(ProofVerificationError::Deserialization)?;
108
109        let check = RistrettoPoint::vartime_multiscalar_mul(
110            vec![&self.z, &(-&c), &(-&Scalar::one())],
111            vec![&(*H), P, &Y],
112        );
113
114        if check.is_identity() {
115            Ok(())
116        } else {
117            Err(ProofVerificationError::AlgebraicRelation.into())
118        }
119    }
120
121    pub fn to_bytes(&self) -> [u8; 64] {
122        let mut buf = [0_u8; 64];
123        buf[..32].copy_from_slice(self.Y.as_bytes());
124        buf[32..64].copy_from_slice(self.z.as_bytes());
125        buf
126    }
127
128    pub fn from_bytes(bytes: &[u8]) -> Result<Self, PubkeyValidityProofError> {
129        if bytes.len() != 64 {
130            return Err(ProofVerificationError::Deserialization.into());
131        }
132
133        let bytes = array_ref![bytes, 0, 64];
134        let (Y, z) = array_refs![bytes, 32, 32];
135
136        let Y = CompressedRistretto::from_slice(Y);
137        let z = Scalar::from_canonical_bytes(*z).ok_or(ProofVerificationError::Deserialization)?;
138
139        Ok(PubkeySigmaProof { Y, z })
140    }
141}
142
143#[cfg(test)]
144mod test {
145    use {
146        super::*,
147        solana_sdk::{pubkey::Pubkey, signature::Keypair},
148    };
149
150    #[test]
151    fn test_pubkey_proof_correctness() {
152        // random ElGamal keypair
153        let keypair = ElGamalKeypair::new_rand();
154
155        let mut prover_transcript = Transcript::new(b"test");
156        let mut verifier_transcript = Transcript::new(b"test");
157
158        let proof = PubkeySigmaProof::new(&keypair, &mut prover_transcript);
159        assert!(proof
160            .verify(&keypair.public, &mut verifier_transcript)
161            .is_ok());
162
163        // derived ElGamal keypair
164        let keypair = ElGamalKeypair::new(&Keypair::new(), &Pubkey::default()).unwrap();
165
166        let mut prover_transcript = Transcript::new(b"test");
167        let mut verifier_transcript = Transcript::new(b"test");
168
169        let proof = PubkeySigmaProof::new(&keypair, &mut prover_transcript);
170        assert!(proof
171            .verify(&keypair.public, &mut verifier_transcript)
172            .is_ok());
173    }
174}