Skip to main content

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,
16    account_abstraction::authenticator_function::{
17        AuthenticatorFunctionRef, AuthenticatorFunctionRefV1,
18    },
19    digests::{Digest, MoveAuthenticatorDigest},
20    transaction::ProgrammableTransaction,
21};
22
23pub const AUTH_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("auth_context");
24pub const AUTH_CONTEXT_STRUCT_NAME: &IdentStr = ident_str!("AuthContext");
25
26/// `AuthContext` provides a lightweight execution context used during the
27/// authentication phase of a transaction.
28///
29/// It allows authenticator functions to:
30/// - Inspect the programmable transaction block (PTB) inputs and commands
31/// - Perform function-level permission checks
32/// - Support OTP, time-locked auth, or regulatory rule enforcement
33///
34/// This struct is **immutable** during the auth phase and must not allow
35/// mutation of state or access to storage beyond what is declared.
36///
37/// It is guaranteed to be available to all smart accounts implementing a
38/// custom authenticator function.
39///
40/// Typical use:
41/// ```move
42/// public fun authenticate(account: &Account, signature: &vector<u8>, auth_ctx: &AuthContext, ctx: &TxContext) {
43///     assert!(ed25519::ed25519_verify(signature, &account.pub_key, ctx.digest()), EEd25519VerificationFailed);
44///
45///     assert!(is_authorized(&extract_function_key(&auth_ctx)), EUnauthorized);
46///     ...
47/// }
48/// ```
49// Conceptually similar to `TxContext`, but designed specifically for use in the authentication
50// flow.
51#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
52pub struct AuthContext {
53    /// The digest of the MoveAuthenticator
54    auth_digest: MoveAuthenticatorDigest,
55    /// The sender's auth digest. For [`MoveAuthenticator`] signatures equals
56    /// [`MoveAuthenticator::digest()`]; for others Blake2b256 of the
57    /// serialized (flag-prefixed) signature bytes.
58    sender_auth_digest: Digest,
59    /// The sponsor's auth digest, present only for sponsored transactions.
60    /// For [`MoveAuthenticator`] signatures equals
61    /// [`MoveAuthenticator::digest()`]; for others Blake2b256 of the
62    /// serialized (flag-prefixed) signature bytes.
63    sponsor_auth_digest: Option<Digest>,
64    /// The sender's authenticator function ref, present when the sender uses a
65    /// [`MoveAuthenticator`] signature.
66    sender_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
67    /// The sponsor's authenticator function ref, present when the sponsor uses
68    /// a [`MoveAuthenticator`] signature.
69    sponsor_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
70    /// The authentication input objects or primitive values
71    tx_inputs: Vec<MoveCallArg>,
72    /// The authentication commands to be executed sequentially.
73    tx_commands: Vec<MoveCommand>,
74    /// The BCS-serialized `TransactionData` bytes.
75    tx_data_bytes: Vec<u8>,
76}
77
78impl AuthContext {
79    pub fn new_from_components(
80        auth_digest: MoveAuthenticatorDigest,
81        sender_auth_digest: Digest,
82        sponsor_auth_digest: Option<Digest>,
83        sender_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
84        sponsor_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
85        ptb: &ProgrammableTransaction,
86        tx_data_bytes: Vec<u8>,
87    ) -> Self {
88        Self {
89            auth_digest,
90            sender_auth_digest,
91            sponsor_auth_digest,
92            sender_authenticator_function_ref_v1,
93            sponsor_authenticator_function_ref_v1,
94            tx_inputs: ptb.inputs.iter().map(MoveCallArg::from).collect(),
95            tx_commands: ptb.commands.iter().map(MoveCommand::from).collect(),
96            tx_data_bytes,
97        }
98    }
99
100    pub fn new_for_testing() -> Self {
101        Self {
102            auth_digest: MoveAuthenticatorDigest::default(),
103            sender_auth_digest: Digest::default(),
104            sponsor_auth_digest: None,
105            sender_authenticator_function_ref_v1: None,
106            sponsor_authenticator_function_ref_v1: None,
107            tx_inputs: Vec::new(),
108            tx_commands: Vec::new(),
109            tx_data_bytes: Vec::new(),
110        }
111    }
112
113    /// Returns the MoveAuthenticator digest.
114    pub fn digest(&self) -> &MoveAuthenticatorDigest {
115        &self.auth_digest
116    }
117
118    /// Returns the sender's auth digest. For
119    /// [`MoveAuthenticator`](crate::move_authenticator::MoveAuthenticator)
120    /// signatures equals
121    /// [`MoveAuthenticator::digest()`](crate::move_authenticator::MoveAuthenticator::digest);
122    /// for others Blake2b256 of the serialized (flag-prefixed) signature bytes.
123    pub fn sender_auth_digest(&self) -> &Digest {
124        &self.sender_auth_digest
125    }
126
127    /// Returns the sponsor's auth digest for sponsored transactions, `None`
128    /// otherwise. For
129    /// [`MoveAuthenticator`](crate::move_authenticator::MoveAuthenticator)
130    /// signatures equals
131    /// [`MoveAuthenticator::digest()`](crate::move_authenticator::MoveAuthenticator::digest);
132    /// for others Blake2b256 of the serialized (flag-prefixed) signature bytes.
133    pub fn sponsor_auth_digest(&self) -> Option<&Digest> {
134        self.sponsor_auth_digest.as_ref()
135    }
136
137    /// Returns the sender's authenticator function ref, present when the sender
138    /// uses a [`MoveAuthenticator`](crate::move_authenticator::MoveAuthenticator) signature.
139    pub fn sender_authenticator_function_ref_v1(&self) -> Option<&AuthenticatorFunctionRefV1> {
140        self.sender_authenticator_function_ref_v1.as_ref()
141    }
142
143    /// Returns the sponsor's authenticator function ref, present when the
144    /// sponsor uses a
145    /// [`MoveAuthenticator`](crate::move_authenticator::MoveAuthenticator)
146    /// signature.
147    pub fn sponsor_authenticator_function_ref_v1(&self) -> Option<&AuthenticatorFunctionRefV1> {
148        self.sponsor_authenticator_function_ref_v1.as_ref()
149    }
150
151    pub fn tx_inputs(&self) -> &Vec<MoveCallArg> {
152        &self.tx_inputs
153    }
154
155    pub fn tx_commands(&self) -> &Vec<MoveCommand> {
156        &self.tx_commands
157    }
158
159    pub fn tx_data_bytes(&self) -> &Vec<u8> {
160        &self.tx_data_bytes
161    }
162
163    pub fn to_bcs_bytes(&self) -> Vec<u8> {
164        bcs::to_bytes(&self).unwrap()
165    }
166
167    pub fn to_move_bcs_bytes(&self) -> Vec<u8> {
168        bcs::to_bytes(&MoveAuthContext::default()).unwrap()
169    }
170
171    /// Returns whether the type signature is &mut AuthContext, &AuthContext, or
172    /// none of the above.
173    pub fn kind(module: &CompiledModule, token: &SignatureToken) -> AuthContextKind {
174        use SignatureToken as S;
175
176        let (kind, token) = match token {
177            S::MutableReference(token) => (AuthContextKind::Mutable, token),
178            S::Reference(token) => (AuthContextKind::Immutable, token),
179            _ => return AuthContextKind::None,
180        };
181
182        let S::Datatype(idx) = &**token else {
183            return AuthContextKind::None;
184        };
185
186        let (module_addr, module_name, struct_name) = resolve_struct(module, *idx);
187
188        if is_auth_context(module_addr, module_name, struct_name) {
189            kind
190        } else {
191            AuthContextKind::None
192        }
193    }
194
195    pub fn type_() -> StructTag {
196        StructTag {
197            address: IOTA_FRAMEWORK_ADDRESS,
198            module: AUTH_CONTEXT_MODULE_NAME.to_owned(),
199            name: AUTH_CONTEXT_STRUCT_NAME.to_owned(),
200            type_params: vec![],
201        }
202    }
203
204    /// Replaces the contents of the `AuthContext` with new values. This is
205    /// intended for use within a Move test function, as the `AuthContext`
206    /// should be immutable during normal use.
207    pub fn replace(
208        &mut self,
209        auth_digest: MoveAuthenticatorDigest,
210        tx_inputs: Vec<MoveCallArg>,
211        tx_commands: Vec<MoveCommand>,
212        tx_data_bytes: Vec<u8>,
213        sender_auth_digest: Digest,
214        sponsor_auth_digest: Option<Digest>,
215        sender_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
216        sponsor_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
217    ) {
218        self.auth_digest = auth_digest;
219        self.tx_inputs = tx_inputs;
220        self.tx_commands = tx_commands;
221        self.tx_data_bytes = tx_data_bytes;
222        self.sender_auth_digest = sender_auth_digest;
223        self.sponsor_auth_digest = sponsor_auth_digest;
224        self.sender_authenticator_function_ref_v1 = sender_authenticator_function_ref_v1;
225        self.sponsor_authenticator_function_ref_v1 = sponsor_authenticator_function_ref_v1;
226    }
227}
228
229/// A Move-side `AuthContext` representation.
230/// It is supposed to be used with empty fields since the Move `AuthContext`
231/// struct is managed by the native functions.
232#[derive(Default, Serialize)]
233pub struct MoveAuthContext {
234    auth_digest: MoveAuthenticatorDigest,
235    tx_inputs: Vec<MoveCallArg>,
236    tx_commands: Vec<MoveCommand>,
237}
238
239#[derive(PartialEq, Eq, Clone, Copy)]
240pub enum AuthContextKind {
241    // Not AuthContext
242    None,
243    // &mut AuthContext
244    Mutable,
245    // &AuthContext
246    Immutable,
247}
248
249pub fn is_auth_context(
250    module_addr: &AccountAddress,
251    module_name: &IdentStr,
252    struct_name: &IdentStr,
253) -> bool {
254    module_addr == &IOTA_FRAMEWORK_ADDRESS
255        && module_name == AUTH_CONTEXT_MODULE_NAME
256        && struct_name == AUTH_CONTEXT_STRUCT_NAME
257}
258
259#[derive(Clone, Debug, PartialEq, Eq)]
260pub struct AuthContextData {
261    pub transaction_data_bytes: Vec<u8>,
262    pub sender_auth_digest: Digest,
263    pub sponsor_auth_digest: Option<Digest>,
264    pub sender_authenticator_function_ref: Option<AuthenticatorFunctionRef>,
265    pub sponsor_authenticator_function_ref: Option<AuthenticatorFunctionRef>,
266}
267
268#[cfg(test)]
269mod tests {
270    use iota_sdk_types::{Argument, Command, Identifier, ObjectId, TypeTag};
271
272    use super::*;
273    use crate::transaction::{CallArg, ProgrammableTransaction};
274
275    #[test]
276    fn auth_context_new_from_components() {
277        let auth_digest = MoveAuthenticatorDigest::new([1u8; 32]);
278        let sender_auth_digest = Digest::new([2u8; 32]);
279        let sponsor_auth_digest = Some(Digest::new([3u8; 32]));
280        let tx_data_bytes = vec![0xde, 0xad, 0xbe, 0xef];
281
282        let ptb = ProgrammableTransaction {
283            inputs: vec![CallArg::Pure(vec![0xab])],
284            commands: vec![Command::new_move_call(
285                ObjectId::from_prefixed_short_hex("0x0000000000000000000000000000000000000001")
286                    .unwrap(),
287                Identifier::new_unchecked("mod"),
288                Identifier::new_unchecked("fun"),
289                vec![TypeTag::U8],
290                vec![Argument::Gas],
291            )],
292        };
293
294        let sender_auth_fun_ref_v1 = AuthenticatorFunctionRefV1 {
295            package: ObjectId::from([0xAAu8; 32]),
296            module: "sender_mod".to_string(),
297            function: "authenticate".to_string(),
298        };
299        let sponsor_auth_fun_ref_v1 = AuthenticatorFunctionRefV1 {
300            package: ObjectId::from([0xBBu8; 32]),
301            module: "sponsor_mod".to_string(),
302            function: "authenticate".to_string(),
303        };
304
305        let ctx = AuthContext::new_from_components(
306            auth_digest,
307            sender_auth_digest,
308            sponsor_auth_digest,
309            Some(sender_auth_fun_ref_v1.clone()),
310            Some(sponsor_auth_fun_ref_v1.clone()),
311            &ptb,
312            tx_data_bytes.clone(),
313        );
314
315        // auth_digest
316        assert_eq!(ctx.digest(), &auth_digest);
317
318        // sender_auth_digest
319        assert_eq!(ctx.sender_auth_digest(), &sender_auth_digest);
320
321        // sponsor_auth_digest
322        assert_eq!(ctx.sponsor_auth_digest(), sponsor_auth_digest.as_ref());
323
324        // sender_authenticator_function_ref_v1
325        assert_eq!(
326            ctx.sender_authenticator_function_ref_v1(),
327            Some(&sender_auth_fun_ref_v1)
328        );
329
330        // sponsor_authenticator_function_ref_v1
331        assert_eq!(
332            ctx.sponsor_authenticator_function_ref_v1(),
333            Some(&sponsor_auth_fun_ref_v1)
334        );
335
336        // tx_inputs: one Pure input
337        assert_eq!(ctx.tx_inputs().len(), 1);
338        assert!(matches!(ctx.tx_inputs()[0], MoveCallArg::Pure(_)));
339
340        // tx_commands: one MoveCall command
341        assert_eq!(ctx.tx_commands().len(), 1);
342        let MoveCommand::MoveCall(call) = &ctx.tx_commands()[0] else {
343            panic!("expected MoveCall");
344        };
345        assert_eq!(call.type_arguments, vec![TypeTag::U8]);
346
347        // tx_data_bytes
348        assert_eq!(ctx.tx_data_bytes(), &tx_data_bytes);
349    }
350
351    #[test]
352    fn auth_context_to_bcs_bytes_is_deterministic() {
353        let ctx = AuthContext::new_for_testing();
354        assert_eq!(ctx.to_bcs_bytes(), ctx.to_bcs_bytes());
355    }
356
357    #[test]
358    fn auth_context_to_bcs_bytes_reflects_content() {
359        let mut ctx = AuthContext::new_for_testing();
360        let empty_bytes = ctx.to_bcs_bytes();
361
362        ctx.replace(
363            MoveAuthenticatorDigest::default(),
364            vec![MoveCallArg::Pure(vec![1])],
365            vec![],
366            vec![],
367            Digest::default(),
368            None,
369            None,
370            None,
371        );
372        let non_empty_bytes = ctx.to_bcs_bytes();
373
374        assert_ne!(empty_bytes, non_empty_bytes);
375    }
376}