|
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. |
| 1 | +//! OR-proof composition example. |
| 2 | +
|
48 | 3 | use curve25519_dalek::ristretto::RistrettoPoint; |
49 | 4 | use curve25519_dalek::scalar::Scalar; |
50 | | -use group::{Group, GroupEncoding}; |
| 5 | +use group::Group; |
51 | 6 | use rand::rngs::OsRng; |
52 | 7 | use sigma_rs::{ |
53 | 8 | codec::ShakeCodec, |
54 | 9 | composition::{Protocol, ProtocolWitness}, |
55 | | - fiat_shamir::NISigmaProtocol, |
56 | | - LinearRelation, |
| 10 | + errors::Error, |
| 11 | + LinearRelation, NISigmaProtocol, |
57 | 12 | }; |
58 | 13 |
|
59 | 14 | type G = RistrettoPoint; |
| 15 | +type ProofResult<T> = Result<T, Error>; |
60 | 16 |
|
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 | 17 | #[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(); |
| 18 | +pub fn discrete_logarithm(x: Scalar) -> (LinearRelation<G>, Vec<Scalar>) { |
| 19 | + let mut relation = LinearRelation::<G>::new(); |
73 | 20 |
|
74 | | - // Define the constraint: `X = x * G` |
75 | | - let var_X = morphism.allocate_eq(var_x * var_G); |
| 21 | + let var_x = relation.allocate_scalar(); |
| 22 | + let var_G = relation.allocate_element(); |
| 23 | + let _var_X = relation.allocate_eq(var_x * var_G); |
76 | 24 |
|
77 | | - // Assign concrete values |
78 | | - morphism.set_element(var_G, G::generator()); |
79 | | - morphism.compute_image(&[x]).unwrap(); |
| 25 | + relation.set_element(var_G, G::generator()); |
| 26 | + relation.compute_image(&[x]).unwrap(); |
80 | 27 |
|
81 | | - // Verify: X = x * G |
82 | | - let X = morphism.linear_map.group_elements.get(var_X).unwrap(); |
83 | | - assert_eq!(X, G::generator() * x); |
84 | | - |
85 | | - (morphism, vec![x]) |
| 28 | + (relation, vec![x]) |
86 | 29 | } |
87 | 30 |
|
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 | 31 | #[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(); |
| 32 | +pub fn dleq(x: Scalar, h: G) -> (LinearRelation<G>, Vec<Scalar>) { |
| 33 | + let mut relation = LinearRelation::<G>::new(); |
99 | 34 |
|
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); |
| 35 | + let var_x = relation.allocate_scalar(); |
| 36 | + let [var_G, var_H] = relation.allocate_elements(); |
| 37 | + let _var_X = relation.allocate_eq(var_x * var_G); |
| 38 | + let _var_Y = relation.allocate_eq(var_x * var_H); |
103 | 39 |
|
104 | | - // Assign concrete values |
105 | | - morphism.set_elements([(var_G, G::generator()), (var_H, H)]); |
106 | | - morphism.compute_image(&[x]).unwrap(); |
| 40 | + relation.set_elements([(var_G, G::generator()), (var_H, h)]); |
| 41 | + relation.compute_image(&[x]).unwrap(); |
107 | 42 |
|
108 | | - // Verify: X = x * G and Y = x * H |
109 | | - let X = morphism.linear_map.group_elements.get(_var_X).unwrap(); |
110 | | - let Y = morphism.linear_map.group_elements.get(_var_Y).unwrap(); |
111 | | - assert_eq!(X, G::generator() * x); |
112 | | - assert_eq!(Y, H * x); |
113 | | - |
114 | | - (morphism, vec![x]) |
| 43 | + (relation, vec![x]) |
115 | 44 | } |
116 | 45 |
|
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 |
| 46 | +fn create_or_relations(x1: Scalar, x2: Scalar, h: G) -> (Protocol<G>, ProtocolWitness<G>) { |
| 47 | + let (rel1, _) = discrete_logarithm(x1); |
| 48 | + let (rel2, witness2) = dleq(x2, h); |
132 | 49 |
|
133 | | - // Wrap each relation into a Sigma protocol |
134 | 50 | let proto1 = Protocol::from(rel1); |
135 | 51 | let proto2 = Protocol::from(rel2); |
136 | | - |
137 | | - // Compose both protocols using logical OR |
138 | 52 | let composed = Protocol::Or(vec![proto1, proto2]); |
139 | 53 |
|
140 | | - // Declare the known witness for the second protocol (index = 1) |
141 | 54 | let witness = ProtocolWitness::Or(1, vec![ProtocolWitness::Simple(witness2)]); |
142 | 55 |
|
143 | | - // Generate and verify proof |
144 | | - // Make it non-interactive via Fiat-Shamir |
| 56 | + (composed, witness) |
| 57 | +} |
| 58 | + |
| 59 | +fn prove_or(x1: Scalar, x2: Scalar, h: G) -> ProofResult<Vec<u8>> { |
| 60 | + let mut rng = OsRng; |
| 61 | + let (composed, witness) = create_or_relations(x1, x2, h); |
| 62 | + let nizk = NISigmaProtocol::<_, ShakeCodec<G>>::new(b"or_proof_example", composed); |
| 63 | + |
| 64 | + nizk.prove_batchable(&witness, &mut rng) |
| 65 | +} |
| 66 | + |
| 67 | +fn verify_or(x1: Scalar, x2: Scalar, h: G, proof: &[u8]) -> ProofResult<()> { |
| 68 | + let (composed, _) = create_or_relations(x1, x2, h); |
145 | 69 | let nizk = NISigmaProtocol::<_, ShakeCodec<G>>::new(b"or_proof_example", composed); |
146 | 70 |
|
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"); |
| 71 | + nizk.verify_batchable(proof) |
| 72 | +} |
| 73 | + |
| 74 | +fn main() { |
| 75 | + let mut rng = OsRng; |
| 76 | + let x1 = Scalar::random(&mut rng); |
| 77 | + let x2 = Scalar::random(&mut rng); |
| 78 | + let h = G::random(&mut rng); |
| 79 | + |
| 80 | + let proof = prove_or(x1, x2, h).expect("Proof generation failed"); |
| 81 | + let verified = verify_or(x1, x2, h, &proof).is_ok(); |
151 | 82 |
|
152 | | - // Verify the proof |
153 | | - let verified = nizk.verify_batchable(&proof).is_ok(); |
154 | 83 | println!("OR-proof verified: {verified}"); |
| 84 | + println!("Proof bytes: {}", hex::encode(&proof)); |
155 | 85 | } |
0 commit comments