iota_types/
authenticator_state.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5pub(crate) use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
6use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    IOTA_AUTHENTICATOR_STATE_OBJECT_ID, IOTA_FRAMEWORK_ADDRESS,
11    base_types::SequenceNumber,
12    dynamic_field::get_dynamic_field_from_store,
13    error::{IotaError, IotaResult},
14    id::UID,
15    object::Owner,
16    storage::ObjectStore,
17};
18
19pub const AUTHENTICATOR_STATE_MODULE_NAME: &IdentStr = ident_str!("authenticator_state");
20pub const AUTHENTICATOR_STATE_STRUCT_NAME: &IdentStr = ident_str!("AuthenticatorState");
21pub const AUTHENTICATOR_STATE_UPDATE_FUNCTION_NAME: &IdentStr =
22    ident_str!("update_authenticator_state");
23pub const AUTHENTICATOR_STATE_CREATE_FUNCTION_NAME: &IdentStr = ident_str!("create");
24pub const AUTHENTICATOR_STATE_EXPIRE_JWKS_FUNCTION_NAME: &IdentStr = ident_str!("expire_jwks");
25pub const RESOLVED_IOTA_AUTHENTICATOR_STATE: (&AccountAddress, &IdentStr, &IdentStr) = (
26    &IOTA_FRAMEWORK_ADDRESS,
27    AUTHENTICATOR_STATE_MODULE_NAME,
28    AUTHENTICATOR_STATE_STRUCT_NAME,
29);
30
31/// Current latest version of the authenticator state object.
32pub const AUTHENTICATOR_STATE_VERSION: u64 = 1;
33
34#[derive(Debug, Serialize, Deserialize)]
35pub struct AuthenticatorState {
36    pub id: UID,
37    pub version: u64,
38}
39
40#[derive(Debug, Serialize, Deserialize)]
41pub struct AuthenticatorStateInner {
42    pub version: u64,
43
44    /// List of currently active JWKs.
45    pub active_jwks: Vec<ActiveJwk>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
49pub struct ActiveJwk {
50    pub jwk_id: JwkId,
51    pub jwk: JWK,
52    // the most recent epoch in which the jwk was validated
53    pub epoch: u64,
54}
55
56fn string_bytes_ord(a: &str, b: &str) -> std::cmp::Ordering {
57    let a_bytes = a.as_bytes();
58    let b_bytes = b.as_bytes();
59
60    if a_bytes.len() < b_bytes.len() {
61        return std::cmp::Ordering::Less;
62    }
63    if a_bytes.len() > b_bytes.len() {
64        return std::cmp::Ordering::Greater;
65    }
66
67    for (a_byte, b_byte) in a_bytes.iter().zip(b_bytes.iter()) {
68        if a_byte < b_byte {
69            return std::cmp::Ordering::Less;
70        }
71        if a_byte > b_byte {
72            return std::cmp::Ordering::Greater;
73        }
74    }
75
76    std::cmp::Ordering::Equal
77}
78
79// This must match the sort order defined by jwk_lt in authenticator_state.move
80fn jwk_ord(a: &ActiveJwk, b: &ActiveJwk) -> std::cmp::Ordering {
81    // note: epoch is ignored
82    if a.jwk_id.iss != b.jwk_id.iss {
83        string_bytes_ord(&a.jwk_id.iss, &b.jwk_id.iss)
84    } else if a.jwk_id.kid != b.jwk_id.kid {
85        string_bytes_ord(&a.jwk_id.kid, &b.jwk_id.kid)
86    } else if a.jwk.kty != b.jwk.kty {
87        string_bytes_ord(&a.jwk.kty, &b.jwk.kty)
88    } else if a.jwk.e != b.jwk.e {
89        string_bytes_ord(&a.jwk.e, &b.jwk.e)
90    } else if a.jwk.n != b.jwk.n {
91        string_bytes_ord(&a.jwk.n, &b.jwk.n)
92    } else {
93        string_bytes_ord(&a.jwk.alg, &b.jwk.alg)
94    }
95}
96
97impl std::cmp::PartialOrd for ActiveJwk {
98    // This must match the sort order defined by jwk_lt in authenticator_state.move
99    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
100        Some(self.cmp(other))
101    }
102}
103
104impl std::cmp::Ord for ActiveJwk {
105    // This must match the sort order defined by jwk_lt in authenticator_state.move
106    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
107        jwk_ord(self, other)
108    }
109}
110
111pub fn get_authenticator_state(
112    object_store: impl ObjectStore,
113) -> IotaResult<Option<AuthenticatorStateInner>> {
114    let outer = object_store.get_object(&IOTA_AUTHENTICATOR_STATE_OBJECT_ID)?;
115    let Some(outer) = outer else {
116        return Ok(None);
117    };
118    let move_object = outer.data.try_as_move().ok_or_else(|| {
119        IotaError::IotaSystemStateRead("AuthenticatorState object must be a Move object".to_owned())
120    })?;
121    let outer = bcs::from_bytes::<AuthenticatorState>(move_object.contents())
122        .map_err(|err| IotaError::IotaSystemStateRead(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            IotaError::DynamicFieldRead(format!(
131                "Failed to load iota 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) -> IotaResult<Option<SequenceNumber>> {
142    Ok(object_store
143        .get_object(&IOTA_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}