iota_types/
move_authenticator.rs

1// Copyright (c) 2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    collections::HashSet,
6    hash::{Hash, Hasher},
7    sync::Arc,
8};
9
10use fastcrypto::{error::FastCryptoError, traits::ToFromBytes};
11use iota_protocol_config::ProtocolConfig;
12use iota_sdk_types::crypto::IntentMessage;
13use once_cell::sync::OnceCell;
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16
17use crate::{
18    base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber},
19    committee::EpochId,
20    crypto::{SignatureScheme, default_hash},
21    digests::{MoveAuthenticatorDigest, ObjectDigest, ZKLoginInputsDigest},
22    error::{IotaError, IotaResult, UserInputError, UserInputResult},
23    signature::{AuthenticatorTrait, VerifyParams},
24    signature_verification::VerifiedDigestCache,
25    transaction::{CallArg, InputObjectKind, ObjectArg, SharedInputObject},
26    type_input::TypeInput,
27};
28
29/// MoveAuthenticator is a GenericSignature variant that enables a new
30/// method of authentication through Move code.
31/// This function represents the data received by the Move authenticate function
32/// during the Account Abstraction authentication flow.
33#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)]
34pub struct MoveAuthenticator {
35    /// Input objects or primitive values
36    call_args: Vec<CallArg>,
37    /// Type arguments for the Move authenticate function
38    #[schemars(with = "Vec<String>")]
39    type_arguments: Vec<TypeInput>,
40    /// The object that is authenticated. Represents the account being the
41    /// sender of the transaction.
42    object_to_authenticate: CallArg,
43    /// A bytes representation of [struct MoveAuthenticator]. This helps with
44    /// implementing trait [AsRef](core::convert::AsRef).
45    #[serde(skip)]
46    bytes: OnceCell<Vec<u8>>,
47}
48
49/// Necessary trait for
50/// [SenderSignerData](crate::transaction::SenderSignedData).
51impl Hash for MoveAuthenticator {
52    fn hash<H: Hasher>(&self, state: &mut H) {
53        self.as_ref().hash(state);
54    }
55}
56
57impl MoveAuthenticator {
58    pub fn new(
59        call_args: Vec<CallArg>,
60        type_arguments: Vec<TypeInput>,
61        object_to_authenticate: CallArg,
62    ) -> Self {
63        Self {
64            call_args,
65            type_arguments,
66            object_to_authenticate,
67            bytes: OnceCell::new(),
68        }
69    }
70
71    pub fn address(&self) -> IotaResult<IotaAddress> {
72        let (id, _, _) = self.object_to_authenticate_components()?;
73        Ok(IotaAddress::from(id))
74    }
75
76    pub fn digest(&self) -> MoveAuthenticatorDigest {
77        MoveAuthenticatorDigest::new(default_hash(self))
78    }
79
80    pub fn call_args(&self) -> &Vec<CallArg> {
81        &self.call_args
82    }
83
84    pub fn type_arguments(&self) -> &Vec<TypeInput> {
85        &self.type_arguments
86    }
87
88    pub fn object_to_authenticate(&self) -> &CallArg {
89        &self.object_to_authenticate
90    }
91
92    pub fn object_to_authenticate_components(
93        &self,
94    ) -> UserInputResult<(ObjectID, Option<SequenceNumber>, Option<ObjectDigest>)> {
95        Ok(match self.object_to_authenticate() {
96            CallArg::Pure(_) => {
97                return Err(UserInputError::Unsupported(
98                    "MoveAuthenticator cannot authenticate pure inputs".to_string(),
99                ));
100            }
101            CallArg::Object(object_arg) => match object_arg {
102                ObjectArg::ImmOrOwnedObject((id, sequence_number, digest)) => {
103                    (*id, Some(*sequence_number), Some(*digest))
104                }
105                ObjectArg::SharedObject { id, mutable, .. } => {
106                    if *mutable {
107                        return Err(UserInputError::Unsupported(
108                            "MoveAuthenticator cannot authenticate mutable shared objects"
109                                .to_string(),
110                        ));
111                    }
112
113                    (*id, None, None)
114                }
115                ObjectArg::Receiving(_) => {
116                    return Err(UserInputError::Unsupported(
117                        "MoveAuthenticator cannot authenticate receiving objects".to_string(),
118                    ));
119                }
120            },
121        })
122    }
123
124    /// Returns all input objects used by the MoveAuthenticator,
125    /// including those from the object to authenticate.
126    pub fn input_objects(&self) -> Vec<InputObjectKind> {
127        self.call_args
128            .iter()
129            .flat_map(|arg| arg.input_objects())
130            .chain(self.object_to_authenticate().input_objects())
131            .collect::<Vec<_>>()
132    }
133
134    pub fn receiving_objects(&self) -> Vec<ObjectRef> {
135        self.call_args
136            .iter()
137            .flat_map(|arg| arg.receiving_objects())
138            .collect()
139    }
140
141    /// Returns all shared input objects used by the MoveAuthenticator,
142    /// including those from the object to authenticate.
143    pub fn shared_objects(&self) -> Vec<SharedInputObject> {
144        self.call_args
145            .iter()
146            .flat_map(|arg| arg.shared_objects())
147            .chain(self.object_to_authenticate().shared_objects())
148            .collect()
149    }
150
151    /// Validity check for MoveAuthenticator.
152    pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
153        // Check that the object to authenticate is valid.
154        self.object_to_authenticate_components()?;
155
156        // Inputs validity check.
157        //
158        // `validity_check` is not called for `object_to_authenticate` because it is
159        // already validated with a dedicated function.
160
161        // `ProtocolConfig::max_function_parameters` is used to check the call arguments
162        // because MoveAuthenticator is considered as a simple programmable call to a
163        // Move function.
164        //
165        // The limit includes the object to authenticate, the auth context and the tx
166        // context, so we subtract 3 here.
167        let max_args = (config.max_function_parameters() - 3) as usize;
168        fp_ensure!(
169            self.call_args().len() < max_args,
170            UserInputError::SizeLimitExceeded {
171                limit: "maximum arguments in MoveAuthenticator".to_string(),
172                value: max_args.to_string()
173            }
174        );
175
176        fp_ensure!(
177            self.receiving_objects().is_empty(),
178            UserInputError::Unsupported(
179                "MoveAuthenticator cannot have receiving objects as input".to_string(),
180            )
181        );
182
183        let mut used = HashSet::new();
184        fp_ensure!(
185            self.input_objects()
186                .iter()
187                .all(|o| used.insert(o.object_id())),
188            UserInputError::DuplicateObjectRefInput
189        );
190
191        self.call_args()
192            .iter()
193            .try_for_each(|obj| obj.validity_check(config))?;
194
195        // Type arguments validity check.
196        //
197        // Each type argument is checked for validity in the same way as it is done for
198        // `ProgrammableMoveCall`.
199        let mut type_arguments_count = 0;
200        self.type_arguments().iter().try_for_each(|type_arg| {
201            crate::transaction::type_input_validity_check(
202                type_arg,
203                config,
204                &mut type_arguments_count,
205            )
206        })?;
207
208        Ok(())
209    }
210}
211
212impl AuthenticatorTrait for MoveAuthenticator {
213    fn verify_user_authenticator_epoch(
214        &self,
215        _epoch: EpochId,
216        _max_epoch_upper_bound_delta: Option<u64>,
217    ) -> IotaResult {
218        Ok(())
219    }
220    // This function accepts all inputs, as signature verification is performed
221    // later on the Move side.
222    fn verify_claims<T>(
223        &self,
224        _value: &IntentMessage<T>,
225        author: IotaAddress,
226        _aux_verify_data: &VerifyParams,
227        _zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
228    ) -> IotaResult
229    where
230        T: Serialize,
231    {
232        if author != self.address()? {
233            return Err(IotaError::InvalidSignature {
234                error: "Invalid author".to_string(),
235            });
236        };
237
238        Ok(())
239    }
240}
241
242/// Necessary trait for
243/// [SenderSignerData](crate::transaction::SenderSignedData).
244impl PartialEq for MoveAuthenticator {
245    fn eq(&self, other: &Self) -> bool {
246        self.as_ref() == other.as_ref()
247    }
248}
249
250impl ToFromBytes for MoveAuthenticator {
251    fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
252        // The first byte matches the flag of MultiSig.
253        if bytes.first().ok_or(FastCryptoError::InvalidInput)?
254            != &SignatureScheme::MoveAuthenticator.flag()
255        {
256            return Err(FastCryptoError::InvalidInput);
257        }
258        let move_auth: MoveAuthenticator =
259            bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
260        Ok(move_auth)
261    }
262}
263
264/// Necessary trait for
265/// [SenderSignerData](crate::transaction::SenderSignedData).
266impl Eq for MoveAuthenticator {}
267
268impl AsRef<[u8]> for MoveAuthenticator {
269    fn as_ref(&self) -> &[u8] {
270        self.bytes
271            .get_or_try_init::<_, eyre::Report>(|| {
272                let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
273                let mut bytes = Vec::with_capacity(1 + as_bytes.len());
274                bytes.push(SignatureScheme::MoveAuthenticator.flag());
275                bytes.extend_from_slice(as_bytes.as_slice());
276                Ok(bytes)
277            })
278            .expect("OnceCell invariant violated")
279    }
280}