Skip to content

Commit 608703d

Browse files
authored
fix(examples): improve quality of examples (#36)
1 parent 1306b45 commit 608703d

File tree

6 files changed

+144
-211
lines changed

6 files changed

+144
-211
lines changed

.github/workflows/lint-fmt.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
- name: Install Rust toolchain
2222
uses: actions-rs/toolchain@v1
2323
with:
24+
toolchain: stable
2425
components: rustfmt
2526

2627
- name: Run cargo fmt
@@ -57,6 +58,7 @@ jobs:
5758
- uses: actions/checkout@v4
5859
- uses: actions-rs/toolchain@v1
5960
with:
61+
toolchain: stable
6062
profile: minimal
6163
override: true
6264
components: clippy

.github/workflows/rust.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ jobs:
1515

1616
steps:
1717
- uses: actions/checkout@v3
18+
- name: Install Rust toolchain
19+
uses: actions-rs/toolchain@v1
20+
with:
21+
toolchain: stable
22+
profile: minimal
23+
override: true
1824
- name: Build
1925
run: cargo build --verbose
2026
- name: Run tests

examples/schnorr.rs

Lines changed: 47 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,69 @@
11
//! Example: Schnorr proof of knowledge.
22
//!
33
//! This example demonstrates how to prove knowledge of a discrete logarithm using `sigma-rs`.
4-
//! A common use case is authentication: proving you know a secret key without revealing it.
54
//!
65
//! The prover convinces a verifier that it knows a secret $x$ such that: $$P = x \cdot G$$
76
//!
87
//! where $G$ is a generator of a prime-order group $\mathbb{G}$ and $P$ is a public group element.
9-
//!
10-
//! ---
11-
//!
12-
//! In `sigma-rs`, this is achieved using three core abstractions:
13-
//!
14-
//! 1. [`LinearRelation`] — describes the *morphism* (algebraic relation) between secret scalars and group elements.
15-
//! This forms the mathematical statement of the protocol. In our case, $P = x \cdot G$.
16-
//!
17-
//! 2. [`SchnorrProtocol`] — defines the interactive Sigma protocol for the given morphism.
18-
//! This handles the commit-challenge-response structure of the protocol, following the standard Sigma protocol flow:
19-
//! - P → V: commit $K = r·G$
20-
//! - V → P: challenge $c$
21-
//! - P → V: response $s = r + c·x$
22-
//!
23-
//! 3. [`NISigmaProtocol`] — wraps the interactive protocol using the Fiat-Shamir transformation,
24-
//! converting it to a *non-interactive zero-knowledge proof* (NIZK) by deriving the challenge $c$
25-
//! from a transcript hash using a [`Codec`] (here, [`ShakeCodec`]).
26-
//!
27-
//! The codec ensures domain separation and deterministic Fiat-Shamir challenges,
28-
//! yielding secure, standalone proofs.
29-
//!
30-
//! ---
31-
//!
32-
//! This example uses the Ristretto group from `curve25519-dalek`, which provides a prime-order group
33-
//! suitable for secure zero-knowledge protocols.
8+
349
use curve25519_dalek::scalar::Scalar;
3510
use curve25519_dalek::RistrettoPoint;
36-
use group::{Group, GroupEncoding};
11+
use group::Group;
3712
use rand::rngs::OsRng;
3813

39-
use sigma_rs::codec::ShakeCodec;
40-
use sigma_rs::fiat_shamir::NISigmaProtocol;
41-
use sigma_rs::linear_relation::LinearRelation;
42-
use sigma_rs::schnorr_protocol::SchnorrProof;
43-
44-
/// Construct the relation `P = x·G` and return it along with the witness `x`:
45-
/// - `x` is an element from a group of prime order $p$, typically $\mathbb{Z}_p$,
46-
/// - `P`, `G` are elements over a group $\mathbb{G}$ of order $p$.
47-
#[allow(non_snake_case)]
48-
pub fn discrete_logarithm<G: Group + GroupEncoding>(
49-
x: G::Scalar,
50-
) -> (LinearRelation<G>, Vec<G::Scalar>) {
51-
let mut morphism: LinearRelation<G> = LinearRelation::new();
52-
53-
// First, we allocate symbolic variables for our relation.
54-
// These represent the structure of our proof, not concrete values yet.
55-
56-
// Allocate a variable for the scalar witness `x`
57-
let var_x = morphism.allocate_scalar();
14+
use sigma_rs::errors::Error;
15+
use sigma_rs::{LinearRelation, NISigmaProtocol};
5816

59-
// Allocate a variable for the group element `G` (the generator)
60-
let var_G = morphism.allocate_element();
17+
type ProofResult<T> = Result<T, Error>;
6118

62-
// Now that all variables are defined, we describe the relation we want to prove.
63-
// The variable `P` is allocated now, as it is the result of the computation to be proven.
64-
65-
// Create the constraint `P = x * G`
66-
let var_P = morphism.allocate_eq(var_x * var_G);
67-
68-
// We now assign real values to the variables we have defined for our relation above.
69-
// Since the witness is provided to the function, we only need to assign the group points.
19+
/// Create a discrete logarithm relation for the given public key P
20+
#[allow(non_snake_case)]
21+
fn create_relation(P: RistrettoPoint) -> LinearRelation<RistrettoPoint> {
22+
let mut relation = LinearRelation::new();
7023

71-
// Assign the group generator to the corresponding variable `G`
72-
morphism.set_element(var_G, G::generator());
24+
let x = relation.allocate_scalar();
25+
let G = relation.allocate_element();
26+
let P_var = relation.allocate_eq(x * G);
27+
relation.set_element(G, RistrettoPoint::generator());
28+
relation.set_element(P_var, P);
7329

74-
// Assign the value of the image to the variable `P` (i.e., evaluate the group equation for `x`)
75-
morphism.compute_image(&[x]).unwrap();
30+
relation
31+
}
7632

77-
// The relation has been defined and is ready to be used in a protocol.
78-
// Sanity check: ensure `P = x * G`
79-
let P = morphism.linear_map.group_elements.get(var_P).unwrap();
80-
assert_eq!(P, G::generator() * x);
33+
/// Prove knowledge of the discrete logarithm: given witness x and public key P,
34+
/// generate a proof that P = x * G
35+
#[allow(non_snake_case)]
36+
fn prove(x: Scalar, P: RistrettoPoint) -> ProofResult<Vec<u8>> {
37+
let nizk: NISigmaProtocol<_, _> = create_relation(P).into();
38+
nizk.prove_batchable(&vec![x], &mut OsRng)
39+
}
8140

82-
// Output the relation and the witness for the upcoming proof
83-
(morphism, vec![x])
41+
/// Verify a proof of knowledge of discrete logarithm for the given public key P
42+
#[allow(non_snake_case)]
43+
fn verify(P: RistrettoPoint, proof: &[u8]) -> ProofResult<()> {
44+
let nizk: NISigmaProtocol<_, _> = create_relation(P).into();
45+
nizk.verify_batchable(proof)
8446
}
8547

8648
#[allow(non_snake_case)]
8749
fn main() {
88-
// Choose a secret scalar `x` (the witness).
89-
let x = Scalar::random(&mut OsRng);
90-
91-
// Construct a relation `P = x * G` and retrieve:
92-
// - The constraint system defined in the helper function above.
93-
// - The witness in vector format (useful for proofs with multiple witnesses).
94-
let (relation, witness) = discrete_logarithm(x);
95-
96-
// Build the Sigma protocol instance from the relation.
97-
let schnorr = SchnorrProof::<RistrettoPoint>::from(relation);
98-
99-
// Convert the Sigma protocol instance to a non-interactive protocol via Fiat-Shamir.
100-
// A domain separator is given as a byte-sequence to identify the current instance being proven.
101-
let nizk = NISigmaProtocol::<_, ShakeCodec<RistrettoPoint>>::new(b"schnorr-example", schnorr);
102-
103-
// Generate a non-interactive proof with the witness.
104-
// The non-interactive proof contains the 3 elements of a sigma protocol transcript: a commitment, a challenge, and a response.
105-
// This transcript can be transmitted by the prover to any verifier, without revealing any secret information about the prover.
106-
let proof = nizk.prove_batchable(&witness, &mut OsRng).unwrap();
107-
108-
// Verification requires checking the proof against the transcript and constraint system.
109-
// The verifier can be convinced that the prover indeed knows `x` so that `P = x*G`. This doesn't require any interaction with the prover other than receiving the transcript.
110-
let verified = nizk.verify_batchable(&proof).is_ok();
111-
println!("Schnorr NIZK proof verified: {verified}");
50+
let x = Scalar::random(&mut OsRng); // Private key (witness)
51+
let P = RistrettoPoint::generator() * x; // Public key (statement)
52+
53+
println!("Generated new key pair:");
54+
println!("Public key P: {:?}", hex::encode(P.compress().as_bytes()));
55+
56+
match prove(x, P) {
57+
Ok(proof) => {
58+
println!("Proof generated successfully:");
59+
println!("Proof (hex): {}", hex::encode(&proof));
60+
61+
// Verify the proof
62+
match verify(P, &proof) {
63+
Ok(()) => println!("✓ Proof verified successfully!"),
64+
Err(e) => println!("✗ Proof verification failed: {:?}", e),
65+
}
66+
}
67+
Err(e) => println!("✗ Failed to generate proof: {:?}", e),
68+
}
11269
}

examples/simple_composition.rs

Lines changed: 51 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,85 @@
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+
483
use curve25519_dalek::ristretto::RistrettoPoint;
494
use curve25519_dalek::scalar::Scalar;
50-
use group::{Group, GroupEncoding};
5+
use group::Group;
516
use rand::rngs::OsRng;
527
use sigma_rs::{
538
codec::ShakeCodec,
549
composition::{Protocol, ProtocolWitness},
55-
fiat_shamir::NISigmaProtocol,
56-
LinearRelation,
10+
errors::Error,
11+
LinearRelation, NISigmaProtocol,
5712
};
5813

5914
type G = RistrettoPoint;
15+
type ProofResult<T> = Result<T, Error>;
6016

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}$.
6417
#[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();
7320

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);
7624

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();
8027

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])
8629
}
8730

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.
9231
#[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();
9934

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);
10339

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();
10742

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])
11544
}
11645

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);
13249

133-
// Wrap each relation into a Sigma protocol
13450
let proto1 = Protocol::from(rel1);
13551
let proto2 = Protocol::from(rel2);
136-
137-
// Compose both protocols using logical OR
13852
let composed = Protocol::Or(vec![proto1, proto2]);
13953

140-
// Declare the known witness for the second protocol (index = 1)
14154
let witness = ProtocolWitness::Or(1, vec![ProtocolWitness::Simple(witness2)]);
14255

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);
14569
let nizk = NISigmaProtocol::<_, ShakeCodec<G>>::new(b"or_proof_example", composed);
14670

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();
15182

152-
// Verify the proof
153-
let verified = nizk.verify_batchable(&proof).is_ok();
15483
println!("OR-proof verified: {verified}");
84+
println!("Proof bytes: {}", hex::encode(&proof));
15585
}

0 commit comments

Comments
 (0)