1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

pub(crate) use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr};
use serde::{Deserialize, Serialize};

use crate::{
    IOTA_AUTHENTICATOR_STATE_OBJECT_ID, IOTA_FRAMEWORK_ADDRESS,
    base_types::SequenceNumber,
    dynamic_field::get_dynamic_field_from_store,
    error::{IotaError, IotaResult},
    id::UID,
    object::Owner,
    storage::ObjectStore,
};

pub const AUTHENTICATOR_STATE_MODULE_NAME: &IdentStr = ident_str!("authenticator_state");
pub const AUTHENTICATOR_STATE_STRUCT_NAME: &IdentStr = ident_str!("AuthenticatorState");
pub const AUTHENTICATOR_STATE_UPDATE_FUNCTION_NAME: &IdentStr =
    ident_str!("update_authenticator_state");
pub const AUTHENTICATOR_STATE_CREATE_FUNCTION_NAME: &IdentStr = ident_str!("create");
pub const AUTHENTICATOR_STATE_EXPIRE_JWKS_FUNCTION_NAME: &IdentStr = ident_str!("expire_jwks");
pub const RESOLVED_IOTA_AUTHENTICATOR_STATE: (&AccountAddress, &IdentStr, &IdentStr) = (
    &IOTA_FRAMEWORK_ADDRESS,
    AUTHENTICATOR_STATE_MODULE_NAME,
    AUTHENTICATOR_STATE_STRUCT_NAME,
);

/// Current latest version of the authenticator state object.
pub const AUTHENTICATOR_STATE_VERSION: u64 = 1;

#[derive(Debug, Serialize, Deserialize)]
pub struct AuthenticatorState {
    pub id: UID,
    pub version: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AuthenticatorStateInner {
    pub version: u64,

    /// List of currently active JWKs.
    pub active_jwks: Vec<ActiveJwk>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct ActiveJwk {
    pub jwk_id: JwkId,
    pub jwk: JWK,
    // the most recent epoch in which the jwk was validated
    pub epoch: u64,
}

fn string_bytes_ord(a: &str, b: &str) -> std::cmp::Ordering {
    let a_bytes = a.as_bytes();
    let b_bytes = b.as_bytes();

    if a_bytes.len() < b_bytes.len() {
        return std::cmp::Ordering::Less;
    }
    if a_bytes.len() > b_bytes.len() {
        return std::cmp::Ordering::Greater;
    }

    for (a_byte, b_byte) in a_bytes.iter().zip(b_bytes.iter()) {
        if a_byte < b_byte {
            return std::cmp::Ordering::Less;
        }
        if a_byte > b_byte {
            return std::cmp::Ordering::Greater;
        }
    }

    std::cmp::Ordering::Equal
}

// This must match the sort order defined by jwk_lt in authenticator_state.move
fn jwk_ord(a: &ActiveJwk, b: &ActiveJwk) -> std::cmp::Ordering {
    // note: epoch is ignored
    if a.jwk_id.iss != b.jwk_id.iss {
        string_bytes_ord(&a.jwk_id.iss, &b.jwk_id.iss)
    } else if a.jwk_id.kid != b.jwk_id.kid {
        string_bytes_ord(&a.jwk_id.kid, &b.jwk_id.kid)
    } else if a.jwk.kty != b.jwk.kty {
        string_bytes_ord(&a.jwk.kty, &b.jwk.kty)
    } else if a.jwk.e != b.jwk.e {
        string_bytes_ord(&a.jwk.e, &b.jwk.e)
    } else if a.jwk.n != b.jwk.n {
        string_bytes_ord(&a.jwk.n, &b.jwk.n)
    } else {
        string_bytes_ord(&a.jwk.alg, &b.jwk.alg)
    }
}

#[allow(clippy::non_canonical_partial_ord_impl)]
impl std::cmp::PartialOrd for ActiveJwk {
    // This must match the sort order defined by jwk_lt in authenticator_state.move
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(jwk_ord(self, other))
    }
}

impl std::cmp::Ord for ActiveJwk {
    // This must match the sort order defined by jwk_lt in authenticator_state.move
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        jwk_ord(self, other)
    }
}

pub fn get_authenticator_state(
    object_store: impl ObjectStore,
) -> IotaResult<Option<AuthenticatorStateInner>> {
    let outer = object_store.get_object(&IOTA_AUTHENTICATOR_STATE_OBJECT_ID)?;
    let Some(outer) = outer else {
        return Ok(None);
    };
    let move_object = outer.data.try_as_move().ok_or_else(|| {
        IotaError::IotaSystemStateRead("AuthenticatorState object must be a Move object".to_owned())
    })?;
    let outer = bcs::from_bytes::<AuthenticatorState>(move_object.contents())
        .map_err(|err| IotaError::IotaSystemStateRead(err.to_string()))?;

    // No other versions exist yet.
    assert_eq!(outer.version, AUTHENTICATOR_STATE_VERSION);

    let id = outer.id.id.bytes;
    let inner: AuthenticatorStateInner =
        get_dynamic_field_from_store(&object_store, id, &outer.version).map_err(|err| {
            IotaError::DynamicFieldRead(format!(
                "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}",
                id, outer.version, err
            ))
        })?;

    Ok(Some(inner))
}

pub fn get_authenticator_state_obj_initial_shared_version(
    object_store: &dyn ObjectStore,
) -> IotaResult<Option<SequenceNumber>> {
    Ok(object_store
        .get_object(&IOTA_AUTHENTICATOR_STATE_OBJECT_ID)?
        .map(|obj| match obj.owner {
            Owner::Shared {
                initial_shared_version,
            } => initial_shared_version,
            _ => unreachable!("Authenticator state object must be shared"),
        }))
}