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 iota_sdk_types::{Digest, MoveAuthenticatorDigest, ProgrammableTransaction};
8use move_binary_format::{CompiledModule, file_format::SignatureToken};
9use move_bytecode_utils::resolve_struct;
10use move_core_types::{
11    account_address::AccountAddress, ident_str, identifier::IdentStr, language_storage::StructTag,
12};
13use serde::Serialize;
14
15use crate::{
16    IOTA_FRAMEWORK_ADDRESS,
17    account_abstraction::authenticator_function::{
18        AuthenticatorFunctionRef, AuthenticatorFunctionRefV1,
19    },
20};
21
22pub const AUTH_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("auth_context");
23pub const AUTH_CONTEXT_STRUCT_NAME: &IdentStr = ident_str!("AuthContext");
24
25/// `AuthContext` provides a lightweight execution context used during the
26/// authentication phase of a transaction.
27///
28/// It allows authenticator functions to:
29/// - Inspect the programmable transaction block (PTB) inputs and commands
30/// - Perform function-level permission checks
31/// - Support OTP, time-locked auth, or regulatory rule enforcement
32///
33/// This struct is **immutable** during the auth phase and must not allow
34/// mutation of state or access to storage beyond what is declared.
35///
36/// It is guaranteed to be available to all smart accounts implementing a
37/// custom authenticator function.
38///
39/// Typical use:
40/// ```move
41/// public fun authenticate(account: &Account, signature: &vector<u8>, auth_ctx: &AuthContext, ctx: &TxContext) {
42///     assert!(ed25519::ed25519_verify(signature, &account.pub_key, ctx.digest()), EEd25519VerificationFailed);
43///
44///     assert!(is_authorized(&extract_function_key(&auth_ctx)), EUnauthorized);
45///     ...
46/// }
47/// ```
48// Conceptually similar to `TxContext`, but designed specifically for use in the authentication
49// flow.
50#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
51pub struct AuthContext {
52    /// The digest of the MoveAuthenticator
53    auth_digest: MoveAuthenticatorDigest,
54    /// The sender's auth digest. For [`MoveAuthenticator`] signatures equals
55    /// [`MoveAuthenticator::digest()`]; for others Blake2b256 of the
56    /// serialized (flag-prefixed) signature bytes.
57    sender_auth_digest: Digest,
58    /// The sponsor's auth digest, present only for sponsored transactions.
59    /// For [`MoveAuthenticator`] signatures equals
60    /// [`MoveAuthenticator::digest()`]; for others Blake2b256 of the
61    /// serialized (flag-prefixed) signature bytes.
62    sponsor_auth_digest: Option<Digest>,
63    /// The sender's authenticator function ref, present when the sender uses a
64    /// [`MoveAuthenticator`] signature.
65    sender_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
66    /// The sponsor's authenticator function ref, present when the sponsor uses
67    /// a [`MoveAuthenticator`] signature.
68    sponsor_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
69    /// The authentication input objects or primitive values
70    tx_inputs: Vec<MoveCallArg>,
71    /// The authentication commands to be executed sequentially.
72    tx_commands: Vec<MoveCommand>,
73    /// The BCS-serialized `TransactionData` bytes.
74    tx_data_bytes: Vec<u8>,
75}
76
77impl AuthContext {
78    pub fn new_from_components(
79        auth_digest: MoveAuthenticatorDigest,
80        sender_auth_digest: Digest,
81        sponsor_auth_digest: Option<Digest>,
82        sender_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
83        sponsor_authenticator_function_ref_v1: Option<AuthenticatorFunctionRefV1>,
84        ptb: &ProgrammableTransaction,
85        tx_data_bytes: Vec<u8>,
86    ) -> Self {
87        Self {
88            auth_digest,
89            sender_auth_digest,
90            sponsor_auth_digest,
91            sender_authenticator_function_ref_v1,
92            sponsor_authenticator_function_ref_v1,
93            tx_inputs: ptb.inputs.iter().map(MoveCallArg::from).collect(),
94            tx_commands: ptb.commands.iter().map(MoveCommand::from).collect(),
95            tx_data_bytes,
96        }
97    }
98
99    pub fn new_for_testing() -> Self {
100        Self {
101            auth_digest: MoveAuthenticatorDigest::default(),
102            sender_auth_digest: Digest::default(),
103            sponsor_auth_digest: None,
104            sender_authenticator_function_ref_v1: None,
105            sponsor_authenticator_function_ref_v1: None,
106            tx_inputs: Vec::new(),
107            tx_commands: Vec::new(),
108            tx_data_bytes: Vec::new(),
109        }
110    }
111
112    /// Returns the MoveAuthenticator digest.
113    pub fn digest(&self) -> &MoveAuthenticatorDigest {
114        &self.auth_digest
115    }
116
117    /// Returns the sender's auth digest. For
118    /// [`MoveAuthenticator`](iota_sdk_types::MoveAuthenticator)
119    /// signatures equals
120    /// [`MoveAuthenticator::digest()`](iota_sdk_types::MoveAuthenticator::digest);
121    /// for others Blake2b256 of the serialized (flag-prefixed) signature bytes.
122    pub fn sender_auth_digest(&self) -> &Digest {
123        &self.sender_auth_digest
124    }
125
126    /// Returns the sponsor's auth digest for sponsored transactions, `None`
127    /// otherwise. For
128    /// [`MoveAuthenticator`](iota_sdk_types::MoveAuthenticator)
129    /// signatures equals
130    /// [`MoveAuthenticator::digest()`](iota_sdk_types::MoveAuthenticator::digest);
131    /// for others Blake2b256 of the serialized (flag-prefixed) signature bytes.
132    pub fn sponsor_auth_digest(&self) -> Option<&Digest> {
133        self.sponsor_auth_digest.as_ref()
134    }
135
136    /// Returns the sender's authenticator function ref, present when the sender
137    /// uses a [`MoveAuthenticator`](iota_sdk_types::MoveAuthenticator)
138    /// 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`](iota_sdk_types::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::{
271        Argument, Command, Identifier, ObjectId, ProgrammableTransaction, TypeTag,
272    };
273
274    use super::*;
275    use crate::transaction::CallArg;
276
277    #[test]
278    fn auth_context_new_from_components() {
279        let auth_digest = MoveAuthenticatorDigest::new([1u8; 32]);
280        let sender_auth_digest = Digest::new([2u8; 32]);
281        let sponsor_auth_digest = Some(Digest::new([3u8; 32]));
282        let tx_data_bytes = vec![0xde, 0xad, 0xbe, 0xef];
283
284        let ptb = ProgrammableTransaction {
285            inputs: vec![CallArg::Pure(vec![0xab])],
286            commands: vec![Command::new_move_call(
287                ObjectId::from_prefixed_short_hex("0x0000000000000000000000000000000000000001")
288                    .unwrap(),
289                Identifier::new_unchecked("mod"),
290                Identifier::new_unchecked("fun"),
291                vec![TypeTag::U8],
292                vec![Argument::Gas],
293            )],
294        };
295
296        let sender_auth_fun_ref_v1 = AuthenticatorFunctionRefV1 {
297            package: ObjectId::from([0xAAu8; 32]),
298            module: "sender_mod".to_string(),
299            function: "authenticate".to_string(),
300        };
301        let sponsor_auth_fun_ref_v1 = AuthenticatorFunctionRefV1 {
302            package: ObjectId::from([0xBBu8; 32]),
303            module: "sponsor_mod".to_string(),
304            function: "authenticate".to_string(),
305        };
306
307        let ctx = AuthContext::new_from_components(
308            auth_digest,
309            sender_auth_digest,
310            sponsor_auth_digest,
311            Some(sender_auth_fun_ref_v1.clone()),
312            Some(sponsor_auth_fun_ref_v1.clone()),
313            &ptb,
314            tx_data_bytes.clone(),
315        );
316
317        // auth_digest
318        assert_eq!(ctx.digest(), &auth_digest);
319
320        // sender_auth_digest
321        assert_eq!(ctx.sender_auth_digest(), &sender_auth_digest);
322
323        // sponsor_auth_digest
324        assert_eq!(ctx.sponsor_auth_digest(), sponsor_auth_digest.as_ref());
325
326        // sender_authenticator_function_ref_v1
327        assert_eq!(
328            ctx.sender_authenticator_function_ref_v1(),
329            Some(&sender_auth_fun_ref_v1)
330        );
331
332        // sponsor_authenticator_function_ref_v1
333        assert_eq!(
334            ctx.sponsor_authenticator_function_ref_v1(),
335            Some(&sponsor_auth_fun_ref_v1)
336        );
337
338        // tx_inputs: one Pure input
339        assert_eq!(ctx.tx_inputs().len(), 1);
340        assert!(matches!(ctx.tx_inputs()[0], MoveCallArg::Pure(_)));
341
342        // tx_commands: one MoveCall command
343        assert_eq!(ctx.tx_commands().len(), 1);
344        let MoveCommand::MoveCall(call) = &ctx.tx_commands()[0] else {
345            panic!("expected MoveCall");
346        };
347        assert_eq!(call.type_arguments, vec![TypeTag::U8]);
348
349        // tx_data_bytes
350        assert_eq!(ctx.tx_data_bytes(), &tx_data_bytes);
351    }
352
353    #[test]
354    fn auth_context_to_bcs_bytes_is_deterministic() {
355        let ctx = AuthContext::new_for_testing();
356        assert_eq!(ctx.to_bcs_bytes(), ctx.to_bcs_bytes());
357    }
358
359    #[test]
360    fn auth_context_to_bcs_bytes_reflects_content() {
361        let mut ctx = AuthContext::new_for_testing();
362        let empty_bytes = ctx.to_bcs_bytes();
363
364        ctx.replace(
365            MoveAuthenticatorDigest::default(),
366            vec![MoveCallArg::Pure(vec![1])],
367            vec![],
368            vec![],
369            Digest::default(),
370            None,
371            None,
372            None,
373        );
374        let non_empty_bytes = ctx.to_bcs_bytes();
375
376        assert_ne!(empty_bytes, non_empty_bytes);
377    }
378}