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"
poseidon-rs = "0.0.8"
primitive-types = "0.11.1"
sha2 = "0.10.1"
ff = { package="ff_ce", version="0.11"}

View File

@ -24,20 +24,20 @@ mod test {
use crate::protocol::*;
use hex_literal::hex;
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]
fn test_merkle_proof() {
const LEAF: Hash = Hash::from_bytes_be(hex!(
"0000000000000000000000000000000000000000000000000000000000000000"
));
let leaf = Field::from(0);
// generate identity
let id = Identity::new(b"hello");
let id = Identity::from_seed(b"hello");
// generate merkle tree
let mut tree = PoseidonTree::new(21, LEAF);
tree.set(0, id.commitment().into());
let mut tree = PoseidonTree::new(21, leaf);
tree.set(0, id.commitment());
let merkle_proof = tree.proof(0).expect("proof should exist");
let root: Field = tree.root().into();
@ -48,38 +48,38 @@ mod test {
#[test]
fn test_semaphore() {
const LEAF: Hash = Hash::from_bytes_be(hex!(
"0000000000000000000000000000000000000000000000000000000000000000"
));
let leaf = Field::from(0);
// generate identity
let id = Identity::new(b"hello");
let id = Identity::from_seed(b"hello");
// generate merkle tree
let mut tree = PoseidonTree::new(21, LEAF);
tree.set(0, id.commitment().into());
let mut tree = PoseidonTree::new(21, leaf);
tree.set(0, id.commitment());
let merkle_proof = tree.proof(0).expect("proof should exist");
let root = tree.root().into();
// change signal and external_nullifier here
let signal = b"xxx";
let external_nullifier = b"appId";
// change signal_hash and external_nullifier here
let signal_hash = hash_to_field(b"xxx");
let external_nullifier_hash = hash_to_field(b"appId");
let external_nullifier_hash =
semaphore::protocol::hash_external_nullifier(external_nullifier);
let nullifier_hash =
semaphore::protocol::generate_nullifier_hash(&id, external_nullifier_hash);
let proof =
semaphore::protocol::generate_proof(&id, &merkle_proof, external_nullifier, signal)
.unwrap();
let proof = semaphore::protocol::generate_proof(
&id,
&merkle_proof,
external_nullifier_hash,
signal_hash,
)
.unwrap();
let success = semaphore::protocol::verify_proof(
root,
nullifier_hash,
signal,
external_nullifier,
signal_hash,
external_nullifier_hash,
&proof,
)
.unwrap();
@ -90,15 +90,13 @@ mod test {
#[ignore]
#[test]
fn test_end_to_end() {
const LEAF: Hash = Hash::from_bytes_be(hex!(
"0000000000000000000000000000000000000000000000000000000000000000"
));
let leaf = Field::from(0);
// generate identity
let id = Identity::new(b"hello");
let id = Identity::from_seed(b"hello");
// generate merkle tree
let mut tree = PoseidonTree::new(21, LEAF);
let mut tree = PoseidonTree::new(21, leaf);
tree.set(0, id.commitment().into());
let merkle_proof = tree.proof(0).expect("proof should exist");
@ -107,14 +105,14 @@ mod test {
println!("Root: {:#}", root);
println!("Merkle proof: {:#?}", merkle_proof);
// change signal and external_nullifier here
let signal = b"xxx";
let external_nullifier = b"appId";
// change signal_hash and external_nullifier_hash here
let signal_hash = hash_to_field(b"xxx");
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 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);
@ -130,7 +128,13 @@ mod test {
//
// Indeed:
// if (public_inputs.len() + 1) != pvk.vk.gamma_abc_g1.len() {
let success =
verify_proof(root, nullifier_hash, signal, external_nullifier, &proof).unwrap();
let success = verify_proof(
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_ec::bn::Bn;
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_std::{rand::thread_rng, UniformRand};
use color_eyre::Result;
use ethers_core::utils::keccak256;
use num_bigint::{BigInt, BigUint, ToBigInt};
use primitive_types::U256;
use semaphore::{
identity::Identity,
merkle_tree::{self, Branch},
@ -17,11 +20,52 @@ use semaphore::{
poseidon_tree::PoseidonHash,
Field,
};
use serde::{Deserialize, Serialize};
use std::time::Instant;
use thiserror::Error;
// 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
/// TODO: we should create a From trait for this
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
.iter()
.map(|x| match x {
Branch::Left(value) | Branch::Right(value) => value.into(),
Branch::Left(value) | Branch::Right(value) => *value,
})
.collect()
}
@ -43,19 +87,6 @@ fn hash_signal(signal: &[u8]) -> Field {
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
#[must_use]
pub fn generate_nullifier_hash(identity: &Identity, external_nullifier: Field) -> Field {
@ -72,11 +103,6 @@ pub enum ProofError {
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:
// const witness = RLN.genWitness(secretHash, merkleProof, epoch, signal, rlnIdentifier)
// const fullProof = await RLN.genProof(witness, wasmFilePath, finalZkeyPath)
@ -91,11 +117,9 @@ fn ark_to_bigint(n: Field) -> BigInt {
pub fn generate_proof(
identity: &Identity,
merkle_proof: &merkle_tree::Proof<PoseidonHash>,
external_nullifier: &[u8],
signal: &[u8],
) -> Result<Proof<Bn<Parameters>>, ProofError> {
let external_nullifier = hash_external_nullifier(external_nullifier);
let signal = hash_signal(signal);
external_nullifier_hash: Field,
signal_hash: Field,
) -> Result<Proof, ProofError> {
// TODO Fix inputs
// Semaphore genWitness corresponds to these
// RLN different, should be:
@ -111,22 +135,18 @@ pub fn generate_proof(
//("identityTrapdoor", vec![identity.trapdoor]),
("path_elements", merkle_proof_to_vec(merkle_proof)),
("identity_path_index", merkle_proof.path_index()),
("externalNullifier", vec![external_nullifier]),
("externalNullifier", vec![external_nullifier_hash]),
// XXX: Assuming signal is hashed
("x", vec![signal]),
("x", vec![signal_hash]),
// FIXME epoch just hardcoded to random value
("epoch", vec![signal]),
("epoch", vec![signal_hash]),
// 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)| {
(
name.to_string(),
values
.iter()
.copied()
.map(ark_to_bigint)
.collect::<Vec<_>>(),
values.iter().copied().map(Into::into).collect::<Vec<_>>(),
)
});
@ -147,7 +167,7 @@ pub fn generate_proof(
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,
r,
s,
@ -156,7 +176,7 @@ pub fn generate_proof(
ZKEY.1.num_constraints,
full_assignment.as_slice(),
)?;
let proof = ark_proof.into();
println!("proof generation took: {:.2?}", now.elapsed());
Ok(proof)
@ -173,9 +193,9 @@ pub fn generate_proof(
pub fn verify_proof(
root: Field,
nullifier_hash: Field,
signal: &[u8],
external_nullifier: &[u8],
proof: &Proof<Bn<Parameters>>,
signal_hash: Field,
external_nullifier_hash: Field,
proof: &Proof,
) -> Result<bool, ProofError> {
// XXX: Why is verification key in zkey but that's not what is used in
// verifyProof with verification_key.json? Is there a difference?
@ -192,11 +212,12 @@ pub fn verify_proof(
// epoch
// rlnIdentifier
let public_inputs = vec![
root,
nullifier_hash,
hash_signal(signal),
hash_external_nullifier(external_nullifier),
root.into(),
nullifier_hash.into(),
signal_hash.into(),
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)
}

View File

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