sui_types/
authenticator_state.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
5use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr};
6use mysten_common::ZipDebugEqIteratorExt;
7use serde::{Deserialize, Serialize};
8
9use crate::base_types::SequenceNumber;
10use crate::dynamic_field::get_dynamic_field_from_store;
11use crate::error::{SuiErrorKind, SuiResult};
12use crate::object::Owner;
13use crate::storage::ObjectStore;
14use crate::{SUI_AUTHENTICATOR_STATE_OBJECT_ID, SUI_FRAMEWORK_ADDRESS, id::UID};
15
16pub const AUTHENTICATOR_STATE_MODULE_NAME: &IdentStr = ident_str!("authenticator_state");
17pub const AUTHENTICATOR_STATE_STRUCT_NAME: &IdentStr = ident_str!("AuthenticatorState");
18pub const AUTHENTICATOR_STATE_UPDATE_FUNCTION_NAME: &IdentStr =
19    ident_str!("update_authenticator_state");
20pub const AUTHENTICATOR_STATE_CREATE_FUNCTION_NAME: &IdentStr = ident_str!("create");
21pub const AUTHENTICATOR_STATE_EXPIRE_JWKS_FUNCTION_NAME: &IdentStr = ident_str!("expire_jwks");
22pub const RESOLVED_SUI_AUTHENTICATOR_STATE: (&AccountAddress, &IdentStr, &IdentStr) = (
23    &SUI_FRAMEWORK_ADDRESS,
24    AUTHENTICATOR_STATE_MODULE_NAME,
25    AUTHENTICATOR_STATE_STRUCT_NAME,
26);
27
28/// Current latest version of the authenticator state object.
29pub const AUTHENTICATOR_STATE_VERSION: u64 = 1;
30
31#[derive(Debug, Serialize, Deserialize)]
32pub struct AuthenticatorState {
33    pub id: UID,
34    pub version: u64,
35}
36
37#[derive(Debug, Serialize, Deserialize)]
38pub struct AuthenticatorStateInner {
39    pub version: u64,
40
41    /// List of currently active JWKs.
42    pub active_jwks: Vec<ActiveJwk>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
46pub struct ActiveJwk {
47    pub jwk_id: JwkId,
48    pub jwk: JWK,
49    // the most recent epoch in which the jwk was validated
50    pub epoch: u64,
51}
52
53fn string_bytes_ord(a: &str, b: &str) -> std::cmp::Ordering {
54    let a_bytes = a.as_bytes();
55    let b_bytes = b.as_bytes();
56
57    if a_bytes.len() < b_bytes.len() {
58        return std::cmp::Ordering::Less;
59    }
60    if a_bytes.len() > b_bytes.len() {
61        return std::cmp::Ordering::Greater;
62    }
63
64    for (a_byte, b_byte) in a_bytes.iter().zip_debug_eq(b_bytes.iter()) {
65        if a_byte < b_byte {
66            return std::cmp::Ordering::Less;
67        }
68        if a_byte > b_byte {
69            return std::cmp::Ordering::Greater;
70        }
71    }
72
73    std::cmp::Ordering::Equal
74}
75
76// This must match the sort order defined by jwk_lt in authenticator_state.move
77fn jwk_ord(a: &ActiveJwk, b: &ActiveJwk) -> std::cmp::Ordering {
78    // note: epoch is ignored
79    if a.jwk_id.iss != b.jwk_id.iss {
80        string_bytes_ord(&a.jwk_id.iss, &b.jwk_id.iss)
81    } else if a.jwk_id.kid != b.jwk_id.kid {
82        string_bytes_ord(&a.jwk_id.kid, &b.jwk_id.kid)
83    } else if a.jwk.kty != b.jwk.kty {
84        string_bytes_ord(&a.jwk.kty, &b.jwk.kty)
85    } else if a.jwk.e != b.jwk.e {
86        string_bytes_ord(&a.jwk.e, &b.jwk.e)
87    } else if a.jwk.n != b.jwk.n {
88        string_bytes_ord(&a.jwk.n, &b.jwk.n)
89    } else {
90        string_bytes_ord(&a.jwk.alg, &b.jwk.alg)
91    }
92}
93
94#[allow(clippy::non_canonical_partial_ord_impl)]
95impl std::cmp::PartialOrd for ActiveJwk {
96    // This must match the sort order defined by jwk_lt in authenticator_state.move
97    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
98        Some(jwk_ord(self, other))
99    }
100}
101
102impl std::cmp::Ord for ActiveJwk {
103    // This must match the sort order defined by jwk_lt in authenticator_state.move
104    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
105        jwk_ord(self, other)
106    }
107}
108
109pub fn get_authenticator_state(
110    object_store: impl ObjectStore,
111) -> SuiResult<Option<AuthenticatorStateInner>> {
112    let outer = object_store.get_object(&SUI_AUTHENTICATOR_STATE_OBJECT_ID);
113    let Some(outer) = outer else {
114        return Ok(None);
115    };
116    let move_object = outer.data.try_as_move().ok_or_else(|| {
117        SuiErrorKind::SuiSystemStateReadError(
118            "AuthenticatorState object must be a Move object".to_owned(),
119        )
120    })?;
121    let outer = bcs::from_bytes::<AuthenticatorState>(move_object.contents())
122        .map_err(|err| SuiErrorKind::SuiSystemStateReadError(err.to_string()))?;
123
124    // No other versions exist yet.
125    assert_eq!(outer.version, AUTHENTICATOR_STATE_VERSION);
126
127    let id = outer.id.id.bytes;
128    let inner: AuthenticatorStateInner =
129        get_dynamic_field_from_store(&object_store, id, &outer.version).map_err(|err| {
130            SuiErrorKind::DynamicFieldReadError(format!(
131                "Failed to load sui system state inner object with ID {:?} and version {:?}: {:?}",
132                id, outer.version, err
133            ))
134        })?;
135
136    Ok(Some(inner))
137}
138
139pub fn get_authenticator_state_obj_initial_shared_version(
140    object_store: &dyn ObjectStore,
141) -> SuiResult<Option<SequenceNumber>> {
142    Ok(object_store
143        .get_object(&SUI_AUTHENTICATOR_STATE_OBJECT_ID)
144        .map(|obj| match obj.owner {
145            Owner::Shared {
146                initial_shared_version,
147            } => initial_shared_version,
148            _ => unreachable!("Authenticator state object must be shared"),
149        }))
150}