1use fastcrypto_zkp::bn254::zk_login::JwkId;
5use fastcrypto_zkp::bn254::zk_login::{JWK, OIDCProvider};
6use fastcrypto_zkp::bn254::zk_login_api::ZkLoginEnv;
7use im::hashmap::HashMap as ImHashMap;
8use itertools::Itertools as _;
9use mysten_common::debug_fatal;
10use nonempty::NonEmpty;
11use parking_lot::RwLock;
12use prometheus::{IntCounter, Registry, register_int_counter_with_registry};
13use shared_crypto::intent::Intent;
14use std::sync::Arc;
15use sui_types::address_alias;
16use sui_types::base_types::{SequenceNumber, SuiAddress};
17use sui_types::digests::SenderSignedDataDigest;
18use sui_types::digests::ZKLoginInputsDigest;
19use sui_types::signature_verification::{
20 VerifiedDigestCache, verify_sender_signed_data_message_signatures,
21};
22use sui_types::storage::ObjectStore;
23use sui_types::transaction::{SenderSignedData, TransactionDataAPI};
24use sui_types::{
25 committee::Committee,
26 crypto::{AuthoritySignInfoTrait, VerificationObligation},
27 error::{SuiErrorKind, SuiResult},
28 message_envelope::Message,
29 messages_checkpoint::SignedCheckpointSummary,
30 signature::VerifyParams,
31};
32use tracing::debug;
33
34pub struct SignatureVerifier {
38 committee: Arc<Committee>,
39 object_store: Arc<dyn ObjectStore + Send + Sync>,
40 signed_data_cache: VerifiedDigestCache<SenderSignedDataDigest, Vec<u8>>,
41 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
42
43 jwks: RwLock<ImHashMap<JwkId, JWK>>,
49
50 zk_login_params: ZkLoginParams,
52
53 enable_address_aliases: bool,
55
56 pub metrics: Arc<SignatureVerifierMetrics>,
57}
58
59#[derive(Clone)]
61struct ZkLoginParams {
62 pub supported_providers: Vec<OIDCProvider>,
64 pub env: ZkLoginEnv,
66 pub zklogin_circuit_mode: u64,
69 pub verify_legacy_zklogin_address: bool,
71 pub accept_zklogin_in_multisig: bool,
73 pub accept_passkey_in_multisig: bool,
75 pub zklogin_max_epoch_upper_bound_delta: Option<u64>,
77 pub additional_multisig_checks: bool,
79 pub validate_zklogin_public_identifier: bool,
81}
82
83impl SignatureVerifier {
84 pub fn new(
85 committee: Arc<Committee>,
86 object_store: Arc<dyn ObjectStore + Send + Sync>,
87 metrics: Arc<SignatureVerifierMetrics>,
88 supported_providers: Vec<OIDCProvider>,
89 zklogin_env: ZkLoginEnv,
90 zklogin_circuit_mode: u64,
91 verify_legacy_zklogin_address: bool,
92 accept_zklogin_in_multisig: bool,
93 accept_passkey_in_multisig: bool,
94 zklogin_max_epoch_upper_bound_delta: Option<u64>,
95 additional_multisig_checks: bool,
96 validate_zklogin_public_identifier: bool,
97 enable_address_aliases: bool,
98 ) -> Self {
99 Self {
100 committee,
101 object_store,
102 signed_data_cache: VerifiedDigestCache::new(
103 metrics.signed_data_cache_hits.clone(),
104 metrics.signed_data_cache_misses.clone(),
105 metrics.signed_data_cache_evictions.clone(),
106 ),
107 zklogin_inputs_cache: Arc::new(VerifiedDigestCache::new(
108 metrics.zklogin_inputs_cache_hits.clone(),
109 metrics.zklogin_inputs_cache_misses.clone(),
110 metrics.zklogin_inputs_cache_evictions.clone(),
111 )),
112 jwks: Default::default(),
113 enable_address_aliases,
114 metrics,
115 zk_login_params: ZkLoginParams {
116 supported_providers,
117 env: zklogin_env,
118 zklogin_circuit_mode,
119 verify_legacy_zklogin_address,
120 accept_zklogin_in_multisig,
121 accept_passkey_in_multisig,
122 zklogin_max_epoch_upper_bound_delta,
123 additional_multisig_checks,
124 validate_zklogin_public_identifier,
125 },
126 }
127 }
128
129 pub(crate) fn insert_jwk(&self, jwk_id: &JwkId, jwk: &JWK) {
132 let mut jwks = self.jwks.write();
133 match jwks.entry(jwk_id.clone()) {
134 im::hashmap::Entry::Occupied(_) => {
135 debug!("JWK with kid {:?} already exists", jwk_id);
136 }
137 im::hashmap::Entry::Vacant(entry) => {
138 debug!("inserting JWK with kid: {:?}", jwk_id);
139 entry.insert(jwk.clone());
140 }
141 }
142 }
143
144 pub fn has_jwk(&self, jwk_id: &JwkId, jwk: &JWK) -> bool {
145 let jwks = self.jwks.read();
146 jwks.get(jwk_id) == Some(jwk)
147 }
148
149 pub fn get_jwks(&self) -> ImHashMap<JwkId, JWK> {
150 self.jwks.read().clone()
151 }
152
153 pub fn verify_tx_with_current_aliases(
156 &self,
157 signed_tx: &SenderSignedData,
158 ) -> SuiResult<NonEmpty<(u8, Option<SequenceNumber>)>> {
159 let mut alias_versions_by_signer = Vec::new();
160 let mut aliases = Vec::new();
161
162 let signers = signed_tx.intent_message().value.required_signers();
164 for signer in signers {
165 if !self.enable_address_aliases {
166 alias_versions_by_signer.push((signer, None));
167 aliases.push((signer, NonEmpty::singleton(signer)));
168 } else {
169 let address_aliases =
171 address_alias::get_address_aliases_from_store(&self.object_store, signer)?;
172
173 alias_versions_by_signer.push((signer, address_aliases.as_ref().map(|(_, v)| *v)));
174 aliases.push((
175 signer,
176 address_aliases
177 .map(|(aliases, _)| {
178 NonEmpty::from_vec(aliases.aliases.contents.clone()).unwrap_or_else(
179 || {
180 debug_fatal!(
181 "AddressAliases struct has empty aliases field for signer {}",
182 signer
183 );
184 NonEmpty::singleton(signer)
185 },
186 )
187 })
188 .unwrap_or(NonEmpty::singleton(signer)),
189 ));
190 }
191 }
192
193 let sig_indices = self.verify_tx(signed_tx, &alias_versions_by_signer, aliases)?;
195
196 let result: Vec<(u8, Option<SequenceNumber>)> = sig_indices
198 .into_iter()
199 .zip_eq(alias_versions_by_signer.into_iter().map(|(_, seq)| seq))
200 .collect();
201
202 Ok(NonEmpty::from_vec(result).expect("must have at least one required_signer"))
203 }
204
205 pub fn verify_tx_require_no_aliases(&self, signed_tx: &SenderSignedData) -> SuiResult {
206 let current_aliases = self.verify_tx_with_current_aliases(signed_tx)?;
207 for (_, version) in current_aliases {
208 if version.is_some() {
209 return Err(SuiErrorKind::AliasesChanged.into());
210 }
211 }
212 Ok(())
213 }
214
215 fn verify_tx(
216 &self,
217 signed_tx: &SenderSignedData,
218 alias_versions: &Vec<(SuiAddress, Option<SequenceNumber>)>,
219 aliased_addresses: Vec<(SuiAddress, NonEmpty<SuiAddress>)>,
220 ) -> SuiResult<Vec<u8>> {
221 let digest = signed_tx.full_message_digest_with_alias_versions(alias_versions);
222
223 if let Some(indices) = self.signed_data_cache.get_cached(&digest) {
224 return Ok(indices);
225 }
226
227 let jwks = self.jwks.read().clone();
228 let verify_params = VerifyParams::new(
229 jwks,
230 self.zk_login_params.supported_providers.clone(),
231 self.zk_login_params.env,
232 self.zk_login_params.zklogin_circuit_mode,
233 self.zk_login_params.verify_legacy_zklogin_address,
234 self.zk_login_params.accept_zklogin_in_multisig,
235 self.zk_login_params.accept_passkey_in_multisig,
236 self.zk_login_params.zklogin_max_epoch_upper_bound_delta,
237 self.zk_login_params.additional_multisig_checks,
238 self.zk_login_params.validate_zklogin_public_identifier,
239 );
240 let indices = verify_sender_signed_data_message_signatures(
241 signed_tx,
242 self.committee.epoch(),
243 &verify_params,
244 self.zklogin_inputs_cache.clone(),
245 aliased_addresses,
246 )?;
247
248 self.signed_data_cache
249 .cache_with_value(digest, indices.clone());
250 Ok(indices)
251 }
252
253 pub fn clear_signature_cache(&self) {
254 self.signed_data_cache.clear();
255 self.zklogin_inputs_cache.clear();
256 }
257}
258
259pub struct SignatureVerifierMetrics {
260 pub signed_data_cache_hits: IntCounter,
261 pub signed_data_cache_misses: IntCounter,
262 pub signed_data_cache_evictions: IntCounter,
263 pub zklogin_inputs_cache_hits: IntCounter,
264 pub zklogin_inputs_cache_misses: IntCounter,
265 pub zklogin_inputs_cache_evictions: IntCounter,
266}
267
268impl SignatureVerifierMetrics {
269 pub fn new(registry: &Registry) -> Arc<Self> {
270 Arc::new(Self {
271 signed_data_cache_hits: register_int_counter_with_registry!(
272 "signed_data_cache_hits",
273 "Number of signed data which were known to be verified because of signature cache.",
274 registry
275 )
276 .unwrap(),
277 signed_data_cache_misses: register_int_counter_with_registry!(
278 "signed_data_cache_misses",
279 "Number of signed data which missed the signature cache.",
280 registry
281 )
282 .unwrap(),
283 signed_data_cache_evictions: register_int_counter_with_registry!(
284 "signed_data_cache_evictions",
285 "Number of times we evict a pre-existing signed data were known to be verified because of signature cache.",
286 registry
287 )
288 .unwrap(),
289 zklogin_inputs_cache_hits: register_int_counter_with_registry!(
290 "zklogin_inputs_cache_hits",
291 "Number of zklogin signature which were known to be partially verified because of zklogin inputs cache.",
292 registry
293 )
294 .unwrap(),
295 zklogin_inputs_cache_misses: register_int_counter_with_registry!(
296 "zklogin_inputs_cache_misses",
297 "Number of zklogin signatures which missed the zklogin inputs cache.",
298 registry
299 )
300 .unwrap(),
301 zklogin_inputs_cache_evictions: register_int_counter_with_registry!(
302 "zklogin_inputs_cache_evictions",
303 "Number of times we evict a pre-existing zklogin inputs digest that was known to be verified because of zklogin inputs cache.",
304 registry
305 )
306 .unwrap(),
307 })
308 }
309}
310
311pub(crate) fn batch_verify_checkpoints(
313 committee: &Committee,
314 checkpoints: &[&SignedCheckpointSummary],
315) -> SuiResult {
316 for ckpt in checkpoints {
317 ckpt.data().verify_epoch(committee.epoch())?;
318 }
319
320 let mut obligation = VerificationObligation::default();
321
322 for ckpt in checkpoints {
323 let idx = obligation.add_message(ckpt.data(), ckpt.epoch(), Intent::sui_app(ckpt.scope()));
324 ckpt.auth_sig()
325 .add_to_verification_obligation(committee, &mut obligation, idx)?;
326 }
327
328 obligation.verify_all()
329}