iota_types/auth_context/
mod.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4mod fields_v1;
5
6pub use fields_v1::*;
7use move_binary_format::{CompiledModule, file_format::SignatureToken};
8use move_bytecode_utils::resolve_struct;
9use move_core_types::{
10    account_address::AccountAddress, ident_str, identifier::IdentStr, language_storage::StructTag,
11};
12use serde::Serialize;
13
14use crate::{
15    IOTA_FRAMEWORK_ADDRESS, digests::MoveAuthenticatorDigest, transaction::ProgrammableTransaction,
16};
17
18pub const AUTH_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("auth_context");
19pub const AUTH_CONTEXT_STRUCT_NAME: &IdentStr = ident_str!("AuthContext");
20
21/// `AuthContext` provides a lightweight execution context used during the
22/// authentication phase of a transaction.
23///
24/// It allows authenticator functions to:
25/// - Inspect the programmable transaction block (PTB) inputs and commands
26/// - Perform function-level permission checks
27/// - Support OTP, time-locked auth, or regulatory rule enforcement
28///
29/// This struct is **immutable** during the auth phase and must not allow
30/// mutation of state or access to storage beyond what is declared.
31///
32/// It is guaranteed to be available to all smart accounts implementing a
33/// custom authenticator function.
34///
35/// Typical use:
36/// ```move
37/// public fun authenticate(account: &Account, signature: &vector<u8>, auth_ctx: &AuthContext, , ctx: &TxContext) {
38///     assert!(ed25519::ed25519_verify(signature, &account.pub_key, ctx.digest()), EEd25519VerificationFailed);
39///     
40///     assert!(is_authorized(&extract_function_key(&auth_ctx)), EUnauthorized);
41///     ...
42/// }
43/// ```
44// Conceptually similar to `TxContext`, but designed specifically for use in the authentication
45// flow.
46#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
47pub struct AuthContext {
48    /// The digest of the MoveAuthenticator
49    auth_digest: MoveAuthenticatorDigest,
50    /// The authentication input objects or primitive values
51    tx_inputs: Vec<MoveCallArg>,
52    /// The authentication commands to be executed sequentially.
53    tx_commands: Vec<MoveCommand>,
54}
55
56impl AuthContext {
57    pub fn new_from_components(
58        auth_digest: MoveAuthenticatorDigest,
59        ptb: &ProgrammableTransaction,
60    ) -> Self {
61        Self {
62            auth_digest,
63            tx_inputs: ptb.inputs.iter().map(MoveCallArg::from).collect(),
64            tx_commands: ptb.commands.iter().map(MoveCommand::from).collect(),
65        }
66    }
67
68    pub fn new_for_testing() -> Self {
69        Self {
70            auth_digest: MoveAuthenticatorDigest::default(),
71            tx_inputs: Vec::new(),
72            tx_commands: Vec::new(),
73        }
74    }
75
76    pub fn digest(&self) -> &MoveAuthenticatorDigest {
77        &self.auth_digest
78    }
79
80    pub fn tx_inputs(&self) -> &Vec<MoveCallArg> {
81        &self.tx_inputs
82    }
83
84    pub fn tx_commands(&self) -> &Vec<MoveCommand> {
85        &self.tx_commands
86    }
87
88    pub fn to_bcs_bytes(&self) -> Vec<u8> {
89        bcs::to_bytes(&self).unwrap()
90    }
91
92    pub fn to_move_bcs_bytes(&self) -> Vec<u8> {
93        bcs::to_bytes(&MoveAuthContext::default()).unwrap()
94    }
95
96    /// Returns whether the type signature is &mut AuthContext, &AuthContext, or
97    /// none of the above.
98    pub fn kind(module: &CompiledModule, token: &SignatureToken) -> AuthContextKind {
99        use SignatureToken as S;
100
101        let (kind, token) = match token {
102            S::MutableReference(token) => (AuthContextKind::Mutable, token),
103            S::Reference(token) => (AuthContextKind::Immutable, token),
104            _ => return AuthContextKind::None,
105        };
106
107        let S::Datatype(idx) = &**token else {
108            return AuthContextKind::None;
109        };
110
111        let (module_addr, module_name, struct_name) = resolve_struct(module, *idx);
112
113        if is_auth_context(module_addr, module_name, struct_name) {
114            kind
115        } else {
116            AuthContextKind::None
117        }
118    }
119
120    pub fn type_() -> StructTag {
121        StructTag {
122            address: IOTA_FRAMEWORK_ADDRESS,
123            module: AUTH_CONTEXT_MODULE_NAME.to_owned(),
124            name: AUTH_CONTEXT_STRUCT_NAME.to_owned(),
125            type_params: vec![],
126        }
127    }
128
129    /// Replaces the contents of the `AuthContext` with new values. This is
130    /// intended for use within a Move test function, as the `AuthContext`
131    /// should be immutable during normal use.
132    pub fn replace(
133        &mut self,
134        auth_digest: MoveAuthenticatorDigest,
135        tx_inputs: Vec<MoveCallArg>,
136        tx_commands: Vec<MoveCommand>,
137    ) {
138        self.auth_digest = auth_digest;
139        self.tx_inputs = tx_inputs;
140        self.tx_commands = tx_commands;
141    }
142}
143
144/// A Move-side `AuthContext` representation.
145/// It is supposed to be used with empty fields since the Move `AuthContext`
146/// struct is managed by the native functions.
147#[derive(Default, Serialize)]
148pub struct MoveAuthContext {
149    auth_digest: MoveAuthenticatorDigest,
150    tx_inputs: Vec<MoveCallArg>,
151    tx_commands: Vec<MoveCommand>,
152}
153
154#[derive(PartialEq, Eq, Clone, Copy)]
155pub enum AuthContextKind {
156    // Not AuthContext
157    None,
158    // &mut AuthContext
159    Mutable,
160    // &AuthContext
161    Immutable,
162}
163
164pub fn is_auth_context(
165    module_addr: &AccountAddress,
166    module_name: &IdentStr,
167    struct_name: &IdentStr,
168) -> bool {
169    module_addr == &IOTA_FRAMEWORK_ADDRESS
170        && module_name == AUTH_CONTEXT_MODULE_NAME
171        && struct_name == AUTH_CONTEXT_STRUCT_NAME
172}
173
174#[cfg(test)]
175mod tests {
176
177    use super::*;
178    use crate::{
179        base_types::ObjectID,
180        transaction::{Argument, CallArg, Command, ProgrammableMoveCall, ProgrammableTransaction},
181        type_input::{TypeInput, TypeName},
182    };
183
184    #[test]
185    fn auth_context_new_from_components() {
186        let ptb = ProgrammableTransaction {
187            inputs: vec![CallArg::Pure(vec![0xab])],
188            commands: vec![Command::MoveCall(Box::new(ProgrammableMoveCall {
189                package: ObjectID::from_hex_literal("0x0000000000000000000000000000000000000001")
190                    .unwrap(),
191                module: "mod".to_string(),
192                function: "fun".to_string(),
193                type_arguments: vec![TypeInput::U8],
194                arguments: vec![Argument::GasCoin],
195            }))],
196        };
197
198        let ctx = AuthContext::new_from_components(MoveAuthenticatorDigest::default(), &ptb);
199
200        assert_eq!(ctx.tx_inputs().len(), 1);
201        assert_eq!(ctx.tx_commands().len(), 1);
202
203        assert!(matches!(ctx.tx_inputs()[0], MoveCallArg::Pure(_)));
204
205        // Commands must have TypeName substituted for TypeInput.
206        let MoveCommand::MoveCall(call) = &ctx.tx_commands()[0] else {
207            panic!("expected MoveCall");
208        };
209        assert_eq!(
210            call.type_arguments,
211            vec![TypeName {
212                name: "u8".to_string()
213            }]
214        );
215    }
216
217    #[test]
218    fn auth_context_to_bcs_bytes_is_deterministic() {
219        let ctx = AuthContext::new_for_testing();
220        assert_eq!(ctx.to_bcs_bytes(), ctx.to_bcs_bytes());
221    }
222
223    #[test]
224    fn auth_context_to_bcs_bytes_reflects_content() {
225        let mut ctx = AuthContext::new_for_testing();
226        let empty_bytes = ctx.to_bcs_bytes();
227
228        ctx.replace(
229            MoveAuthenticatorDigest::default(),
230            vec![MoveCallArg::Pure(vec![1])],
231            vec![],
232        );
233        let non_empty_bytes = ctx.to_bcs_bytes();
234
235        assert_ne!(empty_bytes, non_empty_bytes);
236    }
237}