Skip to main content

zk402_groth16/
lib.rs

1use ark_bls12_381::{Bls12_381, Fr as ConstraintF};
2use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK};
3use ark_ec::{CurveGroup, Group};
4use ark_ed_on_bls12_381::{EdwardsAffine, EdwardsProjective, Fr as JubjubScalar};
5use ark_ff::{BigInteger, PrimeField};
6use ark_groth16::{prepare_verifying_key, Groth16, Proof, ProvingKey, VerifyingKey};
7use ark_r1cs_std::{
8    alloc::AllocVar,
9    boolean::Boolean,
10    eq::EqGadget,
11    groups::{curves::twisted_edwards::AffineVar, CurveVar},
12};
13use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
14use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError};
15use ark_std::rand::{CryptoRng, RngCore};
16use core::fmt;
17use sha2::{Digest, Sha512};
18
19type EdwardsVar =
20    AffineVar<ark_ed_on_bls12_381::JubjubConfig, ark_ed_on_bls12_381::constraints::FqVar>;
21type ZkGroth16 = Groth16<Bls12_381>;
22const SCALAR_BITS: usize = 256;
23
24#[derive(Debug)]
25pub enum Error {
26    Encoding(SerializationError),
27    Synthesis(SynthesisError),
28    Snark(ark_crypto_primitives::Error),
29}
30
31pub type Result<T> = core::result::Result<T, Error>;
32
33impl fmt::Display for Error {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            Self::Encoding(err) => write!(f, "invalid byte encoding: {err}"),
37            Self::Synthesis(err) => write!(f, "proving failed: {err}"),
38            Self::Snark(err) => write!(f, "snark error: {err}"),
39        }
40    }
41}
42
43impl std::error::Error for Error {}
44
45impl From<SerializationError> for Error {
46    fn from(err: SerializationError) -> Self {
47        Self::Encoding(err)
48    }
49}
50
51impl From<SynthesisError> for Error {
52    fn from(err: SynthesisError) -> Self {
53        Self::Synthesis(err)
54    }
55}
56
57impl From<ark_crypto_primitives::Error> for Error {
58    fn from(err: ark_crypto_primitives::Error) -> Self {
59        Self::Snark(err)
60    }
61}
62
63#[derive(Clone)]
64struct SignatureKnowledgeCircuit {
65    public_key: Option<EdwardsAffine>,
66    challenge_bits: Vec<bool>,
67    secret_key_bits: Option<Vec<bool>>,
68    nonce_bits: Option<Vec<bool>>,
69    response_bits: Option<Vec<bool>>,
70}
71
72impl ConstraintSynthesizer<ConstraintF> for SignatureKnowledgeCircuit {
73    fn generate_constraints(
74        self,
75        cs: ConstraintSystemRef<ConstraintF>,
76    ) -> core::result::Result<(), SynthesisError> {
77        let public_key = EdwardsVar::new_input(cs.clone(), || {
78            self.public_key.ok_or(SynthesisError::AssignmentMissing)
79        })?;
80
81        let challenge_bits = allocate_input_bits(cs.clone(), &self.challenge_bits)?;
82        let secret_key_bits = allocate_witness_bits(cs.clone(), self.secret_key_bits)?;
83        let nonce_bits = allocate_witness_bits(cs.clone(), self.nonce_bits)?;
84        let response_bits = allocate_witness_bits(cs, self.response_bits)?;
85
86        let base = EdwardsVar::constant(EdwardsProjective::generator());
87        let derived_public_key = base.scalar_mul_le(secret_key_bits.iter())?;
88        derived_public_key.enforce_equal(&public_key)?;
89
90        let nonce_point = base.scalar_mul_le(nonce_bits.iter())?;
91        let response_point = base.scalar_mul_le(response_bits.iter())?;
92        let challenged_public_key = public_key.scalar_mul_le(challenge_bits.iter())?;
93        let expected_response_point = nonce_point + challenged_public_key;
94
95        response_point.enforce_equal(&expected_response_point)?;
96        Ok(())
97    }
98}
99
100/// Generate a circuit-specific Groth16 proving key and verifying key.
101pub fn setup<R: RngCore + CryptoRng>(rng: &mut R) -> Result<(Vec<u8>, Vec<u8>)> {
102    let one = JubjubScalar::from_le_bytes_mod_order(&[1]);
103    let blank = SignatureKnowledgeCircuit {
104        public_key: Some(EdwardsProjective::generator().into_affine()),
105        challenge_bits: vec![false; SCALAR_BITS],
106        secret_key_bits: Some(scalar_bits_le(&one)),
107        nonce_bits: Some(scalar_bits_le(&one)),
108        response_bits: Some(scalar_bits_le(&one)),
109    };
110    let (pk, vk) = ZkGroth16::setup(blank, rng)?;
111    Ok((serialize_compressed(&pk)?, serialize_compressed(&vk)?))
112}
113
114/// Derive the compressed Jubjub public key for a raw private key byte string.
115pub fn derive_public_key(secret_key: &[u8]) -> Result<Vec<u8>> {
116    let secret = scalar_from_bytes(secret_key);
117    serialize_compressed(&(EdwardsProjective::generator() * secret).into_affine())
118}
119
120/// Create a Groth16 proof for a hidden Schnorr/EdDSA-style signature over `message`.
121///
122/// The proof public inputs are the derived public key and a challenge derived
123/// from `message`; the nonce point and response scalar are private witnesses.
124pub fn prove<R: RngCore + CryptoRng>(
125    proving_key: &[u8],
126    secret_key: &[u8],
127    message: &[u8],
128    nonce: &[u8],
129    rng: &mut R,
130) -> Result<Vec<u8>> {
131    let pk = ProvingKey::<Bls12_381>::deserialize_compressed(proving_key)?;
132    let secret = scalar_from_bytes(secret_key);
133    let nonce = scalar_from_bytes(nonce);
134    let public_key = (EdwardsProjective::generator() * secret).into_affine();
135    let challenge = challenge_scalar(&public_key, message)?;
136    let response = nonce + challenge * secret;
137
138    let circuit = SignatureKnowledgeCircuit {
139        public_key: Some(public_key),
140        challenge_bits: scalar_bits_le(&challenge),
141        secret_key_bits: Some(scalar_bits_le(&secret)),
142        nonce_bits: Some(scalar_bits_le(&nonce)),
143        response_bits: Some(scalar_bits_le(&response)),
144    };
145
146    let proof = ZkGroth16::prove(&pk, circuit, rng)?;
147    serialize_compressed(&proof)
148}
149
150/// Verify a Groth16 proof against raw public-key and message bytes.
151pub fn verify(
152    verifying_key: &[u8],
153    proof: &[u8],
154    public_key: &[u8],
155    message: &[u8],
156) -> Result<bool> {
157    let vk = VerifyingKey::<Bls12_381>::deserialize_compressed(verifying_key)?;
158    let proof = Proof::<Bls12_381>::deserialize_compressed(proof)?;
159    let public_key = EdwardsAffine::deserialize_compressed(public_key)?;
160    let challenge = challenge_scalar(&public_key, message)?;
161    let public_inputs = groth16_public_inputs(&public_key, &challenge);
162    let pvk = prepare_verifying_key(&vk);
163    Ok(ZkGroth16::verify_with_processed_vk(
164        &pvk,
165        &public_inputs,
166        &proof,
167    )?)
168}
169
170fn allocate_input_bits(
171    cs: ConstraintSystemRef<ConstraintF>,
172    bits: &[bool],
173) -> core::result::Result<Vec<Boolean<ConstraintF>>, SynthesisError> {
174    bits.iter()
175        .map(|bit| Boolean::new_input(cs.clone(), || Ok(*bit)))
176        .collect()
177}
178
179fn allocate_witness_bits(
180    cs: ConstraintSystemRef<ConstraintF>,
181    bits: Option<Vec<bool>>,
182) -> core::result::Result<Vec<Boolean<ConstraintF>>, SynthesisError> {
183    let bits = bits.ok_or(SynthesisError::AssignmentMissing)?;
184    bits.into_iter()
185        .map(|bit| Boolean::new_witness(cs.clone(), || Ok(bit)))
186        .collect()
187}
188
189fn scalar_from_bytes(bytes: &[u8]) -> JubjubScalar {
190    JubjubScalar::from_le_bytes_mod_order(bytes)
191}
192
193fn challenge_scalar(public_key: &EdwardsAffine, message: &[u8]) -> Result<JubjubScalar> {
194    let public_key_bytes = serialize_compressed(public_key)?;
195    let mut hasher = Sha512::new();
196    hasher.update(b"zk402.phase1.challenge.v1");
197    hasher.update(public_key_bytes);
198    hasher.update(message);
199    Ok(JubjubScalar::from_le_bytes_mod_order(&hasher.finalize()))
200}
201
202fn scalar_bits_le(scalar: &JubjubScalar) -> Vec<bool> {
203    let mut bits = scalar.into_bigint().to_bits_le();
204    bits.resize(SCALAR_BITS, false);
205    bits
206}
207
208fn groth16_public_inputs(public_key: &EdwardsAffine, challenge: &JubjubScalar) -> Vec<ConstraintF> {
209    let mut inputs = vec![public_key.x, public_key.y];
210    inputs.extend(
211        scalar_bits_le(challenge)
212            .into_iter()
213            .map(|bit| ConstraintF::from_le_bytes_mod_order(&[u8::from(bit)])),
214    );
215    inputs
216}
217
218fn serialize_compressed<T: CanonicalSerialize>(value: &T) -> Result<Vec<u8>> {
219    let mut bytes = Vec::with_capacity(value.compressed_size());
220    value.serialize_compressed(&mut bytes)?;
221    Ok(bytes)
222}
223
224// ProofSystem trait implementation for zk402-core integration
225pub mod proof_system {
226    use super::*;
227    use zk402_core::{ProofSystem, ProofSystemError, PublicInputs, Witness};
228
229    pub struct Groth16ProofSystem;
230
231    impl ProofSystem for Groth16ProofSystem {
232        type ProvingKey = Vec<u8>;
233        type VerifyingKey = Vec<u8>;
234
235        fn prove(
236            pk: &Self::ProvingKey,
237            witness: &Witness,
238            public_inputs: &PublicInputs,
239        ) -> core::result::Result<Vec<u8>, ProofSystemError> {
240            let message = construct_message(public_inputs);
241            let mut rng = ark_std::rand::thread_rng();
242
243            super::prove(
244                pk,
245                &witness.secret_key,
246                &message,
247                &witness.nonce_scalar,
248                &mut rng,
249            )
250            .map_err(|e| ProofSystemError::ProvingFailed(e.to_string()))
251        }
252
253        fn verify(
254            vk: &Self::VerifyingKey,
255            proof: &[u8],
256            public_inputs: &PublicInputs,
257        ) -> core::result::Result<bool, ProofSystemError> {
258            let message = construct_message(public_inputs);
259
260            super::verify(vk, proof, &public_inputs.public_key, &message)
261                .map_err(|e| ProofSystemError::VerificationFailed(e.to_string()))
262        }
263    }
264
265    fn construct_message(inputs: &PublicInputs) -> Vec<u8> {
266        // Construct message from payment parameters following Phase 1 approach
267        let mut message = Vec::new();
268        message.extend_from_slice(b"zk402.payment.v1:");
269        message.extend_from_slice(inputs.from.as_slice());
270        message.extend_from_slice(inputs.pay_to.as_slice());
271        message.extend_from_slice(inputs.amount.as_bytes());
272        message.extend_from_slice(inputs.asset.as_slice());
273        message.extend_from_slice(inputs.network.as_bytes());
274        message.extend_from_slice(&inputs.nonce);
275        message.extend_from_slice(&inputs.valid_after.to_le_bytes());
276        message.extend_from_slice(&inputs.valid_before.to_le_bytes());
277        message
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use ark_std::rand::{rngs::StdRng, SeedableRng};
284    use std::sync::OnceLock;
285
286    fn message(amount: &str, pay_to: &str) -> Vec<u8> {
287        format!("amount={amount};pay_to={pay_to}").into_bytes()
288    }
289
290    fn test_keys() -> &'static (Vec<u8>, Vec<u8>) {
291        static KEYS: OnceLock<(Vec<u8>, Vec<u8>)> = OnceLock::new();
292        KEYS.get_or_init(|| {
293            let mut rng = StdRng::seed_from_u64(1);
294            setup(&mut rng).unwrap()
295        })
296    }
297
298    fn proof_for(message: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
299        let mut rng = StdRng::seed_from_u64(1);
300        let secret_key = b"phase-one-test-secret-key";
301        let nonce = b"phase-one-test-nonce";
302        let public_key = derive_public_key(secret_key).unwrap();
303        let proof = prove(&test_keys().0, secret_key, message, nonce, &mut rng).unwrap();
304        (test_keys().1.clone(), public_key, proof)
305    }
306
307    #[test]
308    fn valid_signature_knowledge_proof_verifies() {
309        let msg = message("100", "0xabc");
310        let (vk, public_key, proof) = proof_for(&msg);
311
312        assert!(verify(&vk, &proof, &public_key, &msg).unwrap());
313    }
314
315    #[test]
316    fn tampered_amount_fails_to_verify() {
317        let (vk, public_key, proof) = proof_for(&message("100", "0xabc"));
318
319        assert!(!verify(&vk, &proof, &public_key, &message("101", "0xabc")).unwrap());
320    }
321
322    #[test]
323    fn tampered_pay_to_fails_to_verify() {
324        let (vk, public_key, proof) = proof_for(&message("100", "0xabc"));
325
326        assert!(!verify(&vk, &proof, &public_key, &message("100", "0xdef")).unwrap());
327    }
328}