1use crate::crypto::PublicKey;
5use crate::signature_verification::VerifiedDigestCache;
6use crate::{
7 base_types::{EpochId, SuiAddress},
8 crypto::{DefaultHash, Signature, SignatureScheme, SuiSignature},
9 digests::ZKLoginInputsDigest,
10 error::{SuiError, SuiErrorKind, SuiResult},
11 signature::{AuthenticatorTrait, VerifyParams},
12};
13use fastcrypto::{error::FastCryptoError, traits::ToFromBytes};
14use fastcrypto_zkp::bn254::zk_login::JwkId;
15use fastcrypto_zkp::bn254::zk_login::{JWK, OIDCProvider};
16use fastcrypto_zkp::bn254::zk_login_api::{ZkLoginCircuitMode, ZkLoginEnv};
17use fastcrypto_zkp::bn254::{zk_login::ZkLoginInputs, zk_login_api::verify_zk_login};
18use once_cell::sync::OnceCell;
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21use shared_crypto::intent::IntentMessage;
22use std::hash::Hash;
23use std::hash::Hasher;
24use std::sync::Arc;
25#[cfg(test)]
26#[path = "unit_tests/zk_login_authenticator_test.rs"]
27mod zk_login_authenticator_test;
28
29#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct ZkLoginAuthenticator {
33 pub inputs: ZkLoginInputs,
34 max_epoch: EpochId,
35 pub user_signature: Signature,
36 #[serde(skip)]
37 pub bytes: OnceCell<Vec<u8>>,
38}
39
40#[derive(Serialize, Deserialize)]
44struct ZkLoginCachingParams {
45 inputs: ZkLoginInputs,
46 max_epoch: EpochId,
47 extended_pk_bytes: Vec<u8>,
48 zklogin_circuit_mode: u64,
49}
50
51impl ZkLoginCachingParams {
52 fn digest(&self) -> ZKLoginInputsDigest {
53 use fastcrypto::hash::HashFunction;
54 let mut hasher = DefaultHash::default();
55 bcs::serialize_into(&mut hasher, self).expect("serde should not fail");
56 ZKLoginInputsDigest::new(hasher.finalize().into())
57 }
58}
59
60pub fn zklogin_circuit_mode_from_flag(flag: u64) -> ZkLoginCircuitMode {
64 match flag {
65 0 => ZkLoginCircuitMode::V1Only,
66 1 => ZkLoginCircuitMode::Both,
67 2 => ZkLoginCircuitMode::V2Only,
68 _ => panic!("invalid zklogin circuit mode flag: {flag}"),
69 }
70}
71
72impl ZkLoginAuthenticator {
73 fn get_caching_params(&self, zklogin_circuit_mode: u64) -> ZkLoginCachingParams {
77 let mut extended_pk_bytes = vec![self.user_signature.scheme().flag()];
78 extended_pk_bytes.extend(self.user_signature.public_key_bytes());
79 ZkLoginCachingParams {
80 inputs: self.inputs.clone(),
81 max_epoch: self.max_epoch,
82 extended_pk_bytes,
83 zklogin_circuit_mode,
84 }
85 }
86
87 pub fn hash_inputs(&self, zklogin_circuit_mode: u64) -> ZKLoginInputsDigest {
88 self.get_caching_params(zklogin_circuit_mode).digest()
89 }
90
91 pub fn new(inputs: ZkLoginInputs, max_epoch: EpochId, user_signature: Signature) -> Self {
93 Self {
94 inputs,
95 max_epoch,
96 user_signature,
97 bytes: OnceCell::new(),
98 }
99 }
100
101 pub fn get_pk(&self) -> SuiResult<PublicKey> {
102 PublicKey::from_zklogin_inputs(&self.inputs)
103 }
104
105 pub fn get_iss(&self) -> &str {
106 self.inputs.get_iss()
107 }
108
109 pub fn get_max_epoch(&self) -> EpochId {
110 self.max_epoch
111 }
112
113 pub fn user_signature_mut_for_testing(&mut self) -> &mut Signature {
114 &mut self.user_signature
115 }
116 pub fn max_epoch_mut_for_testing(&mut self) -> &mut EpochId {
117 &mut self.max_epoch
118 }
119 pub fn zk_login_inputs_mut_for_testing(&mut self) -> &mut ZkLoginInputs {
120 &mut self.inputs
121 }
122}
123
124impl PartialEq for ZkLoginAuthenticator {
126 fn eq(&self, other: &Self) -> bool {
127 self.as_ref() == other.as_ref()
128 }
129}
130
131impl Eq for ZkLoginAuthenticator {}
133
134impl Hash for ZkLoginAuthenticator {
136 fn hash<H: Hasher>(&self, state: &mut H) {
137 self.as_ref().hash(state);
138 }
139}
140
141impl AuthenticatorTrait for ZkLoginAuthenticator {
142 fn verify_user_authenticator_epoch(
143 &self,
144 epoch: EpochId,
145 max_epoch_upper_bound_delta: Option<u64>,
146 ) -> SuiResult {
147 if let Some(delta) = max_epoch_upper_bound_delta {
150 let max_epoch_upper_bound = epoch + delta;
151 if self.get_max_epoch() > max_epoch_upper_bound {
152 return Err(SuiErrorKind::InvalidSignature {
153 error: format!(
154 "ZKLogin max epoch too large {}, current epoch {}, max accepted: {}",
155 self.get_max_epoch(),
156 epoch,
157 max_epoch_upper_bound
158 ),
159 }
160 .into());
161 }
162 }
163 if epoch > self.get_max_epoch() {
165 return Err(SuiErrorKind::InvalidSignature {
166 error: format!(
167 "ZKLogin expired at epoch {}, current epoch {}",
168 self.get_max_epoch(),
169 epoch
170 ),
171 }
172 .into());
173 }
174 Ok(())
175 }
176
177 fn verify_claims<T>(
179 &self,
180 intent_msg: &IntentMessage<T>,
181 author: SuiAddress,
182 aux_verify_data: &VerifyParams,
183 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
184 ) -> SuiResult
185 where
186 T: Serialize,
187 {
188 if author != SuiAddress::try_from_unpadded(&self.inputs)? {
190 if !aux_verify_data.verify_legacy_zklogin_address
192 || author != SuiAddress::try_from_padded(&self.inputs)?
193 {
194 return Err(SuiErrorKind::InvalidAddress.into());
195 }
196 }
197
198 if !aux_verify_data.supported_providers.is_empty()
201 && !aux_verify_data.supported_providers.contains(
202 &OIDCProvider::from_iss(self.inputs.get_iss()).map_err(|_| {
203 SuiErrorKind::InvalidSignature {
204 error: "Unknown provider".to_string(),
205 }
206 })?,
207 )
208 {
209 return Err(SuiErrorKind::InvalidSignature {
210 error: format!("OIDC provider not supported: {}", self.inputs.get_iss()),
211 }
212 .into());
213 }
214
215 match self.user_signature.scheme() {
217 SignatureScheme::ED25519 | SignatureScheme::Secp256k1 | SignatureScheme::Secp256r1 => {}
218 s => {
219 return Err(SuiErrorKind::InvalidSignature {
220 error: format!("zkLogin ephemeral signature scheme not supported: {s}"),
221 }
222 .into());
223 }
224 }
225
226 self.user_signature.verify_secure(
228 intent_msg,
229 author,
230 SignatureScheme::ZkLoginAuthenticator,
231 )?;
232
233 let zklogin_circuit_mode = aux_verify_data.zklogin_circuit_mode;
234 let caching_params = self.get_caching_params(zklogin_circuit_mode);
235 let inputs_digest = caching_params.digest();
236 if zklogin_inputs_cache.is_cached(&inputs_digest) {
237 Ok(())
240 } else {
241 verify_zklogin_inputs_wrapper(
243 caching_params,
244 &aux_verify_data.oidc_provider_jwks,
245 &aux_verify_data.zk_login_env,
246 )
247 .map_err(|e| -> SuiError {
248 SuiErrorKind::InvalidSignature {
249 error: e.to_string(),
250 }
251 .into()
252 })?;
253 zklogin_inputs_cache.cache_digest(inputs_digest);
255 Ok(())
256 }
257 }
258}
259
260fn verify_zklogin_inputs_wrapper(
261 params: ZkLoginCachingParams,
262 all_jwk: &imbl::HashMap<JwkId, JWK>,
263 env: &ZkLoginEnv,
264) -> SuiResult<()> {
265 verify_zk_login(
266 ¶ms.inputs,
267 params.max_epoch,
268 ¶ms.extended_pk_bytes,
269 all_jwk,
270 env,
271 zklogin_circuit_mode_from_flag(params.zklogin_circuit_mode),
272 )
273 .map_err(|e| {
274 SuiErrorKind::InvalidSignature {
275 error: e.to_string(),
276 }
277 .into()
278 })
279}
280
281impl ToFromBytes for ZkLoginAuthenticator {
282 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
283 if bytes.first().ok_or(FastCryptoError::InvalidInput)?
285 != &SignatureScheme::ZkLoginAuthenticator.flag()
286 {
287 return Err(FastCryptoError::InvalidInput);
288 }
289 let mut zk_login: ZkLoginAuthenticator =
290 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
291 zk_login.inputs.init()?;
292 Ok(zk_login)
293 }
294}
295
296impl AsRef<[u8]> for ZkLoginAuthenticator {
297 fn as_ref(&self) -> &[u8] {
298 self.bytes
299 .get_or_try_init::<_, eyre::Report>(|| {
300 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
301 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
302 bytes.push(SignatureScheme::ZkLoginAuthenticator.flag());
303 bytes.extend_from_slice(as_bytes.as_slice());
304 Ok(bytes)
305 })
306 .expect("OnceCell invariant violated")
307 }
308}
309
310#[derive(Debug, Clone)]
311pub struct AddressSeed([u8; 32]);
312
313impl AddressSeed {
314 pub fn unpadded(&self) -> &[u8] {
315 let mut buf = self.0.as_slice();
316
317 while !buf.is_empty() && buf[0] == 0 {
318 buf = &buf[1..];
319 }
320
321 if buf.is_empty() { &self.0[31..] } else { buf }
323 }
324
325 pub fn padded(&self) -> &[u8] {
326 &self.0
327 }
328}
329
330impl std::fmt::Display for AddressSeed {
331 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332 let big_int = num_bigint::BigUint::from_bytes_be(&self.0);
333 let radix10 = big_int.to_str_radix(10);
334 f.write_str(&radix10)
335 }
336}
337
338#[derive(thiserror::Error, Debug)]
339pub enum AddressSeedParseError {
340 #[error("unable to parse radix10 encoded value `{0}`")]
341 Parse(#[from] num_bigint::ParseBigIntError),
342 #[error("larger than 32 bytes")]
343 TooBig,
344}
345
346impl std::str::FromStr for AddressSeed {
347 type Err = AddressSeedParseError;
348
349 fn from_str(s: &str) -> Result<Self, Self::Err> {
350 let big_int = <num_bigint::BigUint as num_traits::Num>::from_str_radix(s, 10)?;
351 let be_bytes = big_int.to_bytes_be();
352 let len = be_bytes.len();
353 let mut buf = [0; 32];
354
355 if len > 32 {
356 return Err(AddressSeedParseError::TooBig);
357 }
358
359 buf[32 - len..].copy_from_slice(&be_bytes);
360 Ok(Self(buf))
361 }
362}
363
364impl Serialize for AddressSeed {
366 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
367 where
368 S: serde::Serializer,
369 {
370 self.to_string().serialize(serializer)
371 }
372}
373
374impl<'de> Deserialize<'de> for AddressSeed {
375 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
376 where
377 D: serde::Deserializer<'de>,
378 {
379 let s = std::borrow::Cow::<'de, str>::deserialize(deserializer)?;
380 std::str::FromStr::from_str(&s).map_err(serde::de::Error::custom)
381 }
382}
383
384#[cfg(test)]
385mod test {
386 use std::str::FromStr;
387
388 use super::AddressSeed;
389 use num_bigint::BigUint;
390 use proptest::prelude::*;
391
392 #[test]
393 fn unpadded_slice() {
394 let seed = AddressSeed([0; 32]);
395 let zero: [u8; 1] = [0];
396 assert_eq!(seed.unpadded(), zero.as_slice());
397
398 let mut seed = AddressSeed([1; 32]);
399 seed.0[0] = 0;
400 assert_eq!(seed.unpadded(), [1; 31].as_slice());
401 }
402
403 proptest! {
404 #[test]
405 fn dont_crash_on_large_inputs(
406 bytes in proptest::collection::vec(any::<u8>(), 33..1024)
407 ) {
408 let big_int = BigUint::from_bytes_be(&bytes);
409 let radix10 = big_int.to_str_radix(10);
410
411 let _ = AddressSeed::from_str(&radix10);
413 }
414
415 #[test]
416 fn valid_address_seeds(
417 bytes in proptest::collection::vec(any::<u8>(), 1..=32)
418 ) {
419 let big_int = BigUint::from_bytes_be(&bytes);
420 let radix10 = big_int.to_str_radix(10);
421
422 let seed = AddressSeed::from_str(&radix10).unwrap();
423 assert_eq!(radix10, seed.to_string());
424 seed.unpadded();
426 }
427 }
428}