chore(rln): update dependencies and refactor code for compatibility (#291)

While publishing the release on crate io it turned out that we can't use
libraries without a version as it was for arc-circom.

During the upgrade to the new version it was also discovered that it is
possible to speed up input preparation for witness calculator by 4 times
by switching from bigint to Fr:


![image](https://github.com/user-attachments/assets/53962387-308b-4aae-8af2-dbd0d3f62369)

it was also checked that it is also possible to use iden3 as a
sub-module instead of copying code, but benchmarks showed that the new
iden3 version with u256 calculations and subsequent conversion of the
result to Fr is slower than the current implementation:


![image](https://github.com/user-attachments/assets/f950f089-b66a-4a13-a86f-f391caf32b4f)

----

- Updated dependencies to their latest versions, including ark-ff,
ark-bn254, ark-std, and others to 0.5.0.
- Refactored circuit and iden3calc modules to use Fr instead of BigInt
for better type consistency.
- Improved utility functions for type conversions between Fr and U256.
- Adjusted Cargo.toml files for rln and utils to reflect new dependency
versions and features.
- Enhanced documentation and comments for clarity on changes made.

This update ensures compatibility with the latest versions of the Ark
framework and improves overall code quality.
This commit is contained in:
Ekaterina Broslavskaya
2025-03-14 20:56:00 +07:00
committed by GitHub
parent ffd5851d7d
commit ba467d370c
9 changed files with 679 additions and 429 deletions

930
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,23 +15,21 @@ bench = false
# This flag disable cargo doctests, i.e. testing example code-snippets in documentation
doctest = false
[dependencies]
# ZKP Generation
ark-ec = { version = "=0.4.2", default-features = false }
ark-ff = { version = "=0.4.2", default-features = false, features = ["asm"] }
ark-std = { version = "=0.4.0", default-features = false }
ark-bn254 = { version = "=0.4.0" }
ark-groth16 = { version = "=0.4.0", features = [
ark-bn254 = { version = "0.5.0", features = ["std"] }
ark-ff = { version = "0.5.0", features = ["std", "asm"] }
ark-serialize = { version = "0.5.0", features = ["derive"] }
ark-ec = { version = "0.5.0", default-features = false }
ark-std = { version = "0.5.0", default-features = false }
ark-groth16 = { version = "0.5.0", features = [
"parallel",
], default-features = false }
ark-relations = { version = "=0.4.0", default-features = false, features = [
ark-relations = { version = "0.5.0", default-features = false, features = [
"std",
] }
ark-serialize = { version = "=0.4.2", default-features = false }
# v0.5.0 use all other ark 0.5.0 versions and they are not compatible with current code.
# master branch contains commit with compatible version
ark-circom = { git = "https://github.com/gakonst/ark-circom.git", rev = "7f80002" }
ark-circom = { version = "0.5.0" }
ark-r1cs-std = { version = "0.5.0" }
# error handling
color-eyre = "0.6.2"
@@ -40,7 +38,7 @@ thiserror = "2.0.11"
# utilities
byteorder = "1.4.3"
cfg-if = "1.0"
num-bigint = { version = "0.4.3", default-features = false, features = [
num-bigint = { version = "0.4.6", default-features = false, features = [
"rand",
] }
num-traits = "0.2.19"
@@ -48,26 +46,20 @@ once_cell = "1.19.0"
lazy_static = "1.4.0"
rand = "0.8.5"
rand_chacha = "0.3.1"
ruint = { version = "1.10.0", features = [
"rand",
"serde",
"ark-ff-04",
"num-bigint",
] }
ruint = { version = "1.12.4", features = ["rand", "serde", "ark-ff-04"] }
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
utils = { package = "zerokit_utils", path = "../utils/", default-features = false }
utils = { package = "zerokit_utils", version = "0.5.2", path = "../utils/", default-features = false }
# serialization
prost = "0.13.1"
serde_json = "1.0.138"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
document-features = { version = "=0.2.10", optional = true }
[dev-dependencies]
sled = "=0.34.7"
criterion = { version = "=0.4.0", features = ["html_reports"] }
sled = "0.34.7"
criterion = { version = "0.4.0", features = ["html_reports"] }
[features]
default = ["parallel", "pmtree-ft"]

View File

@@ -1,6 +1,5 @@
// This crate provides interfaces for the zero-knowledge circuit and keys
use crate::iden3calc::calc_witness;
use ::lazy_static::lazy_static;
use ark_bn254::{
Bn254, Fq as ArkFq, Fq2 as ArkFq2, Fr as ArkFr, G1Affine as ArkG1Affine,
@@ -10,7 +9,8 @@ use ark_groth16::{ProvingKey, VerifyingKey};
use ark_relations::r1cs::ConstraintMatrices;
use cfg_if::cfg_if;
use color_eyre::{Report, Result};
use num_bigint::BigInt;
use crate::iden3calc::calc_witness;
#[cfg(feature = "arkzkey")]
use {
@@ -97,7 +97,7 @@ pub fn check_vk_from_zkey(verifying_key: VerifyingKey<Curve>) -> Result<()> {
}
}
pub fn calculate_rln_witness<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
pub fn calculate_rln_witness<I: IntoIterator<Item = (String, Vec<Fr>)>>(
inputs: I,
graph_data: &[u8],
) -> Vec<Fr> {

View File

@@ -5,22 +5,22 @@ pub mod graph;
pub mod proto;
pub mod storage;
use ark_bn254::Fr;
use graph::Node;
use num_bigint::BigInt;
use ruint::aliases::U256;
use std::collections::HashMap;
use storage::deserialize_witnesscalc_graph;
use crate::circuit::Fr;
use graph::{fr_to_u256, Node};
pub type InputSignalsInfo = HashMap<String, (usize, usize)>;
pub fn calc_witness<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
pub fn calc_witness<I: IntoIterator<Item = (String, Vec<Fr>)>>(
inputs: I,
graph_data: &[u8],
) -> Vec<Fr> {
let inputs: HashMap<String, Vec<U256>> = inputs
.into_iter()
.map(|(key, value)| (key, value.iter().map(|v| U256::from(v)).collect()))
.map(|(key, value)| (key, value.iter().map(fr_to_u256).collect()))
.collect();
let (nodes, signals, input_mapping): (Vec<Node>, Vec<usize>, InputSignalsInfo) =

View File

@@ -1,22 +1,20 @@
// This file is based on the code by iden3. Its preimage can be found here:
// https://github.com/iden3/circom-witnesscalc/blob/5cb365b6e4d9052ecc69d4567fcf5bc061c20e94/src/graph.rs
use crate::iden3calc::proto;
use ark_bn254::Fr;
use ark_ff::{BigInt, BigInteger, One, PrimeField, Zero};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
use rand::Rng;
use ruint::aliases::U256;
use ruint::{aliases::U256, uint};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::error::Error;
use std::ops::{BitOr, BitXor, Deref};
use std::{
cmp::Ordering,
collections::HashMap,
ops::{BitAnd, Shl, Shr},
error::Error,
ops::{BitAnd, BitOr, BitXor, Deref, Shl, Shr},
};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
use ruint::uint;
use crate::circuit::Fr;
use crate::iden3calc::proto;
pub const M: U256 =
uint!(21888242871839275222246405745257275088548364400416034343698204186575808495617_U256);
@@ -40,6 +38,16 @@ where
a.map_err(serde::de::Error::custom)
}
#[inline(always)]
pub fn fr_to_u256(x: &Fr) -> U256 {
U256::from_limbs(x.into_bigint().0)
}
#[inline(always)]
pub fn u256_to_fr(x: &U256) -> Fr {
Fr::from_bigint(BigInt::new(x.into_limbs())).expect("Failed to convert U256 to Fr")
}
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Operation {
Mul,
@@ -125,14 +133,18 @@ impl Operation {
if b.is_zero() {
Fr::zero()
} else {
Fr::new((Into::<U256>::into(a) / Into::<U256>::into(b)).into())
let a_u256 = fr_to_u256(&a);
let b_u256 = fr_to_u256(&b);
u256_to_fr(&(a_u256 / b_u256))
}
}
Mod => {
if b.is_zero() {
Fr::zero()
} else {
Fr::new((Into::<U256>::into(a) % Into::<U256>::into(b)).into())
let a_u256 = fr_to_u256(&a);
let b_u256 = fr_to_u256(&b);
u256_to_fr(&(a_u256 % b_u256))
}
}
Eq => match a.cmp(&b) {
@@ -143,10 +155,10 @@ impl Operation {
Ordering::Equal => Fr::zero(),
_ => Fr::one(),
},
Lt => Fr::new(u_lt(&a.into(), &b.into()).into()),
Gt => Fr::new(u_gt(&a.into(), &b.into()).into()),
Leq => Fr::new(u_lte(&a.into(), &b.into()).into()),
Geq => Fr::new(u_gte(&a.into(), &b.into()).into()),
Lt => u256_to_fr(&u_lt(&fr_to_u256(&a), &fr_to_u256(&b))),
Gt => u256_to_fr(&u_gt(&fr_to_u256(&a), &fr_to_u256(&b))),
Leq => u256_to_fr(&u_lte(&fr_to_u256(&a), &fr_to_u256(&b))),
Geq => u256_to_fr(&u_gte(&fr_to_u256(&a), &fr_to_u256(&b))),
Land => {
if a.is_zero() || b.is_zero() {
Fr::zero()
@@ -415,9 +427,9 @@ pub fn evaluate(nodes: &[Node], inputs: &[U256], outputs: &[usize]) -> Vec<Fr> {
let mut values = Vec::with_capacity(nodes.len());
for &node in nodes.iter() {
let value = match node {
Node::Constant(c) => Fr::new(c.into()),
Node::Constant(c) => u256_to_fr(&c),
Node::MontConstant(c) => c,
Node::Input(i) => Fr::new(inputs[i].into()),
Node::Input(i) => u256_to_fr(&inputs[i]),
Node::Op(op, a, b) => op.eval_fr(values[a], values[b]),
Node::UnoOp(op, a) => op.eval_fr(values[a]),
Node::TresOp(op, a, b, c) => op.eval_fr(values[a], values[b], values[c]),
@@ -633,7 +645,7 @@ pub fn montgomery_form(nodes: &mut [Node]) {
use Node::*;
use Operation::*;
match node {
Constant(c) => *node = MontConstant(Fr::new((*c).into())),
Constant(c) => *node = MontConstant(u256_to_fr(c)),
MontConstant(..) => (),
Input(..) => (),
Op(
@@ -659,10 +671,8 @@ fn shl(a: Fr, b: Fr) -> Fr {
}
let n = b.into_bigint().0[0] as u32;
let mut a = a.into_bigint();
a.muln(n);
Fr::from_bigint(a).unwrap()
let a = a.into_bigint();
Fr::from_bigint(a << n).unwrap()
}
fn shr(a: Fr, b: Fr) -> Fr {

View File

@@ -1,16 +1,17 @@
// This file is based on the code by iden3. Its preimage can be found here:
// https://github.com/iden3/circom-witnesscalc/blob/5cb365b6e4d9052ecc69d4567fcf5bc061c20e94/src/storage.rs
use ark_bn254::Fr;
use ark_ff::PrimeField;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use prost::Message;
use std::io::{Read, Write};
use crate::iden3calc::{
graph,
graph::{Operation, TresOperation, UnoOperation},
proto, InputSignalsInfo,
};
use ark_bn254::Fr;
use ark_ff::PrimeField;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use prost::Message;
use std::io::{Read, Write};
// format of the wtns.graph file:
// + magic line: wtns.graph.001

View File

@@ -1,9 +1,9 @@
// This crate collects all the underlying primitives used to implement RLN
use ark_bn254::Fr;
use ark_circom::CircomReduction;
use ark_groth16::{prepare_verifying_key, Groth16, Proof as ArkProof, ProvingKey, VerifyingKey};
use ark_relations::r1cs::ConstraintMatrices;
use ark_relations::r1cs::SynthesisError;
use ark_relations::r1cs::{ConstraintMatrices, SynthesisError};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{rand::thread_rng, UniformRand};
use color_eyre::{Report, Result};
@@ -16,9 +16,8 @@ use std::time::Instant;
use thiserror::Error;
use tiny_keccak::{Hasher as _, Keccak};
use crate::circuit::{calculate_rln_witness, Curve, Fr};
use crate::hashers::hash_to_field;
use crate::hashers::poseidon_hash;
use crate::circuit::{calculate_rln_witness, Curve};
use crate::hashers::{hash_to_field, poseidon_hash};
use crate::poseidon_tree::*;
use crate::public::RLN_IDENTIFIER;
use crate::utils::*;
@@ -606,40 +605,23 @@ pub fn generate_proof_with_witness(
/// Returns an error if `rln_witness.message_id` is not within `rln_witness.user_message_limit`.
pub fn inputs_for_witness_calculation(
rln_witness: &RLNWitnessInput,
) -> Result<[(&str, Vec<BigInt>); 7]> {
) -> Result<[(&str, Vec<Fr>); 7]> {
message_id_range_check(&rln_witness.message_id, &rln_witness.user_message_limit)?;
// We convert the path indexes to field elements
// TODO: check if necessary
let mut path_elements = Vec::new();
for v in rln_witness.path_elements.iter() {
path_elements.push(to_bigint(v)?);
}
let mut identity_path_index = Vec::new();
let mut identity_path_index = Vec::with_capacity(rln_witness.identity_path_index.len());
rln_witness
.identity_path_index
.iter()
.for_each(|v| identity_path_index.push(BigInt::from(*v)));
.for_each(|v| identity_path_index.push(Fr::from(*v)));
Ok([
(
"identitySecret",
vec![to_bigint(&rln_witness.identity_secret)?],
),
(
"userMessageLimit",
vec![to_bigint(&rln_witness.user_message_limit)?],
),
("messageId", vec![to_bigint(&rln_witness.message_id)?]),
("pathElements", path_elements),
("identitySecret", vec![rln_witness.identity_secret]),
("userMessageLimit", vec![rln_witness.user_message_limit]),
("messageId", vec![rln_witness.message_id]),
("pathElements", rln_witness.path_elements.clone()),
("identityPathIndex", identity_path_index),
("x", vec![to_bigint(&rln_witness.x)?]),
(
"externalNullifier",
vec![to_bigint(&rln_witness.external_nullifier)?],
),
("x", vec![rln_witness.x]),
("externalNullifier", vec![rln_witness.external_nullifier]),
])
}

View File

@@ -1,6 +1,5 @@
// This crate provides cross-module useful utilities (mainly type conversions) not necessarily specific to RLN
use crate::circuit::Fr;
use ark_ff::PrimeField;
use color_eyre::{Report, Result};
use num_bigint::{BigInt, BigUint};
@@ -8,6 +7,8 @@ use num_traits::Num;
use serde_json::json;
use std::io::Cursor;
use crate::circuit::Fr;
#[inline(always)]
pub fn to_bigint(el: &Fr) -> Result<BigInt> {
Ok(BigUint::from(*el).into())

View File

@@ -1,6 +1,6 @@
[package]
name = "zerokit_utils"
version = "0.5.1"
version = "0.5.2"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Various utilities for Zerokit"
@@ -12,19 +12,19 @@ repository = "https://github.com/vacp2p/zerokit"
bench = false
[dependencies]
ark-ff = { version = "=0.4.2", default-features = false, features = ["asm"] }
num-bigint = { version = "0.4.3", default-features = false, features = [
ark-ff = { version = "0.5.0", features = ["asm"] }
num-bigint = { version = "0.4.6", default-features = false, features = [
"rand",
] }
color-eyre = "0.6.2"
pmtree = { package = "vacp2p_pmtree", version = "=2.0.2", optional = true }
sled = "=0.34.7"
sled = "0.34.7"
serde = "1.0"
lazy_static = "1.4.0"
hex = "0.4"
[dev-dependencies]
ark-bn254 = "=0.4.0"
ark-bn254 = { version = "0.5.0", features = ["std"] }
num-traits = "0.2.19"
hex-literal = "0.3.4"
tiny-keccak = { version = "2.0.2", features = ["keccak"] }