From 5fc552c13b6f273e3cdb792b6372a0179ab2ee16 Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Wed, 3 Sep 2025 16:07:28 +0200 Subject: [PATCH 01/10] refactor(iota-types): AuthenticatorInfoV1 now follows expected type check patterns --- crates/iota-core/src/authority.rs | 10 +++---- crates/iota-types/src/account.rs | 27 +++++++++---------- crates/iota-types/src/base_types.rs | 10 +++++++ .../iota-adapter/src/execution_engine.rs | 6 ++--- iota-execution/src/executor.rs | 4 +-- iota-execution/src/latest.rs | 4 +-- 6 files changed, 35 insertions(+), 26 deletions(-) diff --git a/crates/iota-core/src/authority.rs b/crates/iota-core/src/authority.rs index deb5ba18e06..f32621af916 100644 --- a/crates/iota-core/src/authority.rs +++ b/crates/iota-core/src/authority.rs @@ -54,7 +54,7 @@ use iota_storage::{ use iota_types::committee::CommitteeTrait; use iota_types::{ IOTA_SYSTEM_ADDRESS, TypeTag, - account::{AUTHENTICATOR_DF_NAME, AuthenticatorInfo}, + account::{AUTHENTICATOR_DF_NAME, AuthenticatorInfoV1}, authenticator_state::get_authenticator_state, base_types::*, committee::{Committee, EpochId, ProtocolVersion}, @@ -5217,7 +5217,7 @@ impl AuthorityState { auth_account_object_digest: Option, account_object: ObjectReadResult, signer: &IotaAddress, - ) -> IotaResult { + ) -> IotaResult { let account_object = match account_object.object { ObjectReadResultKind::Object(object) => Ok(object), ObjectReadResultKind::DeletedSharedObject(version, digest) => { @@ -5289,7 +5289,7 @@ impl AuthorityState { let authenticator_id = self.get_dynamic_field_object_id( auth_account_object_id, - AuthenticatorInfo::tag().into(), + AuthenticatorInfoV1::tag().into(), AUTHENTICATOR_DF_NAME.as_bytes(), )?; @@ -5302,7 +5302,7 @@ impl AuthorityState { )?; if let Some(authenticator_info) = authenticator_info { - AuthenticatorInfo::try_from(authenticator_info) + AuthenticatorInfoV1::try_from(authenticator_info) } else { Err(UserInputError::MoveAuthenticatorNotFound { authenticator_object_id: authenticator_id, @@ -5329,7 +5329,7 @@ impl AuthorityState { protocol_config: &ProtocolConfig, reference_gas_price: u64, transaction: &TransactionData, - ) -> IotaResult<(IotaGasStatus, CheckedInputObjects, AuthenticatorInfo)> { + ) -> IotaResult<(IotaGasStatus, CheckedInputObjects, AuthenticatorInfoV1)> { let digest = transaction.digest(); let signer = transaction.sender(); diff --git a/crates/iota-types/src/account.rs b/crates/iota-types/src/account.rs index 9f233df4b49..ea258387ecf 100644 --- a/crates/iota-types/src/account.rs +++ b/crates/iota-types/src/account.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::{ IOTA_FRAMEWORK_ADDRESS, - base_types::{MoveObjectType, ObjectID}, + base_types::ObjectID, error::IotaError, object::{Data, Object}, }; @@ -17,16 +17,16 @@ use crate::{ pub const AUTHENTICATOR_DF_NAME: &str = "IOTA_AUTHENTICATION"; pub const AUTHENTICATOR_INFO_MODULE_NAME: &IdentStr = ident_str!("account"); -pub const AUTHENTICATOR_INFO_STRUCT_NAME: &IdentStr = ident_str!("AuthenticatorInfo"); +pub const AUTHENTICATOR_INFO_STRUCT_NAME: &IdentStr = ident_str!("AuthenticatorInfoV1"); #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] -pub struct AuthenticatorInfo { +pub struct AuthenticatorInfoV1 { pub package: ObjectID, pub module: String, pub function: String, } -impl AuthenticatorInfo { +impl AuthenticatorInfoV1 { pub fn tag() -> StructTag { StructTag { address: IOTA_FRAMEWORK_ADDRESS, @@ -38,32 +38,31 @@ impl AuthenticatorInfo { pub fn from_bcs_bytes(content: &[u8]) -> Result { bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization { - error: format!("Unable to deserialize AuthenticatorInfo object: {err}"), + error: format!("Unable to deserialize AuthenticatorInfoV1 object: {err}"), }) } - // TODO: Needs to be moved to MoveObjectType. - pub fn is_authenticator_info(other: &MoveObjectType) -> bool { - other.address() == IOTA_FRAMEWORK_ADDRESS - && other.module() == AUTHENTICATOR_INFO_MODULE_NAME - && other.name() == AUTHENTICATOR_INFO_STRUCT_NAME + pub fn is_authenticator_info(tag: &StructTag) -> bool { + tag.address == IOTA_FRAMEWORK_ADDRESS + && tag.module.as_ident_str() == AUTHENTICATOR_INFO_MODULE_NAME + && tag.name.as_ident_str() == AUTHENTICATOR_INFO_STRUCT_NAME } } -impl TryFrom for AuthenticatorInfo { +impl TryFrom for AuthenticatorInfoV1 { type Error = IotaError; fn try_from(object: Object) -> Result { match &object.data { Data::Move(o) => { - if AuthenticatorInfo::is_authenticator_info(o.type_()) { - return AuthenticatorInfo::from_bcs_bytes(o.contents()); + if o.type_().is_authenticator_info_v1() { + return AuthenticatorInfoV1::from_bcs_bytes(o.contents()); } } Data::Package(_) => {} } Err(IotaError::Type { - error: format!("Object type is not a AuthenticatorInfo: {object:?}"), + error: format!("Object type is not a AuthenticatorInfoV1: {object:?}"), }) } } diff --git a/crates/iota-types/src/base_types.rs b/crates/iota-types/src/base_types.rs index 4a88c1ced2a..2ac4fe85349 100644 --- a/crates/iota-types/src/base_types.rs +++ b/crates/iota-types/src/base_types.rs @@ -36,6 +36,7 @@ use shared_crypto::intent::HashingIntentScope; use crate::{ IOTA_CLOCK_OBJECT_ID, IOTA_FRAMEWORK_ADDRESS, IOTA_SYSTEM_ADDRESS, MOVE_STDLIB_ADDRESS, + account::AuthenticatorInfoV1, balance::Balance, coin::{COIN_MODULE_NAME, COIN_STRUCT_NAME, Coin, CoinMetadata, TreasuryCap}, coin_manager::CoinManager, @@ -420,6 +421,15 @@ impl MoveObjectType { } } + pub fn is_authenticator_info_v1(&self) -> bool { + match &self.0 { + MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => { + false + } + MoveObjectType_::Other(s) => AuthenticatorInfoV1::is_authenticator_info(s), + } + } + pub fn try_extract_field_name(&self, type_: &DynamicFieldType) -> IotaResult { match &self.0 { MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => { diff --git a/iota-execution/latest/iota-adapter/src/execution_engine.rs b/iota-execution/latest/iota-adapter/src/execution_engine.rs index a4418a71c6d..75e7208fbd0 100644 --- a/iota-execution/latest/iota-adapter/src/execution_engine.rs +++ b/iota-execution/latest/iota-adapter/src/execution_engine.rs @@ -16,7 +16,7 @@ mod checked { use iota_types::{ IOTA_AUTHENTICATOR_STATE_OBJECT_ID, IOTA_FRAMEWORK_ADDRESS, IOTA_FRAMEWORK_PACKAGE_ID, IOTA_RANDOMNESS_STATE_OBJECT_ID, IOTA_SYSTEM_PACKAGE_ID, Identifier, - account::AuthenticatorInfo, + account::AuthenticatorInfoV1, auth_context::AuthContext, authenticator_state::{ AUTHENTICATOR_STATE_CREATE_FUNCTION_NAME, @@ -269,7 +269,7 @@ mod checked { gas_status: IotaGasStatus, // Authenticator authenticator: MoveAuthenticator, - authenticator_info: AuthenticatorInfo, + authenticator_info: AuthenticatorInfoV1, authenticator_input_objects: CheckedInputObjects, // Transaction authenticated_transaction_kind: TransactionKind, @@ -1566,7 +1566,7 @@ mod checked { /// last input. fn setup_authenticator_move_call( authenticator: MoveAuthenticator, - authenticator_info: AuthenticatorInfo, + authenticator_info: AuthenticatorInfoV1, auth_ctx: AuthContext, ) -> Result { let mut builder = ProgrammableTransactionBuilder::new(); diff --git a/iota-execution/src/executor.rs b/iota-execution/src/executor.rs index b8ddb12a24c..a5d38eb44ec 100644 --- a/iota-execution/src/executor.rs +++ b/iota-execution/src/executor.rs @@ -6,7 +6,7 @@ use std::{collections::HashSet, sync::Arc}; use iota_protocol_config::ProtocolConfig; use iota_types::{ - account::AuthenticatorInfo, + account::AuthenticatorInfoV1, base_types::{IotaAddress, ObjectRef, TxContext}, committee::EpochId, digests::TransactionDigest, @@ -94,7 +94,7 @@ pub trait Executor { gas_status: IotaGasStatus, // Authenticator authenticator: MoveAuthenticator, - authenticator_info: AuthenticatorInfo, + authenticator_info: AuthenticatorInfoV1, authenticator_input_objects: CheckedInputObjects, // Transaction authenticated_transaction_kind: TransactionKind, diff --git a/iota-execution/src/latest.rs b/iota-execution/src/latest.rs index 8a103054b19..17de60a9ae0 100644 --- a/iota-execution/src/latest.rs +++ b/iota-execution/src/latest.rs @@ -15,7 +15,7 @@ use iota_adapter_latest::{ use iota_move_natives_latest::all_natives; use iota_protocol_config::ProtocolConfig; use iota_types::{ - account::AuthenticatorInfo, + account::AuthenticatorInfoV1, base_types::{IotaAddress, ObjectRef, TxContext}, committee::EpochId, digests::TransactionDigest, @@ -182,7 +182,7 @@ impl executor::Executor for Executor { gas_status: IotaGasStatus, // Authenticator authenticator: MoveAuthenticator, - authenticator_info: AuthenticatorInfo, + authenticator_info: AuthenticatorInfoV1, authenticator_input_objects: CheckedInputObjects, // Transaction authenticated_transaction_kind: TransactionKind, From 336dd7499b5318f3eda829e96b9c0fe0906fe6ea Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Thu, 4 Sep 2025 14:55:08 +0200 Subject: [PATCH 02/10] feat(iota-verifier): Add account `authenticate` verifier A verifier for validating `authenticate` functions introduced for `Move Authentication` (Account Abstraction). Contrary to other verifiers this shouldn't/can't be executed statically. This is why it doesn't appear among the list of other verifiers in: iota_verify_module_metered function. By design it has to be executed during runtime, as it is only executed for a single user specified function. The exact identity of this function will not be known until runtime. --- crates/iota-types/src/auth_context.rs | 45 +++ .../move-binary-format/src/file_format.rs | 17 +- .../src/account_auth_verifier.rs | 261 ++++++++++++++++++ .../latest/iota-verifier/src/lib.rs | 1 + 4 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 iota-execution/latest/iota-verifier/src/account_auth_verifier.rs diff --git a/crates/iota-types/src/auth_context.rs b/crates/iota-types/src/auth_context.rs index 378a1202a3f..5538eeb3380 100644 --- a/crates/iota-types/src/auth_context.rs +++ b/crates/iota-types/src/auth_context.rs @@ -1,13 +1,20 @@ // Copyright (c) 2025 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use move_binary_format::{CompiledModule, file_format::SignatureToken}; +use move_bytecode_utils::resolve_struct; +use move_core_types::{ident_str, identifier::IdentStr}; use serde::{Deserialize, Serialize}; use crate::{ + IOTA_FRAMEWORK_ADDRESS, digests::MoveAuthenticatorDigest, transaction::{CallArg, Command, ProgrammableTransaction}, }; +pub const AUTH_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("auth_context"); +pub const AUTH_CONTEXT_STRUCT_NAME: &IdentStr = ident_str!("AuthContext"); + /// `AuthContext` provides a lightweight execution context used during the /// authentication phase of a transaction. /// @@ -70,4 +77,42 @@ impl AuthContext { pub fn to_bcs_bytes(&self) -> Vec { bcs::to_bytes(&self).unwrap() } + + /// Returns whether the type signature is &mut TxContext, &TxContext, or + /// none of the above. + pub fn kind(module: &CompiledModule, token: &SignatureToken) -> AuthContextKind { + use SignatureToken as S; + + let (kind, token) = match token { + S::MutableReference(token) => (AuthContextKind::Mutable, token), + S::Reference(token) => (AuthContextKind::Immutable, token), + _ => return AuthContextKind::None, + }; + + let S::Datatype(idx) = &**token else { + return AuthContextKind::None; + }; + + let (module_addr, module_name, struct_name) = resolve_struct(module, *idx); + + let is_tx_context_type = module_name == AUTH_CONTEXT_MODULE_NAME + && module_addr == &IOTA_FRAMEWORK_ADDRESS + && struct_name == AUTH_CONTEXT_STRUCT_NAME; + + if is_tx_context_type { + kind + } else { + AuthContextKind::None + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum AuthContextKind { + // Not AuthContext + None, + // &mut AuthContext + Mutable, + // &AuthContext + Immutable, } diff --git a/external-crates/move/crates/move-binary-format/src/file_format.rs b/external-crates/move/crates/move-binary-format/src/file_format.rs index 95021188a39..c4e73ba79b5 100644 --- a/external-crates/move/crates/move-binary-format/src/file_format.rs +++ b/external-crates/move/crates/move-binary-format/src/file_format.rs @@ -1077,14 +1077,27 @@ pub enum SignatureToken { Signer, /// Vector Vector(Box), - /// User defined type + /// User defined type with no template parameters. + /// ``` + /// public struct Data {} + /// ``` Datatype(DatatypeHandleIndex), + /// User defined type with template parameters. + /// ``` + /// public struct Data has drop { + /// v: T + /// } + /// ``` DatatypeInstantiation(Box<(DatatypeHandleIndex, Vec)>), /// Reference to a type. Reference(Box), /// Mutable reference to a type. MutableReference(Box), - /// Type parameter. + /// Parameter representing an unresolved template type. + /// ``` + /// fun temp(val: T) {} + /// ``` + /// `T` will be `TypeParameter` TypeParameter(TypeParameterIndex), /// Unsigned integers, 16 bits length. U16, diff --git a/iota-execution/latest/iota-verifier/src/account_auth_verifier.rs b/iota-execution/latest/iota-verifier/src/account_auth_verifier.rs new file mode 100644 index 00000000000..f3f1bb339ea --- /dev/null +++ b/iota-execution/latest/iota-verifier/src/account_auth_verifier.rs @@ -0,0 +1,261 @@ +// Copyright (c) 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +/// Account `authenticate` function verifier +/// +/// The account `authenticate` verifier module is special in the sense +/// that it isn't called on a module during publication/execution, but on a +/// specific `authenticate` function only during execution. +/// This is because an `authenticate` function only exists as a concept. There +/// is no compiler support for identifying/validating them. Furthermore they are +/// resolved dynamically during execution. +use iota_types::{ + Identifier, + auth_context::{AuthContext, AuthContextKind}, + base_types::{ + RESOLVED_ASCII_STR, RESOLVED_STD_OPTION, RESOLVED_UTF8_STR, TxContext, TxContextKind, + }, + error::ExecutionError, + id::RESOLVED_IOTA_ID, +}; +use move_binary_format::{ + CompiledModule, + file_format::{AbilitySet, SignatureToken, Visibility}, +}; +use move_bytecode_utils::resolve_struct; + +use crate::verification_failure; + +/// Verify if a given function can be used as an `authenticate` function +/// +/// A function is an authenticate function if: +/// - only has read-only inputs (immutable owned/shared references or pure +/// types) +/// - has no return type +/// - the last two arguments in order are AuthContext and TxContext +/// - AuthContext has to be an immutable reference +/// - TxContext hat to be an immutable reference +pub fn verify_authenticate_func( + module: &CompiledModule, + function_identifier: Identifier, +) -> Result<(), ExecutionError> { + let module_name = module.name(); + + let Some((_, function_definition)) = + module.find_function_def_by_name(function_identifier.as_str()) + else { + return Err(verification_failure(format!( + "Authenticator function '{function_identifier}' not found in '{module_name}'" + ))); + }; + + if function_definition.is_entry { + return Err(verification_failure(format!( + "Authenticator function '{function_identifier}' cannot be marked as `entry`" + ))); + } + + // Consider alleviating these restrictions in the future by considering: + // - we can execute private functions from the rust side + // - a dev, setting this function as private, means that this is declared as not + // taking part to composability with other authenticate() (same logic as using + // just entry for normal functions) + if function_definition.visibility != Visibility::Public { + return Err(verification_failure(format!( + "Authenticator function '{function_identifier}' must be public" + ))); + } + + let function_handle = module.function_handle_at(function_definition.function); + let function_signature = module.signature_at(function_handle.parameters); + + // at least two arguments + if function_signature.0.len() < 2 { + return Err(verification_failure(format!( + "Authenticator function '{function_identifier}' must require at least &AuthContext and &TxContext arguments." + ))); + } + + // Apart from AuthContext and TxContext we only require that the arguments are + // not mutable references. They can be mutable values, as their mutability + // cannot affect outside state. + for param in function_signature + .0 + .iter() + .take(function_signature.len() - 2) + { + verify_authenticate_param_type(module, &function_handle.type_parameters, param) + .map_err(verification_failure)?; + } + + // Check type of AuthContext and TxContext, they both must be structs with the + // appropriate names, addresses and access + let auth_context = &function_signature.0[function_signature.len() - 2]; + let tx_context = &function_signature.0[function_signature.len() - 1]; + + // AuthContext could potentially passed as value, but that opens up the + // possibility for the `authenticate` function to receive it as mutable + // value, from which it could mutate before passing it to further `authenticate` + // functions, so similarly to TxContext, it is simply not allowed. + if !matches!( + AuthContext::kind(module, auth_context), + AuthContextKind::Immutable + ) { + return Err(verification_failure(format!( + "Authenticator function '{function_identifier}' can only receive + 'AuthContext' as immutable reference" + ))); + } + + // TxContext can only be an immutable reference. Passing it as mutable would + // allow `authenticate` functions to create objects, which would be + // problematic. + if !matches!( + TxContext::kind(module, tx_context), + TxContextKind::Immutable + ) { + return Err(verification_failure(format!( + "Authenticator function '{function_identifier}' can only receive 'TxContext' as immutable reference" + ))); + } + + let return_signature = module.signature_at(function_handle.return_); + if !return_signature.is_empty() { + return Err(verification_failure(format!( + "Authenticator function '{function_identifier}' cannot have a return type" + ))); + } + + Ok(()) +} + +/// Evaluate that signature type is of [pure input](https://docs.iota.org/developer/iota-101/transactions/ptb/programmable-transaction-blocks#inputs) +/// +/// ATTENTION!/// +/// This check implements a very loose definition of a pure type, because it is +/// based on the assumption that the authenticate function is executed +/// equivalently to a PTB with a single command. +/// 1. This means that potentially, a parameter of type `T`, with `T` being a +/// generic, would be accepted by the check of this verify function even if +/// the instance of `T` is not pure by definition. An example is passing an +/// instance of the `Simple` as concrete type of T; in this case, `Simple` is +/// not considered pure. This verify function works as this because it is +/// executed in a moment in which the concrete types of a generic are not +/// known. However, since the authenticate function is executed equivalently +/// to a PTB with a single command, this assures that only pure types and +/// objects can actually be passed by design. So the case of having ´Simple´ +/// as concrete type of `T` cannot exist. +/// 2. Moreover, this check assures that no object can be passed as concrete +/// type of a generic `T` because in the constraints of every generic it +/// checks that the `key` ability is not set. This is not enough because a +/// case like this could happen `fn authenticate()(...)` where the key +/// constraint is not set. In this case the compiler helps us by forcing the +/// `T` concrete type to have a `drop` ability. To calm the compiler down the +/// function `authenticate` must either: +/// 1. not use the `` constraint and return the parameter with type +/// `T` -> this is not allowed by design, as an authenticate function has +/// no returns; +/// 2. not use the `` constraint but the `` constraint -> +/// this is not allowed by this verify function; +/// 3. use the `` constraint -> this means no object type can be +/// used as concrete type because an object with `drop` ability cannot +/// exist. +/// +/// +/// A parameter is considered `pure input` if that can't be used to modify +/// ledger state in any way, i.e., not an object, and that can be constructed +/// before calling the function itself. +/// +/// A general struct, with no unresolved template arguments: +/// ```move +/// public struct Simple has store { +/// a: u8, +/// some_vec: vector +/// } +/// ``` +/// is not considered a `pure input` either as it is not a built-in type so it +/// can't be constructed before the (single) PTB move call itself. On +/// the contrary std::ascii::String and std::string::String are okay. +/// On a similar notion a simple `vector` and an `Option` are both also +/// acceptable as they are built-in move types with rust side counterpart as +/// long as `T` is recursively `pure` as well. +fn verify_pure_input_type( + module: &CompiledModule, + function_type_args: &[AbilitySet], + param: &SignatureToken, +) -> Result<(), String> { + use SignatureToken::*; + + match param { + U8 | U16 | U32 | U64 | U128 | U256 | Bool | Address => Ok(()), + Vector(inner) => verify_pure_input_type(module, function_type_args, inner), + Datatype(handle_index) => { + let resolved_struct = resolve_struct(module, *handle_index); + if resolved_struct == RESOLVED_ASCII_STR + || resolved_struct == RESOLVED_UTF8_STR + || resolved_struct == RESOLVED_IOTA_ID + { + Ok(()) + } else { + Err(format!( + "Invalid pure type. A datatype must be a string or an ID, offending argument: {:?}", + param + )) + } + } + DatatypeInstantiation(datatype_instance) => { + let (idx, type_args) = &**datatype_instance; + let resolved_struct = resolve_struct(module, *idx); + if resolved_struct == RESOLVED_STD_OPTION && type_args.len() == 1 { + verify_pure_input_type(module, function_type_args, &type_args[0]) + } else { + Err(format!( + "Invalid pure type. A datatype instantiation must be an option of pure types, offending argument: {:?}", + param + )) + } + } + TypeParameter(idx) => { + if function_type_args[*idx as usize].has_key() { + Err(format!( + "Invalid pure type. A type parameter cannot have the 'key' ability, offending argument: {:?}", + param + )) + } else { + Ok(()) + } + } + Signer => Err(format!( + "Invalid pure type. Signer is not a pure type, offending argument: {:?}", + param + )), + Reference(_) => Err(format!( + "Invalid pure type. Reference is not a pure type, offending argument: {:?}", + param + )), + MutableReference(_) => Err(format!( + "Invalid pure type. MutableReference is not a pure type, offending argument: {:?}", + param + )), + } +} + +/// Verify that the parameter type is a valid type for an `authenticate` +/// function The parameter type can be: +/// - an immutable reference +/// - a pure input type (see [verify_pure_input_type]) +fn verify_authenticate_param_type( + module: &CompiledModule, + function_type_args: &[AbilitySet], + param: &SignatureToken, +) -> Result<(), String> { + use SignatureToken::*; + + match param { + Reference(_) => Ok(()), + // This mutable reference could be allowed to enable a smother authenticate() function + // composition, but its usage must be checked before being enabled: + // MutableReference(inner) => verify_pure_input_type(module, function_type_args, inner), + _ => verify_pure_input_type(module, function_type_args, param), + } +} diff --git a/iota-execution/latest/iota-verifier/src/lib.rs b/iota-execution/latest/iota-verifier/src/lib.rs index e01d51b6c07..bdcaabee6f5 100644 --- a/iota-execution/latest/iota-verifier/src/lib.rs +++ b/iota-execution/latest/iota-verifier/src/lib.rs @@ -4,6 +4,7 @@ pub mod verifier; +pub mod account_auth_verifier; pub mod entry_points_verifier; pub mod global_storage_access_verifier; pub mod id_leak_verifier; From 8baa24cedd18d842cc4fc68a1ee18685b8f6a60b Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Thu, 4 Sep 2025 16:32:10 +0200 Subject: [PATCH 03/10] feat(iota-framework): Add `create_auth_info_v1` native function Additionally all relevant components which lacked documentation have been updated.` --- Cargo.lock | 1 + .../iota-framework/sources/account.move | 50 ++++++++ .../packages_compiled/iota-framework | Bin 74610 -> 75011 bytes crates/iota-framework/published_api.txt | 60 +++++---- crates/iota-move/src/unit_test.rs | 3 + crates/iota-open-rpc/spec/openrpc.json | 46 +++---- crates/iota-protocol-config/src/lib.rs | 12 ++ ...ota_protocol_config__test__version_12.snap | 1 + ..._populated_genesis_snapshot_matches-2.snap | 28 ++--- crates/iota-types/src/move_package.rs | 119 ++++++++++++++---- .../move-core-types/src/account_address.rs | 5 +- .../move-core-types/src/language_storage.rs | 17 ++- .../move-vm-runtime/src/native_extensions.rs | 7 +- .../latest/iota-adapter/src/adapter.rs | 17 ++- .../iota-adapter/src/execution_value.rs | 5 + .../src/programmable_transactions/context.rs | 1 + .../latest/iota-move-natives/Cargo.toml | 1 + .../latest/iota-move-natives/src/account.rs | 99 +++++++++++++++ .../latest/iota-move-natives/src/lib.rs | 29 ++++- .../src/raw_module_loader/mod.rs | 52 ++++++++ .../iota-move-natives/src/test_scenario.rs | 12 +- 21 files changed, 468 insertions(+), 97 deletions(-) create mode 100644 crates/iota-framework/packages/iota-framework/sources/account.move create mode 100644 iota-execution/latest/iota-move-natives/src/account.rs create mode 100644 iota-execution/latest/iota-move-natives/src/raw_module_loader/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ece90a9c662..280136f6a29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6905,6 +6905,7 @@ dependencies = [ "indexmap 2.5.0", "iota-protocol-config", "iota-types", + "iota-verifier-latest", "move-binary-format", "move-core-types", "move-stdlib-natives", diff --git a/crates/iota-framework/packages/iota-framework/sources/account.move b/crates/iota-framework/packages/iota-framework/sources/account.move new file mode 100644 index 00000000000..08a6afecb9a --- /dev/null +++ b/crates/iota-framework/packages/iota-framework/sources/account.move @@ -0,0 +1,50 @@ +module iota::account; + +use std::ascii; + +/// Dynamic field key, where the system will look for a potential +/// authenticate function. +#[allow(unused_const)] +const AUTHENTICATOR_ID: vector = b"IOTA_AUTHENTICATION"; + +#[allow(unused_field)] +public struct AuthenticatorInfoV1 has store { + package: ID, + module_name: ascii::String, + function_name: ascii::String, +} + +/// Create an "AuthenticatorInfoV1" using an `authenticate` function defined outside of this version of the package +/// +/// The referred `package`, `module_name`, `function_name` can refer to any valid `authenticate` function, +/// regardless of package dependencies or versions. +/// For example package A has two versions V1 and V2. V2 of package A may refer to an `authenticate` +/// function defined in V1. Or it can refer to any package B with an appropriate `authenticate` function +/// even if package A does not have a dependency on package B. +/// In fact package A may have a dependency on package B version 1, but can still refer to an `authenticate` +/// function defined in package B version 2. +/// Refiring to an `authenticate` function with `create_auth_info_v1` is a strictly runtime dependency and +/// it does not collide with any compile time restrictions. +/// +/// The only scenario which cannot be handled by this function is that of referring to an `authenticate` +/// function from the current version of the package. Simply because the current package address won't be know +/// before publishing, thus the user cannot specify it in code. +/// If an `authenticate` function should be used from the current version of the package please use +/// `create_auth_info_self_v1` instead. +public fun create_auth_info_v1( + package: address, + module_name: ascii::String, + function_name: ascii::String, +): AuthenticatorInfoV1 { + create_auth_info_v1_impl(package, module_name.as_bytes(), function_name.as_bytes()) +} + +native fun create_auth_info_v1_impl( + package: address, + module_name: &vector, + function_name: &vector, +): AuthenticatorInfoV1; + +public fun drop_auth_info_v1(auth_info: AuthenticatorInfoV1) { + let AuthenticatorInfoV1 { .. } = auth_info; +} diff --git a/crates/iota-framework/packages_compiled/iota-framework b/crates/iota-framework/packages_compiled/iota-framework index db81f4d0c574e6b60379af6aa52849523162ee46..7163b228388ca7d9f1bbb0df57e4f4a9c3acbe46 100644 GIT binary patch delta 384 zcmZvYzfZzY5XbNCwXcu15c!cvi~|muII=p?s0np|L3C{%rC=3W0uK`x8TbPnm`U_c zAegv18RN+A>f+{K;M2{-#=cm+W*u{S#~!vn z9S#5)1Ww##g_60qWz`la-m%6Pv}r$*>ps&iFmTWWFaXg4A`5^60$8M*Kn7$OCNNWw zAQT$7O}YTNo;4J`iMogq6^Bx#BR}b;=hZ3sNBmTc;-qKEFiazl;GMPV$BmYM=(YT21Bkf-Z9y5Ke*$2Z13ApI9OYRa XwF8155sW)f)P|5|>%!BzfcG2UZ`4{S delta 36 scmZoZ#q#MG3!~*mMg^A5^I0`HHY>8!F>XG=%gx%HBC, + // `address` module // Cost params for the Move native function `address::from_bytes(bytes: vector)` address_from_bytes_cost_base: Option, @@ -1572,6 +1578,8 @@ impl ProtocolConfig { buffer_stake_for_protocol_upgrade_bps: Some(5000), // === Native Function Costs === + // `account` module + create_auth_info_v1_cost_base: None, // `address` module // Cost params for the Move native function `address::from_bytes(bytes: vector)` address_from_bytes_cost_base: Some(52), @@ -2140,6 +2148,10 @@ impl ProtocolConfig { // max auth gas budget is in NANOS and an absolute value 1IOTA cfg.max_auth_gas = Some(1_000_000_000); cfg.feature_flags.move_auth = true; + // === Native Function Costs === + // `account` module + cfg.create_auth_info_v1_cost_base = Some(1000); /* TODO how do you decide on a good gas * + price? */ } } // Use this template when making changes: diff --git a/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__version_12.snap b/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__version_12.snap index 9f15c119e34..4ff78bc43b6 100644 --- a/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__version_12.snap +++ b/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__version_12.snap @@ -123,6 +123,7 @@ validator_target_reward: 767000000000000 max_transactions_per_checkpoint: 10000 max_checkpoint_size_bytes: 31457280 buffer_stake_for_protocol_upgrade_bps: 5000 +create_auth_info_v1_cost_base: 1000 address_from_bytes_cost_base: 52 address_to_u256_cost_base: 52 address_from_u256_cost_base: 52 diff --git a/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap index a61a405c739..96010de2ba1 100644 --- a/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap +++ b/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap @@ -8,7 +8,7 @@ system_state_version: 1 iota_treasury_cap: inner: id: - id: "0x22213381ee144a2ccb236d0be3e3d29a8ad874c1281f21f982a1206a4e8b328a" + id: "0xf4214d829db511d045607521787595d85706bdbcd8320b543b5fe070021e1c17" total_supply: value: "751500000000000000" validators: @@ -244,13 +244,13 @@ validators: next_epoch_primary_address: ~ extra_fields: id: - id: "0xac974e1561d978091a2a7858e955bfe0ce4b88c8f9be7a5e44b23c5077677529" + id: "0xda95567f97f2b364ae5c86a4768d525674dd69136f6991300e3751fcbdca3750" size: 0 voting_power: 10000 - operation_cap_id: "0x33b2cd7a8edc5746b23da3dd3c5b15aa5aea46ba92db480d3f09a815f41c54d8" + operation_cap_id: "0x954ad0576c2af787fcf81bc2a474d8b11d32cf1845a73cb5a4b0c834450d2f3c" gas_price: 1000 staking_pool: - id: "0xb28906cb23dd9a4d8dbf53a3f1c1d43ccb61f5a47f462bbb9064747566f256c8" + id: "0x0dfae0de9a6c2fc025cb86815f3740cfc334860b7429aad7d7c74c3768b75a3b" activation_epoch: 0 deactivation_epoch: ~ iota_balance: 1500000000000000 @@ -258,14 +258,14 @@ validators: value: 0 pool_token_balance: 1500000000000000 exchange_rates: - id: "0x08074f0d010ba7cdf8d52dc530b34910cc245ce8cbd258538562e570f5b22b3e" + id: "0x9172d63c9b4ee119306afc49602e3ffea6bb8fe5b69ea02e335f2daf4a89c1a8" size: 1 pending_stake: 0 pending_total_iota_withdraw: 0 pending_pool_token_withdraw: 0 extra_fields: id: - id: "0x68023c1d83f479621f772841290bc6df7bda17e230683b5466a81cb1a18662dd" + id: "0x4e16753a53cb95686748edf649eb0b0e19884ca0ec0bf0bed2d2c6a1d4e97b2e" size: 0 commission_rate: 200 next_epoch_stake: 1500000000000000 @@ -273,27 +273,27 @@ validators: next_epoch_commission_rate: 200 extra_fields: id: - id: "0x745ad5df53f343e2eb22285e71d6e4cf0b2e0d8be86efbe476edd8e072123be4" + id: "0x5e5f1c014af296f0d8f2bd104416b5640b6a4341d4b8c1d3f3889eed0e28dd58" size: 0 pending_active_validators: contents: - id: "0x26eba3ed6c5d30b3cd6b0cd1161c6e4590c97d7cbc32645c6919c06eb8c394ce" + id: "0x3970ec6d0bc1c444d01efe49aed24c8ec6eb5ae7fee0a5bcda45d07f6b586c8d" size: 0 pending_removals: [] staking_pool_mappings: - id: "0x20faac9ea67146996707fba719ab2c5aae8ec075dd888d29053481877a6f4e84" + id: "0xa6d855deb11eb75fbdcbc73601a13886c8890585df2a1a8bc7e939fef69c07c5" size: 1 inactive_validators: - id: "0x99877faa15542ba99dc1b4b8a0a07a1e1e9fe02ecce9f34a0a51a735dde0e16d" + id: "0x6827ee47d2f0d30553ee2b42becafc874f630e71610faf7843a36963fe558b5e" size: 0 validator_candidates: - id: "0xdd8b21cf1cd88c6cc60a4222ec7aa51a92267cb652e82b834c0e4d5e0220f72b" + id: "0xb246cb097560a47eb943c5631434b48cf4d217b5d286a05e9f5246b9750d265e" size: 0 at_risk_validators: contents: [] extra_fields: id: - id: "0xad247658fbe6e7107c4c6287c1c2cd666d2c1457ac3676e16d283f595b0dff25" + id: "0x2e6f927c92f3964b8e1a95d420ee740381a07de1152de2eb1fb68c0d01c4c0bf" size: 0 storage_fund: total_object_storage_rebates: @@ -310,7 +310,7 @@ parameters: validator_low_stake_grace_period: 7 extra_fields: id: - id: "0x572a54c652f933b62cdfed0654c5908d1cf2b7d505b301d744ba3a5722f56df6" + id: "0x23d243947fc93e520f882cf65fe3a8843e0ce0065f6f7015394f0b55554f41df" size: 0 iota_system_admin_cap: dummy_field: false @@ -327,5 +327,5 @@ safe_mode_non_refundable_storage_fee: 0 epoch_start_timestamp_ms: 10 extra_fields: id: - id: "0xd2cc7ba5b4f1a957fd5a0560928f21c76813e9d30ff8e415f31e7f7cd83afad3" + id: "0xff4eb2caea72faf5cff5e3df3ed233c7ba7535907f38e73fefb6c9b3920f3fbd" size: 0 diff --git a/crates/iota-types/src/move_package.rs b/crates/iota-types/src/move_package.rs index d50d4f32c99..b0730c5660e 100644 --- a/crates/iota-types/src/move_package.rs +++ b/crates/iota-types/src/move_package.rs @@ -2,6 +2,37 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +//! Move package. +//! +//! This module contains the [MovePackage] and types necessary for describing +//! its update behavior and linkage information for module resolution during +//! execution. +//! +//! Upgradeable packages form a version chain. This is simply the conceptual +//! chain of package versions, with their monotonically increasing version +//! numbers. Package { version: 1 } => Package { version: 2 } => ... +//! +//! The code contains terminology that may be confusing for the uninitiated, +//! like `Module ID`, `Package ID`, `Storage ID` and `Runtime ID`. For avoidance +//! of doubt these concepts are defined like so: +//! - `Package ID` is the [ObjectID] representing the address by which the given +//! package may be found in storage. +//! - `Runtime ID` will always mean the `Package ID`/`Storage ID` of the +//! initially published package. For a non upgradeable package this will +//! always be equal to `Storage ID`. For an upgradeable package, it will be +//! the `Storage ID` of the package's first deployed version. +//! - `Storage ID` is the `Package ID`, and it is mostly used in to highlight +//! that we are talking about the current `Package ID` and not the `Runtime +//! ID` +//! - `Module ID` is the the type +//! [ModuleID](move_core_types::language_storage::ModuleId). +//! +//! Some of these are redundant and have overlapping meaning, so whenever +//! reasonable/necessary the possible naming will be listed. From all of these +//! `Runtime ID` and `Module ID` are the most confusing. `Module ID` may be used +//! with `Runtime ID` and `Storage ID` depending on the context. While `Runtime +//! ID` is mostly used in name resolution during runtime, when a package with +//! its modules has been loaded. use std::collections::{BTreeMap, BTreeSet}; use derive_more::Display; @@ -31,11 +62,6 @@ use crate::{ object::OBJECT_START_VERSION, }; -// TODO: robust MovePackage tests -// #[cfg(test)] -// #[path = "unit_tests/move_package.rs"] -// mod base_types_tests; - pub const PACKAGE_MODULE_NAME: &IdentStr = ident_str!("package"); pub const UPGRADECAP_STRUCT_NAME: &IdentStr = ident_str!("UpgradeCap"); pub const UPGRADETICKET_STRUCT_NAME: &IdentStr = ident_str!("UpgradeTicket"); @@ -59,24 +85,43 @@ pub struct FnInfoKey { /// A map from function info keys to function info pub type FnInfoMap = BTreeMap; -/// Identifies a struct and the module it was defined in +/// Store the origin of a data type where it first appeared in the version +/// chain. +/// +/// A data type is identified by the name of the module and the name of the +/// struct/enum in combination. +/// +/// # Undefined behavior +/// +/// Directly modifying any field is undefined behavior. The fields are only +/// public for read-only access. #[derive( Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, Hash, JsonSchema, )] pub struct TypeOrigin { + /// The name of the module the data type resides in. pub module_name: String, + /// The name of the data type. + /// + /// Here this either refers to an enum or a struct identifier. // `struct_name` alias to support backwards compatibility with the old name #[serde(alias = "struct_name")] pub datatype_name: String, + /// `Storage ID` of the package, where the given type first appeared. pub package: ObjectID, } -/// Upgraded package info for the linkage table +/// Value for the [MovePackage]'s linkage_table. +/// +/// # Undefined behavior +/// +/// Directly modifying any field is undefined behavior. The fields are only +/// public for read-only access. #[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash, JsonSchema)] pub struct UpgradeInfo { - /// ID of the upgraded packages + /// `Storage ID`/`Package ID` of the referred package. pub upgraded_id: ObjectID, - /// Version of the upgraded package + /// The version of the package at `upgraded_id`. pub upgraded_version: SequenceNumber, } @@ -85,6 +130,7 @@ pub struct UpgradeInfo { #[serde_as] #[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)] pub struct MovePackage { + /// The `Storage ID` of the package. pub(crate) id: ObjectID, /// Most move packages are uniquely identified by their ID (i.e. there is /// only one version per ID), but the version is still stored because @@ -98,16 +144,23 @@ pub struct MovePackage { /// In all cases, packages are referred to by move calls using just their /// ID, and they are always loaded at their latest version. pub(crate) version: SequenceNumber, - // TODO use session cache + /// Map module identifiers to their serialized [CompiledModule]. + /// + /// All modules within a package share the `Storage ID` of their containing + /// package. #[serde_as(as = "BTreeMap<_, Bytes>")] pub(crate) module_map: BTreeMap>, - /// Maps struct/module to a package version where it was first defined, - /// stored as a vector for simple serialization and deserialization. + /// Maps structs and enums in a given module to a package version where they + /// were first defined. + /// + /// Stored as a vector for simple serialization and + /// deserialization. pub(crate) type_origin_table: Vec, - // For each dependency, maps original package ID to the info about the (upgraded) dependency - // version that this package is using + /// For each dependency, it maps the `Runtime ID` (the first package's + /// `Storage ID` in a version chain) of the containing package to the + /// `UpgradeInfo` containing the actually used version. pub(crate) linkage_table: BTreeMap, } @@ -177,6 +230,9 @@ pub struct UpgradeReceipt { impl MovePackage { /// Create a package with all required data (including serialized modules, /// type origin and linkage tables) already supplied. + /// + /// It does not perform any type of validation. Ensure that the supplied + /// parts are semantically valid. pub fn new( id: ObjectID, version: SequenceNumber, @@ -203,6 +259,7 @@ impl MovePackage { Ok(pkg) } + /// Calculate the digest of the [MovePackage]. pub fn digest(&self) -> [u8; 32] { Self::compute_digest_for_modules_and_deps( self.module_map.values(), @@ -242,6 +299,11 @@ impl MovePackage { /// Create an initial version of the package along with this version's type /// origin and linkage tables. + /// + /// # Undefined behavior + /// + /// All passed modules must have the same `Runtime ID` or the behavior is + /// undefined. pub fn new_initial<'p>( modules: &[CompiledModule], protocol_config: &ProtocolConfig, @@ -266,6 +328,11 @@ impl MovePackage { /// Create an upgraded version of the package along with this version's type /// origin and linkage tables. + /// + /// # Undefined behavior + /// + /// All passed modules must have the same `Runtime ID` or the behavior is + /// undefined. pub fn new_upgraded<'p>( &self, storage_id: ObjectID, @@ -390,11 +457,11 @@ impl MovePackage { ) } - // Retrieve the module with `ModuleId` in the given package. - // The module must be the `storage_id` or the call will return `None`. - // Check if the address of the module is the same of the package - // and return `None` if that is not the case. - // All modules in a package share the address with the package. + /// Retrieve the module from this package with the given [ModuleId]. + /// + /// [ModuleId] is expected to contain the `Storage ID` of this package. + /// In case the `Storage ID` doesn't match or the module name is not + /// present in this package the function returns None. pub fn get_module(&self, storage_id: &ModuleId) -> Option<&Vec> { if self.id != ObjectID::from(*storage_id.address()) { None @@ -432,6 +499,7 @@ impl MovePackage { 8 /* SequenceNumber */ + module_map_size + type_origin_table_size + linkage_table_size } + /// `Package ID`/`Storage ID` of this package. pub fn id(&self) -> ObjectID { self.id } @@ -478,9 +546,13 @@ impl MovePackage { &self.linkage_table } - /// The ObjectID that this package's modules believe they are from, at - /// runtime (can differ from `MovePackage::id()` in the case of package - /// upgrades). + /// The `Package ID` of the first version of this package. + /// + /// Also referred to as `Runtime ID`. + /// + /// Regardless of which version of the package we are working with, this + /// function will always return the `Package ID`/`Storage ID` of the first + /// package version in the version chain. pub fn original_package_id(&self) -> ObjectID { if self.version == OBJECT_START_VERSION { // for a non-upgraded package, original ID is just the package ID @@ -488,6 +560,9 @@ impl MovePackage { } let bytes = self.module_map.values().next().expect("Empty module map"); + // Remember, that all modules will contain the `Package ID` of the first + // deployed package. This is why taking any of them will produce the + // original package id. let module = CompiledModule::deserialize_with_defaults(bytes) .expect("A Move package contains a module that cannot be deserialized"); (*module.address()).into() diff --git a/external-crates/move/crates/move-core-types/src/account_address.rs b/external-crates/move/crates/move-core-types/src/account_address.rs index d91b0911344..18f58c2eda9 100644 --- a/external-crates/move/crates/move-core-types/src/account_address.rs +++ b/external-crates/move/crates/move-core-types/src/account_address.rs @@ -11,7 +11,10 @@ use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use crate::gas_algebra::AbstractMemorySize; -/// A struct that represents an account address. +/// Represents an address. +/// +/// Contrary to its name an [AccountAddress] may be used to identify an object or an +/// account, depending on the usage. #[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)] #[cfg_attr(any(test, feature = "fuzzing"), derive(proptest_derive::Arbitrary))] #[cfg_attr(any(test, feature = "fuzzing"), derive(arbitrary::Arbitrary))] diff --git a/external-crates/move/crates/move-core-types/src/language_storage.rs b/external-crates/move/crates/move-core-types/src/language_storage.rs index 9e7c057aa9a..7bb1d31aa92 100644 --- a/external-crates/move/crates/move-core-types/src/language_storage.rs +++ b/external-crates/move/crates/move-core-types/src/language_storage.rs @@ -266,8 +266,13 @@ impl FromStr for StructTag { } } -/// Represents the initial key into global storage where we first index by the -/// address, and then the struct tag +/// The identifier of a module. +/// +/// Identifies a module in the given context. +/// The context here is key. Always pay attention to where and with which +/// other components you may wish to use the [ModuleId] with. +/// It is fully the users responsibility to use the appropriately constructed +/// ModuleId as it provides no validity checks. #[derive(Serialize, Deserialize, Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] #[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))] #[cfg_attr(any(test, feature = "fuzzing"), proptest(no_params))] @@ -283,14 +288,22 @@ impl From for (AccountAddress, Identifier) { } impl ModuleId { + /// Construct a [ModuleId] from an [AccountAddress] and an [Identifier]. + /// + /// The address will either be `Runtime ID` or the `Storage ID` of the given + /// module depending on the context. These are not interchangeable. + /// The name is an identifier referencing a module. pub fn new(address: AccountAddress, name: Identifier) -> Self { ModuleId { address, name } } + /// Return the [Identifier] associated with the [ModuleId] instance. pub fn name(&self) -> &IdentStr { &self.name } + /// Return the `Runtime Id` or `Storage Id` associated with the [ModuleId] + /// instance. pub fn address(&self) -> &AccountAddress { &self.address } diff --git a/external-crates/move/crates/move-vm-runtime/src/native_extensions.rs b/external-crates/move/crates/move-vm-runtime/src/native_extensions.rs index e14104f076a..52f8e530ab0 100644 --- a/external-crates/move/crates/move-vm-runtime/src/native_extensions.rs +++ b/external-crates/move/crates/move-vm-runtime/src/native_extensions.rs @@ -8,8 +8,11 @@ use std::{any::TypeId, collections::HashMap}; use better_any::{Tid, TidAble, TidExt}; /// A data type to represent a heterogeneous collection of extensions which are -/// available to native functions. A value to this is passed into the session -/// function execution. +/// available to native functions. +/// +/// It is intended to provide a point for extensions for native functions +/// which are executed outside of the Move VM. For this it erases type +/// information about the provided types. /// /// The implementation uses the crate `better_any` which implements a version of /// the `Any` type, called `Tid<`a>`, which allows for up to one lifetime diff --git a/iota-execution/latest/iota-adapter/src/adapter.rs b/iota-execution/latest/iota-adapter/src/adapter.rs index 8820737d3fb..d5584f524ca 100644 --- a/iota-execution/latest/iota-adapter/src/adapter.rs +++ b/iota-execution/latest/iota-adapter/src/adapter.rs @@ -8,14 +8,18 @@ mod checked { use std::{collections::BTreeMap, path::PathBuf, sync::Arc}; use anyhow::Result; - use iota_move_natives::{NativesCostTable, object_runtime, object_runtime::ObjectRuntime}; + use iota_move_natives::{ + NativesCostTable, + object_runtime::{self, ObjectRuntime}, + raw_module_loader::RawModuleLoader, + }; use iota_protocol_config::ProtocolConfig; use iota_types::{ base_types::*, error::{ExecutionError, ExecutionErrorKind, IotaError}, execution_config_utils::to_binary_config, metrics::{BytecodeVerifierMetrics, LimitsMetrics}, - storage::ChildObjectResolver, + storage::{BackingPackageStore, ChildObjectResolver}, }; use iota_verifier::{ check_for_verifier_timeout, verifier::iota_verify_module_metered_check_timeout_only, @@ -82,13 +86,15 @@ mod checked { .map_err(|_| IotaError::ExecutionInvariantViolation) } - /// Creates a new set of `NativeContextExtensions` for the Move VM, - /// configuring extensions such as `ObjectRuntime` and + /// Creates a new set of `NativeContextExtensions`, + /// + /// Configuring extensions such as `ObjectRuntime` and /// `NativesCostTable`. These extensions manage object resolution, input /// objects, metering, protocol configuration, and metrics tracking. /// They are available and mainly used in native function implementations /// via `NativeContext` instance. pub fn new_native_extensions<'r>( + package_store: &'r dyn BackingPackageStore, child_resolver: &'r dyn ChildObjectResolver, input_objects: BTreeMap, is_metered: bool, @@ -96,6 +102,8 @@ mod checked { metrics: Arc, current_epoch_id: EpochId, ) -> NativeContextExtensions<'r> { + // When changing the list of configured extensions, make sure you also + // update the one used while executing `move test` command. let mut extensions = NativeContextExtensions::default(); extensions.add(ObjectRuntime::new( child_resolver, @@ -106,6 +114,7 @@ mod checked { current_epoch_id, )); extensions.add(NativesCostTable::from_protocol_config(protocol_config)); + extensions.add(RawModuleLoader::new(package_store)); extensions } diff --git a/iota-execution/latest/iota-adapter/src/execution_value.rs b/iota-execution/latest/iota-adapter/src/execution_value.rs index ca4b6988b68..46070ecda87 100644 --- a/iota-execution/latest/iota-adapter/src/execution_value.rs +++ b/iota-execution/latest/iota-adapter/src/execution_value.rs @@ -34,6 +34,7 @@ where pub trait ExecutionState: StorageView + IotaResolver { fn as_iota_resolver(&self) -> &dyn IotaResolver; fn as_child_resolver(&self) -> &dyn ChildObjectResolver; + fn as_package_store(&self) -> &dyn BackingPackageStore; } impl ExecutionState for T @@ -48,6 +49,10 @@ where fn as_child_resolver(&self) -> &dyn ChildObjectResolver { self } + + fn as_package_store(&self) -> &dyn BackingPackageStore { + self + } } #[derive(Clone, Debug)] diff --git a/iota-execution/latest/iota-adapter/src/programmable_transactions/context.rs b/iota-execution/latest/iota-adapter/src/programmable_transactions/context.rs index eb0a0cf3d16..8dc1d78036c 100644 --- a/iota-execution/latest/iota-adapter/src/programmable_transactions/context.rs +++ b/iota-execution/latest/iota-adapter/src/programmable_transactions/context.rs @@ -192,6 +192,7 @@ mod checked { } }; let native_extensions = new_native_extensions( + state_view.as_backing_package_store(), state_view.as_child_resolver(), input_object_map, !gas_charger.is_unmetered(), diff --git a/iota-execution/latest/iota-move-natives/Cargo.toml b/iota-execution/latest/iota-move-natives/Cargo.toml index ab0096ff099..eca9bbb319e 100644 --- a/iota-execution/latest/iota-move-natives/Cargo.toml +++ b/iota-execution/latest/iota-move-natives/Cargo.toml @@ -22,6 +22,7 @@ tracing.workspace = true # internal dependencies iota-protocol-config.workspace = true iota-types.workspace = true +iota-verifier = { path = "../iota-verifier", package = "iota-verifier-latest" } move-binary-format.workspace = true move-core-types.workspace = true move-stdlib-natives = { path = "../../../external-crates/move/crates/move-stdlib-natives" } diff --git a/iota-execution/latest/iota-move-natives/src/account.rs b/iota-execution/latest/iota-move-natives/src/account.rs new file mode 100644 index 00000000000..02cd22939aa --- /dev/null +++ b/iota-execution/latest/iota-move-natives/src/account.rs @@ -0,0 +1,99 @@ +// Copyright (c) 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::VecDeque; + +use iota_types::{Identifier, base_types::ObjectID}; +use iota_verifier::account_auth_verifier; +use move_binary_format::errors::{PartialVMError, PartialVMResult}; +use move_core_types::{ + account_address::AccountAddress, gas_algebra::InternalGas, vm_status::StatusCode, +}; +use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext}; +use move_vm_types::{ + loaded_data::runtime_types::Type, + natives::function::NativeResult, + pop_arg, + values::{Value, VectorRef}, +}; +use smallvec::smallvec; + +use crate::{NativesCostTable, raw_module_loader::RawModuleLoader}; + +#[derive(Copy, Clone, Debug)] +pub struct CreateAuthInfoV1ImplCostParams { + pub create_auth_info_v1_cost_base: InternalGas, +} + +pub fn create_auth_info_v1_impl( + context: &mut NativeContext, + ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + debug_assert!(ty_args.is_empty()); + debug_assert!(args.len() == 3); + + // charge gas + let account_create_auth_info_v1_impl_params = context + .extensions_mut() + .get::() + .account_create_auth_info_v1_impl_params; + native_charge_gas_early_exit!( + context, + account_create_auth_info_v1_impl_params.create_auth_info_v1_cost_base + ); + + let function_name_bytes = pop_arg!(args, VectorRef); + let function_name = String::from(unsafe { + std::str::from_utf8_unchecked(function_name_bytes.as_bytes_ref().as_slice()) + }); + let function_identifier = Identifier::new(function_name.clone()).unwrap(); + + let module_name_bytes = pop_arg!(args, VectorRef); + let module_name = String::from(unsafe { + std::str::from_utf8_unchecked(module_name_bytes.as_bytes_ref().as_slice()) + }); + let module_identifier = Identifier::new(module_name.clone()).unwrap(); + + let package = pop_arg!(args, AccountAddress); + let package_id = ObjectID::from(package); + + // Loading module for context verifying the referenced `authenticate` function. + // There are two base cases when looking for an `authenticate` function. The + // `authenticate` function is either in the current module (which is not handled + // by this function as the user cannot write such a requirement down at the + // moment) or it must be loaded. Either because it is in a completely + // different package or its for this package, but a different version. + let raw_module_loader = &context.extensions().get::(); + let Some(compiled_module) = raw_module_loader.get_module(&package_id, &module_identifier) + else { + return Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( + format!( + "Referenced module: + {package}::{module_name} unavailable" + ), + ), + ); + }; + + if let Err(execution_error) = + account_auth_verifier::verify_authenticate_func(&compiled_module, function_identifier) + { + return Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(execution_error.to_string()), + ); + } + + let authenticator_info_v1 = Value::struct_(move_vm_types::values::Struct::pack([ + Value::address(package), + Value::vector_u8(module_name.as_bytes().iter().copied()), + Value::vector_u8(function_name.as_bytes().iter().copied()), + ])); + + Ok(NativeResult::ok( + context.gas_used(), + smallvec![authenticator_info_v1], + )) +} diff --git a/iota-execution/latest/iota-move-natives/src/lib.rs b/iota-execution/latest/iota-move-natives/src/lib.rs index 153d36680db..81c8079b9f4 100644 --- a/iota-execution/latest/iota-move-natives/src/lib.rs +++ b/iota-execution/latest/iota-move-natives/src/lib.rs @@ -67,14 +67,16 @@ use self::{ types::TypesIsOneTimeWitnessCostParams, validator::ValidatorValidateMetadataBcsCostParams, }; -use crate::crypto::{ - group_ops, - group_ops::GroupOpsCostParams, - poseidon::PoseidonBN254CostParams, - zklogin, - zklogin::{CheckZkloginIdCostParams, CheckZkloginIssuerCostParams}, +use crate::{ + account::CreateAuthInfoV1ImplCostParams, + crypto::{ + group_ops::{self, GroupOpsCostParams}, + poseidon::PoseidonBN254CostParams, + zklogin::{self, CheckZkloginIdCostParams, CheckZkloginIssuerCostParams}, + }, }; +mod account; mod address; mod config; mod crypto; @@ -83,6 +85,7 @@ mod event; mod object; pub mod object_runtime; mod random; +pub mod raw_module_loader; pub mod test_scenario; mod test_utils; mod transfer; @@ -92,6 +95,8 @@ mod validator; #[derive(Tid)] pub struct NativesCostTable { + // Account natives + pub account_create_auth_info_v1_impl_params: CreateAuthInfoV1ImplCostParams, // Address natives pub address_from_bytes_cost_params: AddressFromBytesCostParams, pub address_to_u256_cost_params: AddressToU256CostParams, @@ -184,6 +189,13 @@ pub struct NativesCostTable { impl NativesCostTable { pub fn from_protocol_config(protocol_config: &ProtocolConfig) -> NativesCostTable { Self { + // account + account_create_auth_info_v1_impl_params: CreateAuthInfoV1ImplCostParams { + create_auth_info_v1_cost_base: protocol_config + .create_auth_info_v1_cost_base() + .into(), + }, + // address address_from_bytes_cost_params: AddressFromBytesCostParams { address_from_bytes_cost_base: protocol_config.address_from_bytes_cost_base().into(), }, @@ -770,6 +782,11 @@ pub fn make_stdlib_gas_params_for_protocol_config( pub fn all_natives(silent: bool, protocol_config: &ProtocolConfig) -> NativeFunctionTable { let iota_framework_natives: &[(&str, &str, NativeFunction)] = &[ + ( + "account", + "create_auth_info_v1_impl", + make_native!(account::create_auth_info_v1_impl), + ), ("address", "from_bytes", make_native!(address::from_bytes)), ("address", "to_u256", make_native!(address::to_u256)), ("address", "from_u256", make_native!(address::from_u256)), diff --git a/iota-execution/latest/iota-move-natives/src/raw_module_loader/mod.rs b/iota-execution/latest/iota-move-natives/src/raw_module_loader/mod.rs new file mode 100644 index 00000000000..ff9e316b2ec --- /dev/null +++ b/iota-execution/latest/iota-move-natives/src/raw_module_loader/mod.rs @@ -0,0 +1,52 @@ +// Copyright (c) 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use better_any::{Tid, TidAble}; +use iota_types::{base_types::ObjectID, storage::BackingPackageStore}; +use move_binary_format::CompiledModule; +use move_core_types::identifier::IdentStr; + +/// A raw module loader extension for native functions. +/// +/// This extension enables native functions the loading of +/// packages and modules, without any linkage or other restrictions. +/// +/// For the time being it does not provide caching of any kind. In the current +/// use case of `move authentication` it only needs to load a module +/// only once for each use. +#[derive(Tid)] +pub struct RawModuleLoader<'package_store> { + package_store: &'package_store dyn BackingPackageStore, +} + +impl<'package_store> RawModuleLoader<'package_store> { + pub fn new(package_store: &'package_store dyn BackingPackageStore) -> Self { + RawModuleLoader { package_store } + } + + /// Attempt to load the [CompiledModule] from global storage. + /// + /// It requires the `Storage ID` of the given package and the name of the + /// module associated with the package. + /// + /// On success the [CompiledModule] is returned otherwise None. + pub fn get_module( + &self, + package_id: &ObjectID, + module_name: &IdentStr, + ) -> Option { + // Errors are not propagated as in this scenario only the DB can fail, in which + // case there is absolutely nothing that we can do. + let Ok(package_object) = self.package_store.get_package_object(package_id) else { + return None; + }; + let module_bytes = package_object.and_then(|package| { + package + .move_package() + .serialized_module_map() + .get(module_name.as_str()) + .cloned() + })?; + CompiledModule::deserialize_with_defaults(&module_bytes).ok() + } +} diff --git a/iota-execution/latest/iota-move-natives/src/test_scenario.rs b/iota-execution/latest/iota-move-natives/src/test_scenario.rs index 892a7a68f30..87ceaef075f 100644 --- a/iota-execution/latest/iota-move-natives/src/test_scenario.rs +++ b/iota-execution/latest/iota-move-natives/src/test_scenario.rs @@ -21,7 +21,7 @@ use iota_types::{ id::UID, in_memory_storage::InMemoryStorage, object::{MoveObject, Object, Owner}, - storage::ChildObjectResolver, + storage::{BackingPackageStore, ChildObjectResolver}, }; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ @@ -89,6 +89,16 @@ impl ChildObjectResolver for InMemoryTestStore { } } +impl BackingPackageStore for InMemoryTestStore { + fn get_package_object( + &self, + package_id: &ObjectID, + ) -> iota_types::error::IotaResult> { + self.0 + .with_borrow(|store| store.get_package_object(package_id)) + } +} + // This function updates the inventories based on the transfers and deletes that // occurred in the transaction // native fun end_transaction(): TransactionResult; From 9fb710b1cd01126b7c70121c69e1c0a14bfa7f55 Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Fri, 5 Sep 2025 13:09:32 +0200 Subject: [PATCH 04/10] fix(iota-execution): Make CreateAuthInfoV1ImplCostParams with Optional gas Otherwise we would need a new cut, which we don't want to have. --- ...000000000000000000000000000000000000000002 | Bin 74675 -> 75076 bytes crates/iota-framework-snapshot/manifest.json | 2 +- .../latest/iota-move-natives/src/account.rs | 15 ++++++++++----- .../latest/iota-move-natives/src/lib.rs | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/iota-framework-snapshot/bytecode_snapshot/12/0x0000000000000000000000000000000000000000000000000000000000000002 b/crates/iota-framework-snapshot/bytecode_snapshot/12/0x0000000000000000000000000000000000000000000000000000000000000002 index b81bfe87061f368a26466127b9c6491c8e9315d2..84bfac151ef450fc8f57fccaacaaa287c8b973a5 100644 GIT binary patch delta 382 zcmZvYu}Z^G6o$_^H@Ue@Qn5`z5r;Y`ICgbvP@!4c!RQ)p)3m0xNoj8d7d!X>3eFvT z1gYSvAP8AI?Ag|Kinnc{6URxOcx*H5PsM5*NO)D;A$c$DCcW zyUj0$13&?R6L(p;V(x5M^*M?Utn)do*$?EpPqYmT95ew8K(v6!0^ooE7U?CB0U3q~ z%oHRDg$8bu9zd?O2G5_W(;K%MbT}H6I^%rw=e5|KX0b~1EXw3CafW$kI!Jz*=k0!C z$I2SX_)PYa+jpbhu&Lq&tY0mI(@smKzlnST^rx)#TXh$Wq6+`2{aGYjcap_7)Mw0|EdJvke0P diff --git a/crates/iota-framework-snapshot/manifest.json b/crates/iota-framework-snapshot/manifest.json index aa902d2f8af..855284134a2 100644 --- a/crates/iota-framework-snapshot/manifest.json +++ b/crates/iota-framework-snapshot/manifest.json @@ -315,7 +315,7 @@ ] }, "12": { - "git_revision": "0dfba5a1f03f173cb36f9cd846c8d3e125b2d73b", + "git_revision": "b1ff3fc0487e0266064e1e4991932a7a719af6e0", "packages": [ { "name": "MoveStdlib", diff --git a/iota-execution/latest/iota-move-natives/src/account.rs b/iota-execution/latest/iota-move-natives/src/account.rs index 02cd22939aa..2d9dd14fe98 100644 --- a/iota-execution/latest/iota-move-natives/src/account.rs +++ b/iota-execution/latest/iota-move-natives/src/account.rs @@ -22,7 +22,7 @@ use crate::{NativesCostTable, raw_module_loader::RawModuleLoader}; #[derive(Copy, Clone, Debug)] pub struct CreateAuthInfoV1ImplCostParams { - pub create_auth_info_v1_cost_base: InternalGas, + pub create_auth_info_v1_cost_base: Option, } pub fn create_auth_info_v1_impl( @@ -38,10 +38,15 @@ pub fn create_auth_info_v1_impl( .extensions_mut() .get::() .account_create_auth_info_v1_impl_params; - native_charge_gas_early_exit!( - context, - account_create_auth_info_v1_impl_params.create_auth_info_v1_cost_base - ); + + let create_auth_info_v1_cost_base = account_create_auth_info_v1_impl_params + .create_auth_info_v1_cost_base + .ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message("gas cost is not set".to_string()) + })?; + + native_charge_gas_early_exit!(context, create_auth_info_v1_cost_base); let function_name_bytes = pop_arg!(args, VectorRef); let function_name = String::from(unsafe { diff --git a/iota-execution/latest/iota-move-natives/src/lib.rs b/iota-execution/latest/iota-move-natives/src/lib.rs index 81c8079b9f4..a929ca78529 100644 --- a/iota-execution/latest/iota-move-natives/src/lib.rs +++ b/iota-execution/latest/iota-move-natives/src/lib.rs @@ -192,8 +192,8 @@ impl NativesCostTable { // account account_create_auth_info_v1_impl_params: CreateAuthInfoV1ImplCostParams { create_auth_info_v1_cost_base: protocol_config - .create_auth_info_v1_cost_base() - .into(), + .create_auth_info_v1_cost_base_as_option() + .map(Into::into), }, // address address_from_bytes_cost_params: AddressFromBytesCostParams { From 3b36b75e3468a52166b55fd70b2ce932133bb01b Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Fri, 5 Sep 2025 15:56:34 +0200 Subject: [PATCH 05/10] fix(iota-json-rpc-tests): Add new framework module names to the test --- crates/iota-json-rpc-tests/tests/move_utils.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/iota-json-rpc-tests/tests/move_utils.rs b/crates/iota-json-rpc-tests/tests/move_utils.rs index a80d844525c..9fdee3d38fc 100644 --- a/crates/iota-json-rpc-tests/tests/move_utils.rs +++ b/crates/iota-json-rpc-tests/tests/move_utils.rs @@ -24,8 +24,10 @@ async fn get_normalized_move_modules_by_package() -> Result<(), anyhow::Error> { assert_eq!( move_modules.keys().cloned().collect::>(), [ + "account", "address", "authenticator_state", + "auth_context", "bag", "balance", "bcs", @@ -61,6 +63,7 @@ async fn get_normalized_move_modules_by_package() -> Result<(), anyhow::Error> { "pay", "poseidon", "priority_queue", + "programmable_transaction", "prover", "random", "system_admin_cap", From 59f6f359f33254767655805ebd6a02c6bcc1e749 Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Fri, 5 Sep 2025 16:01:13 +0200 Subject: [PATCH 06/10] fix(iota-swarm-config): Update populated_genesis_snapshot_matches test snap --- ..._populated_genesis_snapshot_matches-2.snap | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap index 96010de2ba1..f085302ee52 100644 --- a/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap +++ b/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap @@ -8,7 +8,7 @@ system_state_version: 1 iota_treasury_cap: inner: id: - id: "0xf4214d829db511d045607521787595d85706bdbcd8320b543b5fe070021e1c17" + id: "0x544b752481d04de2375494fbeb3fba05d65b1b1d7c7f64c807bc4f09c7a2ae8b" total_supply: value: "751500000000000000" validators: @@ -244,13 +244,13 @@ validators: next_epoch_primary_address: ~ extra_fields: id: - id: "0xda95567f97f2b364ae5c86a4768d525674dd69136f6991300e3751fcbdca3750" + id: "0x31a8a9499ad38f367d8aa135f59e5e3bc7908b48f4b09a2a700e85a6437360d2" size: 0 voting_power: 10000 - operation_cap_id: "0x954ad0576c2af787fcf81bc2a474d8b11d32cf1845a73cb5a4b0c834450d2f3c" + operation_cap_id: "0x615ad4d975d5c3987fad4827b53938577300d55e755aff554a3d2218c2f15b67" gas_price: 1000 staking_pool: - id: "0x0dfae0de9a6c2fc025cb86815f3740cfc334860b7429aad7d7c74c3768b75a3b" + id: "0x11708761a65d1453bb91e5a6cebb104f1eb836acac8b53ca315d62b028c715cb" activation_epoch: 0 deactivation_epoch: ~ iota_balance: 1500000000000000 @@ -258,14 +258,14 @@ validators: value: 0 pool_token_balance: 1500000000000000 exchange_rates: - id: "0x9172d63c9b4ee119306afc49602e3ffea6bb8fe5b69ea02e335f2daf4a89c1a8" + id: "0xbd3a970f84e25eefdfbb3ea83822ceff3a553d9c8ffa056faaee9109a14b3155" size: 1 pending_stake: 0 pending_total_iota_withdraw: 0 pending_pool_token_withdraw: 0 extra_fields: id: - id: "0x4e16753a53cb95686748edf649eb0b0e19884ca0ec0bf0bed2d2c6a1d4e97b2e" + id: "0x9dcd2ea6335216ca843a11d9241da3bc97e73f113557af9753a7c02d44dd5160" size: 0 commission_rate: 200 next_epoch_stake: 1500000000000000 @@ -273,27 +273,27 @@ validators: next_epoch_commission_rate: 200 extra_fields: id: - id: "0x5e5f1c014af296f0d8f2bd104416b5640b6a4341d4b8c1d3f3889eed0e28dd58" + id: "0x0306e439637de6662a136eef1b193f20713f7671034c8b0b09391b33fb0378c6" size: 0 pending_active_validators: contents: - id: "0x3970ec6d0bc1c444d01efe49aed24c8ec6eb5ae7fee0a5bcda45d07f6b586c8d" + id: "0xa1f645de7bcf0e0a010d2d380e526f1f0b83e71f42982aa2ad97e23a606f506a" size: 0 pending_removals: [] staking_pool_mappings: - id: "0xa6d855deb11eb75fbdcbc73601a13886c8890585df2a1a8bc7e939fef69c07c5" + id: "0xe9d42ea7ffb282d41034e8861dcd1ef0498283c50b6283d7174fe2df72e349cf" size: 1 inactive_validators: - id: "0x6827ee47d2f0d30553ee2b42becafc874f630e71610faf7843a36963fe558b5e" + id: "0x431ad03eda9078c871f910581bd77070247696e8b50b8ffc2d61bc5caf76add6" size: 0 validator_candidates: - id: "0xb246cb097560a47eb943c5631434b48cf4d217b5d286a05e9f5246b9750d265e" + id: "0x84098c0cd1fb6f9e02e3eae19208452757e6cf711cc392d1226db50588094c2d" size: 0 at_risk_validators: contents: [] extra_fields: id: - id: "0x2e6f927c92f3964b8e1a95d420ee740381a07de1152de2eb1fb68c0d01c4c0bf" + id: "0xc22f08d85de79c885f0b98eadc938ffac430f43dcf3aae0250a8e137b975749b" size: 0 storage_fund: total_object_storage_rebates: @@ -310,7 +310,7 @@ parameters: validator_low_stake_grace_period: 7 extra_fields: id: - id: "0x23d243947fc93e520f882cf65fe3a8843e0ce0065f6f7015394f0b55554f41df" + id: "0x26c96d28975be56554c5db47f0da0dbc50bcee345ba4fa4f3db0097b32a13e04" size: 0 iota_system_admin_cap: dummy_field: false @@ -327,5 +327,5 @@ safe_mode_non_refundable_storage_fee: 0 epoch_start_timestamp_ms: 10 extra_fields: id: - id: "0xff4eb2caea72faf5cff5e3df3ed233c7ba7535907f38e73fefb6c9b3920f3fbd" + id: "0x574fd9492b89a02071c9584c584c177c359b359dbc5546f2304f5d57bbb911cf" size: 0 From 0ad82a71410761fc1e214ecbb2932312df0354c9 Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Mon, 8 Sep 2025 13:44:59 +0200 Subject: [PATCH 07/10] Fix typos and remove confusing doc section in create_auth_info_v1 --- .../iota-framework/sources/account.move | 9 +++----- crates/iota-move/src/unit_test.rs | 2 +- crates/iota-protocol-config/src/lib.rs | 3 +-- crates/iota-types/src/account.rs | 5 +---- crates/iota-types/src/auth_context.rs | 6 ++--- crates/iota-types/src/base_types.rs | 2 +- .../latest/iota-adapter/src/adapter.rs | 2 +- .../src/account_auth_verifier.rs | 22 +++++++------------ 8 files changed, 19 insertions(+), 32 deletions(-) diff --git a/crates/iota-framework/packages/iota-framework/sources/account.move b/crates/iota-framework/packages/iota-framework/sources/account.move index 08a6afecb9a..748bc341b19 100644 --- a/crates/iota-framework/packages/iota-framework/sources/account.move +++ b/crates/iota-framework/packages/iota-framework/sources/account.move @@ -1,3 +1,6 @@ +// Copyright (c) 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + module iota::account; use std::ascii; @@ -25,12 +28,6 @@ public struct AuthenticatorInfoV1 has store { /// function defined in package B version 2. /// Refiring to an `authenticate` function with `create_auth_info_v1` is a strictly runtime dependency and /// it does not collide with any compile time restrictions. -/// -/// The only scenario which cannot be handled by this function is that of referring to an `authenticate` -/// function from the current version of the package. Simply because the current package address won't be know -/// before publishing, thus the user cannot specify it in code. -/// If an `authenticate` function should be used from the current version of the package please use -/// `create_auth_info_self_v1` instead. public fun create_auth_info_v1( package: address, module_name: ascii::String, diff --git a/crates/iota-move/src/unit_test.rs b/crates/iota-move/src/unit_test.rs index 768bfb9ebe3..4a234dc01c9 100644 --- a/crates/iota-move/src/unit_test.rs +++ b/crates/iota-move/src/unit_test.rs @@ -125,7 +125,7 @@ fn new_testing_object_and_natives_cost_runtime(ext: &mut NativeContextExtensions let metrics = Arc::new(LimitsMetrics::new(®istry)); let store = Lazy::force(&TEST_STORE); - // If you this list needs to be updated you likely need to update + // If this list needs to be updated you likely need to update // iota-execution/latest/iota-adapter/src/adapter.rs where it is constructed for // regular execution as well. ext.add(ObjectRuntime::new( diff --git a/crates/iota-protocol-config/src/lib.rs b/crates/iota-protocol-config/src/lib.rs index ff3c1deb873..b568adc911d 100644 --- a/crates/iota-protocol-config/src/lib.rs +++ b/crates/iota-protocol-config/src/lib.rs @@ -2150,8 +2150,7 @@ impl ProtocolConfig { cfg.feature_flags.move_auth = true; // === Native Function Costs === // `account` module - cfg.create_auth_info_v1_cost_base = Some(1000); /* TODO how do you decide on a good gas * - price? */ + cfg.create_auth_info_v1_cost_base = Some(1000); } } // Use this template when making changes: diff --git a/crates/iota-types/src/account.rs b/crates/iota-types/src/account.rs index ea258387ecf..1cd10876081 100644 --- a/crates/iota-types/src/account.rs +++ b/crates/iota-types/src/account.rs @@ -11,9 +11,6 @@ use crate::{ object::{Data, Object}, }; -// Temporary created structures. -// This part will be removed once the real types are implemented. - pub const AUTHENTICATOR_DF_NAME: &str = "IOTA_AUTHENTICATION"; pub const AUTHENTICATOR_INFO_MODULE_NAME: &IdentStr = ident_str!("account"); @@ -42,7 +39,7 @@ impl AuthenticatorInfoV1 { }) } - pub fn is_authenticator_info(tag: &StructTag) -> bool { + pub fn is_authenticator_info_v1(tag: &StructTag) -> bool { tag.address == IOTA_FRAMEWORK_ADDRESS && tag.module.as_ident_str() == AUTHENTICATOR_INFO_MODULE_NAME && tag.name.as_ident_str() == AUTHENTICATOR_INFO_STRUCT_NAME diff --git a/crates/iota-types/src/auth_context.rs b/crates/iota-types/src/auth_context.rs index 5538eeb3380..f994562ea25 100644 --- a/crates/iota-types/src/auth_context.rs +++ b/crates/iota-types/src/auth_context.rs @@ -78,7 +78,7 @@ impl AuthContext { bcs::to_bytes(&self).unwrap() } - /// Returns whether the type signature is &mut TxContext, &TxContext, or + /// Returns whether the type signature is &mut AuthContext, &AuthContext, or /// none of the above. pub fn kind(module: &CompiledModule, token: &SignatureToken) -> AuthContextKind { use SignatureToken as S; @@ -95,11 +95,11 @@ impl AuthContext { let (module_addr, module_name, struct_name) = resolve_struct(module, *idx); - let is_tx_context_type = module_name == AUTH_CONTEXT_MODULE_NAME + let is_auth_context_type = module_name == AUTH_CONTEXT_MODULE_NAME && module_addr == &IOTA_FRAMEWORK_ADDRESS && struct_name == AUTH_CONTEXT_STRUCT_NAME; - if is_tx_context_type { + if is_auth_context_type { kind } else { AuthContextKind::None diff --git a/crates/iota-types/src/base_types.rs b/crates/iota-types/src/base_types.rs index 2ac4fe85349..8e0af187a78 100644 --- a/crates/iota-types/src/base_types.rs +++ b/crates/iota-types/src/base_types.rs @@ -426,7 +426,7 @@ impl MoveObjectType { MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => { false } - MoveObjectType_::Other(s) => AuthenticatorInfoV1::is_authenticator_info(s), + MoveObjectType_::Other(s) => AuthenticatorInfoV1::is_authenticator_info_v1(s), } } diff --git a/iota-execution/latest/iota-adapter/src/adapter.rs b/iota-execution/latest/iota-adapter/src/adapter.rs index d5584f524ca..8946685cbdd 100644 --- a/iota-execution/latest/iota-adapter/src/adapter.rs +++ b/iota-execution/latest/iota-adapter/src/adapter.rs @@ -86,7 +86,7 @@ mod checked { .map_err(|_| IotaError::ExecutionInvariantViolation) } - /// Creates a new set of `NativeContextExtensions`, + /// Creates a new set of `NativeContextExtensions`. /// /// Configuring extensions such as `ObjectRuntime` and /// `NativesCostTable`. These extensions manage object resolution, input diff --git a/iota-execution/latest/iota-verifier/src/account_auth_verifier.rs b/iota-execution/latest/iota-verifier/src/account_auth_verifier.rs index f3f1bb339ea..b0540873549 100644 --- a/iota-execution/latest/iota-verifier/src/account_auth_verifier.rs +++ b/iota-execution/latest/iota-verifier/src/account_auth_verifier.rs @@ -34,7 +34,7 @@ use crate::verification_failure; /// - has no return type /// - the last two arguments in order are AuthContext and TxContext /// - AuthContext has to be an immutable reference -/// - TxContext hat to be an immutable reference +/// - TxContext has to be an immutable reference pub fn verify_authenticate_func( module: &CompiledModule, function_identifier: Identifier, @@ -93,7 +93,7 @@ pub fn verify_authenticate_func( let auth_context = &function_signature.0[function_signature.len() - 2]; let tx_context = &function_signature.0[function_signature.len() - 1]; - // AuthContext could potentially passed as value, but that opens up the + // AuthContext could potentially be passed as value, but that opens up the // possibility for the `authenticate` function to receive it as mutable // value, from which it could mutate before passing it to further `authenticate` // functions, so similarly to TxContext, it is simply not allowed. @@ -198,8 +198,7 @@ fn verify_pure_input_type( Ok(()) } else { Err(format!( - "Invalid pure type. A datatype must be a string or an ID, offending argument: {:?}", - param + "Invalid pure type. A datatype must be a string or an ID, offending argument: {param:?}" )) } } @@ -210,32 +209,27 @@ fn verify_pure_input_type( verify_pure_input_type(module, function_type_args, &type_args[0]) } else { Err(format!( - "Invalid pure type. A datatype instantiation must be an option of pure types, offending argument: {:?}", - param + "Invalid pure type. A datatype instantiation must be an option of pure types, offending argument: {param:?}" )) } } TypeParameter(idx) => { if function_type_args[*idx as usize].has_key() { Err(format!( - "Invalid pure type. A type parameter cannot have the 'key' ability, offending argument: {:?}", - param + "Invalid pure type. A type parameter cannot have the 'key' ability, offending argument: {param:?}" )) } else { Ok(()) } } Signer => Err(format!( - "Invalid pure type. Signer is not a pure type, offending argument: {:?}", - param + "Invalid pure type. Signer is not a pure type, offending argument: {param:?}" )), Reference(_) => Err(format!( - "Invalid pure type. Reference is not a pure type, offending argument: {:?}", - param + "Invalid pure type. Reference is not a pure type, offending argument: {param:?}" )), MutableReference(_) => Err(format!( - "Invalid pure type. MutableReference is not a pure type, offending argument: {:?}", - param + "Invalid pure type. MutableReference is not a pure type, offending argument: {param:?}" )), } } From 8b5720953dac77448f5eec20295d7e979bd522e7 Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Tue, 9 Sep 2025 09:19:38 +0200 Subject: [PATCH 08/10] Add copy, drop to AuthenticatorInfoV1 With this the drop function was removed as well. --- .../iota-framework/sources/account.move | 6 +----- .../packages_compiled/iota-framework | Bin 75011 -> 74974 bytes crates/iota-framework/published_api.txt | 3 --- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/iota-framework/packages/iota-framework/sources/account.move b/crates/iota-framework/packages/iota-framework/sources/account.move index 748bc341b19..c4e3fc94cef 100644 --- a/crates/iota-framework/packages/iota-framework/sources/account.move +++ b/crates/iota-framework/packages/iota-framework/sources/account.move @@ -11,7 +11,7 @@ use std::ascii; const AUTHENTICATOR_ID: vector = b"IOTA_AUTHENTICATION"; #[allow(unused_field)] -public struct AuthenticatorInfoV1 has store { +public struct AuthenticatorInfoV1 has copy, drop, store { package: ID, module_name: ascii::String, function_name: ascii::String, @@ -41,7 +41,3 @@ native fun create_auth_info_v1_impl( module_name: &vector, function_name: &vector, ): AuthenticatorInfoV1; - -public fun drop_auth_info_v1(auth_info: AuthenticatorInfoV1) { - let AuthenticatorInfoV1 { .. } = auth_info; -} diff --git a/crates/iota-framework/packages_compiled/iota-framework b/crates/iota-framework/packages_compiled/iota-framework index 7163b228388ca7d9f1bbb0df57e4f4a9c3acbe46..7cd04fd6e701ecb9ce9ffd479eb301787b72ab56 100644 GIT binary patch delta 130 zcmWm3u?fOJ00q$h|0Q>qT!>y02#AQzVPoYY*~Ea2Woki7JF#&CL0fwV&?VIJ@%l&d zx+mvnwSMb3{R9gjBdly;s!_U_xaE%i#+BWw&G#yblUf+$OaTYdL2`fve-?*Xy%{cb cKM$Unm-qUIaRDWxN<~GA5c;mUb`1~XFZr4i4gdfE delta 168 zcmcb2lBM|+%ZAHB-u=uAWnOc$F)%Q2GBU6+vGFhqiLk0ku{+FVrp26iSO!NJHpSyH%3NHC=+zaT!bv?L=w zGcPSazRYm)Y~f}-9u7t>4kk_xCXfbZc5VhfMnPsiCP8LFMxbsc29OcVY}^dOK(iT{ On(aik+leqP5C8xYei|eI diff --git a/crates/iota-framework/published_api.txt b/crates/iota-framework/published_api.txt index 26d39433115..2c389b08879 100644 --- a/crates/iota-framework/published_api.txt +++ b/crates/iota-framework/published_api.txt @@ -1273,9 +1273,6 @@ create_auth_info_v1 create_auth_info_v1_impl fun 0x2::account -drop_auth_info_v1 - public fun - 0x2::account ProgrammableMoveCall public struct 0x2::programmable_transaction From 82eda5b3d873bce4c9a21999a01af2be58f63c60 Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Tue, 9 Sep 2025 09:48:41 +0200 Subject: [PATCH 09/10] Rename the authenticateor info constant to include the version number --- ...000000000000000000000000000000000000000002 | Bin 75076 -> 75039 bytes crates/iota-framework-snapshot/manifest.json | 2 +- crates/iota-types/src/account.rs | 6 +++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/iota-framework-snapshot/bytecode_snapshot/12/0x0000000000000000000000000000000000000000000000000000000000000002 b/crates/iota-framework-snapshot/bytecode_snapshot/12/0x0000000000000000000000000000000000000000000000000000000000000002 index 84bfac151ef450fc8f57fccaacaaa287c8b973a5..39860551d81a8c4d5db0f3b80ec79fd3bdeab3cc 100644 GIT binary patch delta 130 zcmWm3I|{-;00hvPNwT}iF424-AQpNKL99HO3u!DJ5@A)7Vt1I!$Z?m^fo(36IM)^? z9-aeCstn8w+>ERY3@i+cjO+|dOzaE{Yz&MH4D3umf`gHHvZZj7kYGwtenEU bool { tag.address == IOTA_FRAMEWORK_ADDRESS && tag.module.as_ident_str() == AUTHENTICATOR_INFO_MODULE_NAME - && tag.name.as_ident_str() == AUTHENTICATOR_INFO_STRUCT_NAME + && tag.name.as_ident_str() == AUTHENTICATOR_INFO_V1_STRUCT_NAME } } From 458aa7183715868039c2d09b1294a41d60951b66 Mon Sep 17 00:00:00 2001 From: Istvan Davorin Acs Date: Tue, 9 Sep 2025 12:46:08 +0200 Subject: [PATCH 10/10] Fix CI pipeline --- crates/iota-framework-snapshot/manifest.json | 2 +- ..._populated_genesis_snapshot_matches-2.snap | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/iota-framework-snapshot/manifest.json b/crates/iota-framework-snapshot/manifest.json index 46f7d0187dd..3b12c9c002d 100644 --- a/crates/iota-framework-snapshot/manifest.json +++ b/crates/iota-framework-snapshot/manifest.json @@ -315,7 +315,7 @@ ] }, "12": { - "git_revision": "2a17efb5abdce284c2fcac42d2b66f3b64c25faf", + "git_revision": "6ac6db501d0baf15a3528ef82af63be25256333e", "packages": [ { "name": "MoveStdlib", diff --git a/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap index f085302ee52..f8fe1bb4150 100644 --- a/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap +++ b/crates/iota-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap @@ -8,7 +8,7 @@ system_state_version: 1 iota_treasury_cap: inner: id: - id: "0x544b752481d04de2375494fbeb3fba05d65b1b1d7c7f64c807bc4f09c7a2ae8b" + id: "0x46781695e9849203cc8fe122246bd36a56b5e7ed5cf3d1a7dbf73b3fdbbc4a08" total_supply: value: "751500000000000000" validators: @@ -244,13 +244,13 @@ validators: next_epoch_primary_address: ~ extra_fields: id: - id: "0x31a8a9499ad38f367d8aa135f59e5e3bc7908b48f4b09a2a700e85a6437360d2" + id: "0x2aa7c1e0fbe49cbc7372703177a10c073597b7580c915577098d8b040e20eb7b" size: 0 voting_power: 10000 - operation_cap_id: "0x615ad4d975d5c3987fad4827b53938577300d55e755aff554a3d2218c2f15b67" + operation_cap_id: "0x23d5db42bc7b0fc9373e2c723301e32ef6e52c16aaba30dbc15c1fbf405e7a8c" gas_price: 1000 staking_pool: - id: "0x11708761a65d1453bb91e5a6cebb104f1eb836acac8b53ca315d62b028c715cb" + id: "0xe4a65901c2ea28ef0693ebfdf8c3ebe2296193a5f1d899f67b2101b0246152f5" activation_epoch: 0 deactivation_epoch: ~ iota_balance: 1500000000000000 @@ -258,14 +258,14 @@ validators: value: 0 pool_token_balance: 1500000000000000 exchange_rates: - id: "0xbd3a970f84e25eefdfbb3ea83822ceff3a553d9c8ffa056faaee9109a14b3155" + id: "0x5afc020eaa1ef02deace359aaf2304382a5a81434f5d7f798db5b8b485e75310" size: 1 pending_stake: 0 pending_total_iota_withdraw: 0 pending_pool_token_withdraw: 0 extra_fields: id: - id: "0x9dcd2ea6335216ca843a11d9241da3bc97e73f113557af9753a7c02d44dd5160" + id: "0xde0c13305094e6ce7651414e2b8039414cff203299da84f61394d6eebaa249c4" size: 0 commission_rate: 200 next_epoch_stake: 1500000000000000 @@ -273,27 +273,27 @@ validators: next_epoch_commission_rate: 200 extra_fields: id: - id: "0x0306e439637de6662a136eef1b193f20713f7671034c8b0b09391b33fb0378c6" + id: "0x3ae93351e6304de8027a86df4e46df29c0eeb8167ec3e3b412d96ea8c0972e53" size: 0 pending_active_validators: contents: - id: "0xa1f645de7bcf0e0a010d2d380e526f1f0b83e71f42982aa2ad97e23a606f506a" + id: "0x5b0584210ae0672ee90ed9e5d74c9211942b11262fbdf7044e06e58179548b9b" size: 0 pending_removals: [] staking_pool_mappings: - id: "0xe9d42ea7ffb282d41034e8861dcd1ef0498283c50b6283d7174fe2df72e349cf" + id: "0x17591563df6e79f881775c76ea220fcb59c3d900b6d745288340f9d25b501a8e" size: 1 inactive_validators: - id: "0x431ad03eda9078c871f910581bd77070247696e8b50b8ffc2d61bc5caf76add6" + id: "0xfbc66435fa6d70256beec41a40d271d32eada88a66aced1d24ce38dc9b699e70" size: 0 validator_candidates: - id: "0x84098c0cd1fb6f9e02e3eae19208452757e6cf711cc392d1226db50588094c2d" + id: "0x6aa1d289535ca32d76eeaaf7bd69a20477abbe6ba10669ba7e46d690392b32fb" size: 0 at_risk_validators: contents: [] extra_fields: id: - id: "0xc22f08d85de79c885f0b98eadc938ffac430f43dcf3aae0250a8e137b975749b" + id: "0x6f5adf68a2074503511017a7ba234ef2ac4646e9a11d0d29eeda7547163f886e" size: 0 storage_fund: total_object_storage_rebates: @@ -310,7 +310,7 @@ parameters: validator_low_stake_grace_period: 7 extra_fields: id: - id: "0x26c96d28975be56554c5db47f0da0dbc50bcee345ba4fa4f3db0097b32a13e04" + id: "0xaf21f3d64a64f5e2b6b51ff56860eb94835a8d28bc2b6fa90feacdef94c0d335" size: 0 iota_system_admin_cap: dummy_field: false @@ -327,5 +327,5 @@ safe_mode_non_refundable_storage_fee: 0 epoch_start_timestamp_ms: 10 extra_fields: id: - id: "0x574fd9492b89a02071c9584c584c177c359b359dbc5546f2304f5d57bbb911cf" + id: "0x9d10f5b958a28b180f92678cc2fd78dd3a2f03dfd6eb773b72af5e8d2fd0d3d6" size: 0