This repository contains the source code and necessary files to implement
verifiable encryption using Halo2.
It lets a prover convince a verifier of some property of a message m
while the message itself is kept private inside an ElGamal ciphertext C.
The prover commits to m by publishing C = ElGamal.Enc(pk, p_m), where
p_m is the curve-point encoding of m. A Halo2 zk-SNARK then proves
that C is a well-formed encryption of p_m = Encode(m; r_encode), and
(in the second circuit) that m is additionally the private key of a
DSA public key pk_dsa = [m]G. Fiat-Shamir is instantiated with Blake2b.
- Proving system: Halo2 (PLONK-ish) from the
QED-it/halo2fork, branchverifiable-encryption. - Curves: the Pasta cycle (
pallas/vesta). Proofs are overvesta; the circuit field ispallas::Base(=Fp); ElGamal and point encoding live on thepallascurve. - Circuit size:
K = 11(i.e.2^11rows) for both circuits. - Transcript:
Blake2bWrite/Blake2bReadwithChallenge255.
src/lib.rs— crate root; re-exportsadd_sub_mul,encode,elgamal,constants; keepscircuitsmodule-private.src/circuits/verifiable_encryption.rs— Task 1 circuit (Encode + ElGamal, no relation).src/circuits/verifiable_encryption_with_relation.rs— Task 2 circuit (Task 1 + DSA key relation).src/add_sub_mul/chip.rs— minimal add / sub / mul custom chip used to enforce the affine encoding equationp_m.x = r_encode + m.src/elgamal/elgamal.rs— raw EC-ElGamal keygen, encrypt, decrypt.src/elgamal/extended_elgamal.rs— encode-then-encrypt / decrypt-then-decode wrapper; defines the publicDataInTransmit { ct, r_encode }.src/encode/encode.rs— scalar → curve-point encoder (rejection sampling ony^2 = x^3 + 5).src/encode/utf8.rs— helpers to pack UTF-8 strings intopallas::Base.src/constants/fixed_bases.rs— fixed bases for the Halo2EccChip.
src/circuits/verifiable_encryption.rs
implements a system for the prover to prove to the verifier that she
knows a message m that encrypts to a ciphertext C.
[doc, Section 3.2]
The circuit proves:
Encode(m; r_encode) = p_m, checked asp_m.x = r_encode + m(the curve equationp_m.x^3 + 5 = p_m.y^2is already enforced implicitly byNonIdentityPoint::new).ct_1 = [r_enc]G, withGthepallasgenerator.ct_2 = p_m + [r_enc] · pk_elgamal.
Private inputs: m, p_m, r_enc.
Public instance (7 rows): [0, ct_1.x, ct_1.y, ct_2.x, ct_2.y, pk_elgamal.x, pk_elgamal.y].
src/circuits/verifiable_encryption_with_relation.rs
implements a system for the prover to prove to the verifier that she
knows a message m that encrypts to a ciphertext C, and that
m is the private key of a digital-signature keypair.
[doc, Section 3.3]
The circuit reuses Task 1's configuration and check_encryption, and
adds the extra constraint:
pk_dsa = [m]G, wheremis reinterpreted as a scalar.
Public instance (9 rows): the 7 rows from Task 1, followed by
pk_dsa.x (row 7) and pk_dsa.y (row 8).
Both circuits follow the same pipeline in their tests:
Params::new(K)— SRS / commitment parameters forK = 11.- Build circuit: call
create_circuit(m, elgamal_keypair), which runsextended_elgamal_encryptoff-circuit to obtainp_m,r_encode,r_enc, asserts round-trip decryption, and wraps everything in the circuit struct. - Build instance:
to_halo2_instance()lays out the public-input vector. plonk::keygen_vk→plonk::keygen_pk.plonk::create_proofwithBlake2bWritetranscript.plonk::verify_proofwithSingleVerifierand a matchingBlake2bReadtranscript.- Assert
proof.len() == CircuitCost::measure(K, circuit).proof_size(instance.len()).
Round-trip test (splits a sample string into 31-byte blocks and proves/verifies each one):
cargo test --package halo2_verifiable_encryption --lib circuits::verifiable_encryption::tests::round_tripRound-trip (positive) test:
cargo test --package halo2_verifiable_encryption --lib circuits::verifiable_encryption_with_relation::tests::round_tripNegative test — tamper with the public dsa_public_key after proving
and expect verification to fail:
cargo test --package halo2_verifiable_encryption --lib circuits::verifiable_encryption_with_relation::tests::negative_testNegative test — generate the proof from a witness whose DSA private key differs from the one implied by the public instance and expect verification to fail:
cargo test --package halo2_verifiable_encryption --lib circuits::verifiable_encryption_with_relation::tests::negative_witness_testThe encode and elgamal modules ship their own 1000-iteration
round-trip tests:
cargo test --package halo2_verifiable_encryption --lib encode::encode::tests
cargo test --package halo2_verifiable_encryption --lib elgamal::elgamal::tests
cargo test --package halo2_verifiable_encryption --lib elgamal::extended_elgamal::testscargo test