|
| 1 | +//! Example: OR-proofs. |
| 2 | +//! |
| 3 | +//! This example demonstrates disjunctive proofs: proving you satisfy ONE of multiple conditions |
| 4 | +//! without revealing which. For instance, proving you own either address A or address B, |
| 5 | +//! without revealing which address is yours. |
| 6 | +//! |
| 7 | +//! The two statements are expressed over a group \mathbb{G} of prime order $p$, where the discrete logarithm problem is hard. |
| 8 | +//! In this group, the prover wishes to convince a verifier that it knows a secret scalar $x \in \mathbb{Z}_p$ |
| 9 | +//! such that *at least one* of the following statements holds: |
| 10 | +//! |
| 11 | +//! 1. **Discrete Logarithm (DLog):** |
| 12 | +//! |
| 13 | +//! $$X_1 = x_1 \cdot G$$ |
| 14 | +//! |
| 15 | +//! 2. **Discrete Log Equality (DLEQ):** |
| 16 | +//! |
| 17 | +//! $$X_2 = x_2 \cdot G \quad \text{and} \quad Y_2 = x_2 \cdot H$$ |
| 18 | +//! |
| 19 | +//! For these statements, the elements $G, H, X_1, X_2, Y_2$ are all publicly available to any verifier. |
| 20 | +//! |
| 21 | +//! In our specific case, we will consider that the prover only knows a witness for the second statement. However, |
| 22 | +//! he will prove *the disjunction* of the two statements, using an OR-composition of Sigma protocols. |
| 23 | +//! This proof reveals nothing about *which* statement and witness has been used by the prover. |
| 24 | +//! |
| 25 | +//! --- |
| 26 | +//! |
| 27 | +//! In `sigma-rs`, this is implemented using three core abstractions: |
| 28 | +//! |
| 29 | +//! 1. [`LinearRelation`] — describes the *morphism* (algebraic relation) between secret scalars and group elements. |
| 30 | +//! This forms the mathematical statement of the protocol. In our case, $X_1 = x \cdot G \cup X_2 = x_2 \cdot G \quad \text{and} \quad Y_2 = x_2 \cdot H$ |
| 31 | +//! |
| 32 | +//! 2. [`Protocol`] — defines the interactive Sigma protocol for the given morphism. |
| 33 | +//! This handles the commit-challenge-response structure of the protocol, following the standard Sigma protocol flow: |
| 34 | +//! - P → V: commit |
| 35 | +//! - V → P: challenge |
| 36 | +//! - P → V: response |
| 37 | +//! |
| 38 | +//! 3. [`NISigmaProtocol`] — wraps the interactive protocol using the Fiat-Shamir transformation, |
| 39 | +//! converting it to a *non-interactive zero-knowledge proof* (NIZK) by deriving the challenge |
| 40 | +//! from a transcript hash using a [`Codec`] (here, [`ShakeCodec`]). |
| 41 | +//! |
| 42 | +//! The resulting proof is non-interactive, zero-knowledge, and secure in the random oracle model. |
| 43 | +//! |
| 44 | +//! --- |
| 45 | +//! |
| 46 | +//! This example uses the Ristretto group from `curve25519-dalek`, a prime-order group designed for security and |
| 47 | +//! compatibility with zero-knowledge protocols. |
| 48 | +use curve25519_dalek::ristretto::RistrettoPoint; |
| 49 | +use curve25519_dalek::scalar::Scalar; |
| 50 | +use group::{Group, GroupEncoding}; |
| 51 | +use rand::rngs::OsRng; |
| 52 | +use sigma_rs::{ |
| 53 | + LinearRelation, |
| 54 | + codec::ShakeCodec, |
| 55 | + composition::{Protocol, ProtocolWitness}, |
| 56 | + fiat_shamir::NISigmaProtocol, |
| 57 | +}; |
| 58 | + |
| 59 | +type G = RistrettoPoint; |
| 60 | + |
| 61 | +/// Construct the relation `X = x·G` and return it along with the witness `x`. |
| 62 | +/// - `x` is a secret scalar in $\mathbb{Z}_p$. |
| 63 | +/// - `G`, `X` are elements in a prime-order group $\mathbb{G}$. |
| 64 | +#[allow(non_snake_case)] |
| 65 | +pub fn discrete_logarithm<G: Group + GroupEncoding>( |
| 66 | + x: G::Scalar, |
| 67 | +) -> (LinearRelation<G>, Vec<G::Scalar>) { |
| 68 | + let mut morphism = LinearRelation::<G>::new(); |
| 69 | + |
| 70 | + // Allocate symbolic variables for our relation |
| 71 | + let var_x = morphism.allocate_scalar(); |
| 72 | + let var_G = morphism.allocate_element(); |
| 73 | + |
| 74 | + // Define the constraint: `X = x * G` |
| 75 | + let var_X = morphism.allocate_eq(var_x * var_G); |
| 76 | + |
| 77 | + // Assign concrete values |
| 78 | + morphism.assign_element(var_G, G::generator()); |
| 79 | + morphism.compute_image(&[x]).unwrap(); |
| 80 | + |
| 81 | + // Verify: X = x * G |
| 82 | + let X = morphism.morphism.group_elements.get(var_X).unwrap(); |
| 83 | + assert_eq!(X, G::generator() * x); |
| 84 | + |
| 85 | + (morphism, vec![x]) |
| 86 | +} |
| 87 | + |
| 88 | +/// Construct the relation `(X = x·G, Y = x·H)` and return it along with the witness `x`. |
| 89 | +/// This represents a DLEQ (discrete log equality) statement between two basepoints. |
| 90 | +/// - `x` is a secret scalar in $\mathbb{Z}_p$. |
| 91 | +/// - `G`, `H`, `X`, `Y` are elements in a prime-order group $\mathbb{G}$. `G` in particular is a fixed generator. |
| 92 | +#[allow(non_snake_case)] |
| 93 | +pub fn dleq<G: Group + GroupEncoding>(x: G::Scalar, H: G) -> (LinearRelation<G>, Vec<G::Scalar>) { |
| 94 | + let mut morphism = LinearRelation::<G>::new(); |
| 95 | + |
| 96 | + // Allocate symbolic variables |
| 97 | + let var_x = morphism.allocate_scalar(); |
| 98 | + let [var_G, var_H] = morphism.allocate_elements(); |
| 99 | + |
| 100 | + // Define the constraints: X = x * G, Y = x * H |
| 101 | + let _var_X = morphism.allocate_eq(var_x * var_G); |
| 102 | + let _var_Y = morphism.allocate_eq(var_x * var_H); |
| 103 | + |
| 104 | + // Assign concrete values |
| 105 | + morphism.assign_elements([(var_G, G::generator()), (var_H, H)]); |
| 106 | + morphism.compute_image(&[x]).unwrap(); |
| 107 | + |
| 108 | + // Verify: X = x * G and Y = x * H |
| 109 | + let X = morphism.morphism.group_elements.get(_var_X).unwrap(); |
| 110 | + let Y = morphism.morphism.group_elements.get(_var_Y).unwrap(); |
| 111 | + assert_eq!(X, G::generator() * x); |
| 112 | + assert_eq!(Y, H * x); |
| 113 | + |
| 114 | + (morphism, vec![x]) |
| 115 | +} |
| 116 | + |
| 117 | +#[allow(non_snake_case)] |
| 118 | +fn main() { |
| 119 | + let mut rng = OsRng; |
| 120 | + |
| 121 | + // Setup: Create two relations |
| 122 | + // Relation 1: DLog (we don't know a witness for this) |
| 123 | + let _x1 = Scalar::random(&mut rng); |
| 124 | + let (rel1, _) = discrete_logarithm::<G>(_x1); |
| 125 | + |
| 126 | + // Relation 2: DLEQ (we DO know the witness) |
| 127 | + let x2 = Scalar::random(&mut rng); |
| 128 | + let H = G::random(&mut rng); |
| 129 | + let (rel2, witness2) = dleq::<G>(x2, H); |
| 130 | + |
| 131 | + // Compose into OR protocol |
| 132 | + |
| 133 | + // Wrap each relation into a Sigma protocol |
| 134 | + let proto1 = Protocol::from(rel1); |
| 135 | + let proto2 = Protocol::from(rel2); |
| 136 | + |
| 137 | + // Compose both protocols using logical OR |
| 138 | + let composed = Protocol::Or(vec![proto1, proto2]); |
| 139 | + |
| 140 | + // Declare the known witness for the second protocol (index = 1) |
| 141 | + let witness = ProtocolWitness::Or(1, vec![ProtocolWitness::Simple(witness2)]); |
| 142 | + |
| 143 | + // Generate and verify proof |
| 144 | + // Make it non-interactive via Fiat-Shamir |
| 145 | + let nizk = NISigmaProtocol::<_, ShakeCodec<G>>::new(b"or_proof_example", composed); |
| 146 | + |
| 147 | + // Generate proof (proving we know witness for statement 2, but not revealing which) |
| 148 | + let proof = nizk |
| 149 | + .prove_batchable(&witness, &mut rng) |
| 150 | + .expect("Proof generation should succeed"); |
| 151 | + |
| 152 | + // Verify the proof |
| 153 | + let verified = nizk.verify_batchable(&proof).is_ok(); |
| 154 | + println!("OR-proof verified: {verified}"); |
| 155 | +} |
0 commit comments