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 verify_legacy_zklogin_address: bool,
68 pub accept_zklogin_in_multisig: bool,
70 pub accept_passkey_in_multisig: bool,
72 pub zklogin_max_epoch_upper_bound_delta: Option<u64>,
74 pub additional_multisig_checks: bool,
76 pub validate_zklogin_public_identifier: bool,
78}
79
80impl SignatureVerifier {
81 pub fn new(
82 committee: Arc<Committee>,
83 object_store: Arc<dyn ObjectStore + Send + Sync>,
84 metrics: Arc<SignatureVerifierMetrics>,
85 supported_providers: Vec<OIDCProvider>,
86 zklogin_env: ZkLoginEnv,
87 verify_legacy_zklogin_address: bool,
88 accept_zklogin_in_multisig: bool,
89 accept_passkey_in_multisig: bool,
90 zklogin_max_epoch_upper_bound_delta: Option<u64>,
91 additional_multisig_checks: bool,
92 validate_zklogin_public_identifier: bool,
93 enable_address_aliases: bool,
94 ) -> Self {
95 Self {
96 committee,
97 object_store,
98 signed_data_cache: VerifiedDigestCache::new(
99 metrics.signed_data_cache_hits.clone(),
100 metrics.signed_data_cache_misses.clone(),
101 metrics.signed_data_cache_evictions.clone(),
102 ),
103 zklogin_inputs_cache: Arc::new(VerifiedDigestCache::new(
104 metrics.zklogin_inputs_cache_hits.clone(),
105 metrics.zklogin_inputs_cache_misses.clone(),
106 metrics.zklogin_inputs_cache_evictions.clone(),
107 )),
108 jwks: Default::default(),
109 enable_address_aliases,
110 metrics,
111 zk_login_params: ZkLoginParams {
112 supported_providers,
113 env: zklogin_env,
114 verify_legacy_zklogin_address,
115 accept_zklogin_in_multisig,
116 accept_passkey_in_multisig,
117 zklogin_max_epoch_upper_bound_delta,
118 additional_multisig_checks,
119 validate_zklogin_public_identifier,
120 },
121 }
122 }
123
124 pub(crate) fn insert_jwk(&self, jwk_id: &JwkId, jwk: &JWK) {
127 let mut jwks = self.jwks.write();
128 match jwks.entry(jwk_id.clone()) {
129 im::hashmap::Entry::Occupied(_) => {
130 debug!("JWK with kid {:?} already exists", jwk_id);
131 }
132 im::hashmap::Entry::Vacant(entry) => {
133 debug!("inserting JWK with kid: {:?}", jwk_id);
134 entry.insert(jwk.clone());
135 }
136 }
137 }
138
139 pub fn has_jwk(&self, jwk_id: &JwkId, jwk: &JWK) -> bool {
140 let jwks = self.jwks.read();
141 jwks.get(jwk_id) == Some(jwk)
142 }
143
144 pub fn get_jwks(&self) -> ImHashMap<JwkId, JWK> {
145 self.jwks.read().clone()
146 }
147
148 pub fn verify_tx_with_current_aliases(
151 &self,
152 signed_tx: &SenderSignedData,
153 ) -> SuiResult<NonEmpty<(u8, Option<SequenceNumber>)>> {
154 let mut alias_versions_by_signer = Vec::new();
155 let mut aliases = Vec::new();
156
157 let signers = signed_tx.intent_message().value.required_signers();
159 for signer in signers {
160 if !self.enable_address_aliases {
161 alias_versions_by_signer.push((signer, None));
162 aliases.push((signer, NonEmpty::singleton(signer)));
163 } else {
164 let address_aliases =
166 address_alias::get_address_aliases_from_store(&self.object_store, signer)?;
167
168 alias_versions_by_signer.push((signer, address_aliases.as_ref().map(|(_, v)| *v)));
169 aliases.push((
170 signer,
171 address_aliases
172 .map(|(aliases, _)| {
173 NonEmpty::from_vec(aliases.aliases.contents.clone()).unwrap_or_else(
174 || {
175 debug_fatal!(
176 "AddressAliases struct has empty aliases field for signer {}",
177 signer
178 );
179 NonEmpty::singleton(signer)
180 },
181 )
182 })
183 .unwrap_or(NonEmpty::singleton(signer)),
184 ));
185 }
186 }
187
188 let sig_indices = self.verify_tx(signed_tx, &alias_versions_by_signer, aliases)?;
190
191 let result: Vec<(u8, Option<SequenceNumber>)> = sig_indices
193 .into_iter()
194 .zip_eq(alias_versions_by_signer.into_iter().map(|(_, seq)| seq))
195 .collect();
196
197 Ok(NonEmpty::from_vec(result).expect("must have at least one required_signer"))
198 }
199
200 pub fn verify_tx_require_no_aliases(&self, signed_tx: &SenderSignedData) -> SuiResult {
201 let current_aliases = self.verify_tx_with_current_aliases(signed_tx)?;
202 for (_, version) in current_aliases {
203 if version.is_some() {
204 return Err(SuiErrorKind::AliasesChanged.into());
205 }
206 }
207 Ok(())
208 }
209
210 fn verify_tx(
211 &self,
212 signed_tx: &SenderSignedData,
213 alias_versions: &Vec<(SuiAddress, Option<SequenceNumber>)>,
214 aliased_addresses: Vec<(SuiAddress, NonEmpty<SuiAddress>)>,
215 ) -> SuiResult<Vec<u8>> {
216 let digest = signed_tx.full_message_digest_with_alias_versions(alias_versions);
217
218 if let Some(indices) = self.signed_data_cache.get_cached(&digest) {
219 return Ok(indices);
220 }
221
222 let jwks = self.jwks.read().clone();
223 let verify_params = VerifyParams::new(
224 jwks,
225 self.zk_login_params.supported_providers.clone(),
226 self.zk_login_params.env,
227 self.zk_login_params.verify_legacy_zklogin_address,
228 self.zk_login_params.accept_zklogin_in_multisig,
229 self.zk_login_params.accept_passkey_in_multisig,
230 self.zk_login_params.zklogin_max_epoch_upper_bound_delta,
231 self.zk_login_params.additional_multisig_checks,
232 self.zk_login_params.validate_zklogin_public_identifier,
233 );
234 let indices = verify_sender_signed_data_message_signatures(
235 signed_tx,
236 self.committee.epoch(),
237 &verify_params,
238 self.zklogin_inputs_cache.clone(),
239 aliased_addresses,
240 )?;
241
242 self.signed_data_cache
243 .cache_with_value(digest, indices.clone());
244 Ok(indices)
245 }
246
247 pub fn clear_signature_cache(&self) {
248 self.signed_data_cache.clear();
249 self.zklogin_inputs_cache.clear();
250 }
251}
252
253pub struct SignatureVerifierMetrics {
254 pub signed_data_cache_hits: IntCounter,
255 pub signed_data_cache_misses: IntCounter,
256 pub signed_data_cache_evictions: IntCounter,
257 pub zklogin_inputs_cache_hits: IntCounter,
258 pub zklogin_inputs_cache_misses: IntCounter,
259 pub zklogin_inputs_cache_evictions: IntCounter,
260}
261
262impl SignatureVerifierMetrics {
263 pub fn new(registry: &Registry) -> Arc<Self> {
264 Arc::new(Self {
265 signed_data_cache_hits: register_int_counter_with_registry!(
266 "signed_data_cache_hits",
267 "Number of signed data which were known to be verified because of signature cache.",
268 registry
269 )
270 .unwrap(),
271 signed_data_cache_misses: register_int_counter_with_registry!(
272 "signed_data_cache_misses",
273 "Number of signed data which missed the signature cache.",
274 registry
275 )
276 .unwrap(),
277 signed_data_cache_evictions: register_int_counter_with_registry!(
278 "signed_data_cache_evictions",
279 "Number of times we evict a pre-existing signed data were known to be verified because of signature cache.",
280 registry
281 )
282 .unwrap(),
283 zklogin_inputs_cache_hits: register_int_counter_with_registry!(
284 "zklogin_inputs_cache_hits",
285 "Number of zklogin signature which were known to be partially verified because of zklogin inputs cache.",
286 registry
287 )
288 .unwrap(),
289 zklogin_inputs_cache_misses: register_int_counter_with_registry!(
290 "zklogin_inputs_cache_misses",
291 "Number of zklogin signatures which missed the zklogin inputs cache.",
292 registry
293 )
294 .unwrap(),
295 zklogin_inputs_cache_evictions: register_int_counter_with_registry!(
296 "zklogin_inputs_cache_evictions",
297 "Number of times we evict a pre-existing zklogin inputs digest that was known to be verified because of zklogin inputs cache.",
298 registry
299 )
300 .unwrap(),
301 })
302 }
303}
304
305pub(crate) fn batch_verify_checkpoints(
307 committee: &Committee,
308 checkpoints: &[&SignedCheckpointSummary],
309) -> SuiResult {
310 for ckpt in checkpoints {
311 ckpt.data().verify_epoch(committee.epoch())?;
312 }
313
314 let mut obligation = VerificationObligation::default();
315
316 for ckpt in checkpoints {
317 let idx = obligation.add_message(ckpt.data(), ckpt.epoch(), Intent::sui_app(ckpt.scope()));
318 ckpt.auth_sig()
319 .add_to_verification_obligation(committee, &mut obligation, idx)?;
320 }
321
322 obligation.verify_all()
323}