fix(rln): switch to field type, add field arithmetic

This commit is contained in:
s1fr0 2022-06-09 00:41:50 +02:00
parent cf6168bf4e
commit 7ac5b60cb6
No known key found for this signature in database
GPG Key ID: 2C041D60117BFF46
5 changed files with 243 additions and 77 deletions

View File

@ -51,6 +51,7 @@ fn fq_from_str(s: &str) -> Fq {
.into() .into()
} }
// Extracts the element in G1 corresponding to its JSON serialization
fn json_to_g1(json: &Value, key: &str) -> G1Affine { fn json_to_g1(json: &Value, key: &str) -> G1Affine {
let els: Vec<String> = json let els: Vec<String> = json
.get(key) .get(key)
@ -67,6 +68,7 @@ fn json_to_g1(json: &Value, key: &str) -> G1Affine {
)) ))
} }
// Extracts the vector of G1 elements corresponding to its JSON serialization
fn json_to_g1_vec(json: &Value, key: &str) -> Vec<G1Affine> { fn json_to_g1_vec(json: &Value, key: &str) -> Vec<G1Affine> {
let els: Vec<Vec<String>> = json let els: Vec<Vec<String>> = json
.get(key) .get(key)
@ -94,6 +96,7 @@ fn json_to_g1_vec(json: &Value, key: &str) -> Vec<G1Affine> {
.collect() .collect()
} }
// Extracts the element in G2 corresponding to its JSON serialization
fn json_to_g2(json: &Value, key: &str) -> G2Affine { fn json_to_g2(json: &Value, key: &str) -> G2Affine {
let els: Vec<Vec<String>> = json let els: Vec<Vec<String>> = json
.get(key) .get(key)
@ -116,6 +119,7 @@ fn json_to_g2(json: &Value, key: &str) -> G2Affine {
G2Affine::from(G2Projective::new(x, y, z)) G2Affine::from(G2Projective::new(x, y, z))
} }
// Computes the verification key from its JSON serialization
fn vk_from_json(vk_path: &str) -> VerifyingKey<Bn254> { fn vk_from_json(vk_path: &str) -> VerifyingKey<Bn254> {
let json = std::fs::read_to_string(vk_path).unwrap(); let json = std::fs::read_to_string(vk_path).unwrap();
let json: Value = serde_json::from_str(&json).unwrap(); let json: Value = serde_json::from_str(&json).unwrap();
@ -129,6 +133,7 @@ fn vk_from_json(vk_path: &str) -> VerifyingKey<Bn254> {
} }
} }
// Checks verification key to be correct with respect to proving key
pub fn check_vk_from_zkey(verifying_key: VerifyingKey<Bn254>) { pub fn check_vk_from_zkey(verifying_key: VerifyingKey<Bn254>) {
assert_eq!(ZKEY().vk, verifying_key); assert_eq!(ZKEY().vk, verifying_key);
} }

View File

@ -10,8 +10,7 @@ pub mod circuit;
pub mod ffi; pub mod ffi;
pub mod protocol; pub mod protocol;
pub mod public; pub mod public;
pub mod utils;
pub type Field = Fr;
#[cfg(test)] #[cfg(test)]
mod test { mod test {
@ -55,8 +54,59 @@ mod test {
// We check correct computation of the path and indexes // We check correct computation of the path and indexes
let expected_path_elements = vec![ let expected_path_elements = vec![
"0", Field::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")
"14744269619966411208579211824598458697587494354926760081771325075741142829156", .unwrap(),
Field::from_str("0x2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864")
.unwrap(),
Field::from_str("0x1069673dcdb12263df301a6ff584a7ec261a44cb9dc68df067a4774460b1f1e1")
.unwrap(),
Field::from_str("0x18f43331537ee2af2e3d758d50f72106467c6eea50371dd528d57eb2b856d238")
.unwrap(),
Field::from_str("0x07f9d837cb17b0d36320ffe93ba52345f1b728571a568265caac97559dbc952a")
.unwrap(),
Field::from_str("0x2b94cf5e8746b3f5c9631f4c5df32907a699c58c94b2ad4d7b5cec1639183f55")
.unwrap(),
Field::from_str("0x2dee93c5a666459646ea7d22cca9e1bcfed71e6951b953611d11dda32ea09d78")
.unwrap(),
Field::from_str("0x078295e5a22b84e982cf601eb639597b8b0515a88cb5ac7fa8a4aabe3c87349d")
.unwrap(),
Field::from_str("0x2fa5e5f18f6027a6501bec864564472a616b2e274a41211a444cbe3a99f3cc61")
.unwrap(),
Field::from_str("0x0e884376d0d8fd21ecb780389e941f66e45e7acce3e228ab3e2156a614fcd747")
.unwrap(),
Field::from_str("0x1b7201da72494f1e28717ad1a52eb469f95892f957713533de6175e5da190af2")
.unwrap(),
Field::from_str("0x1f8d8822725e36385200c0b201249819a6e6e1e4650808b5bebc6bface7d7636")
.unwrap(),
Field::from_str("0x2c5d82f66c914bafb9701589ba8cfcfb6162b0a12acf88a8d0879a0471b5f85a")
.unwrap(),
Field::from_str("0x14c54148a0940bb820957f5adf3fa1134ef5c4aaa113f4646458f270e0bfbfd0")
.unwrap(),
Field::from_str("0x190d33b12f986f961e10c0ee44d8b9af11be25588cad89d416118e4bf4ebe80c")
.unwrap(),
];
let expected_identity_path_index: Vec<u8> =
vec![1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(path_elements, expected_path_elements);
assert_eq!(identity_path_index, expected_identity_path_index);
// We check correct verification of the proof
assert!(tree.verify(id_commitment.into(), &merkle_proof));
}
#[test]
// We test a RLN proof generation and verification
fn test_witness_from_json() {
// From rln JSON witness
// Input generated with https://github.com/oskarth/zk-kit/commit/b6a872f7160c7c14e10a0ea40acab99cbb23c9a8
let input_json_str = r#"
{
"identity_secret": "12825549237505733615964533204745049909430608936689388901883576945030025938736",
"path_elements": [
"18622655742232062119094611065896226799484910997537830749762961454045300666333",
"20590447254980891299813706518821659736846425329007960381537122689749540452732",
"7423237065226347324353380772367382631490014989348495481811164164159255474657", "7423237065226347324353380772367382631490014989348495481811164164159255474657",
"11286972368698509976183087595462810875513684078608517520839298933882497716792", "11286972368698509976183087595462810875513684078608517520839298933882497716792",
"3607627140608796879659380071776844901612302623152076817094415224584923813162", "3607627140608796879659380071776844901612302623152076817094415224584923813162",
@ -69,17 +119,46 @@ mod test {
"14271763308400718165336499097156975241954733520325982997864342600795471836726", "14271763308400718165336499097156975241954733520325982997864342600795471836726",
"20066985985293572387227381049700832219069292839614107140851619262827735677018", "20066985985293572387227381049700832219069292839614107140851619262827735677018",
"9394776414966240069580838672673694685292165040808226440647796406499139370960", "9394776414966240069580838672673694685292165040808226440647796406499139370960",
"11331146992410411304059858900317123658895005918277453009197229807340014528524", "11331146992410411304059858900317123658895005918277453009197229807340014528524"
]; ],
"identity_path_index": [
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"x": "8143228284048792769012135629627737459844825626241842423967352803501040982",
"epoch": "0x0000005b612540fc986b42322f8cb91c2273afad58ed006fdba0c97b4b16b12f",
"rln_identifier": "11412926387081627876309792396682864042420635853496105400039841573530884328439"
}
"#;
let expected_identity_path_index: Vec<u8> = // We generate all relevant keys
vec![1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let proving_key = &ZKEY();
let verification_key = &VK();
let builder = CIRCOM();
assert_eq!(path_elements, expected_path_elements); // We compute witness from the json input example
assert_eq!(identity_path_index, expected_identity_path_index); let rln_witness = rln_witness_from_json(input_json_str);
// We check correct verification of the proof // Let's generate a zkSNARK proof
assert!(tree.verify(id_commitment.into(), &merkle_proof)); let (proof, inputs) = generate_proof(builder, proving_key, rln_witness).unwrap();
// Let's verify the proof
let verified = verify_proof(verification_key, proof, inputs);
assert!(verified.unwrap());
} }
#[test] #[test]

View File

@ -9,7 +9,7 @@ use ark_groth16::{
VerifyingKey, VerifyingKey,
}; };
use ark_relations::r1cs::SynthesisError; use ark_relations::r1cs::SynthesisError;
use ark_std::{rand::thread_rng, UniformRand}; use ark_std::{rand::thread_rng, str::FromStr, 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};
@ -25,24 +25,52 @@ use serde::{Deserialize, Serialize};
use std::time::Instant; use std::time::Instant;
use thiserror::Error; use thiserror::Error;
pub use crate::utils::{str_to_field, vec_to_field, vec_to_fr};
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
// RLN Witness data structure and utility functions // RLN Witness data structure and utility functions
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct RLNWitnessInput { pub struct RLNWitnessInput {
identity_secret: String, identity_secret: Field,
path_elements: Vec<String>, path_elements: Vec<Field>,
identity_path_index: Vec<u8>, identity_path_index: Vec<u8>,
x: String, x: Field,
epoch: String, epoch: Field,
rln_identifier: String, rln_identifier: Field,
} }
pub fn rln_witness_from_json(input_json_str: &str) -> RLNWitnessInput { pub fn rln_witness_from_json(input_json_str: &str) -> RLNWitnessInput {
let rln_witness: RLNWitnessInput = let input_json: serde_json::Value =
serde_json::from_str(input_json_str).expect("JSON was not well-formatted"); serde_json::from_str(input_json_str).expect("JSON was not well-formatted");
rln_witness
let identity_secret = str_to_field(input_json["identity_secret"].to_string(), 10);
let mut path_elements: Vec<Field> = vec![];
for v in input_json["path_elements"].as_array().unwrap().iter() {
path_elements.push(str_to_field(v.to_string(), 10));
}
let mut identity_path_index: Vec<u8> = vec![];
for v in input_json["identity_path_index"].as_array().unwrap().iter() {
identity_path_index.push(v.as_u64().unwrap() as u8);
}
let x = str_to_field(input_json["x"].to_string(), 10);
let epoch = str_to_field(input_json["epoch"].to_string(), 16);
let rln_identifier = str_to_field(input_json["rln_identifier"].to_string(), 10);
RLNWitnessInput {
identity_secret,
path_elements,
identity_path_index,
x,
epoch,
rln_identifier,
}
} }
pub fn rln_witness_from_values( pub fn rln_witness_from_values(
@ -55,16 +83,14 @@ pub fn rln_witness_from_values(
let path_elements = get_path_elements(merkle_proof); let path_elements = get_path_elements(merkle_proof);
let identity_path_index = get_identity_path_index(merkle_proof); let identity_path_index = get_identity_path_index(merkle_proof);
let rln_witness = RLNWitnessInput { RLNWitnessInput {
identity_secret: BigInt::from(identity_secret).to_str_radix(10), identity_secret,
path_elements, path_elements,
identity_path_index, identity_path_index,
x: BigInt::from(x).to_str_radix(10), x,
epoch: format!("{:#066x}", BigInt::from(epoch)), //We format it as a padded 32 bytes hex with leading 0x for compatibility with zk-kit epoch,
rln_identifier: BigInt::from(rln_identifier).to_str_radix(10), rln_identifier,
}; }
rln_witness
} }
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
@ -117,12 +143,12 @@ impl From<Proof> for ArkProof<Bn<Parameters>> {
/// 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
pub fn get_path_elements(proof: &merkle_tree::Proof<PoseidonHash>) -> Vec<String> { pub fn get_path_elements(proof: &merkle_tree::Proof<PoseidonHash>) -> Vec<Field> {
proof proof
.0 .0
.iter() .iter()
.map(|x| match x { .map(|x| match x {
Branch::Left(value) | Branch::Right(value) => BigInt::from(*value).to_str_radix(10), Branch::Left(value) | Branch::Right(value) => *value,
}) })
.collect() .collect()
} }
@ -180,44 +206,29 @@ pub fn generate_proof(
mut builder: CircomBuilder<Bn254>, mut builder: CircomBuilder<Bn254>,
proving_key: &ProvingKey<Bn254>, proving_key: &ProvingKey<Bn254>,
rln_witness: RLNWitnessInput, rln_witness: RLNWitnessInput,
) -> Result<(Proof, Vec<Fr>), ProofError> { ) -> Result<(Proof, Vec<Field>), ProofError> {
let now = Instant::now(); let now = Instant::now();
builder.push_input( builder.push_input("identity_secret", BigInt::from(rln_witness.identity_secret));
"identity_secret",
BigInt::parse_bytes(rln_witness.identity_secret.as_bytes(), 10).unwrap(),
);
for v in rln_witness.path_elements.iter() { for v in rln_witness.path_elements.iter() {
builder.push_input( builder.push_input("path_elements", BigInt::from(*v));
"path_elements",
BigInt::parse_bytes(v.as_bytes(), 10).unwrap(),
);
} }
for v in rln_witness.identity_path_index.iter() { for v in rln_witness.identity_path_index.iter() {
builder.push_input("identity_path_index", BigInt::from(*v)); builder.push_input("identity_path_index", BigInt::from(*v));
} }
builder.push_input( builder.push_input("x", BigInt::from(rln_witness.x));
"x",
BigInt::parse_bytes(rln_witness.x.as_bytes(), 10).unwrap(),
);
builder.push_input( builder.push_input("epoch", BigInt::from(rln_witness.epoch));
"epoch",
BigInt::parse_bytes(rln_witness.epoch.strip_prefix("0x").unwrap().as_bytes(), 16).unwrap(),
);
builder.push_input( builder.push_input("rln_identifier", BigInt::from(rln_witness.rln_identifier));
"rln_identifier",
BigInt::parse_bytes(rln_witness.rln_identifier.as_bytes(), 10).unwrap(),
);
let circom = builder.build().unwrap(); let circom = builder.build().unwrap();
// Get the populated instance of the circuit with the witness // Get the populated instance of the circuit with the witness
let inputs = circom.get_public_inputs().unwrap(); let inputs = vec_to_field(circom.get_public_inputs().unwrap());
println!("witness generation took: {:.2?}", now.elapsed()); println!("witness generation took: {:.2?}", now.elapsed());
@ -249,12 +260,12 @@ pub fn generate_proof(
pub fn verify_proof( pub fn verify_proof(
verifying_key: &VerifyingKey<Bn254>, verifying_key: &VerifyingKey<Bn254>,
proof: Proof, proof: Proof,
inputs: Vec<Fr>, inputs: Vec<Field>,
) -> Result<bool, ProofError> { ) -> Result<bool, ProofError> {
// Check that the proof is valid // Check that the proof is valid
let pvk = prepare_verifying_key(verifying_key); let pvk = prepare_verifying_key(verifying_key);
let pr: ArkProof<Bn254> = proof.into(); let pr: ArkProof<Bn254> = proof.into();
let verified = ark_verify_proof(&pvk, &pr, &inputs)?; let verified = ark_verify_proof(&pvk, &pr, &vec_to_fr(inputs))?;
Ok(verified) Ok(verified)
} }

View File

@ -17,26 +17,18 @@ use serde::Deserialize;
use serde_json; use serde_json;
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use crate::circuit::{CIRCOM, ZKEY};
use crate::protocol;
// TODO Add Engine here? i.e. <E: Engine> not <Bn254> // TODO Add Engine here? i.e. <E: Engine> not <Bn254>
// TODO Assuming we want to use IncrementalMerkleTree, figure out type/trait conversions // TODO Assuming we want to use IncrementalMerkleTree, figure out type/trait conversions
// TODO Adopt to new protocol structure // TODO Adopt to new protocol structure
pub struct RLN { pub struct RLN {
circom: CircomCircuit<Bn254>, circom: CircomCircuit<Bn254>, //Was CircomCircuit
params: ProvingKey<Bn254>, params: ProvingKey<Bn254>,
tree: PoseidonTree, tree: PoseidonTree,
} }
#[derive(Debug, Deserialize)]
//#[serde(rename_all = "camelCase")]
struct WitnessInput {
identity_secret: String,
path_elements: Vec<String>,
identity_path_index: Vec<i32>,
x: String,
epoch: String,
rln_identifier: String,
}
// TODO Expand API to have better coverage of things needed // TODO Expand API to have better coverage of things needed
impl RLN { impl RLN {
@ -47,19 +39,18 @@ impl RLN {
let builder = CircomBuilder::new(cfg); let builder = CircomBuilder::new(cfg);
// create an empty instance for setting it up let params = ZKEY();
let circom = builder.setup();
let mut rng = thread_rng();
let params = generate_random_parameters::<Bn254, _, _>(circom, &mut rng).unwrap();
let circom = builder.build().unwrap(); let circom = builder.build().unwrap();
let inputs = circom.get_public_inputs().unwrap(); let inputs = circom.get_public_inputs().unwrap();
println!("Public inputs {:#?} ", inputs); println!("Public inputs {:#?} ", inputs);
// We compute a default empty tree
// Probably better to pass it as parameter
let TREE_HEIGHT = 21;
let leaf = Field::from(0); let leaf = Field::from(0);
let tree = PoseidonTree::new(21, leaf); let tree = PoseidonTree::new(TREE_HEIGHT, leaf);
RLN { RLN {
circom, circom,
@ -68,6 +59,18 @@ impl RLN {
} }
} }
pub fn set_tree<R: Read>(&self, _input_data: R) -> io::Result<()> {
//Implement leaf and deserialization
//let leaf = Leaf::deserialize(input_data).unwrap();
//returns H::Hash, which is a 256 bit hash value
//let root = self.tree.root();
// TODO Return root as LE here
//root.write_le(&mut result_data)?;
//println!("NYI: root le write buffer {:#?}", root);
Ok(())
}
/// returns current membership root /// returns current membership root
/// * `root` is a scalar field element in 32 bytes /// * `root` is a scalar field element in 32 bytes
pub fn get_root<W: Write>(&self, _result_data: W) -> io::Result<()> { pub fn get_root<W: Write>(&self, _result_data: W) -> io::Result<()> {

68
rln/src/utils.rs Normal file
View File

@ -0,0 +1,68 @@
use ark_bn254::{Bn254, Fr, Parameters};
use ark_ff::{Fp256, PrimeField};
use ark_std::str::FromStr;
use ethers_core::utils::keccak256;
use num_bigint::{BigInt, BigUint, ToBigInt};
use semaphore::{identity::Identity, Field};
pub fn to_fr(el: Field) -> Fr {
Fr::try_from(el).unwrap()
}
pub fn to_field(el: Fr) -> Field {
el.try_into().unwrap()
}
pub fn vec_to_fr(v: Vec<Field>) -> Vec<Fr> {
let mut result: Vec<Fr> = vec![];
for el in v {
result.push(to_fr(el));
}
result
}
pub fn vec_to_field(v: Vec<Fr>) -> Vec<Field> {
let mut result: Vec<Field> = vec![];
for el in v {
result.push(to_field(el));
}
result
}
pub fn str_to_field(input: String, radix: i32) -> Field {
assert!((radix == 10) || (radix == 16));
// We remove any quote present and we trim
let input_clean = input.replace("\"", "");
let input_clean = input_clean.trim();
if radix == 10 {
Field::from_str(&format!(
"{:01$x}",
BigUint::from_str(input_clean).unwrap(),
64
))
.unwrap()
} else {
let input_clean = input_clean.replace("0x", "");
Field::from_str(&format!("{:0>64}", &input_clean)).unwrap()
}
}
// Arithmetic over Field elements (wrapped over arkworks algebra crate)
pub fn add(a: Field, b: Field) -> Field {
to_field(to_fr(a) + to_fr(b))
}
pub fn mul(a: Field, b: Field) -> Field {
to_field(to_fr(a) * to_fr(b))
}
pub fn div(a: Field, b: Field) -> Field {
to_field(to_fr(a) / to_fr(b))
}
pub fn inv(a: Field) -> Field {
to_field(Fr::from(1) / to_fr(a))
}