Skip to content

Commit 06e2013

Browse files
committed
add caching for jwk.n hash
1 parent d109a5c commit 06e2013

3 files changed

Lines changed: 89 additions & 16 deletions

File tree

fastcrypto-zkp/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ harness = false
1515
[[bench]]
1616
name = "zklogin"
1717
harness = false
18+
required-features = ["test-utils"]
1819

1920
[[bench]]
2021
name = "poseidon"
@@ -64,3 +65,4 @@ proptest = "1.1.0"
6465

6566
[features]
6667
e2e = []
68+
test-utils = []

fastcrypto-zkp/benches/zklogin.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ mod zklogin_benches {
88

99
use ark_std::rand::rngs::StdRng;
1010
use ark_std::rand::SeedableRng;
11-
use criterion::Criterion;
11+
use criterion::{BatchSize, Criterion};
1212
use fastcrypto::ed25519::Ed25519KeyPair;
1313
use fastcrypto::error::FastCryptoError;
1414
use fastcrypto::rsa::{Base64UrlUnpadded, Encoding};
1515
use fastcrypto::traits::KeyPair;
1616
use fastcrypto_zkp::bn254::utils::gen_address_seed;
17+
use fastcrypto_zkp::bn254::zk_login::clear_cache_for_testing;
1718
use fastcrypto_zkp::bn254::zk_login::ZkLoginInputs;
1819
use fastcrypto_zkp::bn254::zk_login::JWK;
1920
use fastcrypto_zkp::bn254::zk_login::{JwkId, OIDCProvider};
@@ -106,18 +107,38 @@ mod zklogin_benches {
106107
},
107108
);
108109

109-
// Benchmark the entire `verify_zk_login` function.
110-
c.bench_function("verify_zk_login", move |b| {
110+
// Benchmark the entire `verify_zk_login` function (warm: modulus hash cache hit).
111+
let input_warm = input.clone();
112+
let eph_warm = eph_pubkey.clone();
113+
let map_warm = map.clone();
114+
c.bench_function("verify_zk_login/warm", move |b| {
111115
b.iter(|| {
112116
fastcrypto_zkp::bn254::zk_login_api::verify_zk_login(
113-
&input,
117+
&input_warm,
114118
max_epoch,
115-
&eph_pubkey,
116-
&map,
119+
&eph_warm,
120+
&map_warm,
117121
&ZkLoginEnv::Test,
118122
)
119123
})
120124
});
125+
126+
// Benchmark `verify_zk_login` on a cold cache (first call after a JWK refresh).
127+
c.bench_function("verify_zk_login/cold", move |b| {
128+
b.iter_batched(
129+
clear_cache_for_testing,
130+
|_| {
131+
fastcrypto_zkp::bn254::zk_login_api::verify_zk_login(
132+
&input,
133+
max_epoch,
134+
&eph_pubkey,
135+
&map,
136+
&ZkLoginEnv::Test,
137+
)
138+
},
139+
BatchSize::SmallInput,
140+
)
141+
});
121142
}
122143

123144
/// Benchmark V2 proof verification for 8192-bit RSA keys
@@ -199,18 +220,38 @@ mod zklogin_benches {
199220
},
200221
);
201222

202-
// Benchmark the entire `verify_zk_login` function.
203-
c.bench_function("verify_zk_login_v2", move |b| {
223+
// Benchmark the entire `verify_zk_login` function (warm: modulus hash cache hit).
224+
let input_warm = input.clone();
225+
let eph_warm = eph_pubkey.clone();
226+
let map_warm = map.clone();
227+
c.bench_function("verify_zk_login_v2/warm", move |b| {
204228
b.iter(|| {
205229
fastcrypto_zkp::bn254::zk_login_api::verify_zk_login(
206-
&input,
230+
&input_warm,
207231
max_epoch,
208-
&eph_pubkey,
209-
&map,
232+
&eph_warm,
233+
&map_warm,
210234
&ZkLoginEnv::Test,
211235
)
212236
})
213237
});
238+
239+
// Benchmark `verify_zk_login` on a cold cache (first call after a JWK refresh).
240+
c.bench_function("verify_zk_login_v2/cold", move |b| {
241+
b.iter_batched(
242+
clear_cache_for_testing,
243+
|_| {
244+
fastcrypto_zkp::bn254::zk_login_api::verify_zk_login(
245+
&input,
246+
max_epoch,
247+
&eph_pubkey,
248+
&map,
249+
&ZkLoginEnv::Test,
250+
)
251+
},
252+
BatchSize::SmallInput,
253+
)
254+
});
214255
}
215256

216257
criterion_group! {

fastcrypto-zkp/src/bn254/zk_login.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,47 @@ pub use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
2121
use fastcrypto::error::FastCryptoError;
2222
use itertools::Itertools;
2323
use num_bigint::BigUint;
24+
use once_cell::sync::Lazy;
2425
use regex::Regex;
2526
use schemars::JsonSchema;
2627
use serde::{Deserialize, Serialize};
2728
use std::cmp::Ordering::{Equal, Greater, Less};
29+
use std::collections::HashMap;
2830
use std::error::Error;
2931
use std::fmt::Display;
3032
use std::str::FromStr;
33+
use std::sync::RwLock;
34+
35+
/// Key for the modulus hash cache: (modulus bytes, max_rsa_bits).
36+
type ModulusHashKey = (Vec<u8>, u16);
37+
38+
/// JWKs rotate occasionally, so caching by (modulus bytes, max_rsa_bits) avoids recomputing
39+
/// bit-packing + poseidon hash on every verification.
40+
static MODULUS_HASH_CACHE: Lazy<RwLock<HashMap<ModulusHashKey, Bn254Fr>>> =
41+
Lazy::new(|| RwLock::new(HashMap::new()));
42+
43+
fn cached_modulus_hash(modulus: &[u8], max_rsa_bits: u16) -> Result<Bn254Fr, FastCryptoError> {
44+
if let Some(f) = MODULUS_HASH_CACHE
45+
.read()
46+
.ok()
47+
.and_then(|m| m.get(&(modulus.to_vec(), max_rsa_bits)).copied())
48+
{
49+
return Ok(f);
50+
}
51+
let f = hash_to_field(&[BigUint::from_bytes_be(modulus)], max_rsa_bits, PACK_WIDTH)?;
52+
if let Ok(mut m) = MODULUS_HASH_CACHE.write() {
53+
m.insert((modulus.to_vec(), max_rsa_bits), f);
54+
}
55+
Ok(f)
56+
}
57+
58+
/// Clear the modulus hash cache for testing only benchmark.
59+
#[cfg(any(test, feature = "test-utils"))]
60+
pub fn clear_cache_for_testing() {
61+
if let Ok(mut m) = MODULUS_HASH_CACHE.write() {
62+
m.clear();
63+
}
64+
}
3165

3266
#[cfg(test)]
3367
#[path = "unit_tests/zk_login_tests.rs"]
@@ -589,11 +623,7 @@ impl ZkLoginInputs {
589623
let iss_base64_f =
590624
hash_ascii_str_to_field(&self.iss_base64_details.value, config.max_iss_len_b64)?;
591625
let header_f = hash_ascii_str_to_field(&self.header_base64, config.max_header_len_b64)?;
592-
let modulus_f = hash_to_field(
593-
&[BigUint::from_bytes_be(modulus)],
594-
config.max_rsa_bits,
595-
PACK_WIDTH,
596-
)?;
626+
let modulus_f = cached_modulus_hash(modulus, config.max_rsa_bits)?;
597627
poseidon_zk_login(&[
598628
first,
599629
second,

0 commit comments

Comments
 (0)