Skip to main content

iota_types/
move_authenticator.rs

1// Copyright (c) 2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::HashSet;
5
6use iota_protocol_config::ProtocolConfig;
7use iota_sdk_types::{
8    Address, MoveAuthenticator, MoveAuthenticatorV1, ObjectDigest, ObjectId, ObjectReference,
9    SharedObjectReference, TypeTag, Version, crypto::IntentMessage,
10};
11use serde::Serialize;
12
13use crate::{
14    error::{IotaError, IotaResult, UserInputError, UserInputResult},
15    signature::{AuthenticatorTrait, VerifyParams},
16    transaction::{CallArg, CallArgExt, InputObjectKind},
17};
18
19mod move_authenticator_ext {
20    pub trait Sealed {}
21    impl Sealed for super::MoveAuthenticator {}
22    impl Sealed for super::MoveAuthenticatorV1 {}
23}
24
25pub trait MoveAuthenticatorExt: Sized + move_authenticator_ext::Sealed {
26    /// Returns the address of the object being authenticated, which acts as the
27    /// sender of the transaction.
28    fn address(&self) -> Address;
29
30    /// Returns the input objects or primitive values passed to the authenticate
31    /// function.
32    fn call_args(&self) -> &[CallArg];
33
34    /// Returns the type arguments for the Move authenticate function.
35    fn type_args(&self) -> &[TypeTag];
36
37    /// Returns the object that is being authenticated (the account/sender).
38    fn object_to_authenticate(&self) -> &CallArg;
39
40    fn object_to_authenticate_components(
41        &self,
42    ) -> UserInputResult<(ObjectId, Option<Version>, Option<ObjectDigest>)>;
43
44    fn input_objects(&self) -> Vec<InputObjectKind>;
45
46    fn receiving_objects(&self) -> Vec<ObjectReference>;
47
48    fn shared_objects(&self) -> Vec<SharedObjectReference>;
49
50    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
51}
52
53impl MoveAuthenticatorExt for MoveAuthenticator {
54    fn address(&self) -> Address {
55        match self {
56            Self::V1(v1) => v1.address(),
57            _ => unimplemented!(
58                "a new MoveAuthenticator enum variant was added and needs to be handled"
59            ),
60        }
61    }
62
63    fn call_args(&self) -> &[CallArg] {
64        match self {
65            Self::V1(v1) => v1.call_args(),
66            _ => unimplemented!(
67                "a new MoveAuthenticator enum variant was added and needs to be handled"
68            ),
69        }
70    }
71
72    fn type_args(&self) -> &[TypeTag] {
73        match self {
74            Self::V1(v1) => v1.type_args(),
75            _ => unimplemented!(
76                "a new MoveAuthenticator enum variant was added and needs to be handled"
77            ),
78        }
79    }
80
81    fn object_to_authenticate(&self) -> &CallArg {
82        match self {
83            Self::V1(v1) => v1.object_to_authenticate(),
84            _ => unimplemented!(
85                "a new MoveAuthenticator enum variant was added and needs to be handled"
86            ),
87        }
88    }
89
90    fn object_to_authenticate_components(
91        &self,
92    ) -> UserInputResult<(ObjectId, Option<Version>, Option<ObjectDigest>)> {
93        match self {
94            Self::V1(v1) => v1.object_to_authenticate_components(),
95            _ => unimplemented!(
96                "a new MoveAuthenticator enum variant was added and needs to be handled"
97            ),
98        }
99    }
100
101    fn input_objects(&self) -> Vec<InputObjectKind> {
102        match self {
103            Self::V1(v1) => v1.input_objects(),
104            _ => unimplemented!(
105                "a new MoveAuthenticator enum variant was added and needs to be handled"
106            ),
107        }
108    }
109
110    fn receiving_objects(&self) -> Vec<ObjectReference> {
111        match self {
112            Self::V1(v1) => v1.receiving_objects(),
113            _ => unimplemented!(
114                "a new MoveAuthenticator enum variant was added and needs to be handled"
115            ),
116        }
117    }
118
119    fn shared_objects(&self) -> Vec<SharedObjectReference> {
120        match self {
121            Self::V1(v1) => v1.shared_objects(),
122            _ => unimplemented!(
123                "a new MoveAuthenticator enum variant was added and needs to be handled"
124            ),
125        }
126    }
127
128    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
129        match self {
130            Self::V1(v1) => v1.validity_check(config),
131            _ => unimplemented!(
132                "a new MoveAuthenticator enum variant was added and needs to be handled"
133            ),
134        }
135    }
136}
137
138impl MoveAuthenticatorExt for MoveAuthenticatorV1 {
139    // Forward to the inherent accessors of the same name; inherent methods take
140    // priority in resolution, so these are not recursive.
141    fn address(&self) -> Address {
142        self.address()
143    }
144
145    fn call_args(&self) -> &[CallArg] {
146        self.call_args()
147    }
148
149    fn type_args(&self) -> &[TypeTag] {
150        self.type_args()
151    }
152
153    fn object_to_authenticate(&self) -> &CallArg {
154        self.object_to_authenticate()
155    }
156
157    fn object_to_authenticate_components(
158        &self,
159    ) -> UserInputResult<(ObjectId, Option<Version>, Option<ObjectDigest>)> {
160        Ok(match self.object_to_authenticate() {
161            CallArg::Pure(_) => {
162                return Err(UserInputError::Unsupported(
163                    "MoveAuthenticatorV1 cannot authenticate pure inputs".to_string(),
164                ));
165            }
166            CallArg::ImmutableOrOwned(object_ref) => (
167                object_ref.object_id,
168                Some(object_ref.version),
169                Some(object_ref.digest),
170            ),
171            CallArg::Shared(SharedObjectReference {
172                object_id, mutable, ..
173            }) => {
174                if *mutable {
175                    return Err(UserInputError::Unsupported(
176                        "MoveAuthenticatorV1 cannot authenticate mutable shared objects"
177                            .to_string(),
178                    ));
179                }
180
181                (*object_id, None, None)
182            }
183            CallArg::Receiving(_) => {
184                return Err(UserInputError::Unsupported(
185                    "MoveAuthenticator cannot authenticate receiving objects".to_string(),
186                ));
187            }
188            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
189        })
190    }
191
192    /// Returns all input objects used by the MoveAuthenticatorV1,
193    /// including those from the object to authenticate.
194    fn input_objects(&self) -> Vec<InputObjectKind> {
195        self.call_args()
196            .iter()
197            .filter_map(|arg| arg.input_object_kind())
198            .chain(self.object_to_authenticate().input_object_kind())
199            .collect::<Vec<_>>()
200    }
201
202    fn receiving_objects(&self) -> Vec<ObjectReference> {
203        self.call_args()
204            .iter()
205            .filter_map(|arg| arg.as_opt_receiving().copied())
206            .collect()
207    }
208
209    /// Returns all shared input objects used by the MoveAuthenticatorV1,
210    /// including those from the object to authenticate.
211    fn shared_objects(&self) -> Vec<SharedObjectReference> {
212        self.call_args()
213            .iter()
214            .filter_map(|arg| arg.as_opt_shared())
215            .chain(self.object_to_authenticate().as_opt_shared())
216            .cloned()
217            .collect()
218    }
219
220    /// Validity check for MoveAuthenticatorV1.
221    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
222        // Check that the object to authenticate is valid.
223        self.object_to_authenticate_components()?;
224
225        // Inputs validity check.
226        //
227        // `ProtocolConfig::max_function_parameters` is used to check the call arguments
228        // because MoveAuthenticatorV1 is considered as a simple programmable call to a
229        // Move function.
230        //
231        // The limit includes the object to authenticate, the auth context and the tx
232        // context, so we subtract 3 here.
233        let max_args = (config.max_function_parameters() - 3) as usize;
234        fp_ensure!(
235            self.call_args().len() < max_args,
236            UserInputError::SizeLimitExceeded {
237                limit: "maximum arguments in MoveAuthenticatorV1".to_string(),
238                value: max_args.to_string()
239            }
240        );
241
242        fp_ensure!(
243            self.receiving_objects().is_empty(),
244            UserInputError::Unsupported(
245                "MoveAuthenticatorV1 cannot have receiving objects as input".to_string(),
246            )
247        );
248
249        // An authenticate function cannot derive randomness, so naming the
250        // randomness state object only schedules the transaction as
251        // randomness-using for nothing. Checked on the object id alone, so it
252        // covers the call arguments and the object to authenticate whatever
253        // their input kind.
254        let input_objects = self.input_objects();
255        if config.disallow_randomness_in_move_authenticator() {
256            fp_ensure!(
257                input_objects
258                    .iter()
259                    .all(|o| o.object_id() != ObjectId::RANDOMNESS_STATE),
260                UserInputError::RandomnessStateIsInMoveAuthenticatorInput {
261                    object_id: ObjectId::RANDOMNESS_STATE,
262                }
263            );
264        }
265
266        // Read only the declaration, so validation at a commit decides them
267        // the same way on every validator, with or without the object there
268        // to read. Without deterministic validation the checks on the loaded
269        // objects apply these rules.
270        if config.enable_pcool_flow() {
271            for kind in &input_objects {
272                match *kind {
273                    InputObjectKind::MovePackage(package_id) => {
274                        return Err(UserInputError::PackageIsInMoveAuthenticatorInput {
275                            package_id,
276                        });
277                    }
278                    InputObjectKind::SharedMoveObject {
279                        id: ObjectId::AUTHENTICATOR_STATE,
280                        ..
281                    } => {
282                        return Err(UserInputError::InaccessibleSystemObject {
283                            object_id: ObjectId::AUTHENTICATOR_STATE,
284                        });
285                    }
286                    InputObjectKind::SharedMoveObject {
287                        id, mutable: true, ..
288                    } => {
289                        return Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput {
290                            object_id: id,
291                        });
292                    }
293                    InputObjectKind::SharedMoveObject { .. }
294                    | InputObjectKind::ImmOrOwnedMoveObject(_) => {}
295                }
296            }
297        }
298
299        let mut used = HashSet::new();
300        fp_ensure!(
301            input_objects.iter().all(|o| used.insert(o.object_id())),
302            UserInputError::DuplicateObjectRefInput
303        );
304
305        // Like every other input, the account object must not name a version
306        // in or right below the range assigned to canceled transactions. Its
307        // kind is settled above, so the version bound is all this leaves.
308        self.object_to_authenticate().validity_check(config)?;
309
310        self.call_args()
311            .iter()
312            .try_for_each(|obj| obj.validity_check(config))?;
313
314        // Type arguments validity check.
315        //
316        // Each type argument is checked for validity in the same way as it is done for
317        // `MoveCall`.
318        let mut type_arguments_count = 0;
319        self.type_args().iter().try_for_each(|type_arg| {
320            crate::transaction::type_tag_validity_check(type_arg, config, &mut type_arguments_count)
321        })?;
322
323        Ok(())
324    }
325}
326
327impl AuthenticatorTrait for MoveAuthenticator {
328    // This function accepts all inputs, as signature verification is performed
329    // later on the Move side.
330    fn verify_claims<T>(
331        &self,
332        value: &IntentMessage<T>,
333        author: Address,
334        aux_verify_data: &VerifyParams,
335    ) -> IotaResult
336    where
337        T: Serialize,
338    {
339        match self {
340            Self::V1(v1) => v1.verify_claims(value, author, aux_verify_data),
341            _ => unimplemented!(
342                "a new MoveAuthenticator enum variant was added and needs to be handled"
343            ),
344        }
345    }
346}
347
348impl AuthenticatorTrait for MoveAuthenticatorV1 {
349    // This function accepts all inputs, as signature verification is performed
350    // later on the Move side.
351    fn verify_claims<T>(
352        &self,
353        _value: &IntentMessage<T>,
354        author: Address,
355        _aux_verify_data: &VerifyParams,
356    ) -> IotaResult
357    where
358        T: Serialize,
359    {
360        if author != self.address() {
361            return Err(IotaError::InvalidSignature {
362                error: "Invalid author".to_string(),
363            });
364        };
365
366        Ok(())
367    }
368}