diff --git a/rln/Cargo.toml b/rln/Cargo.toml index e57072e..68b0c8c 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -17,6 +17,7 @@ num-bigint = { version = "0.4", default-features = false, features = ["rand"] } # ZKP Generation ark-ec = { version = "0.3.0", default-features = false, features = ["parallel"] } ark-ff = { version = "0.3.0", default-features = false, features = ["parallel", "asm"] } +#ark-ff = { git = "https://github.com/arkworks-rs/algebra/"} ark-std = { version = "0.3.0", default-features = false, features = ["parallel"] } ark-bn254 = { version = "0.3.0" } ark-groth16 = { git = "https://github.com/arkworks-rs/groth16", rev = "765817f", features = ["parallel"] } @@ -64,7 +65,9 @@ blake2 = "0.8.1" sapling-crypto = { package = "sapling-crypto_ce", version = "0.1.3", default-features = false } bellman = { package = "bellman_ce", version = "0.3.4", default-features = false } -semaphore = { git = "https://github.com/oskarth/semaphore-rs" } +#semaphore = { git = "https://github.com/oskarth/semaphore-rs" } +semaphore = { git = "https://github.com/worldcoin/semaphore-rs", rev = "d462a43"} + tempfile = "3.3.0" diff --git a/rln/resources/rln.r1cs b/rln/resources/rln.r1cs index 461315b..dad7d2a 100644 Binary files a/rln/resources/rln.r1cs and b/rln/resources/rln.r1cs differ diff --git a/rln/resources/rln.wasm b/rln/resources/rln.wasm index d70d315..0a55196 100644 Binary files a/rln/resources/rln.wasm and b/rln/resources/rln.wasm differ diff --git a/rln/resources/rln_final.zkey b/rln/resources/rln_final.zkey index 6972bf7..2edcc67 100644 Binary files a/rln/resources/rln_final.zkey and b/rln/resources/rln_final.zkey differ diff --git a/rln/resources/rln_final.zkey_old b/rln/resources/rln_final.zkey_old new file mode 100644 index 0000000..e19abc6 Binary files /dev/null and b/rln/resources/rln_final.zkey_old differ diff --git a/rln/src/circuit.rs b/rln/src/circuit.rs index e7bca47..9188008 100644 --- a/rln/src/circuit.rs +++ b/rln/src/circuit.rs @@ -1,28 +1,137 @@ -/// Adapted from semaphore-rs -use ark_bn254::{Bn254, Fr}; -use ark_circom::{read_zkey, WitnessCalculator}; -use ark_groth16::ProvingKey; +use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine, G2Projective}; +use ark_circom::{read_zkey, CircomBuilder, CircomConfig, WitnessCalculator}; +use ark_ff::BigInteger256; +use ark_groth16::{ProvingKey, VerifyingKey}; use ark_relations::r1cs::ConstraintMatrices; use core::include_bytes; -use once_cell::sync::Lazy; +use num_bigint::BigUint; +use serde_json::Value; +use std::convert::TryFrom; +use std::fs::File; use std::io::{Cursor, Write}; -use tempfile::NamedTempFile; +use std::path::Path; +use std::str::FromStr; -const ZKEY_BYTES: &[u8] = include_bytes!("../resources/rln_final.zkey"); -const WASM: &[u8] = include_bytes!("../resources/rln.wasm"); +const ZKEY_PATH: &str = "./resources/rln_final.zkey"; +const VK_PATH: &str = "./resources/verifying_key.json"; +const R1CS_PATH: &str = "./resources/rln.r1cs"; +const WASM_PATH: &str = "./resources/rln.wasm"; -pub static ZKEY: Lazy<(ProvingKey, ConstraintMatrices)> = Lazy::new(|| { - let mut reader = Cursor::new(ZKEY_BYTES); - read_zkey(&mut reader).expect("zkey should be valid") -}); +pub fn ZKEY() -> ProvingKey /*, ConstraintMatrices)*/ { + let mut file = File::open(ZKEY_PATH).unwrap(); + let (proving_key, _matrices) = read_zkey(&mut file).unwrap(); + proving_key +} -pub static WITNESS_CALCULATOR: Lazy = Lazy::new(|| { - // HACK: ark-circom requires a file, so we make one! - let mut tmpfile = NamedTempFile::new().expect("Failed to create temp file"); - let written = tmpfile.write(WASM).expect("Failed to write to temp file"); - assert_eq!(written, WASM.len()); - let path = tmpfile.into_temp_path(); - let result = WitnessCalculator::new(&path).expect("Failed to create witness calculator"); - path.close().expect("Could not remove tempfile"); - result -}); +pub fn VK() -> VerifyingKey { + let verifying_key: VerifyingKey; + + if Path::new(VK_PATH).exists() { + verifying_key = vk_from_json(VK_PATH); + verifying_key + } else if Path::new(ZKEY_PATH).exists() { + verifying_key = ZKEY().vk; + verifying_key + } else { + panic!("No proving/verification key present!"); + } +} + +pub fn CIRCOM() -> CircomBuilder { + // Load the WASM and R1CS for witness and proof generation + let cfg = CircomConfig::::new(WASM_PATH, R1CS_PATH).unwrap(); // should be )?; but need to address "the trait `From` is not implemented for `protocol::ProofError`" + // We build and return the circuit + CircomBuilder::new(cfg) +} + +// Utilities to convert a json verification key in a groth16::VerificationKey +fn fq_from_str(s: &str) -> Fq { + Fq::try_from(BigUint::from_str(s).unwrap()).unwrap() //was BigInteger256:: and .into() +} + +// Extracts the element in G1 corresponding to its JSON serialization +fn json_to_g1(json: &Value, key: &str) -> G1Affine { + let els: Vec = json + .get(key) + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|i| i.as_str().unwrap().to_string()) + .collect(); + G1Affine::from(G1Projective::new( + fq_from_str(&els[0]), + fq_from_str(&els[1]), + fq_from_str(&els[2]), + )) +} + +// Extracts the vector of G1 elements corresponding to its JSON serialization +fn json_to_g1_vec(json: &Value, key: &str) -> Vec { + let els: Vec> = json + .get(key) + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|i| { + i.as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect::>() + }) + .collect(); + + els.iter() + .map(|coords| { + G1Affine::from(G1Projective::new( + fq_from_str(&coords[0]), + fq_from_str(&coords[1]), + fq_from_str(&coords[2]), + )) + }) + .collect() +} + +// Extracts the element in G2 corresponding to its JSON serialization +fn json_to_g2(json: &Value, key: &str) -> G2Affine { + let els: Vec> = json + .get(key) + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|i| { + i.as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect::>() + }) + .collect(); + + let x = Fq2::new(fq_from_str(&els[0][0]), fq_from_str(&els[0][1])); + let y = Fq2::new(fq_from_str(&els[1][0]), fq_from_str(&els[1][1])); + let z = Fq2::new(fq_from_str(&els[2][0]), fq_from_str(&els[2][1])); + G2Affine::from(G2Projective::new(x, y, z)) +} + +// Computes the verification key from its JSON serialization +fn vk_from_json(vk_path: &str) -> VerifyingKey { + let json = std::fs::read_to_string(vk_path).unwrap(); + let json: Value = serde_json::from_str(&json).unwrap(); + + VerifyingKey { + alpha_g1: json_to_g1(&json, "vk_alpha_1"), + beta_g2: json_to_g2(&json, "vk_beta_2"), + gamma_g2: json_to_g2(&json, "vk_gamma_2"), + delta_g2: json_to_g2(&json, "vk_delta_2"), + gamma_abc_g1: json_to_g1_vec(&json, "IC"), + } +} + +// Checks verification key to be correct with respect to proving key +pub fn check_vk_from_zkey(verifying_key: VerifyingKey) { + assert_eq!(ZKEY().vk, verifying_key); +} diff --git a/rln/src/lib.rs b/rln/src/lib.rs index c189db2..7a2aefb 100644 --- a/rln/src/lib.rs +++ b/rln/src/lib.rs @@ -1,22 +1,16 @@ #![allow(dead_code)] #![allow(unused_imports)] -pub mod ffi; -pub mod public; - +use crate::circuit::{CIRCOM, VK, ZKEY}; use ark_bn254::{Fr, Parameters}; use ark_ec::bn::Bn; +use ark_std::str::FromStr; pub mod circuit; +pub mod ffi; pub mod protocol; - -pub type Field = Fr; -pub type Groth16Proof = ark_groth16::Proof>; -pub type EthereumGroth16Proof = ark_circom::ethereum::Proof; - -// RLN lib -pub mod merkle; -pub mod poseidon; +pub mod public; +pub mod utils; #[cfg(test)] mod test { @@ -25,116 +19,192 @@ mod test { use hex_literal::hex; use num_bigint::BigInt; use semaphore::{ - hash::Hash, hash_to_field, identity::Identity, poseidon_tree::PoseidonTree, Field, + hash::Hash, hash_to_field, identity::Identity, poseidon_hash, poseidon_tree::PoseidonTree, + Field, }; #[test] + // We test Merkle Tree generation, proofs and verification fn test_merkle_proof() { - let leaf = Field::from(0); + let tree_height = 16; + let leaf_index = 3; // generate identity - let id = Identity::from_seed(b"hello"); + // We follow zk-kit approach for identity generation + let id = Identity::from_seed(b"test-merkle-proof"); + let identity_secret = poseidon_hash(&vec![id.trapdoor, id.nullifier]); + let id_commitment = poseidon_hash(&vec![identity_secret]); // generate merkle tree - let mut tree = PoseidonTree::new(21, leaf); - tree.set(0, id.commitment()); + let default_leaf = Field::from(0); + let mut tree = PoseidonTree::new(tree_height, default_leaf); + tree.set(leaf_index, id_commitment.into()); - let merkle_proof = tree.proof(0).expect("proof should exist"); - let root: Field = tree.root().into(); + // We check correct computation of the root + let root = tree.root(); + assert_eq!( + root, + Field::from_str("0x27401a4559ce263630907ce3b77c570649e28ede22d2a7f5296839627a16e870") + .unwrap() + ); - println!("Root: {:#}", root); - println!("Merkle proof: {:#?}", merkle_proof); + let merkle_proof = tree.proof(leaf_index).expect("proof should exist"); + let path_elements = get_path_elements(&merkle_proof); + let identity_path_index = get_identity_path_index(&merkle_proof); + + // We check correct computation of the path and indexes + let expected_path_elements = vec![ + Field::from_str("0x0000000000000000000000000000000000000000000000000000000000000000") + .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 = + 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] - fn test_semaphore() { - let leaf = Field::from(0); + // 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", + "11286972368698509976183087595462810875513684078608517520839298933882497716792", + "3607627140608796879659380071776844901612302623152076817094415224584923813162", + "19712377064642672829441595136074946683621277828620209496774504837737984048981", + "20775607673010627194014556968476266066927294572720319469184847051418138353016", + "3396914609616007258851405644437304192397291162432396347162513310381425243293", + "21551820661461729022865262380882070649935529853313286572328683688269863701601", + "6573136701248752079028194407151022595060682063033565181951145966236778420039", + "12413880268183407374852357075976609371175688755676981206018884971008854919922", + "14271763308400718165336499097156975241954733520325982997864342600795471836726", + "20066985985293572387227381049700832219069292839614107140851619262827735677018", + "9394776414966240069580838672673694685292165040808226440647796406499139370960", + "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" + } + "#; - // generate identity + // We generate all relevant keys + let proving_key = &ZKEY(); + let verification_key = &VK(); + let builder = CIRCOM(); + + // We compute witness from the json input example + let rln_witness = rln_witness_from_json(input_json_str); + + // Let's generate a zkSNARK proof + let proof = generate_proof(builder, proving_key, &rln_witness).unwrap(); + + let proof_values = proof_values_from_witness(&rln_witness); + + // Let's verify the proof + let verified = verify_proof(verification_key, proof, &proof_values); + + assert!(verified.unwrap()); + } + + #[test] + // We test a RLN proof generation and verification + fn test_end_to_end() { + let tree_height = 16; + let leaf_index = 3; + + // Generate identity + // We follow zk-kit approach for identity generation let id = Identity::from_seed(b"hello"); + let identity_secret = poseidon_hash(&vec![id.trapdoor, id.nullifier]); + let id_commitment = poseidon_hash(&vec![identity_secret]); - // generate merkle tree - let mut tree = PoseidonTree::new(21, leaf); - tree.set(0, id.commitment()); + //// generate merkle tree + let default_leaf = Field::from(0); + let mut tree = PoseidonTree::new(tree_height, default_leaf); + tree.set(leaf_index, id_commitment.into()); - let merkle_proof = tree.proof(0).expect("proof should exist"); - let root = tree.root().into(); + let merkle_proof = tree.proof(leaf_index).expect("proof should exist"); - // 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 signal = b"hey hey"; + let x = hash_to_field(signal); - let nullifier_hash = - semaphore::protocol::generate_nullifier_hash(&id, external_nullifier_hash); + // We set the remaining values to random ones + let epoch = hash_to_field(b"test-epoch"); + let rln_identifier = hash_to_field(b"test-rln-identifier"); - let proof = semaphore::protocol::generate_proof( - &id, - &merkle_proof, - external_nullifier_hash, - signal_hash, - ) - .unwrap(); + let rln_witness: RLNWitnessInput = + rln_witness_from_values(identity_secret, &merkle_proof, x, epoch, rln_identifier); - let success = semaphore::protocol::verify_proof( - root, - nullifier_hash, - signal_hash, - external_nullifier_hash, - &proof, - ) - .unwrap(); + // We generate all relevant keys + let proving_key = &ZKEY(); + let verification_key = &VK(); + let builder = CIRCOM(); + + // Let's generate a zkSNARK proof + let proof = generate_proof(builder, proving_key, &rln_witness).unwrap(); + + let proof_values = proof_values_from_witness(&rln_witness); + + // Let's verify the proof + let success = verify_proof(verification_key, proof, &proof_values).unwrap(); assert!(success); } - - #[ignore] - #[test] - fn test_end_to_end() { - let leaf = Field::from(0); - - // generate identity - let id = Identity::from_seed(b"hello"); - - // generate merkle tree - let mut tree = PoseidonTree::new(21, leaf); - tree.set(0, id.commitment().into()); - - let merkle_proof = tree.proof(0).expect("proof should exist"); - let root = tree.root().into(); - - println!("Root: {:#}", root); - println!("Merkle proof: {:#?}", merkle_proof); - - // 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 nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash); - - let proof = - generate_proof(&id, &merkle_proof, external_nullifier_hash, signal_hash).unwrap(); - - println!("Proof: {:#?}", proof); - - // TODO Make this test pass - // - // Currently fails at: - // thread 'test::test_end_to_end' panicked at 'called `Result::unwrap()` - // on an `Err` value: SynthesisError(MalformedVerifyingKey)', - // rln/src/lib.rs:62:84 - // - // Not sure why this is MalformedVerifyingKey, though the proof is - // likely incorrect with wrong fields in protocol.rs - // - // Indeed: - // if (public_inputs.len() + 1) != pvk.vk.gamma_abc_g1.len() { - let success = verify_proof( - root, - nullifier_hash, - signal_hash, - external_nullifier_hash, - &proof, - ) - .unwrap(); - } } diff --git a/rln/src/main.rs b/rln/src/main.rs index 38e120d..3489dda 100644 --- a/rln/src/main.rs +++ b/rln/src/main.rs @@ -1,38 +1,15 @@ -/// This is basic entry point for `cargo run` to play around with proof, -/// outputs, etc. -/// -use ark_circom::{CircomBuilder, CircomConfig}; -use ark_std::rand::thread_rng; use color_eyre::Result; -use ark_bn254::Bn254; -use ark_groth16::{ - create_random_proof, generate_random_parameters, prepare_verifying_key, verify_proof, -}; - -use num_bigint::BigInt; - // Tracing use ark_relations::r1cs::{ConstraintLayer, ConstraintTrace, TracingMode}; -//use tracing::{event, span, Level}; use tracing_subscriber::layer::SubscriberExt; // JSON -use serde::Deserialize; -//use serde_json; -#[derive(Debug, Deserialize)] -//#[serde(rename_all = "camelCase")] -struct WitnessInput { - identity_secret: String, - path_elements: Vec, - identity_path_index: Vec, - x: String, - epoch: String, - rln_identifier: String, -} - -// TODO: This should use public.rs as much as possible +use rln::circuit::{CIRCOM, VK, ZKEY}; +use rln::protocol::{ + generate_proof, proof_values_from_witness, rln_witness_from_json, verify_proof, +}; // RLN fn groth16_proof_example() -> Result<()> { @@ -45,122 +22,68 @@ fn groth16_proof_example() -> Result<()> { let trace = ConstraintTrace::capture(); println!("Trace is: {:?}", trace); - let cfg = CircomConfig::::new("./resources/rln.wasm", "./resources/rln.r1cs")?; - - // Test - let trace = ConstraintTrace::capture(); - println!("Trace is: {:?}", trace); - // 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", - "11286972368698509976183087595462810875513684078608517520839298933882497716792", - "3607627140608796879659380071776844901612302623152076817094415224584923813162", - "19712377064642672829441595136074946683621277828620209496774504837737984048981", - "20775607673010627194014556968476266066927294572720319469184847051418138353016", - "3396914609616007258851405644437304192397291162432396347162513310381425243293", - "21551820661461729022865262380882070649935529853313286572328683688269863701601", - "6573136701248752079028194407151022595060682063033565181951145966236778420039", - "12413880268183407374852357075976609371175688755676981206018884971008854919922", - "14271763308400718165336499097156975241954733520325982997864342600795471836726", - "20066985985293572387227381049700832219069292839614107140851619262827735677018", - "9394776414966240069580838672673694685292165040808226440647796406499139370960", - "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" - } -"#; + { + "identity_secret": "12825549237505733615964533204745049909430608936689388901883576945030025938736", + "path_elements": [ + "18622655742232062119094611065896226799484910997537830749762961454045300666333", + "20590447254980891299813706518821659736846425329007960381537122689749540452732", + "7423237065226347324353380772367382631490014989348495481811164164159255474657", + "11286972368698509976183087595462810875513684078608517520839298933882497716792", + "3607627140608796879659380071776844901612302623152076817094415224584923813162", + "19712377064642672829441595136074946683621277828620209496774504837737984048981", + "20775607673010627194014556968476266066927294572720319469184847051418138353016", + "3396914609616007258851405644437304192397291162432396347162513310381425243293", + "21551820661461729022865262380882070649935529853313286572328683688269863701601", + "6573136701248752079028194407151022595060682063033565181951145966236778420039", + "12413880268183407374852357075976609371175688755676981206018884971008854919922", + "14271763308400718165336499097156975241954733520325982997864342600795471836726", + "20066985985293572387227381049700832219069292839614107140851619262827735677018", + "9394776414966240069580838672673694685292165040808226440647796406499139370960", + "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 witness_input: WitnessInput = - serde_json::from_str(input_json_str).expect("JSON was not well-formatted"); + // We generate all relevant keys + let proving_key = &ZKEY(); + let verification_key = &VK(); + let builder = CIRCOM(); - println!("Witness input JSON: {:?}", witness_input); + // We compute witness from the json input example + let rln_witness = rln_witness_from_json(input_json_str); - let mut builder = CircomBuilder::new(cfg); + // Let's generate a zkSNARK proof + let proof = generate_proof(builder, proving_key, &rln_witness).unwrap(); - builder.push_input( - "identity_secret", - BigInt::parse_bytes(witness_input.identity_secret.as_bytes(), 10).unwrap(), - ); + let proof_values = proof_values_from_witness(&rln_witness); - for v in witness_input.path_elements.iter() { - builder.push_input( - "path_elements", - BigInt::parse_bytes(v.as_bytes(), 10).unwrap(), - ); - } + // Let's verify the proof + let verified = verify_proof(verification_key, proof, &proof_values); - for v in witness_input.identity_path_index.iter() { - builder.push_input("identity_path_index", BigInt::from(*v)); - } - - builder.push_input( - "x", - BigInt::parse_bytes(witness_input.x.as_bytes(), 10).unwrap(), - ); - - builder.push_input( - "epoch", - BigInt::parse_bytes( - witness_input.epoch.strip_prefix("0x").unwrap().as_bytes(), - 16, - ) - .unwrap(), - ); - - builder.push_input( - "rln_identifier", - BigInt::parse_bytes(witness_input.rln_identifier.as_bytes(), 10).unwrap(), - ); - - println!("Builder input:\n {:#?}", builder.inputs); - - // create an empty instance for setting it up - let circom = builder.setup(); - - let mut rng = thread_rng(); - let params = generate_random_parameters::(circom, &mut rng)?; - - let circom = builder.build()?; - - let inputs = circom.get_public_inputs().unwrap(); - - println!("Public inputs {:#?} ", inputs); - - let proof = create_random_proof(circom, ¶ms, &mut rng)?; - - println!("Proof: {:?}", proof); - - let pvk = prepare_verifying_key(¶ms.vk); - - let verified = verify_proof(&pvk, &proof, &inputs)?; - - assert!(verified); + assert!(verified.unwrap()); Ok(()) } @@ -168,7 +91,6 @@ fn groth16_proof_example() -> Result<()> { fn main() { println!("rln example proof"); - // Tornado-core match groth16_proof_example() { Ok(_) => println!("Success"), Err(_) => println!("Error"), diff --git a/rln/src/merkle.rs b/rln/src/merkle.rs deleted file mode 100644 index 3b8737b..0000000 --- a/rln/src/merkle.rs +++ /dev/null @@ -1,227 +0,0 @@ -// Adapted from https://github.com/kilic/rln/blob/master/src/merkle.rs -// -use crate::poseidon::{Poseidon as Hasher, PoseidonParams}; -// TODO Replace these with arkworks -use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; -use sapling_crypto::bellman::pairing::Engine; -use std::io::{self, Error, ErrorKind}; -use std::{collections::HashMap, hash::Hash}; - -pub struct IncrementalMerkleTree -where - E: Engine, -{ - pub current_index: usize, - merkle_tree: MerkleTree, -} - -impl IncrementalMerkleTree -where - E: Engine, -{ - pub fn empty(hasher: Hasher, depth: usize) -> Self { - let mut zero: Vec = Vec::with_capacity(depth + 1); - zero.push(E::Fr::from_str("0").unwrap()); - for i in 0..depth { - zero.push(hasher.hash([zero[i]; 2].to_vec())); - } - zero.reverse(); - let merkle_tree = MerkleTree { - hasher: hasher, - zero: zero.clone(), - depth: depth, - nodes: HashMap::new(), - }; - let current_index: usize = 0; - IncrementalMerkleTree { - current_index, - merkle_tree, - } - } - - pub fn update_next(&mut self, leaf: E::Fr) -> io::Result<()> { - self.merkle_tree.update(self.current_index, leaf)?; - self.current_index += 1; - Ok(()) - } - - pub fn delete(&mut self, index: usize) -> io::Result<()> { - let zero = E::Fr::from_str("0").unwrap(); - self.merkle_tree.update(index, zero)?; - Ok(()) - } - - pub fn get_witness(&self, index: usize) -> io::Result> { - if index >= self.current_index { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "index exceeds incremental index", - )); - } - self.merkle_tree.get_witness(index) - } - - pub fn hash(&self, inputs: Vec) -> E::Fr { - self.merkle_tree.hasher.hash(inputs) - } - - pub fn check_inclusion( - &self, - witness: Vec<(E::Fr, bool)>, - leaf_index: usize, - ) -> io::Result { - if leaf_index >= self.current_index { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "index exceeds incremental index", - )); - } - self.merkle_tree.check_inclusion(witness, leaf_index) - } - - pub fn get_root(&self) -> E::Fr { - return self.merkle_tree.get_root(); - } -} - -pub struct MerkleTree -where - E: Engine, -{ - pub hasher: Hasher, - pub depth: usize, - zero: Vec, - nodes: HashMap<(usize, usize), E::Fr>, -} - -impl MerkleTree -where - E: Engine, -{ - pub fn empty(hasher: Hasher, depth: usize) -> Self { - let mut zero: Vec = Vec::with_capacity(depth + 1); - zero.push(E::Fr::from_str("0").unwrap()); - for i in 0..depth { - zero.push(hasher.hash([zero[i]; 2].to_vec())); - } - zero.reverse(); - MerkleTree { - hasher: hasher, - zero: zero.clone(), - depth: depth, - nodes: HashMap::new(), - } - } - - pub fn set_size(&self) -> usize { - 1 << self.depth - } - - pub fn update(&mut self, index: usize, leaf: E::Fr) -> io::Result<()> { - if index >= self.set_size() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "index exceeds set size", - )); - } - self.nodes.insert((self.depth, index), leaf); - self.recalculate_from(index); - Ok(()) - } - - pub fn check_inclusion(&self, witness: Vec<(E::Fr, bool)>, index: usize) -> io::Result { - if index >= self.set_size() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "index exceeds set size", - )); - } - let mut acc = self.get_node(self.depth, index); - - for w in witness.into_iter() { - if w.1 { - acc = self.hasher.hash(vec![acc, w.0]); - } else { - acc = self.hasher.hash(vec![w.0, acc]); - } - } - Ok(acc.eq(&self.get_root())) - } - - pub fn get_root(&self) -> E::Fr { - return self.get_node(0, 0); - } - - pub fn get_witness(&self, index: usize) -> io::Result> { - if index >= self.set_size() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "index exceeds set size", - )); - } - let mut witness = Vec::<(E::Fr, bool)>::with_capacity(self.depth); - let mut i = index; - let mut depth = self.depth; - loop { - i ^= 1; - witness.push((self.get_node(depth, i), (i & 1 == 1))); - i >>= 1; - depth -= 1; - if depth == 0 { - break; - } - } - assert_eq!(i, 0); - Ok(witness) - } - - fn get_node(&self, depth: usize, index: usize) -> E::Fr { - let node = *self - .nodes - .get(&(depth, index)) - .unwrap_or_else(|| &self.zero[depth]); - node - } - - fn get_leaf(&self, index: usize) -> E::Fr { - self.get_node(self.depth, index) - } - - fn hash_couple(&mut self, depth: usize, index: usize) -> E::Fr { - let b = index & !1; - self.hasher - .hash([self.get_node(depth, b), self.get_node(depth, b + 1)].to_vec()) - } - - fn recalculate_from(&mut self, index: usize) { - let mut i = index; - let mut depth = self.depth; - loop { - let h = self.hash_couple(depth, i); - i >>= 1; - depth -= 1; - self.nodes.insert((depth, i), h); - if depth == 0 { - break; - } - } - assert_eq!(depth, 0); - assert_eq!(i, 0); - } -} - -#[test] -fn test_merkle_set() { - let data: Vec = (0..8) - .map(|s| Fr::from_str(&format!("{}", s)).unwrap()) - .collect(); - use sapling_crypto::bellman::pairing::bn256::{Bn256, Fr, FrRepr}; - let params = PoseidonParams::::new(8, 55, 3, None, None, None); - let hasher = Hasher::new(params); - let mut set = MerkleTree::empty(hasher.clone(), 3); - let leaf_index = 6; - let leaf = hasher.hash(vec![data[0]]); - set.update(leaf_index, leaf).unwrap(); - let witness = set.get_witness(leaf_index).unwrap(); - assert!(set.check_inclusion(witness, leaf_index).unwrap()); -} diff --git a/rln/src/poseidon.rs b/rln/src/poseidon.rs deleted file mode 100644 index 47045da..0000000 --- a/rln/src/poseidon.rs +++ /dev/null @@ -1,233 +0,0 @@ -// Adapted from https://github.com/kilic/rln/blob/master/src/poseidon.rs -// -use blake2::{Blake2s, Digest}; - -use sapling_crypto::bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr}; -use sapling_crypto::bellman::pairing::Engine; - -// TODO: Using arkworks libs here instead -//use ff::{Field, PrimeField, PrimeFieldRepr}; -//use ark_ec::{PairingEngine as Engine}; - -#[derive(Clone)] -pub struct PoseidonParams { - rf: usize, - rp: usize, - t: usize, - round_constants: Vec, - mds_matrix: Vec, -} - -#[derive(Clone)] -pub struct Poseidon { - params: PoseidonParams, -} - -impl PoseidonParams { - pub fn new( - rf: usize, - rp: usize, - t: usize, - round_constants: Option>, - mds_matrix: Option>, - seed: Option>, - ) -> PoseidonParams { - let seed = match seed { - Some(seed) => seed, - None => b"".to_vec(), - }; - - let _round_constants = match round_constants { - Some(round_constants) => round_constants, - None => PoseidonParams::::generate_constants(b"drlnhdsc", seed.clone(), rf + rp), - }; - assert_eq!(rf + rp, _round_constants.len()); - - let _mds_matrix = match mds_matrix { - Some(mds_matrix) => mds_matrix, - None => PoseidonParams::::generate_mds_matrix(b"drlnhdsm", seed.clone(), t), - }; - PoseidonParams { - rf, - rp, - t, - round_constants: _round_constants, - mds_matrix: _mds_matrix, - } - } - - pub fn width(&self) -> usize { - return self.t; - } - - pub fn partial_round_len(&self) -> usize { - return self.rp; - } - - pub fn full_round_half_len(&self) -> usize { - return self.rf / 2; - } - - pub fn total_rounds(&self) -> usize { - return self.rf + self.rp; - } - - pub fn round_constant(&self, round: usize) -> E::Fr { - return self.round_constants[round]; - } - - pub fn mds_matrix_row(&self, i: usize) -> Vec { - let w = self.width(); - self.mds_matrix[i * w..(i + 1) * w].to_vec() - } - - pub fn mds_matrix(&self) -> Vec { - self.mds_matrix.clone() - } - - pub fn generate_mds_matrix(persona: &[u8; 8], seed: Vec, t: usize) -> Vec { - let v: Vec = PoseidonParams::::generate_constants(persona, seed, t * 2); - let mut matrix: Vec = Vec::with_capacity(t * t); - for i in 0..t { - for j in 0..t { - let mut tmp = v[i]; - tmp.add_assign(&v[t + j]); - let entry = tmp.inverse().unwrap(); - matrix.insert((i * t) + j, entry); - } - } - matrix - } - - pub fn generate_constants(persona: &[u8; 8], seed: Vec, len: usize) -> Vec { - let mut constants: Vec = Vec::new(); - let mut source = seed.clone(); - loop { - let mut hasher = Blake2s::new(); - hasher.input(persona); - hasher.input(source); - source = hasher.result().to_vec(); - let mut candidate_repr = ::Repr::default(); - candidate_repr.read_le(&source[..]).unwrap(); - if let Ok(candidate) = E::Fr::from_repr(candidate_repr) { - constants.push(candidate); - if constants.len() == len { - break; - } - } - } - constants - } -} - -impl Poseidon { - pub fn new(params: PoseidonParams) -> Poseidon { - Poseidon { params } - } - - pub fn hash(&self, inputs: Vec) -> E::Fr { - let mut state = inputs.clone(); - state.resize(self.t(), E::Fr::zero()); - let mut round_counter: usize = 0; - loop { - self.round(&mut state, round_counter); - round_counter += 1; - if round_counter == self.params.total_rounds() { - break; - } - } - state[0] - } - - fn t(&self) -> usize { - self.params.t - } - - fn round(&self, state: &mut Vec, round: usize) { - let a1 = self.params.full_round_half_len(); - let a2 = a1 + self.params.partial_round_len(); - let a3 = self.params.total_rounds(); - if round < a1 { - self.full_round(state, round); - } else if round >= a1 && round < a2 { - self.partial_round(state, round); - } else if round >= a2 && round < a3 { - if round == a3 - 1 { - self.full_round_last(state); - } else { - self.full_round(state, round); - } - } else { - panic!("should not be here") - } - } - - fn full_round(&self, state: &mut Vec, round: usize) { - self.add_round_constants(state, round); - self.apply_quintic_sbox(state, true); - self.mul_mds_matrix(state); - } - - fn full_round_last(&self, state: &mut Vec) { - let last_round = self.params.total_rounds() - 1; - self.add_round_constants(state, last_round); - self.apply_quintic_sbox(state, true); - } - - fn partial_round(&self, state: &mut Vec, round: usize) { - self.add_round_constants(state, round); - self.apply_quintic_sbox(state, false); - self.mul_mds_matrix(state); - } - - fn add_round_constants(&self, state: &mut Vec, round: usize) { - for (_, b) in state.iter_mut().enumerate() { - let c = self.params.round_constants[round]; - b.add_assign(&c); - } - } - - fn apply_quintic_sbox(&self, state: &mut Vec, full: bool) { - for s in state.iter_mut() { - let mut b = s.clone(); - b.square(); - b.square(); - s.mul_assign(&b); - if !full { - break; - } - } - } - - fn mul_mds_matrix(&self, state: &mut Vec) { - let w = self.params.t; - let mut new_state = vec![E::Fr::zero(); w]; - for (i, ns) in new_state.iter_mut().enumerate() { - for (j, s) in state.iter().enumerate() { - let mut tmp = s.clone(); - tmp.mul_assign(&self.params.mds_matrix[i * w + j]); - ns.add_assign(&tmp); - } - } - for (i, ns) in new_state.iter_mut().enumerate() { - state[i].clone_from(ns); - } - } -} - -#[test] -fn test_poseidon_hash() { - use sapling_crypto::bellman::pairing::bn256; - use sapling_crypto::bellman::pairing::bn256::{Bn256, Fr}; - let params = PoseidonParams::::new(8, 55, 3, None, None, None); - let hasher = Poseidon::::new(params); - let input1: Vec = ["0"].iter().map(|e| Fr::from_str(e).unwrap()).collect(); - let r1: Fr = hasher.hash(input1); - let input2: Vec = ["0", "0"] - .iter() - .map(|e| Fr::from_str(e).unwrap()) - .collect(); - let r2: Fr = hasher.hash(input2.to_vec()); - // println!("{:?}", r1); - assert_eq!(r1, r2, "just to see if internal state resets"); -} diff --git a/rln/src/protocol.rs b/rln/src/protocol.rs index 5bc002a..ba9ccab 100644 --- a/rln/src/protocol.rs +++ b/rln/src/protocol.rs @@ -1,14 +1,15 @@ -/// Adapted from semaphore-rs -use crate::circuit::{WITNESS_CALCULATOR, ZKEY}; -use ark_bn254::{Bn254, Parameters}; -use ark_circom::CircomReduction; +use crate::circuit::{VK, ZKEY}; +use ark_bn254::{Bn254, Fr, Parameters}; +use ark_circom::{read_zkey, CircomBuilder, CircomConfig, CircomReduction}; use ark_ec::bn::Bn; use ark_ff::{Fp256, PrimeField}; use ark_groth16::{ - create_proof_with_reduction_and_matrices, prepare_verifying_key, Proof as ArkProof, + create_proof_with_reduction_and_matrices, create_random_proof_with_reduction, + prepare_verifying_key, verify_proof as ark_verify_proof, Proof as ArkProof, ProvingKey, + VerifyingKey, }; 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 ethers_core::utils::keccak256; use num_bigint::{BigInt, BigUint, ToBigInt}; @@ -24,7 +25,190 @@ use serde::{Deserialize, Serialize}; use std::time::Instant; use thiserror::Error; -// TODO Fields need to be updated to RLN based ones +pub use crate::utils::{add, bytes_to_field, mul, str_to_field, vec_to_field, vec_to_fr}; + +/////////////////////////////////////////////////////// +// RLN Witness data structure and utility functions +/////////////////////////////////////////////////////// + +#[derive(Debug, Deserialize, Clone)] +pub struct RLNWitnessInput { + identity_secret: Field, + path_elements: Vec, + identity_path_index: Vec, + x: Field, + epoch: Field, + rln_identifier: Field, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct RLNProofValues { + // Public outputs: + y: Field, + nullifier: Field, + root: Field, + // Public Inputs: + x: Field, + epoch: Field, + rln_identifier: Field, +} + +pub fn rln_witness_from_json(input_json_str: &str) -> RLNWitnessInput { + let input_json: serde_json::Value = + serde_json::from_str(input_json_str).expect("JSON was not well-formatted"); + + let identity_secret = str_to_field(input_json["identity_secret"].to_string(), 10); + + let mut path_elements: Vec = 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 = 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( + identity_secret: Field, + merkle_proof: &merkle_tree::Proof, + x: Field, + epoch: Field, + rln_identifier: Field, +) -> RLNWitnessInput { + let path_elements = get_path_elements(merkle_proof); + let identity_path_index = get_identity_path_index(merkle_proof); + + RLNWitnessInput { + identity_secret, + path_elements, + identity_path_index, + x, + epoch, + rln_identifier, + } +} + +pub fn proof_values_from_witness(rln_witness: &RLNWitnessInput) -> RLNProofValues { + // y share + let a_0 = rln_witness.identity_secret; + let a_1 = poseidon_hash(&[a_0, rln_witness.epoch]); + let y = mul(rln_witness.x, a_1); + let y = add(y, a_0); + + // Nullifier + let nullifier = poseidon_hash(&[a_1, rln_witness.rln_identifier]); + + // Merkle tree root computations + let mut root = poseidon_hash(&[rln_witness.identity_secret]); + for i in 0..rln_witness.identity_path_index.len() { + if rln_witness.identity_path_index[i] == 0 { + root = poseidon_hash(&[root, rln_witness.path_elements[i]]); + } else { + root = poseidon_hash(&[rln_witness.path_elements[i], root]); + } + } + + let root = get_tree_root( + rln_witness.identity_secret, + &rln_witness.path_elements, + &rln_witness.identity_path_index, + true, + ); + + RLNProofValues { + y, + nullifier, + root, + x: rln_witness.x, + epoch: rln_witness.epoch, + rln_identifier: rln_witness.rln_identifier, + } +} + +/////////////////////////////////////////////////////// +// Merkle tree utility functions +/////////////////////////////////////////////////////// + +/// Helper to merkle proof into a bigint vector +/// TODO: we should create a From trait for this +pub fn get_path_elements(proof: &merkle_tree::Proof) -> Vec { + proof + .0 + .iter() + .map(|x| match x { + Branch::Left(value) | Branch::Right(value) => *value, + }) + .collect() +} + +pub fn get_identity_path_index(proof: &merkle_tree::Proof) -> Vec { + proof + .0 + .iter() + .map(|branch| match branch { + Branch::Left(_) => 0, + Branch::Right(_) => 1, + }) + .collect() +} + +pub fn get_tree_root( + leaf: Field, + path_elements: &[Field], + identity_path_index: &[u8], + hash_leaf: bool, +) -> Field { + let mut root = leaf; + if hash_leaf { + root = poseidon_hash(&[root]); + } + + for i in 0..identity_path_index.len() { + if identity_path_index[i] == 0 { + root = poseidon_hash(&[root, path_elements[i]]); + } else { + root = poseidon_hash(&[path_elements[i], root]); + } + } + + root +} + +/////////////////////////////////////////////////////// +// Signal/nullifier utility functions +/////////////////////////////////////////////////////// + +fn hash_signal(signal: &[u8]) -> Field { + let hash = keccak256(signal); + bytes_to_field(&hash) +} + +/// Generates the nullifier hash +#[must_use] +pub fn generate_nullifier_hash(identity: &Identity, external_nullifier: Field) -> Field { + poseidon_hash(&[external_nullifier, identity.nullifier]) +} + +/////////////////////////////////////////////////////// +// Proof data structure and utility functions +/////////////////////////////////////////////////////// // Matches the private G1Tup type in ark-circom. pub type G1 = (U256, U256); @@ -66,32 +250,9 @@ impl From for ArkProof> { } } -/// 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) -> Vec { - proof - .0 - .iter() - .map(|x| match x { - Branch::Left(value) | Branch::Right(value) => *value, - }) - .collect() -} - -/// Internal helper to hash the signal to make sure it's in the field -fn hash_signal(signal: &[u8]) -> Field { - let hash = keccak256(signal); - // Shift right one byte to make it fit in the field - let mut bytes = [0_u8; 32]; - bytes[1..].copy_from_slice(&hash[..31]); - Field::from_be_bytes_mod_order(&bytes) -} - -/// Generates the nullifier hash -#[must_use] -pub fn generate_nullifier_hash(identity: &Identity, external_nullifier: Field) -> Field { - poseidon_hash(&[external_nullifier, identity.nullifier]) -} +/////////////////////////////////////////////////////// +// zkSNARK utility functions +/////////////////////////////////////////////////////// #[derive(Error, Debug)] pub enum ProofError { @@ -103,121 +264,86 @@ pub enum ProofError { SynthesisError(#[from] SynthesisError), } -// 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) -// - -// TODO Change API here -/// Generates a semaphore proof +/// Generates a RLN proof /// /// # Errors /// /// Returns a [`ProofError`] if proving fails. pub fn generate_proof( - identity: &Identity, - merkle_proof: &merkle_tree::Proof, - external_nullifier_hash: Field, - signal_hash: Field, + mut builder: CircomBuilder, + proving_key: &ProvingKey, + rln_witness: &RLNWitnessInput, ) -> Result { - // TODO Fix inputs - // Semaphore genWitness corresponds to these - // RLN different, should be: - // identity_secret - // path_elements (merkleProof.siblings)) - // identity_path_index (merkleProof.pathIndices) - // x (RLN.genSignalHash(signal), assuming shouldHash is true) - // epoch - // rln_identifier - let inputs = [ - // FIXME should be identity_secret, not just nullifier! - ("identity_secret", vec![identity.nullifier]), - //("identityTrapdoor", vec![identity.trapdoor]), - ("path_elements", merkle_proof_to_vec(merkle_proof)), - ("identity_path_index", merkle_proof.path_index()), - ("externalNullifier", vec![external_nullifier_hash]), - // XXX: Assuming signal is hashed - ("x", vec![signal_hash]), - // FIXME epoch just hardcoded to random value - ("epoch", vec![signal_hash]), - // FIXME rln_identifier just hardcoded to random value - ("rln_identifier", vec![signal_hash]), - ]; - let inputs = inputs.into_iter().map(|(name, values)| { - ( - name.to_string(), - values.iter().copied().map(Into::into).collect::>(), - ) - }); - let now = Instant::now(); - let full_assignment = WITNESS_CALCULATOR - .clone() - .calculate_witness_element::(inputs, false) - .map_err(ProofError::WitnessError)?; + builder.push_input("identity_secret", BigInt::from(rln_witness.identity_secret)); + + for v in rln_witness.path_elements.iter() { + builder.push_input("path_elements", BigInt::from(*v)); + } + + for v in rln_witness.identity_path_index.iter() { + builder.push_input("identity_path_index", BigInt::from(*v)); + } + + builder.push_input("x", BigInt::from(rln_witness.x)); + + builder.push_input("epoch", BigInt::from(rln_witness.epoch)); + + builder.push_input("rln_identifier", BigInt::from(rln_witness.rln_identifier)); + + let circom = builder.build().unwrap(); + + // This can be checked against proof_values_from_witness + // Get the populated instance of the circuit with the witness + //let inputs = vec_to_field(circom.get_public_inputs().unwrap()); println!("witness generation took: {:.2?}", now.elapsed()); - let mut rng = thread_rng(); - let rng = &mut rng; - - let r = ark_bn254::Fr::rand(rng); - let s = ark_bn254::Fr::rand(rng); - let now = Instant::now(); - let ark_proof = create_proof_with_reduction_and_matrices::<_, CircomReduction>( - &ZKEY.0, - r, - s, - &ZKEY.1, - ZKEY.1.num_instance_variables, - ZKEY.1.num_constraints, - full_assignment.as_slice(), - )?; + // Generate a random proof + let mut rng = thread_rng(); + + let ark_proof = create_random_proof_with_reduction::<_, _, _, CircomReduction>( + circom, + proving_key, + &mut rng, + ) + .unwrap(); + let proof = ark_proof.into(); + println!("proof generation took: {:.2?}", now.elapsed()); Ok(proof) } -// TODO Update API here - -/// Verifies a given semaphore proof +/// Verifies a given RLN proof /// /// # Errors /// /// Returns a [`ProofError`] if verifying fails. Verification failure does not /// necessarily mean the proof is incorrect. pub fn verify_proof( - root: Field, - nullifier_hash: Field, - signal_hash: Field, - external_nullifier_hash: Field, - proof: &Proof, + verifying_key: &VerifyingKey, + proof: Proof, + proof_values: &RLNProofValues, ) -> Result { - // XXX: Why is verification key in zkey but that's not what is used in - // verifyProof with verification_key.json? Is there a difference? - let pvk = prepare_verifying_key(&ZKEY.0.vk); - - // TODO Update this, should be: - // XXX This is returned from the proof! Why is it called yShare here? - // Isn't this publicOutput? - // publicSignals 0..5 in specific order: - // yShare - // merkleRoot - // internalNullifier - // signalHash - // epoch - // rlnIdentifier - let public_inputs = vec![ - root.into(), - nullifier_hash.into(), - signal_hash.into(), - external_nullifier_hash.into(), + // We re-arrange proof-values according to the circuit specification + let inputs = vec![ + proof_values.y, + proof_values.root, + proof_values.nullifier, + proof_values.x, + proof_values.epoch, + proof_values.rln_identifier, ]; - let ark_proof = (*proof).into(); - let result = ark_groth16::verify_proof(&pvk, &ark_proof, &public_inputs[..])?; - Ok(result) + + // Check that the proof is valid + let pvk = prepare_verifying_key(verifying_key); + let pr: ArkProof = proof.into(); + let verified = ark_verify_proof(&pvk, &pr, &vec_to_fr(inputs))?; + + Ok(verified) } diff --git a/rln/src/public.rs b/rln/src/public.rs index 30a7bcf..20ea812 100644 --- a/rln/src/public.rs +++ b/rln/src/public.rs @@ -1,34 +1,24 @@ /// This is the main public API for RLN. It is used by the FFI, and should be /// used by tests etc as well /// -use crate::merkle::IncrementalMerkleTree; -use crate::poseidon::{Poseidon as PoseidonHasher, PoseidonParams}; -use semaphore::{ - hash_to_field, identity::Identity, poseidon_tree::PoseidonTree, protocol::*, Field, -}; - -use ark_circom::{CircomBuilder, CircomCircuit, CircomConfig}; -use ark_std::rand::thread_rng; - use ark_bn254::Bn254; +use ark_circom::{CircomBuilder, CircomCircuit, CircomConfig}; use ark_groth16::{ create_random_proof as prove, generate_random_parameters, prepare_verifying_key, verify_proof, Proof, ProvingKey, }; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -// , SerializationError}; - -use std::io::{self, Read, Write}; - +use ark_std::rand::thread_rng; use num_bigint::BigInt; - -// JSON +use semaphore::{ + hash_to_field, identity::Identity, poseidon_tree::PoseidonTree, protocol::*, Field, +}; use serde::Deserialize; use serde_json; +use std::io::{self, Read, Write}; -// For RLN Rust version -//use bellman::pairing::ff::{Field, PrimeField, PrimeFieldRepr, ScalarEngine}; -//use sapling_crypto::bellman::pairing::bn256::Bn256; +use crate::circuit::{CIRCOM, ZKEY}; +use crate::protocol; // TODO Add Engine here? i.e. not // TODO Assuming we want to use IncrementalMerkleTree, figure out type/trait conversions @@ -36,22 +26,9 @@ use serde_json; pub struct RLN { circom: CircomCircuit, params: ProvingKey, - // RLN Rust version - //tree: IncrementalMerkleTree, tree: PoseidonTree, } -#[derive(Debug, Deserialize)] -//#[serde(rename_all = "camelCase")] -struct WitnessInput { - identity_secret: String, - path_elements: Vec, - identity_path_index: Vec, - x: String, - epoch: String, - rln_identifier: String, -} - // TODO Expand API to have better coverage of things needed impl RLN { @@ -60,21 +37,20 @@ impl RLN { let cfg = CircomConfig::::new("./resources/rln.wasm", "./resources/rln.r1cs").unwrap(); - let mut builder = CircomBuilder::new(cfg); + let builder = CircomBuilder::new(cfg); - // create an empty instance for setting it up - let circom = builder.setup(); - - let mut rng = thread_rng(); - let params = generate_random_parameters::(circom, &mut rng).unwrap(); + let params = ZKEY(); let circom = builder.build().unwrap(); let inputs = circom.get_public_inputs().unwrap(); 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 mut tree = PoseidonTree::new(21, leaf); + let tree = PoseidonTree::new(TREE_HEIGHT, leaf); RLN { circom, @@ -83,134 +59,21 @@ impl RLN { } } - // XXX This is a tempory hack to get end to end proving/verification working - // Not supposed to be part of public API - pub fn new_json_spike() -> RLN { - let cfg = - CircomConfig::::new("./resources/rln.wasm", "./resources/rln.r1cs").unwrap(); + pub fn set_tree(&self, _input_data: R) -> io::Result<()> { + //Implement leaf and deserialization + //let leaf = Leaf::deserialize(input_data).unwrap(); - // TODO Refactor - // 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", - "11286972368698509976183087595462810875513684078608517520839298933882497716792", - "3607627140608796879659380071776844901612302623152076817094415224584923813162", - "19712377064642672829441595136074946683621277828620209496774504837737984048981", - "20775607673010627194014556968476266066927294572720319469184847051418138353016", - "3396914609616007258851405644437304192397291162432396347162513310381425243293", - "21551820661461729022865262380882070649935529853313286572328683688269863701601", - "6573136701248752079028194407151022595060682063033565181951145966236778420039", - "12413880268183407374852357075976609371175688755676981206018884971008854919922", - "14271763308400718165336499097156975241954733520325982997864342600795471836726", - "20066985985293572387227381049700832219069292839614107140851619262827735677018", - "9394776414966240069580838672673694685292165040808226440647796406499139370960", - "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 witness_input: WitnessInput = - serde_json::from_str(input_json_str).expect("JSON was not well-formatted"); - - println!("Witness input JSON: {:?}", witness_input); - - let mut builder = CircomBuilder::new(cfg); - - builder.push_input( - "identity_secret", - BigInt::parse_bytes(witness_input.identity_secret.as_bytes(), 10).unwrap(), - ); - - for v in witness_input.path_elements.iter() { - builder.push_input( - "path_elements", - BigInt::parse_bytes(v.as_bytes(), 10).unwrap(), - ); - } - - for v in witness_input.identity_path_index.iter() { - builder.push_input("identity_path_index", BigInt::from(*v)); - } - - builder.push_input( - "x", - BigInt::parse_bytes(witness_input.x.as_bytes(), 10).unwrap(), - ); - - builder.push_input( - "epoch", - BigInt::parse_bytes( - witness_input.epoch.strip_prefix("0x").unwrap().as_bytes(), - 16, - ) - .unwrap(), - ); - - builder.push_input( - "rln_identifier", - BigInt::parse_bytes(witness_input.rln_identifier.as_bytes(), 10).unwrap(), - ); - - println!("Builder input:\n {:#?}", builder.inputs); - - // create an empty instance for setting it up - let circom = builder.setup(); - - let mut rng = thread_rng(); - let params = generate_random_parameters::(circom, &mut rng).unwrap(); - - let circom = builder.build().unwrap(); - - let inputs = circom.get_public_inputs().unwrap(); - - println!("Public inputs {:#?} ", inputs); - - // Sapling based tree - // // TODO Add as parameter(s) - // let merkle_depth: usize = 3; - // let poseidon_params = PoseidonParams::::new(8, 55, 3, None, None, None); - // let hasher = PoseidonHasher::new(poseidon_params.clone()); - // let tree = IncrementalMerkleTree::empty(hasher, merkle_depth); - - let leaf = Field::from(0); - let mut tree = PoseidonTree::new(21, leaf); - - RLN { - circom, - params, - tree, - } + //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 /// * `root` is a scalar field element in 32 bytes - pub fn get_root(&self, mut result_data: W) -> io::Result<()> { + pub fn get_root(&self, _result_data: W) -> io::Result<()> { //let root = self.tree.get_root(); // Converts PrimeFieldRepr into LE //root.into_repr().write_le(&mut result_data)?; @@ -262,26 +125,3 @@ impl Default for RLN { Self::new() } } - -// NOTE: Expensive test, ignoring by default -#[ignore] -#[test] -fn rln_proof() { - let rln = RLN::new(); - let rln_spike = RLN::new_json_spike(); - //let inputs = mul.circom.get_public_inputs().unwrap(); - - let mut output_data: Vec = Vec::new(); - let _ = rln_spike.prove(&mut output_data); - - let proof_data = &output_data[..]; - - // XXX Pass as arg? - //let pvk = prepare_verifying_key(&mul.params.vk); - - // XXX: Something is wrong here I think, because it doesn't verify with the - // full proof fields like yShare - just witness? Might be a bug - let verified = rln.verify(proof_data).unwrap(); - - assert!(verified); -} diff --git a/rln/src/utils.rs b/rln/src/utils.rs new file mode 100644 index 0000000..e83a833 --- /dev/null +++ b/rln/src/utils.rs @@ -0,0 +1,76 @@ +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) -> Vec { + let mut result: Vec = vec![]; + for el in v { + result.push(to_fr(el)); + } + result +} + +pub fn vec_to_field(v: Vec) -> Vec { + let mut result: Vec = 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() + } +} + +pub fn bytes_to_fr(input: &[u8]) -> Fr { + Fr::from(BigUint::from_bytes_le(input)) +} + +pub fn bytes_to_field(input: &[u8]) -> Field { + to_field(bytes_to_fr(input)) +} + +// 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)) +} diff --git a/semaphore/Cargo.toml b/semaphore/Cargo.toml index 3543d97..b6d0132 100644 --- a/semaphore/Cargo.toml +++ b/semaphore/Cargo.toml @@ -21,7 +21,7 @@ num-bigint = { version = "0.4", default-features = false, features = ["rand"] } once_cell = "1.8" primitive-types = "0.11.1" rand = "0.8.4" -semaphore = { git = "https://github.com/worldcoin/semaphore-rs" } +semaphore = { git = "https://github.com/worldcoin/semaphore-rs", rev = "d462a43"} serde = "1.0" thiserror = "1.0.0" wasmer = { version = "2.0" }