iota_types/
auth_context.rs

1// Copyright (c) 2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use move_binary_format::{CompiledModule, file_format::SignatureToken};
5use move_bytecode_utils::resolve_struct;
6use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr};
7use serde::{
8    Serialize, Serializer,
9    ser::{SerializeStruct, SerializeStructVariant, SerializeTupleVariant},
10};
11
12use crate::{
13    IOTA_FRAMEWORK_ADDRESS,
14    digests::MoveAuthenticatorDigest,
15    transaction::{CallArg, Command, ProgrammableTransaction},
16    type_input::TypeName,
17};
18
19pub const AUTH_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("auth_context");
20pub const AUTH_CONTEXT_STRUCT_NAME: &IdentStr = ident_str!("AuthContext");
21
22/// `AuthContext` provides a lightweight execution context used during the
23/// authentication phase of a transaction.
24///
25/// It allows authenticator functions to:
26/// - Identify the transaction sender
27/// - Inspect the programmable transaction block (PTB) inputs and commands
28/// - Perform function-level permission checks
29/// - Support OTP, time-locked auth, or regulatory rule enforcement
30///
31/// This struct is **immutable** during the auth phase and must not allow
32/// mutation of state or access to storage beyond what is declared.
33///
34/// It is guaranteed to be available to all smart accounts implementing a
35/// custom authenticator function.
36///
37/// Typical use:
38/// ```move
39/// public fun authenticate(tx_hash: vector<u8>, input: &MyAuthInput, ctx: &AuthContext) {
40///     assert!(ed25519::ed25519_verify(&input.sig, &input.pk, &tx_hash), 0);
41///     assert!(verify_digest(ctx.digest()), 1);
42///     ...
43/// }
44/// ```
45// Conceptually similar to `TxContext`, but designed specifically for use in the authentication
46// flow.
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct AuthContext {
49    /// The digest of the MoveAuthenticator
50    auth_digest: MoveAuthenticatorDigest,
51    /// The authentication input objects or primitive values
52    tx_inputs: Vec<CallArg>,
53    /// The authentication commands to be executed sequentially.
54    tx_commands: Vec<Command>,
55}
56
57impl AuthContext {
58    pub fn new_from_components(
59        auth_digest: MoveAuthenticatorDigest,
60        ptb: &ProgrammableTransaction,
61    ) -> Self {
62        Self {
63            auth_digest,
64            tx_inputs: ptb.inputs.clone(),
65            tx_commands: ptb.commands.clone(),
66        }
67    }
68
69    pub fn digest(&self) -> &MoveAuthenticatorDigest {
70        &self.auth_digest
71    }
72
73    pub fn tx_inputs(&self) -> &Vec<CallArg> {
74        &self.tx_inputs
75    }
76
77    pub fn tx_commands(&self) -> &Vec<Command> {
78        &self.tx_commands
79    }
80
81    pub fn to_bcs_bytes(&self) -> Vec<u8> {
82        bcs::to_bytes(&self).unwrap()
83    }
84
85    /// Returns whether the type signature is &mut AuthContext, &AuthContext, or
86    /// none of the above.
87    pub fn kind(module: &CompiledModule, token: &SignatureToken) -> AuthContextKind {
88        use SignatureToken as S;
89
90        let (kind, token) = match token {
91            S::MutableReference(token) => (AuthContextKind::Mutable, token),
92            S::Reference(token) => (AuthContextKind::Immutable, token),
93            _ => return AuthContextKind::None,
94        };
95
96        let S::Datatype(idx) = &**token else {
97            return AuthContextKind::None;
98        };
99
100        let (module_addr, module_name, struct_name) = resolve_struct(module, *idx);
101
102        if is_auth_context(module_addr, module_name, struct_name) {
103            kind
104        } else {
105            AuthContextKind::None
106        }
107    }
108}
109
110// TODO: add a deserializer that can handle the Command::MoveCall and
111// Command::MakeMoveVec variants properly. For now, we only need serialization
112// for inclusion in the tx authenticator input, so we implement Serialize only.
113// Alternatively, a custom Command struct could be created for de/serialization
114// purposes or to add new functionalities.
115impl Serialize for AuthContext {
116    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
117    where
118        S: Serializer,
119    {
120        let mut state = serializer.serialize_struct("AuthContext", 3)?;
121        state.serialize_field("auth_digest", &self.auth_digest)?;
122        state.serialize_field("tx_inputs", &self.tx_inputs)?;
123
124        // Serialize tx_commands as a Vec of enums, matching the original logic
125        struct CommandSer<'a>(&'a Command);
126
127        impl<'a> Serialize for CommandSer<'a> {
128            fn serialize<SC>(&self, serializer: SC) -> Result<SC::Ok, SC::Error>
129            where
130                SC: Serializer,
131            {
132                match self.0 {
133                    Command::MoveCall(m) => {
134                        let mut s =
135                            serializer.serialize_struct_variant("Command", 0, "MoveCall", 5)?;
136                        s.serialize_field("package", &m.package)?;
137                        s.serialize_field("module", &m.module)?;
138                        s.serialize_field("function", &m.function)?;
139                        s.serialize_field(
140                            "type_arguments",
141                            &m.type_arguments
142                                .iter()
143                                .map(TypeName::from)
144                                .collect::<Vec<_>>(),
145                        )?;
146                        s.serialize_field("arguments", &m.arguments)?;
147                        s.end()
148                    }
149                    Command::TransferObjects(objects, recipient) => {
150                        let mut s = serializer.serialize_struct_variant(
151                            "Command",
152                            1,
153                            "TransferObjects",
154                            2,
155                        )?;
156                        s.serialize_field("objects", objects)?;
157                        s.serialize_field("recipient", recipient)?;
158                        s.end()
159                    }
160                    Command::SplitCoins(coin, amounts) => {
161                        let mut s =
162                            serializer.serialize_struct_variant("Command", 2, "SplitCoins", 2)?;
163                        s.serialize_field("coin", coin)?;
164                        s.serialize_field("amounts", amounts)?;
165                        s.end()
166                    }
167                    Command::MergeCoins(target_coin, source_coins) => {
168                        let mut s =
169                            serializer.serialize_struct_variant("Command", 3, "MergeCoins", 2)?;
170                        s.serialize_field("target_coin", target_coin)?;
171                        s.serialize_field("source_coins", source_coins)?;
172                        s.end()
173                    }
174                    Command::Publish(modules, dependencies) => {
175                        let mut s =
176                            serializer.serialize_struct_variant("Command", 4, "Publish", 2)?;
177                        s.serialize_field("modules", modules)?;
178                        s.serialize_field("dependencies", dependencies)?;
179                        s.end()
180                    }
181                    Command::MakeMoveVec(type_arg, elements) => {
182                        let mut s =
183                            serializer.serialize_tuple_variant("Command", 5, "MakeMoveVec", 2)?;
184                        s.serialize_field(&type_arg.as_ref().map(TypeName::from))?;
185                        s.serialize_field(elements)?;
186                        s.end()
187                    }
188                    Command::Upgrade(modules, dependencies, package, upgrade_ticket) => {
189                        let mut s =
190                            serializer.serialize_struct_variant("Command", 6, "Upgrade", 4)?;
191                        s.serialize_field("modules", modules)?;
192                        s.serialize_field("dependencies", dependencies)?;
193                        s.serialize_field("package", package)?;
194                        s.serialize_field("upgrade_ticket", upgrade_ticket)?;
195                        s.end()
196                    }
197                }
198            }
199        }
200
201        state.serialize_field(
202            "tx_commands",
203            &self.tx_commands.iter().map(CommandSer).collect::<Vec<_>>(),
204        )?;
205
206        state.end()
207    }
208}
209
210#[derive(PartialEq, Eq, Clone, Copy)]
211pub enum AuthContextKind {
212    // Not AuthContext
213    None,
214    // &mut AuthContext
215    Mutable,
216    // &AuthContext
217    Immutable,
218}
219
220pub fn is_auth_context(
221    module_addr: &AccountAddress,
222    module_name: &IdentStr,
223    struct_name: &IdentStr,
224) -> bool {
225    module_addr == &IOTA_FRAMEWORK_ADDRESS
226        && module_name == AUTH_CONTEXT_MODULE_NAME
227        && struct_name == AUTH_CONTEXT_STRUCT_NAME
228}