refactor(rln): Upgrade semaphore-rs

This commit is contained in:
Oskar Thoren 2022-05-23 09:27:52 +01:00
parent 1d8408d743
commit 45520e7869
No known key found for this signature in database
GPG Key ID: B2ECCFD3BC2EF77E
4 changed files with 112 additions and 85 deletions

View File

@ -48,6 +48,7 @@ serde_json = "1.0.48"
once_cell = "1.8" once_cell = "1.8"
poseidon-rs = "0.0.8" poseidon-rs = "0.0.8"
primitive-types = "0.11.1"
sha2 = "0.10.1" sha2 = "0.10.1"
ff = { package="ff_ce", version="0.11"} ff = { package="ff_ce", version="0.11"}

View File

@ -24,20 +24,20 @@ mod test {
use crate::protocol::*; use crate::protocol::*;
use hex_literal::hex; use hex_literal::hex;
use num_bigint::BigInt; use num_bigint::BigInt;
use semaphore::{hash::Hash, identity::Identity, poseidon_tree::PoseidonTree}; use semaphore::{
hash::Hash, hash_to_field, identity::Identity, poseidon_tree::PoseidonTree, Field,
};
#[test] #[test]
fn test_merkle_proof() { fn test_merkle_proof() {
const LEAF: Hash = Hash::from_bytes_be(hex!( let leaf = Field::from(0);
"0000000000000000000000000000000000000000000000000000000000000000"
));
// generate identity // generate identity
let id = Identity::new(b"hello"); let id = Identity::from_seed(b"hello");
// generate merkle tree // generate merkle tree
let mut tree = PoseidonTree::new(21, LEAF); let mut tree = PoseidonTree::new(21, leaf);
tree.set(0, id.commitment().into()); tree.set(0, id.commitment());
let merkle_proof = tree.proof(0).expect("proof should exist"); let merkle_proof = tree.proof(0).expect("proof should exist");
let root: Field = tree.root().into(); let root: Field = tree.root().into();
@ -48,38 +48,38 @@ mod test {
#[test] #[test]
fn test_semaphore() { fn test_semaphore() {
const LEAF: Hash = Hash::from_bytes_be(hex!( let leaf = Field::from(0);
"0000000000000000000000000000000000000000000000000000000000000000"
));
// generate identity // generate identity
let id = Identity::new(b"hello"); let id = Identity::from_seed(b"hello");
// generate merkle tree // generate merkle tree
let mut tree = PoseidonTree::new(21, LEAF); let mut tree = PoseidonTree::new(21, leaf);
tree.set(0, id.commitment().into()); tree.set(0, id.commitment());
let merkle_proof = tree.proof(0).expect("proof should exist"); let merkle_proof = tree.proof(0).expect("proof should exist");
let root = tree.root().into(); let root = tree.root().into();
// change signal and external_nullifier here // change signal_hash and external_nullifier here
let signal = b"xxx"; let signal_hash = hash_to_field(b"xxx");
let external_nullifier = b"appId"; let external_nullifier_hash = hash_to_field(b"appId");
let external_nullifier_hash =
semaphore::protocol::hash_external_nullifier(external_nullifier);
let nullifier_hash = let nullifier_hash =
semaphore::protocol::generate_nullifier_hash(&id, external_nullifier_hash); semaphore::protocol::generate_nullifier_hash(&id, external_nullifier_hash);
let proof = let proof = semaphore::protocol::generate_proof(
semaphore::protocol::generate_proof(&id, &merkle_proof, external_nullifier, signal) &id,
.unwrap(); &merkle_proof,
external_nullifier_hash,
signal_hash,
)
.unwrap();
let success = semaphore::protocol::verify_proof( let success = semaphore::protocol::verify_proof(
root, root,
nullifier_hash, nullifier_hash,
signal, signal_hash,
external_nullifier, external_nullifier_hash,
&proof, &proof,
) )
.unwrap(); .unwrap();
@ -90,15 +90,13 @@ mod test {
#[ignore] #[ignore]
#[test] #[test]
fn test_end_to_end() { fn test_end_to_end() {
const LEAF: Hash = Hash::from_bytes_be(hex!( let leaf = Field::from(0);
"0000000000000000000000000000000000000000000000000000000000000000"
));
// generate identity // generate identity
let id = Identity::new(b"hello"); let id = Identity::from_seed(b"hello");
// generate merkle tree // generate merkle tree
let mut tree = PoseidonTree::new(21, LEAF); let mut tree = PoseidonTree::new(21, leaf);
tree.set(0, id.commitment().into()); tree.set(0, id.commitment().into());
let merkle_proof = tree.proof(0).expect("proof should exist"); let merkle_proof = tree.proof(0).expect("proof should exist");
@ -107,14 +105,14 @@ mod test {
println!("Root: {:#}", root); println!("Root: {:#}", root);
println!("Merkle proof: {:#?}", merkle_proof); println!("Merkle proof: {:#?}", merkle_proof);
// change signal and external_nullifier here // change signal_hash and external_nullifier_hash here
let signal = b"xxx"; let signal_hash = hash_to_field(b"xxx");
let external_nullifier = b"appId"; let external_nullifier_hash = hash_to_field(b"appId");
let external_nullifier_hash = hash_external_nullifier(external_nullifier);
let nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash); let nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash);
let proof = generate_proof(&id, &merkle_proof, external_nullifier, signal).unwrap(); let proof =
generate_proof(&id, &merkle_proof, external_nullifier_hash, signal_hash).unwrap();
println!("Proof: {:#?}", proof); println!("Proof: {:#?}", proof);
@ -130,7 +128,13 @@ mod test {
// //
// Indeed: // Indeed:
// if (public_inputs.len() + 1) != pvk.vk.gamma_abc_g1.len() { // if (public_inputs.len() + 1) != pvk.vk.gamma_abc_g1.len() {
let success = let success = verify_proof(
verify_proof(root, nullifier_hash, signal, external_nullifier, &proof).unwrap(); root,
nullifier_hash,
signal_hash,
external_nullifier_hash,
&proof,
)
.unwrap();
} }
} }

View File

@ -4,12 +4,15 @@ use ark_bn254::{Bn254, Parameters};
use ark_circom::CircomReduction; use ark_circom::CircomReduction;
use ark_ec::bn::Bn; use ark_ec::bn::Bn;
use ark_ff::{Fp256, PrimeField}; use ark_ff::{Fp256, PrimeField};
use ark_groth16::{create_proof_with_reduction_and_matrices, prepare_verifying_key, Proof}; use ark_groth16::{
create_proof_with_reduction_and_matrices, prepare_verifying_key, Proof as ArkProof,
};
use ark_relations::r1cs::SynthesisError; use ark_relations::r1cs::SynthesisError;
use ark_std::{rand::thread_rng, UniformRand}; use ark_std::{rand::thread_rng, UniformRand};
use color_eyre::Result; use color_eyre::Result;
use ethers_core::utils::keccak256; use ethers_core::utils::keccak256;
use num_bigint::{BigInt, BigUint, ToBigInt}; use num_bigint::{BigInt, BigUint, ToBigInt};
use primitive_types::U256;
use semaphore::{ use semaphore::{
identity::Identity, identity::Identity,
merkle_tree::{self, Branch}, merkle_tree::{self, Branch},
@ -17,11 +20,52 @@ use semaphore::{
poseidon_tree::PoseidonHash, poseidon_tree::PoseidonHash,
Field, Field,
}; };
use serde::{Deserialize, Serialize};
use std::time::Instant; use std::time::Instant;
use thiserror::Error; use thiserror::Error;
// TODO Fields need to be updated to RLN based ones // TODO Fields need to be updated to RLN based ones
// Matches the private G1Tup type in ark-circom.
pub type G1 = (U256, U256);
// Matches the private G2Tup type in ark-circom.
pub type G2 = ([U256; 2], [U256; 2]);
/// Wrap a proof object so we have serde support
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Proof(G1, G2, G1);
impl From<ArkProof<Bn<Parameters>>> for Proof {
fn from(proof: ArkProof<Bn<Parameters>>) -> Self {
let proof = ark_circom::ethereum::Proof::from(proof);
let (a, b, c) = proof.as_tuple();
Self(a, b, c)
}
}
impl From<Proof> for ArkProof<Bn<Parameters>> {
fn from(proof: Proof) -> Self {
let eth_proof = ark_circom::ethereum::Proof {
a: ark_circom::ethereum::G1 {
x: proof.0 .0,
y: proof.0 .1,
},
#[rustfmt::skip] // Rustfmt inserts some confusing spaces
b: ark_circom::ethereum::G2 {
// The order of coefficients is flipped.
x: [proof.1.0[1], proof.1.0[0]],
y: [proof.1.1[1], proof.1.1[0]],
},
c: ark_circom::ethereum::G1 {
x: proof.2 .0,
y: proof.2 .1,
},
};
eth_proof.into()
}
}
/// Helper to merkle proof into a bigint vector /// Helper to merkle proof into a bigint vector
/// TODO: we should create a From trait for this /// TODO: we should create a From trait for this
fn merkle_proof_to_vec(proof: &merkle_tree::Proof<PoseidonHash>) -> Vec<Field> { fn merkle_proof_to_vec(proof: &merkle_tree::Proof<PoseidonHash>) -> Vec<Field> {
@ -29,7 +73,7 @@ fn merkle_proof_to_vec(proof: &merkle_tree::Proof<PoseidonHash>) -> Vec<Field> {
.0 .0
.iter() .iter()
.map(|x| match x { .map(|x| match x {
Branch::Left(value) | Branch::Right(value) => value.into(), Branch::Left(value) | Branch::Right(value) => *value,
}) })
.collect() .collect()
} }
@ -43,19 +87,6 @@ fn hash_signal(signal: &[u8]) -> Field {
Field::from_be_bytes_mod_order(&bytes) Field::from_be_bytes_mod_order(&bytes)
} }
/// Internal helper to hash the external nullifier
#[must_use]
pub fn hash_external_nullifier(nullifier: &[u8]) -> Field {
// Hash input to 256 bits.
let mut hash = keccak256(nullifier);
// Clear first four bytes to make sure the hash is in the field.
for byte in &mut hash[0..4] {
*byte = 0;
}
// Convert to field element.
Fp256::from_be_bytes_mod_order(&hash)
}
/// Generates the nullifier hash /// Generates the nullifier hash
#[must_use] #[must_use]
pub fn generate_nullifier_hash(identity: &Identity, external_nullifier: Field) -> Field { pub fn generate_nullifier_hash(identity: &Identity, external_nullifier: Field) -> Field {
@ -72,11 +103,6 @@ pub enum ProofError {
SynthesisError(#[from] SynthesisError), SynthesisError(#[from] SynthesisError),
} }
fn ark_to_bigint(n: Field) -> BigInt {
let n: BigUint = n.into();
n.to_bigint().expect("conversion always succeeds for uint")
}
// XXX This is different from zk-kit API: // XXX This is different from zk-kit API:
// const witness = RLN.genWitness(secretHash, merkleProof, epoch, signal, rlnIdentifier) // const witness = RLN.genWitness(secretHash, merkleProof, epoch, signal, rlnIdentifier)
// const fullProof = await RLN.genProof(witness, wasmFilePath, finalZkeyPath) // const fullProof = await RLN.genProof(witness, wasmFilePath, finalZkeyPath)
@ -91,11 +117,9 @@ fn ark_to_bigint(n: Field) -> BigInt {
pub fn generate_proof( pub fn generate_proof(
identity: &Identity, identity: &Identity,
merkle_proof: &merkle_tree::Proof<PoseidonHash>, merkle_proof: &merkle_tree::Proof<PoseidonHash>,
external_nullifier: &[u8], external_nullifier_hash: Field,
signal: &[u8], signal_hash: Field,
) -> Result<Proof<Bn<Parameters>>, ProofError> { ) -> Result<Proof, ProofError> {
let external_nullifier = hash_external_nullifier(external_nullifier);
let signal = hash_signal(signal);
// TODO Fix inputs // TODO Fix inputs
// Semaphore genWitness corresponds to these // Semaphore genWitness corresponds to these
// RLN different, should be: // RLN different, should be:
@ -111,22 +135,18 @@ pub fn generate_proof(
//("identityTrapdoor", vec![identity.trapdoor]), //("identityTrapdoor", vec![identity.trapdoor]),
("path_elements", merkle_proof_to_vec(merkle_proof)), ("path_elements", merkle_proof_to_vec(merkle_proof)),
("identity_path_index", merkle_proof.path_index()), ("identity_path_index", merkle_proof.path_index()),
("externalNullifier", vec![external_nullifier]), ("externalNullifier", vec![external_nullifier_hash]),
// XXX: Assuming signal is hashed // XXX: Assuming signal is hashed
("x", vec![signal]), ("x", vec![signal_hash]),
// FIXME epoch just hardcoded to random value // FIXME epoch just hardcoded to random value
("epoch", vec![signal]), ("epoch", vec![signal_hash]),
// FIXME rln_identifier just hardcoded to random value // FIXME rln_identifier just hardcoded to random value
("rln_identifier", vec![signal]), ("rln_identifier", vec![signal_hash]),
]; ];
let inputs = inputs.into_iter().map(|(name, values)| { let inputs = inputs.into_iter().map(|(name, values)| {
( (
name.to_string(), name.to_string(),
values values.iter().copied().map(Into::into).collect::<Vec<_>>(),
.iter()
.copied()
.map(ark_to_bigint)
.collect::<Vec<_>>(),
) )
}); });
@ -147,7 +167,7 @@ pub fn generate_proof(
let now = Instant::now(); let now = Instant::now();
let proof = create_proof_with_reduction_and_matrices::<_, CircomReduction>( let ark_proof = create_proof_with_reduction_and_matrices::<_, CircomReduction>(
&ZKEY.0, &ZKEY.0,
r, r,
s, s,
@ -156,7 +176,7 @@ pub fn generate_proof(
ZKEY.1.num_constraints, ZKEY.1.num_constraints,
full_assignment.as_slice(), full_assignment.as_slice(),
)?; )?;
let proof = ark_proof.into();
println!("proof generation took: {:.2?}", now.elapsed()); println!("proof generation took: {:.2?}", now.elapsed());
Ok(proof) Ok(proof)
@ -173,9 +193,9 @@ pub fn generate_proof(
pub fn verify_proof( pub fn verify_proof(
root: Field, root: Field,
nullifier_hash: Field, nullifier_hash: Field,
signal: &[u8], signal_hash: Field,
external_nullifier: &[u8], external_nullifier_hash: Field,
proof: &Proof<Bn<Parameters>>, proof: &Proof,
) -> Result<bool, ProofError> { ) -> Result<bool, ProofError> {
// XXX: Why is verification key in zkey but that's not what is used in // XXX: Why is verification key in zkey but that's not what is used in
// verifyProof with verification_key.json? Is there a difference? // verifyProof with verification_key.json? Is there a difference?
@ -192,11 +212,12 @@ pub fn verify_proof(
// epoch // epoch
// rlnIdentifier // rlnIdentifier
let public_inputs = vec![ let public_inputs = vec![
root, root.into(),
nullifier_hash, nullifier_hash.into(),
hash_signal(signal), signal_hash.into(),
hash_external_nullifier(external_nullifier), external_nullifier_hash.into(),
]; ];
let result = ark_groth16::verify_proof(&pvk, proof, &public_inputs)?; let ark_proof = (*proof).into();
let result = ark_groth16::verify_proof(&pvk, &ark_proof, &public_inputs[..])?;
Ok(result) Ok(result)
} }

View File

@ -3,8 +3,9 @@
/// ///
use crate::merkle::IncrementalMerkleTree; use crate::merkle::IncrementalMerkleTree;
use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams}; use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams};
use semaphore::hash::Hash; use semaphore::{
use semaphore::poseidon_tree::PoseidonTree; hash_to_field, identity::Identity, poseidon_tree::PoseidonTree, protocol::*, Field,
};
use ark_circom::{CircomBuilder, CircomCircuit, CircomConfig}; use ark_circom::{CircomBuilder, CircomCircuit, CircomConfig};
use ark_std::rand::thread_rng; use ark_std::rand::thread_rng;
@ -72,8 +73,8 @@ impl RLN {
let inputs = circom.get_public_inputs().unwrap(); let inputs = circom.get_public_inputs().unwrap();
println!("Public inputs {:#?} ", inputs); println!("Public inputs {:#?} ", inputs);
const LEAF: Hash = Hash::from_bytes_be([0u8; 32]); let leaf = Field::from(0);
let mut tree = PoseidonTree::new(21, LEAF); let mut tree = PoseidonTree::new(21, leaf);
RLN { RLN {
circom, circom,
@ -197,8 +198,8 @@ impl RLN {
// let hasher = PoseidonHasher::new(poseidon_params.clone()); // let hasher = PoseidonHasher::new(poseidon_params.clone());
// let tree = IncrementalMerkleTree::empty(hasher, merkle_depth); // let tree = IncrementalMerkleTree::empty(hasher, merkle_depth);
const LEAF: Hash = Hash::from_bytes_be([0u8; 32]); let leaf = Field::from(0);
let mut tree = PoseidonTree::new(21, LEAF); let mut tree = PoseidonTree::new(21, leaf);
RLN { RLN {
circom, circom,