Skip to content

Commit

Permalink
More documentation in examples and ergonomics.
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaker committed Jan 20, 2024
1 parent bd2b38f commit 7e94e9f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 34 deletions.
6 changes: 5 additions & 1 deletion examples/bulletproof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,11 @@ fn main() {
arthur.public_points(&[statement]).unwrap();
arthur.ratchet().unwrap();
let proof = prove(&mut arthur, generators, &statement, witness).expect("Error proving");
println!("Here's a bulletproof for {} elements:\n{}", size, hex::encode(proof));
println!(
"Here's a bulletproof for {} elements:\n{}",
size,
hex::encode(proof)
);

let mut verifier_transcript = io_pattern.to_merlin(proof);
verifier_transcript.public_points(&[statement]).unwrap();
Expand Down
109 changes: 77 additions & 32 deletions examples/schnorr.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,119 @@
/// Example: simple Schnorr proofs.
///
/// Schnorr proofs allow to prove knowledge of a secret key over a group $\mathbb{G}$ of prime order $p$ where the discrete logarithm problem is hard.
/// The protocols is as follows:
///
use ark_ec::{CurveGroup, PrimeGroup};
use ark_std::UniformRand;
use nimue::{DuplexHash, ProofResult};

use nimue::plugins::arkworks::*;
use nimue::{DuplexHash, ProofResult};
use rand::rngs::OsRng;

/// The key generation algorithm otuputs
/// a secret key `sk` in $\mathbb{Z}_p$
/// and its respective public key `pk` in $\mathbb{G}$.
fn keygen<G: CurveGroup>() -> (G::ScalarField, G) {
let sk = G::ScalarField::rand(&mut OsRng);
let pk = G::generator() * sk;
(sk, pk)
}

/// The prove algorithm takes as input
/// - the prover state `Arthur`, that has access to a random oracle `H` and can absorb/squeeze elements from the group `G`.
/// - the secret key $x \in \mathbb{Z}_p$
/// It returns a zero-knowledge proof of knowledge of `x` as a sequence of bytes.
#[allow(non_snake_case)]
fn prove<H: DuplexHash<u8>, G: CurveGroup>(
// `ArkGroupArthur` is a wrapper around `Arthur` that is aware of serialization/deserialization of group elements
// the hash function `H` works over bytes, unless otherwise denoted with an additional type argument implementing `nimue::Unit`.
arthur: &mut ArkGroupArthur<G, H>,
witness: G::ScalarField,
// the generator
P: G,
// the secret key
x: G::ScalarField,
) -> ProofResult<&[u8]> {
let k = G::ScalarField::rand(&mut arthur.rng());
let commitment = G::generator() * k;
arthur.add_points(&[commitment])?;
// `Arthur` types implement a cryptographically-secure random number generator that is tied to the protocol transcript
// and that can be accessed via the `rng()` funciton.
let k = G::ScalarField::rand(arthur.rng());
let K = P * k;

let [challenge] = arthur.challenge_scalars()?;
// Add a sequence of points to the protocol transcript.
// An error is returned in case of failed serialization, or inconsistencies with the IO pattern provided (see below).
arthur.add_points(&[K])?;

let response = k + challenge * witness;
arthur.add_scalars(&[response])?;
// Fetch a challenge from the current transcript state.
let [c] = arthur.challenge_scalars()?;

let r = k + c * x;
// Add a sequence of scalar elements to the protocol transcript.
arthur.add_scalars(&[r])?;

// Output the current protocol transcript as a sequence of bytes.
Ok(arthur.transcript())
}

fn verify<H, G>(merlin: &mut ArkGroupMerlin<G, H>, g: G, pk: G) -> ProofResult<()>
where
H: DuplexHash<u8>,
G: CurveGroup,
{
let [commitment] = merlin.next_points().unwrap();
let [challenge] = merlin.squeeze_scalars().unwrap();
let [response] = merlin.next_scalars().unwrap();
/// The verify algorithm takes as input
/// - the verifier state `Merlin`, that has access to a random oracle `H` and can deserialize/squeeze elements from the group `G`.
/// - the secret key `witness`
/// It returns a zero-knowledge proof of knowledge of `witness` as a sequence of bytes.
#[allow(non_snake_case)]
fn verify<G: CurveGroup, H: DuplexHash>(
// `ArkGroupMelin` contains the veirifier state, including the messages currently read. In addition, it is aware of the group `G`
// from which it can serialize/deserialize elements.
merlin: &mut ArkGroupMerlin<G, H>,
// The group generator `P``
P: G,
// The public key `X`
X: G,
) -> ProofResult<()> {
// Read the protocol from the transcript:
let [K] = merlin.next_points().unwrap();
let [c] = merlin.squeeze_scalars().unwrap();
let [r] = merlin.next_scalars().unwrap();

if commitment == g * response - pk * challenge {
// Check the verification equation, otherwise return a verification error.
if P * r == K + X * c {
Ok(())
} else {
Err(nimue::ProofError::InvalidProof)
}
}

#[allow(non_snake_case)]
fn main() {
// Instantiate the group and the random oracle:
// Set the group:
type G = ark_curve25519::EdwardsProjective;
// Set the hash function (commented out other valid choices):
type H = nimue::hash::Keccak;
// type H = nimue::legacy::DigestBridge<blake2::Blake2s256>;
// type H = nimue::legacy::DigestBridge<sha2::Sha256>;
type H = nimue::hash::Keccak;
type G = ark_curve25519::EdwardsProjective;

let g = G::generator();
let (sk, pk) = keygen();

// Set up the IO for the protocol transcript with domain separator "nimue::examples::schnorr"
let io = ArkGroupIOPattern::<G, H>::new("nimue::examples::schnorr")
.add_points(1, "g")
.add_points(1, "pk")
.add_points(1, "P")
.add_points(1, "X")
.ratchet()
.add_points(1, "commitment")
.challenge_scalars(1, "challenge")
.add_scalars(1, "response");
.add_points(1, "commitment (K)")
.challenge_scalars(1, "challenge (c)")
.add_scalars(1, "response (r)");

// Set up the elements to prove
let P = G::generator();
let (x, X) = keygen();

// Create the prover transcript, add the statement to it, and then invoke the prover.
let mut arthur = io.to_arthur();
arthur.public_points(&[g, pk]).unwrap();
arthur.public_points(&[P, X]).unwrap();
arthur.ratchet().unwrap();
let proof = prove(&mut arthur, sk).expect("Valid proof");
let proof = prove(&mut arthur, P, x).expect("Invalid proof");

// Print out the hex-encoded schnorr proof.
println!("Here's a Schnorr signature:\n{}", hex::encode(proof));

// Verify the proof: create the verifier transcript, add the statement to it, and invoke the verifier.
let mut merlin = io.to_merlin(proof);
merlin.public_points(&[g, pk]).unwrap();
merlin.public_points(&[P, X]).unwrap();
merlin.ratchet().unwrap();
verify(&mut merlin, g, pk).expect("Valid proof");
verify(&mut merlin, P, X).expect("Invalid proof");
}
5 changes: 4 additions & 1 deletion src/hash/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ pub trait Unit: Clone + Sized + zeroize::Zeroize {
///
/// **HAZARD**: Don't implement this trait unless you know what you are doing.
/// Consider using the sponges already provided by this library.
pub trait DuplexHash<U: Unit>: Default + Clone + zeroize::Zeroize {
pub trait DuplexHash<U = u8>: Default + Clone + zeroize::Zeroize
where
U: Unit,
{
/// Initializes a new sponge, setting up the state.
fn new(tag: [u8; 32]) -> Self;

Expand Down

0 comments on commit 7e94e9f

Please sign in to comment.